|
|
@@ -160,7 +160,11 @@ Transform_Component :: struct {
|
|
|
}
|
|
|
|
|
|
Physics_Component :: struct {
|
|
|
- velocity: Vec3,
|
|
|
+ velocity: Vec3,
|
|
|
+ jump_timer: f32,
|
|
|
+ jump_duration: f32,
|
|
|
+ start_pos: Vec3,
|
|
|
+ is_jumping: bool,
|
|
|
}
|
|
|
|
|
|
Render_Component :: struct {}
|
|
|
@@ -506,33 +510,47 @@ push_entity :: proc() -> Entity_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
|
|
|
+ t_min: f32 = -math.F32_MAX
|
|
|
+ t_max: f32 = math.F32_MAX
|
|
|
+
|
|
|
+ // X axis
|
|
|
+ if math.abs(dir.x) > 0.0001 {
|
|
|
+ tx_min := (box_min.x - origin.x) / dir.x
|
|
|
+ tx_max := (box_max.x - origin.x) / dir.x
|
|
|
+ if tx_min > tx_max do tx_min, tx_max = tx_max, tx_min
|
|
|
+ t_min = math.max(t_min, tx_min)
|
|
|
+ t_max = math.min(t_max, tx_max)
|
|
|
+ } else if origin.x < box_min.x || origin.x > box_max.x {
|
|
|
+ return false, 0
|
|
|
+ }
|
|
|
|
|
|
- if (t_min > tz_max) || (tz_min > t_max) do return false, 0
|
|
|
+ // Y axis
|
|
|
+ if math.abs(dir.y) > 0.0001 {
|
|
|
+ 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
|
|
|
+ t_min = math.max(t_min, ty_min)
|
|
|
+ t_max = math.min(t_max, ty_max)
|
|
|
+ } else if origin.y < box_min.y || origin.y > box_max.y {
|
|
|
+ return false, 0
|
|
|
+ }
|
|
|
|
|
|
- if tz_min > t_min do t_min = tz_min
|
|
|
- if tz_max < t_max do t_max = tz_max
|
|
|
+ // Z axis
|
|
|
+ if math.abs(dir.z) > 0.0001 {
|
|
|
+ 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
|
|
|
+ t_min = math.max(t_min, tz_min)
|
|
|
+ t_max = math.min(t_max, tz_max)
|
|
|
+ } else if origin.z < box_min.z || origin.z > box_max.z {
|
|
|
+ return false, 0
|
|
|
+ }
|
|
|
|
|
|
- if t_min < 0 do return false, 0 // Пересечение сзади луча
|
|
|
+ // Проверяем пересечение
|
|
|
+ if t_max < t_min || t_max < 0 do return false, 0
|
|
|
|
|
|
+ // Возвращаем ближайшую точку входа
|
|
|
+ if t_min < 0 do return true, t_max // Луч начинается внутри бокса
|
|
|
return true, t_min
|
|
|
}
|
|
|
|
|
|
@@ -639,7 +657,7 @@ init :: proc "c" () {
|
|
|
ui_pip_desc.depth.write_enabled = false
|
|
|
ui_pip_desc.depth.compare = .ALWAYS
|
|
|
state.ui_pip = sgl.make_pipeline(ui_pip_desc)
|
|
|
-
|
|
|
+
|
|
|
// Grid pipeline - no depth write, but test against depth
|
|
|
grid_pip_desc: sg.Pipeline_Desc
|
|
|
grid_pip_desc.depth.write_enabled = false
|
|
|
@@ -648,19 +666,28 @@ init :: proc "c" () {
|
|
|
|
|
|
// Create 3D shader and pipeline
|
|
|
state.scene_shader = sg.make_shader(scene_shader_desc(sg.query_backend()))
|
|
|
-
|
|
|
+
|
|
|
scene_pip_desc: sg.Pipeline_Desc
|
|
|
scene_pip_desc.shader = state.scene_shader
|
|
|
scene_pip_desc.layout.buffers[0].stride = i32(size_of(Vertex))
|
|
|
- scene_pip_desc.layout.attrs[ATTR_scene_position] = {format = .FLOAT3, offset = i32(offset_of(Vertex, pos))}
|
|
|
- scene_pip_desc.layout.attrs[ATTR_scene_normal0] = {format = .FLOAT3, offset = i32(offset_of(Vertex, normal))}
|
|
|
- scene_pip_desc.layout.attrs[ATTR_scene_color0] = {format = .FLOAT3, offset = i32(offset_of(Vertex, color))}
|
|
|
+ scene_pip_desc.layout.attrs[ATTR_scene_position] = {
|
|
|
+ format = .FLOAT3,
|
|
|
+ offset = i32(offset_of(Vertex, pos)),
|
|
|
+ }
|
|
|
+ scene_pip_desc.layout.attrs[ATTR_scene_normal0] = {
|
|
|
+ format = .FLOAT3,
|
|
|
+ offset = i32(offset_of(Vertex, normal)),
|
|
|
+ }
|
|
|
+ scene_pip_desc.layout.attrs[ATTR_scene_color0] = {
|
|
|
+ format = .FLOAT3,
|
|
|
+ offset = i32(offset_of(Vertex, color)),
|
|
|
+ }
|
|
|
scene_pip_desc.index_type = .UINT16
|
|
|
scene_pip_desc.depth.write_enabled = true
|
|
|
scene_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
scene_pip_desc.cull_mode = .FRONT
|
|
|
state.scene_pip = sg.make_pipeline(scene_pip_desc)
|
|
|
-
|
|
|
+
|
|
|
// Upload mesh data to GPU
|
|
|
upload_mesh(&state.mesh_cube)
|
|
|
upload_mesh(&state.debug_line_mesh)
|
|
|
@@ -672,6 +699,10 @@ init :: proc "c" () {
|
|
|
|
|
|
state.player_ent_id = push_entity()
|
|
|
add_transofrm_component(state.player_ent_id)
|
|
|
+ add_collider_component(state.player_ent_id, {
|
|
|
+ type = .AABB,
|
|
|
+ half = {0.5, 0.5, 0.5}, // Половина размера куба (т.к. меш от -0.5 до 0.5)
|
|
|
+ })
|
|
|
|
|
|
// Garden_Grid
|
|
|
{
|
|
|
@@ -683,9 +714,11 @@ init :: proc "c" () {
|
|
|
t.position = {(1.0 * f32(x)) + offset.x, -0.5, (1.0 * f32(z)) + offset.z}
|
|
|
t.scale.y = 0.1
|
|
|
|
|
|
+ add_physics_component(ent_id) // Добавляем физику для анимации
|
|
|
+
|
|
|
add_collider_component(
|
|
|
ent_id,
|
|
|
- Collider_Component {
|
|
|
+ {
|
|
|
type = .AABB,
|
|
|
half = {0.5, 0.5, 0.5}, // Половина размера куба (т.к. меш от -0.5 до 0.5)
|
|
|
},
|
|
|
@@ -706,13 +739,19 @@ init :: proc "c" () {
|
|
|
// Основная функция отрисовки всех коллайдеров
|
|
|
system_render_debug :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
vp := proj * view
|
|
|
-
|
|
|
+
|
|
|
// Create debug line pipeline
|
|
|
debug_pip_desc: sg.Pipeline_Desc
|
|
|
debug_pip_desc.shader = state.scene_shader
|
|
|
- debug_pip_desc.layout.attrs[ATTR_scene_position] = {format = .FLOAT3}
|
|
|
- debug_pip_desc.layout.attrs[ATTR_scene_normal0] = {format = .FLOAT3}
|
|
|
- debug_pip_desc.layout.attrs[ATTR_scene_color0] = {format = .FLOAT3}
|
|
|
+ debug_pip_desc.layout.attrs[ATTR_scene_position] = {
|
|
|
+ format = .FLOAT3,
|
|
|
+ }
|
|
|
+ debug_pip_desc.layout.attrs[ATTR_scene_normal0] = {
|
|
|
+ format = .FLOAT3,
|
|
|
+ }
|
|
|
+ debug_pip_desc.layout.attrs[ATTR_scene_color0] = {
|
|
|
+ format = .FLOAT3,
|
|
|
+ }
|
|
|
debug_pip_desc.index_type = .UINT16
|
|
|
debug_pip_desc.depth.write_enabled = false
|
|
|
debug_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
@@ -720,7 +759,7 @@ system_render_debug :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
debug_pip_desc.primitive_type = .LINES
|
|
|
debug_pip := sg.make_pipeline(debug_pip_desc)
|
|
|
defer sg.destroy_pipeline(debug_pip)
|
|
|
-
|
|
|
+
|
|
|
sg.apply_pipeline(debug_pip)
|
|
|
|
|
|
for ent in state.entities {
|
|
|
@@ -733,27 +772,27 @@ system_render_debug :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
case .AABB:
|
|
|
// Зеленый для AABB
|
|
|
final_half := c.half * t.scale
|
|
|
-
|
|
|
+
|
|
|
// Calculate model matrix
|
|
|
model := linalg.matrix4_translate_f32(t.position)
|
|
|
model = model * linalg.matrix4_scale_f32(final_half * 2.0)
|
|
|
mvp := vp * model
|
|
|
-
|
|
|
+
|
|
|
vs_params := Vs_Params{}
|
|
|
vs_params.mvp = transmute([16]f32)mvp
|
|
|
-
|
|
|
+
|
|
|
bindings := sg.Bindings {
|
|
|
vertex_buffers = {0 = state.debug_line_mesh.vbuf},
|
|
|
index_buffer = state.debug_line_mesh.ibuf,
|
|
|
}
|
|
|
sg.apply_bindings(bindings)
|
|
|
sg.apply_uniforms(UB_vs_params, {ptr = &vs_params, size = size_of(Vs_Params)})
|
|
|
-
|
|
|
+
|
|
|
sg.draw(0, i32(len(state.debug_line_mesh.indices)), 1)
|
|
|
|
|
|
case .Sphere:
|
|
|
- // Красный для Сфер - пока пропустим, нужен отдельный меш
|
|
|
- // TODO: create sphere wireframe mesh
|
|
|
+ // Красный для Сфер - пока пропустим, нужен отдельный меш
|
|
|
+ // TODO: create sphere wireframe mesh
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -780,17 +819,24 @@ frame :: proc "c" () {
|
|
|
if hit {
|
|
|
fmt.println("Hit Entity ID:", hit_id, "at distance:", dist)
|
|
|
|
|
|
- // Пример: Удалить сущность при клике
|
|
|
- // (Внимание: удаление из dynamic array может инвалидировать индексы,
|
|
|
- // лучше помечать как 'dead' или использовать swap_remove аккуратно)
|
|
|
-
|
|
|
- // Пример: Изменить масштаб при клике
|
|
|
- if t, ok := get_transform_component(hit_id); ok {
|
|
|
- t.position *= 1.2
|
|
|
+ // Проверяем, есть ли у сущности физика
|
|
|
+ if phys, phys_ok := get_physics_component(hit_id); phys_ok {
|
|
|
+ if t, t_ok := get_transform_component(hit_id); t_ok {
|
|
|
+ // Запускаем анимацию прыжка только если не прыгает
|
|
|
+ if !phys.is_jumping {
|
|
|
+ phys.jump_timer = 0.0
|
|
|
+ phys.jump_duration = 0.5 // 0.5 секунды на прыжок
|
|
|
+ phys.start_pos = t.position
|
|
|
+ phys.is_jumping = true
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Система анимации прыжков
|
|
|
+ system_jump_animation(dt)
|
|
|
+
|
|
|
// Garden_Grid
|
|
|
{
|
|
|
for id in state.garden_grid.items {
|
|
|
@@ -819,29 +865,29 @@ frame :: proc "c" () {
|
|
|
|
|
|
sgl.pop_matrix(); sgl.matrix_mode_projection(); sgl.pop_matrix(); sgl.pop_pipeline()
|
|
|
sg.begin_pass({action = state.pass_action, swapchain = sglue.swapchain()})
|
|
|
-
|
|
|
+
|
|
|
// Render 3D scene FIRST
|
|
|
system_render(view, proj)
|
|
|
-
|
|
|
+
|
|
|
// Render debug (colliders)
|
|
|
system_render_debug(view, proj)
|
|
|
-
|
|
|
+
|
|
|
// Render simple grid with sgl (no depth write)
|
|
|
sgl.defaults()
|
|
|
sgl.load_pipeline(state.grid_pip)
|
|
|
sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&proj)
|
|
|
sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&view)
|
|
|
-
|
|
|
+
|
|
|
// Draw simple grid
|
|
|
sgl.begin_lines()
|
|
|
grid_size: f32 = 50.0
|
|
|
grid_spacing: f32 = 1.0
|
|
|
half := grid_size * grid_spacing * 0.5
|
|
|
y: f32 = -0.501
|
|
|
-
|
|
|
+
|
|
|
for i in 0 ..= i32(grid_size) {
|
|
|
p := -half + f32(i) * grid_spacing
|
|
|
-
|
|
|
+
|
|
|
// Center axes
|
|
|
if math.abs(p) < 0.0001 {
|
|
|
// X axis (red)
|
|
|
@@ -852,21 +898,21 @@ frame :: proc "c" () {
|
|
|
sgl.v3f(0, y, -half); sgl.v3f(0, y, half)
|
|
|
continue
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Grid lines
|
|
|
if i % 10 == 0 {
|
|
|
sgl.c3f(0.4, 0.4, 0.4)
|
|
|
} else {
|
|
|
sgl.c3f(0.6, 0.6, 0.6)
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Line along X
|
|
|
sgl.v3f(-half, y, p); sgl.v3f(half, y, p)
|
|
|
// Line along Z
|
|
|
sgl.v3f(p, y, -half); sgl.v3f(p, y, half)
|
|
|
}
|
|
|
sgl.end()
|
|
|
-
|
|
|
+
|
|
|
// Render UI on top
|
|
|
sgl.defaults()
|
|
|
sgl.load_pipeline(state.ui_pip)
|
|
|
@@ -874,7 +920,7 @@ frame :: proc "c" () {
|
|
|
sgl.ortho(0.0, w, h, 0.0, -1.0, +1.0)
|
|
|
sgl.matrix_mode_modelview()
|
|
|
sgl.draw()
|
|
|
-
|
|
|
+
|
|
|
sg.end_pass()
|
|
|
sg.commit()
|
|
|
|
|
|
@@ -921,26 +967,59 @@ system_input_player :: proc(dt: f32) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+system_jump_animation :: proc(dt: f32) {
|
|
|
+ JUMP_HEIGHT: f32 : 0.5 // Высота прыжка
|
|
|
+
|
|
|
+ for ent in state.entities {
|
|
|
+ phys, phys_ok := get_physics_component(ent.id)
|
|
|
+ if !phys_ok do continue
|
|
|
+
|
|
|
+ t, t_ok := get_transform_component(ent.id)
|
|
|
+ if !t_ok do continue
|
|
|
+
|
|
|
+ // Если анимация активна
|
|
|
+ if phys.is_jumping {
|
|
|
+ phys.jump_timer += dt
|
|
|
+
|
|
|
+ // Нормализованное время (0..1)
|
|
|
+ progress := math.clamp(phys.jump_timer / phys.jump_duration, 0.0, 1.0)
|
|
|
+
|
|
|
+ // Параболическая траектория (sin для плавности)
|
|
|
+ jump_offset := math.sin(progress * math.PI) * JUMP_HEIGHT
|
|
|
+
|
|
|
+ // Обновляем позицию
|
|
|
+ t.position.y = phys.start_pos.y + jump_offset
|
|
|
+
|
|
|
+ // Завершаем анимацию
|
|
|
+ if progress >= 1.0 {
|
|
|
+ t.position.y = phys.start_pos.y // Гарантируем точное возвращение
|
|
|
+ phys.is_jumping = false
|
|
|
+ phys.jump_timer = 0.0
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
system_render :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
v := view; p := proj
|
|
|
vp := p * v
|
|
|
-
|
|
|
+
|
|
|
// Draw entities with proper shader
|
|
|
sg.apply_pipeline(state.scene_pip)
|
|
|
-
|
|
|
+
|
|
|
for ent in state.entities {
|
|
|
t, is_found := get_transform_component(ent.id)
|
|
|
if !is_found do continue
|
|
|
-
|
|
|
+
|
|
|
// Calculate model matrix
|
|
|
model := linalg.matrix4_translate_f32(t.position)
|
|
|
model = model * linalg.matrix4_scale_f32(t.scale)
|
|
|
mvp := vp * model
|
|
|
-
|
|
|
+
|
|
|
// Set uniforms
|
|
|
vs_params := Vs_Params{}
|
|
|
vs_params.mvp = transmute([16]f32)mvp
|
|
|
-
|
|
|
+
|
|
|
// Apply bindings
|
|
|
bindings := sg.Bindings {
|
|
|
vertex_buffers = {0 = state.mesh_cube.vbuf},
|
|
|
@@ -948,7 +1027,7 @@ system_render :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
}
|
|
|
sg.apply_bindings(bindings)
|
|
|
sg.apply_uniforms(UB_vs_params, {ptr = &vs_params, size = size_of(Vs_Params)})
|
|
|
-
|
|
|
+
|
|
|
// Draw
|
|
|
sg.draw(0, i32(len(state.mesh_cube.indices)), 1)
|
|
|
}
|
|
|
@@ -1187,7 +1266,7 @@ upload_mesh :: proc(m: ^Mesh, allocator := context.temp_allocator) {
|
|
|
// Create interleaved vertex data
|
|
|
vertex_data := make([]Vertex, len(m.vertices), allocator)
|
|
|
defer if allocator == context.temp_allocator do delete(vertex_data, allocator)
|
|
|
-
|
|
|
+
|
|
|
for i in 0 ..< len(m.vertices) {
|
|
|
vertex_data[i] = Vertex {
|
|
|
pos = m.vertices.pos[i],
|
|
|
@@ -1195,7 +1274,7 @@ upload_mesh :: proc(m: ^Mesh, allocator := context.temp_allocator) {
|
|
|
color = m.vertices.color[i],
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Upload vertices
|
|
|
m.vbuf = sg.make_buffer(
|
|
|
{
|
|
|
@@ -1203,7 +1282,7 @@ upload_mesh :: proc(m: ^Mesh, allocator := context.temp_allocator) {
|
|
|
usage = {vertex_buffer = true, immutable = true},
|
|
|
},
|
|
|
)
|
|
|
-
|
|
|
+
|
|
|
// Upload indices
|
|
|
m.ibuf = sg.make_buffer(
|
|
|
{
|