UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

293 lines (222 loc) • 8.73 kB
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);