0xc3 3 săptămâni în urmă
părinte
comite
e5b2b91629
1 a modificat fișierele cu 145 adăugiri și 135 ștergeri
  1. 145 135
      src/cmd/sandbox/main.odin

+ 145 - 135
src/cmd/sandbox/main.odin

@@ -31,15 +31,12 @@ Mesh :: struct {
 	indices:  [dynamic]u16,
 }
 
-// Entity Kind (Tag)
 Entity_Kind :: enum u8 {
 	Player,
 	Enemy,
+	Cube,
 }
 
-// The Big Data Struct
-// Благодаря #soa, это превращается в структуру массивов:
-// struct { pos: [N]vec3, vel: [N]vec3, ... }
 Entity :: struct {
 	pos:   linalg.Vector3f32,
 	vel:   linalg.Vector3f32,
@@ -51,67 +48,104 @@ Entity :: struct {
 Camera :: struct {
 	pitch: f32,
 	yaw:   f32,
-	dist:  f32, // Расстояние от игрока (вид от 3-го лица)
+	dist:  f32,
 }
 
 Input_State :: struct {
-	keys:     map[sapp.Keycode]bool,
-	mouse_dx: f32,
-	mouse_dy: f32,
-	locked:   bool,
+	keys:         map[sapp.Keycode]bool,
+	mouse_dx:     f32,
+	mouse_dy:     f32,
+	mouse_x:      f32,
+	mouse_y:      f32,
+	left_clicked: bool,
+	locked:       bool,
 }
 
 App_State :: struct {
-	// Memory
-	arena:       virtual.Arena,
-	allocator:   mem.Allocator,
-
-	// Resources
-	pass_action: sg.Pass_Action,
-	mu_ctx:      mu.Context,
-	atlas_img:   sg.Image,
-	atlas_view:  sg.View,
-	atlas_smp:   sg.Sampler,
-	ui_pip:      sgl.Pipeline,
-	scene_pip:   sgl.Pipeline,
-
-	// Assets
-	mesh_cube:   Mesh,
-
-	// ECS (Entity Component System - Lite)
-	// Храним всех в одном массиве. Игрок всегда index 0.
-	entities:    #soa[dynamic]Entity,
-
-	// Runtime
-	dpi_scale:   f32,
-	camera:      Camera,
-	input:       Input_State,
+	arena:          virtual.Arena,
+	allocator:      mem.Allocator,
+	pass_action:    sg.Pass_Action,
+	mu_ctx:         mu.Context,
+	atlas_img:      sg.Image,
+	atlas_view:     sg.View,
+	atlas_smp:      sg.Sampler,
+	ui_pip:         sgl.Pipeline,
+	scene_pip:      sgl.Pipeline,
+	mesh_cube:      Mesh,
+	entities:       #soa[dynamic]Entity,
+	dpi_scale:      f32,
+	camera:         Camera,
+	input:          Input_State,
+	selected_color: [3]f32,
 }
 
 state: App_State
 
+// --- Math Helpers ---
+
+get_mouse_ray :: proc(
+	mx, my: f32,
+	view, proj: linalg.Matrix4f32,
+) -> (
+	origin, dir: linalg.Vector3f32,
+) {
+	// Используем физические размеры фреймбуфера
+	w := sapp.widthf()
+	h := sapp.heightf()
+
+	// Переводим логические координаты мыши в физические
+	// 1. NDC (-1..1)
+	// Y инвертирован: в окне 0 вверху, в NDC 1 вверху
+	x := (2.0 * mx) / w - 1.0
+	y := 1.0 - (2.0 * my) / h
+
+	// 2. Clip Space
+	ray_clip := linalg.Vector4f32{x, y, -1.0, 1.0}
+
+	// 3. Eye Space
+	inv_proj := linalg.matrix4_inverse(proj)
+	ray_eye := inv_proj * ray_clip
+	ray_eye = {ray_eye.x, ray_eye.y, -1.0, 0.0}
+
+	// 4. World Space
+	inv_view := linalg.matrix4_inverse(view)
+	ray_world := inv_view * ray_eye
+
+	// Позиция камеры
+	origin = {inv_view[3][0], inv_view[3][1], inv_view[3][2]}
+	dir = linalg.normalize(ray_world.xyz)
+
+	return origin, dir
+}
+
+ray_plane_intersect :: proc(
+	origin, dir: linalg.Vector3f32,
+	plane_y: f32,
+) -> (
+	pos: linalg.Vector3f32,
+	hit: bool,
+) {
+	if abs(dir.y) < 0.001 do return {}, false
+	t := (plane_y - origin.y) / dir.y
+	if t < 0 do return {}, false
+	return origin + dir * t, true
+}
+
 // --- Systems ---
 
-// 1. Input System: Управляет скоростью игрока (Entity 0)
 system_input_player :: proc(dt: f32) {
 	if len(state.entities) == 0 do return
-
-	// Получаем указатель на игрока (SoA access)
-	// В Odin доступ к элементу #soa массива возвращает структуру-прокси,
-	// которая ведет себя как ссылка на данные в соответствующих массивах.
 	player := &state.entities[0]
 
 	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)
 	}
 
-	// Расчет направления движения относительно камеры
 	sin_yaw, cos_yaw := math.sincos(state.camera.yaw)
 	forward := linalg.Vector3f32{sin_yaw, 0, cos_yaw}
 	right := linalg.Vector3f32{cos_yaw, 0, -sin_yaw}
@@ -122,7 +156,6 @@ system_input_player :: proc(dt: f32) {
 	if state.input.keys[.A] do move_dir -= right
 	if state.input.keys[.D] do move_dir += right
 
-	// Применяем скорость к игроку (только XZ, Y управляется гравитацией)
 	target_vel_x: f32 = 0.0
 	target_vel_z: f32 = 0.0
 
@@ -132,49 +165,50 @@ system_input_player :: proc(dt: f32) {
 		target_vel_z = move_dir.z * SPEED
 	}
 
-	// Мгновенная реакция (можно добавить инерцию, если lerp-ить)
 	player.vel.x = target_vel_x
 	player.vel.z = target_vel_z
 
-	// Прыжок
 	if state.input.keys[.SPACE] && player.pos.y <= 0.51 {
 		player.vel.y = 10.0
 	}
 }
 
-// 2. Physics System: Обрабатывает ВСЕ сущности линейно
+system_builder :: proc(view, proj: linalg.Matrix4f32) {
+	// Исправлено: доступ к полю hover_root напрямую
+	if state.input.locked || state.mu_ctx.hover_root != nil {
+		return
+	}
+
+	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 {
+			spawn_entity(hit_pos, .Cube)
+		}
+	}
+}
+
 system_physics :: proc(dt: f32) {
 	GRAVITY :: 30.0
 	FLOOR_Y :: 0.5
 
-	// Итерация по SoA массиву очень быстрая (кэш-френдли).
-	// Компилятор может векторизовать этот цикл.
 	for i in 0 ..< len(state.entities) {
 		e := &state.entities[i]
+		if e.kind == .Cube do continue
 
-		// Гравитация
 		e.vel.y -= GRAVITY * dt
-
-		// Интеграция позиции
 		e.pos += e.vel * dt
 
-		// Коллизия с полом (простейшая)
 		if e.pos.y < FLOOR_Y {
 			e.pos.y = FLOOR_Y
-
-			// Отскок
 			if e.kind == .Player {
-				e.vel.y = 0 // Игрок не прыгает как мячик
+				e.vel.y = 0
 			} else {
-				e.vel.y *= -0.8 // Враги прыгают
-
-				// Трение об пол
+				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)
@@ -182,29 +216,24 @@ system_physics :: proc(dt: f32) {
 	}
 }
 
-// 3. Render System
 system_render :: proc(view, proj: linalg.Matrix4f32) {
-	view_copy := view
-	proj_copy := proj
+	// Исправлено: Создаем локальные копии матриц, чтобы взять их адрес
+	v := view
+	p := proj
 
 	sgl.defaults()
 	sgl.push_pipeline()
 	sgl.load_pipeline(state.scene_pip)
 
 	sgl.matrix_mode_projection()
-	sgl.load_matrix(cast(^f32)&proj_copy)
+	sgl.load_matrix(cast(^f32)&p) // Адрес локальной копии
 	sgl.matrix_mode_modelview()
-	sgl.load_matrix(cast(^f32)&view_copy)
+	sgl.load_matrix(cast(^f32)&v) // Адрес локальной копии
 
 	draw_grid(100, 1.0)
 
-	// Рисуем всех одной пачкой
-	// В реальном движке здесь был бы Instancing, но для SGL просто цикл.
 	for i in 0 ..< len(state.entities) {
-		e := state.entities[i] // Копия для чтения (быстро)
-
-		// Цвет меняем через uniform (в sgl это c3f)
-		// Геометрию берем одну и ту же
+		e := state.entities[i]
 		draw_mesh_instance(&state.mesh_cube, e.pos, e.scale, e.color)
 	}
 
@@ -220,8 +249,6 @@ get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
 
 	player_pos := state.entities[0].pos
 
-	// Камера смотрит на игрока
-	// Вычисляем позицию камеры на сфере вокруг игрока
 	cam_offset :=
 		linalg.Vector3f32 {
 			math.cos(state.camera.pitch) * math.sin(state.camera.yaw),
@@ -248,15 +275,15 @@ spawn_entity :: proc(pos: linalg.Vector3f32, kind: Entity_Kind) {
 
 	switch kind {
 	case .Player:
-		e.color = {0.2, 0.8, 0.2} // Green
-		e.scale = 1.0
+		e.color = {0.2, 0.8, 0.2}
 	case .Enemy:
-		e.color = {0.8, 0.3, 0.3} // Red
+		e.color = {0.8, 0.3, 0.3}
 		e.scale = rand.float32_range(0.5, 1.5)
-		// Случайная начальная скорость для врагов
 		e.vel.x = rand.float32_range(-5, 5)
 		e.vel.z = rand.float32_range(-5, 5)
 		e.vel.y = rand.float32_range(5, 15)
+	case .Cube:
+		e.color = state.selected_color
 	}
 
 	append_soa(&state.entities, e)
@@ -266,45 +293,27 @@ spawn_entity :: proc(pos: linalg.Vector3f32, kind: Entity_Kind) {
 
 make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
 	m: Mesh
-	// 24 вершины (6 граней * 4 вершины)
 	m.vertices = make(#soa[dynamic]Vertex, 0, 24, allocator)
-	// 36 индексов (6 граней * 2 треугольника * 3 индекса)
 	m.indices = make([dynamic]u16, 0, 36, allocator)
 
-	// Цвета для наглядности сторон
-	c_front := [3]f32{1.0, 0.5, 0.2} // Orange
+	c_front := [3]f32{1.0, 0.5, 0.2}
 	c_back := [3]f32{0.8, 0.4, 0.1}
 	c_left := [3]f32{0.9, 0.45, 0.15}
 	c_right := [3]f32{0.9, 0.45, 0.15}
-	c_top := [3]f32{1.0, 1.0, 1.0} // Lighter
-	c_bottom := [3]f32{1.0, 1.0, 1.0} // Darker
+	c_top := [3]f32{1.0, 0.6, 0.3}
+	c_bottom := [3]f32{0.6, 0.3, 0.1}
 
-	// Helper для добавления грани (4 вершины + 6 индексов)
 	add_face :: proc(m: ^Mesh, p1, p2, p3, p4: [3]f32, color: [3]f32) {
 		start_idx := u16(len(m.vertices))
-
 		append_soa(&m.vertices, Vertex{p1, color})
 		append_soa(&m.vertices, Vertex{p2, color})
 		append_soa(&m.vertices, Vertex{p3, color})
 		append_soa(&m.vertices, Vertex{p4, color})
-
-		// CCW порядок (Против часовой стрелки)
-		// 1---2
-		// | / |
-		// 0---3
 		append(&m.indices, start_idx + 0, start_idx + 1, start_idx + 2)
 		append(&m.indices, start_idx + 0, start_idx + 2, start_idx + 3)
 	}
 
-	// Координаты
-	// Z+ (Front), Z- (Back)
-	// Y+ (Top),   Y- (Bottom)
-	// X+ (Right), X- (Left)
-
-	// Front (Z+)
 	add_face(&m, {-0.5, -0.5, 0.5}, {-0.5, 0.5, 0.5}, {0.5, 0.5, 0.5}, {0.5, -0.5, 0.5}, c_front)
-
-	// Back (Z-) - смотрим сзади, порядок точек зеркальный для CCW
 	add_face(
 		&m,
 		{0.5, -0.5, -0.5},
@@ -313,11 +322,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
 		{-0.5, -0.5, -0.5},
 		c_back,
 	)
-
-	// Top (Y+)
 	add_face(&m, {-0.5, 0.5, 0.5}, {-0.5, 0.5, -0.5}, {0.5, 0.5, -0.5}, {0.5, 0.5, 0.5}, c_top)
-
-	// Bottom (Y-)
 	add_face(
 		&m,
 		{-0.5, -0.5, -0.5},
@@ -326,11 +331,7 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
 		{0.5, -0.5, -0.5},
 		c_bottom,
 	)
-
-	// Right (X+)
 	add_face(&m, {0.5, -0.5, 0.5}, {0.5, 0.5, 0.5}, {0.5, 0.5, -0.5}, {0.5, -0.5, -0.5}, c_right)
-
-	// Left (X-)
 	add_face(
 		&m,
 		{-0.5, -0.5, -0.5},
@@ -348,21 +349,19 @@ make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
 init :: proc "c" () {
 	context = runtime.default_context()
 
-	// 1. Memory
 	_ = virtual.arena_init_growing(&state.arena, 64 * mem.Megabyte)
 	state.allocator = virtual.arena_allocator(&state.arena)
 
-	// 2. Sokol
 	sg.setup({environment = sglue.environment(), logger = {func = slog.func}})
 	sgl.setup({logger = {func = slog.func}})
 
-	// 3. UI
 	mu.init(&state.mu_ctx)
 	state.mu_ctx.text_width = mu.default_atlas_text_width
 	state.mu_ctx.text_height = mu.default_atlas_text_height
 
-	// 4. Resources
-	// UI Atlas
+	// Исправлено: убрана инициализация state.camera.pos
+	state.selected_color = {0.2, 0.5, 1.0}
+
 	w := mu.DEFAULT_ATLAS_WIDTH
 	h := mu.DEFAULT_ATLAS_HEIGHT
 	pixels := make([]u32, w * h, context.temp_allocator)
@@ -380,39 +379,30 @@ init :: proc "c" () {
 	state.atlas_view = sg.make_view({texture = {image = state.atlas_img}})
 	state.atlas_smp = sg.make_sampler({min_filter = .NEAREST, mag_filter = .NEAREST})
 
-	// Pipelines
 	ui_pip_desc: sg.Pipeline_Desc
 	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
 	state.ui_pip = sgl.make_pipeline(ui_pip_desc)
 
-	// 3D Pipeline
 	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
-
 	state.scene_pip = sgl.make_pipeline(scene_pip_desc)
 
 	state.pass_action = {
 		colors = {0 = {load_action = .CLEAR, clear_value = {0.1, 0.1, 0.15, 1}}},
 	}
 
-	// 5. Game Data
 	state.mesh_cube = make_cube_mesh(state.allocator)
-
-	// Init Entities (SoA array)
-	// Reserve memory upfront to avoid reallocations
 	state.entities = make(#soa[dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
 
-	// Spawn Player (Index 0)
 	spawn_entity({0, 5, 0}, .Player)
 	state.camera.dist = 10.0
 	state.camera.pitch = 0.5
 
-	// Spawn Enemies
-	for i in 0 ..< 1000 {
+	for i in 0 ..< 50 {
 		x := rand.float32_range(-40, 40)
 		z := rand.float32_range(-40, 40)
 		y := rand.float32_range(10, 50)
@@ -427,29 +417,48 @@ frame :: proc "c" () {
 	state.dpi_scale = sapp.dpi_scale()
 	dt := f32(sapp.frame_duration())
 
-	// --- Systems Update ---
 	system_input_player(dt)
 	system_physics(dt)
 
-	// Reset input deltas
+	view, proj := get_camera_matrices()
+	system_builder(view, proj)
+
 	state.input.mouse_dx = 0
 	state.input.mouse_dy = 0
+	state.input.left_clicked = false
 
-	// --- Render ---
-
-	// UI
 	mu.begin(&state.mu_ctx)
+
 	if mu.begin_window(&state.mu_ctx, "Stats", {10, 10, 200, 120}) {
 		mu.label(&state.mu_ctx, fmt.tprintf("FPS: %.0f", 1.0 / dt))
 		mu.label(&state.mu_ctx, fmt.tprintf("Entities: %d", len(state.entities)))
 		mu.label(&state.mu_ctx, "WASD to Move")
-		mu.label(&state.mu_ctx, "Space to Jump")
+		mu.label(&state.mu_ctx, "Click to Spawn Cube")
+		mu.end_window(&state.mu_ctx)
+	}
+
+	if mu.begin_window(&state.mu_ctx, "Builder", {10, 140, 200, 150}) {
+		mu.label(&state.mu_ctx, "Cube Color:")
+		mu.slider(&state.mu_ctx, &state.selected_color.x, 0, 1, 0, "R: %.2f")
+		mu.slider(&state.mu_ctx, &state.selected_color.y, 0, 1, 0, "G: %.2f")
+		mu.slider(&state.mu_ctx, &state.selected_color.z, 0, 1, 0, "B: %.2f")
+
+		r := mu.layout_next(&state.mu_ctx)
+		mu.draw_rect(
+			&state.mu_ctx,
+			r,
+			{
+				u8(state.selected_color.x * 255),
+				u8(state.selected_color.y * 255),
+				u8(state.selected_color.z * 255),
+				255,
+			},
+		)
+
 		mu.end_window(&state.mu_ctx)
 	}
 	mu.end(&state.mu_ctx)
 
-	// Scene
-	view, proj := get_camera_matrices()
 	system_render(view, proj)
 	render_ui()
 
@@ -470,7 +479,6 @@ draw_mesh_instance :: proc(m: ^Mesh, pos: linalg.Vector3f32, scale: f32, tint: l
 	for i := 0; i < len(m.indices); i += 1 {
 		idx := m.indices[i]
 		v := m.vertices[idx]
-		// Умножаем цвет вершины на цвет сущности (Tint)
 		sgl.c3f(v.color.x * tint.x, v.color.y * tint.y, v.color.z * tint.z)
 		sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
 	}
@@ -617,20 +625,22 @@ event :: proc "c" (ev: ^sapp.Event) {
 	case .KEY_UP:
 		state.input.keys[ev.key_code] = false
 	case .MOUSE_MOVE:
+		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
 		}
 	case .MOUSE_DOWN:
-		if ev.mouse_button == .LEFT && !state.input.locked {
-			sapp.lock_mouse(true)
-			state.input.locked = true
+		if ev.mouse_button == .LEFT {
+			state.input.left_clicked = true
 		}
 	}
 
 	if ev.type == .KEY_DOWN && ev.key_code == .ESCAPE {
-		state.input.locked = false
-		sapp.lock_mouse(false)
+		state.input.locked = !state.input.locked
+		sapp.lock_mouse(state.input.locked)
 	}
 
 	if !state.input.locked {
@@ -678,11 +688,11 @@ main :: proc() {
 			event_cb = event,
 			width = 1280,
 			height = 720,
-			window_title = "Sokol DOD",
+			window_title = "Sokol 3D Builder",
 			icon = {sokol_default = true},
 			logger = {func = slog.func},
 			high_dpi = true,
-			swap_interval = 0, // Unlock FPS
+			swap_interval = 0,
 		},
 	)
 }