Skip to content

Commit 7c03cef

Browse files
committed
Add Velocity component and system for entity movement; integrate into EngineLoop and EntityScene for circular motion demonstration
1 parent 13e3a34 commit 7c03cef

5 files changed

Lines changed: 222 additions & 3 deletions

File tree

src/core/components/EngineLoop.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ReactNode, useEffect } from 'react';
44

55
import { useGameLoop } from '../lib/gameLoop';
66
import { runPhysicsSyncSystem } from '../systems/PhysicsSyncSystem';
7+
import { runVelocitySystem } from '../systems/VelocitySystem';
78

89
// Types for component props
910
interface EngineLoopProps {
@@ -82,15 +83,18 @@ export const EngineLoop = ({
8283
* This would be expanded as more systems are added
8384
*/
8485
function runECSSystems(deltaTime: number) {
86+
// Run velocity system first - updates positions based on velocity
87+
const velocityCount = runVelocitySystem(deltaTime);
88+
8589
// Run physics sync system - syncs physics bodies with Transform components
8690
const physicsSyncCount = runPhysicsSyncSystem(deltaTime);
8791

8892
// For future systems, add them here in the appropriate order
8993
// e.g., movementSystem(ecsWorld, deltaTime);
9094

9195
// Debug info
92-
if (physicsSyncCount > 0) {
96+
if (physicsSyncCount > 0 || velocityCount > 0) {
9397
// Uncomment for debugging:
94-
// console.log(`Updated ${physicsSyncCount} physics entities`);
98+
// console.log(`Updated ${velocityCount} velocity entities and ${physicsSyncCount} physics entities`);
9599
}
96100
}

src/core/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export { useGameLoop } from './lib/gameLoop';
2727

2828
// ECS
2929
export {
30+
addVelocity,
3031
createEntity,
3132
destroyEntity,
3233
entityToObject,
@@ -35,6 +36,8 @@ export {
3536
resetWorld,
3637
Transform,
3738
transformQuery,
39+
Velocity,
40+
velocityQuery,
3841
world,
3942
} from './lib/ecs';
4043

@@ -45,3 +48,4 @@ export {
4548
unregisterPhysicsBody,
4649
} from './systems/PhysicsSyncSystem';
4750
export { transformSystem } from './systems/transformSystem';
51+
export { runVelocitySystem } from './systems/VelocitySystem';

src/core/lib/ecs.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,21 @@ export const Transform = defineComponent({
3030
needsUpdate: Types.ui8,
3131
});
3232

33+
// Velocity component for movement
34+
export const Velocity = defineComponent({
35+
// Linear velocity (x, y, z)
36+
linear: [Types.f32, 3],
37+
// Angular velocity (around x, y, z axes)
38+
angular: [Types.f32, 3],
39+
// Damping factor for linear velocity (0-1)
40+
linearDamping: Types.f32,
41+
// Damping factor for angular velocity (0-1)
42+
angularDamping: Types.f32,
43+
});
44+
3345
// Define queries
3446
export const transformQuery = defineQuery([Transform]);
47+
export const velocityQuery = defineQuery([Transform, Velocity]);
3548

3649
// Core entity functions
3750
export function createEntity() {
@@ -59,6 +72,35 @@ export function createEntity() {
5972
return entity;
6073
}
6174

75+
// Add velocity to an entity with default values
76+
export function addVelocity(
77+
entity: number,
78+
options?: {
79+
linear?: [number, number, number];
80+
angular?: [number, number, number];
81+
linearDamping?: number;
82+
angularDamping?: number;
83+
},
84+
) {
85+
addComponent(world, Velocity, entity);
86+
87+
// Set default or provided values
88+
Velocity.linear[entity][0] = options?.linear?.[0] || 0;
89+
Velocity.linear[entity][1] = options?.linear?.[1] || 0;
90+
Velocity.linear[entity][2] = options?.linear?.[2] || 0;
91+
92+
Velocity.angular[entity][0] = options?.angular?.[0] || 0;
93+
Velocity.angular[entity][1] = options?.angular?.[1] || 0;
94+
Velocity.angular[entity][2] = options?.angular?.[2] || 0;
95+
96+
Velocity.linearDamping[entity] =
97+
options?.linearDamping !== undefined ? options.linearDamping : 0.01;
98+
Velocity.angularDamping[entity] =
99+
options?.angularDamping !== undefined ? options.angularDamping : 0.01;
100+
101+
return entity;
102+
}
103+
62104
export function destroyEntity(entity: number) {
63105
// Remove from maps if associated with an object
64106
const object = entityToObject.get(entity);

src/core/systems/VelocitySystem.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Velocity System - Updates entity positions based on velocity
2+
import { Quaternion, Vector3 } from 'three';
3+
4+
import { Transform, Velocity, velocityQuery, world } from '@core/lib/ecs';
5+
6+
// Temporary vectors to avoid garbage collection
7+
const linearVelocity = new Vector3();
8+
const angularVelocity = new Vector3();
9+
const tempQuat = new Quaternion();
10+
11+
/**
12+
* System that applies velocity to transform positions
13+
*
14+
* @param deltaTime Time since last frame in seconds
15+
* @returns Number of entities processed
16+
*/
17+
export function runVelocitySystem(deltaTime: number): number {
18+
try {
19+
// Find all entities with Transform and Velocity components
20+
const entities = velocityQuery(world);
21+
22+
for (let i = 0; i < entities.length; i++) {
23+
const eid = entities[i];
24+
25+
// Get linear velocity
26+
linearVelocity.set(Velocity.linear[eid][0], Velocity.linear[eid][1], Velocity.linear[eid][2]);
27+
28+
// Skip if velocity is zero
29+
if (linearVelocity.lengthSq() > 0.00001) {
30+
// Apply linear damping
31+
const linearDamping = Velocity.linearDamping[eid];
32+
if (linearDamping > 0) {
33+
linearVelocity.multiplyScalar(Math.pow(1 - linearDamping, deltaTime));
34+
35+
// Update the component values
36+
Velocity.linear[eid][0] = linearVelocity.x;
37+
Velocity.linear[eid][1] = linearVelocity.y;
38+
Velocity.linear[eid][2] = linearVelocity.z;
39+
}
40+
41+
// Scale by delta time
42+
linearVelocity.multiplyScalar(deltaTime);
43+
44+
// Apply to position
45+
Transform.position[eid][0] += linearVelocity.x;
46+
Transform.position[eid][1] += linearVelocity.y;
47+
Transform.position[eid][2] += linearVelocity.z;
48+
49+
// Mark for update
50+
Transform.needsUpdate[eid] = 1;
51+
}
52+
53+
// Get angular velocity
54+
angularVelocity.set(
55+
Velocity.angular[eid][0],
56+
Velocity.angular[eid][1],
57+
Velocity.angular[eid][2],
58+
);
59+
60+
// Skip if angular velocity is zero
61+
if (angularVelocity.lengthSq() > 0.00001) {
62+
// Apply angular damping
63+
const angularDamping = Velocity.angularDamping[eid];
64+
if (angularDamping > 0) {
65+
angularVelocity.multiplyScalar(Math.pow(1 - angularDamping, deltaTime));
66+
67+
// Update the component values
68+
Velocity.angular[eid][0] = angularVelocity.x;
69+
Velocity.angular[eid][1] = angularVelocity.y;
70+
Velocity.angular[eid][2] = angularVelocity.z;
71+
}
72+
73+
// Scale by delta time
74+
angularVelocity.multiplyScalar(deltaTime);
75+
76+
// Create a rotation quaternion from the angular velocity
77+
// This is a simplified approach - in a real physics system, we'd use more accurate integration
78+
tempQuat.setFromAxisAngle(angularVelocity.normalize(), angularVelocity.length());
79+
80+
// Current rotation
81+
const currentQuat = new Quaternion(
82+
Transform.rotation[eid][0],
83+
Transform.rotation[eid][1],
84+
Transform.rotation[eid][2],
85+
Transform.rotation[eid][3],
86+
);
87+
88+
// Apply rotation (multiply quaternions)
89+
currentQuat.premultiply(tempQuat);
90+
currentQuat.normalize(); // Normalize to prevent floating point errors accumulating
91+
92+
// Update the transform rotation
93+
Transform.rotation[eid][0] = currentQuat.x;
94+
Transform.rotation[eid][1] = currentQuat.y;
95+
Transform.rotation[eid][2] = currentQuat.z;
96+
Transform.rotation[eid][3] = currentQuat.w;
97+
98+
// Mark for update
99+
Transform.needsUpdate[eid] = 1;
100+
}
101+
}
102+
103+
return entities.length;
104+
} catch (error) {
105+
console.error('Error in velocity system:', error);
106+
return 0;
107+
}
108+
}

src/game/scenes/EntityScene.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Physics, RigidBody } from '@react-three/rapier';
44
import React, { useEffect, useRef, useState } from 'react';
55
import { Mesh } from 'three';
66

7-
import { Entity, PhysicsBox, PhysicsSphere } from '@/core';
7+
import { Entity, PhysicsBox, PhysicsSphere, addVelocity } from '@/core';
88

99
/**
1010
* Entity Scene Component
@@ -61,6 +61,9 @@ export const EntityScene: React.FC = () => {
6161

6262
{/* Custom entity with update logic */}
6363
<RotatingEntity />
64+
65+
{/* Entity using velocity component */}
66+
<VelocityEntity />
6467
</>
6568
)}
6669
</Physics>
@@ -150,4 +153,62 @@ const RotatingEntity: React.FC = () => {
150153
);
151154
};
152155

156+
/**
157+
* Entity that uses the Velocity component to move in a circular pattern
158+
*/
159+
const VelocityEntity: React.FC = () => {
160+
const entityRef = useRef<number | null>(null);
161+
const time = useRef(0);
162+
163+
// Setup and update velocity for circular movement
164+
useFrame((_, delta) => {
165+
time.current += delta;
166+
167+
// Calculate velocity for circular movement
168+
if (entityRef.current !== null) {
169+
// Example of how to change velocity dynamically
170+
// This creates a circular-like motion by changing velocity direction
171+
const speed = 2;
172+
const circleRadius = 3;
173+
174+
// Calculate velocity based on position in circle
175+
const angle = time.current * 0.5; // Rotate slower than time
176+
const vx = Math.cos(angle) * speed;
177+
const vz = Math.sin(angle) * speed;
178+
179+
// Update the velocity - normally we'd use ECS component access but
180+
// for demo purposes and to avoid race conditions, we're calling addVelocity
181+
addVelocity(entityRef.current, {
182+
linear: [vx, 0, vz],
183+
linearDamping: 0.1, // Add some damping to smooth movement
184+
});
185+
}
186+
});
187+
188+
return (
189+
<Entity
190+
position={[-3, 0.5, 0]}
191+
onUpdate={(entityId) => {
192+
// Store the entity ID on first update
193+
if (entityRef.current === null) {
194+
entityRef.current = entityId;
195+
196+
// Initialize velocity with zero to add the component
197+
addVelocity(entityId, {
198+
linear: [0, 0, 0],
199+
angular: [0, 0.5, 0], // Add some spin too
200+
linearDamping: 0.1,
201+
angularDamping: 0.01,
202+
});
203+
}
204+
}}
205+
>
206+
<mesh castShadow>
207+
<dodecahedronGeometry args={[0.7]} />
208+
<meshStandardMaterial color="#00ffcc" />
209+
</mesh>
210+
</Entity>
211+
);
212+
};
213+
153214
export default EntityScene;

0 commit comments

Comments
 (0)