Created
April 9, 2019 21:07
-
-
Save clinuxrulz/53b37177cd0e13a4b598a9af6986b15b to your computer and use it in GitHub Desktop.
ECS + Sodium + THREE.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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