Skip to content

Commit

Permalink
Robot Simulator (Part 1) (#300)
Browse files Browse the repository at this point in the history
* Initial commit

* Add some permissions thing

* Get the templating done for the modal up and down

* Add permisisons to all

* Add the first edition of rapier-three controller

* Initialization of my module

* Make a cube

* Add ambient light

* Added the suspension forced

* Remove car from init_meshes

* Try to init

* Change init

* Added the iterator

* Add realistic dimensions and weight to the car

* Massive changes

* Massive changes again

* motor done

* changed

* fix type

* Clean up code

* Formatting

* Move buffer

* Change the wheel

* Add the interception

* MAssive push

* Added color sensor

* Change folder name

* Remove console.log for impulse

* Add ultrasonic sensor

* Minor changes to ultrasonic sensor

* Achieved determinism

* Add ultrasonic motor function

* Add new event handler and event console

* Remove unneeded files

* Minor modifications so that js-slang/context is not imported

* Add @types/three

* Update threejs

* Change mesh to without wheels

* Add console

* Add wheel mesh logic

* Add a bunch of test

* Did partial tuning of the motor

* Change the way the timing is done

* Make CallbackHandler deterministic

* Make wall yellow

* Fix the program identifier bug and callback controller determinism

* Fix controller bug and add test

* Make Three implement SimpleVector and MeshFactory tests

* Add a comment in jest polyfills

* Function clean ups

* Change the paths in the tests

* Make the lighting nicer

* Add Paper and DebugArrow

* Massive refactor

* Fix Mesh Factory test

* Update tests

* Add docs for ev3_functions

* Add documentation for ev3_pause

* Change the version of three

* Change the quotes from double to single

* Remove part 2 from robot simulator

* Remove unused libraries in package.json

* Fix eslint errors

* Fix more eslint errors

* Fix more eslint errors

* More linting errors

* update yarn.lock

* Change the react components to React.FC

* Remove the empty file

* More formatting changes

* Change RecursivePartial to DeepPartial
  • Loading branch information
JoelChanZhiYang authored Apr 1, 2024
1 parent 048f012 commit 15ba7a7
Show file tree
Hide file tree
Showing 53 changed files with 2,635 additions and 0 deletions.
Empty file modified .husky/.gitignore
100644 → 100755
Empty file.
Empty file modified .husky/post-checkout
100644 → 100755
Empty file.
Empty file modified .husky/post-merge
100644 → 100755
Empty file.
Empty file modified .husky/post-rewrite
100644 → 100755
Empty file.
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
Empty file modified .husky/pre-push
100644 → 100755
Empty file.
5 changes: 5 additions & 0 deletions modules.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
"physics_2d"
]
},
"robot_simulation": {
"tabs": [
"RobotSimulation"
]
},
"ar": {
"tabs": [
"AugmentedReality"
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@types/plotly.js-dist": "npm:@types/plotly.js",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/three": "^0.161.2",
"@vitejs/plugin-react": "^4.0.4",
"acorn": "^8.8.1",
"acorn-jsx": "^5.3.2",
Expand Down Expand Up @@ -96,6 +97,7 @@
"@blueprintjs/icons": "^5.7.1",
"@box2d/core": "^0.10.0",
"@box2d/debug-draw": "^0.10.0",
"@dimforge/rapier3d-compat": "^0.11.2",
"@jscad/modeling": "2.9.6",
"@jscad/regl-renderer": "^2.6.1",
"@jscad/stl-serializer": "2.1.11",
Expand All @@ -120,6 +122,7 @@
"save-file": "^2.3.1",
"source-academy-utils": "^1.0.0",
"source-academy-wabt": "^1.0.4",
"three": "^0.162.0",
"tslib": "^2.3.1",
"uniqid": "^5.4.0",
"url": "^0.11.3"
Expand Down
4 changes: 4 additions & 0 deletions src/bundles/robot_simulation/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const sceneConfig = {
width: 900,
height: 500,
};
68 changes: 68 additions & 0 deletions src/bundles/robot_simulation/controllers/environment/Cuboid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as THREE from 'three';

import {
EntityFactory,
MeshFactory,
type Physics,
type Renderer,
} from '../../engine';
import type {
EntityCuboidOptions,
RigidBodyType,
} from '../../engine/Entity/EntityFactory';
import type { Dimension, SimpleVector } from '../../engine/Math/Vector';
import type { RenderCuboidOptions } from '../../engine/Render/helpers/MeshFactory';

export type CuboidConfig = {
position: SimpleVector;
dimension: Dimension;
mass: number;
color: number | string;
type: RigidBodyType;
};

const noRotation = {
x: 0,
y: 0,
z: 0,
w: 1,
};

export class Cuboid {
physics: Physics;
render: Renderer;
config: CuboidConfig;

constructor(physics: Physics, renderer: Renderer, config: CuboidConfig) {
this.physics = physics;
this.render = renderer;
this.config = config;

const renderCuboidOption: RenderCuboidOptions = {
orientation: {
position: config.position,
rotation: noRotation,
},
dimension: config.dimension,
color: new THREE.Color(config.color),
debug: false,
};

const mesh = MeshFactory.addCuboid(renderCuboidOption);
this.render.add(mesh);
}

start() {
const entityCuboidOption: EntityCuboidOptions = {
orientation: {
position: this.config.position,
rotation: noRotation,
},
dimension: this.config.dimension,
mass: this.config.mass,
type: this.config.type,
};

EntityFactory.addCuboid(this.physics, entityCuboidOption);
}
}
6 changes: 6 additions & 0 deletions src/bundles/robot_simulation/controllers/program/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class ProgramError extends Error {
constructor(message) {
super(message);
this.name = 'ProgramError';
}
}
9 changes: 9 additions & 0 deletions src/bundles/robot_simulation/controllers/utils/mergeConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as _ from 'lodash';
import type { DeepPartial } from '../../../../common/deepPartial';

export const mergeConfig = <T> (defaultConfig:T, userConfig?: DeepPartial<T>) :T => {
if (userConfig === undefined) {
return defaultConfig;
}
return _.merge({ ...defaultConfig }, userConfig);
};
39 changes: 39 additions & 0 deletions src/bundles/robot_simulation/engine/Core/CallbackHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { PhysicsTimingInfo } from '../Physics';

export type TimeoutCallback = () => void;
export type CallbackEntry = { callback: TimeoutCallback; delay: number };

export class CallbackHandler {
callbackStore: Array<CallbackEntry>;
currentStepCount: number;

constructor() {
this.callbackStore = [];
this.currentStepCount = 0;
}

addCallback(callback: TimeoutCallback, delay: number): void {
this.callbackStore.push({
callback,
delay,
});
}

checkCallbacks(frameTimingInfo: PhysicsTimingInfo): void {
if (frameTimingInfo.stepCount === this.currentStepCount) {
return;
}

this.currentStepCount = frameTimingInfo.stepCount;

this.callbackStore = this.callbackStore.filter((callbackEntry) => {
callbackEntry.delay -= frameTimingInfo.timestep;

if (callbackEntry.delay <= 0) {
callbackEntry.callback();
return false;
}
return true;
});
}
}
93 changes: 93 additions & 0 deletions src/bundles/robot_simulation/engine/Core/Controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { type PhysicsTimingInfo } from '../Physics';

export interface Controller {
name?: string;
start?(): Promise<void> | void;
update?(timingInfo: PhysicsTimingInfo): void;
fixedUpdate?(timingInfo: PhysicsTimingInfo): void;
onDestroy?(): void;
}

export class ControllerMap<M extends Record<string, Controller>>
implements Controller {
map: M;
callbacks?: Partial<Controller>;

constructor(map: M, callbacks?: Partial<Controller>) {
this.map = map;
this.callbacks = callbacks;
}

get<K extends keyof M>(name: K): M[K] {
return this.map[name];
}

async start(): Promise<void> {
await this.callbacks?.start?.();
await Promise.all(
Object.values(this.map)
.map(async (controller) => {
await controller.start?.();
}),
);
}

update(timingInfo: PhysicsTimingInfo): void {
this.callbacks?.update?.(timingInfo);
Object.values(this.map)
.forEach((controller) => {
controller.update?.(timingInfo);
});
}

fixedUpdate(timingInfo: PhysicsTimingInfo): void {
this.callbacks?.fixedUpdate?.(timingInfo);
Object.values(this.map)
.forEach((controller) => {
controller.fixedUpdate?.(timingInfo);
});
}

onDestroy(): void {
this.callbacks?.onDestroy?.();
Object.values(this.map)
.forEach((controller) => {
controller.onDestroy?.();
});
}
}

export class ControllerGroup implements Controller {
controllers: Controller[] = [];

public addController(...controllers: Controller[]): void {
this.controllers.push(...controllers);
}

async start?(): Promise<void> {
await Promise.all(
this.controllers.map(async (controller) => {
await controller.start?.();
}),
);
}

update(timingInfo: PhysicsTimingInfo): void {
this.controllers.forEach((controller) => {
controller.update?.(timingInfo);
});
}

fixedUpdate(timingInfo: PhysicsTimingInfo): void {
this.controllers.forEach((controller) => {
controller.fixedUpdate?.(timingInfo);
});
}

onDestroy(): void {
this.controllers.forEach((controller) => {
controller.onDestroy?.();
});
this.controllers = [];
}
}
41 changes: 41 additions & 0 deletions src/bundles/robot_simulation/engine/Core/Events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export type Listener<EventMap, EventName extends keyof EventMap> = (
event: EventMap[EventName]
) => Promise<void> | void;

type ValueIsEvent<EventMap> = {
[EventName in keyof EventMap]: Event;
};

export class TypedEventTarget<EventMap extends ValueIsEvent<EventMap>> {
private listeners: {
[EventName in keyof EventMap]?: Array<Listener<EventMap, EventName>>;
};

constructor() {
this.listeners = {};
}

addEventListener<EventName extends string & keyof EventMap>(
type: EventName,
listener: Listener<EventMap, EventName>,
): void {
if (!this.listeners[type]) {
this.listeners[type] = [];
}
// Non-null assertion is safe because we just checked if it's undefined
this.listeners[type]!.push(listener);
}

public dispatchEvent<EventName extends keyof EventMap>(
_type: EventName,
event: EventMap[EventName],
): boolean {
const listeners = this.listeners[_type];
if (listeners) {
listeners.forEach((listener) => {
listener(event);
});
}
return true;
}
}
28 changes: 28 additions & 0 deletions src/bundles/robot_simulation/engine/Core/RobotConsole.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const logLevels = ['source', 'error'] as const;
type LogLevel = typeof logLevels[number];

export type LogEntry = {
message: string;
level: LogLevel;
timestamp: number;
};

export class RobotConsole {
logs: LogEntry[];

constructor() {
this.logs = [];
}

log(message: string, level) {
this.logs.push({
message,
level,
timestamp: Date.now(),
});
}

getLogs() {
return this.logs;
}
}
63 changes: 63 additions & 0 deletions src/bundles/robot_simulation/engine/Core/Timer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export type FrameTimingInfo = {
elapsedTimeReal: number;
elapsedTimeSimulated: number;
frameDuration: number;
framesPerSecond: number;
};

export class Timer {
private _isRunning = false;
private _elapsedSimulatedTime = 0;
private _timeSpentPaused = 0;
private _frameDuration = 0;

private _startTime: number | null = null;
private _pausedAt: number | null = null;
private _currentTime: number | null = null;

/**
* Pauses the timer and marks the pause time.
*/
pause(): void {
if (this._currentTime === null) {
return;
}

if (this._isRunning) {
this._isRunning = false;
this._pausedAt = this._currentTime;
}
}

/**
* Steps the timer forward, calculates frame timing info.
* @param timestamp - The current timestamp.
* @returns The frame timing information.
*/
step(timestamp: number): FrameTimingInfo {
if (this._startTime === null) {
this._startTime = timestamp;
}

if (!this._isRunning) {
this._isRunning = true;
}

if (this._pausedAt !== null) {
this._timeSpentPaused += timestamp - this._pausedAt;
this._pausedAt = null;
}

this._frameDuration = this._currentTime ? timestamp - this._currentTime : 0;
this._currentTime = timestamp;

this._elapsedSimulatedTime = timestamp - this._startTime - this._timeSpentPaused;

return {
elapsedTimeReal: this._currentTime - this._startTime,
elapsedTimeSimulated: this._elapsedSimulatedTime,
frameDuration: this._frameDuration,
framesPerSecond: 1000 / this._frameDuration,
};
}
}
Loading

0 comments on commit 15ba7a7

Please sign in to comment.