Skip to content

Commit 183442f

Browse files
committed
feat: Implement 2D physics system with Rapier integration
1 parent 8544e38 commit 183442f

39 files changed

Lines changed: 922 additions & 123 deletions

.github/workflows/publish-examples.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- name: Set up Node.js
1616
uses: actions/setup-node@v4
1717
with:
18-
node-version: "20"
18+
node-version: "24"
1919

2020
- name: Install pnpm
2121
uses: pnpm/action-setup@v4

examples/showcase/package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@
99
"preview": "vite preview"
1010
},
1111
"dependencies": {
12+
"@silwalanish/engine": "workspace:*",
1213
"@silwalanish/geometry": "workspace:*",
14+
"@silwalanish/physics": "workspace:*",
1315
"@silwalanish/planet": "workspace:*",
1416
"@silwalanish/renderer": "workspace:*",
1517
"@silwalanish/scene": "workspace:*",
1618
"@silwalanish/shader": "workspace:*",
17-
"@silwalanish/engine": "workspace:*",
18-
"gl-matrix": "^3.4.3"
19+
"gl-matrix": "^3.4.3",
20+
"nanoid": "^5.1.5"
1921
},
2022
"devDependencies": {
2123
"@silwalanish/tsconfig": "workspace:*",
2224
"@types/three": "^0.176.0",
2325
"typescript": "~5.7.2",
24-
"vite": "^6.3.1"
26+
"vite": "^6.3.1",
27+
"vite-plugin-top-level-await": "^1.5.0",
28+
"vite-plugin-wasm": "^3.4.1"
2529
}
2630
}

examples/showcase/src/app.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
import { vec3, vec4 } from "gl-matrix";
2+
import { Plane } from "@silwalanish/geometry";
3+
import { GameObject } from "@silwalanish/engine";
24
import { BasicMaterial } from "@silwalanish/shader";
35
import { GroundManager } from "@silwalanish/planet";
6+
import { PhysicsScene2D } from "@silwalanish/physics";
7+
import { Camera, MeshComponent, SceneNode } from "@silwalanish/scene";
48
import {
59
CanvasSurface,
610
Orthographic,
711
Perspective,
812
Renderer,
913
} from "@silwalanish/renderer";
10-
import {
11-
Camera,
12-
MeshComponent,
13-
SceneGraph,
14-
SceneNode,
15-
} from "@silwalanish/scene";
16-
import { Plane } from "@silwalanish/geometry";
14+
1715
import { Player } from "./player";
1816
import { PlayerAction } from "./playercontrol";
1917

@@ -27,12 +25,12 @@ export class App {
2725
private _lastTime: number;
2826
private _surface: CanvasSurface;
2927
private _renderer: Renderer;
30-
private _scene: SceneGraph;
28+
private _scene: PhysicsScene2D;
3129
private _perspective: Perspective;
3230
private _orthographic: Orthographic;
3331
private _groundManager: GroundManager;
34-
private _camera: Camera;
35-
private _sky: SceneNode;
32+
private _camera: Camera<any>;
33+
private _sky: GameObject<any>;
3634
private _player: Player;
3735
private _domElement: HTMLDivElement;
3836
private _speedLabel: HTMLLabelElement;
@@ -46,13 +44,13 @@ export class App {
4644
});
4745
this._renderer = new Renderer(this._surface);
4846

49-
this._scene = new SceneGraph();
47+
this._scene = new PhysicsScene2D();
5048
this._perspective = new Perspective(90, 1.33, 0.01, 1000);
51-
this._orthographic = new Orthographic(-50, 50, -50, 50, 0.01, 1000);
49+
this._orthographic = new Orthographic(-50, 50, 0, 100, 0.01, 1000);
5250
this._camera = new Camera("activeCamera");
5351
this._groundManager = new GroundManager(this._camera);
5452
this._sky = new SceneNode("sky");
55-
this._player = new Player(this._camera);
53+
this._player = new Player();
5654

5755
this._speedLabel = document.createElement("label");
5856

@@ -146,7 +144,7 @@ export class App {
146144
this._scene.camera = this._camera;
147145

148146
this._sky.mesh = new MeshComponent(
149-
new Plane(150, 100),
147+
new Plane(150, 400),
150148
new BasicMaterial("skyMaterial", vec4.fromValues(0.5, 0.7, 1, 1))
151149
);
152150
this._sky.transform.Position = vec3.fromValues(0, 0, -20);
@@ -173,8 +171,17 @@ export class App {
173171
3,
174172
"0"
175173
);
174+
this._scene.physicsUpdate();
175+
176176
this._scene.update(deltaTime);
177177
this._renderer.render(this._scene, this._orthographic);
178+
179+
this._camera.transform.Position = vec3.fromValues(
180+
this._player.transform.getWorldPosition()[0],
181+
this._camera.transform.Position[1],
182+
this._camera.transform.Position[2]
183+
);
184+
this._camera.lookAt(vec3.fromValues(0, 0, -1));
178185
}
179186

180187
window.requestAnimationFrame(() => {

examples/showcase/src/main.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import "./app.css";
22
import { App } from "./app";
33

4-
window.addEventListener("DOMContentLoaded", () => {
4+
import { initPhysics } from "@silwalanish/physics";
5+
6+
initPhysics();
7+
8+
window.addEventListener("rapierLoaded", () => {
59
const element = document.getElementById("app");
610
if (!element) {
711
throw new Error("Element with id 'app' not found");

examples/showcase/src/player.ts

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
1-
import { vec3, vec4 } from "gl-matrix";
1+
import { vec2, vec3, vec4 } from "gl-matrix";
22
import { Plane } from "@silwalanish/geometry";
33
import { BasicMaterial } from "@silwalanish/shader";
4-
import { Camera, MeshComponent, SceneNode } from "@silwalanish/scene";
4+
import { Physics2DType } from "@silwalanish/engine";
5+
import { MeshComponent, SceneNode } from "@silwalanish/scene";
6+
import { RapierPhysics2D, PhysicsShapeType, Box } from "@silwalanish/physics";
7+
58
import { PlayerControl } from "./playercontrol";
69

7-
const MAX_ACCELERATION = 100.0;
8-
const MAX_DECCELERATION = 0.0;
10+
const MAX_SPEED = 100.0;
911
const ACCELERATION_RATE = 5.0;
10-
const DECCLERATION_RATE = 20.0;
11-
12-
export class Player extends SceneNode {
13-
private _camera: Camera;
12+
const DECCLERATION_RATE = 5.0;
1413

14+
export class Player extends SceneNode<PhysicsShapeType> {
1515
private _speed: number = 0.0;
1616
private _control: PlayerControl;
1717

18-
public constructor(camera: Camera) {
18+
public constructor() {
1919
super("player");
2020

21-
this._camera = camera;
2221
this._control = new PlayerControl();
2322

2423
this.mesh = new MeshComponent(
25-
new Plane(2, 5),
24+
new Plane(2, 2),
2625
new BasicMaterial("playerMaterial", vec4.fromValues(1, 0, 0, 1))
2726
);
27+
28+
this.physics = new RapierPhysics2D(
29+
Physics2DType.DYNAMIC,
30+
10.0, // mass
31+
0.0, // friction
32+
Box(1, 1)
33+
);
34+
35+
this.transform.Position = vec3.fromValues(0, 70, 0);
2836
}
2937

3038
public get control() {
@@ -35,28 +43,27 @@ export class Player extends SceneNode {
3543
return this._speed;
3644
}
3745

38-
public override update(deltaTime: number): void {
39-
super.update(deltaTime);
46+
public override physicsUpdate(): void {
47+
super.physicsUpdate();
4048

41-
if (this._control.isAccelerating) {
42-
this._speed = Math.min(
43-
this._speed + ACCELERATION_RATE * deltaTime,
44-
MAX_ACCELERATION
45-
);
46-
} else if (this._control.isDeccelerating) {
47-
this._speed = Math.max(
48-
this._speed - DECCLERATION_RATE * deltaTime,
49-
-MAX_DECCELERATION
50-
);
51-
} else if (this._speed > 0.0) {
52-
this._speed = Math.max(this._speed - ACCELERATION_RATE * deltaTime, 0.0);
53-
} else if (this._speed < 0.0) {
54-
this._speed = Math.min(this._speed + ACCELERATION_RATE * deltaTime, 0.0);
49+
if (!this.physicsBody) {
50+
return;
5551
}
5652

57-
this.transform.Position[0] += this._speed * deltaTime;
53+
const body = this.physicsBody;
5854

59-
this._camera.transform.Position[0] = this.transform.Position[0];
60-
this._camera.lookAt(vec3.fromValues(0, 0, -1));
55+
this._speed = body.velocity[0];
56+
57+
if (this._speed < MAX_SPEED) {
58+
if (this._control.isAccelerating) {
59+
body.applyImpulse(vec2.fromValues(ACCELERATION_RATE, 0));
60+
} else if (this._control.isDeccelerating) {
61+
body.applyImpulse(vec2.fromValues(-DECCLERATION_RATE, 0));
62+
}
63+
}
64+
}
65+
66+
public override update(deltaTime: number): void {
67+
super.update(deltaTime);
6168
}
6269
}

examples/showcase/vite.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
import wasm from "vite-plugin-wasm";
2+
import topLevelAwait from "vite-plugin-top-level-await";
3+
14
/** @type {import('vite').UserConfig} */
25
export default {
36
base: "./",
7+
plugins: [wasm(), topLevelAwait()],
48
};

packages/engine/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
},
2121
"scripts": {
2222
"build": "tsc",
23-
"clean": "tsc --build --clean",
23+
"clean": "tsc --build --clean && rm -rf dist",
2424
"dev": "tsc --watch",
2525
"test": "echo \"Error: no test specified\" && exit 1"
2626
},

packages/engine/src/gameobject.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
11
import { Mesh } from "./mesh";
2+
import { Scene } from "./scene";
23
import { Physics2D } from "./physics2d";
34
import { Transform } from "./transform";
45
import { PhysicsBody } from "./physicsbody";
56

6-
export interface GameObject {
7+
export interface GameObject<ShapeType = any> {
78
get id(): string;
89
get name(): string;
910
get mesh(): Mesh | null;
1011
get transform(): Transform;
1112
get children(): GameObject[];
12-
get physics(): Physics2D | null;
13+
get physics(): Physics2D<ShapeType> | null;
1314
get parent(): GameObject | null;
1415
get physicsBody(): PhysicsBody | null;
16+
get scene(): Scene<ShapeType> | null;
1517

1618
set mesh(value: Mesh | null);
1719
set transform(value: Transform);
18-
set physics(value: Physics2D | null);
20+
set physics(value: Physics2D<ShapeType> | null);
1921
set parent(value: GameObject | null);
2022
set physicsBody(value: PhysicsBody | null);
23+
set scene(value: Scene<ShapeType> | null);
2124

2225
update(deltaTime: number): void;
26+
physicsUpdate(): void;
2327
addChild(child: GameObject): void;
2428
removeChild(child: GameObject): void;
2529
}

packages/engine/src/physics2d.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
import { vec2 } from "gl-matrix";
2-
31
export enum Physics2DType {
42
STATIC = "static",
53
DYNAMIC = "dynamic",
64
KINEMATIC = "kinematic",
75
}
86

9-
export interface Physics2D {
7+
export interface Physics2D<TShapeType> {
108
get id(): string;
119
get type(): Physics2DType;
1210

13-
get density(): number;
11+
get mass(): number;
1412
get friction(): number;
1513

16-
get shape(): vec2[];
14+
get shape(): TShapeType;
1715
}

packages/engine/src/physicsbody.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
import { vec2 } from "gl-matrix";
2+
13
export interface PhysicsBody {
24
get id(): string;
5+
6+
get velocity(): vec2;
7+
8+
resetForces(): void;
9+
10+
addForce(force: vec2): void;
11+
applyImpulse(impulse: vec2): void;
312
}

0 commit comments

Comments
 (0)