UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

561 lines (558 loc) 19.9 kB
import { RefCountedObject } from '../core/ref-counted-object.js'; import { Vec3 } from '../core/math/vec3.js'; import { BoundingBox } from '../core/shape/bounding-box.js'; import { SEMANTIC_TANGENT, SEMANTIC_BLENDINDICES, TYPE_UINT8, SEMANTIC_BLENDWEIGHT, SEMANTIC_POSITION, TYPE_UINT16, TYPE_INT16, TYPE_INT8, BUFFER_STATIC, BUFFER_DYNAMIC, TYPE_FLOAT32, SEMANTIC_NORMAL, SEMANTIC_TEXCOORD, SEMANTIC_COLOR, PRIMITIVE_TRIANGLES, INDEXFORMAT_UINT32, INDEXFORMAT_UINT16, PRIMITIVE_POINTS, typedArrayIndexFormats, PRIMITIVE_LINES } from '../platform/graphics/constants.js'; import { IndexBuffer } from '../platform/graphics/index-buffer.js'; import { VertexBuffer } from '../platform/graphics/vertex-buffer.js'; import { VertexFormat } from '../platform/graphics/vertex-format.js'; import { VertexIterator } from '../platform/graphics/vertex-iterator.js'; import { RENDERSTYLE_WIREFRAME, RENDERSTYLE_POINTS, RENDERSTYLE_SOLID } from './constants.js'; let id = 0; class GeometryData { constructor(){ this.initDefaults(); } initDefaults() { this.recreate = false; this.verticesUsage = BUFFER_STATIC; this.indicesUsage = BUFFER_STATIC; this.maxVertices = 0; this.maxIndices = 0; this.vertexCount = 0; this.indexCount = 0; this.vertexStreamsUpdated = false; this.indexStreamUpdated = false; this.vertexStreamDictionary = {}; this.indices = null; } _changeVertexCount(count, semantic) { if (!this.vertexCount) { this.vertexCount = count; } } static{ this.DEFAULT_COMPONENTS_POSITION = 3; } static{ this.DEFAULT_COMPONENTS_NORMAL = 3; } static{ this.DEFAULT_COMPONENTS_UV = 2; } static{ this.DEFAULT_COMPONENTS_COLORS = 4; } } class GeometryVertexStream { constructor(data, componentCount, dataType, dataTypeNormalize, asInt){ this.data = data; this.componentCount = componentCount; this.dataType = dataType; this.dataTypeNormalize = dataTypeNormalize; this.asInt = asInt; } } class Mesh extends RefCountedObject { constructor(graphicsDevice, options){ super(), this.indexBuffer = [ null ], this.vertexBuffer = null, this.primitive = [ { type: 0, base: 0, baseVertex: 0, count: 0 } ], this.skin = null, this.boneAabb = null, this._aabbVer = 0, this._aabb = new BoundingBox(), this._geometryData = null, this._morph = null, this._storageIndex = false, this._storageVertex = false; this.id = id++; this.device = graphicsDevice; this._storageIndex = options?.storageIndex || false; this._storageVertex = options?.storageVertex || false; } static fromGeometry(graphicsDevice, geometry, options = {}) { const mesh = new Mesh(graphicsDevice, options); const { positions, normals, tangents, colors, uvs, uvs1, blendIndices, blendWeights, indices } = geometry; if (positions) { mesh.setPositions(positions); } if (normals) { mesh.setNormals(normals); } if (tangents) { mesh.setVertexStream(SEMANTIC_TANGENT, tangents, 4); } if (colors) { mesh.setColors32(colors); } if (uvs) { mesh.setUvs(0, uvs); } if (uvs1) { mesh.setUvs(1, uvs1); } if (blendIndices) { mesh.setVertexStream(SEMANTIC_BLENDINDICES, blendIndices, 4, blendIndices.length / 4, TYPE_UINT8); } if (blendWeights) { mesh.setVertexStream(SEMANTIC_BLENDWEIGHT, blendWeights, 4); } if (indices) { mesh.setIndices(indices); } mesh.update(); return mesh; } set morph(morph) { if (morph !== this._morph) { if (this._morph) { this._morph.decRefCount(); } this._morph = morph; if (morph) { morph.incRefCount(); } } } get morph() { return this._morph; } set aabb(aabb) { this._aabb = aabb; this._aabbVer++; } get aabb() { return this._aabb; } destroy() { const morph = this.morph; if (morph) { this.morph = null; if (morph.refCount < 1) { morph.destroy(); } } if (this.vertexBuffer) { this.vertexBuffer.destroy(); this.vertexBuffer = null; } for(let j = 0; j < this.indexBuffer.length; j++){ this._destroyIndexBuffer(j); } this.indexBuffer.length = 0; this._geometryData = null; } _destroyIndexBuffer(index) { if (this.indexBuffer[index]) { this.indexBuffer[index].destroy(); this.indexBuffer[index] = null; } } _initBoneAabbs(morphTargets) { this.boneAabb = []; this.boneUsed = []; let x, y, z; let bMax, bMin; const boneMin = []; const boneMax = []; const boneUsed = this.boneUsed; const numBones = this.skin.boneNames.length; let maxMorphX, maxMorphY, maxMorphZ; for(let i = 0; i < numBones; i++){ boneMin[i] = new Vec3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE); boneMax[i] = new Vec3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE); } const iterator = new VertexIterator(this.vertexBuffer); const posElement = iterator.element[SEMANTIC_POSITION]; const weightsElement = iterator.element[SEMANTIC_BLENDWEIGHT]; const indicesElement = iterator.element[SEMANTIC_BLENDINDICES]; const numVerts = this.vertexBuffer.numVertices; for(let j = 0; j < numVerts; j++){ for(let k = 0; k < 4; k++){ const boneWeight = weightsElement.array[weightsElement.index + k]; if (boneWeight > 0) { const boneIndex = indicesElement.array[indicesElement.index + k]; boneUsed[boneIndex] = true; x = posElement.array[posElement.index]; y = posElement.array[posElement.index + 1]; z = posElement.array[posElement.index + 2]; bMax = boneMax[boneIndex]; bMin = boneMin[boneIndex]; if (bMin.x > x) bMin.x = x; if (bMin.y > y) bMin.y = y; if (bMin.z > z) bMin.z = z; if (bMax.x < x) bMax.x = x; if (bMax.y < y) bMax.y = y; if (bMax.z < z) bMax.z = z; if (morphTargets) { let minMorphX = maxMorphX = x; let minMorphY = maxMorphY = y; let minMorphZ = maxMorphZ = z; for(let l = 0; l < morphTargets.length; l++){ const target = morphTargets[l]; const dx = target.deltaPositions[j * 3]; const dy = target.deltaPositions[j * 3 + 1]; const dz = target.deltaPositions[j * 3 + 2]; if (dx < 0) { minMorphX += dx; } else { maxMorphX += dx; } if (dy < 0) { minMorphY += dy; } else { maxMorphY += dy; } if (dz < 0) { minMorphZ += dz; } else { maxMorphZ += dz; } } if (bMin.x > minMorphX) bMin.x = minMorphX; if (bMin.y > minMorphY) bMin.y = minMorphY; if (bMin.z > minMorphZ) bMin.z = minMorphZ; if (bMax.x < maxMorphX) bMax.x = maxMorphX; if (bMax.y < maxMorphY) bMax.y = maxMorphY; if (bMax.z < maxMorphZ) bMax.z = maxMorphZ; } } } iterator.next(); } const positionElement = this.vertexBuffer.getFormat().elements.find((e)=>e.name === SEMANTIC_POSITION); if (positionElement && positionElement.normalize) { const func = (()=>{ switch(positionElement.dataType){ case TYPE_INT8: return (x)=>Math.max(x / 127.0, -1); case TYPE_UINT8: return (x)=>x / 255.0; case TYPE_INT16: return (x)=>Math.max(x / 32767.0, -1); case TYPE_UINT16: return (x)=>x / 65535.0; default: return (x)=>x; } })(); for(let i = 0; i < numBones; i++){ if (boneUsed[i]) { const min = boneMin[i]; const max = boneMax[i]; min.set(func(min.x), func(min.y), func(min.z)); max.set(func(max.x), func(max.y), func(max.z)); } } } for(let i = 0; i < numBones; i++){ const aabb = new BoundingBox(); aabb.setMinMax(boneMin[i], boneMax[i]); this.boneAabb.push(aabb); } } _initGeometryData() { if (!this._geometryData) { this._geometryData = new GeometryData(); if (this.vertexBuffer) { this._geometryData.vertexCount = this.vertexBuffer.numVertices; this._geometryData.maxVertices = this.vertexBuffer.numVertices; } if (this.indexBuffer.length > 0 && this.indexBuffer[0]) { this._geometryData.indexCount = this.indexBuffer[0].numIndices; this._geometryData.maxIndices = this.indexBuffer[0].numIndices; } } } clear(verticesDynamic, indicesDynamic, maxVertices = 0, maxIndices = 0) { this._initGeometryData(); this._geometryData.initDefaults(); this._geometryData.recreate = true; this._geometryData.maxVertices = maxVertices; this._geometryData.maxIndices = maxIndices; this._geometryData.verticesUsage = verticesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC; this._geometryData.indicesUsage = indicesDynamic ? BUFFER_STATIC : BUFFER_DYNAMIC; } setVertexStream(semantic, data, componentCount, numVertices, dataType = TYPE_FLOAT32, dataTypeNormalize = false, asInt = false) { this._initGeometryData(); const vertexCount = numVertices || data.length / componentCount; this._geometryData._changeVertexCount(vertexCount, semantic); this._geometryData.vertexStreamsUpdated = true; this._geometryData.vertexStreamDictionary[semantic] = new GeometryVertexStream(data, componentCount, dataType, dataTypeNormalize, asInt); } getVertexStream(semantic, data) { let count = 0; let done = false; if (this._geometryData) { const stream = this._geometryData.vertexStreamDictionary[semantic]; if (stream) { done = true; count = this._geometryData.vertexCount; if (ArrayBuffer.isView(data)) { data.set(stream.data); } else { data.length = 0; data.push(stream.data); } } } if (!done) { if (this.vertexBuffer) { const iterator = new VertexIterator(this.vertexBuffer); count = iterator.readData(semantic, data); } } return count; } setPositions(positions, componentCount = GeometryData.DEFAULT_COMPONENTS_POSITION, numVertices) { this.setVertexStream(SEMANTIC_POSITION, positions, componentCount, numVertices, TYPE_FLOAT32, false); } setNormals(normals, componentCount = GeometryData.DEFAULT_COMPONENTS_NORMAL, numVertices) { this.setVertexStream(SEMANTIC_NORMAL, normals, componentCount, numVertices, TYPE_FLOAT32, false); } setUvs(channel, uvs, componentCount = GeometryData.DEFAULT_COMPONENTS_UV, numVertices) { this.setVertexStream(SEMANTIC_TEXCOORD + channel, uvs, componentCount, numVertices, TYPE_FLOAT32, false); } setColors(colors, componentCount = GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices) { this.setVertexStream(SEMANTIC_COLOR, colors, componentCount, numVertices, TYPE_FLOAT32, false); } setColors32(colors, numVertices) { this.setVertexStream(SEMANTIC_COLOR, colors, GeometryData.DEFAULT_COMPONENTS_COLORS, numVertices, TYPE_UINT8, true); } setIndices(indices, numIndices) { this._initGeometryData(); this._geometryData.indexStreamUpdated = true; this._geometryData.indices = indices; this._geometryData.indexCount = numIndices || indices.length; } getPositions(positions) { return this.getVertexStream(SEMANTIC_POSITION, positions); } getNormals(normals) { return this.getVertexStream(SEMANTIC_NORMAL, normals); } getUvs(channel, uvs) { return this.getVertexStream(SEMANTIC_TEXCOORD + channel, uvs); } getColors(colors) { return this.getVertexStream(SEMANTIC_COLOR, colors); } getIndices(indices) { let count = 0; if (this._geometryData && this._geometryData.indices) { const streamIndices = this._geometryData.indices; count = this._geometryData.indexCount; if (ArrayBuffer.isView(indices)) { indices.set(streamIndices); } else { indices.length = 0; for(let i = 0, il = streamIndices.length; i < il; i++){ indices.push(streamIndices[i]); } } } else { if (this.indexBuffer.length > 0 && this.indexBuffer[0]) { const indexBuffer = this.indexBuffer[0]; count = indexBuffer.readData(indices); } } return count; } update(primitiveType = PRIMITIVE_TRIANGLES, updateBoundingBox = true) { if (this._geometryData) { if (updateBoundingBox) { const stream = this._geometryData.vertexStreamDictionary[SEMANTIC_POSITION]; if (stream) { if (stream.componentCount === 3) { this._aabb.compute(stream.data, this._geometryData.vertexCount); this._aabbVer++; } } } let destroyVB = this._geometryData.recreate; if (this._geometryData.vertexCount > this._geometryData.maxVertices) { destroyVB = true; this._geometryData.maxVertices = this._geometryData.vertexCount; } if (destroyVB) { if (this.vertexBuffer) { this.vertexBuffer.destroy(); this.vertexBuffer = null; } } let destroyIB = this._geometryData.recreate; if (this._geometryData.indexCount > this._geometryData.maxIndices) { destroyIB = true; this._geometryData.maxIndices = this._geometryData.indexCount; } if (destroyIB) { if (this.indexBuffer.length > 0 && this.indexBuffer[0]) { this.indexBuffer[0].destroy(); this.indexBuffer[0] = null; } } if (this._geometryData.vertexStreamsUpdated) { this._updateVertexBuffer(); } if (this._geometryData.indexStreamUpdated) { this._updateIndexBuffer(); } this.primitive[0].type = primitiveType; if (this.indexBuffer.length > 0 && this.indexBuffer[0]) { if (this._geometryData.indexStreamUpdated) { this.primitive[0].count = this._geometryData.indexCount; this.primitive[0].indexed = true; } } else { if (this._geometryData.vertexStreamsUpdated) { this.primitive[0].count = this._geometryData.vertexCount; this.primitive[0].indexed = false; } } this._geometryData.vertexCount = 0; this._geometryData.indexCount = 0; this._geometryData.vertexStreamsUpdated = false; this._geometryData.indexStreamUpdated = false; this._geometryData.recreate = false; this.updateRenderStates(); } } _buildVertexFormat(vertexCount) { const vertexDesc = []; for(const semantic in this._geometryData.vertexStreamDictionary){ const stream = this._geometryData.vertexStreamDictionary[semantic]; vertexDesc.push({ semantic: semantic, components: stream.componentCount, type: stream.dataType, normalize: stream.dataTypeNormalize, asInt: stream.asInt }); } return new VertexFormat(this.device, vertexDesc, vertexCount); } _updateVertexBuffer() { if (!this.vertexBuffer) { const allocateVertexCount = this._geometryData.maxVertices; const format = this._buildVertexFormat(allocateVertexCount); this.vertexBuffer = new VertexBuffer(this.device, format, allocateVertexCount, { usage: this._geometryData.verticesUsage, storage: this._storageVertex }); } const iterator = new VertexIterator(this.vertexBuffer); const numVertices = this._geometryData.vertexCount; for(const semantic in this._geometryData.vertexStreamDictionary){ const stream = this._geometryData.vertexStreamDictionary[semantic]; iterator.writeData(semantic, stream.data, numVertices); delete this._geometryData.vertexStreamDictionary[semantic]; } iterator.end(); } _updateIndexBuffer() { if (this.indexBuffer.length <= 0 || !this.indexBuffer[0]) { const maxVertices = this._geometryData.maxVertices; const createFormat = maxVertices > 0xffff || maxVertices === 0 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16; const options = this._storageIndex ? { storage: true } : undefined; this.indexBuffer[0] = new IndexBuffer(this.device, createFormat, this._geometryData.maxIndices, this._geometryData.indicesUsage, undefined, options); } const srcIndices = this._geometryData.indices; if (srcIndices) { const indexBuffer = this.indexBuffer[0]; indexBuffer.writeData(srcIndices, this._geometryData.indexCount); this._geometryData.indices = null; } } prepareRenderState(renderStyle) { if (renderStyle === RENDERSTYLE_WIREFRAME) { this.generateWireframe(); } else if (renderStyle === RENDERSTYLE_POINTS) { this.primitive[RENDERSTYLE_POINTS] = { type: PRIMITIVE_POINTS, base: 0, baseVertex: 0, count: this.vertexBuffer ? this.vertexBuffer.numVertices : 0, indexed: false }; } } updateRenderStates() { if (this.primitive[RENDERSTYLE_POINTS]) { this.prepareRenderState(RENDERSTYLE_POINTS); } if (this.primitive[RENDERSTYLE_WIREFRAME]) { this.prepareRenderState(RENDERSTYLE_WIREFRAME); } } generateWireframe() { this._destroyIndexBuffer(RENDERSTYLE_WIREFRAME); const numVertices = this.vertexBuffer.numVertices; let lines; let format; if (this.indexBuffer.length > 0 && this.indexBuffer[0]) { const offsets = [ [ 0, 1 ], [ 1, 2 ], [ 2, 0 ] ]; const base = this.primitive[RENDERSTYLE_SOLID].base; const count = this.primitive[RENDERSTYLE_SOLID].count; const baseVertex = this.primitive[RENDERSTYLE_SOLID].baseVertex || 0; const indexBuffer = this.indexBuffer[RENDERSTYLE_SOLID]; const indicesArrayType = typedArrayIndexFormats[indexBuffer.format]; const srcIndices = new indicesArrayType(indexBuffer.storage); const tmpIndices = new indicesArrayType(count * 2); const seen = new Set(); let len = 0; for(let j = base; j < base + count; j += 3){ for(let k = 0; k < 3; k++){ const i1 = srcIndices[j + offsets[k][0]] + baseVertex; const i2 = srcIndices[j + offsets[k][1]] + baseVertex; const hash = i1 > i2 ? i2 * numVertices + i1 : i1 * numVertices + i2; if (!seen.has(hash)) { seen.add(hash); tmpIndices[len++] = i1; tmpIndices[len++] = i2; } } } seen.clear(); format = indexBuffer.format; lines = tmpIndices.slice(0, len); } else { const safeNumVertices = numVertices - numVertices % 3; const count = safeNumVertices / 3 * 6; format = count > 65535 ? INDEXFORMAT_UINT32 : INDEXFORMAT_UINT16; lines = count > 65535 ? new Uint32Array(count) : new Uint16Array(count); let idx = 0; for(let i = 0; i < safeNumVertices; i += 3){ lines[idx++] = i; lines[idx++] = i + 1; lines[idx++] = i + 1; lines[idx++] = i + 2; lines[idx++] = i + 2; lines[idx++] = i; } } const wireBuffer = new IndexBuffer(this.vertexBuffer.device, format, lines.length, BUFFER_STATIC, lines.buffer); this.primitive[RENDERSTYLE_WIREFRAME] = { type: PRIMITIVE_LINES, base: 0, baseVertex: 0, count: lines.length, indexed: true }; this.indexBuffer[RENDERSTYLE_WIREFRAME] = wireBuffer; } } export { Mesh };