const THREE = require('three'); const THREEGeometry = require('./three/Geometry').Geometry; const SpriteText = require('three-spritetext').default; function resolveURL(url) { let actualURL = url; const prefix = (require("./zinc").modelPrefix); if (prefix) { if (prefix[prefix.length -1] != '/') prefix = prefix + '/'; const r = new RegExp('^(?:[a-z]+:)?//', 'i'); if (!r.test(url)) { actualURL = prefix + url; } } return actualURL; } function createNewURL(target, reference) { const getNewURL = (target, reference) => { try { let newURL = (new URL(target, reference)).href; //Make sure the target url does not contain parameters if (target && target.split("?").length < 2) { const paramsStrings = reference.split("?"); //There are parameters, add them to the target if (paramsStrings.length === 2) { newURL = newURL + "?" + paramsStrings[1]; } } return newURL; } catch { console.error(`There is an issue creting the url link with: ${target}.` ); } } if (!Array.isArray(target)) { return getNewURL(target, reference); } else { const urls = []; target.forEach((url) => { urls.push(getNewURL(url, reference)); }); return urls; } } /* * Calculate the bounding box of a mesh, values will be * set for cachedBox, b1, v1 and v2 and they need to be * defined. */ function getBoundingBox(mesh, cachedBox, b1, v1, v2) { let influences = mesh.morphTargetInfluences; let attributes = undefined; if (mesh.geometry) attributes = mesh.geometry.morphAttributes; let found = false; if (influences && attributes && attributes.position) { v1.set(0.0, 0.0, 0.0); v2.set(0.0, 0.0, 0.0); for (let i = 0; i < influences.length; i++) { if (influences[i] > 0) { found = true; b1.setFromArray(attributes.position[i].array); v1.add(b1.min.multiplyScalar(influences[i])); v2.add(b1.max.multiplyScalar(influences[i])); } } if (found) { cachedBox.set(v1, v2); } } if (!found) { cachedBox.setFromBufferAttribute( mesh.geometry.attributes.position); } mesh.updateWorldMatrix(true, true); cachedBox.applyMatrix4(mesh.matrixWorld); } //Convenient function function loadExternalFile(url, data, callback, errorCallback) { // Set up an asynchronous request const request = new XMLHttpRequest(); request.open('GET', resolveURL(url), true); // Hook the event that gets called as the request progresses request.onreadystatechange = () => { // If the request is "DONE" (completed or failed) if (request.readyState == 4) { // If we got HTTP status 200 (OK) if (request.status == 200) { callback(request.responseText, data) } else { // Failed errorCallback(url); } } }; request.send(null); } function loadExternalFiles(urls, callback, errorCallback) { const numUrls = urls.length; let numComplete = 0; const result = []; // Callback for a single file function partialCallback(text, urlIndex) { result[urlIndex] = text; numComplete++; // When all files have downloaded if (numComplete == numUrls) { callback(result); } } for (let i = 0; i < numUrls; i++) { loadExternalFile(urls[i], i, partialCallback, errorCallback); } } //Get the colours at index exports.getColorsRGB = (colors, index) => { const index_in_colors = Math.floor(index/3); const remainder = index%3; let hex_value = 0; if (remainder == 0) { hex_value = colors[index_in_colors].r; } else if (remainder == 1) { hex_value = colors[index_in_colors].g; } else if (remainder == 2) { hex_value = colors[index_in_colors].b; } const mycolor = new THREE.Color(hex_value); return [mycolor.r, mycolor.g, mycolor.b]; } exports.updateMorphColorAttribute = function(targetGeometry, morph) { if (morph && targetGeometry && targetGeometry.morphAttributes && targetGeometry.morphAttributes[ "color" ]) { const morphColors = targetGeometry.morphAttributes[ "color" ]; const influences = morph.morphTargetInfluences; const length = influences.length; targetGeometry.deleteAttribute( 'morphColor0' ); targetGeometry.deleteAttribute( 'morphColor1' ); let bound = 0; let morphArray = []; for (let i = 0; (1 > bound) || (i < length); i++) { if (influences[i] > 0) { bound++; morphArray.push([i, influences[i]]); } } if (morphArray.length == 2) { targetGeometry.setAttribute('morphColor0', morphColors[ morphArray[0][0] ] ); targetGeometry.setAttribute('morphColor1', morphColors[ morphArray[1][0] ] ); } else if (morphArray.length == 1) { targetGeometry.setAttribute('morphColor0', morphColors[ morphArray[0][0] ] ); targetGeometry.setAttribute('morphColor1', morphColors[ morphArray[0][0] ] ); } } } exports.toBufferGeometry = (geometryIn, options) => { let geometry = undefined; if (geometryIn instanceof THREEGeometry) { if (options.localTimeEnabled && !geometryIn.morphNormalsReady && (geometryIn.morphNormals == undefined || geometryIn.morphNormals.length == 0)) geometryIn.computeMorphNormals(); geometry = geometryIn.toIndexedBufferGeometry(); if (options.localMorphColour) { copyMorphColorsToIndexedBufferGeometry(geometryIn, geometry); } } else if (geometryIn instanceof THREE.BufferGeometry) { geometry = geometryIn.clone(); } geometry.colorsNeedUpdate = true; geometry.computeBoundingBox(); geometry.computeBoundingSphere(); if (geometryIn._video) geometry._video = geometryIn._video; return geometry; } exports.copyMorphColorsToBufferGeometry = (geometry, bufferGeometry) => { if (geometry && geometry.morphColors && geometry.morphColors.length > 0 ) { let array = []; let morphColors = geometry.morphColors; const getColorsRGB = require("./utilities").getColorsRGB; for ( var i = 0, l = morphColors.length; i < l; i ++ ) { let morphColor = morphColors[ i ]; let colorArray = []; for ( var j = 0; j < geometry.faces.length; j ++ ) { let face = geometry.faces[j]; let color = getColorsRGB(morphColor.colors, face.a); colorArray.push(color[0], color[1], color[2]); color = getColorsRGB(morphColor.colors, face.b); colorArray.push(color[0], color[1], color[2]); color = getColorsRGB(morphColor.colors, face.c); colorArray.push(color[0], color[1], color[2]); } var attribute = new THREE.Float32BufferAttribute( geometry.faces.length * 3 * 3, 3 ); attribute.name = morphColor.name; array.push( attribute.copyArray( colorArray ) ); } bufferGeometry.morphAttributes[ "color" ] = array; } } const copyMorphColorsToIndexedBufferGeometry = (geometry, bufferGeometry) => { if (geometry && geometry.morphColors && geometry.morphColors.length > 0 ) { let array = []; let morphColors = geometry.morphColors; const getColorsRGB = require("./utilities").getColorsRGB; for ( let i = 0, l = morphColors.length; i < l; i ++ ) { const morphColor = morphColors[ i ]; const colorArray = []; for ( let j = 0; j < morphColor.colors.length * 3; j ++ ) { let color = getColorsRGB(morphColor.colors, j); colorArray.push(color[0], color[1], color[2]); } const attribute = new THREE.Float32BufferAttribute( colorArray, 3 ); attribute.name = morphColor.name; array.push( attribute ); } bufferGeometry.morphAttributes[ "color" ] = array; } } exports.mergeVertices = ( geometry, tolerance = 1e-4 ) => { tolerance = Math.max( tolerance, Number.EPSILON ); // Generate an index buffer if the geometry doesn't have one, or optimize it // if it's already available. var hashToIndex = {}; var indices = geometry.getIndex(); var positions = geometry.getAttribute( 'position' ); var vertexCount = indices ? indices.count : positions.count; // next value for triangle indices var nextIndex = 0; // attributes and new attribute arrays var attributeNames = Object.keys( geometry.attributes ); var attrArrays = {}; var morphAttrsArrays = {}; var newIndices = []; var getters = [ 'getX', 'getY', 'getZ', 'getW' ]; // initialize the arrays for ( var i = 0, l = attributeNames.length; i < l; i ++ ) { var name = attributeNames[ i ]; attrArrays[ name ] = []; var morphAttr = geometry.morphAttributes[ name ]; if ( morphAttr ) { morphAttrsArrays[ name ] = new Array( morphAttr.length ).fill().map( () => [] ); } } // convert the error tolerance to an amount of decimal places to truncate to var decimalShift = Math.log10( 1 / tolerance ); var shiftMultiplier = Math.pow( 10, decimalShift ); for ( var i = 0; i < vertexCount; i ++ ) { var index = indices ? indices.getX( i ) : i; // Generate a hash for the vertex attributes at the current index 'i' var hash = ''; for ( var j = 0, l = attributeNames.length; j < l; j ++ ) { var name = attributeNames[ j ]; var attribute = geometry.getAttribute( name ); var itemSize = attribute.itemSize; for ( var k = 0; k < itemSize; k ++ ) { // double tilde truncates the decimal value hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * shiftMultiplier ) },`; } } // Add another reference to the vertex if it's already // used by another index if ( hash in hashToIndex ) { newIndices.push( hashToIndex[ hash ] ); } else { // copy data to the new index in the attribute arrays for ( var j = 0, l = attributeNames.length; j < l; j ++ ) { var name = attributeNames[ j ]; var attribute = geometry.getAttribute( name ); var morphAttr = geometry.morphAttributes[ name ]; var itemSize = attribute.itemSize; var newarray = attrArrays[ name ]; var newMorphArrays = morphAttrsArrays[ name ]; for ( var k = 0; k < itemSize; k ++ ) { var getterFunc = getters[ k ]; newarray.push( attribute[ getterFunc ]( index ) ); if ( morphAttr ) { for ( var m = 0, ml = morphAttr.length; m < ml; m ++ ) { newMorphArrays[ m ].push( morphAttr[ m ][ getterFunc ]( index ) ); } } } } hashToIndex[ hash ] = nextIndex; newIndices.push( nextIndex ); nextIndex ++; } } // Generate typed arrays from new attribute arrays and update // the attributeBuffers const result = geometry.clone(); for ( var i = 0, l = attributeNames.length; i < l; i ++ ) { var name = attributeNames[ i ]; var oldAttribute = geometry.getAttribute( name ); var attribute; var buffer = new oldAttribute.array.constructor( attrArrays[ name ] ); if ( oldAttribute.isInterleavedBufferAttribute ) { attribute = new THREE.BufferAttribute( buffer, oldAttribute.itemSize, oldAttribute.itemSize ); } else { attribute = geometry.getAttribute( name ).clone(); attribute.setArray( buffer ); } result.setAttribute( name, attribute ); // Update the attribute arrays if ( name in morphAttrsArrays ) { for ( var j = 0; j < morphAttrsArrays[ name ].length; j ++ ) { var morphAttribute = geometry.morphAttributes[ name ][ j ].clone(); morphAttribute.setArray( new morphAttribute.array.constructor( morphAttrsArrays[ name ][ j ] ) ); result.morphAttributes[ name ][ j ] = morphAttribute; } } } // Generate an index buffer typed array var cons = Uint8Array; if ( newIndices.length >= Math.pow( 2, 8 ) ) cons = Uint16Array; if ( newIndices.length >= Math.pow( 2, 16 ) ) cons = Uint32Array; var newIndexBuffer = new cons( newIndices ); var newIndices = null; if ( indices === null ) { newIndices = new THREE.BufferAttribute( newIndexBuffer, 1 ); } else { newIndices = geometry.getIndex().clone(); newIndices.setArray( newIndexBuffer ); } result.setIndex( newIndices ); return result; } function PhongToToon(materialIn) { if (materialIn.isMeshPhongMaterial) { let material = new THREE.MeshToonMaterial({ color : materialIn.color.clone(), morphTargets : materialIn.morphTargets, morphNormals : materialIn.morphNormals, vertexColors : materialIn.vertexColors, transparent : materialIn.transparent, opacity : materialIn.opacity, side : materialIn.side }); if (materialIn.map) material.map = materialIn.map; return material; } return materialIn; } /** * Create and return a new buffer geometry with the size of length, * and initial coords. */ function createBufferGeometry(length, coords) { if (coords && (length >= coords.length)) { const geometry = new THREE.BufferGeometry() const vertices = new Float32Array(length * 3); let i = 0; coords.forEach(coord => { vertices[i++] = coord[0]; vertices[i++] = coord[1]; vertices[i++] = coord[2]; }); geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); geometry.setDrawRange(0, coords.length); return geometry; } return undefined; }; function getCircularTexture() { const image = new Image(); image.src = require("./assets/disc.png"); const texture = new THREE.Texture(); texture.image = image; texture.needsUpdate = true; return texture; } function createNewSpriteText(text, height, colour, font, pixel, weight) { const sprite = new SpriteText(text, height, colour, font, pixel, weight); sprite.fontFace = font; sprite.fontSize = pixel; sprite.fontWeight = weight; sprite.material.map.generateMipmaps = false; sprite.material.map.anisotropy = 4; sprite.material.sizeAttenuation = false; sprite.material.alphaTest = 0.5; sprite.material.transparent = true; sprite.material.depthWrite = false; sprite.material.depthTest = false; sprite.center.set(0.5, -1.2); sprite.renderOrder = 10000; return sprite; } exports.getBoundingBox = getBoundingBox; exports.createNewURL = createNewURL; exports.createBufferGeometry = createBufferGeometry; exports.getCircularTexture = getCircularTexture; exports.resolveURL = resolveURL; exports.loadExternalFile = loadExternalFile; exports.loadExternalFiles = loadExternalFiles; exports.PhongToToon = PhongToToon; exports.createNewSpriteText = createNewSpriteText;