@wonderlandengine/components
Version:
Wonderland Engine's official component library.
175 lines • 7.03 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Component, Mesh, MeshIndexType, MeshAttribute, } from '@wonderlandengine/api';
import { property } from '@wonderlandengine/api/decorators.js';
import { vec3 } from 'gl-matrix';
const direction = vec3.create();
const offset = vec3.create();
const normal = vec3.create();
const UP = vec3.fromValues(0, 1, 0);
/**
* Dynamic mesh-based trail
*
* This component keeps track of the world position of the object it's added to.
* At a fixed interval the world position is stored as start and end points of the trail segments.
*
* The trail tapers off along its length. UV texture coordinates are setup such that the
* U-axis covers the width of the trail and the V-axis covers the length of the trail.
* This allows the trail's appearance to be defined using a texture.
*/
class Trail extends Component {
static TypeName = 'trail';
/** The material to apply to the trail mesh */
material = null;
/** The number of segments in the trail mesh */
segments = 50;
/** The time interval before recording a new point */
interval = 0.1;
/** The width of the trail (in world space) */
width = 1.0;
/** Whether or not the trail should taper off */
taper = true;
/**
* The maximum delta time in seconds, above which the trail resets.
* This prevents the trail from jumping around when updates happen
* infrequently (e.g. when the tab doesn't have focus).
*/
resetThreshold = 0.5;
_currentPointIndex = 0;
_timeTillNext = 0;
_points = [];
_trailContainer = null;
_meshComp = null;
_mesh = null;
_indexData = null;
start() {
this._points = new Array(this.segments + 1);
for (let i = 0; i < this._points.length; ++i) {
this._points[i] = vec3.create();
}
/* The points array is circular, so keep track of its head */
this._timeTillNext = this.interval;
this._trailContainer = this.engine.scene.addObject();
/* Workaround for new objects sometimes not being set dirty before 1.4.6 */
this._trailContainer.setDirty();
this._meshComp = this._trailContainer.addComponent('mesh');
this._meshComp.material = this.material;
/* Each point will have two vertices; one on either side */
const vertexCount = 2 * this._points.length;
/* Each segment consists of two triangles */
this._indexData = new Uint32Array(6 * this.segments);
for (let i = 0, v = 0; i < vertexCount - 2; i += 2, v += 6) {
this._indexData
.subarray(v, v + 6)
.set([i + 1, i + 0, i + 2, i + 2, i + 3, i + 1]);
}
this._mesh = new Mesh(this.engine, {
vertexCount: vertexCount,
indexData: this._indexData,
indexType: MeshIndexType.UnsignedInt,
});
this._meshComp.mesh = this._mesh;
}
updateVertices() {
if (!this._mesh)
return;
const positions = this._mesh.attribute(MeshAttribute.Position);
const texCoords = this._mesh.attribute(MeshAttribute.TextureCoordinate);
const normals = this._mesh.attribute(MeshAttribute.Normal);
vec3.set(direction, 0, 0, 0);
for (let i = 0; i < this._points.length; ++i) {
const curr = this._points[(this._currentPointIndex + i + 1) % this._points.length];
const next = this._points[(this._currentPointIndex + i + 2) % this._points.length];
/* The last point has no next, so re-use the direction of the previous segment */
if (i !== this._points.length - 1) {
vec3.sub(direction, next, curr);
}
vec3.cross(offset, UP, direction);
vec3.normalize(offset, offset);
const timeFraction = 1.0 - this._timeTillNext / this.interval;
const fraction = (i - timeFraction) / this.segments;
vec3.scale(offset, offset, ((this.taper ? fraction : 1.0) * this.width) / 2.0);
positions.set(i * 2, [
curr[0] - offset[0],
curr[1] - offset[1],
curr[2] - offset[2],
]);
positions.set(i * 2 + 1, [
curr[0] + offset[0],
curr[1] + offset[1],
curr[2] + offset[2],
]);
if (normals) {
vec3.cross(normal, direction, offset);
vec3.normalize(normal, normal);
normals.set(i * 2, normal);
normals.set(i * 2 + 1, normal);
}
if (texCoords) {
texCoords.set(i * 2, [0, fraction]);
texCoords.set(i * 2 + 1, [1, fraction]);
}
}
/* Notify WLE that the mesh has changed */
this._mesh.update();
}
resetTrail() {
this.object.getPositionWorld(this._points[0]);
for (let i = 1; i < this._points.length; ++i) {
vec3.copy(this._points[i], this._points[0]);
}
this._currentPointIndex = 0;
this._timeTillNext = this.interval;
}
update(dt) {
this._timeTillNext -= dt;
if (dt > this.resetThreshold) {
this.resetTrail();
}
if (this._timeTillNext < 0) {
this._currentPointIndex = (this._currentPointIndex + 1) % this._points.length;
this._timeTillNext = (this._timeTillNext % this.interval) + this.interval;
}
this.object.getPositionWorld(this._points[this._currentPointIndex]);
this.updateVertices();
}
onActivate() {
this.resetTrail();
if (this._meshComp)
this._meshComp.active = true;
}
onDeactivate() {
if (this._meshComp)
this._meshComp.active = false;
}
onDestroy() {
if (this._trailContainer)
this._trailContainer.destroy();
if (this._mesh)
this._mesh.destroy();
}
}
__decorate([
property.material()
], Trail.prototype, "material", void 0);
__decorate([
property.int(50)
], Trail.prototype, "segments", void 0);
__decorate([
property.float(0.1)
], Trail.prototype, "interval", void 0);
__decorate([
property.float(1.0)
], Trail.prototype, "width", void 0);
__decorate([
property.bool(true)
], Trail.prototype, "taper", void 0);
__decorate([
property.float(1.0)
], Trail.prototype, "resetThreshold", void 0);
export { Trail };
//# sourceMappingURL=trail.js.map