UNPKG

acc-viewer

Version:

Acc Viewer

2,059 lines (1,803 loc) 437 kB
import * as THREE from 'three'; import { TGALoader } from 'three/examples/jsm/loaders/TGALoader.js'; import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'; import { ColladaLoader } from 'three/examples/jsm/loaders/ColladaLoader.js'; import { VRMLLoader } from 'three/examples/jsm/loaders/VRMLLoader.js'; import { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js'; import * as fflate from 'fflate'; import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js'; function IsDefined (val) { return val !== undefined && val !== null; } function ValueOrDefault (val, def) { if (val === undefined || val === null) { return def; } return val; } function CopyObjectAttributes (src, dest) { if (!IsDefined (src)) { return; } for (let attribute of Object.keys (src)) { if (IsDefined (src[attribute])) { dest[attribute] = src[attribute]; } } } function IsObjectEmpty (obj) { return Object.keys (obj).length === 0; } function EscapeHtmlChars (str) { return str.replace (/</g, '&lt;').replace (/>/g, '&gt;'); } class EventNotifier { constructor () { this.eventListeners = new Map (); } AddEventListener (eventId, listener) { if (!this.eventListeners.has (eventId)) { this.eventListeners.set (eventId, []); } let listeners = this.eventListeners.get (eventId); listeners.push (listener); } HasEventListener (eventId) { return this.eventListeners.has (eventId); } GetEventNotifier (eventId) { return () => { this.NotifyEventListeners (eventId); }; } NotifyEventListeners (eventId, ...args) { if (!this.eventListeners.has (eventId)) { return; } let listeners = this.eventListeners.get (eventId); for (let listener of listeners) { listener (...args); } } } class TaskRunner { constructor () { this.count = null; this.current = null; this.callbacks = null; } Run (count, callbacks) { this.count = count; this.current = 0; this.callbacks = callbacks; if (count === 0) { this.TaskReady (); } else { this.RunOnce (); } } RunBatch (count, batchCount, callbacks) { let stepCount = 0; if (count > 0) { stepCount = parseInt ((count - 1) / batchCount, 10) + 1; } this.Run (stepCount, { runTask : (index, ready) => { const firstIndex = index * batchCount; const lastIndex = Math.min ((index + 1) * batchCount, count) - 1; callbacks.runTask (firstIndex, lastIndex, ready); }, onReady : callbacks.onReady }); } RunOnce () { setTimeout (() => { this.callbacks.runTask (this.current, this.TaskReady.bind (this)); }, 0); } TaskReady () { this.current += 1; if (this.current < this.count) { this.RunOnce (); } else { if (this.callbacks.onReady) { this.callbacks.onReady (); } } } } function RunTaskAsync (task) { setTimeout (() => { task (); }, 10); } function RunTasks (count, callbacks) { let taskRunner = new TaskRunner (); taskRunner.Run (count, callbacks); } function RunTasksBatch (count, batchCount, callbacks) { let taskRunner = new TaskRunner (); taskRunner.RunBatch (count, batchCount, callbacks); } function WaitWhile (expression) { function Waiter (expression) { if (expression ()) { setTimeout (() => { Waiter (expression); }, 10); } } Waiter (expression); } let externalLibLocation = null; let loadedExternalLibs = new Set (); /** * Sets the location of the external libraries used by the engine. This is the content of the libs * folder in the package. The location must be relative to the main file. * @param {string} newExternalLibLocation Relative path to the libs folder. */ function SetExternalLibLocation (newExternalLibLocation) { externalLibLocation = newExternalLibLocation; } function GetExternalLibPath (libName) { if (externalLibLocation === null) { return null; } return externalLibLocation + '/' + libName; } function LoadExternalLibrary (libName) { return new Promise ((resolve, reject) => { if (externalLibLocation === null) { reject (); return; } if (loadedExternalLibs.has (libName)) { resolve (); return; } let scriptElement = document.createElement ('script'); scriptElement.type = 'text/javascript'; scriptElement.src = GetExternalLibPath (libName); scriptElement.onload = () => { loadedExternalLibs.add (libName); resolve (); }; scriptElement.onerror = () => { reject (); }; document.head.appendChild (scriptElement); }); } /** * File source identifier for import. * @enum */ const FileSource = { /** The file is provided by a URL. */ Url : 1, /** The file is provided by a {@link File} object. */ File : 2, /** Used internally if a file is originated by a compressed archive. */ Decompressed : 3 }; const FileFormat = { Text : 1, Binary : 2 }; function GetFileName (filePath) { let fileName = filePath; let firstParamIndex = fileName.indexOf ('?'); if (firstParamIndex !== -1) { fileName = fileName.substring (0, firstParamIndex); } let firstSeparator = fileName.lastIndexOf ('/'); if (firstSeparator === -1) { firstSeparator = fileName.lastIndexOf ('\\'); } if (firstSeparator !== -1) { fileName = fileName.substring (firstSeparator + 1); } return decodeURI (fileName); } function GetFileExtension (filePath) { let fileName = GetFileName (filePath); let firstPoint = fileName.lastIndexOf ('.'); if (firstPoint === -1) { return ''; } let extension = fileName.substring (firstPoint + 1); return extension.toLowerCase (); } function RequestUrl (url, onProgress) { return new Promise ((resolve, reject) => { let request = new XMLHttpRequest (); request.open ('GET', url, true); request.onprogress = (event) => { onProgress (event.loaded, event.total); }; request.onload = () => { if (request.status === 200) { resolve (request.response); } else { reject (); } }; request.onerror = () => { reject (); }; request.responseType = 'arraybuffer'; request.send (null); }); } function ReadFile (file, onProgress) { return new Promise ((resolve, reject) => { let reader = new FileReader (); reader.onprogress = (event) => { onProgress (event.loaded, event.total); }; reader.onloadend = (event) => { if (event.target.readyState === FileReader.DONE) { resolve (event.target.result); } }; reader.onerror = () => { reject (); }; reader.readAsArrayBuffer (file); }); } function TransformFileHostUrls (urls) { for (let i = 0; i < urls.length; i++) { let url = urls[i]; if (url.search (/www\.dropbox\.com/u) !== -1) { url = url.replace ('www.dropbox.com', 'dl.dropbox.com'); let separatorPos = url.indexOf ('?'); if (separatorPos !== -1) { url = url.substring (0, separatorPos); } urls[i] = url; } else if (url.search (/github\.com/u) !== -1) { url = url.replace ('github.com', 'raw.githubusercontent.com'); url = url.replace ('/blob', ''); let separatorPos = url.indexOf ('?'); if (separatorPos !== -1) { url = url.substring (0, separatorPos); } urls[i] = url; } } } function IsUrl (str) { const regex = /^https?:\/\/\S+$/g; const match = str.match (regex); return match !== null; } const Eps = 0.00000001; const BigEps = 0.0001; const RadDeg = 57.29577951308232; const DegRad = 0.017453292519943; function IsZero (a) { return Math.abs (a) < Eps; } function IsLower (a, b) { return b - a > Eps; } function IsGreater (a, b) { return a - b > Eps; } function IsLowerOrEqual (a, b) { return b - a > -Eps; } function IsGreaterOrEqual (a, b) { return a - b > -Eps; } function IsEqual (a, b) { return Math.abs (b - a) < Eps; } function IsEqualEps (a, b, eps) { return Math.abs (b - a) < eps; } function IsPositive (a) { return a > Eps; } function IsNegative (a) { return a < -Eps; } const Direction = { X : 1, Y : 2, Z : 3 }; class Coord2D { constructor (x, y) { this.x = x; this.y = y; } Clone () { return new Coord2D (this.x, this.y); } } function CoordIsEqual2D (a, b) { return IsEqual (a.x, b.x) && IsEqual (a.y, b.y); } function AddCoord2D (a, b) { return new Coord2D (a.x + b.x, a.y + b.y); } function SubCoord2D (a, b) { return new Coord2D (a.x - b.x, a.y - b.y); } function CoordDistance2D (a, b) { return Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } /** * RGB color object. Components are integers in the range of 0..255. */ class RGBColor { /** * @param {integer} r Red component. * @param {integer} g Green component. * @param {integer} b Blue component. */ constructor (r, g, b) { this.r = r; this.g = g; this.b = b; } /** * Sets the value of all components. * @param {integer} r Red component. * @param {integer} g Green component. * @param {integer} b Blue component. */ Set (r, g, b) { this.r = r; this.g = g; this.b = b; } /** * Creates a clone of the object. * @returns {RGBColor} */ Clone () { return new RGBColor (this.r, this.g, this.b); } } /** * RGBA color object. Components are integers in the range of 0..255. */ class RGBAColor { /** * @param {integer} r Red component. * @param {integer} g Green component. * @param {integer} b Blue component. * @param {integer} a Alpha component. */ constructor (r, g, b, a) { this.r = r; this.g = g; this.b = b; this.a = a; } /** * Sets the value of all components. * @param {integer} r Red component. * @param {integer} g Green component. * @param {integer} b Blue component. * @param {integer} a Alpha component. */ Set (r, g, b, a) { this.r = r; this.g = g; this.b = b; this.a = a; } /** * Creates a clone of the object. * @returns {RGBAColor} */ Clone () { return new RGBAColor (this.r, this.g, this.b, this.a); } } function ColorComponentFromFloat (component) { return parseInt (Math.round (component * 255.0), 10); } function ColorComponentToFloat (component) { return component / 255.0; } function RGBColorFromFloatComponents (r, g, b) { return new RGBColor ( ColorComponentFromFloat (r), ColorComponentFromFloat (g), ColorComponentFromFloat (b) ); } function SRGBToLinear (component) { if (component < 0.04045) { return component * 0.0773993808; } else { return Math.pow (component * 0.9478672986 + 0.0521327014, 2.4); } } function LinearToSRGB (component) { if (component < 0.0031308) { return component * 12.92; } else { return 1.055 * (Math.pow (component, 0.41666)) - 0.055; } } function IntegerToHexString (intVal) { let result = parseInt (intVal, 10).toString (16); while (result.length < 2) { result = '0' + result; } return result; } function RGBColorToHexString (color) { let r = IntegerToHexString (color.r); let g = IntegerToHexString (color.g); let b = IntegerToHexString (color.b); return r + g + b; } function RGBAColorToHexString (color) { let r = IntegerToHexString (color.r); let g = IntegerToHexString (color.g); let b = IntegerToHexString (color.b); let a = IntegerToHexString (color.a); return r + g + b + a; } function HexStringToRGBColor (hexString) { if (hexString.length !== 6) { return null; } let r = parseInt (hexString.substring (0, 2), 16); let g = parseInt (hexString.substring (2, 4), 16); let b = parseInt (hexString.substring (4, 6), 16); return new RGBColor (r, g, b); } function HexStringToRGBAColor (hexString) { if (hexString.length !== 6 && hexString.length !== 8) { return null; } let r = parseInt (hexString.substring (0, 2), 16); let g = parseInt (hexString.substring (2, 4), 16); let b = parseInt (hexString.substring (4, 6), 16); let a = 255; if (hexString.length === 8) { a = parseInt (hexString.substring (6, 8), 16); } return new RGBAColor (r, g, b, a); } function ArrayToRGBColor (arr) { return new RGBColor (arr[0], arr[1], arr[2]); } function RGBColorIsEqual (a, b) { return a.r === b.r && a.g === b.g && a.b === b.b; } class TextureMap { constructor () { this.name = null; this.mimeType = null; this.buffer = null; this.offset = new Coord2D (0.0, 0.0); this.scale = new Coord2D (1.0, 1.0); this.rotation = 0.0; // radians } IsValid () { return this.name !== null && this.buffer !== null; } HasTransformation () { if (!CoordIsEqual2D (this.offset, new Coord2D (0.0, 0.0))) { return true; } if (!CoordIsEqual2D (this.scale, new Coord2D (1.0, 1.0))) { return true; } if (!IsEqual (this.rotation, 0.0)) { return true; } return false; } IsEqual (rhs) { if (this.name !== rhs.name) { return false; } if (this.mimeType !== rhs.mimeType) { return false; } if (!CoordIsEqual2D (this.offset, rhs.offset)) { return false; } if (!CoordIsEqual2D (this.scale, rhs.scale)) { return false; } if (!IsEqual (this.rotation, rhs.rotation)) { return false; } return true; } } function TextureMapIsEqual (aTex, bTex) { if (aTex === null && bTex === null) { return true; } else if (aTex === null || bTex === null) { return false; } return aTex.IsEqual (bTex); } const MaterialType = { Phong : 1, Physical : 2 }; class MaterialBase { constructor (type) { this.type = type; this.isDefault = false; this.name = ''; this.color = new RGBColor (0, 0, 0); this.vertexColors = false; } IsEqual (rhs) { if (this.type !== rhs.type) { return false; } if (this.isDefault !== rhs.isDefault) { return false; } if (this.name !== rhs.name) { return false; } if (!RGBColorIsEqual (this.color, rhs.color)) { return false; } if (this.vertexColors !== rhs.vertexColors) { return false; } return true; } } class FaceMaterial extends MaterialBase { constructor (type) { super (type); this.emissive = new RGBColor (0, 0, 0); this.opacity = 1.0; // 0.0 .. 1.0 this.transparent = false; this.diffuseMap = null; this.bumpMap = null; this.normalMap = null; this.emissiveMap = null; this.alphaTest = 0.0; // 0.0 .. 1.0 this.multiplyDiffuseMap = false; } IsEqual (rhs) { if (!super.IsEqual (rhs)) { return false; } if (!RGBColorIsEqual (this.emissive, rhs.emissive)) { return false; } if (!IsEqual (this.opacity, rhs.opacity)) { return false; } if (this.transparent !== rhs.transparent) { return false; } if (!TextureMapIsEqual (this.diffuseMap, rhs.diffuseMap)) { return false; } if (!TextureMapIsEqual (this.bumpMap, rhs.bumpMap)) { return false; } if (!TextureMapIsEqual (this.normalMap, rhs.normalMap)) { return false; } if (!TextureMapIsEqual (this.emissiveMap, rhs.emissiveMap)) { return false; } if (!IsEqual (this.alphaTest, rhs.alphaTest)) { return false; } if (this.multiplyDiffuseMap !== rhs.multiplyDiffuseMap) { return false; } return true; } } class PhongMaterial extends FaceMaterial { constructor () { super (MaterialType.Phong); this.ambient = new RGBColor (0, 0, 0); this.specular = new RGBColor (0, 0, 0); this.shininess = 0.0; // 0.0 .. 1.0 this.specularMap = null; } IsEqual (rhs) { if (!super.IsEqual (rhs)) { return false; } if (!RGBColorIsEqual (this.ambient, rhs.ambient)) { return false; } if (!RGBColorIsEqual (this.specular, rhs.specular)) { return false; } if (!IsEqual (this.shininess, rhs.shininess)) { return false; } if (!TextureMapIsEqual (this.specularMap, rhs.specularMap)) { return false; } return true; } } class PhysicalMaterial extends FaceMaterial { constructor () { super (MaterialType.Physical); this.metalness = 0.0; // 0.0 .. 1.0 this.roughness = 1.0; // 0.0 .. 1.0 this.metalnessMap = null; } IsEqual (rhs) { if (!super.IsEqual (rhs)) { return false; } if (!IsEqual (this.metalness, rhs.metalness)) { return false; } if (!IsEqual (this.roughness, rhs.roughness)) { return false; } if (!TextureMapIsEqual (this.metalnessMap, rhs.metalnessMap)) { return false; } return true; } } function TextureIsEqual (a, b) { if (a.name !== b.name) { return false; } if (a.mimeType !== b.mimeType) { return false; } if (!CoordIsEqual2D (a.offset, b.offset)) { return false; } if (!CoordIsEqual2D (a.scale, b.scale)) { return false; } if (!IsEqual (a.rotation, b.rotation)) { return false; } return true; } class Coord3D { constructor (x, y, z) { this.x = x; this.y = y; this.z = z; } Length () { return Math.sqrt (this.x * this.x + this.y * this.y + this.z * this.z); } MultiplyScalar (scalar) { this.x *= scalar; this.y *= scalar; this.z *= scalar; return this; } Normalize () { let length = this.Length (); if (length > 0.0) { this.MultiplyScalar (1.0 / length); } return this; } Offset (direction, distance) { let normal = direction.Clone ().Normalize (); this.x += normal.x * distance; this.y += normal.y * distance; this.z += normal.z * distance; return this; } Rotate (axis, angle, origo) { let normal = axis.Clone ().Normalize (); let u = normal.x; let v = normal.y; let w = normal.z; let x = this.x - origo.x; let y = this.y - origo.y; let z = this.z - origo.z; let si = Math.sin (angle); let co = Math.cos (angle); this.x = - u * (- u * x - v * y - w * z) * (1.0 - co) + x * co + (- w * y + v * z) * si; this.y = - v * (- u * x - v * y - w * z) * (1.0 - co) + y * co + (w * x - u * z) * si; this.z = - w * (- u * x - v * y - w * z) * (1.0 - co) + z * co + (- v * x + u * y) * si; this.x += origo.x; this.y += origo.y; this.z += origo.z; return this; } Clone () { return new Coord3D (this.x, this.y, this.z); } } function CoordIsEqual3D (a, b) { return IsEqual (a.x, b.x) && IsEqual (a.y, b.y) && IsEqual (a.z, b.z); } function AddCoord3D (a, b) { return new Coord3D (a.x + b.x, a.y + b.y, a.z + b.z); } function SubCoord3D (a, b) { return new Coord3D (a.x - b.x, a.y - b.y, a.z - b.z); } function CoordDistance3D (a, b) { return Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z)); } function DotVector3D (a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; } function VectorAngle3D (a, b) { let aDirection = a.Clone ().Normalize (); let bDirection = b.Clone ().Normalize (); if (CoordIsEqual3D (aDirection, bDirection)) { return 0.0; } let product = DotVector3D (aDirection, bDirection); return Math.acos (product); } function CrossVector3D (a, b) { let result = new Coord3D (0.0, 0.0, 0.0); result.x = a.y * b.z - a.z * b.y; result.y = a.z * b.x - a.x * b.z; result.z = a.x * b.y - a.y * b.x; return result; } function VectorLength3D (x, y, z) { return Math.sqrt (x * x + y * y + z * z); } function ArrayToCoord3D (arr) { return new Coord3D (arr[0], arr[1], arr[2]); } class MeshPrimitiveBuffer { constructor () { this.indices = []; this.vertices = []; this.colors = []; this.normals = []; this.uvs = []; this.material = null; } GetBounds () { let min = [Infinity, Infinity, Infinity]; let max = [-Infinity, -Infinity, -Infinity]; for (let i = 0; i < this.vertices.length / 3; i++) { for (let j = 0; j < 3; j++) { min[j] = Math.min (min[j], this.vertices[i * 3 + j]); max[j] = Math.max (max[j], this.vertices[i * 3 + j]); } } return { min : min, max : max }; } GetByteLength (indexTypeSize, numberTypeSize) { let indexCount = this.indices.length; let numberCount = this.vertices.length + this.colors.length + this.normals.length + this.uvs.length; return indexCount * indexTypeSize + numberCount * numberTypeSize; } } class MeshBuffer { constructor () { this.primitives = []; } PrimitiveCount () { return this.primitives.length; } GetPrimitive (index) { return this.primitives[index]; } GetByteLength (indexTypeSize, numberTypeSize) { let byteLength = 0; for (let i = 0; i < this.primitives.length; i++) { let primitive = this.primitives[i]; byteLength += primitive.GetByteLength (indexTypeSize, numberTypeSize); } return byteLength; } } function ConvertMeshToMeshBuffer (mesh) { function AddVertexToPrimitiveBuffer (mesh, indices, primitiveBuffer, meshVertexToPrimitiveVertices) { function GetColorOrDefault (mesh, colorIndex, forceColors) { if (colorIndex !== null) { return mesh.GetVertexColor (colorIndex); } else if (forceColors) { return new RGBColor (0, 0, 0); } else { return null; } } function GetUVOrDefault (mesh, uvIndex, forceUVs) { if (uvIndex !== null) { return mesh.GetTextureUV (uvIndex); } else if (forceUVs) { return new Coord2D (0.0, 0.0); } else { return null; } } function AddVertex (mesh, indices, primitiveBuffer) { let forceColors = mesh.VertexColorCount () > 0; let forceUVs = mesh.TextureUVCount () > 0; let vertex = mesh.GetVertex (indices.vertex); let normal = mesh.GetNormal (indices.normal); let primitiveVertexIndex = primitiveBuffer.vertices.length / 3; primitiveBuffer.indices.push (primitiveVertexIndex); primitiveBuffer.vertices.push (vertex.x, vertex.y, vertex.z); let color = GetColorOrDefault (mesh, indices.color, forceColors); if (color !== null) { primitiveBuffer.colors.push (color.r / 255.0, color.g / 255.0, color.b / 255.0); } primitiveBuffer.normals.push (normal.x, normal.y, normal.z); let uv = GetUVOrDefault (mesh, indices.uv, forceUVs); if (uv !== null) { primitiveBuffer.uvs.push (uv.x, uv.y); } return { index : primitiveVertexIndex, color : color, normal : normal, uv : uv }; } function FindMatchingPrimitiveVertex (mesh, primitiveVertices, indices) { function IsEqualColor (mesh, colorIndex, existingColor) { if (existingColor === null && colorIndex === null) { return true; } let color = GetColorOrDefault (mesh, colorIndex, true); return RGBColorIsEqual (existingColor, color); } function IsEqualNormal (mesh, normalIndex, existingNormal) { let normal = mesh.GetNormal (normalIndex); return CoordIsEqual3D (existingNormal, normal); } function IsEqualUV (mesh, uvIndex, existingUv) { if (existingUv === null && uvIndex === null) { return true; } let uv = GetUVOrDefault (mesh, uvIndex, true); return CoordIsEqual2D (existingUv, uv); } for (let i = 0; i < primitiveVertices.length; i++) { let primitiveVertex = primitiveVertices[i]; let equalColor = IsEqualColor (mesh, indices.color, primitiveVertex.color); let equalNormal = IsEqualNormal (mesh, indices.normal, primitiveVertex.normal); let equalUv = IsEqualUV (mesh, indices.uv, primitiveVertex.uv); if (equalColor && equalNormal && equalUv) { return primitiveVertex; } } return null; } if (meshVertexToPrimitiveVertices.has (indices.vertex)) { let primitiveVertices = meshVertexToPrimitiveVertices.get (indices.vertex); let existingPrimitiveVertex = FindMatchingPrimitiveVertex (mesh, primitiveVertices, indices); if (existingPrimitiveVertex !== null) { primitiveBuffer.indices.push (existingPrimitiveVertex.index); } else { let primitiveVertex = AddVertex (mesh, indices, primitiveBuffer); primitiveVertices.push (primitiveVertex); } } else { let primitiveVertex = AddVertex (mesh, indices, primitiveBuffer); meshVertexToPrimitiveVertices.set (indices.vertex, [primitiveVertex]); } } let meshBuffer = new MeshBuffer (); let triangleCount = mesh.TriangleCount (); if (triangleCount === 0) { return null; } let triangleIndices = []; for (let i = 0; i < triangleCount; i++) { triangleIndices.push (i); } triangleIndices.sort ((a, b) => { let aTriangle = mesh.GetTriangle (a); let bTriangle = mesh.GetTriangle (b); return aTriangle.mat - bTriangle.mat; }); let primitiveBuffer = null; let meshVertexToPrimitiveVertices = null; for (let i = 0; i < triangleIndices.length; i++) { let triangleIndex = triangleIndices[i]; let triangle = mesh.GetTriangle (triangleIndex); if (primitiveBuffer === null || primitiveBuffer.material !== triangle.mat) { primitiveBuffer = new MeshPrimitiveBuffer (); primitiveBuffer.material = triangle.mat; meshVertexToPrimitiveVertices = new Map (); meshBuffer.primitives.push (primitiveBuffer); } let v0Indices = { vertex : triangle.v0, color : triangle.c0, normal : triangle.n0, uv : triangle.u0 }; let v1Indices = { vertex : triangle.v1, color : triangle.c1, normal : triangle.n1, uv : triangle.u1 }; let v2Indices = { vertex : triangle.v2, color : triangle.c2, normal : triangle.n2, uv : triangle.u2 }; AddVertexToPrimitiveBuffer (mesh, v0Indices, primitiveBuffer, meshVertexToPrimitiveVertices); AddVertexToPrimitiveBuffer (mesh, v1Indices, primitiveBuffer, meshVertexToPrimitiveVertices); AddVertexToPrimitiveBuffer (mesh, v2Indices, primitiveBuffer, meshVertexToPrimitiveVertices); } return meshBuffer; } function ArrayBufferToUtf8String (buffer) { let decoder = new TextDecoder ('utf-8'); return decoder.decode (buffer); } function ArrayBufferToAsciiString (buffer) { let text = ''; let bufferView = new Uint8Array (buffer); for (let i = 0; i < bufferView.byteLength; i++) { text += String.fromCharCode (bufferView[i]); } return text; } function AsciiStringToArrayBuffer (str) { let buffer = new ArrayBuffer (str.length); let bufferView = new Uint8Array (buffer); for (let i = 0; i < str.length; i++) { bufferView[i] = str.charCodeAt (i); } return buffer; } function Utf8StringToArrayBuffer (str) { let encoder = new TextEncoder (); let uint8Array = encoder.encode (str); return uint8Array.buffer; } function Base64DataURIToArrayBuffer (uri) { let dataPrefix = 'data:'; if (!uri.startsWith (dataPrefix)) { return null; } let mimeSeparator = uri.indexOf (';'); if (mimeSeparator === -1) { return null; } let bufferSeparator = uri.indexOf (','); if (bufferSeparator === -1) { return null; } let mimeType = uri.substring (dataPrefix.length, dataPrefix.length + mimeSeparator - 5); let base64String = atob (uri.substring (bufferSeparator + 1)); let buffer = new ArrayBuffer (base64String.length); let bufferView = new Uint8Array (buffer); for (let i = 0; i < base64String.length; i++) { bufferView[i] = base64String.charCodeAt (i); } return { mimeType : mimeType, buffer : buffer }; } function GetFileExtensionFromMimeType (mimeType) { if (mimeType === undefined || mimeType === null) { return ''; } let mimeParts = mimeType.split ('/'); if (mimeParts.length === 0) { return ''; } return mimeParts[mimeParts.length - 1]; } function CreateObjectUrl (content) { let blob = new Blob ([content]); let url = URL.createObjectURL (blob); return url; } function CreateObjectUrlWithMimeType (content, mimeType) { let blob = new Blob ([content], { type : mimeType }); let url = URL.createObjectURL (blob); return url; } function RevokeObjectUrl (url) { URL.revokeObjectURL (url); } class ExportedFile { constructor (name) { this.name = name; this.content = null; } GetName () { return this.name; } SetName (name) { this.name = name; } GetTextContent () { let text = ArrayBufferToUtf8String (this.content); return text; } GetBufferContent () { return this.content; } SetTextContent (content) { let buffer = Utf8StringToArrayBuffer (content); this.content = buffer; } SetBufferContent (content) { this.content = content; } } class ExporterBase { constructor () { } CanExport (format, extension) { return false; } Export (exporterModel, format, onFinish) { let files = []; this.ExportContent (exporterModel, format, files, () => { onFinish (files); }); } ExportContent (exporterModel, format, files, onFinish) { } GetExportedMaterialName (originalName) { return this.GetExportedName (originalName, 'Material'); } GetExportedMeshName (originalName) { return this.GetExportedName (originalName, 'Mesh'); } GetExportedName (originalName, defaultName) { if (originalName.length === 0) { return defaultName; } return originalName; } } class Exporter3dm extends ExporterBase { constructor () { super (); this.rhino = null; } CanExport (format, extension) { return format === FileFormat.Binary && extension === '3dm'; } ExportContent (exporterModel, format, files, onFinish) { if (this.rhino === null) { LoadExternalLibrary ('loaders/rhino3dm.min.js').then (() => { rhino3dm ().then ((rhino) => { this.rhino = rhino; this.ExportRhinoContent (exporterModel, files, onFinish); }); }).catch (() => { onFinish (); }); } else { this.ExportRhinoContent (exporterModel, files, onFinish); } } ExportRhinoContent (exporterModel, files, onFinish) { function ColorToRhinoColor (color) { return { r : color.r, g : color.g, b : color.b, a : 255 }; } let rhinoFile = new ExportedFile ('model.3dm'); files.push (rhinoFile); let rhinoDoc = new this.rhino.File3dm (); exporterModel.EnumerateTransformedMeshInstances ((mesh) => { let meshBuffer = ConvertMeshToMeshBuffer (mesh); for (let primitiveIndex = 0; primitiveIndex < meshBuffer.PrimitiveCount (); primitiveIndex++) { let primitive = meshBuffer.GetPrimitive (primitiveIndex); let threeJson = { data : { attributes : { position : { itemSize : 3, type : 'Float32Array', array : primitive.vertices }, normal : { itemSize : 3, type : 'Float32Array', array : primitive.normals } }, index : { type : 'Uint16Array', array : primitive.indices } } }; let material = exporterModel.GetMaterial (primitive.material); let rhinoMaterial = new this.rhino.Material (); rhinoMaterial.name = this.GetExportedMaterialName (material.name); if (material.type === MaterialType.Phong) { rhinoMaterial.ambientColor = ColorToRhinoColor (material.ambient); rhinoMaterial.specularColor = ColorToRhinoColor (material.specular); } rhinoMaterial.diffuseColor = ColorToRhinoColor (material.color); rhinoMaterial.transparency = 1.0 - material.opacity; let rhinoMaterialIndex = rhinoDoc.materials ().count (); rhinoDoc.materials ().add (rhinoMaterial); let rhinoMesh = new this.rhino.Mesh.createFromThreejsJSON (threeJson); let rhinoAttributes = new this.rhino.ObjectAttributes (); rhinoAttributes.name = this.GetExportedMeshName (mesh.GetName ()); rhinoAttributes.materialSource = this.rhino.ObjectMaterialSource.MaterialFromObject; rhinoAttributes.materialIndex = rhinoMaterialIndex; rhinoDoc.objects ().add (rhinoMesh, rhinoAttributes); } }); let writeOptions = new this.rhino.File3dmWriteOptions (); writeOptions.version = 6; let rhinoDocBuffer = rhinoDoc.toByteArray (writeOptions); rhinoFile.SetBufferContent (rhinoDocBuffer); onFinish (); } } const PropertyType = { Text : 1, Integer : 2, Number : 3, Boolean : 4, Percent : 5, Color : 6 }; class Property { constructor (type, name, value) { this.type = type; this.name = name; this.value = value; } Clone () { const clonable = (this.type === PropertyType.Color); if (clonable) { return new Property (this.type, this.name, this.value.Clone ()); } else { return new Property (this.type, this.name, this.value); } } } class PropertyGroup { constructor (name) { this.name = name; this.properties = []; } PropertyCount () { return this.properties.length; } AddProperty (property) { this.properties.push (property); } GetProperty (index) { return this.properties[index]; } Clone () { let cloned = new PropertyGroup (this.name); for (let property of this.properties) { cloned.AddProperty (property.Clone ()); } return cloned; } } function PropertyToString (property) { if (property.type === PropertyType.Text) { return EscapeHtmlChars (property.value); } else if (property.type === PropertyType.Integer) { return property.value.toLocaleString (); } else if (property.type === PropertyType.Number) { return property.value.toLocaleString (undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } else if (property.type === PropertyType.Boolean) { return property.value ? 'True' : 'False'; } else if (property.type === PropertyType.Percent) { return parseInt (property.value * 100, 10).toString () + '%'; } else if (property.type === PropertyType.Color) { return '#' + RGBColorToHexString (property.value); } return null; } function GenerateGuid () { // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid let template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'; return template.replace (/[xy]/g, (c) => { let r = Math.random () * 16 | 0; let v = (c === 'x') ? r : (r & 0x3 | 0x8); return v.toString (16); }); } class ExporterBim extends ExporterBase { constructor () { super (); } CanExport (format, extension) { return format === FileFormat.Text && extension === 'bim'; } ExportContent (exporterModel, format, files, onFinish) { let bimContent = { schema_version : '1.1.0', meshes : [], elements : [], info : {} }; this.ExportProperties (exporterModel.GetModel (), bimContent.info); let meshId = 0; exporterModel.EnumerateTransformedMeshInstances ((mesh) => { let bimMesh = { mesh_id : meshId, coordinates : [], indices : [] }; mesh.EnumerateVertices ((vertex) => { bimMesh.coordinates.push (vertex.x, vertex.y, vertex.z); }); mesh.EnumerateTriangleVertexIndices ((v0, v1, v2) => { bimMesh.indices.push (v0, v1, v2); }); let bimElement = { mesh_id : meshId, type : 'Other', color : { r : 200, g : 200, b : 200, a : 255 }, vector : { x : 0.0, y : 0.0, z : 0.0 }, rotation : { qx: 0.0, qy: 0.0, qz: 0.0, qw: 1.0 }, guid : GenerateGuid (), info : {} }; let defaultColor = null; let hasOnlyOneColor = true; let faceColors = []; for (let i = 0; i < mesh.TriangleCount (); i++) { let triangle = mesh.GetTriangle (i); let material = exporterModel.GetMaterial (triangle.mat); let faceColor = { r : material.color.r, g : material.color.g, b : material.color.b, a : ColorComponentFromFloat (material.opacity), }; faceColors.push (faceColor.r, faceColor.g, faceColor.b, faceColor.a); if (hasOnlyOneColor) { if (defaultColor === null) { defaultColor = faceColor; } else { if (defaultColor.r !== faceColor.r || defaultColor.g !== faceColor.g || defaultColor.b !== faceColor.b || defaultColor.a !== faceColor.a) { hasOnlyOneColor = false; defaultColor = null; } } } } if (hasOnlyOneColor) { bimElement.color = defaultColor; } else { bimElement.face_colors = faceColors; } bimElement.info['Name'] = mesh.GetName (); this.ExportProperties (mesh, bimElement.info); bimContent.meshes.push (bimMesh); bimContent.elements.push (bimElement); meshId += 1; }); let bimFile = new ExportedFile ('model.bim'); bimFile.SetTextContent (JSON.stringify (bimContent, null, 4)); files.push (bimFile); onFinish (); } ExportProperties (element, targetObject) { for (let groupIndex = 0; groupIndex < element.PropertyGroupCount (); groupIndex++) { let group = element.GetPropertyGroup (groupIndex); for (let propertyIndex = 0; propertyIndex < group.PropertyCount (); propertyIndex++) { let property = group.GetProperty (propertyIndex); targetObject[property.name] = PropertyToString (property); } } } } class BinaryWriter { constructor (byteLength, isLittleEndian) { this.arrayBuffer = new ArrayBuffer (byteLength); this.dataView = new DataView (this.arrayBuffer); this.isLittleEndian = isLittleEndian; this.position = 0; } GetPosition () { return this.position; } SetPosition (position) { this.position = position; } End () { return this.position >= this.arrayBuffer.byteLength; } GetBuffer () { return this.arrayBuffer; } WriteArrayBuffer (arrayBuffer) { let bufferView = new Uint8Array (arrayBuffer); let thisBufferView = new Uint8Array (this.arrayBuffer); thisBufferView.set (bufferView, this.position); this.position += arrayBuffer.byteLength; } WriteBoolean8 (val) { this.dataView.setInt8 (this.position, val ? 1 : 0); this.position = this.position + 1; } WriteCharacter8 (val) { this.dataView.setInt8 (this.position, val); this.position = this.position + 1; } WriteUnsignedCharacter8 (val) { this.dataView.setUint8 (this.position, val); this.position = this.position + 1; } WriteInteger16 (val) { this.dataView.setInt16 (this.position, val, this.isLittleEndian); this.position = this.position + 2; } WriteUnsignedInteger16 (val) { this.dataView.setUint16 (this.position, val, this.isLittleEndian); this.position = this.position + 2; } WriteInteger32 (val) { this.dataView.setInt32 (this.position, val, this.isLittleEndian); this.position = this.position + 4; } WriteUnsignedInteger32 (val) { this.dataView.setUint32 (this.position, val, this.isLittleEndian); this.position = this.position + 4; } WriteFloat32 (val) { this.dataView.setFloat32 (this.position, val, this.isLittleEndian); this.position = this.position + 4; } WriteDouble64 (val) { this.dataView.setFloat64 (this.position, val, this.isLittleEndian); this.position = this.position + 8; } } class Coord4D { constructor (x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; } Clone () { return new Coord4D (this.x, this.y, this.z, this.w); } } class Quaternion { constructor (x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; } } function QuaternionIsEqual (a, b) { return IsEqual (a.x, b.x) && IsEqual (a.y, b.y) && IsEqual (a.z, b.z) && IsEqual (a.w, b.w); } function ArrayToQuaternion (arr) { return new Quaternion (arr[0], arr[1], arr[2], arr[3]); } function QuaternionFromAxisAngle (axis, angle) { const a = angle / 2.0; const s = Math.sin (a); return new Quaternion ( axis.x * s, axis.y * s, axis.z * s, Math.cos (a) ); } function QuaternionFromXYZ (x, y, z, mode) { const c1 = Math.cos (x / 2.0); const c2 = Math.cos (y / 2.0); const c3 = Math.cos (z / 2.0); const s1 = Math.sin (x / 2.0); const s2 = Math.sin (y / 2.0); const s3 = Math.sin (z / 2.0); let quaternion = new Quaternion (0.0, 0.0, 0.0, 1.0); if (mode === 'XYZ') { quaternion.x = s1 * c2 * c3 + c1 * s2 * s3; quaternion.y = c1 * s2 * c3 - s1 * c2 * s3; quaternion.z = c1 * c2 * s3 + s1 * s2 * c3; quaternion.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (mode === 'YXZ') { quaternion.x = s1 * c2 * c3 + c1 * s2 * s3; quaternion.y = c1 * s2 * c3 - s1 * c2 * s3; quaternion.z = c1 * c2 * s3 - s1 * s2 * c3; quaternion.w = c1 * c2 * c3 + s1 * s2 * s3; } else if (mode === 'ZXY') { quaternion.x = s1 * c2 * c3 - c1 * s2 * s3; quaternion.y = c1 * s2 * c3 + s1 * c2 * s3; quaternion.z = c1 * c2 * s3 + s1 * s2 * c3; quaternion.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (mode === 'ZYX') { quaternion.x = s1 * c2 * c3 - c1 * s2 * s3; quaternion.y = c1 * s2 * c3 + s1 * c2 * s3; quaternion.z = c1 * c2 * s3 - s1 * s2 * c3; quaternion.w = c1 * c2 * c3 + s1 * s2 * s3; } else if (mode === 'YZX') { quaternion.x = s1 * c2 * c3 + c1 * s2 * s3; quaternion.y = c1 * s2 * c3 + s1 * c2 * s3; quaternion.z = c1 * c2 * s3 - s1 * s2 * c3; quaternion.w = c1 * c2 * c3 - s1 * s2 * s3; } else if (mode === 'XZY') { quaternion.x = s1 * c2 * c3 - c1 * s2 * s3; quaternion.y = c1 * s2 * c3 - s1 * c2 * s3; quaternion.z = c1 * c2 * s3 + s1 * s2 * c3; quaternion.w = c1 * c2 * c3 + s1 * s2 * s3; } else { return null; } return quaternion; } class Matrix { constructor (matrix) { this.matrix = null; if (matrix !== undefined && matrix !== null) { this.matrix = matrix; } } IsValid () { return this.matrix !== null; } Set (matrix) { this.matrix = matrix; return this; } Get () { return this.matrix; } Clone () { let result = [ this.matrix[0], this.matrix[1], this.matrix[2], this.matrix[3], this.matrix[4], this.matrix[5], this.matrix[6], this.matrix[7], this.matrix[8], this.matrix[9], this.matrix[10], this.matrix[11], this.matrix[12], this.matrix[13], this.matrix[14], this.matrix[15] ]; return new Matrix (result); } CreateIdentity () { this.matrix = [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ]; return this; } IsIdentity () { let identity = new Matrix ().CreateIdentity ().Get (); for (let i = 0; i < 16; i++) { if (!IsEqual (this.matrix[i], identity[i])) { return false; } } return true; } CreateTranslation (x, y, z) { this.matrix = [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, x, y, z, 1.0 ]; return this; } CreateRotation (x, y, z, w) { let x2 = x + x; let y2 = y + y; let z2 = z + z; let xx = x * x2; let xy = x * y2; let xz = x * z2; let yy = y * y2; let yz = y * z2; let zz = z * z2; let wx = w * x2; let wy = w * y2; let wz = w * z2; this.matrix = [ 1.0 - (yy + zz), xy + wz, xz - wy, 0.0, xy - wz, 1.0 - (xx + zz), yz + wx, 0.0, xz + wy, yz - wx, 1.0 - (xx + yy), 0.0, 0.0, 0.0, 0.0, 1.0 ]; return this; } CreateRotationAxisAngle (axis, angle) { let quaternion = QuaternionFromAxisAngle (axis, angle); return this.CreateRotation (quaternion.x, quaternion.y, quaternion.z, quaternion.w); } CreateScale (x, y, z) { this.matrix = [ x, 0.0, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, 0.0, z, 0.0, 0.0, 0.0, 0.0, 1.0 ]; return this; } ComposeTRS (translation, rotation, scale) { let tx = translation.x; let ty = translation.y; let tz = translation.z; let qx = rotation.x; let qy = rotation.y; let qz = rotation.z; let qw = rotation.w; let sx = scale.x; let sy = scale.y; let sz = scale.z; let x2 = qx + qx; let y2 = qy + qy; let z2 = qz + qz; let xx = qx * x2; let xy = qx * y2; let xz = qx * z2; let yy = qy * y2; let yz = qy * z2; let zz = qz * z2; let wx = qw * x2; let wy = qw * y2; let wz = qw * z2; this.matrix = [ (1.0 - (yy + zz)) * sx, (xy + wz) * sx, (xz - wy) * sx, 0.0, (xy - wz) * sy, (1.0 - (xx + zz)) * sy, (yz + wx) * sy, 0.0, (xz + wy) * sz, (yz - wx) * sz, (1.0 - (xx + yy)) * sz, 0.0, tx, ty, tz, 1.0 ]; return this; } DecomposeTRS () { let translation = new Coord3D ( this.matrix[12], this.matr