0xc3 před 3 týdny
rodič
revize
1da85d5b70
1 změnil soubory, kde provedl 147 přidání a 15 odebrání
  1. 147 15
      src/cmd/sandbox/main.odin

+ 147 - 15
src/cmd/sandbox/main.odin

@@ -139,16 +139,9 @@ Entity_Kind :: enum u8 {
 	Garden,
 }
 
-Entity_Id :: distinct u32
-
-Entity :: struct {
-	pos:        Vec3,
-	vel:        Vec3,
-	color:      Vec3,
-	kind:       Entity_Kind,
-	scale:      Vec3,
-	box_id:     Box_Id,
-	is_physics: bool,
+AABB :: struct {
+	min: Vec3,
+	max: Vec3,
 }
 
 Garden_State :: enum u8 {
@@ -157,9 +150,18 @@ Garden_State :: enum u8 {
 	Plant, // Растение
 }
 
-Garden :: struct {
-	using _: Entity,
-	state:   Garden_State,
+Entity_Id :: distinct u32
+
+Entity :: struct {
+	pos:          Vec3,
+	vel:          Vec3,
+	color:        Vec3,
+	kind:         Entity_Kind,
+	scale:        Vec3,
+	box_id:       Box_Id,
+	is_physics:   bool,
+	collider:     AABB,
+	garden_state: Garden_State,
 }
 
 Camera :: struct {
@@ -493,9 +495,10 @@ push_entity :: proc(
 	scale: Vec3 = {1.0, 1.0, 1.0},
 ) -> Entity_Id {
 	e := Entity {
-		pos   = pos,
-		kind  = kind,
+		pos = pos,
+		kind = kind,
 		scale = scale,
+		collider = AABB{min = {-0.5, -0.5, -0.5}, max = {0.5, 0.5, 0.5}},
 	}
 	switch kind {
 	case .Player:
@@ -515,6 +518,39 @@ push_entity :: proc(
 	return Entity_Id(id)
 }
 
+// Проверка пересечения луча и AABB (Axis Aligned Bounding Box)
+// Возвращает hit (попадание) и dist (дистанцию до точки входа)
+ray_aabb_intersect :: proc(origin, dir: Vec3, box_min, box_max: Vec3) -> (bool, f32) {
+	t_min := (box_min.x - origin.x) / dir.x
+	t_max := (box_max.x - origin.x) / dir.x
+
+	if t_min > t_max do t_min, t_max = t_max, t_min
+
+	ty_min := (box_min.y - origin.y) / dir.y
+	ty_max := (box_max.y - origin.y) / dir.y
+
+	if ty_min > ty_max do ty_min, ty_max = ty_max, ty_min
+
+	if (t_min > ty_max) || (ty_min > t_max) do return false, 0
+
+	if ty_min > t_min do t_min = ty_min
+	if ty_max < t_max do t_max = ty_max
+
+	tz_min := (box_min.z - origin.z) / dir.z
+	tz_max := (box_max.z - origin.z) / dir.z
+
+	if tz_min > tz_max do tz_min, tz_max = tz_max, tz_min
+
+	if (t_min > tz_max) || (tz_min > t_max) do return false, 0
+
+	if tz_min > t_min do t_min = tz_min
+	if tz_max < t_max do t_max = tz_max
+
+	if t_min < 0 do return false, 0 // Пересечение сзади луча
+
+	return true, t_min
+}
+
 // --- Initialization ---
 
 init :: proc "c" () {
@@ -596,6 +632,7 @@ frame :: proc "c" () {
 	system_input_player(dt)
 	system_physics(dt)
 	view, proj := get_camera_matrices()
+	system_interaction(view, proj)
 	system_builder(view, proj)
 
 	system_render(view, proj)
@@ -809,6 +846,101 @@ draw_hud :: proc(screen_w, screen_h: f32) {
 	}
 }
 
+system_interaction :: proc(view, proj: linalg.Matrix4f32) {
+	// Если курсор заблокирован или открыт инвентарь - не взаимодействуем
+	if state.input.locked do return
+	if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do return
+
+	// Получаем луч из камеры
+	ray_origin, ray_dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)
+
+	closest_dist := f32(10000.0)
+	hit_entity_idx := -1
+
+	// 1. Ищем ближайшую сущность, с которой пересекся луч
+	for i in 0 ..< len(state.entities) {
+		// Не взаимодействуем с самим игроком
+		if Entity_Id(i) == state.player_id do continue
+
+		e := &state.entities[i]
+
+		// Переводим локальный AABB в мировые координаты с учетом позиции и масштаба
+		world_min := e.pos + (e.collider.min * e.scale)
+		world_max := e.pos + (e.collider.max * e.scale)
+
+		if hit, dist := ray_aabb_intersect(ray_origin, ray_dir, world_min, world_max); hit {
+			if dist < closest_dist {
+				closest_dist = dist
+				hit_entity_idx = i
+			}
+		}
+	}
+
+	// 2. Если нашли сущность
+	if hit_entity_idx != -1 {
+		target := &state.entities[hit_entity_idx]
+
+		proj_copy := proj
+		view_copy := view
+
+		// --- HOVER (Наведение) ---
+		// Рисуем желтую обводку вокруг объекта
+		sgl.defaults()
+		sgl.push_pipeline()
+		sgl.load_pipeline(state.scene_pip)
+		sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&proj_copy)
+		sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&view_copy)
+
+		// Рисуем линии чуть шире и желтым цветом
+		sgl.push_matrix()
+		sgl.translate(target.pos.x, target.pos.y, target.pos.z)
+		// Чуть увеличиваем масштаб обводки, чтобы она была видна поверх объекта
+		scale_hover := target.scale * 1.05
+		sgl.scale(scale_hover.x, scale_hover.y, scale_hover.z)
+
+		sgl.begin_lines()
+		sgl.c3f(1.0, 1.0, 0.0) // Желтый цвет
+		// Используем существующий меш линий, но игнорируем его черный цвет, задавая свой через c3f
+		// (Примечание: в debug_draw_mesh_lines используется цвет вершин, поэтому напишем упрощенный код здесь)
+		m := &state.debug_line_mesh
+		for idx in m.indices {
+			v := m.vertices[idx]
+			sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
+		}
+		sgl.end()
+		sgl.pop_matrix()
+		sgl.pop_pipeline()
+
+		// --- CLICK (Клик) ---
+		if state.input.left_clicked {
+			fmt.println("Clicked on entity:", target.kind, "ID:", hit_entity_idx)
+
+			// Пример взаимодействия:
+			// 1. Если это грядка - меняем состояние
+			if target.kind == .Garden {
+				if target.garden_state == .Dry {
+					target.garden_state = .Water
+					target.color = {0.2, 0.2, 0.8} // Синий
+				} else if target.garden_state == .Water {
+					target.garden_state = .Plant
+					target.color = {0.2, 0.8, 0.2} // Зеленый
+				} else {
+					target.garden_state = .Dry
+					target.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0} // Коричневый
+				}
+			} else {
+				// 2. Если это куб или враг - меняем цвет на случайный или подбрасываем
+
+				target.color = {rand.float32(), rand.float32(), rand.float32()}
+				target.vel.y += 5.0 // Подбросить
+			}
+
+			// Сбрасываем клик, чтобы не сработал builder (строительство) в этом же кадре
+			state.input.left_clicked = false
+		}
+	}
+}
+
 system_input_player :: proc(dt: f32) {
 	if len(state.entities) == 0 do return
 	player := &state.entities[state.player_id]