Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ECS + Sodium + THREE.js
import * as sodium from 'sodiumjs';
import * as THREE from 'three';
import { EcsComponentType } from './EcsComponentType';
import { EcsReadOnlySceneContext } from './EcsReadOnlySceneContext';
import { EcsSceneChanges } from './EcsSceneChanges';
import { IsEcsComponentValue } from './IsEcsComponentValue';
import { ArcComponent } from './components/ArcComponent';
import { Axes2DComponent } from './components/Axes2DComponent';
import { CircleComponent } from './components/CircleComponent';
import { Line3DComponent } from './components/Line3DComponent';
import { Lazy } from '../../Lazy';
import { Model, vector3DToTHREE, THREEObject3DSetAxes } from '../../MkModelEffect';
import { Option } from '../../Option';
import * as SodiumUtil from '../../SodiumUtil';
import { T2 } from '../../Tuples';
import { Axes2D } from '../../math/Axes2D';
import { Axes3D } from '../../math/Axes3D';
import { Line3D } from '../../math/Line3D';
import { Quaternion } from '../../math/Quaternion';
import { toRadians } from '../../math/Util';
import { Vector3D } from '../../math/Vector3D';
export class EcsTHREESceneUpdater {
private _cSceneCtxOp: sodium.Cell<Option<EcsReadOnlySceneContext>>;
private _sSceneChanges: sodium.Stream<EcsSceneChanges>;
private _scene: THREE.Scene;
private _repaintCallback: ()=>void;
private _entityIdModelMap: Map<string,Model>[] = [];
private _cleanups: (()=>void)[] = [];
private _connected: boolean = false;
private constructor(
cSceneCtxOp: sodium.Cell<Option<EcsReadOnlySceneContext>>,
sSceneChanges: sodium.Stream<EcsSceneChanges>,
scene: THREE.Scene,
repaintCallback: ()=>void
) {
this._cSceneCtxOp = cSceneCtxOp;
this._sSceneChanges = sSceneChanges;
this._scene = scene;
this._repaintCallback = repaintCallback;
}
public static create(
cSceneCtxOp: sodium.Cell<Option<EcsReadOnlySceneContext>>,
sSceneChanges: sodium.Stream<EcsSceneChanges>,
scene: THREE.Scene,
repaintCallback: ()=>void
): EcsTHREESceneUpdater {
return new EcsTHREESceneUpdater(
cSceneCtxOp,
sSceneChanges,
scene,
repaintCallback
);
}
public connect() {
if (this._connected) {
return;
}
sodium.Transaction.run(() => {
this.initScene(this._cSceneCtxOp.sample());
this._cleanups.push(this._sSceneChanges.listen(sceneChanges => {
let sceneCtxOp = this._cSceneCtxOp.sample();
for (let i = 0; i < sceneChanges.changes.length; ++i) {
let sceneChange = sceneChanges.changes[i];
sceneChange.match({
entityCreated: (entityId: number) => () => {
},
entityDestroyed: (entityId: number) => () => {
this.onEntityDestroyed(entityId);
},
componentsSet: (entityId: number, components: IsEcsComponentValue[]) => () => {
let axes2d_ = Lazy.create(() => {
if (sceneCtxOp.isNone) {
return Axes2D.identity;
}
let sceneCtx = sceneCtxOp.fromSome();
return sceneCtx.getComponent(entityId, Axes2DComponent.ecsComponent).map(x => x.axes2D()).orSome(Axes2D.identity);
});
let axes3d_ = Lazy.create(() => {
let axes2d = axes2d_.get();
return Axes3D.create(
axes2d.origin.toVector3D(),
Quaternion.fromWU(Vector3D.unitZ, axes2d.orientation.u.toVector3D())
);
});
for (let i = 0; i < components.length; ++i) {
let component = components[i].ecsComponentValue();
let lineOp = component.value$com_shedmasta_core_ecs2_EcsComponent(Line3DComponent.ecsComponent).map(x => x.line);
if (lineOp.isSome) {
let line = lineOp.fromSome();
if (!this.entityModelExists(entityId, Line3DComponent.ecsComponent.type().typeName())) {
this.onLineAdded(entityId, line);
}
}
let circleOp = component.value$com_shedmasta_core_ecs2_EcsComponent(CircleComponent.ecsComponent);
if (circleOp.isSome) {
let circle = circleOp.fromSome();
if (!this.entityModelExists(entityId, CircleComponent.ecsComponent.type().typeName())) {
this.onCircleAdded(entityId, axes3d_.get(), circle.radius());
}
}
let arcOp = component.value$com_shedmasta_core_ecs2_EcsComponent(ArcComponent.ecsComponent);
if (arcOp.isSome) {
let arc = arcOp.fromSome();
if (!this.entityModelExists(entityId, ArcComponent.ecsComponent.type().typeName())) {
this.onArcAdded(entityId, axes3d_.get(), arc.radius(), arc.startAngle(), arc.extent());
}
}
}
},
componentsUnset: (entityId: number, componentTypes: EcsComponentType[]) => () => {
}
})();
}
this._repaintCallback();
}));
});
this._connected = true;
this._repaintCallback();
}
public disconnect() {
if (!this._connected) {
return;
}
this._cleanups.forEach(cleanup => cleanup());
this._cleanups = [];
this._connected = false;
}
private initScene(sceneCtxOp: Option<EcsReadOnlySceneContext>) {
if (sceneCtxOp.isNone) {
return;
}
let sceneCtx = sceneCtxOp.fromSome();
sceneCtx
.entitiesWithAllComponents([Line3DComponent.ecsComponent.type()])
.forEach(lineId => {
let lineOp = sceneCtx.getComponent(lineId, Line3DComponent.ecsComponent).map(x => x.line);
if (lineOp.isNone) {
return;
}
let line = lineOp.fromSome();
this.onLineAdded(lineId, line);
});
}
private onLineAdded(entityId: number, line: Line3D) {
let sLineChange =
SodiumUtil.streamFilterOption(
this._sSceneChanges.map(
sceneChanges => {
let lineChangeOp = Option.none<Line3D>();
for (let i = 0; i < sceneChanges.changes.length; ++i) {
let sceneChange = sceneChanges.changes[i];
lineChangeOp =
sceneChange
.partialMatch(Option.none<Line3D>(), {
componentsSet: (entityId2, components) => {
if (entityId2 != entityId) {
return Option.none<Line3D>();
}
let lineChangeOp = Option.none<Line3D>();
for (let j = 0; j < components.length; ++j) {
let component = components[j].ecsComponentValue();
lineChangeOp =
component
.value$com_shedmasta_core_ecs2_EcsComponent(Line3DComponent.ecsComponent)
.map(x => x.line)
.orElse(lineChangeOp);
}
return lineChangeOp;
}
})
.orElse(lineChangeOp);
}
return lineChangeOp;
}
)
);
let model = Model.create(
() => {
let geometry = new THREE.Geometry();
geometry.vertices.push(vector3DToTHREE(line.v1));
geometry.vertices.push(vector3DToTHREE(line.v2));
let material = new THREE.LineBasicMaterial({ color: 0x000000 });
let line2 = new THREE.Line(geometry, material);
//
let cleanups: (()=>void)[] = [];
cleanups.push(sLineChange.listen(
lineChange => {
geometry.vertices[0].set(lineChange.v1.x, lineChange.v1.y, lineChange.v1.z);
geometry.vertices[1].set(lineChange.v2.x, lineChange.v2.y, lineChange.v2.z);
geometry.verticesNeedUpdate = true;
this._repaintCallback();
}
));
cleanups.push(() => {
geometry.dispose();
material.dispose();
});
return T2.of(
line2,
() => cleanups.forEach(cleanup => cleanup())
);
}
);
this._scene.add(model.threeObject3D);
this.setEntityModel(entityId, Line3DComponent.ecsComponent.type().typeName(), model);
}
private onCircleAdded(entityId: number, axes: Axes3D, radius: number) {
let model = Model.create(
() => {
let geometry = new THREE.CircleGeometry(radius, 32);
geometry.vertices.shift();
let material = new THREE.LineBasicMaterial({ color: 0x000000 });
let circle = new THREE.LineLoop(geometry, material);
THREEObject3DSetAxes(circle, axes);
//
let cleanups: (()=>void)[] = [];
cleanups.push(() => {
geometry.dispose();
material.dispose();
});
return T2.of(
circle,
() => this._cleanups.forEach(cleanup => cleanup())
)
}
);
this._scene.add(model.threeObject3D);
this.setEntityModel(entityId, CircleComponent.ecsComponent.type().typeName(), model);
}
private onArcAdded(entityId: number, axes: Axes3D, radius: number, startAngle: number, extent: number) {
let model = Model.create(
() => {
let geometry = new THREE.CircleGeometry(radius, 32, toRadians(startAngle), toRadians(extent));
geometry.vertices.shift();
let material = new THREE.LineBasicMaterial({ color: 0x000000 });
let arc = new THREE.Line(geometry, material);
THREEObject3DSetAxes(arc, axes);
//
let cleanups: (()=>void)[] = [];
cleanups.push(() => {
geometry.dispose();
material.dispose();
});
return T2.of(
arc,
() => this._cleanups.forEach(cleanup => cleanup())
)
}
);
this._scene.add(model.threeObject3D);
this.setEntityModel(entityId, ArcComponent.ecsComponent.type().typeName(), model);
}
private onEntityDestroyed(entityId: number) {
let typeModelMap = this._entityIdModelMap[entityId];
typeModelMap.forEach((model, forComponentType) => {
this._scene.remove(model.threeObject3D);
model.decRef();
});
delete this._entityIdModelMap[entityId];
}
private entityModelExists(entityId: number, forComponentType: string): boolean {
let typeModelMap = this._entityIdModelMap[entityId];
if (!typeModelMap) {
return false;
}
return typeModelMap.has(forComponentType);
}
private setEntityModel(entityId: number, forComponentType: string, model: Model) {
let typeModelMap = this._entityIdModelMap[entityId];
if (!typeModelMap) {
typeModelMap = new Map();
this._entityIdModelMap[entityId] = typeModelMap;
}
typeModelMap.set(forComponentType, model);
}
private unsetEntityModel(entityId: number, forComponentType: string) {
let typeModelMap = this._entityIdModelMap[entityId];
if (!typeModelMap) {
return;
}
typeModelMap.delete(forComponentType);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.