0xc3 3 недель назад
Родитель
Сommit
34bfc3825f
1 измененных файлов с 180 добавлено и 45 удалено
  1. 180 45
      src/cmd/sandbox/main.odin

+ 180 - 45
src/cmd/sandbox/main.odin

@@ -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,