|
|
@@ -19,6 +19,10 @@ import sglue "third-party:sokol/glue"
|
|
|
import slog "third-party:sokol/log"
|
|
|
import stbtt "vendor:stb/truetype"
|
|
|
|
|
|
+Vec2 :: linalg.Vector2f32
|
|
|
+Vec3 :: linalg.Vector3f32
|
|
|
+Vec4 :: linalg.Vector4f32
|
|
|
+
|
|
|
// --- Constants ---
|
|
|
MAX_ENTITIES :: 1024
|
|
|
MAX_BOX :: 32
|
|
|
@@ -132,15 +136,30 @@ Entity_Kind :: enum u8 {
|
|
|
Player,
|
|
|
Enemy,
|
|
|
Cube,
|
|
|
+ Garden,
|
|
|
}
|
|
|
|
|
|
+Entity_Id :: distinct u32
|
|
|
+
|
|
|
Entity :: struct {
|
|
|
- pos: linalg.Vector3f32,
|
|
|
- vel: linalg.Vector3f32,
|
|
|
- color: linalg.Vector3f32,
|
|
|
- kind: Entity_Kind,
|
|
|
- scale: f32,
|
|
|
- box_id: Box_Id,
|
|
|
+ pos: Vec3,
|
|
|
+ vel: Vec3,
|
|
|
+ color: Vec3,
|
|
|
+ kind: Entity_Kind,
|
|
|
+ scale: Vec3,
|
|
|
+ box_id: Box_Id,
|
|
|
+ is_physics: bool,
|
|
|
+}
|
|
|
+
|
|
|
+Garden_State :: enum u8 {
|
|
|
+ Dry, // Сухой
|
|
|
+ Water,
|
|
|
+ Plant, // Растение
|
|
|
+}
|
|
|
+
|
|
|
+Garden :: struct {
|
|
|
+ using _: Entity,
|
|
|
+ state: Garden_State,
|
|
|
}
|
|
|
|
|
|
Camera :: struct {
|
|
|
@@ -170,6 +189,7 @@ App_State :: struct {
|
|
|
mesh_cube: Mesh,
|
|
|
box_sys: #soa[dynamic]Box_System,
|
|
|
entities: #soa[dynamic]Entity,
|
|
|
+ player_id: Entity_Id,
|
|
|
dpi_scale: f32,
|
|
|
camera: Camera,
|
|
|
last_input: Input_State,
|
|
|
@@ -178,6 +198,8 @@ App_State :: struct {
|
|
|
active_hotbar_slot: int,
|
|
|
drag: Drag_State,
|
|
|
sprites: [dynamic]Sprite,
|
|
|
+ debug_cube_mesh: Mesh,
|
|
|
+ debug_line_mesh: Mesh,
|
|
|
}
|
|
|
|
|
|
state: App_State
|
|
|
@@ -455,7 +477,7 @@ ui_slider :: proc(label: string, val: ^f32, min_v, max_v: f32) {
|
|
|
|
|
|
// --- Game Logic ---
|
|
|
|
|
|
-box_system_push :: proc(size: u32) -> Box_Id {
|
|
|
+push_box :: proc(size: u32) -> Box_Id {
|
|
|
box := Box_System {
|
|
|
slots = make(#soa[dynamic]Box_Slot, 0, size),
|
|
|
}
|
|
|
@@ -465,17 +487,32 @@ box_system_push :: proc(size: u32) -> Box_Id {
|
|
|
return Box_Id(id)
|
|
|
}
|
|
|
|
|
|
-spawn_entity :: proc(pos: linalg.Vector3f32, kind: Entity_Kind) {
|
|
|
- e: Entity; e.pos = pos; e.kind = kind; e.scale = 1.0
|
|
|
+push_entity :: proc(
|
|
|
+ kind: Entity_Kind,
|
|
|
+ pos: Vec3 = {0.0, 0.0, 0.0},
|
|
|
+ scale: Vec3 = {1.0, 1.0, 1.0},
|
|
|
+) -> Entity_Id {
|
|
|
+ e := Entity {
|
|
|
+ pos = pos,
|
|
|
+ kind = kind,
|
|
|
+ scale = scale,
|
|
|
+ }
|
|
|
switch kind {
|
|
|
case .Player:
|
|
|
e.color = {0.2, 0.8, 0.2}
|
|
|
+ e.is_physics = true
|
|
|
case .Enemy:
|
|
|
e.color = {0.8, 0.3, 0.3}
|
|
|
+ e.is_physics = true
|
|
|
case .Cube:
|
|
|
e.color = state.selected_color
|
|
|
+ e.is_physics = true
|
|
|
+ case .Garden:
|
|
|
+ e.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0}
|
|
|
}
|
|
|
+ id := len(state.entities)
|
|
|
append_soa(&state.entities, e)
|
|
|
+ return Entity_Id(id)
|
|
|
}
|
|
|
|
|
|
// --- Initialization ---
|
|
|
@@ -487,6 +524,9 @@ init :: proc "c" () {
|
|
|
sg.setup({environment = sglue.environment(), logger = {func = slog.func}})
|
|
|
sgl.setup({logger = {func = slog.func}})
|
|
|
|
|
|
+ state.mesh_cube = make_cube_mesh(state.allocator)
|
|
|
+ state.debug_line_mesh = debug_make_cube_lines(state.allocator)
|
|
|
+
|
|
|
// 1. Сначала инициализируем массивы (включая sprites)
|
|
|
state.mesh_cube = make_cube_mesh(state.allocator)
|
|
|
state.entities = make(#soa[dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
|
|
|
@@ -513,23 +553,35 @@ init :: proc "c" () {
|
|
|
colors = {0 = {load_action = .CLEAR, clear_value = {0.9, 0.9, 0.95, 1}}},
|
|
|
}
|
|
|
|
|
|
- spawn_entity({0, 5, 0}, .Player)
|
|
|
- player_box_id := box_system_push(INVENTORY_SIZE)
|
|
|
- state.entities[0].box_id = player_box_id
|
|
|
+ state.player_id = push_entity(.Player)
|
|
|
+ player_ref := &state.entities[state.player_id]
|
|
|
+ player_ref.box_id = push_box(INVENTORY_SIZE)
|
|
|
|
|
|
- state.box_sys[player_box_id].slots[0] = Box_Slot {
|
|
|
+ state.box_sys[player_ref.box_id].slots[0] = Box_Slot {
|
|
|
kind = .Shovel,
|
|
|
count = 1,
|
|
|
}
|
|
|
- state.box_sys[player_box_id].slots[1] = Box_Slot {
|
|
|
+ state.box_sys[player_ref.box_id].slots[1] = Box_Slot {
|
|
|
kind = .Sword,
|
|
|
count = 1,
|
|
|
}
|
|
|
- state.box_sys[player_box_id].slots[2] = Box_Slot {
|
|
|
+ state.box_sys[player_ref.box_id].slots[2] = Box_Slot {
|
|
|
kind = .Potion,
|
|
|
count = 5,
|
|
|
}
|
|
|
|
|
|
+ // TODO:
|
|
|
+ for x in 0 ..< 10 {
|
|
|
+ for z in 0 ..< 10 {
|
|
|
+ offset := Vec2{0.1 * f32(x), 0.1 * f32(z)}
|
|
|
+ push_entity(
|
|
|
+ .Garden,
|
|
|
+ {0.0 + (1.0 * f32(x)) + offset.x, 0.0, 0.0 + (1.0 * f32(z)) + offset.y},
|
|
|
+ {1.0, 0.1, 1.0},
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
state.camera.dist = 10.0; state.camera.pitch = 0.5; state.selected_color = {0.2, 0.5, 1.0}
|
|
|
}
|
|
|
|
|
|
@@ -565,7 +617,7 @@ frame :: proc "c" () {
|
|
|
ui_window_begin("Stats", 20, 20, 220, 150)
|
|
|
ui_label(fmt.tprintf("FPS: %.0f", 1.0 / dt))
|
|
|
ui_label(fmt.tprintf("Entities: %d", len(state.entities)))
|
|
|
- if ui_button("Spawn Enemy") do spawn_entity({0, 10, 0}, .Enemy)
|
|
|
+ if ui_button("Spawn Enemy") do push_entity(.Enemy, {0, 10, 0})
|
|
|
|
|
|
ui_window_begin("Builder", 20, 200, 250, 180)
|
|
|
ui_slider("Red", &state.selected_color.x, 0, 1)
|
|
|
@@ -579,7 +631,7 @@ frame :: proc "c" () {
|
|
|
{state.selected_color.x, state.selected_color.y, state.selected_color.z, 1},
|
|
|
)
|
|
|
|
|
|
- player_box_id := state.entities[0].box_id
|
|
|
+ player_box_id := state.entities[state.player_id].box_id
|
|
|
draw_hud(w, h)
|
|
|
if state.box_sys[player_box_id].is_open do draw_inventory(player_box_id, w, h)
|
|
|
|
|
|
@@ -686,7 +738,7 @@ draw_inventory :: proc(box_id: Box_Id, screen_w, screen_h: f32) {
|
|
|
}
|
|
|
|
|
|
draw_hud :: proc(screen_w, screen_h: f32) {
|
|
|
- player_box_id := state.entities[0].box_id
|
|
|
+ player_box_id := state.entities[state.player_id].box_id
|
|
|
slots := &state.box_sys[player_box_id].slots
|
|
|
start_slot_idx := INVENTORY_SIZE - INVENTORY_COLS
|
|
|
total_w := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
|
|
|
@@ -759,7 +811,7 @@ draw_hud :: proc(screen_w, screen_h: f32) {
|
|
|
|
|
|
system_input_player :: proc(dt: f32) {
|
|
|
if len(state.entities) == 0 do return
|
|
|
- player := &state.entities[0]
|
|
|
+ player := &state.entities[state.player_id]
|
|
|
SPEED :: 10.0
|
|
|
SENSITIVITY :: 0.15 * 0.02
|
|
|
|
|
|
@@ -770,10 +822,10 @@ system_input_player :: proc(dt: f32) {
|
|
|
}
|
|
|
|
|
|
sin_yaw, cos_yaw := math.sincos(state.camera.yaw)
|
|
|
- forward := linalg.Vector3f32{sin_yaw, 0, cos_yaw}
|
|
|
- right := linalg.Vector3f32{cos_yaw, 0, -sin_yaw}
|
|
|
+ forward := Vec3{sin_yaw, 0, cos_yaw}
|
|
|
+ right := Vec3{cos_yaw, 0, -sin_yaw}
|
|
|
|
|
|
- move_dir := linalg.Vector3f32{0, 0, 0}
|
|
|
+ move_dir := Vec3{0, 0, 0}
|
|
|
if state.input.keys[.W] do move_dir -= forward
|
|
|
if state.input.keys[.S] do move_dir += forward
|
|
|
if state.input.keys[.A] do move_dir -= right
|
|
|
@@ -817,12 +869,12 @@ system_input_player :: proc(dt: f32) {
|
|
|
system_builder :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
if state.input.locked do return
|
|
|
if state.input.mouse_x < 300 do return
|
|
|
- if len(state.entities) > 0 && state.box_sys[state.entities[0].box_id].is_open do return
|
|
|
+ if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do return
|
|
|
|
|
|
if state.input.left_clicked {
|
|
|
origin, dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)
|
|
|
if hit_pos, hit := ray_plane_intersect(origin, dir, 0.5); hit {
|
|
|
- spawn_entity(hit_pos, .Cube)
|
|
|
+ push_entity(.Cube, hit_pos)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -832,9 +884,11 @@ system_physics :: proc(dt: f32) {
|
|
|
FLOOR_Y :: 0.5
|
|
|
for i in 0 ..< len(state.entities) {
|
|
|
e := &state.entities[i]
|
|
|
- if e.kind == .Cube do continue
|
|
|
+ if !e.is_physics do continue
|
|
|
+
|
|
|
e.vel.y -= GRAVITY * dt
|
|
|
e.pos += e.vel * dt
|
|
|
+
|
|
|
if e.pos.y < FLOOR_Y {
|
|
|
e.pos.y = FLOOR_Y
|
|
|
if e.kind ==
|
|
|
@@ -854,19 +908,33 @@ system_render :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
sgl.load_pipeline(state.scene_pip)
|
|
|
sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&p)
|
|
|
sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&v)
|
|
|
+
|
|
|
draw_grid(100, 1.0)
|
|
|
+
|
|
|
for i in 0 ..< len(state.entities) {
|
|
|
- e := state.entities[i]
|
|
|
+ e := &state.entities[i]
|
|
|
+
|
|
|
+ // 1. Рисуем сам цветной куб
|
|
|
draw_mesh_instance(&state.mesh_cube, e.pos, e.scale, e.color)
|
|
|
+
|
|
|
+ // 2. Если это Garden, рисуем черную обводку поверх
|
|
|
+ if e.kind == .Garden {
|
|
|
+ // offset сдвигает глубину линий чуть ближе к камере,
|
|
|
+ // чтобы они не проваливались внутрь куба (Z-fighting)
|
|
|
+ // sgl.offset(1.0, 1.0)
|
|
|
+ debug_draw_mesh_lines(&state.debug_line_mesh, e.pos, e.scale)
|
|
|
+ // sgl.offset(0.0, 0.0) // Обязательно сбрасываем обратно
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
sgl.pop_pipeline()
|
|
|
}
|
|
|
|
|
|
get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
|
|
|
if len(state.entities) == 0 do return linalg.MATRIX4F32_IDENTITY, linalg.MATRIX4F32_IDENTITY
|
|
|
- player_pos := state.entities[0].pos
|
|
|
+ player_pos := state.entities[state.player_id].pos
|
|
|
cam_offset :=
|
|
|
- linalg.Vector3f32 {
|
|
|
+ Vec3 {
|
|
|
math.cos(state.camera.pitch) * math.sin(state.camera.yaw),
|
|
|
math.sin(state.camera.pitch),
|
|
|
math.cos(state.camera.pitch) * math.cos(state.camera.yaw),
|
|
|
@@ -915,10 +983,88 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
return m
|
|
|
}
|
|
|
|
|
|
-draw_mesh_instance :: proc(m: ^Mesh, pos: linalg.Vector3f32, scale: f32, tint: linalg.Vector3f32) {
|
|
|
+debug_make_cube_lines :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
+ m: Mesh
|
|
|
+ // Нам нужно всего 8 уникальных вершин для каркаса
|
|
|
+ m.vertices = make(#soa[dynamic]Vertex, 0, 8, allocator)
|
|
|
+ m.indices = make([dynamic]u16, 0, 24, allocator) // 12 ребер * 2 индекса
|
|
|
+
|
|
|
+ // Черный цвет для вершин
|
|
|
+ black := [3]f32{0, 0, 0}
|
|
|
+
|
|
|
+ // Добавляем 8 углов куба
|
|
|
+ // Нижняя грань (y = -0.5)
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, -0.5, -0.5}, black}) // 0: лево-низ-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, -0.5, -0.5}, black}) // 1: право-низ-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, -0.5, 0.5}, black}) // 2: право-низ-перед
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, -0.5, 0.5}, black}) // 3: лево-низ-перед
|
|
|
+
|
|
|
+ // Верхняя грань (y = 0.5)
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, 0.5, -0.5}, black}) // 4: лево-верх-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, 0.5, -0.5}, black}) // 5: право-верх-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, 0.5, 0.5}, black}) // 6: право-верх-перед
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, 0.5, 0.5}, black}) // 7: лево-верх-перед
|
|
|
+
|
|
|
+ // Соединяем линиями (пары индексов)
|
|
|
+ append(
|
|
|
+ &m.indices,
|
|
|
+ // Нижний квадрат
|
|
|
+ 0,
|
|
|
+ 1,
|
|
|
+ 1,
|
|
|
+ 2,
|
|
|
+ 2,
|
|
|
+ 3,
|
|
|
+ 3,
|
|
|
+ 0,
|
|
|
+ // Верхний квадрат
|
|
|
+ 4,
|
|
|
+ 5,
|
|
|
+ 5,
|
|
|
+ 6,
|
|
|
+ 6,
|
|
|
+ 7,
|
|
|
+ 7,
|
|
|
+ 4,
|
|
|
+ // Вертикальные стойки
|
|
|
+ 0,
|
|
|
+ 4,
|
|
|
+ 1,
|
|
|
+ 5,
|
|
|
+ 2,
|
|
|
+ 6,
|
|
|
+ 3,
|
|
|
+ 7,
|
|
|
+ )
|
|
|
+
|
|
|
+ return m
|
|
|
+}
|
|
|
+
|
|
|
+debug_draw_mesh_lines :: proc(m: ^Mesh, pos: Vec3, scale: Vec3) {
|
|
|
+ sgl.push_matrix()
|
|
|
+ sgl.translate(pos.x, pos.y, pos.z)
|
|
|
+ sgl.scale(scale.x, scale.y, scale.z)
|
|
|
+
|
|
|
+ // Важно: используем begin_lines вместо begin_triangles
|
|
|
+ sgl.begin_lines()
|
|
|
+
|
|
|
+ for i := 0; i < len(m.indices); i += 1 {
|
|
|
+ idx := m.indices[i]
|
|
|
+ v := m.vertices[idx]
|
|
|
+
|
|
|
+ // Используем цвет из вершины (черный)
|
|
|
+ sgl.c3f(v.color.x, v.color.y, v.color.z)
|
|
|
+ sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
|
|
|
+ }
|
|
|
+
|
|
|
+ sgl.end()
|
|
|
+ sgl.pop_matrix()
|
|
|
+}
|
|
|
+
|
|
|
+draw_mesh_instance :: proc(m: ^Mesh, pos: Vec3, scale: Vec3, tint: Vec3) {
|
|
|
sgl.push_matrix()
|
|
|
sgl.translate(pos.x, pos.y, pos.z)
|
|
|
- sgl.scale(scale, scale, scale)
|
|
|
+ sgl.scale(scale.x, scale.y, scale.z)
|
|
|
sgl.begin_triangles()
|
|
|
for i := 0; i < len(m.indices); i += 1 {
|
|
|
idx := m.indices[i]
|
|
|
@@ -942,28 +1088,17 @@ draw_grid :: proc(slices: int, spacing: f32) {
|
|
|
sgl.end()
|
|
|
}
|
|
|
|
|
|
-get_mouse_ray :: proc(
|
|
|
- mx, my: f32,
|
|
|
- view, proj: linalg.Matrix4f32,
|
|
|
-) -> (
|
|
|
- origin, dir: linalg.Vector3f32,
|
|
|
-) {
|
|
|
+get_mouse_ray :: proc(mx, my: f32, view, proj: linalg.Matrix4f32) -> (origin, dir: Vec3) {
|
|
|
x := (2.0 * mx) / sapp.widthf() - 1.0
|
|
|
y := 1.0 - (2.0 * my) / sapp.heightf()
|
|
|
- ray_eye := linalg.matrix4_inverse(proj) * linalg.Vector4f32{x, y, -1.0, 1.0}
|
|
|
+ ray_eye := linalg.matrix4_inverse(proj) * Vec4{x, y, -1.0, 1.0}
|
|
|
ray_eye = {ray_eye.x, ray_eye.y, -1.0, 0.0}
|
|
|
inv_view := linalg.matrix4_inverse(view)
|
|
|
ray_world := inv_view * ray_eye
|
|
|
return {inv_view[3][0], inv_view[3][1], inv_view[3][2]}, linalg.normalize(ray_world.xyz)
|
|
|
}
|
|
|
|
|
|
-ray_plane_intersect :: proc(
|
|
|
- origin, dir: linalg.Vector3f32,
|
|
|
- plane_y: f32,
|
|
|
-) -> (
|
|
|
- pos: linalg.Vector3f32,
|
|
|
- hit: bool,
|
|
|
-) {
|
|
|
+ray_plane_intersect :: proc(origin, dir: Vec3, plane_y: f32) -> (pos: Vec3, hit: bool) {
|
|
|
if abs(dir.y) < 0.001 do return {}, false
|
|
|
t := (plane_y - origin.y) / dir.y
|
|
|
if t < 0 do return {}, false
|
|
|
@@ -1018,7 +1153,7 @@ main :: proc() {
|
|
|
width = 1920,
|
|
|
height = 1080,
|
|
|
window_title = "Sandbox (Dev)",
|
|
|
- icon = {sokol_default = true},
|
|
|
+ icon = {sokol_default = false},
|
|
|
logger = {func = slog.func},
|
|
|
high_dpi = true,
|
|
|
swap_interval = 1,
|