acc-viewer
Version:
Acc Viewer
2,059 lines (1,803 loc) • 437 kB
JavaScript
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, '<').replace (/>/g, '>');
}
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