|
|
@@ -49,18 +49,19 @@ SLOT_SIZE: f32 : 40.0
|
|
|
SLOT_SPACING: f32 : 4.0
|
|
|
SLOT_PADDING: f32 : 4.0
|
|
|
|
|
|
-transform_nil: Transform_Component = {}
|
|
|
-
|
|
|
// --- Data Types ---
|
|
|
|
|
|
-Vertex :: struct {
|
|
|
- pos: [3]f32,
|
|
|
- color: [3]f32,
|
|
|
+Vertex :: struct #packed {
|
|
|
+ pos: [3]f32,
|
|
|
+ normal: [3]f32,
|
|
|
+ color: [3]f32,
|
|
|
}
|
|
|
|
|
|
Mesh :: struct {
|
|
|
vertices: #soa[dynamic]Vertex,
|
|
|
indices: [dynamic]u16,
|
|
|
+ vbuf: sg.Buffer,
|
|
|
+ ibuf: sg.Buffer,
|
|
|
}
|
|
|
|
|
|
// Sprites
|
|
|
@@ -121,7 +122,7 @@ Component_Type :: enum {
|
|
|
Physics,
|
|
|
Render,
|
|
|
Garden,
|
|
|
- ColliderAABB,
|
|
|
+ Collider,
|
|
|
}
|
|
|
Component_Set :: distinct bit_set[Component_Type;u64]
|
|
|
|
|
|
@@ -131,11 +132,21 @@ Garden_State :: enum u8 {
|
|
|
Plant, // Растение
|
|
|
}
|
|
|
|
|
|
+Garden_Grid :: struct {
|
|
|
+ items: [dynamic]Entity_Id,
|
|
|
+}
|
|
|
+
|
|
|
Entity_Id :: distinct u32
|
|
|
|
|
|
-ColliderAABB_Component :: struct {
|
|
|
- min: Vec3,
|
|
|
- max: Vec3,
|
|
|
+Collider_Type :: enum u8 {
|
|
|
+ AABB,
|
|
|
+ Sphere,
|
|
|
+}
|
|
|
+
|
|
|
+Collider_Component :: struct {
|
|
|
+ type: Collider_Type,
|
|
|
+ half: Vec3, // для AABB
|
|
|
+ radius: f32, // для Sphere
|
|
|
}
|
|
|
|
|
|
Garden_Component :: struct {
|
|
|
@@ -177,31 +188,34 @@ Input_State :: struct {
|
|
|
}
|
|
|
|
|
|
App_State :: struct {
|
|
|
- arena: virtual.Arena,
|
|
|
- allocator: mem.Allocator,
|
|
|
- pass_action: sg.Pass_Action,
|
|
|
- ui: UI_Context,
|
|
|
- ui_pip: sgl.Pipeline,
|
|
|
- scene_pip: sgl.Pipeline,
|
|
|
- mesh_cube: Mesh,
|
|
|
- entities: [dynamic]Entity,
|
|
|
- player_ent: ^Entity,
|
|
|
+ arena: virtual.Arena,
|
|
|
+ allocator: mem.Allocator,
|
|
|
+ pass_action: sg.Pass_Action,
|
|
|
+ ui: UI_Context,
|
|
|
+ ui_pip: sgl.Pipeline,
|
|
|
+ grid_pip: sgl.Pipeline,
|
|
|
+ scene_pip: sg.Pipeline,
|
|
|
+ scene_shader: sg.Shader,
|
|
|
+ mesh_cube: Mesh,
|
|
|
+ entities: [dynamic]Entity,
|
|
|
+ player_ent_id: Entity_Id,
|
|
|
+ garden_grid: Garden_Grid,
|
|
|
// Componets
|
|
|
- garde_components: [dynamic]Garden_Component,
|
|
|
- transform_components: [dynamic]Transform_Component,
|
|
|
- physics_components: [dynamic]Physics_Component,
|
|
|
- render_components: [dynamic]Render_Component,
|
|
|
- collider_aabb_components: [dynamic]ColliderAABB_Component,
|
|
|
+ garden_components: [dynamic]Garden_Component,
|
|
|
+ transform_components: [dynamic]Transform_Component,
|
|
|
+ physics_components: [dynamic]Physics_Component,
|
|
|
+ render_components: [dynamic]Render_Component,
|
|
|
+ collider_components: [dynamic]Collider_Component,
|
|
|
//
|
|
|
- dpi_scale: f32,
|
|
|
- camera: Camera,
|
|
|
- last_input: Input_State,
|
|
|
- input: Input_State,
|
|
|
- selected_color: [3]f32,
|
|
|
- active_hotbar_slot: int,
|
|
|
- sprites: [dynamic]Sprite,
|
|
|
- debug_cube_mesh: Mesh,
|
|
|
- debug_line_mesh: Mesh,
|
|
|
+ dpi_scale: f32,
|
|
|
+ camera: Camera,
|
|
|
+ last_input: Input_State,
|
|
|
+ input: Input_State,
|
|
|
+ selected_color: [3]f32,
|
|
|
+ active_hotbar_slot: int,
|
|
|
+ sprites: [dynamic]Sprite,
|
|
|
+ debug_cube_mesh: Mesh,
|
|
|
+ debug_line_mesh: Mesh,
|
|
|
}
|
|
|
|
|
|
state: App_State
|
|
|
@@ -479,12 +493,14 @@ ui_slider :: proc(label: string, val: ^f32, min_v, max_v: f32) {
|
|
|
|
|
|
// --- Game Logic ---
|
|
|
|
|
|
-push_entity :: proc() -> ^Entity {
|
|
|
+push_entity :: proc() -> Entity_Id {
|
|
|
ent := Entity {
|
|
|
id = Entity_Id(len(state.entities)),
|
|
|
}
|
|
|
+
|
|
|
append(&state.entities, ent)
|
|
|
- return &state.entities[ent.id]
|
|
|
+
|
|
|
+ return ent.id
|
|
|
}
|
|
|
|
|
|
// Проверка пересечения луча и AABB (Axis Aligned Bounding Box)
|
|
|
@@ -520,6 +536,83 @@ ray_aabb_intersect :: proc(origin, dir: Vec3, box_min, box_max: Vec3) -> (bool,
|
|
|
return true, t_min
|
|
|
}
|
|
|
|
|
|
+// --- Дополнительная математика ---
|
|
|
+
|
|
|
+// Проверка пересечения луча и Сферы
|
|
|
+ray_sphere_intersect :: proc(
|
|
|
+ ray_origin, ray_dir: Vec3,
|
|
|
+ sphere_center: Vec3,
|
|
|
+ radius: f32,
|
|
|
+) -> (
|
|
|
+ bool,
|
|
|
+ f32,
|
|
|
+) {
|
|
|
+ L := sphere_center - ray_origin
|
|
|
+ tca := linalg.dot(L, ray_dir)
|
|
|
+ if tca < 0 do return false, 0
|
|
|
+
|
|
|
+ d2 := linalg.dot(L, L) - tca * tca
|
|
|
+ r2 := radius * radius
|
|
|
+ if d2 > r2 do return false, 0
|
|
|
+
|
|
|
+ thc := math.sqrt(r2 - d2)
|
|
|
+ t0 := tca - thc
|
|
|
+ t1 := tca + thc
|
|
|
+
|
|
|
+ if t0 < 0 do t0 = t1
|
|
|
+ if t0 < 0 do return false, 0
|
|
|
+
|
|
|
+ return true, t0
|
|
|
+}
|
|
|
+
|
|
|
+// --- Основная функция Raycast ---
|
|
|
+
|
|
|
+// Проходит по всем сущностям с коллайдерами и возвращает ID ближайшей
|
|
|
+raycast :: proc(origin, dir: Vec3) -> (Entity_Id, f32, bool) {
|
|
|
+ closest_dist: f32 = 1000000.0 // Бесконечность
|
|
|
+ hit_id: Entity_Id
|
|
|
+ has_hit := false
|
|
|
+
|
|
|
+ // Проходим по всем сущностям
|
|
|
+ for ent in state.entities {
|
|
|
+ // Получаем компоненты. Если нет Трансформа или Коллайдера — пропускаем
|
|
|
+ t, t_ok := get_transform_component(ent.id)
|
|
|
+ c, c_ok := get_collider_component(ent.id)
|
|
|
+
|
|
|
+ if !t_ok || !c_ok do continue
|
|
|
+
|
|
|
+ hit := false
|
|
|
+ dist: f32 = 0.0
|
|
|
+
|
|
|
+ switch c.type {
|
|
|
+ case .AABB:
|
|
|
+ // Рассчитываем границы AABB в мировых координатах с учетом масштаба
|
|
|
+ // half - это половина размера (extents)
|
|
|
+ scaled_half := c.half * t.scale
|
|
|
+ box_min := t.position - scaled_half
|
|
|
+ box_max := t.position + scaled_half
|
|
|
+
|
|
|
+ hit, dist = ray_aabb_intersect(origin, dir, box_min, box_max)
|
|
|
+
|
|
|
+ case .Sphere:
|
|
|
+ // Для сферы берем максимальный масштаб по осям, чтобы она оставалась сферой
|
|
|
+ max_scale := math.max(t.scale.x, math.max(t.scale.y, t.scale.z))
|
|
|
+ scaled_radius := c.radius * max_scale
|
|
|
+
|
|
|
+ hit, dist = ray_sphere_intersect(origin, dir, t.position, scaled_radius)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Если попали и это попадание ближе предыдущего
|
|
|
+ if hit && dist < closest_dist {
|
|
|
+ closest_dist = dist
|
|
|
+ hit_id = ent.id
|
|
|
+ has_hit = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return hit_id, closest_dist, has_hit
|
|
|
+}
|
|
|
+
|
|
|
// --- Initialization ---
|
|
|
|
|
|
init :: proc "c" () {
|
|
|
@@ -529,11 +622,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.debug_line_mesh = debug_make_cube_lines(state.allocator)
|
|
|
state.entities = make([dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
|
|
|
state.sprites = make([dynamic]Sprite, 0, 16, state.allocator)
|
|
|
|
|
|
@@ -545,26 +636,128 @@ init :: proc "c" () {
|
|
|
ui_pip_desc.colors[0].blend.enabled = true
|
|
|
ui_pip_desc.colors[0].blend.src_factor_rgb = .SRC_ALPHA
|
|
|
ui_pip_desc.colors[0].blend.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
|
|
|
+ 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
|
|
|
+ grid_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
+ state.grid_pip = sgl.make_pipeline(grid_pip_desc)
|
|
|
+
|
|
|
+ // 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.index_type = .UINT16
|
|
|
scene_pip_desc.depth.write_enabled = true
|
|
|
scene_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
- scene_pip_desc.cull_mode = .BACK
|
|
|
- state.scene_pip = sgl.make_pipeline(scene_pip_desc)
|
|
|
+ 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)
|
|
|
|
|
|
state.pass_action = {
|
|
|
- colors = {0 = {load_action = .CLEAR, clear_value = {0.9, 0.9, 0.95, 1}}},
|
|
|
+ colors = {0 = {load_action = .CLEAR, clear_value = {0.45, 0.45, 0.5, 1}}},
|
|
|
+ depth = {load_action = .CLEAR, clear_value = 1.0, store_action = .STORE},
|
|
|
}
|
|
|
|
|
|
- state.player_ent = push_entity()
|
|
|
- add_transofrm_component(state.player_ent)
|
|
|
+ state.player_ent_id = push_entity()
|
|
|
+ add_transofrm_component(state.player_ent_id)
|
|
|
+
|
|
|
+ // Garden_Grid
|
|
|
+ {
|
|
|
+ for x in 0 ..< 10 {
|
|
|
+ for z in 0 ..< 10 {
|
|
|
+ offset := Vec3{0.1 * f32(x), 0.0, 0.1 * f32(z)}
|
|
|
+ ent_id := push_entity()
|
|
|
+ t := add_transofrm_component(ent_id)
|
|
|
+ t.position = {(1.0 * f32(x)) + offset.x, -0.5, (1.0 * f32(z)) + offset.z}
|
|
|
+ t.scale.y = 0.1
|
|
|
+
|
|
|
+ add_collider_component(
|
|
|
+ ent_id,
|
|
|
+ Collider_Component {
|
|
|
+ type = .AABB,
|
|
|
+ half = {0.5, 0.5, 0.5}, // Половина размера куба (т.к. меш от -0.5 до 0.5)
|
|
|
+ },
|
|
|
+ )
|
|
|
+ append(&state.garden_grid.items, ent_id)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
state.camera.dist = 10.0; state.camera.pitch = 0.5; state.selected_color = {0.2, 0.5, 1.0}
|
|
|
}
|
|
|
|
|
|
// --- Frame ---
|
|
|
|
|
|
+// --- Debug Rendering ---
|
|
|
+
|
|
|
+// Рисует каркасную сферу (3 кольца)
|
|
|
+// Основная функция отрисовки всех коллайдеров
|
|
|
+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.index_type = .UINT16
|
|
|
+ debug_pip_desc.depth.write_enabled = false
|
|
|
+ debug_pip_desc.depth.compare = .LESS_EQUAL
|
|
|
+ debug_pip_desc.cull_mode = .NONE
|
|
|
+ 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 {
|
|
|
+ t, t_ok := get_transform_component(ent.id)
|
|
|
+ c, c_ok := get_collider_component(ent.id)
|
|
|
+
|
|
|
+ if !t_ok || !c_ok do continue
|
|
|
+
|
|
|
+ switch c.type {
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
frame :: proc "c" () {
|
|
|
context = runtime.default_context()
|
|
|
defer free_all(context.temp_allocator)
|
|
|
@@ -573,7 +766,38 @@ frame :: proc "c" () {
|
|
|
|
|
|
system_input_player(dt)
|
|
|
view, proj := get_camera_matrices()
|
|
|
- system_render(view, proj)
|
|
|
+
|
|
|
+ // Логика выбора сущности по клику (ЛКМ)
|
|
|
+ if state.input.left_clicked && !state.input.locked {
|
|
|
+ // 1. Получаем луч из позиции мыши
|
|
|
+ mx := state.input.mouse_x
|
|
|
+ my := state.input.mouse_y
|
|
|
+ ro, rd := get_mouse_ray(mx, my, view, proj)
|
|
|
+
|
|
|
+ // 2. Делаем рейкаст
|
|
|
+ hit_id, dist, hit := raycast(ro, rd)
|
|
|
+
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Garden_Grid
|
|
|
+ {
|
|
|
+ for id in state.garden_grid.items {
|
|
|
+ t, is_found := get_transform_component(id)
|
|
|
+ if !is_found do continue
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
sgl.defaults(); sgl.push_pipeline(); sgl.load_pipeline(state.ui_pip)
|
|
|
sgl.matrix_mode_projection(); sgl.push_matrix(); sgl.load_identity()
|
|
|
@@ -595,7 +819,62 @@ 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)
|
|
|
+ sgl.c3f(0.8, 0.0, 0.0)
|
|
|
+ sgl.v3f(-half, y, 0); sgl.v3f(half, y, 0)
|
|
|
+ // Z axis (blue)
|
|
|
+ sgl.c3f(0.0, 0.0, 0.8)
|
|
|
+ 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)
|
|
|
+ sgl.matrix_mode_projection()
|
|
|
+ sgl.ortho(0.0, w, h, 0.0, -1.0, +1.0)
|
|
|
+ sgl.matrix_mode_modelview()
|
|
|
sgl.draw()
|
|
|
+
|
|
|
sg.end_pass()
|
|
|
sg.commit()
|
|
|
|
|
|
@@ -609,8 +888,9 @@ system_input_player :: proc(dt: f32) {
|
|
|
SPEED: f32 : 10.0 // units per second
|
|
|
SENSITIVITY: f32 : 0.002 // radians per pixel
|
|
|
|
|
|
- player := state.player_ent
|
|
|
- transform := get_transform_component(player)
|
|
|
+ transform, is_found := get_transform_component(state.player_ent_id)
|
|
|
+ if !is_found do return
|
|
|
+
|
|
|
|
|
|
if state.input.locked {
|
|
|
dx := f32(state.input.mouse_dx)
|
|
|
@@ -643,91 +923,147 @@ system_input_player :: proc(dt: f32) {
|
|
|
|
|
|
system_render :: proc(view, proj: linalg.Matrix4f32) {
|
|
|
v := view; p := proj
|
|
|
- sgl.defaults()
|
|
|
- sgl.push_pipeline()
|
|
|
- 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_thick(100, 1.0, 0.05)
|
|
|
-
|
|
|
- for i in 0 ..< len(state.entities) {
|
|
|
- ent := &state.entities[i]
|
|
|
- ent_transform := get_transform_component(ent)
|
|
|
- draw_mesh_instance(
|
|
|
- &state.mesh_cube,
|
|
|
- ent_transform.position,
|
|
|
- ent_transform.scale,
|
|
|
- {1.0, 0.0, 1.0},
|
|
|
- )
|
|
|
+ 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},
|
|
|
+ index_buffer = state.mesh_cube.ibuf,
|
|
|
+ }
|
|
|
+ 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)
|
|
|
}
|
|
|
-
|
|
|
- sgl.pop_pipeline()
|
|
|
}
|
|
|
|
|
|
-has_component :: proc(ent: ^Entity, comp_id: Component_Type) -> bool {
|
|
|
- return comp_id in ent.components
|
|
|
+has_component :: proc(ent_id: Entity_Id, comp_type: Component_Type) -> bool {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+
|
|
|
+ return comp_type in ent_ref.components
|
|
|
}
|
|
|
|
|
|
-remove_component :: proc(ent: ^Entity, comp_id: Component_Type) {
|
|
|
- ent.components -= {comp_id}
|
|
|
+remove_component :: proc(ent_id: Entity_Id, comp_type: Component_Type) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ ent_ref.components -= {comp_type}
|
|
|
}
|
|
|
|
|
|
add_transofrm_component :: proc(
|
|
|
- ent: ^Entity,
|
|
|
- comp: Transform_Component = {{}, {}, {1.0, 1.0, 1.0}},
|
|
|
-) {
|
|
|
- ent.components += {.Transform}
|
|
|
- inject_at(&state.transform_components, ent.id, comp)
|
|
|
+ ent_id: Entity_Id,
|
|
|
+ comp: Transform_Component = {position = {}, rotation = {}, scale = {1.0, 1.0, 1.0}},
|
|
|
+) -> ^Transform_Component {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ ent_ref.components += {.Transform}
|
|
|
+ inject_at(&transform_components, ent_ref.id, comp)
|
|
|
+
|
|
|
+ return &transform_components[ent_ref.id]
|
|
|
}
|
|
|
|
|
|
-get_transform_component :: proc(ent: ^Entity) -> ^Transform_Component {
|
|
|
- if !has_component(ent, Component_Type.Transform) {
|
|
|
- transform_nil = {}
|
|
|
- return &transform_nil
|
|
|
+get_transform_component :: proc(ent_id: Entity_Id) -> (^Transform_Component, bool) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ comp_ref := &transform_components[ent_ref.id]
|
|
|
+
|
|
|
+ if !has_component(ent_ref.id, Component_Type.Transform) {
|
|
|
+ comp_ref = {}
|
|
|
+ return comp_ref, false
|
|
|
}
|
|
|
- return &state.transform_components[ent.id]
|
|
|
+
|
|
|
+ return comp_ref, true
|
|
|
}
|
|
|
|
|
|
-add_physics_component :: proc(ent: ^Entity, comp: Physics_Component = {}) {
|
|
|
- ent.components += {.Physics}
|
|
|
- inject_at(&state.physics_components, ent.id, comp)
|
|
|
+add_physics_component :: proc(ent_id: Entity_Id, comp: Physics_Component = {}) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ ent_ref.components += {.Physics}
|
|
|
+ inject_at(&physics_components, ent_ref.id, comp)
|
|
|
}
|
|
|
|
|
|
-get_physics_component :: proc(ent: ^Entity) -> ^Physics_Component {
|
|
|
- if !has_component(ent, Component_Type.Physics) {
|
|
|
- return nil
|
|
|
+get_physics_component :: proc(ent_id: Entity_Id) -> (^Physics_Component, bool) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ comp_ref := &physics_components[ent_ref.id]
|
|
|
+
|
|
|
+ if !has_component(ent_ref.id, Component_Type.Physics) {
|
|
|
+ comp_ref = {}
|
|
|
+ return comp_ref, false
|
|
|
}
|
|
|
- return &state.physics_components[ent.id]
|
|
|
+
|
|
|
+ return comp_ref, true
|
|
|
}
|
|
|
|
|
|
-add_garden_component :: proc(ent: ^Entity, comp: Garden_Component) {
|
|
|
- ent.components += {.Garden}
|
|
|
- inject_at(&state.garde_components, ent.id, comp)
|
|
|
+add_garden_component :: proc(ent_id: Entity_Id, comp: Garden_Component) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ ent_ref.components += {.Garden}
|
|
|
+ inject_at(&garden_components, ent_ref.id, comp)
|
|
|
}
|
|
|
|
|
|
-get_garden_component :: proc(ent: ^Entity) -> ^Garden_Component {
|
|
|
- if !has_component(ent, Component_Type.Garden) {
|
|
|
+get_garden_component :: proc(ent_id: Entity_Id) -> ^Garden_Component {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+
|
|
|
+ if !has_component(ent_ref.id, Component_Type.Garden) {
|
|
|
return nil
|
|
|
}
|
|
|
- return &state.garde_components[ent.id]
|
|
|
+
|
|
|
+ return &garden_components[ent_ref.id]
|
|
|
}
|
|
|
|
|
|
-add_collider_aabb_component :: proc(ent: ^Entity, comp: ColliderAABB_Component) {
|
|
|
- ent.components += {.ColliderAABB}
|
|
|
- inject_at(&state.collider_aabb_components, ent.id, comp)
|
|
|
+add_collider_component :: proc(ent_id: Entity_Id, comp: Collider_Component) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ ent_ref.components += {.Collider}
|
|
|
+ inject_at(&collider_components, ent_ref.id, comp)
|
|
|
}
|
|
|
|
|
|
-get_collider_aabb_component :: proc(ent: ^Entity) -> ^ColliderAABB_Component {
|
|
|
- if !has_component(ent, Component_Type.ColliderAABB) {
|
|
|
- return nil
|
|
|
+get_collider_component :: proc(ent_id: Entity_Id) -> (^Collider_Component, bool) {
|
|
|
+ using state
|
|
|
+
|
|
|
+ ent_ref := &entities[ent_id]
|
|
|
+ comp_ref := &collider_components[ent_ref.id]
|
|
|
+
|
|
|
+ if !has_component(ent_ref.id, Component_Type.Collider) {
|
|
|
+ comp_ref = {}
|
|
|
+ return comp_ref, false
|
|
|
}
|
|
|
- return &state.collider_aabb_components[ent.id]
|
|
|
+
|
|
|
+ return &collider_components[ent_ref.id], true
|
|
|
}
|
|
|
|
|
|
get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
|
|
|
if len(state.entities) == 0 do return linalg.MATRIX4F32_IDENTITY, linalg.MATRIX4F32_IDENTITY
|
|
|
- transform := get_transform_component(state.player_ent)
|
|
|
+ transform, is_found := get_transform_component(state.player_ent_id)
|
|
|
+ if !is_found do return
|
|
|
cam_offset :=
|
|
|
Vec3 {
|
|
|
math.cos(state.camera.pitch) * math.sin(state.camera.yaw),
|
|
|
@@ -754,14 +1090,14 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
m.vertices = make(#soa[dynamic]Vertex, 0, 24, allocator)
|
|
|
m.indices = make([dynamic]u16, 0, 36, allocator)
|
|
|
|
|
|
- add_face :: proc(m: ^Mesh, p1, p2, p3, p4: [3]f32, color: [3]f32) {
|
|
|
+ add_face :: proc(m: ^Mesh, p1, p2, p3, p4: [3]f32, normal: [3]f32, color: [3]f32) {
|
|
|
start_idx := u16(len(m.vertices))
|
|
|
append_soa(
|
|
|
&m.vertices,
|
|
|
- Vertex{p1, color},
|
|
|
- Vertex{p2, color},
|
|
|
- Vertex{p3, color},
|
|
|
- Vertex{p4, color},
|
|
|
+ Vertex{p1, normal, color},
|
|
|
+ Vertex{p2, normal, color},
|
|
|
+ Vertex{p3, normal, color},
|
|
|
+ Vertex{p4, normal, color},
|
|
|
)
|
|
|
// Индексы: два треугольника, CCW
|
|
|
append(
|
|
|
@@ -784,6 +1120,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
{0.5, -0.5, 0.5}, // bottom-right
|
|
|
{0.5, 0.5, 0.5}, // top-right
|
|
|
{-0.5, 0.5, 0.5}, // top-left
|
|
|
+ {0, 0, 1}, // normal
|
|
|
c,
|
|
|
)
|
|
|
|
|
|
@@ -794,6 +1131,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
{-0.5, -0.5, -0.5}, // bottom-left
|
|
|
{-0.5, 0.5, -0.5}, // top-left
|
|
|
{0.5, 0.5, -0.5}, // top-right
|
|
|
+ {0, 0, -1}, // normal
|
|
|
c,
|
|
|
)
|
|
|
|
|
|
@@ -804,6 +1142,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
{0.5, 0.5, 0.5}, // front-right
|
|
|
{0.5, 0.5, -0.5}, // back-right
|
|
|
{-0.5, 0.5, -0.5}, // back-left
|
|
|
+ {0, 1, 0}, // normal
|
|
|
c,
|
|
|
)
|
|
|
|
|
|
@@ -814,6 +1153,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
{0.5, -0.5, -0.5}, // back-right
|
|
|
{0.5, -0.5, 0.5}, // front-right
|
|
|
{-0.5, -0.5, 0.5}, // front-left
|
|
|
+ {0, -1, 0}, // normal
|
|
|
c,
|
|
|
)
|
|
|
|
|
|
@@ -824,6 +1164,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
{0.5, -0.5, -0.5}, // back-bottom
|
|
|
{0.5, 0.5, -0.5}, // back-top
|
|
|
{0.5, 0.5, 0.5}, // front-top
|
|
|
+ {1, 0, 0}, // normal
|
|
|
c,
|
|
|
)
|
|
|
|
|
|
@@ -834,33 +1175,66 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
|
|
|
{-0.5, -0.5, 0.5}, // front-bottom
|
|
|
{-0.5, 0.5, 0.5}, // front-top
|
|
|
{-0.5, 0.5, -0.5}, // back-top
|
|
|
+ {-1, 0, 0}, // normal
|
|
|
c,
|
|
|
)
|
|
|
|
|
|
return m
|
|
|
}
|
|
|
|
|
|
+upload_mesh :: proc(m: ^Mesh, allocator := context.temp_allocator) {
|
|
|
+ // SOA layout needs to be converted to AOS for GPU
|
|
|
+ // 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],
|
|
|
+ normal = m.vertices.normal[i],
|
|
|
+ color = m.vertices.color[i],
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Upload vertices
|
|
|
+ m.vbuf = sg.make_buffer(
|
|
|
+ {
|
|
|
+ data = {ptr = raw_data(vertex_data), size = len(vertex_data) * size_of(Vertex)},
|
|
|
+ usage = {vertex_buffer = true, immutable = true},
|
|
|
+ },
|
|
|
+ )
|
|
|
+
|
|
|
+ // Upload indices
|
|
|
+ m.ibuf = sg.make_buffer(
|
|
|
+ {
|
|
|
+ data = {ptr = raw_data(m.indices), size = len(m.indices) * size_of(u16)},
|
|
|
+ usage = {index_buffer = true, immutable = true},
|
|
|
+ },
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
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}
|
|
|
+ // Зеленый цвет для коллайдеров
|
|
|
+ green := [3]f32{0, 1, 0}
|
|
|
+ zero_normal := [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: лево-низ-перед
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, -0.5, -0.5}, zero_normal, green}) // 0: лево-низ-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, -0.5, -0.5}, zero_normal, green}) // 1: право-низ-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, -0.5, 0.5}, zero_normal, green}) // 2: право-низ-перед
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, -0.5, 0.5}, zero_normal, green}) // 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_soa(&m.vertices, Vertex{{-0.5, 0.5, -0.5}, zero_normal, green}) // 4: лево-верх-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, 0.5, -0.5}, zero_normal, green}) // 5: право-верх-зад
|
|
|
+ append_soa(&m.vertices, Vertex{{0.5, 0.5, 0.5}, zero_normal, green}) // 6: право-верх-перед
|
|
|
+ append_soa(&m.vertices, Vertex{{-0.5, 0.5, 0.5}, zero_normal, green}) // 7: лево-верх-перед
|
|
|
|
|
|
// Соединяем линиями (пары индексов)
|
|
|
append(
|
|
|
@@ -933,53 +1307,6 @@ draw_mesh_instance :: proc(m: ^Mesh, pos: Vec3, scale: Vec3, tint: Vec3) {
|
|
|
sgl.pop_matrix()
|
|
|
}
|
|
|
|
|
|
-draw_grid_thick :: proc(slices: int, spacing: f32, thickness: f32) {
|
|
|
- half := f32(slices) * spacing * 0.5
|
|
|
- y: f32 = -0.5
|
|
|
-
|
|
|
- for i in 0 ..= slices {
|
|
|
- p := -half + f32(i) * spacing
|
|
|
-
|
|
|
- // Центральные оси
|
|
|
- if math.abs(p) < 0.0001 {
|
|
|
- // X axis (red)
|
|
|
- draw_thick_line(
|
|
|
- Vec3{-half, y + 0.001, 0},
|
|
|
- Vec3{half, y, 0},
|
|
|
- thickness * 2,
|
|
|
- Vec3{0.8, y, 0.2},
|
|
|
- )
|
|
|
- // Z axis (blue)
|
|
|
- draw_thick_line(Vec3{0, y, -half}, Vec3{0, y, half}, thickness * 2, Vec3{0.2, y, 0.8})
|
|
|
- continue
|
|
|
- }
|
|
|
-
|
|
|
- // Обычные линии сетки (серые)
|
|
|
- draw_thick_line(Vec3{-half, y, p}, Vec3{half, y, p}, thickness, Vec3{0.35, 0.35, 0.35})
|
|
|
- draw_thick_line(Vec3{p, y, -half}, Vec3{p, y, half}, thickness, Vec3{0.35, 0.35, 0.35})
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-draw_thick_line :: proc(p0: Vec3, p1: Vec3, thickness: f32, color: Vec3) {
|
|
|
- dir := linalg.normalize(p1 - p0)
|
|
|
- right := Vec3{-dir.z, 0, dir.x} * (thickness * 0.5) // перпендикуляр в XZ
|
|
|
-
|
|
|
- sgl.c3f(color.x, color.y, color.z)
|
|
|
-
|
|
|
- sgl.begin_triangles()
|
|
|
- // Первая тройка вершин
|
|
|
- sgl.v3f(p0.x + right.x, p0.y + right.y, p0.z + right.z)
|
|
|
- sgl.v3f(p1.x + right.x, p1.y + right.y, p1.z + right.z)
|
|
|
- sgl.v3f(p1.x - right.x, p1.y - right.y, p1.z - right.z)
|
|
|
-
|
|
|
- // Вторая тройка вершин
|
|
|
- sgl.v3f(p1.x - right.x, p1.y - right.y, p1.z - right.z)
|
|
|
- sgl.v3f(p0.x - right.x, p0.y - right.y, p0.z - right.z)
|
|
|
- sgl.v3f(p0.x + right.x, p0.y + right.y, p0.z + right.z)
|
|
|
- sgl.end()
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
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()
|
|
|
@@ -1049,6 +1376,7 @@ main :: proc() {
|
|
|
logger = {func = slog.func},
|
|
|
high_dpi = true,
|
|
|
swap_interval = 1,
|
|
|
+ sample_count = 4,
|
|
|
},
|
|
|
)
|
|
|
}
|