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

+ 199 - 496
src/cmd/sandbox/main.odin

@@ -1,6 +1,7 @@
 package main
 
 import "base:runtime"
+import "core:encoding/uuid"
 import "core:fmt"
 import "core:image/png"
 import "core:math"
@@ -86,25 +87,6 @@ Item_Info :: struct {
 // Global Item Database (mutable to set sprite_ids at runtime)
 item_db: [Item_Kind]Item_Info
 
-Box_Id :: distinct u32
-
-Box_Slot :: struct {
-	kind:  Item_Kind,
-	count: u32,
-}
-
-Box_System :: struct {
-	is_open: bool,
-	slots:   #soa[dynamic]Box_Slot,
-}
-
-Drag_State :: struct {
-	active:      bool,
-	item:        Box_Slot,
-	source_box:  Box_Id,
-	source_slot: int,
-}
-
 // --- UI System Types ---
 
 Font_Atlas :: struct {
@@ -132,16 +114,12 @@ UI_Context :: struct {
 	line_height:   f32,
 }
 
-Entity_Kind :: enum u8 {
-	Player,
-	Enemy,
-	Cube,
+ComponentID :: enum u8 {
+	Transform,
+	Physics,
+	Render,
 	Garden,
-}
-
-AABB :: struct {
-	min: Vec3,
-	max: Vec3,
+	ColliderAABB,
 }
 
 Garden_State :: enum u8 {
@@ -152,16 +130,31 @@ Garden_State :: enum u8 {
 
 Entity_Id :: distinct u32
 
+ColliderAABB_Component :: struct {
+	min: Vec3,
+	max: Vec3,
+}
+
+Garden_Component :: struct {
+	state: Garden_State,
+}
+
+Transform_Component :: struct {
+	position: Vec3,
+	rotation: Vec3,
+	scale:    Vec3,
+}
+transform_nil := Transform_Component{{}, {}, {1.0, 1.0, 1.0}}
+
+Physics_Component :: struct {
+	velocity: Vec3,
+}
+
+Render_Component :: struct {}
+
 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,
+	id:         Entity_Id,
+	components: u64,
 }
 
 Camera :: struct {
@@ -182,26 +175,31 @@ 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,
-	box_sys:            #soa[dynamic]Box_System,
-	entities:           #soa[dynamic]Entity,
-	player_id:          Entity_Id,
-	dpi_scale:          f32,
-	camera:             Camera,
-	last_input:         Input_State,
-	input:              Input_State,
-	selected_color:     [3]f32,
-	active_hotbar_slot: int,
-	drag:               Drag_State,
-	sprites:            [dynamic]Sprite,
-	debug_cube_mesh:    Mesh,
-	debug_line_mesh:    Mesh,
+	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,
+	// 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,
+	//
+	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,43 +477,12 @@ ui_slider :: proc(label: string, val: ^f32, min_v, max_v: f32) {
 
 // --- Game Logic ---
 
-push_box :: proc(size: u32) -> Box_Id {
-	box := Box_System {
-		slots = make(#soa[dynamic]Box_Slot, 0, size),
-	}
-	for i in 0 ..< size do append_soa(&box.slots, Box_Slot{.None, 0})
-	id := len(state.box_sys)
-	append_soa(&state.box_sys, box)
-	return Box_Id(id)
-}
-
-push_entity :: proc(
-	kind: Entity_Kind,
-	pos: Vec3 = {0.0, 0.0, 0.0},
-	scale: Vec3 = {1.0, 1.0, 1.0},
-) -> Entity_Id {
-	e := Entity {
-		pos = pos,
-		kind = kind,
-		scale = scale,
-		collider = AABB{min = {-0.5, -0.5, -0.5}, max = {0.5, 0.5, 0.5}},
+push_entity :: proc() -> ^Entity {
+	ent := Entity {
+		id = Entity_Id(len(state.entities)),
 	}
-	switch kind {
-	case .Player:
-		e.color = {0.2, 0.8, 0.2}
-		e.is_physics = true
-	case .Enemy:
-		e.color = {0.8, 0.3, 0.3}
-		e.is_physics = true
-	case .Cube:
-		e.color = state.selected_color
-		e.is_physics = true
-	case .Garden:
-		e.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0}
-	}
-	id := len(state.entities)
-	append_soa(&state.entities, e)
-	return Entity_Id(id)
+	append(&state.entities, ent)
+	return &state.entities[ent.id]
 }
 
 // Проверка пересечения луча и AABB (Axis Aligned Bounding Box)
@@ -565,8 +532,7 @@ init :: proc "c" () {
 
 	// 1. Сначала инициализируем массивы (включая sprites)
 	state.mesh_cube = make_cube_mesh(state.allocator)
-	state.entities = make(#soa[dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
-	state.box_sys = make(#soa[dynamic]Box_System, 0, MAX_BOX, state.allocator)
+	state.entities = make([dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
 	state.sprites = make([dynamic]Sprite, 0, 16, state.allocator)
 
 	// 2. Затем загружаем ресурсы (шрифты и спрайты предметов)
@@ -582,41 +548,15 @@ init :: proc "c" () {
 	scene_pip_desc: sg.Pipeline_Desc
 	scene_pip_desc.depth.write_enabled = true
 	scene_pip_desc.depth.compare = .LESS_EQUAL
-	scene_pip_desc.cull_mode = .FRONT
+	scene_pip_desc.cull_mode = .BACK
 	state.scene_pip = sgl.make_pipeline(scene_pip_desc)
 
 	state.pass_action = {
 		colors = {0 = {load_action = .CLEAR, clear_value = {0.9, 0.9, 0.95, 1}}},
 	}
 
-	state.player_id = push_entity(.Player)
-	player_ref := &state.entities[state.player_id]
-	player_ref.box_id = push_box(INVENTORY_SIZE)
-
-	state.box_sys[player_ref.box_id].slots[0] = Box_Slot {
-		kind  = .Shovel,
-		count = 1,
-	}
-	state.box_sys[player_ref.box_id].slots[1] = Box_Slot {
-		kind  = .Sword,
-		count = 1,
-	}
-	state.box_sys[player_ref.box_id].slots[2] = Box_Slot {
-		kind  = .Potion,
-		count = 5,
-	}
-
-	// TODO:
-	for x in 0 ..< 10 {
-		for z in 0 ..< 10 {
-			offset := Vec2{0.1 * f32(x), 0.1 * f32(z)}
-			push_entity(
-				.Garden,
-				{0.0 + (1.0 * f32(x)) + offset.x, 0.0, 0.0 + (1.0 * f32(z)) + offset.y},
-				{1.0, 0.1, 1.0},
-			)
-		}
-	}
+	state.player_ent = push_entity()
+	add_transofrm_component(state.player_ent)
 
 	state.camera.dist = 10.0; state.camera.pitch = 0.5; state.selected_color = {0.2, 0.5, 1.0}
 }
@@ -630,11 +570,7 @@ frame :: proc "c" () {
 	dt := f32(sapp.frame_duration())
 
 	system_input_player(dt)
-	system_physics(dt)
 	view, proj := get_camera_matrices()
-	system_interaction(view, proj)
-	system_builder(view, proj)
-
 	system_render(view, proj)
 
 	sgl.defaults(); sgl.push_pipeline(); sgl.load_pipeline(state.ui_pip)
@@ -654,34 +590,6 @@ frame :: proc "c" () {
 	ui_window_begin("Stats", 20, 20, 220, 150)
 	ui_label(fmt.tprintf("FPS: %.0f", 1.0 / dt))
 	ui_label(fmt.tprintf("Entities: %d", len(state.entities)))
-	if ui_button("Spawn Enemy") do push_entity(.Enemy, {0, 10, 0})
-
-	ui_window_begin("Builder", 20, 200, 250, 180)
-	ui_slider("Red", &state.selected_color.x, 0, 1)
-	ui_slider("Green", &state.selected_color.y, 0, 1)
-	ui_slider("Blue", &state.selected_color.z, 0, 1)
-	ui_draw_rect(
-		state.ui.curr_x,
-		state.ui.curr_y,
-		state.ui.curr_w,
-		30,
-		{state.selected_color.x, state.selected_color.y, state.selected_color.z, 1},
-	)
-
-	player_box_id := state.entities[state.player_id].box_id
-	draw_hud(w, h)
-	if state.box_sys[player_box_id].is_open do draw_inventory(player_box_id, w, h)
-
-	if state.drag.active {
-		info := item_db[state.drag.item.kind]
-		mx, my := state.ui.mouse_pos.x, state.ui.mouse_pos.y
-		if info.sprite_id >= 0 {
-			ui_draw_sprite(info.sprite_id, mx - 20, my - 20, 40, 40)
-		} else {
-			ui_draw_rect(mx - 20, my - 20, 40, 40, info.color)
-		}
-		ui_draw_text(fmt.tprintf("%d", state.drag.item.count), mx, my, UI_COLOR_TEXT)
-	}
 
 	sgl.pop_matrix(); sgl.matrix_mode_projection(); sgl.pop_matrix(); sgl.pop_pipeline()
 	sg.begin_pass({action = state.pass_action, swapchain = sglue.swapchain()})
@@ -693,378 +601,130 @@ frame :: proc "c" () {
 	state.input.mouse_dx = 0; state.input.mouse_dy = 0; state.input.left_clicked = false
 }
 
-draw_inventory :: proc(box_id: Box_Id, screen_w, screen_h: f32) {
-	slots := &state.box_sys[box_id].slots
-	row_width := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
-	main_h := f32(INVENTORY_MAIN_ROWS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
-	win_padding := f32(10.0)
-	win_w := row_width + win_padding * 2
-	win_h := main_h + win_padding * 2 + 20
-	start_x := (screen_w - win_w) / 2
-	start_y := (screen_h - win_h) / 2 - 50.0
-
-	ui_draw_rect(start_x, start_y, win_w, win_h, UI_COLOR_WINDOW_BG)
-	ui_draw_outline(start_x, start_y, win_w, win_h, 1.0, UI_COLOR_BORDER)
-	ui_draw_text("Backpack", start_x + win_padding, start_y + 5, UI_COLOR_TEXT)
-
-	ctx := &state.ui
-	grid_start_x := start_x + win_padding
-	grid_start_y := start_y + 30
-	slots_count := INVENTORY_MAIN_ROWS * INVENTORY_COLS
-
-	for i in 0 ..< slots_count {
-		if i >= len(slots) do break
-		col := i % INVENTORY_COLS; row := i / INVENTORY_COLS
-		sx := grid_start_x + f32(col) * (SLOT_SIZE + SLOT_SPACING)
-		sy := grid_start_y + f32(row) * (SLOT_SIZE + SLOT_SPACING)
-		slot := &slots[i]
-		hovered :=
-			ctx.mouse_pos.x >= sx &&
-			ctx.mouse_pos.x < sx + SLOT_SIZE &&
-			ctx.mouse_pos.y >= sy &&
-			ctx.mouse_pos.y < sy + SLOT_SIZE
-
-		bg_color := UI_COLOR_SLOT_BG
-		if hovered do bg_color = UI_COLOR_SLOT_HOVER
-		ui_draw_rect(sx, sy, SLOT_SIZE, SLOT_SIZE, bg_color)
-		ui_draw_outline(sx, sy, SLOT_SIZE, SLOT_SIZE, 1.0, {0.7, 0.7, 0.7, 1.0})
-
-		if slot.kind != .None {
-			info := item_db[slot.kind]
-			pad := SLOT_PADDING
-			if info.sprite_id >= 0 {
-				ui_draw_sprite(
-					info.sprite_id,
-					sx + pad,
-					sy + pad,
-					SLOT_SIZE - pad * 2,
-					SLOT_SIZE - pad * 2,
-				)
-			} else {
-				ui_draw_rect(
-					sx + pad,
-					sy + pad,
-					SLOT_SIZE - pad * 2,
-					SLOT_SIZE - pad * 2,
-					info.color,
-				)
-			}
-			if slot.count > 1 {
-				count_str := fmt.tprintf("%d", slot.count)
-				tw := ui_measure_text(count_str)
-				ui_draw_text(
-					count_str,
-					sx + SLOT_SIZE - tw - 2,
-					sy + SLOT_SIZE - 12,
-					UI_COLOR_TEXT,
-				)
-			}
-		}
-
-		if hovered {
-			if ctx.mouse_pressed && !state.drag.active && slot.kind != .None {
-				state.drag.active =
-					true; state.drag.item = slot^; state.drag.source_box = box_id; state.drag.source_slot = i
-				slot.kind = .None; slot.count = 0
-			} else if !ctx.mouse_down && state.last_input.left_down && state.drag.active {
-				if slot.kind ==
-				   .None {slot^ = state.drag.item; state.drag.active = false} else if slot.kind == state.drag.item.kind {slot.count += state.drag.item.count; state.drag.active = false} else {temp := slot^; slot^ = state.drag.item; state.drag.item = temp}
-			}
-		}
-	}
-}
-
-draw_hud :: proc(screen_w, screen_h: f32) {
-	player_box_id := state.entities[state.player_id].box_id
-	slots := &state.box_sys[player_box_id].slots
-	start_slot_idx := INVENTORY_SIZE - INVENTORY_COLS
-	total_w := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
-	start_x := (screen_w - total_w) / 2.0
-	start_y := screen_h - SLOT_SIZE - 10.0
-	ctx := &state.ui
-
-	for i in 0 ..< INVENTORY_COLS {
-		idx := start_slot_idx + i
-		slot := &slots[idx]
-		sx := start_x + f32(i) * (SLOT_SIZE + SLOT_SPACING)
-		sy := start_y
-		hovered :=
-			ctx.mouse_pos.x >= sx &&
-			ctx.mouse_pos.x < sx + SLOT_SIZE &&
-			ctx.mouse_pos.y >= sy &&
-			ctx.mouse_pos.y < sy + SLOT_SIZE
-
-		ui_draw_rect(sx, sy, SLOT_SIZE, SLOT_SIZE, {0.9, 0.9, 0.9, 0.8})
-		ui_draw_outline(sx, sy, SLOT_SIZE, SLOT_SIZE, 1.0, UI_COLOR_BORDER)
-
-		if i == state.active_hotbar_slot {
-			ui_draw_outline(sx - 2, sy - 2, SLOT_SIZE + 4, SLOT_SIZE + 4, 3.0, UI_COLOR_ACCENT)
-		}
-
-		if slot.kind != .None {
-			info := item_db[slot.kind]
-			pad := SLOT_PADDING
-			if info.sprite_id >= 0 {
-				ui_draw_sprite(
-					info.sprite_id,
-					sx + pad,
-					sy + pad,
-					SLOT_SIZE - pad * 2,
-					SLOT_SIZE - pad * 2,
-				)
-			} else {
-				ui_draw_rect(
-					sx + pad,
-					sy + pad,
-					SLOT_SIZE - pad * 2,
-					SLOT_SIZE - pad * 2,
-					info.color,
-				)
-			}
-			if slot.count > 1 {
-				count_str := fmt.tprintf("%d", slot.count)
-				tw := ui_measure_text(count_str)
-				ui_draw_text(
-					count_str,
-					sx + SLOT_SIZE - tw - 2,
-					sy + SLOT_SIZE - 12,
-					UI_COLOR_TEXT,
-				)
-			}
-		}
-
-		if state.box_sys[player_box_id].is_open && hovered {
-			if ctx.mouse_pressed && !state.drag.active && slot.kind != .None {
-				state.drag.active =
-					true; state.drag.item = slot^; state.drag.source_box = player_box_id; state.drag.source_slot = idx
-				slot.kind = .None; slot.count = 0
-			} else if !ctx.mouse_down && state.last_input.left_down && state.drag.active {
-				if slot.kind ==
-				   .None {slot^ = state.drag.item; state.drag.active = false} else if slot.kind == state.drag.item.kind {slot.count += state.drag.item.count; state.drag.active = false} else {temp := slot^; slot^ = state.drag.item; state.drag.item = temp}
-			}
-		}
-	}
-}
-
-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
+system_input_player :: proc(dt: f32) {
+	if len(state.entities) == 0 do return
 
-		e := &state.entities[i]
+	SPEED: f32 : 10.0 // units per second
+	SENSITIVITY: f32 : 0.002 // radians per pixel
 
-		// Переводим локальный AABB в мировые координаты с учетом позиции и масштаба
-		world_min := e.pos + (e.collider.min * e.scale)
-		world_max := e.pos + (e.collider.max * e.scale)
+	player := state.player_ent
+	transform := get_transform_component(player)
 
-		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
-			}
-		}
-	}
+	if state.input.locked {
+		dx := f32(state.input.mouse_dx)
+		dy := f32(state.input.mouse_dy)
 
-	// 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
-		}
-	}
-}
+		state.camera.yaw -= dx * SENSITIVITY
+		state.camera.pitch += dy * SENSITIVITY
+		if state.camera.yaw > math.PI * 2 do state.camera.yaw -= math.PI * 2
+		if state.camera.yaw < 0 do state.camera.yaw += math.PI * 2
 
-system_input_player :: proc(dt: f32) {
-	if len(state.entities) == 0 do return
-	player := &state.entities[state.player_id]
-	SPEED :: 10.0
-	SENSITIVITY :: 0.15 * 0.02
 
-	if state.input.locked {
-		state.camera.yaw -= state.input.mouse_dx * SENSITIVITY
-		state.camera.pitch += state.input.mouse_dy * SENSITIVITY
-		state.camera.pitch = math.clamp(state.camera.pitch, -1.5, 1.5)
+		state.camera.pitch = math.clamp(state.camera.pitch, -math.PI * 0.49, math.PI * 0.49)
 	}
 
 	sin_yaw, cos_yaw := math.sincos(state.camera.yaw)
 	forward := Vec3{sin_yaw, 0, cos_yaw}
 	right := Vec3{cos_yaw, 0, -sin_yaw}
 
-	move_dir := Vec3{0, 0, 0}
+	move_dir := Vec3{}
 	if state.input.keys[.W] do move_dir -= forward
 	if state.input.keys[.S] do move_dir += forward
 	if state.input.keys[.A] do move_dir -= right
 	if state.input.keys[.D] do move_dir += right
 
-	if linalg.length2(move_dir) > 0.01 {
+	if linalg.length2(move_dir) > 0.0001 {
 		move_dir = linalg.normalize(move_dir)
-		player.vel.x = move_dir.x * SPEED
-		player.vel.z = move_dir.z * SPEED
-	} else {
-		player.vel.x = 0
-		player.vel.z = 0
+		transform.position += move_dir * SPEED * dt
 	}
+}
 
-	if state.input.keys[.SPACE] && player.pos.y <= 0.51 {
-		player.vel.y = 10.0
-	}
+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)
 
-	if state.input.keys[._1] do state.active_hotbar_slot = 0
-	if state.input.keys[._2] do state.active_hotbar_slot = 1
-	if state.input.keys[._3] do state.active_hotbar_slot = 2
-	if state.input.keys[._4] do state.active_hotbar_slot = 3
-	if state.input.keys[._5] do state.active_hotbar_slot = 4
-	if state.input.keys[._6] do state.active_hotbar_slot = 5
-	if state.input.keys[._7] do state.active_hotbar_slot = 6
-	if state.input.keys[._8] do state.active_hotbar_slot = 7
-	if state.input.keys[._9] do state.active_hotbar_slot = 8
-
-	if state.last_input.keys[.TAB] && !state.input.keys[.TAB] {
-		state.box_sys[player.box_id].is_open = !state.box_sys[player.box_id].is_open
-		state.input.locked = !state.box_sys[player.box_id].is_open
-		sapp.lock_mouse(state.input.locked)
+	draw_grid_thick(100, 1.0, 0.05)
 
-		if !state.box_sys[player.box_id].is_open && state.drag.active {
-			state.box_sys[state.drag.source_box].slots[state.drag.source_slot] = state.drag.item
-			state.drag.active = false
-		}
+	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},
+		)
 	}
-}
 
-system_builder :: proc(view, proj: linalg.Matrix4f32) {
-	if state.input.locked do return
-	if state.input.mouse_x < 300 do return
-	if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do return
+	sgl.pop_pipeline()
+}
 
-	if state.input.left_clicked {
-		origin, dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)
-		if hit_pos, hit := ray_plane_intersect(origin, dir, 0.5); hit {
-			push_entity(.Cube, hit_pos)
-		}
-	}
+has_component :: proc(ent: ^Entity, comp_id: ComponentID) -> bool {
+	return (ent.components & (1 << comp_id)) != 0
 }
 
-system_physics :: proc(dt: f32) {
-	GRAVITY :: 30.0
-	FLOOR_Y :: 0.5
-	for i in 0 ..< len(state.entities) {
-		e := &state.entities[i]
-		if !e.is_physics do continue
+remove_component :: proc(ent: ^Entity, comp_id: ComponentID) {
+	ent.components &= ~(1 << comp_id)
+}
 
-		e.vel.y -= GRAVITY * dt
-		e.pos += e.vel * dt
+add_transofrm_component :: proc(
+	ent: ^Entity,
+	comp: Transform_Component = {{}, {}, {1.0, 1.0, 1.0}},
+) {
+	ent.components |= (1 << ComponentID.Transform)
+	inject_at(&state.transform_components, ent.id, comp)
+}
 
-		if e.pos.y < FLOOR_Y {
-			e.pos.y = FLOOR_Y
-			if e.kind ==
-			   .Player {e.vel.y = 0} else {e.vel.y *= -0.8; e.vel.x *= 0.95; e.vel.z *= 0.95}
-		}
-		if e.pos.x > 50 do e.vel.x = -abs(e.vel.x)
-		if e.pos.x < -50 do e.vel.x = abs(e.vel.x)
-		if e.pos.z > 50 do e.vel.z = -abs(e.vel.z)
-		if e.pos.z < -50 do e.vel.z = abs(e.vel.z)
+get_transform_component :: proc(ent: ^Entity) -> ^Transform_Component {
+	if !has_component(ent, ComponentID.Transform) {
+		return &transform_nil
 	}
+	return &state.transform_components[ent.id]
 }
 
-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)
+add_physics_component :: proc(ent: ^Entity, comp: Physics_Component = {}) {
+	ent.components |= (1 << ComponentID.Physics)
+	// inject_at_soa(&state.physics_components, ent.id, comp)
+}
+
+get_physics_component :: proc(ent: ^Entity) -> ^Physics_Component {
+	if !has_component(ent, ComponentID.Physics) {
+		return nil
+	}
+	return &state.physics_components[ent.id]
+}
 
-	draw_grid(100, 1.0)
+add_garden_component :: proc(ent: ^Entity, comp: Garden_Component) {
+	ent.components |= (1 << ComponentID.Garden)
+	// inject_at_soa(&state.garde_components, ent.id, comp)
+}
 
-	for i in 0 ..< len(state.entities) {
-		e := &state.entities[i]
-
-		// 1. Рисуем сам цветной куб
-		draw_mesh_instance(&state.mesh_cube, e.pos, e.scale, e.color)
-
-		// 2. Если это Garden, рисуем черную обводку поверх
-		if e.kind == .Garden {
-			// offset сдвигает глубину линий чуть ближе к камере,
-			// чтобы они не проваливались внутрь куба (Z-fighting)
-			// sgl.offset(1.0, 1.0)
-			debug_draw_mesh_lines(&state.debug_line_mesh, e.pos, e.scale)
-			// sgl.offset(0.0, 0.0) // Обязательно сбрасываем обратно
-		}
+get_garden_component :: proc(ent: ^Entity) -> ^Garden_Component {
+	if !has_component(ent, ComponentID.Garden) {
+		return nil
 	}
+	return &state.garde_components[ent.id]
+}
 
-	sgl.pop_pipeline()
+add_collider_aabb_component :: proc(ent: ^Entity, comp: ColliderAABB_Component) {
+	ent.components |= (1 << ComponentID.ColliderAABB)
+	// inject_at_soa(&state.collider_aabb_components, ent.id, comp)
+}
+
+get_collider_aabb_component :: proc(ent: ^Entity) -> ^ColliderAABB_Component {
+	if !has_component(ent, ComponentID.ColliderAABB) {
+		return nil
+	}
+	return &state.collider_aabb_components[ent.id]
 }
 
 get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
 	if len(state.entities) == 0 do return linalg.MATRIX4F32_IDENTITY, linalg.MATRIX4F32_IDENTITY
-	player_pos := state.entities[state.player_id].pos
+	transform := get_transform_component(state.player_ent)
 	cam_offset :=
 		Vec3 {
 			math.cos(state.camera.pitch) * math.sin(state.camera.yaw),
@@ -1072,7 +732,11 @@ get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
 			math.cos(state.camera.pitch) * math.cos(state.camera.yaw),
 		} *
 		state.camera.dist
-	view = linalg.matrix4_look_at_f32(player_pos + cam_offset, player_pos, {0, 1, 0})
+	view = linalg.matrix4_look_at_f32(
+		transform.position + cam_offset,
+		transform.position,
+		{0, 1, 0},
+	)
 	proj = linalg.matrix4_perspective_f32(
 		60.0 * (math.PI / 180.0),
 		sapp.widthf() / sapp.heightf(),
@@ -1208,18 +872,57 @@ draw_mesh_instance :: proc(m: ^Mesh, pos: Vec3, scale: Vec3, tint: Vec3) {
 	sgl.pop_matrix()
 }
 
-draw_grid :: proc(slices: int, spacing: f32) {
-	sgl.begin_lines()
-	sgl.c3f(0.3, 0.3, 0.3)
+draw_grid_thick :: proc(slices: int, spacing: f32, thickness: f32) {
 	half := f32(slices) * spacing * 0.5
+
 	for i in 0 ..= slices {
 		p := -half + f32(i) * spacing
-		sgl.v3f(-half, 0, p); sgl.v3f(half, 0, p)
-		sgl.v3f(p, 0, -half); sgl.v3f(p, 0, half)
+
+		// Центральные оси
+		if math.abs(p) < 0.0001 {
+			// X axis (red)
+			draw_thick_line(
+				Vec3{-half, 0, 0},
+				Vec3{half, 0, 0},
+				thickness * 2,
+				Vec3{0.8, 0.2, 0.2},
+			)
+			// Z axis (blue)
+			draw_thick_line(
+				Vec3{0, 0, -half},
+				Vec3{0, 0, half},
+				thickness * 2,
+				Vec3{0.2, 0.2, 0.8},
+			)
+			continue
+		}
+
+		// Обычные линии сетки (серые)
+		draw_thick_line(Vec3{-half, 0, p}, Vec3{half, 0, p}, thickness, Vec3{0.35, 0.35, 0.35})
+		draw_thick_line(Vec3{p, 0, -half}, Vec3{p, 0, 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()
@@ -1249,8 +952,8 @@ event :: proc "c" (ev: ^sapp.Event) {
 		state.input.mouse_x = ev.mouse_x
 		state.input.mouse_y = ev.mouse_y
 		if state.input.locked {
-			state.input.mouse_dx = ev.mouse_dx
-			state.input.mouse_dy = ev.mouse_dy
+			state.input.mouse_dx += ev.mouse_dx
+			state.input.mouse_dy += ev.mouse_dy
 		}
 	case .MOUSE_DOWN:
 		if ev.mouse_button == .LEFT {