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