@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
293 lines (222 loc) • 8.73 kB
JavaScript
import { GUI } from "dat.gui";
import { MeshBasicMaterial, MeshStandardMaterial, OctahedronBufferGeometry } from "three";
import { BinaryDataType } from "../../../core/binary/type/BinaryDataType.js";
import { Color } from "../../../core/color/Color.js";
import { max2 } from "../../../core/math/max2.js";
import { min2 } from "../../../core/math/min2.js";
import { randomFloatBetween } from "../../../core/math/random/randomFloatBetween.js";
import { seededRandom } from "../../../core/math/random/seededRandom.js";
import Clock from "../../Clock.js";
import Entity from "../../ecs/Entity.js";
import { Transform } from "../../ecs/transform/Transform.js";
import { EngineHarness } from "../../EngineHarness.js";
import { ShadedGeometry } from "../../graphics/ecs/mesh-v2/ShadedGeometry.js";
import { ShadedGeometrySystem } from "../../graphics/ecs/mesh-v2/ShadedGeometrySystem.js";
import Trail2D from "../../graphics/ecs/trail2d/Trail2D.js";
import Trail2DSystem from "../../graphics/ecs/trail2d/Trail2DSystem.js";
import {
RGBA_LUT_HEATMAP_IR
} from "../../graphics/particles/particular/engine/parameter/sample/RGBA_LUT_HEATMAP_IR.js";
import { SequenceBehavior } from "../../intelligence/behavior/composite/SequenceBehavior.js";
import { RepeatBehavior } from "../../intelligence/behavior/decorator/RepeatBehavior.js";
import { BehaviorComponent } from "../../intelligence/behavior/ecs/BehaviorComponent.js";
import { BehaviorSystem } from "../../intelligence/behavior/ecs/BehaviorSystem.js";
import { ActionBehavior } from "../../intelligence/behavior/primitive/ActionBehavior.js";
import { RandomDelayBehavior } from "../../intelligence/behavior/util/RandomDelayBehavior.js";
import { TimeSeries } from "./TimeSeries.js";
const harness = new EngineHarness();
/**
*
* @param {EntityComponentDataset} ecd
* @param {TimeSeries} table
* @param {number} [count]
*/
function makeGhostMarkers({
ecd,
table,
count = 5
}) {
const g = new OctahedronBufferGeometry(1, 3);
/**
*
* @type {Entity[]}
*/
const entities = [];
for (let i = 0; i < count; i++) {
const color = new Color();
const sample = [];
RGBA_LUT_HEATMAP_IR.sample(i / (count - 1), sample);
color.setRGBUint8(...sample);
const transform = new Transform();
const entity = new Entity();
entities.push(entity);
entity
.add(transform)
.add(ShadedGeometry.from(g, new MeshBasicMaterial({
wireframe: true,
color: color.toUint()
})))
.build(ecd);
}
return {
set time(time) {
const sample = table.sampleObjectLinear(time);
const ref_index = table.findLowSampleIndexByTime(time);
const start_index = max2(0, ref_index - Math.floor(count / 2));
const limit_index = min2(table.sample_count - 1, ref_index + Math.ceil(count / 2));
for (let i = 0; i < count; i++) {
const entity = entities[i];
const sample_index = ref_index - Math.floor(count / 2) + i;
const out_of_bound_marker = sample_index < start_index || sample_index > limit_index;
if (out_of_bound_marker) {
if (entity.isBuilt) {
entity.destroy();
}
} else {
if (!entity.isBuilt) {
entity.build(ecd);
}
}
if (out_of_bound_marker) {
continue;
}
const transform = entity.getComponentSafe(Transform);
const sample = table.getSampleObjectByIndex(sample_index);
transform.position.set(
sample.x,
sample.y,
sample.z
);
}
}
};
}
/**
*
* @param {Engine} engine
* @return {Promise<void>}
*/
async function main(engine) {
await EngineHarness.buildBasics({
engine
});
const timeSeries = new TimeSeries({
time: BinaryDataType.Float64,
x: BinaryDataType.Float32,
y: BinaryDataType.Float32,
z: BinaryDataType.Float32,
}, 'time');
const random = seededRandom(42);
timeSeries.addSample([
0, 0, 1, 0
]);
const sim_clock = new Clock();
sim_clock.start();
function addSample() {
const time_delta = sim_clock.getDelta();
// get last row
const last_record = timeSeries.getSampleObjectByIndex(timeSeries.sample_count - 1);
const speed = 7;
const displacement = speed * time_delta;
const direction = randomFloatBetween(random, -Math.PI, Math.PI);
const displacement_x = displacement * Math.cos(direction);
const displacement_y = displacement * Math.sin(direction);
const new_x = last_record.x + displacement_x;
const new_z = last_record.z + displacement_y;
timeSeries.addSample([
last_record.time + time_delta,
new_x,
1,
new_z,
]);
}
const ecd = engine.entityManager.dataset;
// simulate data arriving from the network
const sample_delay = RandomDelayBehavior.from(0.5, 1);
new Entity()
.add(BehaviorComponent.from(RepeatBehavior.from(
SequenceBehavior.from([
sample_delay,
new ActionBehavior(addSample)
])
)))
.build(ecd);
const sim_parameters = {
playing: true,
play_time: -1,
get latest_data_time() {
return timeSeries.last_timestamp;
},
get behind_latest() {
return timeSeries.last_timestamp - this.play_time;
},
set behind_latest(v) {
const current_delay = timeSeries.last_timestamp - this.play_time;
const delta = v - current_delay;
this.play_time += delta;
}
};
// simulate agent moving through space
const subject = new Entity();
subject
.add(Transform.fromJSON({
position: 0
}))
.add(Trail2D.fromJSON({
textureURL: "data/textures/trail/Circle_04.png",
width: 0.2
}))
.add(ShadedGeometry.from(new OctahedronBufferGeometry(1, 5), new MeshStandardMaterial()))
.add(BehaviorComponent.from(RepeatBehavior.from(
new ActionBehavior((delta) => {
if (sim_parameters.playing) {
sim_parameters.play_time += delta;
}
const sample = timeSeries.sampleObjectLinear(sim_parameters.play_time);
const transform = subject.getComponentSafe(Transform);
transform.position.set(
sample.x,
sample.y,
sample.z,
);
})
)))
.build(ecd);
const markers = makeGhostMarkers({
ecd, table: timeSeries, count: 5
});
engine.ticker.onTick.add(() => markers.time = sim_parameters.play_time);
const gui = new GUI();
const gui_samples = gui.addFolder("samples");
gui_samples.add(sample_delay.limits, 'min').name("min_delay").step(0.01)
gui_samples.add(sample_delay.limits, 'max').name("max_delay").step(0.01)
for (const k in sim_parameters) {
const control = gui.add(sim_parameters, k);
if (typeof sim_parameters[k] === "number") {
control.step(0.01);
}
let last_value = sim_parameters[k];
engine.ticker.onTick.add(() => {
let should_update = true;
const current_value = sim_parameters[k];
if (last_value === current_value) {
should_update = false;
} else {
last_value = current_value;
}
if (should_update) {
control.updateDisplay();
}
})
}
gui.open();
}
harness.initialize({
configuration(config, engine) {
config.addManySystems(
new BehaviorSystem(engine),
new ShadedGeometrySystem(engine),
new Trail2DSystem(engine)
)
}
}).then(main);