Separate input component

This commit is contained in:
Hugo Mårdbrink 2025-08-25 15:47:42 +02:00
parent 29e7b5e499
commit 705f498daa
9 changed files with 318 additions and 178 deletions

56
ecs/camera_system.odin Normal file
View file

@ -0,0 +1,56 @@
package ecs
import "core:math"
import "core:math/linalg"
import sa "../sokol/app"
CameraSystem :: struct {
using base: SystemBase,
}
camera_system_init :: proc(camera_system: ^CameraSystem) {
}
camera_system_update :: proc(camera_system: ^CameraSystem, coordinator: ^Coordinator, dt: f32) {
for entity in camera_system.entities {
camera := coordinator_get_component(CameraComponent, coordinator, entity)
input := coordinator_get_component(InputComponent, coordinator, entity)
if input.key_down[.ESCAPE] do sa.quit()
move_input: Vec3
if input.key_down[.W] do move_input.y = 1
else if input.key_down[.S] do move_input.y = -1
if input.key_down[.D] do move_input.x = 1
else if input.key_down[.A] do move_input.x = -1
if input.key_down[.SPACE] do move_input.z = 1
else if input.key_down[.LEFT_SHIFT] do move_input.z = -1
look_input: Vec2 = -input.mouse_movement * camera.look_sensitivity
camera.look += look_input
camera.look.x = math.wrap(camera.look.x, 360)
camera.look.y = math.clamp(camera.look.y, -89.5, 89.5)
look_mat := linalg.matrix4_from_yaw_pitch_roll_f32(
linalg.to_radians(camera.look.x),
linalg.to_radians(camera.look.y),
0,
)
forward := (look_mat * Vec4{0, 0, -1, 1}).xyz
right := (look_mat * Vec4{1, 0, 0, 1}).xyz
up := (look_mat * Vec4{0, 1, 0, 1}).xyz
move_dir := forward * move_input.y + right * move_input.x + up * move_input.z
motion := linalg.normalize0(move_dir) * camera.movement_speed * dt
camera.position += motion
camera.target = camera.position + forward
}
}

View file

@ -1,5 +1,6 @@
package ecs
import sa "../sokol/app"
import sg "../sokol/gfx"
Mat4 :: matrix[4, 4]f32
@ -8,38 +9,51 @@ Vec2 :: [2]f32
Vec3 :: [3]f32
Vec4 :: [4]f32
Gravity :: struct {
GravityComponent :: struct {
force: Vec3
}
RigidBody :: struct {
RigidBodyComponent :: struct {
velocity: Vec3,
acceleration: Vec3,
}
Transform :: struct {
TransformComponent :: struct {
position: Vec3,
rotation: Vec3,
scale: Vec3,
}
Color :: struct {
ColorComponent :: struct {
color: Vec4,
}
Mesh :: struct {
vertex_buffer: sg.Buffer,
index_buffer: sg.Buffer,
MeshID :: enum {
CubeMesh,
}
Material :: struct {
shader: sg.Shader,
image: sg.Image,
sampler: sg.Sampler,
MeshComponent :: struct {
mesh_id: MeshID
}
Camera :: struct {
MaterialID :: enum {
GridTexture,
}
MaterialComponent :: struct {
material_id: MaterialID
}
CameraComponent :: struct {
position: Vec3,
target: Vec3,
look: Vec2
look: Vec2,
look_sensitivity: f32,
movement_speed: f32,
}
InputComponent :: struct {
mouse_movement: Vec2,
key_down: #sparse[sa.Keycode]bool,
}

30
ecs/input_system.odin Normal file
View file

@ -0,0 +1,30 @@
package ecs
import sa "../sokol/app"
InputSystem :: struct {
using base: SystemBase,
}
input_system_update :: proc(input_system: ^InputSystem, coordinator: ^Coordinator, event: ^sa.Event) {
for entity in input_system.entities {
input := coordinator_get_component(InputComponent, coordinator, entity)
#partial switch event.type {
case .MOUSE_MOVE:
input.mouse_movement += {event.mouse_dx, event.mouse_dy}
case .KEY_DOWN:
input.key_down[event.key_code] = true
case .KEY_UP:
input.key_down[event.key_code] = false
}
}
}
input_system_mouse_reset :: proc(input_system: ^InputSystem, coordinator: ^Coordinator) {
for entity in input_system.entities {
input := coordinator_get_component(InputComponent, coordinator, entity)
input.mouse_movement = { 0, 0 }
}
}

View file

@ -6,9 +6,9 @@ PhysicsSystem :: struct {
physics_system_update :: proc(physics_system: ^PhysicsSystem, coordinator: ^Coordinator, dt: f32) {
for entity in physics_system.entities {
rigid_body := coordinator_get_component(RigidBody, coordinator, entity)
transform := coordinator_get_component(Transform, coordinator, entity)
gravity := coordinator_get_component(Gravity, coordinator, entity)
rigid_body := coordinator_get_component(RigidBodyComponent, coordinator, entity)
transform := coordinator_get_component(TransformComponent, coordinator, entity)
gravity := coordinator_get_component(GravityComponent, coordinator, entity)
transform.position += rigid_body.velocity * dt
rigid_body.velocity += gravity.force * dt

View file

@ -27,32 +27,28 @@ VertexData :: struct {
uv: Vec2,
}
RenderSystem :: struct {
using base: SystemBase,
shader: sg.Shader,
pipeline: sg.Pipeline,
Mesh :: struct {
vertex_buffer: sg.Buffer,
index_buffer: sg.Buffer,
image: sg.Image,
sampler: sg.Sampler,
camera: struct {
pos: Vec3,
target: Vec3,
look: Vec2,
},
}
init_render_system :: proc(render_system: ^RenderSystem) {
default_context := runtime.default_context()
Material :: struct {
pipeline: sg.Pipeline,
shader: sg.Shader,
image: sg.Image,
sampler: sg.Sampler,
}
sg.setup({
environment = sh.glue_environment(),
allocator = sg.Allocator(sh.allocator(&default_context)),
logger = sg.Logger(sh.logger(&default_context)),
})
RenderSystem :: struct {
using base: SystemBase,
camera_entity: EntityID,
materials: map[MaterialID]Material,
meshes : map[MeshID]Mesh,
}
@(private)
init_grid_material :: proc(render_system: ^RenderSystem) {
shader := sg.make_shader(shaders.main_shader_desc(sg.query_backend()))
pipeline := sg.make_pipeline({
shader = shader,
@ -74,7 +70,40 @@ init_render_system :: proc(render_system: ^RenderSystem) {
},
})
vertices := []VertexData {
w, h: i32
pixels := stbi.load("res/white.png", &w, &h, nil, 4)
assert(pixels != nil)
defer(stbi.image_free(pixels))
image := sg.make_image({
width = w,
height = h,
pixel_format = .RGBA8,
data = {
subimage = {
0 = {
0 = {
ptr = pixels,
size = uint(w * h * 4)
}
}
}
}
})
sampler := sg.make_sampler({})
render_system.materials[.GridTexture] = Material{
shader = shader,
pipeline = pipeline,
image = image,
sampler = sampler,
}
}
@(private)
init_cube_mesh :: proc (render_system: ^RenderSystem) {
cube_vertices := []VertexData {
{ pos = { -0.5, -0.5, 0.5 }, uv = { 0, 0 } },
{ pos = { 0.5, -0.5, 0.5 }, uv = { 1, 0 } },
{ pos = { 0.5, 0.5, 0.5 }, uv = { 1, 1 } },
@ -106,10 +135,10 @@ init_render_system :: proc(render_system: ^RenderSystem) {
{ pos = { -0.5, 0.5, 0.5 }, uv = { 1, 1 } },
}
vertex_buffer := sg.make_buffer({
data = sg_range(vertices)
data = sg_range(cube_vertices)
})
indices := []u16 {
cube_indices := []u16 {
1, 0, 2,
3, 2, 0,
7, 4, 6,
@ -126,78 +155,80 @@ init_render_system :: proc(render_system: ^RenderSystem) {
index_buffer := sg.make_buffer({
usage = { index_buffer = true },
data = sg_range(indices),
data = sg_range(cube_indices),
})
w, h: i32
pixels := stbi.load("res/white.png", &w, &h, nil, 4)
assert(pixels != nil)
defer(stbi.image_free(pixels))
image := sg.make_image({
width = w,
height = h,
pixel_format = .RGBA8,
data = {
subimage = {
0 = {
0 = {
ptr = pixels,
size = uint(w * h * 4)
}
}
}
}
})
sampler := sg.make_sampler({})
render_system.sampler = sampler
render_system.shader = shader
render_system.image = image
render_system.pipeline = pipeline
render_system.vertex_buffer = vertex_buffer
render_system.index_buffer = index_buffer
render_system.camera = {
pos = { 30, 0, 60 },
target = { 0, 0, 1 },
render_system.meshes[.CubeMesh] = Mesh{
vertex_buffer = vertex_buffer,
index_buffer = index_buffer,
}
}
delete_render_system :: proc(render_system: ^RenderSystem) {
sg.destroy_buffer(render_system.index_buffer)
sg.destroy_buffer(render_system.vertex_buffer)
sg.destroy_image(render_system.image)
sg.destroy_sampler(render_system.sampler)
sg.destroy_pipeline(render_system.pipeline)
sg.destroy_shader(render_system.shader)
render_system_init :: proc(render_system: ^RenderSystem, camera_entity: EntityID) {
default_context := runtime.default_context()
sg.setup({
environment = sh.glue_environment(),
allocator = sg.Allocator(sh.allocator(&default_context)),
logger = sg.Logger(sh.logger(&default_context)),
})
render_system.materials = make(map[MaterialID]Material)
render_system.meshes = make(map[MeshID]Mesh)
render_system.camera_entity = camera_entity
init_cube_mesh(render_system)
init_grid_material(render_system)
}
render_system_delete :: proc(render_system: ^RenderSystem, coordinator: ^Coordinator) {
cube_mesh := render_system.meshes[.CubeMesh]
sg.destroy_buffer(cube_mesh.index_buffer)
sg.destroy_buffer(cube_mesh.vertex_buffer)
grid_material := render_system.materials[.GridTexture]
sg.destroy_image(grid_material.image)
sg.destroy_sampler(grid_material.sampler)
sg.destroy_pipeline(grid_material.pipeline)
sg.destroy_shader(grid_material.shader)
delete(render_system.materials)
delete(render_system.meshes)
sg.shutdown()
}
render_system_update :: proc(render_system: ^RenderSystem, coordinator: ^Coordinator, dt: f32, key_down: #sparse[sa.Keycode]bool, mouse_move: Vec2) {
update_camera(render_system, dt, key_down, mouse_move)
render_system_update :: proc(render_system: ^RenderSystem, coordinator: ^Coordinator, dt: f32) {
camera := coordinator_get_component(CameraComponent, coordinator, render_system.camera_entity)
proj := linalg.matrix4_perspective_f32(70, sa.widthf() / sa.heightf(), 0.001, 1000)
view := linalg.matrix4_look_at_f32(render_system.camera.pos, render_system.camera.target, { 0, 1, 0 } )
view := linalg.matrix4_look_at_f32(camera.position, camera.target, { 0, 1, 0 } )
sg.begin_pass({ swapchain = sh.glue_swapchain() })
sg.apply_pipeline(render_system.pipeline)
sg.apply_bindings({
vertex_buffers = { 0 = render_system.vertex_buffer },
index_buffer = render_system.index_buffer,
images = { shaders.IMG_tex = render_system.image },
samplers = { shaders.SMP_smp = render_system.sampler },
})
for entity in render_system.entities {
transform := coordinator_get_component(Transform, coordinator, entity)
color := coordinator_get_component(Color, coordinator, entity)
transform := coordinator_get_component(TransformComponent, coordinator, entity)
color := coordinator_get_component(ColorComponent, coordinator, entity)
mesh_id := coordinator_get_component(MeshComponent, coordinator, entity).mesh_id
mesh := render_system.meshes[mesh_id]
material_id := coordinator_get_component(MaterialComponent, coordinator, entity).material_id
material := render_system.materials[material_id]
pos_mat := linalg.matrix4_translate_f32(transform.position)
rot_mat := linalg.matrix4_from_yaw_pitch_roll_f32(transform.rotation.y, transform.rotation.x, transform.rotation.z)
model := pos_mat * rot_mat
sg.apply_pipeline(material.pipeline)
sg.apply_bindings({
images = { shaders.IMG_tex = material.image },
samplers = { shaders.SMP_smp = material.sampler },
vertex_buffers = { 0 = mesh.vertex_buffer },
index_buffer = mesh.index_buffer,
})
sg.apply_uniforms(shaders.UB_VsParams, sg_range(&shaders.Vsparams{
mvp = proj * view * model,
col = color.color
@ -210,44 +241,6 @@ render_system_update :: proc(render_system: ^RenderSystem, coordinator: ^Coordin
sg.commit()
}
@(private)
update_camera :: proc(render_system: ^RenderSystem, dt: f32, key_down: #sparse[sa.Keycode]bool, mouse_move: Vec2) {
MOVE_SPEED :: 3
LOOK_SENSITIVITY :: 0.15
move_input: Vec3
if key_down[.W] do move_input.y = 1
else if key_down[.S] do move_input.y = -1
if key_down[.D] do move_input.x = 1
else if key_down[.A] do move_input.x = -1
if key_down[.SPACE] do move_input.z = 1
else if key_down[.LEFT_SHIFT] do move_input.z = -1
look_input: Vec2 = -mouse_move * LOOK_SENSITIVITY
render_system.camera.look += look_input
render_system.camera.look.x = math.wrap(render_system.camera.look.x, 360)
render_system.camera.look.y = math.clamp(render_system.camera.look.y, -89.5, 89.5)
look_mat := linalg.matrix4_from_yaw_pitch_roll_f32(
linalg.to_radians(render_system.camera.look.x),
linalg.to_radians(render_system.camera.look.y),
0,
)
forward := (look_mat * Vec4{0, 0, -1, 1}).xyz
right := (look_mat * Vec4{1, 0, 0, 1}).xyz
up := (look_mat * Vec4{0, 1, 0, 1}).xyz
move_dir := forward * move_input.y + right * move_input.x + up * move_input.z
motion := linalg.normalize0(move_dir) * MOVE_SPEED * dt
render_system.camera.pos += motion
render_system.camera.target = render_system.camera.pos + forward
}
sg_range :: proc {
sg_range_from_slice,
sg_range_from_struct,