0xc3 2 nedēļas atpakaļ
vecāks
revīzija
6505f41538
3 mainītis faili ar 510 papildinājumiem un 167 dzēšanām
  1. 0 2
      Makefile
  2. 20 3
      res/shaders/3d.glsl
  3. 490 162
      src/cmd/sandbox/main.odin

+ 0 - 2
Makefile

@@ -2,7 +2,5 @@ build_third_party:
 	cd ./third-party/sokol && ./build_clibs_linux.sh
 
 run_sandbox_debug:
-	# TODO
-	# ./tools/sokol-shdc -i ./res/shaders/2d.glsl -o ./src/cmd/sandbox/2d_shader_gen.odin -l glsl430 -f sokol_odin
 	./tools/sokol-shdc -i ./res/shaders/3d.glsl -o ./src/cmd/sandbox/3d_shader_gen.odin -l glsl430 -f sokol_odin
 	odin run src/cmd/sandbox -o:none -debug -collection:huginn=src -collection:third-party=third-party

+ 20 - 3
res/shaders/3d.glsl

@@ -4,24 +4,41 @@
 @header import m "huginn:core/math"
 
 @vs vs
+layout(binding=0) uniform vs_params {
+    mat4 mvp;
+};
+
 in vec4 position;
+in vec3 normal0;
 in vec4 color0;
 
 out vec4 color;
+out vec3 normal;
 
 void main() {
-    gl_Position = position;
+    gl_Position = mvp * position;
+    normal = normal0;
     color = color0;
 }
 @end
 
 @fs fs
 in vec4 color;
+in vec3 normal;
 out vec4 frag_color;
 
 void main() {
-    frag_color = color;
+    // Простое освещение по нормалям
+    vec3 light_dir = normalize(vec3(0.5, 1.0, 0.3));
+    vec3 norm = normalize(normal);
+    float diff = max(dot(norm, light_dir), 0.0);
+    
+    // Ambient + diffuse
+    float ambient = 0.3;
+    float lighting = ambient + diff * 0.7;
+    
+    frag_color = vec4(color.rgb * lighting, color.a);
 }
 @end
 
-@program triangle vs fs
+@program scene vs fs

+ 490 - 162
src/cmd/sandbox/main.odin

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