UNPKG

@awayjs/graphics

Version:
509 lines (390 loc) 11.6 kB
import { Rectangle, Vector3D, } from '@awayjs/core'; import { AttributesView } from '@awayjs/stage'; type TGenCallback = (source: PolygonView<any>, result: PolygonView, back: boolean) => void; export class Vertice3DView { private _data: Float32Array[] = []; public get length() { return this._data.length; } public toVec(vect: Vector3D = new Vector3D(), index = 0): Vector3D { const s = this._data[index]; const l = s.length; for (let i = 0; i < 4; i++) { vect._rawData[i] = i < l ? s[i] : 0; } return vect; } public fromVec(vect: Vector3D, index = 0): this { const s = this._data[index]; const l = s.length; for (let i = 0; i < l; i++) { s[i] = vect._rawData[i]; } return this; } public setData(data: Float32Array, index: number, clone = false): this { this._data[index] = clone ? data.slice() : data; return this; } public getData(index: number): Float32Array { return this._data[index]; } public lerpTo (target: Vertice3DView, alpha: number): this { if (alpha === 0) { return this; } if (alpha === 1) { return this.copyFrom(target); } // interpolate all values for (let i = 0; i < this._data.length; i++) { const to = target._data[i]; const from = this._data[i]; for (let k = 0; k < this._data[i].length; k++) { from[k] = (1 - alpha) * from[k] + alpha * to[k]; } } return this; } public add (source: Vertice3DView, scale: number = 1): this { // interpolate all values for (let i = 0; i < this._data.length; i++) { const from = source._data[i]; const to = this._data[i]; for (let k = 0; k < this._data[i].length; k++) { to[k] += from[k] * scale; } } return this; } public scale (s: number): this { // interpolate all values for (let i = 0; i < this._data.length; i++) { const to = this._data[i]; for (let k = 0; k < this._data[i].length; k++) { to[k] *= s; } } return this; } public copyFrom (from: Vertice3DView): this { const to = this._data; for (let i = 0; i < from.length; i++) { if (to[i]) { to[i].set(from._data[i]); } else { to[i] = from._data[i].slice(); } } return this; } public clone(): Vertice3DView { return new Vertice3DView().copyFrom(this); } } export class PolygonView<T extends {clone?(): T} = any> { public vertices: Array<Vertice3DView> = []; public userData: T; private _middle: Vertice3DView; get middle(): Vertice3DView { if (this._middle) return this._middle; this._middle = this.vertices[0].clone(); for (let i = 1; i < this.vertices.length; i++) { this._middle.add(this.vertices[i]); } this._middle.scale(1 / this.vertices.length); return this._middle; } constructor(points?: Vertice3DView[], userData?: T) { if (points) { this.vertices = points.map((e)=> e.clone()); } if (userData) { this.userData = userData; } } public clone(): PolygonView<T> { const p = new PolygonView<T>(); const l = this.vertices.length; for (let i = 0; i < l; i++) { p.vertices[i] = this.vertices[i].clone(); } if (this.userData) { p.userData = this.userData.clone ? this.userData.clone() : Object.assign({}, this.userData); } return p; } public add(view: Vertice3DView, clone: boolean = false): this { this.vertices.push(clone ? view.clone() : view); return this; } public get length() { return this.vertices.length; } } export class MeshView<T extends {} = never> { public poly: PolygonView<T>[] = []; public rect: Rectangle = new Rectangle(); public polySize: number = 3; public hasNGones: boolean = false; push (p: PolygonView<T>): this { this.poly.push(p); return this; } /** * Unroll all n-gone convex polys to 3-gone polygons */ public normalise(): this { if (!this.hasNGones) { return; } const normalised = []; for (const p of this.poly) { // invalid vertices if (p.length < this.polySize) { continue; } if (p.length === this.polySize) { normalised.push(p); continue; } for (let i = 1; i < p.length - 1; i++) { const n = new PolygonView(); // shared user data; n.userData = p.userData; // construct poly in direct order with shared 1 vert (CCW or CW? i don't know ;) // fan triangulation // we sure that this a convex polygon n.add(p.vertices[0].clone()); n.add(p.vertices[(i) % p.length].clone()); n.add(p.vertices[(i + 1) % p.length].clone()); normalised.push(n); } } this.poly = normalised; return this; } /** * Construct buffer from poly data * @param index Index of vertices attribute 0 - pos, 1 - uv etc */ public toFloatArray(index: number = 0): Float32Array { const count = this.poly.length; if (this.poly.length === 0) return null; // check a dimension for calc a output size const test = this.poly[0].vertices[0].getData(index); const dim = test.length; // polygon should has 3 vertices const data = new Float32Array(count * dim * 3); for (let i = 0; i < count * 3; i++) { const poly = this.poly[i / 3 | 0]; const vert = poly.vertices[i % 3]; data.set(vert.getData(index), i * dim); } return data; } public static fromAttributes<T> ( attrs: AttributesView[], length: number, polySize: number = 0, userDataCtor?: { new(): T}): MeshView<T> { const mesh = new MeshView<T>(); mesh.polySize = polySize; let poly: PolygonView<T>; const views: Float32Array[] = <any>attrs.map((e) => e.get(length, 0)); for (let i = 0; i < length; i++) { if (i % polySize === 0) { poly = new PolygonView<T>(); poly.userData = userDataCtor ? new userDataCtor() : null; } mesh.poly.push(poly); const v = new Vertice3DView(); poly.add(v); for (let k = 0; k < attrs.length; k++) { const o = attrs[k].offset; const s = attrs[k].stride; const d = attrs[k].dimensions; v.setData(views[k].slice(o + s * i, o + s * i + d), k); } } return mesh; } } export class GeneratorUtils { private static EPS = 0.0001; private static InFront (n: Vector3D, d: number, p: Vector3D): boolean { return n.dotProduct(p) - d > this.EPS; } private static Behind (n: Vector3D, d: number, p: Vector3D): boolean { return n.dotProduct(p) - d < -this.EPS; } private static OnPlane (n: Vector3D, d: number, p: Vector3D): boolean { return !this.InFront(n, d, p) && !this.Behind(n, d, p); } private static Intersect(a: Vertice3DView ,b: Vertice3DView ,d1: number , d2: number): Vertice3DView { const alpha = d1 / (d1 - d2); return new Vertice3DView().copyFrom(a).lerpTo(b, alpha); } private static SliceNaive<T> ( out: MeshView<T>, a: Vertice3DView, b: Vertice3DView, c: Vertice3DView, d1: number, d2: number, d3: number): MeshView<T> { // Calculate the intersection point from a to b const ab = this.Intersect(a, b, d1, d2); if (d1 < 0.0) { // b to c crosses the clipping plane if (d3 < 0.0) { const bc = this.Intersect(b, c, d2, d3); out .push(new PolygonView<T>([b, bc, ab])) .push(new PolygonView<T>([bc, c, a])) .push(new PolygonView<T>([ab, bc, a])); // c to a crosses the clipping plane } else { const ac = this.Intersect(a, c, d1, d3); out .push(new PolygonView<T>([a, ab, ac])) .push(new PolygonView<T>([ab, b, c])) .push(new PolygonView<T>([ac, ab, c])); } } else { // c to a crosses the clipping plane if (d3 < 0.0) { const ac = this.Intersect(a, c, d1, d3); out .push(new PolygonView<T>([a, ab, ac])) .push(new PolygonView<T>([ac, ab, b])) .push(new PolygonView<T>([b, c, ac])); // b to c crosses the clipping plane } else { const bc = this.Intersect(b, c, d2, d3); out .push(new PolygonView<T>([b, bc, ab])) .push(new PolygonView<T>([a, ab, bc])) .push(new PolygonView<T>([c, a, bc])); } } return out; } // Slices all triangles given a vector of triangles. // A new output triangle list is generated. The old // list of triangles is discarded. // n - The normal of the clipping plane // d - Distance of clipping plane from the origin // Reference: Exact Bouyancy for Polyhedra by // Erin Catto in Game Programming Gems 6 public static SliceAllNaive <T>(mesh: MeshView<T>, n: Vector3D, d: number): MeshView<T> { const out = new MeshView<any>(); out.polySize = mesh.polySize; const tmp = new Vector3D(); for (let i = 0; i < mesh.poly.length; ++i) { // Grab a triangle from the global triangle list const tri = mesh.poly[i]; const a = tri.vertices[0]; const b = tri.vertices[1]; const c = tri.vertices[2]; a.toVec(tmp,0); // Compute distance of each triangle vertex to the clipping plane const d1 = a.toVec(tmp).dotProduct(n) - d; const d2 = b.toVec(tmp).dotProduct(n) - d; const d3 = c.toVec(tmp).dotProduct(n) - d; // a to b crosses the clipping plane if (d1 * d2 < 0.0) { this.SliceNaive(out, a, b, c, d1, d2, d3); // a to c crosses the clipping plane } else if (d1 * d3 < 0.0) { this.SliceNaive(out, c, a, b, d3, d1, d2); // b to c crosses the clipping plane } else if (d2 * d3 < 0.0) { this.SliceNaive(out, b, c, a, d2, d3, d1); } else { out.push(tri); } } return out; } /** * It gen corrupted results, need improve, eXponenta * * Splits a polygon in half along a splitting plane using a clipping algorithm * call Sutherland-Hodgman clipping * Resource: Page 367 of Ericson (Real-Time Collision Detection) */ public static SutherlandHodgman<T> ( poly: PolygonView<T>, normal: Vector3D, d: number, out: MeshView<T>, callback?: TGenCallback ): MeshView<T> { const frontPoly = new PolygonView<T>(); const backPoly = new PolygonView<T>(); const s = poly.length; const verts = poly.vertices; const tmpA = new Vector3D(); const tmpB = new Vector3D(); if (d < 0) { d = -d; normal.scaleBy(-1); } let vA = verts[s - 1]; let vecA = vA.toVec(tmpA, 0); let da = normal.dotProduct(vecA) - d; for (let i = 0; i < s; ++i) { const vB = verts[i]; const vecB = vB.toVec(tmpB, 0); const db = normal.dotProduct(vecB) - d; if (this.InFront(normal, d, vecB)) { if (this.Behind(normal, d, vecA)) { const itr = this.Intersect(vB, vA, db, da); frontPoly.add(itr); backPoly.add(itr, true); } frontPoly.add(vB, true); } else if (this.Behind(normal, d, vecB)) { if (this.InFront(normal, d, vecA)) { const itr = this.Intersect(vA, vB, da, db); frontPoly.add(itr, false); backPoly.add(itr, true); } else if (this.OnPlane(normal, d, vecA)) { backPoly.add(vA, true); } backPoly.add(vB, true); } else { frontPoly.add(vB, true); if (this.OnPlane(normal, d, vecA)) { backPoly.add(vB, true); } } vA = vB.clone(); vecA = vA.toVec(vecA, 0); da = db; } if (frontPoly.length) { //if (frontPoly.length < 3) debugger; callback && callback(poly, frontPoly, false); if (out.polySize !== frontPoly.length) { out.hasNGones = true; } out.poly.push(frontPoly); } if (backPoly.length) { //if (backPoly.length < 3) debugger; callback && callback(poly, backPoly, true); if (out.polySize !== backPoly.length) { out.hasNGones = true; } out.poly.push(backPoly); } return out; } public static SliceHodgman<T>(mesh: MeshView<T>, n: Vector3D, d: number, callback?: TGenCallback): MeshView<T> { const out = new MeshView<T>(); out.polySize = mesh.polySize; for (const p of mesh.poly) { this.SutherlandHodgman(p, n, d, out, callback); } return out; } }