UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

254 lines (199 loc) • 6.24 kB
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; } }