@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
254 lines (199 loc) • 6.24 kB
JavaScript
import { BufferAttribute, PlaneBufferGeometry } from 'three';
import { assert } from "../../../../core/assert.js";
import Vector3 from "../../../../core/geom/Vector3.js";
import { Quad } from "./Quad.js";
function fillArray(arr, val) {
let i = 0;
const l = arr.length;
for (; i < l; i++) {
arr[i] = val;
}
}
const
vA = new Vector3(),
vB = new Vector3(),
vC = new Vector3(),
vD = new Vector3(),
vTailMid = new Vector3(),
vRelativePosition = new Vector3(),
vCross = new Vector3();
/*
Head quad has following structure
+ ---- A ---- C
| | Head | <- Unconnected side (Tip)
| | |
+ ---- B ---- D
*/
export class Ribbon {
/**
*
* @param {number} length
* @param {number} width
* @constructor
*/
constructor(length, width) {
assert.isNonNegativeInteger(length, "length");
assert.isNumber(width, "width");
const geometry = this.geometry = new PlaneBufferGeometry(length, width, length, 1);
const position = geometry.attributes.position;
const opacity = new Float32Array(position.count);
fillArray(opacity, 1);
geometry.setAttribute("opacity", new BufferAttribute(opacity, 1));
//make quads
this.quads = new Array(length);
let lastQuad = null;
for (let i = 0; i < length; i++) {
const quad = new Quad(geometry, i);
quad.previous = lastQuad;
if (lastQuad !== null) {
lastQuad.next = quad;
}
this.quads[i] = quad;
lastQuad = quad;
}
this.__tail = this.quads[0];
this.__head = this.quads[length - 1];
this.length = length;
//this.validate();
}
/**
*
* @returns {Quad}
*/
head() {
return this.__head;
}
/**
*
* @returns {Quad}
*/
tail() {
return this.__tail;
}
/**
*
* @param {Vector3} v3
*/
moveToPoint(v3) {
const position = this.geometry.attributes.position;
const array = position.array;
const l = position.count * 3;
for (let i = 0; i < l; i += 3) {
array[i] = v3.x;
array[i + 1] = v3.y;
array[i + 2] = v3.z;
}
}
/**
*
* @param {Vector3} position
* @param {Vector3} normal
* @param {number} width
*/
positionHead(position, normal, width) {
const head = this.head();
const halfWidth = width / 2;
//read previous positions of the tip edge
head.getVertexA(vA);
head.getVertexB(vB);
//compute new tip positions
vTailMid
.copy(vA)
.add(vB)
.multiplyScalar(0.5);
vRelativePosition.copy(position).sub(vTailMid);
vCross.copy(vRelativePosition)
.cross(normal);
if (vCross.isZero()) {
//use old positions as markers, since cross product didn't yield a perpendicular vector
vC.copy(vA).sub(vTailMid).normalize().multiplyScalar(halfWidth).add(position);
vD.copy(vB).sub(vTailMid).normalize().multiplyScalar(halfWidth).add(position);
} else {
vCross.normalize();
vC.copy(vCross).multiplyScalar(halfWidth).add(position);
vD.copy(vCross).negate().multiplyScalar(halfWidth).add(position);
}
head.setVertexC(vC.x, vC.y, vC.z);
head.setVertexD(vD.x, vD.y, vD.z);
}
/**
*
* @param {number} startValue
* @param {number} endValue
* @param {function} callback
*/
traverseLerpEdges(startValue, endValue, callback) {
const valueDelta = (endValue - startValue);
this.traverseEdges(function (a, b, index, maxIndex) {
const value = startValue + (index / maxIndex) * valueDelta;
callback(a, b, value);
});
}
/**
*
* @param {function(a:number, b:number, i:number, length:number)} callback
*/
traverseEdges(callback) {
const quads = this.quads;
let q = quads[0];
const numQuads = this.length;
const numEdges = numQuads + 1;
callback(q.getA(), q.getB(), 0, numEdges);
let i = 0;
for (let q = this.__head; q !== null; q = q.previous, i++) {
callback(q.getC(), q.getD(), i, numEdges);
}
}
validate() {
let i0, i1;
let q0 = this.quads[0];
for (let i = 1; i < this.quads.length; i++) {
const q1 = this.quads[i];
let q0a = q0.getA();
let q0b = q0.getB();
const q0c = q0.getC();
const q0d = q0.getD();
const q1a = q1.getA();
const q1b = q1.getB();
let q1c = q1.getC();
let q1d = q1.getD();
if (q0c !== q1a || q0d !== q1b) {
//segments are disconnected
throw new Error("segments are disconnected");
}
q0 = q1;
}
}
/**
* moves last segment of ribbon to become new head
*/
rotate() {
//take first quad
const quads = this.quads;
const length = quads.length;
//take current head
const tail = this.__tail;
const head = this.__head;
//patch tail to become new head
const a = tail.getA();
const b = tail.getB();
tail.setC(a);
tail.setD(b);
const c = head.getC();
const d = head.getD();
tail.setA(c);
tail.setB(d);
//rotate array
head.next = tail;
tail.previous = head;
//set new tail's end
const newTail = tail.next;
newTail.previous = null;
tail.next = null;
//update tail and head references
this.__tail = newTail;
this.__head = tail;
//this.validate();
return this;
}
}