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