@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
266 lines (199 loc) • 7.31 kB
JavaScript
import { assert } from "../../../../core/assert.js";
import { BVH } from "../../../../core/bvh2/bvh3/BVH.js";
import Vector3 from '../../../../core/geom/Vector3.js';
import { clamp } from "../../../../core/math/clamp.js";
import { max2 } from "../../../../core/math/max2.js";
import { ResourceAccessKind } from "../../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../../core/model/ResourceAccessSpecification.js";
import { System } from '../../../ecs/System.js';
import { Transform } from '../../../ecs/transform/Transform.js';
import { Reference } from "../../../reference/v2/Reference.js";
import { GraphicsEngine } from "../../GraphicsEngine.js";
import { make_bvh_visibility_builder } from "../../render/make_bvh_visibility_builder.js";
import {
RIBBON_ATTRIBUTE_ADDRESS_AGE,
RIBBON_ATTRIBUTE_ADDRESS_UV_OFFSET
} from "../../trail/x/ribbon_attributes_spec.js";
import { RibbonX } from "../../trail/x/RibbonX.js";
import { RibbonXPlugin } from "../../trail/x/RibbonXPlugin.js";
import { RibbonXFixedPhysicsSimulator } from "../../trail/x/simulator/RibbonXFixedPhysicsSimulator.js";
import Trail2D from './Trail2D.js';
import { Trail2DFlags } from "./Trail2DFlags.js";
const v3Temp1 = new Vector3();
class Trail2DSystem extends System {
dependencies = [Trail2D, Transform];
components_used = [
ResourceAccessSpecification.from(Trail2D, ResourceAccessKind.Write)
];
/**
*
* @type {RibbonXFixedPhysicsSimulator}
*/
simulator = new RibbonXFixedPhysicsSimulator();
/**
*
* @type {Reference<RibbonXPlugin>}
* @private
*/
__ribbon_plugin = Reference.NULL;
/**
*
* @type {number}
* @private
*/
__timeDelta = 0;
/**
* @private
* @type {BVH}
*/
bvh = new BVH();
/**
* @param {Engine} engine
* @constructor
*/
constructor(engine) {
super();
assert.defined(engine, 'engine');
assert.notNull(engine, 'engine');
assert.equal(engine.isEngine, true, 'engine.isEngine !== true');
/**
*
* @type {Engine}
* @private
*/
this.__engine = engine;
/**
*
* @type {GraphicsEngine}
*/
this.graphics = engine.graphics;
/**
*
* @type {RenderLayer|null}
*/
this.renderLayer = null;
}
async startup(entityManager) {
this.entityManager = entityManager;
this.renderLayer = this.graphics.layers.create('trail-2d-system');
this.renderLayer.buildVisibleSet = make_bvh_visibility_builder(
this.entityManager, this.bvh,
(destination, offset, entity, ecd) => {
destination[offset] = ecd.getComponent(entity, Trail2D).mesh
return 1;
}
);
this.__ribbon_plugin = await this.__engine.plugins.acquire(RibbonXPlugin);
}
async shutdown(entityManager) {
this.graphics.layers.remove(this.renderLayer);
this.__ribbon_plugin.release();
}
/**
*
* @param {Trail2D} trail
* @param {Transform} transform
* @private
*/
__build_trail(trail, transform) {
const segmentsPerSecond = 60;
const maxSegments = 1024;
//instantiation
//make a mesh
const segment_count = Math.ceil(clamp(trail.maxAge * segmentsPerSecond, 2, maxSegments));
trail.build(segment_count);
const ribbon = trail.ribbon;
const ribbons = this.__ribbon_plugin.getValue();
const material = ribbons.obtain_material_x(trail.material);
trail.mesh.material = material;
trail.time = 0;
trail.trailingIndex = 0;
trail.ribbon = ribbon;
trail.timeSinceLastUpdate = 0;
const trail_offset = trail.offset;
const transform_position = transform.position;
const position_x = trail_offset.x + transform_position.x;
const position_y = trail_offset.y + transform_position.y;
const position_z = trail_offset.z + transform_position.z;
const color = trail.color;
//initialize segments
for (let i = 0; i < segment_count; i++) {
const f = i / (segment_count - 1);
ribbon.setPointColor(i, color.x * 255, color.y * 255, color.z * 255);
ribbon.setPointPosition(i, position_x, position_y, position_z);
ribbon.setPointThickness(i, trail.width);
ribbon.setPointAttribute_Scalar(i, RIBBON_ATTRIBUTE_ADDRESS_UV_OFFSET, f);
ribbon.setPointAlpha(i, color.w);
ribbon.setPointAttribute_Scalar(i, RIBBON_ATTRIBUTE_ADDRESS_AGE, trail.maxAge);
}
trail.bvh.resize(
position_x, position_y, position_z,
position_x, position_y, position_z
);
trail.setFlag(Trail2DFlags.Built);
}
/**
*
* @param {Transform} transform
* @param {Trail2D} trail
* @param {number} entityId
*/
link(trail, transform, entityId) {
if (!trail.getFlag(Trail2DFlags.Built)) {
this.__build_trail(trail, transform);
} else {
const material = this.__ribbon_plugin.getValue().obtain_material_x(trail.material);
trail.mesh.material = material;
}
trail.bvh.link(this.bvh, entityId);
}
/**
*
* @param {Trail2D} component
* @param {Transform} transform
* @param {number} entity
*/
unlink(component, transform, entity) {
component.bvh.unlink();
// release resources
component.dispose();
}
/**
*
* @param {Trail2D} trail
* @param {Transform} transform
*/
updateTrailEntity(trail, transform) {
const timeDelta = this.__timeDelta;
/**
*
* @type {RibbonX|null}
*/
const ribbon = trail.ribbon;
const newPosition = v3Temp1;
newPosition.copy(trail.offset);
newPosition.applyMatrix4(transform.matrix);
trail.timeSinceLastUpdate += timeDelta;
trail.time += timeDelta;
const ageOffset = max2(0, trail.maxAge - trail.time);
this.simulator.update(ribbon, trail.maxAge, timeDelta);
if (trail.getFlag(Trail2DFlags.Spawning)) {
trail.updateHead(newPosition.x, newPosition.y, newPosition.z, ageOffset);
}
if (trail.getFlag(Trail2DFlags.BoundsNeedUpdate)) {
const bvh = trail.bvh;
ribbon.computeBoundingBox(bvh.bounds);
bvh.write_bounds();
trail.clearFlag(Trail2DFlags.BoundsNeedUpdate);
}
}
update(timeDelta) {
const em = this.entityManager;
const dataset = em.dataset;
this.__timeDelta = timeDelta;
if (dataset !== null) {
dataset.traverseEntities([Trail2D, Transform], this.updateTrailEntity, this);
}
}
}
export default Trail2DSystem;