hauling this over to the server
Gio e@mail
Tue, 06 Jan 2026 21:17:11 -0500
15 files changed,
1422 insertions(+),
0 deletions(-)
jump to
A
src/client/client.odin
@@ -0,0 +1,104 @@
+package main + +import rl "vendor:raylib" +import "core:time" +import p "../player" +import cm "../common" + +SENSITIVITY : f32 : 0.01 +SLIGHTLY_DOWN : f32 : -rl.PI/2 + 1e-4 +ENTITY_LIMIT : int : 2048 + +DerivXFloat :: struct { + x: [3]f32, + v: [3]f32, + a: [3]f32, +} + +clientEntityDerivX: [dynamic]Maybe(DerivXFloat) + + +InitClient :: proc() { + clientEntityDerivX = make([dynamic]Maybe(DerivXFloat),256,ENTITY_LIMIT) + debugBoxes = make([dynamic]Maybe(AABox),256,ENTITY_LIMIT) + PlayerInit() + kinematics, ok := p.player^.kinematics[player^.id].? + if !ok do return + simFoward := p.player^.foward + camera = rl.Camera3D{ + position = ToFloatVector(kinematics.x) + [3]f32{0.0,2.0,0.0}, + fovy = 110.0, + up = [3]f32{0.0,1.0,0.0}, + projection = rl.CameraProjection.PERSPECTIVE + } + cameraFoward = ToFloatVector(simFoward) + camera.target = foward^ + cam.position +} + +DestroyClient :: proc() { + delete(clientEntityDerivX) +} + +ClientUpdate :: proc(clk: ^cm.Clock) { + delta := time.tick_since(clk.lastTick) + CameraControlFPS(&camera, p.player) + MovementControl(&commandBuffer) + ClientKinematicUpdate(delta, &clientEntityDerivX) + Render() +} + +ClientKinematicUpdate :: proc(delta: time.Duration, entities: ^[dynamic]Maybe(DerivXFloat)) { + for e,i in entities^ { + eNew, ok := e.? + if !ok do continue + eNew.v += eNew.a * f32(time.duration_seconds(delta)) + eNew.x += eNew.v * f32(time.duration_seconds(delta)) + + entities[i] = eNew + } +} + +CameraControlFPS :: proc (cam: ^rl.Camera3D, player: Player) { + kinematics, ok := player.clientKinematics[player.id].? + if !ok do return + + delta: rl.Vector2 = rl.GetMouseDelta() + yaw: f32 = -SENSITIVITY*delta.x + pitch: f32 = -SENSITIVITY*delta.y + + cameraFoward = rl.Vector3RotateByAxisAngle(cameraFoward,cam.up,yaw) + pitchAxis := rl.Vector3CrossProduct(cameraFoward,cam.up) + newTarget := cam.target-cam.position + newTarget = rl.Vector3RotateByAxisAngle(newTarget,cam.up,yaw) + newTarget = rl.Vector3RotateByAxisAngle(newTarget,pitchAxis,pitch) + + cam.position = kinematics.x + [3]f32{0.0,2.0,0.0} + + if rl.Vector3DotProduct(newTarget,cameraFoward) <= 0 && newTarget[1] > 0 { + cam.target = cam.position + rl.Vector3RotateByAxisAngle(cameraFoward,pitchAxis,-SLIGHTLY_DOWN) + } else if rl.Vector3DotProduct(newTarget,cameraFoward) <= 0 && newTarget[1] < 0 { + cam.target = cam.position + rl.Vector3RotateByAxisAngle(cameraFoward,pitchAxis,SLIGHTLY_DOWN) + } else { + cam.target = cam.position + newTarget + } +} + +MovementControl :: proc(commandBuffer: ^bit_set[Command]) { + if rl.IsKeyDown(rl.KeyboardKey.W) { + commandBuffer^ += {.MOVE_FOWARDS} + } + if rl.IsKeyDown(rl.KeyboardKey.S) { + commandBuffer^ += {.MOVE_BACKWARDS} + } + if rl.IsKeyDown(rl.KeyboardKey.A) { + commandBuffer^ += {.STRAFE_LEFT} + } + if rl.IsKeyDown(rl.KeyboardKey.D) { + commandBuffer^ += {.STRAFE_RIGHT} + } + if rl.IsKeyPressed(rl.KeyboardKey.SPACE) { + commandBuffer^ += {.JUMP} + } +} + +
A
src/client/render.odin
@@ -0,0 +1,51 @@
+package main + +import rl "vendor:raylib" + +AABox :: struct { + min: [3]f32, + max: [3]f32, +} + +debugBoxes: [dynamic]Maybe(AABox) + + +Render :: proc() { + rl.BeginDrawing() + rl.ClearBackground(rl.GRAY) + + rl.BeginMode3D(camera) + DisplayDebugBoxes(&debugBoxes, &clientEntityDerivX) + //DebugHighlightSector(&octree) + rl.EndMode3D() + + DebugPlayerInfo() + + rl.EndDrawing() +} + +DebugPlayerInfo :: proc() { + kinematics, ok := player.kinematics[player.id].? + if !ok { rl.EndDrawing(); return} + + rl.DrawText(rl.TextFormat("FPS: %i", rl.GetFPS()),10, 10, 20, rl.RED) + x := ToFloatVector(kinematics.x) + v := ToFloatVector(kinematics.v) + a := ToFloatVector(kinematics.a) + rl.DrawText(rl.TextFormat("Position: %f %f %f", x[0], x[1], x[2]),10, 50, 20, rl.RED) + rl.DrawText(rl.TextFormat("Velocity: %f %f %f", v[0], v[1], v[2]),10, 100, 20, rl.RED) + rl.DrawText(rl.TextFormat("Acceleration: %f %f %f", a[0], a[1], a[2]),10, 150, 20, rl.RED) +} + +DisplayDebugBoxes :: proc(boxes: ^[dynamic]Maybe(AABox), positions: ^[dynamic]Maybe(DerivXFloat)) { + for b,i in boxes^ { + box, ok1 := b.? + pos, ok2 := positions[i].? + if !(ok1 && ok2) do continue + + length := box.max[0]-box.min[0] + height := box.max[1]-box.min[1] + width := box.max[2]-box.min[2] + rl.DrawCube(pos.x,length,height,width,rl.RED) + } +}
A
src/common/list.odin
@@ -0,0 +1,38 @@
+package common + +import "core:container/intrusive/list" +import "core:fmt" + +ListLinSearch :: proc(l: list.List, item: ^$E) -> (^E, bool) { + it := list.iterator_head(l, type_of(item^), "node") + for e in list.iterate_next(&it) { + if item.value == e.value do return (^E)(e), true + } + return nil, false +} + +UniqueAppend :: proc(l: ^list.List, item: ^$E) -> bool { + _, found := ListLinSearch(l^, item) + if !found { + list.push_back(l, &item.node) + return true + } else do return false +} + +ListRemove :: proc(l: ^list.List, item: ^$E) -> bool { + element, found := ListLinSearch(l^, item) + if found { + list.remove(l,&element.node) + return true + } else do return false +} + +ListPrint :: proc(l: ^list.List) { + if list.is_empty(l) { + return + } + it := list.iterator_head(l^, NodeEntity, "node") + for e in list.iterate_next(&it) { + fmt.println(e.value) + } +}
A
src/common/math.odin
@@ -0,0 +1,153 @@
+package common + +import "core:fmt" +import "core:math" + +FRAC_BITS : u64 : 31 +FRAC_BIT_MASK : iFP : 0x7FFFFFFF +INT_BITS : u64 : 32 +FP_ONE :: f32(1 << FRAC_BITS) +DB_ONE :: f64(1 << FRAC_BITS) +TOLERENCE : iFP : 21474 +METER : iFP : 2147483648 +TWO_METER : iFP : 4294967296 +FOUR_METER : iFP : 8589934592 +INF : iFP : 0x7FFFFFFFFFFFFFFF +SQRT2 : iFP : 3037000448 + +BOX_NORMALS := [6][3]iFP { + [3]iFP{METER,0,0}, + [3]iFP{-METER,0,0}, + [3]iFP{0,METER,0}, + [3]iFP{0,-METER,0}, + [3]iFP{0,0,METER}, + [3]iFP{0,0,-METER}, +} + + +iFP :: distinct i64 + +Plane :: struct { + normal: [3]iFP, + center: [3]iFP, +} + +PlaneSide :: enum { + OUTSIDE, + INSIDE +} + + +FloatToInt :: proc(f: f32) -> iFP { + intComponent := iFP(f) + fracComponent := f-f32(intComponent) + result := intComponent << FRAC_BITS + result += iFP(fracComponent * FP_ONE) + return result +} + +DoubleToInt :: proc(f: f64) -> iFP { + intComponent := iFP(f) + fracComponent := f-f64(intComponent) + result := intComponent << FRAC_BITS + result += iFP(fracComponent * DB_ONE) + return result +} + +IntToFloat :: proc(i: iFP) -> f32 { + intPart := f32(i >> FRAC_BITS) + fracPart := f32(i & FRAC_BIT_MASK)/FP_ONE + return intPart+fracPart +} + +ToFloatVector :: proc(vec: [3]iFP) -> [3]f32 { + vecFloat :[3]f32 + for x,i in vec do vecFloat[i] = IntToFloat(x) + return vecFloat +} + +ToIntVector :: proc(vec: [3]f32) -> [3]iFP { + vecInt : [3]iFP + for x,i in vec do vecInt[i] = FloatToInt(x) + return vecInt +} + +Mul :: proc(i1: iFP, i2: iFP) -> iFP { + intPart1 := i1 >> FRAC_BITS + intPart2 := i2 >> FRAC_BITS + + fracPart1 := i1 & FRAC_BIT_MASK + fracPart2 := i2 & FRAC_BIT_MASK + + result: iFP = 0 + result += (intPart1*intPart2) << FRAC_BITS + result += (intPart1*fracPart2) + result += (fracPart1*intPart2) + result += ((fracPart1*fracPart2) >> FRAC_BITS) & FRAC_BIT_MASK + + return result +} + +Div :: proc(n: iFP, d: iFP) -> iFP { + one := iFP(1 << (FRAC_BITS+INT_BITS-1)) + reciprocal: iFP = (one/d) + return Mul(n,reciprocal) +} + +VecMul :: proc(vec: [3]iFP, sc: iFP) -> [3]iFP { + vecNew : [3]iFP + for x,i in vec do vecNew[i] = Mul(x,sc) + return vecNew +} + +VecDiv :: proc(vec: [3]iFP, sc: iFP) -> [3]iFP { + vecNew : [3]iFP + for x,i in vec do vecNew[i] = Div(x,sc) + return vecNew +} + +InvSqrt :: proc(x: iFP) -> iFP { + xf :f32 = IntToFloat(x) + if xf <= 0.0 do return 0 + yf :f32 = 1.0 / math.sqrt(xf) + + y0 := FloatToInt(yf) + y1 := Div((Mul(x,Mul(y0,Mul(y0,y0))) + y0),FloatToInt(2.0)) + + for math.abs(y1-y0) > TOLERENCE { + y0 = y1 + y1 = Div((Mul(x,Mul(y0,Mul(y0,y0))) - y0),FloatToInt(2.0)) + } + + return y1 +} + +CrossProduct :: proc(v1: [3]iFP, v2: [3]iFP) -> [3]iFP { + return [3]iFP{ + Mul(v1[1],v2[2]) - Mul(v1[2],v2[1]), + Mul(v1[2],v2[0]) - Mul(v1[0],v2[2]), + Mul(v1[0],v2[1]) - Mul(v1[1],v2[0]), + } +} + +DotProduct :: proc(v1: [3]iFP, v2: [3]iFP) -> iFP { + return Mul(v1[0],v2[0])+Mul(v1[1],v2[1])+Mul(v1[2],v2[2]) +} + +Normalize :: proc(vec: [3]iFP) -> [3]iFP { + return VecMul(vec,InvSqrt(DotProduct(vec,vec))) +} + +PointInsidePlane :: proc(point: [3]iFP, plane: Plane) -> PlaneSide { + if DotProduct(point-plane.center,plane.normal) > 0 do return PlaneSide.OUTSIDE + else do return PlaneSide.INSIDE +} + +LinePlaneIntersection :: proc(origin: [3]iFP, target: [3]iFP, plane: Plane) -> ([3]iFP, bool) { + denom: iFP = DotProduct(Normalize(target-origin),plane.normal) + if denom == 0 { + return origin, false + } + return VecMul(Normalize(target-origin),Div(DotProduct(plane.center-origin,plane.normal),denom)) + origin, true +} +
A
src/common/time.odin
@@ -0,0 +1,17 @@
+package common + +import "core:time" + +Clock :: struct { + frames: u64, + period: time.Duration, + lastTick: time.Tick, +} + +ClockUpdate :: proc(clk: ^Clock, callback: proc(clk: ^Clock)) { + if time.tick_since(clk.lastTick) >= clk.period { + callback(clk) + clk.frames += 1 + clk.lastTick = time.tick_now() + } +}
A
src/level.odin
@@ -0,0 +1,56 @@
+package main + +import cm "common" +import sim "simulation" +import clt "client" + +CubeInfo :: struct { + center: [3]cm.iFP, + lwh: [3]cm.iFP +} + +InitDebugCube :: proc( + info: CubeInfo, + eKin: ^[dynamic]Maybe(sim.DerivX), + aabbs: ^[dynamic]Maybe(sim.AABB), + boxes: ^[dynamic]Maybe(clt.AABox), + ecKin: ^[dynamic]Maybe(clt.DerivXFloat) + ) { + using sim + using cm + using clt + index, found := AvailableSlot() + if !found do return + + x := DerivX { + x = info.center, + } + bb := AABB { + solid = true, + min = [3]iFP{-Div(info.lwh[0],TWO_METER),-Div(info.lwh[1],TWO_METER),-Div(info.lwh[2],TWO_METER)}, + max = [3]iFP{Div(info.lwh[0],TWO_METER),Div(info.lwh[1],TWO_METER),Div(info.lwh[2],TWO_METER)} + } + b := AABox { + max = cm.ToFloatVector(bb.max), + min = cm.ToFloatVector(bb.min) + } + cx := DerivXFloat { + x = cm.ToFloatVector(x.x) + } + eKin^[index] = x + ecKin^[index] = cx + aabbs^[index] = bb + boxes^[index] = b + + // Add Octree to funcuntion procs + AddEntityToNode(index,FindEnclosingNode(bb,x.x,&octree)) +} + +SpawnLevelObjects :: proc() { + using cm + using clt + InitDebugCube(CubeInfo { + center=[3]iFP{0,-METER,0}, + lwh=[3]iFP{TWO_METER,TWO_METER,TWO_METER} + }, sim.entityDerivX[0], sim.entityAABB[0], &debugBoxes, &clientEntityDerivX) +}
A
src/main.odin
@@ -0,0 +1,47 @@
+package main + +import rl "vendor:raylib" +import "core:time" +import cm "common" +import sim "simulation" +import clt "client" + +appState: struct { + win: struct { + height: i32, + width: i32, + name: cstring, + }, + clock: Clock +} = { + win = { + height = 2560, + width = 1440, + name = "window", + }, + clock = cm.Clock{ + frames = 0, + period = 6944444 * time.Nanosecond, + lastTick = time.tick_now() + } +} + +main :: proc() { + rl.InitWindow(appState.win.width,appState.win.height,appState.win.name) + rl.DisableCursor() + defer rl.CloseWindow() + sim.StartSimulation() + clt.InitClient() + SpawnLevelObjects() + + for !rl.WindowShouldClose() { + cm.ClockUpdate(&appState.clock,AppLoop) + cm.ClockUpdate(&sim.worldClock,sim.SimulationUpdate) + } +} + + +AppLoop :: proc(clk: ^cm.Clock) { + clt.ClientUpdate(clk) +} +
A
src/network.odin
@@ -0,0 +1,97 @@
+package main + +import "core:fmt" +import net "vendor:ENet" +import str "core:strings" + +clientNetworkState: struct{ + client: ^net.Host, + peer: ^net.Peer, + serverAddress: net.Address, + event: net.Event +} + +serverNetworkState: struct{ + server: ^net.Host, + address: net.Address, + event: net.Event, + maxClients: uint +} + +StartClient :: proc() { + if net.initialize() != 0 { + fmt.printfln("ENet failed to Initialize") + return + } + clientNetworkState.client = net.host_create(nil,1,2,0,0) + if clientNetworkState.client == nil { + fmt.printfln("Client could not be created") + } + fmt.printfln("Client Created") +} + +KillClient :: proc() { + net.host_destroy(clientNetworkState.client) + net.deinitialize() +} + +StartServer :: proc() { + if net.initialize() != 0 { + fmt.printfln("ENet failed to Initialize") + return + } + serverNetworkState.server = net.host_create(&serverNetworkState.address,serverNetworkState.maxClients,2,0,0) + if serverNetworkState.server == nil { + fmt.printfln("Server could not be created") + } + fmt.printfln("Server Created") +} + +KillServer :: proc() { + net.host_destroy(serverNetworkState.server) + net.deinitialize() +} + +ConnectToServer :: proc() { + clientNetworkState.peer = net.host_connect(clientNetworkState.client, &clientNetworkState.serverAddress,2,0) + if clientNetworkState.peer == nil { + fmt.printfln("No Available Connection") + return + } + if net.host_service(clientNetworkState.client, &clientNetworkState.event, 5000) > 0 && + clientNetworkState.event.type == net.EventType.CONNECT { + fmt.printfln("Connection Succeeded") + } else { + net.peer_reset(clientNetworkState.peer) + fmt.printfln("Connection Failed") + } +} + +ServerListen :: proc() { + for net.host_service(serverNetworkState.server, &serverNetworkState.event, 0) > 0 { + switch serverNetworkState.event.type { + case net.EventType.CONNECT: + fmt.printfln("Client connected.") + break + case net.EventType.RECEIVE: + fmt.printfln("Received %i bytes of data", serverNetworkState.event.packet.dataLength) + fmt.printfln("Received %s", str.string_from_null_terminated_ptr( + serverNetworkState.event.packet.data, int(serverNetworkState.event.packet.dataLength))) + net.packet_destroy(serverNetworkState.event.packet) + break + case net.EventType.NONE: + break + case net.EventType.DISCONNECT: + fmt.printfln("Client disconnected.") + break + } + } +} + +YellAtServer :: proc() { + scream :cstring = "Niggers" + flag := net.PacketFlags{net.PacketFlag.RELIABLE,} + packet := net.packet_create(rawptr(scream),len(scream)+1,flag) + net.peer_send(clientNetworkState.peer,0,packet) + net.host_flush(clientNetworkState.client) +}
A
src/player/player_server.odin
@@ -0,0 +1,108 @@
+package player + +import "core:fmt" +import cm "../common" +import sim "../simulation" +import clt "../client" + +PlayerInit :: proc() { + using cm + index, found := sim.AvailableSlot() + if !found do return + + player.id = index + player.foward = [3]cm.iFP{0,0,METER} + player.kinematics = sim.entityDerivX[0] + player.clientKinematics = clt.clientEntities + player.walkForce = sim.DEFAULT_WALK_FORCE + player.groundDrag = sim.DEFAULT_GROUND_DRAG + player.walkDrag = sim.DEFAULT_WALK_DRAG + x := sim.DerivX{ + x = [3]iFP{0.0,FOUR_METER,0.0}, + v = [3]iFP{0.0,0.0,0.0}, + a = [3]iFP{0.0,0.0,0.0}, + } + cx := clt.DerivXFloat{ + x = [3]f32{0.0,0.0,0.0}, + v = [3]f32{0.0,0.0,0.0}, + a = [3]f32{0.0,0.0,0.0}, + } + bb := sim.AABB{ + solid = true, + min = [3]cm.iFP{-Div(METER,TWO_METER),-METER,-Div(METER,TWO_METER)}, + max = [3]cm.iFP{Div(METER,TWO_METER),METER,Div(METER,TWO_METER)} + } + sim.entities[0]^[index] = x + clt.clientEntities^[index] = cx + sim.entityAABB[0]^[index] = bb + sim.entityCollisionCallback[0]^[index] = sim.RigidInelasticCollision + sim.entityGravity[0]^[index] = true + + // Add Octree to funcuntion procs + AddEntityToNode(index,FindEnclosingNode(bb,x.x,&octree)) +} + +PlayerMove :: proc() { + using cm + if commandBuffer^ == {} do return + kinematics, ok := player^.kinematics[player^.id].? + if !ok do return + + vecLen: i64 = 0 + if Command.MOVE_FOWARDS in commandBuffer^ { + kinematics.a += VecMul(player.foward,player.walkForce) + vecLen += 1 + } + if Command.MOVE_BACKWARDS in commandBuffer^ { + kinematics.a -= VecMul(player.foward,player.walkForce) + vecLen += 1 + } + if Command.STRAFE_LEFT in commandBuffer^ { + kinematics.a -= VecMul(player.rightward,player.walkForce) + vecLen += 1 + } + if Command.STRAFE_RIGHT in commandBuffer^ { + kinematics.a += VecMul(player.rightward,player.walkForce) + vecLen += 1 + } + if Command.JUMP in commandBuffer^ { + kinematics.v += VecMul(sim.UP, FloatToInt(10.0)) + } + if vecLen == 2 { + kinematics.a = VecDiv(kinematics.a,SQRT2) + } + + player^.kinematics[player^.id] = kinematics +} + +PlayerDrag :: proc() { + using cm + kinematics, ok := player^.kinematics[player^.id].? + if !ok do return + kinematics.a -= VecMul(kinematics.v,player.walkDrag) + + if Command.MOVE_FOWARDS not_in commandBuffer && Command.MOVE_BACKWARDS in commandBuffer { + force := VecMul( player.foward, Mul(player.groundDrag , DotProduct(kinematics.v,player.foward)) ) + if DotProduct(kinematics.v,player.foward) > 0 do kinematics.a -= force + } + if Command.MOVE_BACKWARDS not_in commandBuffer && Command.MOVE_FOWARDS in commandBuffer { + force := VecMul( player.foward, Mul(player.groundDrag , DotProduct(kinematics.v,player.foward)) ) + if DotProduct(kinematics.v,player.foward) < 0 do kinematics.a -= force + } + if Command.STRAFE_RIGHT not_in commandBuffer && Command.STRAFE_LEFT in commandBuffer { + force := VecMul( player.rightward, Mul(player.groundDrag , DotProduct(kinematics.v,player.rightward)) ) + if DotProduct(kinematics.v,player.rightward) > 0 do kinematics.a -= force + } + if Command.STRAFE_LEFT not_in commandBuffer && Command.STRAFE_RIGHT in commandBuffer { + force := VecMul( player.rightward, Mul(player.groundDrag , DotProduct(kinematics.v,player.rightward)) ) + if DotProduct(kinematics.v,player.rightward) < 0 do kinematics.a -= force + } + + player^.kinematics[player^.id] = kinematics +} + +PlayerAlignToCamera :: proc(camFoward: ^[3]f32, player: ^Player) { + foward := ToIntVector(camFoward^) + player^.foward = foward + player.rightward = CrossProduct(foward,UP) +}
A
src/player/state.odin
@@ -0,0 +1,30 @@
+package player + +import cm "../common" +import sim "../simulation" + +Player :: struct { + id: int, + walkForce: cm.iFP, + walkDrag: cm.iFP, + groundDrag: cm.iFP, + rightward: [3]cm.iFP, + foward: [3]cm.iFP, + //Might Turn into a pointer of a pointer if the Heap fucks with me + kinematics: ^[dynamic]Maybe(sim.DerivX), + clientKinematics: ^[dynamic]Maybe(DerivXFloat), + aabb: ^[dynamic]Maybe(AABB), + collision: ^[dynamic]Maybe(proc(e1: int, e2: int)) +} + +Command :: enum { + MOVE_FOWARDS, + MOVE_BACKWARDS, + STRAFE_LEFT, + STRAFE_RIGHT, + JUMP +} + +player: Player +camera: rl.Camera3D +cameraFoward: [3]f32
A
src/player_server.old
@@ -0,0 +1,123 @@
+package main + +import "core:fmt" + +Player :: struct { + id: int, + walkForce: iFP, + walkDrag: iFP, + groundDrag: iFP, + rightward: [3]iFP, + foward: [3]iFP, + //Might Turn into a pointer of a pointer if the Heap fucks with me + kinematics: ^[dynamic]Maybe(DerivX), + clientKinematics: ^[dynamic]Maybe(DerivXFloat), + aabb: ^[dynamic]Maybe(AABB), + collision: ^[dynamic]Maybe(proc(e1: int, e2: int)) +} + +PlayerInit :: proc( + player: ^Player, entities: ^[dynamic]Maybe(DerivX), + clientEntities: ^[dynamic]Maybe(DerivXFloat), + aabbs: ^[dynamic]Maybe(AABB), + collision: ^[dynamic]Maybe(proc(e1: int, e2: int, delta: iFP)), + gravity: ^[dynamic]Maybe(bool) + ) { + index, found := AvailableSlot() + if !found do return + + player.id = index + player.foward = [3]iFP{0,0,METER} + player.kinematics = entities + player.clientKinematics = clientEntities + player.walkForce = DEFAULT_WALK_FORCE + player.groundDrag = DEFAULT_GROUND_DRAG + player.walkDrag = DEFAULT_WALK_DRAG + x := DerivX{ + x = [3]iFP{0.0,FOUR_METER,0.0}, + v = [3]iFP{0.0,0.0,0.0}, + a = [3]iFP{0.0,0.0,0.0}, + } + cx := DerivXFloat{ + x = [3]f32{0.0,0.0,0.0}, + v = [3]f32{0.0,0.0,0.0}, + a = [3]f32{0.0,0.0,0.0}, + } + bb := AABB{ + solid = true, + min = [3]iFP{-Div(METER,TWO_METER),-METER,-Div(METER,TWO_METER)}, + max = [3]iFP{Div(METER,TWO_METER),METER,Div(METER,TWO_METER)} + } + entities^[index] = x + clientEntities^[index] = cx + aabbs^[index] = bb + collision^[index] = RigidInelasticCollision + gravity^[index] = true + + // Add Octree to funcuntion procs + AddEntityToNode(index,FindEnclosingNode(bb,x.x,&octree)) +} + +PlayerMove :: proc(player: ^Player, commandBuffer: ^bit_set[Command]) { + if commandBuffer^ == {} do return + kinematics, ok := player^.kinematics[player^.id].? + if !ok do return + + vecLen: i64 = 0 + if Command.MOVE_FOWARDS in commandBuffer^ { + kinematics.a += VecMul(player.foward,player.walkForce) + vecLen += 1 + } + if Command.MOVE_BACKWARDS in commandBuffer^ { + kinematics.a -= VecMul(player.foward,player.walkForce) + vecLen += 1 + } + if Command.STRAFE_LEFT in commandBuffer^ { + kinematics.a -= VecMul(player.rightward,player.walkForce) + vecLen += 1 + } + if Command.STRAFE_RIGHT in commandBuffer^ { + kinematics.a += VecMul(player.rightward,player.walkForce) + vecLen += 1 + } + if Command.JUMP in commandBuffer^ { + kinematics.v += VecMul(UP, FloatToInt(10.0)) + fmt.println("asfhhlak") + } + if vecLen == 2 { + kinematics.a = VecDiv(kinematics.a,SQRT2) + } + + player^.kinematics[player^.id] = kinematics +} + +PlayerDrag :: proc(player: ^Player, commandBuffer: bit_set[Command]) { + kinematics, ok := player^.kinematics[player^.id].? + if !ok do return + kinematics.a -= VecMul(kinematics.v,player.walkDrag) + + if Command.MOVE_FOWARDS not_in commandBuffer && Command.MOVE_BACKWARDS in commandBuffer { + force := VecMul( player.foward, Mul(player.groundDrag , DotProduct(kinematics.v,player.foward)) ) + if DotProduct(kinematics.v,player.foward) > 0 do kinematics.a -= force + } + if Command.MOVE_BACKWARDS not_in commandBuffer && Command.MOVE_FOWARDS in commandBuffer { + force := VecMul( player.foward, Mul(player.groundDrag , DotProduct(kinematics.v,player.foward)) ) + if DotProduct(kinematics.v,player.foward) < 0 do kinematics.a -= force + } + if Command.STRAFE_RIGHT not_in commandBuffer && Command.STRAFE_LEFT in commandBuffer { + force := VecMul( player.rightward, Mul(player.groundDrag , DotProduct(kinematics.v,player.rightward)) ) + if DotProduct(kinematics.v,player.rightward) > 0 do kinematics.a -= force + } + if Command.STRAFE_LEFT not_in commandBuffer && Command.STRAFE_RIGHT in commandBuffer { + force := VecMul( player.rightward, Mul(player.groundDrag , DotProduct(kinematics.v,player.rightward)) ) + if DotProduct(kinematics.v,player.rightward) < 0 do kinematics.a -= force + } + + player^.kinematics[player^.id] = kinematics +} + +PlayerAlignToCamera :: proc(camFoward: ^[3]f32, player: ^Player) { + foward := ToIntVector(camFoward^) + player^.foward = foward + player.rightward = CrossProduct(foward,UP) +}
A
src/simulation/octree.odin
@@ -0,0 +1,259 @@
+package simulation + +import "core:fmt" +import "core:container/intrusive/list" +import rl "vendor:raylib" +import cm "../common" + +MAX_TREE_SIZE : int : 16777216 +MAX_TREE_DEPTH : int : 5 +MAX_LEAF_SIZE : int : 64 + +OctreeNode :: struct { + center: [3]cm.iFP, + sector: OctantSector, + entities: list.List, + parent: ^OctreeNode, + topNW: ^OctreeNode, + topNE: ^OctreeNode, + topSW: ^OctreeNode, + topSE: ^OctreeNode, + botNW: ^OctreeNode, + botNE: ^OctreeNode, + botSW: ^OctreeNode, + botSE: ^OctreeNode, +} + +OctantSector :: enum { + TOPNW = 0b110, + TOPNE = 0b111, + TOPSW = 0b010, + TOPSE = 0b011, + BOTNW = 0b100, + BOTNE = 0b101, + BOTSW = 0b000, + BOTSE = 0b001, +} + +NodeEntity :: struct { + value: int, + node: list.Node +} + +octree: [dynamic]OctreeNode +sectorSize := [MAX_TREE_DEPTH]cm.iFP{858993459200,429496729600,214748364800,107374182400,53687091200} + +InitOctree :: proc() { + octree = make([dynamic]OctreeNode,0,MAX_TREE_SIZE) + append(&octree,OctreeNode{}) + GrowTree(&octree[0],&octree) +} + +DestroyOctree :: proc() { + // dont forget to free linked lists + delete(octree) +} + +GrowTree :: proc(branch: ^OctreeNode, tree: ^[dynamic]OctreeNode, recursionDepth: int=0) { + using cm + + nextNode := OctreeNode { + parent = branch, + center = branch.center, + } + halfLen := Div(sectorSize[recursionDepth],FOUR_METER) + for sector in OctantSector { + index := len(tree) + append(tree,nextNode) + switch sector { + case OctantSector.TOPNW: { + branch.topNW = &tree[index] + tree[index].sector = OctantSector.TOPNW + tree[index].center += [3]iFP{halfLen,halfLen,-halfLen} + } + case OctantSector.TOPNE: { + branch.topNE = &tree[index] + tree[index].sector = OctantSector.TOPNE + tree[index].center += [3]iFP{halfLen,halfLen,halfLen} + } + case OctantSector.TOPSW: { + branch.topSW = &tree[index] + tree[index].sector = OctantSector.TOPSW + tree[index].center += [3]iFP{-halfLen,halfLen,-halfLen} + } + case OctantSector.TOPSE: { + branch.topSE = &tree[index] + tree[index].sector = OctantSector.TOPSE + tree[index].center += [3]iFP{-halfLen,halfLen,halfLen} + } + case OctantSector.BOTNW: { + branch.botNW = &tree[index] + tree[index].sector = OctantSector.BOTNW + tree[index].center += [3]iFP{halfLen,-halfLen,-halfLen} + } + case OctantSector.BOTNE: { + branch.botNE = &tree[index] + tree[index].sector = OctantSector.BOTNE + tree[index].center += [3]iFP{halfLen,-halfLen,halfLen} + } + case OctantSector.BOTSW: { + branch.botSW = &tree[index] + tree[index].sector = OctantSector.BOTSW + tree[index].center += [3]iFP{-halfLen,-halfLen,-halfLen} + } + case OctantSector.BOTSE: { + branch.botSE = &tree[index] + tree[index].sector = OctantSector.BOTSE + tree[index].center += [3]iFP{-halfLen,-halfLen,halfLen} + } + } + if recursionDepth < MAX_TREE_DEPTH-2 do GrowTree(&tree[index], tree, recursionDepth+1) + } +} + + +FindEnclosingNode :: proc(box: AABB, point: [3]cm.iFP, tree: ^[dynamic]OctreeNode) -> ^OctreeNode { + b := AABB{ + max = box.max+point, + min = box.min+point, + } + min_leaf := FindLeaf(b.min, tree) + max_leaf := FindLeaf(b.max, tree) + + return FindCommonNode(min_leaf,max_leaf,tree) +} + +FindCommonNode :: proc(leaf1: ^OctreeNode, leaf2: ^OctreeNode, tree: ^[dynamic]OctreeNode) -> ^OctreeNode { + if leaf1 == leaf2 do return leaf1 + if leaf1.parent == nil do return leaf1 + if leaf2.parent == nil do return leaf2 + branch1 := leaf1.parent + branch2 := leaf2.parent + + for _ in 0..<MAX_TREE_DEPTH { + if branch1 == branch2 do return branch1 + if branch1.parent == nil do return branch1 + if branch2.parent == nil do return branch2 + branch1 = branch1.parent + branch2 = branch2.parent + } + + return nil +} + +FindLeaf :: proc(point: [3]cm.iFP, tree: ^[dynamic]OctreeNode) -> ^OctreeNode { + currentBranch := &tree[0] + + for _ in 0..<MAX_TREE_DEPTH { + nextNode: ^OctreeNode + switch FindOctant(point,currentBranch.center) { + case OctantSector.TOPNW: { nextNode = currentBranch.topNW } + case OctantSector.TOPNE: { nextNode = currentBranch.topNE } + case OctantSector.TOPSW: { nextNode = currentBranch.topSW } + case OctantSector.TOPSE: { nextNode = currentBranch.topSE } + case OctantSector.BOTNW: { nextNode = currentBranch.botNW } + case OctantSector.BOTNE: { nextNode = currentBranch.botNE } + case OctantSector.BOTSW: { nextNode = currentBranch.botSW } + case OctantSector.BOTSE: { nextNode = currentBranch.botSE } + } + + if nextNode.topNW == nil do return nextNode + else do currentBranch = nextNode + + } + return nil +} + +AddEntityToNode :: proc(entity: int, node: ^OctreeNode) -> bool { + newNode := new(NodeEntity) + newNode.value = entity + + return UniqueAppend(&node.entities, newNode) +} + +RemoveEntityFromNode :: proc(entity: int, node: ^OctreeNode) -> bool { + newNode: NodeEntity + newNode.value = entity + + return ListRemove(&node.entities, &newNode) +} + +FindOctant :: proc(point: [3]cm.iFP, cubeCenter: [3]cm.iFP) -> OctantSector { + isTop := point[1] > cubeCenter[1] + isNorth := point[0] > cubeCenter[0] + isWest := point[2] < cubeCenter[2] + + if isTop { + if isNorth { + if isWest do return .TOPNW + else do return .TOPNE + } else { + if isWest do return .TOPSW + else do return .TOPSE + } + } else { + if isNorth { + if isWest do return .BOTNW + else do return .BOTNE + } else { + if isWest do return .BOTSW + else do return .BOTSE + } + } +} + +IterateTree :: proc(caller: int, delta: cm.iFP, function: proc(e1: int, e2: int, delta: cm.iFP), node: ^OctreeNode) { + if !list.is_empty(&node.entities) { + it := list.iterator_head(node.entities, NodeEntity, "node") + for e in list.iterate_next(&it) { + if e.value == caller do continue + function(caller,e.value,delta) + } + } + if node.topNW == nil do return + for sector in OctantSector { + switch sector { + case OctantSector.TOPNW: { + IterateTree(caller, delta, function, node.topNW) + } + case OctantSector.TOPNE: { + IterateTree(caller, delta, function, node.topNE) + } + case OctantSector.TOPSW: { + IterateTree(caller, delta, function, node.topSW) + } + case OctantSector.TOPSE: { + IterateTree(caller, delta, function, node.topSE) + } + case OctantSector.BOTNW: { + IterateTree(caller, delta, function, node.botNW) + } + case OctantSector.BOTNE: { + IterateTree(caller, delta, function, node.botNE) + } + case OctantSector.BOTSW: { + IterateTree(caller, delta, function, node.botSW) + } + case OctantSector.BOTSE: { + IterateTree(caller, delta, function, node.botSE) + } + } + } +} + +DebugHighlightSector :: proc(tree: ^[dynamic]OctreeNode) { + for e in tree { + ent := e + if !list.is_empty(&ent.entities) { + nodeDepth: int = 0 + for i in 0..<MAX_TREE_DEPTH { + nodeDepth = i + if ent.parent == nil do break + ent = ent.parent^ + } + center := e.center + cubeSize := IntToFloat(sectorSize[nodeDepth]) + rl.DrawCubeWires(ToFloatVector(center), cubeSize, cubeSize, cubeSize, rl.RED) + } + } +}
A
src/simulation/physics.odin
@@ -0,0 +1,228 @@
+package simulation + +import "core:math" +import cm "../common" + +DerivX :: struct { + x: [3]cm.iFP, + v: [3]cm.iFP, + a: [3]cm.iFP, +} + +AABB :: struct { + solid: bool, + min: [3]cm.iFP, + max: [3]cm.iFP, +} + +Ray :: struct { + origin: [3]cm.iFP, + heading: [3]cm.iFP, +} + +KinematicUpdate :: proc(delta: cm.iFP, entities: ^[dynamic]Maybe(DerivX), boundingBoxes: ^[dynamic]Maybe(AABB), tree: ^[dynamic]OctreeNode) { + using cm + for e,i in entities^ { + eNew, ok := e.? + if !ok do continue + + startPos := eNew.x + eNew.v += VecMul(eNew.a, delta) + eNew.x += VecMul(eNew.v, delta) + + entities[i] = eNew + + bb, alsoOk := boundingBoxes[i].? + if !alsoOk do continue + + sector1 := FindEnclosingNode(bb,startPos,tree) + sector2 := FindEnclosingNode(bb,eNew.x,tree) + + if !RemoveEntityFromNode(i, sector1) { + for _ in 0..<MAX_TREE_DEPTH { + if sector1.parent == nil do break + removed := RemoveEntityFromNode(i, sector1.parent) + if removed do break + sector1 = sector1.parent + } + } + AddEntityToNode(i, sector2) + } +} + +ResetForces :: proc(entities: ^[dynamic]Maybe(DerivX)) { + using cm + for e,i in entities^ { + eNew, ok := e.? + if !ok do continue + eNew.a = [3]iFP{0,0,0} + + entities[i] = eNew + } +} + +ApplyGravity :: proc(entities: ^[dynamic]Maybe(DerivX), gravity: ^[dynamic]Maybe(bool)) { + using cm + for e,i in entities^ { + eNew, ok1 := e.? + _, ok2 := gravity[i].? + if !(ok1 && ok2) do continue + eNew.a += [3]iFP{0,GRAVITY,0} + + entities[i] = eNew + } +} + +MoveCast :: proc(delta: cm.iFP, entities: ^[dynamic]Maybe(DerivX), boundingBoxes: ^[dynamic]Maybe(AABB), callbacks: ^[dynamic]Maybe(proc(e1: int, e2: int, delta: cm.iFP)) , tree: ^[dynamic]OctreeNode) { + using cm + for ent,i in entities^ { + eNew, ok1 := ent.? + function, ok2 := callbacks[i].? + bb, ok3 := boundingBoxes[i].? + if !(ok1 && ok2 && ok3 ) do continue + + start := eNew + eNew.x = VecMul(eNew.a,Div(Mul(delta,delta),TWO_METER)) + VecMul(eNew.v,delta) + + if math.abs(DotProduct(start.x - eNew.x,start.x - eNew.x)) < TOLERENCE do continue + + sector1 := FindEnclosingNode(bb,start.x,tree) + sector2 := FindEnclosingNode(bb,eNew.x,tree) + sector12 : ^OctreeNode + + if sector1 != sector2 { + sector12 = FindCommonNode(sector1,sector2,tree) + RemoveEntityFromNode(i,sector1) + AddEntityToNode(i, sector12) + } else do sector12 = sector1 + + _,intersect := AABBCollision(bb, start.x, bb, eNew.x) + + if !intersect { // Ray + IterateTree(i, delta, RaySweep, sector12) + } + IterateTree(i, delta, function, sector12) // Bounding Box + } +} + + +AABBCollision :: proc(b1: AABB, p1: [3]cm.iFP, b2: AABB, p2: [3]cm.iFP) -> (normal: [3]cm.iFP, collided: bool) { + using cm + collision := true + penetration :iFP = INF + n : [3]iFP + + delta := p1-p2 + extent := b1.max-b1.min + b2.max-b2.min + + for d,i in delta { + if math.abs(d) >= extent[i] do collision = false + } + + if !collision do return n, collision + + gb1 := AABB { max = b1.max + p1 , min = b1.min + p1 } + gb2 := AABB { max = b2.max + p2 , min = b2.min + p2 } + + distances := [6]iFP{ + gb2.max[0]-gb1.min[0], + gb1.max[0]-gb2.min[0], + gb2.max[1]-gb1.min[1], + gb1.max[1]-gb2.min[1], + gb2.max[2]-gb1.min[2], + gb1.max[2]-gb2.min[2], + } + + for d,i in distances { + if d < penetration { + penetration = d + n = BOX_NORMALS[i] + } + } + + return n, collision +} + +RayAABBCollision :: proc(ray: Ray, bb: AABB, pos: [3]cm.iFP) -> (hit: bool, t_hit: cm.iFP, normal: [3]cm.iFP) { + using cm + bmin := bb.min+pos + bmax := bb.max+pos + tEnter := -INF + tExit := INF + normal = [3]iFP{0,0,0} + + for i in 0..<3 { + if ray.heading[i] == 0 { + if ray.origin[i] < bmin[i] || ray.origin[i] > bmax[i] { + return false, 0, normal + } + continue + } + + inv_d := Div(METER, ray.heading[i]) + + t1 := Mul(bmin[i] - ray.origin[i], inv_d); + t2 := Mul(bmax[i] - ray.origin[i], inv_d); + + faceNormal: [3]iFP = [3]iFP{0,0,0}; + + if t1 > t2 { + temp := t1; t1 = t2; t2 = temp + faceNormal[i] = METER + } else do faceNormal[i] = -METER + + if t1 > tEnter { + tEnter = t1 + normal = faceNormal + } + + if t2 < tExit do tExit = t2 + if tEnter > tExit do return false, 0, normal + } + + if tExit < 0 do return false, 0, normal + if tEnter < 0 do tEnter = 0 + + return true, tEnter, normal +} + +//Do a ray test, if it hits do e1's collsion callback +//Use delta as a parameter +RaySweep :: proc(e1: int, e2: int, delta: cm.iFP) { + using cm + x1, ok1 := entityDerivX[0][e1].? + function, ok2 := entityCollisionCallback[0][e1].? + x2, ok3 := entityDerivX[0][e2].? + bb2, ok4 := entityAABB[0][e2].? + if !(ok1 && ok2 && ok3 && ok4) do return + + x1New := VecMul(x1.a,Div(Mul(delta,delta),TWO_METER)) + VecMul(x1.v,delta) + x1.x + ray := Ray{origin=x1.x, heading=x1New} + + collided, t, _ := RayAABBCollision(ray,bb2,x2.x) + if !collided do return + + function(e1,e2,t) +} + +RigidInelasticCollision :: proc(e1: int, e2: int, delta: cm.iFP) { + using cm + x1, ok1 := entityDerivX[0][e1].? + bb1, ok2 := entityAABB[0][e1].? + x2, ok3 := entityDerivX[0][e2].? + bb2, ok4 := entityAABB[0][e2].? + if !(ok1 && ok2 && ok3 && ok4) do return + if !(bb1.solid && bb2.solid) do return + + x1New := VecMul(x1.a,Div(Mul(delta,delta),TWO_METER)) + VecMul(x1.v,delta) + x1.x + + normal, collsion := AABBCollision(bb1,x1New,bb2,x2.x) + if !collsion do return + + wallImpulse := DotProduct(normal,x1.v) + reactionForce := DotProduct(normal,x1.a) + if wallImpulse < 0 do x1.v -= VecMul(normal,wallImpulse) + if reactionForce < 0 do x1.a -= VecMul(normal,reactionForce) + + entityDerivX[0][e1] = x1 +}
A
src/simulation/simulation.odin
@@ -0,0 +1,101 @@
+package simulation + +import "core:time" +import "core:fmt" +import cm "../common" + +UP :: [3]cm.iFP{0,cm.METER,0} +DEFAULT_WALK_FORCE : cm.iFP : 21474836480 +DEFAULT_WALK_DRAG : cm.iFP : 2147483648 +DEFAULT_GROUND_DRAG : cm.iFP : 21474836480 +GRAVITY : cm.iFP : -21045340160 +ENTITY_LIMIT : int : 2048 +MAX_SNAPSHOTS : int : 3 + +Stage :: enum { + NON_DETERMINISTIC, + DETERMINISTIC, + COLLISION, +} + +Start :: proc() { + MakeComponentList(&entities) + MakeComponentList(&entityDerivX) + MakeComponentList(&entityAABB) + MakeComponentList(&entityCollisionCallback) + MakeComponentList(&entityGravity) + InitOctree() + worldClock = cm.Clock { + frames = 0, + period = 15 * time.Millisecond, + lastTick = time.tick_now() + } +} + +End :: proc() { + DestroyComponentList(&entities) + DestroyComponentList(&entityDerivX) + DestroyComponentList(&entityAABB) + DestroyComponentList(&entityCollisionCallback) + DestroyComponentList(&entityGravity) + DestroyOctree() +} + +MakeComponentList :: proc(list: ^[MAX_SNAPSHOTS]^[dynamic]$T ) { + for i in 0..<MAX_SNAPSHOTS { + list[i]^ = make(type_of(list[i]^),0,ENTITY_LIMIT) + } +} + +DestroyComponentList :: proc(list: ^[MAX_SNAPSHOTS]^[dynamic]$T ) { + for i in 0..<MAX_SNAPSHOTS { + delete(list[i]^) + } +} + +Update :: proc(clk: ^cm.Clock) { + fmt.printfln("--------------------------------------------%i", clk.frames) + delta := DoubleToInt(time.duration_seconds(time.tick_since(clk.lastTick))) + ResetForces(entityDerivX[0]) + + //Non-Deterministic Forces + PlayerAlignToCamera(&cameraFoward,&player) + PlayerMove(&player,&commandBuffer) + PlayerDrag(&player, commandBuffer) + commandBuffer = {} + + //Deterministic Forces + ApplyGravity(entityDerivX[0], entityGravity[0]) + + //Collisions + MoveCast(delta,entityDerivX[0], entityAABB[0], entityCollisionCallback[0],&octree) + + //Apply Forces + KinematicUpdate(delta, entityDerivX[0], entityAABB[0], &octree) + UpdateClient(entityDerivX[0], &clientEntityDerivX) + +} + +UpdateClient :: proc(entities: ^[dynamic]Maybe(DerivX), clientEntities: ^[dynamic]Maybe(DerivXFloat)) { + for e,i in clientEntities^ { + cEnt, ok1 := e.? + ent, ok2 := entities[i].? + if !(ok1 && ok2) do continue + cEnt.x = ToFloatVector(ent.x) + cEnt.v = ToFloatVector(ent.v) + cEnt.a = ToFloatVector(ent.a) + clientEntities^[i] = cEnt + } +} + +AvailableSlot :: proc() -> (index: int, found: bool) { + for e,i in entities[0] { + if e == false { + entities[0][i] = true + return i, true + } + } + return -1, false +} + +
A
src/simulation/state.odin
@@ -0,0 +1,10 @@
+package simulation + +worldClock: Clock +commandBuffer: bit_set[Command] + +entities: [MAX_SNAPSHOTS]^[dynamic]bool +entityDerivX: [MAX_SNAPSHOTS]^[dynamic]Maybe(DerivX) +entityAABB: [MAX_SNAPSHOTS]^[dynamic]Maybe(AABB) +entityCollisionCallback: [MAX_SNAPSHOTS]^[dynamic]Maybe(proc(e1: int, e2: int, delta: iFP)) +entityGravity: [MAX_SNAPSHOTS]^[dynamic]Maybe(bool)