UNPKG

@awayjs/renderer

Version:
880 lines (709 loc) 22.3 kB
import { Matrix3D, Vector3D, Box, Sphere, Rectangle, Matrix } from '@awayjs/core'; import { AttributesView, Short2Attributes } from '@awayjs/stage'; import { TriangleElements } from '../elements/TriangleElements'; import { GeneratorUtils, MeshView } from './GeneratorUtils'; import { HitTestCache } from './HitTestCache'; export class TriangleElementsUtils { //TODO - generate this dyanamically based on num tris public static hitTest( x: number, y: number, _z: number, box: Box, triangleElements: TriangleElements, count: number, offset: number = 0, ): boolean { const positionAttributes: AttributesView = triangleElements.positions; const curveAttributes: AttributesView = triangleElements.getCustomAtributes('curves'); const posStride: number = positionAttributes.stride; const curveStride: number = curveAttributes ? curveAttributes.stride : null; let positions: ArrayBufferView = positionAttributes.get(count, offset); let curves: ArrayBufferView = curveAttributes ? curveAttributes.get(count, offset) : null; let indices: Uint16Array; let len: number; if (triangleElements.indices) { indices = triangleElements.indices.get(count, offset); positions = positionAttributes.get(positionAttributes.count); curves = curveAttributes ? curveAttributes.get(curveAttributes.count) : null; len = count * triangleElements.indices.dimensions; } else { positions = positionAttributes.get(count, offset); curves = curveAttributes ? curveAttributes.get(count, offset) : null; len = count; } let id0: number; let id1: number; let id2: number; let ax: number; let ay: number; let bx: number; let by: number; let cx: number; let cy: number; const hitTestCache: HitTestCache = triangleElements.hitTestCache[offset] || (triangleElements.hitTestCache[offset] = new HitTestCache()); const index: number = hitTestCache.lastCollisionIndex; if (index != -1 && index < len) { precheck: { if (indices) { id0 = indices[index + 2]; id1 = indices[index + 1]; id2 = indices[index]; } else { id0 = index + 2; id1 = index + 1; id2 = index; } ax = positions[id0 * posStride]; ay = positions[id0 * posStride + 1]; bx = positions[id1 * posStride]; by = positions[id1 * posStride + 1]; cx = positions[id2 * posStride]; cy = positions[id2 * posStride + 1]; //from a to p let dx = ax - x; let dy = ay - y; //edge normal (a-b) let nx = by - ay; let ny = -(bx - ax); let dot = dx * nx + dy * ny; if (dot > 0) break precheck; dx = bx - x; dy = by - y; nx = cy - by; ny = -(cx - bx); dot = dx * nx + dy * ny; if (dot > 0) break precheck; dx = cx - x; dy = cy - y; nx = ay - cy; ny = -(ax - cx); dot = dx * nx + dy * ny; if (dot > 0) break precheck; if (curves) { //check if not solid if (curves[id0 * curveStride + 2] != -128) { const v0x = bx - ax; const v0y = by - ay; const v1x = cx - ax; const v1y = cy - ay; const v2x = x - ax; const v2y = y - ay; const den = v0x * v1y - v1x * v0y; const v = (v2x * v1y - v1x * v2y) / den; const w = (v0x * v2y - v2x * v0y) / den; //var u:number = 1 - v - w; //commented out as inlined away //here be dragons const uu = 0.5 * v + w; const vv = w; const d = uu * uu - vv; const az = curves[id0 * curveStride]; if (d > 0 && az == -128) { break precheck; } else if (d < 0 && az == 127) { break precheck; } } } return true; } } //hard coded min vertex count to bother using a grid for if (len > 150) { const cells: Array<Array<number>> = hitTestCache.cells; const divisions: number = cells.length ? hitTestCache.divisions : (hitTestCache.divisions = Math.min(Math.ceil(Math.sqrt(len)), 32)); const conversionX: number = divisions / box.width; const conversionY: number = divisions / box.height; const minx: number = box.x; const miny: number = box.y; if (!cells.length) { //build grid //now we have bounds start creating grid cells and filling cells.length = divisions * divisions; for (let k = 0; k < len; k += 3) { if (indices) { id0 = indices[k + 2]; id1 = indices[k + 1]; id2 = indices[k]; } else { id0 = k + 2; id1 = k + 1; id2 = k; } ax = positions[id0 * posStride]; ay = positions[id0 * posStride + 1]; bx = positions[id1 * posStride]; by = positions[id1 * posStride + 1]; cx = positions[id2 * posStride]; cy = positions[id2 * posStride + 1]; //subtractions to push into positive space const min_index_x: number = Math.floor((Math.min(ax, bx, cx) - minx) * conversionX); const min_index_y: number = Math.floor((Math.min(ay, by, cy) - miny) * conversionY); const max_index_x: number = Math.floor((Math.max(ax, bx, cx) - minx) * conversionX); const max_index_y: number = Math.floor((Math.max(ay, by, cy) - miny) * conversionY); for (let i: number = min_index_x; i <= max_index_x; i++) { for (let j: number = min_index_y; j <= max_index_y; j++) { const c: number = i + j * divisions; const nodes: Array<number> = cells[c] || (cells[c] = new Array<number>()); //push in the triangle ids nodes.push(k); } } } } const index_x: number = Math.floor((x - minx) * conversionX); const index_y: number = Math.floor((y - miny) * conversionY); const nodes: Array<number> = cells[index_x + index_y * divisions]; if (nodes == null) { hitTestCache.lastCollisionIndex = -1; return false; } const nodeCount = nodes.length; for (let n = 0; n < nodeCount; n++) { const k = nodes[n]; if (indices) { id2 = indices[k]; } else { id2 = k; } if (id2 == index) continue; if (indices) { id0 = indices[k + 2]; id1 = indices[k + 1]; } else { id0 = k + 2; id1 = k + 1; } ax = positions[id0 * posStride]; ay = positions[id0 * posStride + 1]; bx = positions[id1 * posStride]; by = positions[id1 * posStride + 1]; cx = positions[id2 * posStride]; cy = positions[id2 * posStride + 1]; //from a to p let dx = ax - x; let dy = ay - y; //edge normal (a-b) let nx = by - ay; let ny = -(bx - ax); let dot = dx * nx + dy * ny; if (dot > 0) continue; dx = bx - x; dy = by - y; nx = cy - by; ny = -(cx - bx); dot = dx * nx + dy * ny; if (dot > 0) continue; dx = cx - x; dy = cy - y; nx = ay - cy; ny = -(ax - cx); dot = dx * nx + dy * ny; if (dot > 0) continue; if (curves) { //check if not solid if (curves[id0 * curveStride + 2] != -128) { const v0x = bx - ax; const v0y = by - ay; const v1x = cx - ax; const v1y = cy - ay; const v2x = x - ax; const v2y = y - ay; const den = v0x * v1y - v1x * v0y; const v = (v2x * v1y - v1x * v2y) / den; const w = (v0x * v2y - v2x * v0y) / den; //var u:number = 1 - v - w; //commented out as inlined away //here be dragons const uu = 0.5 * v + w; const vv = w; const d = uu * uu - vv; const az = curves[id0 * curveStride]; if (d > 0 && az == -128) { continue; } else if (d < 0 && az == 127) { continue; } } } hitTestCache.lastCollisionIndex = k; return true; } hitTestCache.lastCollisionIndex = -1; return false; } //brute force for (let k = 0; k < len; k += 3) { if (indices) { id2 = indices[k]; } else { id2 = k; } if (id2 == index) continue; if (indices) { id0 = indices[k + 2]; id1 = indices[k + 1]; } else { id0 = k + 2; id1 = k + 1; } ax = positions[id0 * posStride]; ay = positions[id0 * posStride + 1]; bx = positions[id1 * posStride]; by = positions[id1 * posStride + 1]; cx = positions[id2 * posStride]; cy = positions[id2 * posStride + 1]; //from a to p let dx = ax - x; let dy = ay - y; //edge normal (a-b) let nx = by - ay; let ny = -(bx - ax); let dot = dx * nx + dy * ny; if (dot > 0) continue; dx = bx - x; dy = by - y; nx = cy - by; ny = -(cx - bx); dot = dx * nx + dy * ny; if (dot > 0) continue; dx = cx - x; dy = cy - y; nx = ay - cy; ny = -(ax - cx); dot = dx * nx + dy * ny; if (dot > 0) continue; if (curves) { //check if not solid if (curves[id0 * curveStride + 2] != -128) { const v0x = bx - ax; const v0y = by - ay; const v1x = cx - ax; const v1y = cy - ay; const v2x = x - ax; const v2y = y - ay; const den = v0x * v1y - v1x * v0y; const v = (v2x * v1y - v1x * v2y) / den; const w = (v0x * v2y - v2x * v0y) / den; //var u:number = 1 - v - w; //commented out as inlined away //here be dragons const uu = 0.5 * v + w; const vv = w; const d = uu * uu - vv; const az = curves[id0 * curveStride]; if (d > 0 && az == -128) { continue; } else if (d < 0 && az == 127) { continue; } } } hitTestCache.lastCollisionIndex = k; return true; } hitTestCache.lastCollisionIndex = -1; return false; } public static getBoxBounds( positionAttributes: AttributesView, indexAttributes: Short2Attributes, matrix3D: Matrix3D, cache: Box, target: Box, count: number, offset: number = 0, ): Box { let positions: ArrayBufferView; const posDim: number = positionAttributes.dimensions; const posStride: number = positionAttributes.stride; let minX: number = 0, minY: number = 0, minZ: number = 0; let maxX: number = 0, maxY: number = 0, maxZ: number = 0; let indices: Uint16Array; let len: number; if (indexAttributes) { len = count * indexAttributes.dimensions; indices = indexAttributes.get(count, offset); positions = positionAttributes.get(positionAttributes.count); } else { len = count; positions = positionAttributes.get(count, offset); } if (len == 0) return target; let i: number = 0; let index: number; let pos1: number, pos2: number, pos3: number, rawData: Float32Array; if (matrix3D) rawData = matrix3D._rawData; if (target == null) { target = cache || new Box(); index = indices ? indices[i] * posStride : i * posStride; if (matrix3D) { if (posDim == 3) { pos1 = positions[index] * rawData[0] + positions[index + 1] * rawData[4] + positions[index + 2] * rawData[8] + rawData[12]; pos2 = positions[index] * rawData[1] + positions[index + 1] * rawData[5] + positions[index + 2] * rawData[9] + rawData[13]; pos3 = positions[index] * rawData[2] + positions[index + 1] * rawData[6] + positions[index + 2] * rawData[10] + rawData[14]; } else { pos1 = positions[index] * rawData[0] + positions[index + 1] * rawData[4] + rawData[12]; pos2 = positions[index] * rawData[1] + positions[index + 1] * rawData[5] + rawData[13]; } } else { pos1 = positions[index]; pos2 = positions[index + 1]; pos3 = posDim == 3 ? positions[index + 2] : 0; } maxX = minX = pos1; maxY = minY = pos2; maxZ = minZ = posDim == 3 ? pos3 : 0; i++; } else { maxX = (minX = target.x) + target.width; maxY = (minY = target.y) + target.height; maxZ = (minZ = target.z) + target.depth; } for (; i < len; i++) { index = indices ? indices[i] * posStride : i * posStride; if (matrix3D) { if (posDim == 3) { pos1 = positions[index] * rawData[0] + positions[index + 1] * rawData[4] + positions[index + 2] * rawData[8] + rawData[12]; pos2 = positions[index] * rawData[1] + positions[index + 1] * rawData[5] + positions[index + 2] * rawData[9] + rawData[13]; pos3 = positions[index] * rawData[2] + positions[index + 1] * rawData[6] + positions[index + 2] * rawData[10] + rawData[14]; } else { pos1 = positions[index] * rawData[0] + positions[index + 1] * rawData[4] + rawData[12]; pos2 = positions[index] * rawData[1] + positions[index + 1] * rawData[5] + rawData[13]; } } else { pos1 = positions[index]; pos2 = positions[index + 1]; pos3 = posDim == 3 ? positions[index + 2] : 0; } if (pos1 < minX) minX = pos1; else if (pos1 > maxX) maxX = pos1; if (pos2 < minY) minY = pos2; else if (pos2 > maxY) maxY = pos2; if (posDim == 3) { if (pos3 < minZ) minZ = pos3; else if (pos3 > maxZ) maxZ = pos3; } } target.width = maxX - (target.x = minX); target.height = maxY - (target.y = minY); target.depth = maxZ - (target.z = minZ); return target; } public static getSphereBounds( positionAttributes: AttributesView, center: Vector3D, _matrix3D: Matrix3D, _cache: Sphere, output: Sphere, count: number, offset: number = 0, ): Sphere { const positions: ArrayBufferView = positionAttributes.get(count, offset); const posDim: number = positionAttributes.dimensions; const posStride: number = positionAttributes.stride; let maxRadiusSquared: number = 0; let radiusSquared: number; const len = count * posStride; let distanceX: number; let distanceY: number; let distanceZ: number; for (let i: number = 0; i < len; i += posStride) { distanceX = positions[i] - center.x; distanceY = positions[i + 1] - center.y; distanceZ = posDim == 3 ? positions[i + 2] - center.z : -center.z; radiusSquared = distanceX * distanceX + distanceY * distanceY + distanceZ * distanceZ; if (maxRadiusSquared < radiusSquared) maxRadiusSquared = radiusSquared; } if (output == null) output = new Sphere(); output.x = center.x; output.y = center.y; output.z = center.z; output.radius = Math.sqrt(maxRadiusSquared); return output; } public static prepareScale9 ( elem: TriangleElements, bounds: Rectangle, grid: Rectangle, copy: boolean, emitUV?: boolean, uvMatrix?: Matrix ): TriangleElements { if (elem._numElements !== 0) { throw 'Indices not support yet'; } const target = copy ? elem.clone() : elem; const shapeBounds = TriangleElementsUtils.getBoxBounds ( elem.positions, elem.indices, null,null, null, elem._numElements | elem._numVertices, 0); const sliceX = [ -Infinity, grid.x, grid.right, Infinity ]; const sliceY = [ -Infinity, grid.y, grid.bottom, Infinity ]; const chunkX = { from: 0, to: 0 }; const chunkY = { from: 0, to: 0 }; for (let i = 1; i < 3; i++) { if (shapeBounds.x >= sliceX[i]) { chunkX.from = chunkX.to = i; } if (shapeBounds.y >= sliceY[i]) { chunkY.from = chunkY.to = i; } } for (let i = 0; i < 3; i++) { if (shapeBounds.right > sliceX[i] && i >= chunkX.from) { chunkX.to = i; } if (shapeBounds.bottom > sliceY[i] && i >= chunkY.from) { chunkY.to = i; } } const indices = Array.from({ length: 9 }, (_) => 0); target.scale9Indices = indices; target.scale9Grid = grid; target.originalScale9Bounds = bounds; if (emitUV) { if (target.positions.dimensions !== 2) { throw 'Emit UV support only 2D position buffer'; } const uv = (<Float32Array> target.positions.get(target._numVertices, 0)).slice(0); const raw = uvMatrix.rawData; for (let i = 0; i < uv.length; i += 2) { const x = uv[i + 0]; const y = uv[i + 1]; uv[i + 0] = x * raw[0] + y * raw[2] + raw[4]; uv[i + 1] = x * raw[1] + y * raw[3] + raw[5]; } target.setUVs(uv); target.invalidate(); } // shape already in valid region // not require run slicer for this case if (chunkX.from === chunkX.to && chunkY.from === chunkY.to) { target.scale9Indices[chunkY.from * 3 + chunkX.from] = target._numVertices; return target; } // run splitter const attrs = target.uvs ? [target.positions, target.uvs] : [target.positions]; let mesh = MeshView.fromAttributes<number>(attrs, target._numVertices, 3); const vector = new Vector3D(0,0); vector.setTo(1, 0, 0, 0); // we slice only over 2 offsets for (let i = chunkX.from; i < chunkX.to; i++) { //mesh = GeneratorUtils.SliceHodgman(mesh, vector, sliceX[i]); mesh = GeneratorUtils.SliceAllNaive(mesh, vector, sliceX[i + 1]); } vector.setTo(0, 1, 0, 0); // we slice only over 2 offsets for (let i = chunkY.from; i < chunkY.to; i++) { // generate errors // mesh = GeneratorUtils.SliceHodgman(mesh, vector, sliceY[i]); // Naive is more stable, but unoptimal mesh = GeneratorUtils.SliceAllNaive(mesh, vector, sliceY[i + 1]); } mesh.normalise(); // ordering - determine chunk id for (const p of mesh.poly) { for (let i = 0; i < 9; i++) { // over middle point // because all polygons should be inside rect, all their points should too, // we can use any points to chek chunk // but use middle point to redice slicing errors // BUT this is not garanteed stable results const data = p.middle.getData(0); const px = data[0]; const py = data[1]; if ( (px >= sliceX[i % 3]) && (px < sliceX[i % 3 + 1]) && (py >= sliceY[i / 3 | 0]) && (py < sliceY[(i / 3 | 0) + 1]) ) { // slicer can not generate user data p.userData = i; } } } mesh.poly.sort((a, b) => a.userData - b.userData); for (let i = 0; i < mesh.poly.length; i++) { const index = mesh.poly[i].userData; indices[index] = i * 3 + 3; } // pos const pos = mesh.toFloatArray(0); target.setPositions(pos); if (target.uvs) { const uvs = mesh.toFloatArray(1); target.setUVs(uvs); } //throw '[TriangleElementUtils] Not implemented'; target.invalidate(); return target; } public static updateScale9( elem: TriangleElements, originalRect: Rectangle, scaleX: number, scaleY: number, init: boolean = false, copy: boolean = false, ): TriangleElements { // todo: for now this only works for Float2Attributes. if (elem.scale9Indices.length !== 9) { throw 'ElementUtils: Error - triangleElement does not provide valid slice9Indices!'; } const offsets = elem.scale9Grid; const left = offsets.x - originalRect.x; const right = originalRect.right - offsets.right; const top = offsets.y - originalRect.y; const bottom = originalRect.bottom - offsets.bottom; const s_len = elem.scale9Indices.length; let innerWidth = originalRect.width * scaleX - (left + right); let innerHeight = originalRect.height * scaleY - (top + bottom); let cornerScaleX = 1; let cornerScaleY = 1; // reduce a overflow, when scale to small if (innerWidth < 0) { innerWidth = 0; cornerScaleX = originalRect.width * scaleX / (left + right); } if (innerHeight < 0) { innerHeight = 0; cornerScaleY = originalRect.height * scaleY / (top + bottom); } const innerScaleX = innerWidth / offsets.width; const innerScaleY = innerHeight / offsets.height; const stride = elem.positions.stride; const attrOffset = elem.positions.offset; let newElem: TriangleElements; let positions: ArrayBufferView; if (copy) { // there are not garaties that a buffer is 2 and not has stride // and a element may has UV // should working newElem = elem.clone(); newElem.scale9Grid = elem.scale9Grid; newElem.initialScale9Positions = elem.initialScale9Positions; newElem.scale9Indices = elem.scale9Indices; positions = newElem.positions.get(newElem._numVertices); } else { positions = elem.positions.get(elem._numVertices); } // todo: i had trouble when just cloning the positions // for now i just create the initialSlice9Positions by iterating the positions let initPos: number[]; if (init || !elem.initialScale9Positions) { initPos = []; // we store only XY, but buffer can be XYZ initPos.length = elem._numVertices * 2; let vindex = 0; const len = elem.positions.length; for (let i = 0; i < len; i += stride) { initPos[vindex + 0] = positions[attrOffset + i + 0]; initPos[vindex + 1] = positions[attrOffset + i + 1]; vindex += 2; } elem.initialScale9Positions = initPos; } else { initPos = <number[]>elem.initialScale9Positions; } const slice9Indices: number[] = elem.scale9Indices; const slice9Offsets_x = [ 0, left * cornerScaleX - left * innerScaleX, innerWidth - offsets.width * cornerScaleX, ]; const slice9Offsets_y = [ 0, top * cornerScaleY - top * innerScaleY, innerHeight - offsets.height * cornerScaleY, ]; // internal buffer iterator let attrindex = attrOffset; let vindex = 0; // iterating over the 9 chunks - keep in mind that we are constructing a 3x3 grid: for (let s = 0; s < s_len; s++) { const row = s / 3 | 0; const col = s % 3; // only need to x-scale if this is the middle column // if the innerWidth<=0 we can skip this complete column const scalex = col === 1 ? innerScaleX : cornerScaleX; // only need to y-scale if this is the middle row // if the innerHeight<=0 we can skip this complete row const scaley = row === 1 ? innerScaleY : cornerScaleY; // offsetx is different for each column const offsetx = slice9Offsets_x[col]; // offsety is different for each row const offsety = slice9Offsets_y[row]; // iterate the verts and apply the translation / scale // slice9Indices is vertices indeces, is not attribute indices while (vindex < slice9Indices[s]) { let vx = initPos[vindex * 2 + 0] - originalRect.x; let vy = initPos[vindex * 2 + 1] - originalRect.y; vx = offsetx + vx * scalex; vy = offsety + vy * scaley; vx /= scaleX; vy /= scaleY; positions[attrindex + 0] = vx + originalRect.x; positions[attrindex + 1] = vy + originalRect.y; // we should include a stride, because buffer maybe be contecated // or XYZ instead of XY attrindex += stride; vindex++; } } //console.log("positions",positions); if (copy) { newElem.positions.invalidate(); newElem.invalidate(); return newElem; } elem.positions.invalidate(); elem.invalidate(); return elem; } }