Add code for new version.

This commit is contained in:
Viktor Kovacs 2021-03-27 08:29:19 +01:00
parent 6eb0a65645
commit 9264c1dd6d
292 changed files with 28019 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
build/*
website_meta_data.txt
website_analytics_data.txt
website_script_data.txt
package-lock.json
node_modules
__pycache__

39
.jshintrc Normal file
View File

@ -0,0 +1,39 @@
{
"esversion" : 6,
"curly" : true,
"forin" : true,
"latedef" : true,
"nonew" : true,
"quotmark" : true,
"funcscope" : true,
"lastsemic" : true,
"loopfunc" : true,
"shadow" : false,
"undef" : true,
"unused" : false,
"bitwise" : false,
"freeze" : true,
"leanswitch" : true,
"eqeqeq" : true,
"futurehostile" : true,
"regexpu" : true,
"varstmt" : true,
"globals" : {
"OV" : true,
"THREE" : false,
"TextDecoder" : false,
"XMLHttpRequest" : false,
"FileReader" : false,
"Blob" : false,
"URL" : false,
"window" : false,
"document" : false,
"setTimeout" : false,
"requestAnimationFrame" : false,
"getComputedStyle" : false,
"alert" : false,
"atob" : false,
"console" : false,
"$" : false
}
}

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Viktor Kovacs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

41
README.md Normal file
View File

@ -0,0 +1,41 @@
# Online 3D Viewer
This repository contains the source code of the https://3dviewer.net website. The website can open several 3D file formats and visualize the model in your browser.
## Supported file formats
### Import
- obj (with mtl and texture)
- 3ds (with texture)
- stl (ascii and binary)
- ply (ascii and binary)
- gltf (ascii and binary)
- off (ascii only)
### Export
- obj (with mtl)
- stl (ascii and binary)
- ply (ascii and binary)
- off (ascii only)
## Features
- Load model:
- Select files from a file browser dialog
- Drag and drop files from your computer
- Specify files by web url
- Specify files by web url in hash parameters
- Explore model:
- Orbit, pan, zoom
- Set up direction
- Fit to window
- Investigate model:
- List used and missing files
- List all materials and meshes
- Show/hide and zoom to a specific mesh
- List materials used by a specific mesh
- Show model information (model size, vertex and polygon count)
- Export model to various format
- Embed viewer in your website

1
StartServer.bat Normal file
View File

@ -0,0 +1 @@
python -m http.server 8000

2
libs/jquery-3.5.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

20
libs/jquery.license.txt Normal file
View File

@ -0,0 +1,20 @@
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

21
libs/three.license.txt Normal file
View File

@ -0,0 +1,21 @@
The MIT License
Copyright © 2010-2021 three.js authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

2
libs/three.min-126.js Normal file

File diff suppressed because one or more lines are too long

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "online-3d-viewer",
"description": "Online 3D Viewer",
"version": "0.7.0",
"repository": "github:kovacsv/Online3DViewer",
"license": "MIT",
"devDependencies": {
"google-closure-compiler": "^20210302.0.0",
"jshint": "^2.12.0",
"mocha": "^8.3.2",
"svgo": "^2.2.2"
},
"scripts": {
"test": "mocha test",
"build": "python tools/create_package.py",
"update": "python tools/update_includes.py",
"svg": "python tools/optimize_svg_files.py"
}
}

4
source/core/core.js Normal file
View File

@ -0,0 +1,4 @@
OV =
{
};

41
source/core/taskrunner.js Normal file
View File

@ -0,0 +1,41 @@
OV.TaskRunner = class
{
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 ();
}
}
RunOnce ()
{
let obj = this;
setTimeout (function () {
obj.callbacks.runTask (obj.current, obj.TaskReady.bind (obj));
}, 0);
}
TaskReady ()
{
this.current += 1;
if (this.current < this.count) {
this.RunOnce ();
} else {
if (this.callbacks.onReady) {
this.callbacks.onReady ();
}
}
}
};

26
source/export/exporter.js Normal file
View File

@ -0,0 +1,26 @@
OV.Exporter = class
{
constructor ()
{
this.exporters = [
new OV.ExporterObj (),
new OV.ExporterStl (),
new OV.ExporterPly (),
new OV.ExporterOff (),
new OV.ExporterGltf ()
];
}
Export (model, format, extension, callbacks)
{
let files = [];
for (let i = 0; i < this.exporters.length; i++) {
let exporter = this.exporters[i];
if (exporter.CanExport (format, extension)) {
exporter.Export (model, format, files, callbacks);
break;
}
}
return files;
}
};

View File

@ -0,0 +1,81 @@
OV.ExportedFile = class
{
constructor (name)
{
this.name = name;
this.url = null;
this.content = null;
}
GetName ()
{
return this.name;
}
SetName (name)
{
this.name = name;
}
GetUrl ()
{
return this.url;
}
SetUrl (url)
{
this.url = url;
}
GetContent ()
{
return this.content;
}
SetContent (content)
{
this.content = content;
}
};
OV.ExporterBase = class
{
constructor ()
{
this.callbacks = null;
}
CanExport (format, extension)
{
return false;
}
Export (model, format, files, callbacks)
{
this.callbacks = callbacks;
this.ExportContent (model, format, files);
}
ExportContent (model, format, files)
{
}
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;
}
};

View File

@ -0,0 +1,404 @@
OV.ExporterGltf = class extends OV.ExporterBase
{
constructor ()
{
super ();
this.components = {
index : {
type : 5125, // unsigned int 32
size : 4
},
number : {
type : 5126, // float 32
size : 4
}
};
}
CanExport (format, extension)
{
return (format === OV.FileFormat.Text && extension === 'gltf') || (format === OV.FileFormat.Binary && extension === 'glb');
}
ExportContent (model, format, files)
{
if (format === OV.FileFormat.Text) {
this.ExportAsciiContent (model, files);
} else if (format === OV.FileFormat.Binary) {
this.ExportBinaryContent (model, files);
}
}
ExportAsciiContent (model, files)
{
let gltfFile = new OV.ExportedFile ('model.gltf');
let binFile = new OV.ExportedFile ('model.bin');
files.push (gltfFile);
files.push (binFile);
let meshDataArr = this.GetMeshData (model);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (meshDataArr);
mainJson.buffers.push ({
uri : binFile.GetName (),
byteLength : mainBuffer.byteLength
});
let fileNameToIndex = [];
this.ExportMaterials (model, mainJson, function (texture) {
let fileName = OV.GetFileName (texture.name);
let textureIndex = fileNameToIndex[fileName];
if (textureIndex === undefined) {
let textureFile = new OV.ExportedFile (fileName);
textureFile.SetUrl (texture.url);
files.push (textureFile);
textureIndex = mainJson.textures.length;
fileNameToIndex[fileName] = textureIndex;
mainJson.images.push ({
uri : fileName
});
mainJson.textures.push ({
source : textureIndex
});
}
return textureIndex;
});
gltfFile.SetContent (JSON.stringify (mainJson, null, 4));
binFile.SetContent (mainBuffer);
}
ExportBinaryContent (model, files)
{
let glbFile = new OV.ExportedFile ('model.glb');
files.push (glbFile);
let meshDataArr = this.GetMeshData (model);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (meshDataArr);
mainJson.buffers.push ({
byteLength : mainBuffer.byteLength
});
let textureBuffers = [];
let obj = this;
let fileNameToIndex = [];
this.ExportMaterials (model, mainJson, function (texture) {
let fileName = OV.GetFileName (texture.name);
let extension = OV.GetFileExtension (texture.name);
let textureIndex = fileNameToIndex[fileName];
if (textureIndex === undefined) {
let bufferIndex = mainJson.buffers.length;
let bufferViewIndex = mainJson.bufferViews.length;
textureIndex = mainJson.textures.length;
fileNameToIndex[fileName] = textureIndex;
let textureBuffer = obj.callbacks.getTextureBuffer (texture.name);
textureBuffers.push (textureBuffer);
mainJson.buffers.push ({
byteLength : textureBuffer.byteLength
});
mainJson.bufferViews.push ({
buffer : bufferIndex,
byteOffset : 0,
byteLength : textureBuffer.byteLength
});
mainJson.images.push ({
bufferView : bufferViewIndex,
mimeType : 'image/' + extension
});
mainJson.textures.push ({
source : textureIndex
});
}
return textureIndex;
});
let mainJsonString = JSON.stringify (mainJson, null, 4);
let glbSize = 12 + 8 + mainJsonString.length;
for (let i = 0; i < mainJson.buffers.length; i++) {
glbSize += 8 + mainJson.buffers[i].byteLength;
}
let glbWriter = new OV.BinaryWriter (glbSize, true);
glbWriter.WriteUnsignedInteger32 (0x46546C67);
glbWriter.WriteUnsignedInteger32 (2);
glbWriter.WriteUnsignedInteger32 (glbSize);
glbWriter.WriteUnsignedInteger32 (mainJsonString.length);
glbWriter.WriteUnsignedInteger32 (0x4E4F534A);
for (let i = 0; i < mainJsonString.length; i++) {
glbWriter.WriteUnsignedCharacter8 (mainJsonString.charCodeAt (i));
}
glbWriter.WriteUnsignedInteger32 (mainBuffer.byteLength);
glbWriter.WriteUnsignedInteger32 (0x004E4942);
glbWriter.WriteArrayBuffer (mainBuffer);
for (let i = 0; i < textureBuffers.length; i++) {
let textureBuffer = textureBuffers[i];
glbWriter.WriteUnsignedInteger32 (textureBuffer.byteLength);
glbWriter.WriteUnsignedInteger32 (0x004E4942);
glbWriter.WriteArrayBuffer (textureBuffer);
}
glbFile.SetContent (glbWriter.GetBuffer ());
}
GetMeshData (model)
{
let meshDataArr = [];
for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
let mesh = model.GetMesh (meshIndex);
let buffer = OV.ConvertMeshToMeshBuffer (mesh);
meshDataArr.push ({
name : mesh.GetName (),
buffer : buffer,
offsets : [],
sizes : []
});
}
return meshDataArr;
}
GetMainBuffer (meshDataArr)
{
let mainBufferSize = 0;
for (let meshIndex = 0; meshIndex < meshDataArr.length; meshIndex++) {
let meshData = meshDataArr[meshIndex];
mainBufferSize += meshData.buffer.GetByteLength (this.components.index.size, this.components.number.size);
}
let writer = new OV.BinaryWriter (mainBufferSize, true);
for (let meshIndex = 0; meshIndex < meshDataArr.length; meshIndex++) {
let meshData = meshDataArr[meshIndex];
let primitives = meshData.buffer.primitives;
for (let primitiveIndex = 0; primitiveIndex < primitives.length; primitiveIndex++) {
let primitive = primitives[primitiveIndex];
let offset = writer.GetPosition ();
for (let i = 0; i < primitive.indices.length; i++) {
writer.WriteUnsignedInteger32 (primitive.indices[i]);
}
for (let i = 0; i < primitive.vertices.length; i++) {
writer.WriteFloat32 (primitive.vertices[i]);
}
for (let i = 0; i < primitive.normals.length; i++) {
writer.WriteFloat32 (primitive.normals[i]);
}
for (let i = 0; i < primitive.uvs.length; i++) {
let texCoord = primitive.uvs[i];
if (i % 2 === 1) {
texCoord *= -1.0;
}
writer.WriteFloat32 (texCoord);
}
meshData.offsets.push (offset);
meshData.sizes.push (writer.GetPosition () - offset);
}
}
return writer.GetBuffer ();
}
GetMainJson (meshDataArr)
{
let mainJson = {
asset : {
version : '2.0'
},
scene : 0,
scenes : [
{
nodes : []
}
],
nodes : [],
materials : [],
meshes : [],
buffers : [],
bufferViews : [],
accessors : []
};
for (let meshIndex = 0; meshIndex < meshDataArr.length; meshIndex++) {
let meshData = meshDataArr[meshIndex];
mainJson.scenes[0].nodes.push (meshIndex);
mainJson.nodes.push ({
mesh : meshIndex
});
let jsonMesh = {
name : this.GetExportedMeshName (meshData.name),
primitives : []
};
let primitives = meshData.buffer.primitives;
for (let primitiveIndex = 0; primitiveIndex < primitives.length; primitiveIndex++) {
let primitive = primitives[primitiveIndex];
let bufferViewIndex = mainJson.bufferViews.length;
let accessorIndex = mainJson.accessors.length;
let accessorOffset = 0;
let jsonPrimitive = {
attributes : {
POSITION : accessorIndex + 1,
NORMAL : accessorIndex + 2
},
indices : accessorIndex,
mode : 4,
material : primitive.material
};
mainJson.bufferViews.push ({
buffer : 0,
byteOffset : meshData.offsets[primitiveIndex],
byteLength : meshData.sizes[primitiveIndex]
});
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
componentType : this.components.index.type,
count : primitive.indices.length,
type : 'SCALAR'
});
accessorOffset += primitive.indices.length * this.components.index.size;
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
componentType : this.components.number.type,
count : primitive.vertices.length / 3,
type : 'VEC3'
});
accessorOffset += primitive.vertices.length * this.components.number.size;
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
componentType : this.components.number.type,
count : primitive.normals.length / 3,
type : 'VEC3'
});
accessorOffset += primitive.normals.length * this.components.number.size;
if (primitive.uvs.length > 0) {
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
componentType : this.components.number.type,
count : primitive.uvs.length / 2,
type : 'VEC2'
});
accessorOffset += primitive.uvs.length * this.components.number.size;
jsonPrimitive.attributes.TEXCOORD_0 = accessorIndex + 3;
}
jsonMesh.primitives.push (jsonPrimitive);
}
mainJson.meshes.push (jsonMesh);
}
return mainJson;
}
ExportMaterials (model, mainJson, addTexture)
{
function ExportMaterial (obj, mainJson, material, addTexture)
{
function ColorToRGBA (color, opacity)
{
return [
OV.SRGBToLinear (color.r / 255.0),
OV.SRGBToLinear (color.g / 255.0),
OV.SRGBToLinear (color.b / 255.0),
opacity
];
}
function ColorToRGB (color)
{
return [
OV.SRGBToLinear (color.r / 255.0),
OV.SRGBToLinear (color.g / 255.0),
OV.SRGBToLinear (color.b / 255.0)
];
}
function GetTextureParams (mainJson, texture, addTexture)
{
if (texture === null || texture.name === null || texture.url === null) {
return null;
}
if (mainJson.images === undefined) {
mainJson.images = [];
}
if (mainJson.textures === undefined) {
mainJson.textures = [];
}
let textureIndex = addTexture (texture);
let textureParams = {
index : textureIndex
};
if (texture.HasTransformation ()) {
textureParams.extensions = {
KHR_texture_transform : {
offset : [texture.offset.x, -texture.offset.y],
scale : [texture.scale.x, texture.scale.y],
rotation : -texture.rotation
}
};
}
return textureParams;
}
let jsonMaterial = {
name : obj.GetExportedMaterialName (material.name),
pbrMetallicRoughness : {
baseColorFactor : ColorToRGBA (material.diffuse, material.opacity),
metallicFactor : 0.0,
roughnessFactor : 1.0
},
emissiveFactor : ColorToRGB (material.emissive),
alphaMode : 'OPAQUE',
alphaCutoff : material.alphaTest
};
if (material.transparent) {
// TODO: mask?
jsonMaterial.alphaMode = 'BLEND';
}
let baseColorTexture = GetTextureParams (mainJson, material.diffuseMap, addTexture);
if (baseColorTexture !== null) {
if (!material.multiplyDiffuseMap) {
jsonMaterial.pbrMetallicRoughness.baseColorFactor = ColorToRGBA (new OV.Color (255, 255, 255), material.opacity);
}
jsonMaterial.pbrMetallicRoughness.baseColorTexture = baseColorTexture;
}
let normalTexture = GetTextureParams (mainJson, material.normalMap, addTexture);
if (normalTexture !== null) {
jsonMaterial.normalTexture = normalTexture;
}
let emissiveTexture = GetTextureParams (mainJson, material.emissiveMap, addTexture);
if (emissiveTexture !== null) {
jsonMaterial.emissiveTexture = emissiveTexture;
}
mainJson.materials.push (jsonMaterial);
}
for (let materialIndex = 0; materialIndex < model.MaterialCount (); materialIndex++) {
let material = model.GetMaterial (materialIndex);
ExportMaterial (this, mainJson, material, addTexture);
}
}
};

View File

@ -0,0 +1,107 @@
OV.ExporterObj = class extends OV.ExporterBase
{
constructor ()
{
super ();
}
CanExport (format, extension)
{
return format === OV.FileFormat.Text && extension === 'obj';
}
ExportContent (model, format, files)
{
function WriteTexture (mtlWriter, keyword, texture, files)
{
if (texture !== null && texture.name !== null && texture.url !== null) {
let fileName = OV.GetFileName (texture.name);
mtlWriter.WriteArrayLine ([keyword, fileName]);
let fileIndex = files.findIndex (function (file) {
return file.GetName () === fileName;
});
if (fileIndex === -1) {
let textureFile = new OV.ExportedFile (fileName);
textureFile.SetUrl (texture.url);
files.push (textureFile);
}
}
}
let mtlFile = new OV.ExportedFile ('model.mtl');
let objFile = new OV.ExportedFile ('model.obj');
files.push (mtlFile);
files.push (objFile);
let mtlWriter = new OV.TextWriter ();
for (let materialIndex = 0; materialIndex < model.MaterialCount (); materialIndex++) {
let material = model.GetMaterial (materialIndex);
mtlWriter.WriteArrayLine (['newmtl', this.GetExportedMaterialName (material.name)]);
mtlWriter.WriteArrayLine (['Ka', material.ambient.r / 255.0, material.ambient.g / 255.0, material.ambient.b / 255.0]);
mtlWriter.WriteArrayLine (['Kd', material.diffuse.r / 255.0, material.diffuse.g / 255.0, material.diffuse.b / 255.0]);
mtlWriter.WriteArrayLine (['Ks', material.specular.r / 255.0, material.specular.g / 255.0, material.specular.b / 255.0]);
mtlWriter.WriteArrayLine (['Ns', material.shininess * 100.0]);
mtlWriter.WriteArrayLine (['d', material.opacity]);
WriteTexture (mtlWriter, 'map_Kd', material.diffuseMap, files);
WriteTexture (mtlWriter, 'map_Ks', material.specularMap, files);
WriteTexture (mtlWriter, 'bump', material.bumpMap, files);
}
mtlFile.SetContent (mtlWriter.GetText ());
let objWriter = new OV.TextWriter ();
objWriter.WriteArrayLine (['mtllib', mtlFile.GetName ()]);
let vertexOffset = 0;
let normalOffset = 0;
let uvOffset = 0;
let usedMaterialName = null;
for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
let mesh = model.GetMesh (meshIndex);
objWriter.WriteArrayLine (['g', this.GetExportedMeshName (mesh.GetName ())]);
for (let vertexIndex = 0; vertexIndex < mesh.VertexCount (); vertexIndex++) {
let vertex = mesh.GetVertex (vertexIndex);
objWriter.WriteArrayLine (['v', vertex.x, vertex.y, vertex.z]);
}
for (let normalIndex = 0; normalIndex < mesh.NormalCount (); normalIndex++) {
let normal = mesh.GetNormal (normalIndex);
objWriter.WriteArrayLine (['vn', normal.x, normal.y, normal.z]);
}
for (let textureUVIndex = 0; textureUVIndex < mesh.TextureUVCount (); textureUVIndex++) {
let uv = mesh.GetTextureUV (textureUVIndex);
objWriter.WriteArrayLine (['vt', uv.x, uv.y]);
}
for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
let triangle = mesh.GetTriangle (triangleIndex);
let v0 = triangle.v0 + vertexOffset + 1;
let v1 = triangle.v1 + vertexOffset + 1;
let v2 = triangle.v2 + vertexOffset + 1;
let n0 = triangle.n0 + normalOffset + 1;
let n1 = triangle.n1 + normalOffset + 1;
let n2 = triangle.n2 + normalOffset + 1;
if (triangle.mat !== null) {
let material = model.GetMaterial (triangle.mat);
let materialName = this.GetExportedMaterialName (material.name);
if (materialName !== usedMaterialName) {
objWriter.WriteArrayLine (['usemtl', materialName]);
usedMaterialName = materialName;
}
}
let u0 = '';
let u1 = '';
let u2 = '';
if (triangle.HasTextureUVs ()) {
u0 = triangle.u0 + uvOffset + 1;
u1 = triangle.u1 + uvOffset + 1;
u2 = triangle.u2 + uvOffset + 1;
}
objWriter.WriteArrayLine (['f', [v0, u0, n0].join ('/'), [v1, u1, n1].join ('/'), [v2, u2, n2].join ('/')]);
}
vertexOffset += mesh.VertexCount ();
normalOffset += mesh.NormalCount ();
uvOffset += mesh.TextureUVCount ();
}
objFile.SetContent (objWriter.GetText ());
}
};

View File

@ -0,0 +1,33 @@
OV.ExporterOff = class extends OV.ExporterBase
{
constructor ()
{
super ();
}
CanExport (format, extension)
{
return format === OV.FileFormat.Text && extension === 'off';
}
ExportContent (model, format, files)
{
let offFile = new OV.ExportedFile ('model.off');
files.push (offFile);
let offWriter = new OV.TextWriter ();
offWriter.WriteLine ('OFF');
offWriter.WriteArrayLine ([model.VertexCount (), model.TriangleCount (), 0]);
OV.EnumerateModelVerticesAndTriangles (model, {
onVertex : function (x, y, z) {
offWriter.WriteArrayLine ([x, y, z]);
},
onTriangle : function (v0, v1, v2) {
offWriter.WriteArrayLine ([3, v0, v1, v2]);
}
});
offFile.SetContent (offWriter.GetText ());
}
};

View File

@ -0,0 +1,94 @@
OV.ExporterPly = class extends OV.ExporterBase
{
constructor ()
{
super ();
}
CanExport (format, extension)
{
return (format === OV.FileFormat.Text || format === OV.FileFormat.Binary) && extension === 'ply';
}
ExportContent (model, format, files)
{
if (format === OV.FileFormat.Text) {
this.ExportText (model, files);
} else {
this.ExportBinary (model, files);
}
}
ExportText (model, files)
{
let plyFile = new OV.ExportedFile ('model.ply');
files.push (plyFile);
let plyWriter = new OV.TextWriter ();
let vertexCount = model.VertexCount ();
let triangleCount = model.TriangleCount ();
let headerText = this.GetHeaderText ('ascii', vertexCount, triangleCount);
plyWriter.Write (headerText);
OV.EnumerateModelVerticesAndTriangles (model, {
onVertex : function (x, y, z) {
plyWriter.WriteArrayLine ([x, y, z]);
},
onTriangle : function (v0, v1, v2) {
plyWriter.WriteArrayLine ([3, v0, v1, v2]);
}
});
plyFile.SetContent (plyWriter.GetText ());
}
ExportBinary (model, files)
{
let plyFile = new OV.ExportedFile ('model.ply');
files.push (plyFile);
let vertexCount = model.VertexCount ();
let triangleCount = model.TriangleCount ();
let headerText = this.GetHeaderText ('binary_little_endian', vertexCount, triangleCount);
let fullByteLength = headerText.length + vertexCount * 3 * 4 + triangleCount * (1 + 3 * 4);
let plyWriter = new OV.BinaryWriter (fullByteLength, true);
for (let i = 0; i < headerText.length; i++) {
plyWriter.WriteUnsignedCharacter8 (headerText.charCodeAt (i));
}
OV.EnumerateModelVerticesAndTriangles (model, {
onVertex : function (x, y, z) {
plyWriter.WriteFloat32 (x);
plyWriter.WriteFloat32 (y);
plyWriter.WriteFloat32 (z);
},
onTriangle : function (v0, v1, v2) {
plyWriter.WriteUnsignedCharacter8 (3);
plyWriter.WriteInteger32 (v0);
plyWriter.WriteInteger32 (v1);
plyWriter.WriteInteger32 (v2);
}
});
plyFile.SetContent (plyWriter.GetBuffer ());
}
GetHeaderText (format, vertexCount, triangleCount)
{
let headerWriter = new OV.TextWriter ();
headerWriter.WriteLine ('ply');
headerWriter.WriteLine ('format ' + format + ' 1.0');
headerWriter.WriteLine ('element vertex ' + vertexCount);
headerWriter.WriteLine ('property float x');
headerWriter.WriteLine ('property float y');
headerWriter.WriteLine ('property float z');
headerWriter.WriteLine ('element face ' + triangleCount);
headerWriter.WriteLine ('property list uchar int vertex_index');
headerWriter.WriteLine ('end_header');
return headerWriter.GetText ();
}
};

View File

@ -0,0 +1,84 @@
OV.ExporterStl = class extends OV.ExporterBase
{
constructor ()
{
super ();
}
CanExport (format, extension)
{
return (format === OV.FileFormat.Text || format === OV.FileFormat.Binary) && extension === 'stl';
}
ExportContent (model, format, files)
{
if (format === OV.FileFormat.Text) {
this.ExportText (model, files);
} else {
this.ExportBinary (model, files);
}
}
ExportText (model, files)
{
let stlFile = new OV.ExportedFile ('model.stl');
files.push (stlFile);
let stlWriter = new OV.TextWriter ();
stlWriter.WriteLine ('solid Model');
OV.EnumerateModelTrianglesWithNormals (model, function (v0, v1, v2, normal) {
stlWriter.WriteArrayLine (['facet', 'normal', normal.x, normal.y, normal.z]);
stlWriter.Indent (1);
stlWriter.WriteLine ('outer loop');
stlWriter.Indent (1);
stlWriter.WriteArrayLine (['vertex', v0.x, v0.y, v0.z]);
stlWriter.WriteArrayLine (['vertex', v1.x, v1.y, v1.z]);
stlWriter.WriteArrayLine (['vertex', v2.x, v2.y, v2.z]);
stlWriter.Indent (-1);
stlWriter.WriteLine ('endloop');
stlWriter.Indent (-1);
stlWriter.WriteLine ('endfacet');
});
stlWriter.WriteLine ('endsolid Model');
stlFile.SetContent (stlWriter.GetText ());
}
ExportBinary (model, files)
{
let stlFile = new OV.ExportedFile ('model.stl');
files.push (stlFile);
let triangleCount = model.TriangleCount ();
let headerSize = 80;
let fullByteLength = headerSize + 4 + triangleCount * 50;
let stlWriter = new OV.BinaryWriter (fullByteLength, true);
for (let i = 0; i < headerSize; i++) {
stlWriter.WriteUnsignedCharacter8 (0);
}
stlWriter.WriteUnsignedInteger32 (triangleCount);
OV.EnumerateModelTrianglesWithNormals (model, function (v0, v1, v2, normal) {
stlWriter.WriteFloat32 (normal.x);
stlWriter.WriteFloat32 (normal.y);
stlWriter.WriteFloat32 (normal.z);
stlWriter.WriteFloat32 (v0.x);
stlWriter.WriteFloat32 (v0.y);
stlWriter.WriteFloat32 (v0.z);
stlWriter.WriteFloat32 (v1.x);
stlWriter.WriteFloat32 (v1.y);
stlWriter.WriteFloat32 (v1.z);
stlWriter.WriteFloat32 (v2.x);
stlWriter.WriteFloat32 (v2.y);
stlWriter.WriteFloat32 (v2.z);
stlWriter.WriteUnsignedInteger16 (0);
});
stlFile.SetContent (stlWriter.GetBuffer ());
}
};

View File

@ -0,0 +1,34 @@
OV.EnumerateModelVerticesAndTriangles = function (model, callbacks)
{
for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
let mesh = model.GetMesh (meshIndex);
for (let vertexIndex = 0; vertexIndex < mesh.VertexCount (); vertexIndex++) {
let vertex = mesh.GetVertex (vertexIndex);
callbacks.onVertex (vertex.x, vertex.y, vertex.z);
}
}
let vertexOffset = 0;
for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
let mesh = model.GetMesh (meshIndex);
for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
let triangle = mesh.GetTriangle (triangleIndex);
callbacks.onTriangle (triangle.v0 + vertexOffset, triangle.v1 + vertexOffset, triangle.v2 + vertexOffset);
}
vertexOffset += mesh.VertexCount ();
}
};
OV.EnumerateModelTrianglesWithNormals = function (model, onTriangle)
{
for (let meshIndex = 0; meshIndex < model.MeshCount (); meshIndex++) {
let mesh = model.GetMesh (meshIndex);
for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
let triangle = mesh.GetTriangle (triangleIndex);
let v0 = mesh.GetVertex (triangle.v0);
let v1 = mesh.GetVertex (triangle.v1);
let v2 = mesh.GetVertex (triangle.v2);
let normal = OV.CalculateTriangleNormal (v0, v1, v2);
onTriangle (v0, v1, v2, normal);
}
}
};

195
source/external/three.converter.js vendored Normal file
View File

@ -0,0 +1,195 @@
OV.ConvertModelToThreeMeshes = function (model, callbacks)
{
function CreateThreeMaterial (model, materialIndex)
{
function SetTextureParameters (texture, threeTexture)
{
threeTexture.wrapS = THREE.RepeatWrapping;
threeTexture.wrapT = THREE.RepeatWrapping;
threeTexture.rotation = texture.rotation;
threeTexture.offset.x = texture.offset.x;
threeTexture.offset.y = texture.offset.y;
threeTexture.repeat.x = texture.scale.x;
threeTexture.repeat.y = texture.scale.y;
}
function LoadTexture (threeMaterial, texture, onLoad)
{
if (texture === null || texture.url === null) {
return;
}
let loader = new THREE.TextureLoader ();
loader.load (texture.url, function (threeTexture) {
SetTextureParameters (texture, threeTexture);
threeTexture.image = OV.ResizeImageToPowerOfTwoSides (threeTexture.image);
threeMaterial.needsUpdate = true;
onLoad (threeTexture);
});
}
let material = model.GetMaterial (materialIndex);
let diffuseColor = new THREE.Color (material.diffuse.r / 255.0, material.diffuse.g / 255.0, material.diffuse.b / 255.0);
let specularColor = new THREE.Color (material.specular.r / 255.0, material.specular.g / 255.0, material.specular.b / 255.0);
let emissiveColor = new THREE.Color (material.emissive.r / 255.0, material.emissive.g / 255.0, material.emissive.b / 255.0);
if (OV.IsEqual (material.shininess, 0.0)) {
specularColor.setRGB (0.0, 0.0, 0.0);
}
let threeMaterial = new THREE.MeshPhongMaterial ({
color : diffuseColor,
specular : specularColor,
emissive : emissiveColor,
shininess : material.shininess * 10.0,
opacity : material.opacity,
transparent : material.transparent,
alphaTest : material.alphaTest,
side : THREE.DoubleSide
});
LoadTexture (threeMaterial, material.diffuseMap, function (threeTexture) {
if (!material.multiplyDiffuseMap) {
threeMaterial.color.setRGB (1.0, 1.0, 1.0);
}
threeMaterial.map = threeTexture;
callbacks.onTextureLoaded ();
});
LoadTexture (threeMaterial, material.specularMap, function (threeTexture) {
threeMaterial.specularMap = threeTexture;
callbacks.onTextureLoaded ();
});
LoadTexture (threeMaterial, material.bumpMap, function (threeTexture) {
threeMaterial.bumpMap = threeTexture;
callbacks.onTextureLoaded ();
});
LoadTexture (threeMaterial, material.normalMap, function (threeTexture) {
threeMaterial.normalMap = threeTexture;
callbacks.onTextureLoaded ();
});
LoadTexture (threeMaterial, material.emissiveMap, function (threeTexture) {
threeMaterial.emissiveMap = threeTexture;
callbacks.onTextureLoaded ();
});
return threeMaterial;
}
function CreateThreeMesh (model, meshIndex, modelThreeMaterials)
{
let mesh = model.GetMesh (meshIndex);
let triangleCount = mesh.TriangleCount ();
if (triangleCount === 0) {
return null;
}
let triangleIndices = [];
for (let i = 0; i < triangleCount; i++) {
triangleIndices.push (i);
}
triangleIndices.sort (function (a, b) {
let aTriangle = mesh.GetTriangle (a);
let bTriangle = mesh.GetTriangle (b);
return aTriangle.mat - bTriangle.mat;
});
let threeGeometry = new THREE.BufferGeometry ();
let meshThreeMaterials = [];
let meshOriginalMaterials = [];
let modelToThreeMaterials = {};
let vertices = [];
let normals = [];
let uvs = [];
let groups = [];
groups.push ({
start : 0,
end : -1
});
let meshHasUVs = mesh.TextureUVCount () > 0;
for (let i = 0; i < triangleIndices.length; i++) {
let triangleIndex = triangleIndices[i];
let triangle = mesh.GetTriangle (triangleIndex);
let v0 = mesh.GetVertex (triangle.v0);
let v1 = mesh.GetVertex (triangle.v1);
let v2 = mesh.GetVertex (triangle.v2);
vertices.push (v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z);
let n0 = mesh.GetNormal (triangle.n0);
let n1 = mesh.GetNormal (triangle.n1);
let n2 = mesh.GetNormal (triangle.n2);
normals.push (n0.x, n0.y, n0.z, n1.x, n1.y, n1.z, n2.x, n2.y, n2.z);
if (triangle.HasTextureUVs ()) {
let u0 = mesh.GetTextureUV (triangle.u0);
let u1 = mesh.GetTextureUV (triangle.u1);
let u2 = mesh.GetTextureUV (triangle.u2);
uvs.push (u0.x, u0.y, u1.x, u1.y, u2.x, u2.y);
} else if (meshHasUVs) {
uvs.push (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
}
let modelMaterialIndex = triangle.mat;
let materialIndex = modelToThreeMaterials[modelMaterialIndex];
if (materialIndex === undefined) {
materialIndex = meshThreeMaterials.length;
modelToThreeMaterials[modelMaterialIndex] = materialIndex;
meshThreeMaterials.push (modelThreeMaterials[modelMaterialIndex]);
meshOriginalMaterials.push (modelMaterialIndex);
if (i > 0) {
groups[groups.length - 1].end = i - 1;
groups.push ({
start : groups[groups.length - 1].end + 1,
end : -1
});
}
}
}
groups[groups.length - 1].end = triangleCount - 1;
threeGeometry.setAttribute ('position', new THREE.Float32BufferAttribute (vertices, 3));
threeGeometry.setAttribute ('normal', new THREE.Float32BufferAttribute (normals, 3));
if (uvs.length !== 0) {
threeGeometry.setAttribute ('uv', new THREE.Float32BufferAttribute (uvs, 2));
}
for (let i = 0; i < groups.length; i++) {
let group = groups[i];
threeGeometry.addGroup (group.start * 3, (group.end - group.start + 1) * 3, i);
}
let threeMesh = new THREE.Mesh (threeGeometry, meshThreeMaterials);
threeMesh.userData = {
originalMeshIndex : meshIndex,
originalMaterials : meshOriginalMaterials,
threeMaterials : null
};
return threeMesh;
}
let modelThreeMaterials = [];
for (let materialIndex = 0; materialIndex < model.MaterialCount (); materialIndex++) {
let threeMaterial = CreateThreeMaterial (model, materialIndex);
modelThreeMaterials.push (threeMaterial);
}
let threeMeshes = [];
let taskRunner = new OV.TaskRunner ();
taskRunner.Run (model.MeshCount (), {
runTask : function (index, ready) {
let mesh = model.GetMesh (index);
if (mesh.TriangleCount () === 0) {
ready ();
} else {
let threeMesh = CreateThreeMesh (model, index, modelThreeMaterials);
threeMeshes.push (threeMesh);
ready ();
}
},
onReady : function () {
callbacks.onModelLoaded (threeMeshes);
}
});
};

85
source/external/three.model.loader.js vendored Normal file
View File

@ -0,0 +1,85 @@
OV.ThreeModelLoader = class
{
constructor ()
{
this.importer = new OV.Importer ();
this.callbacks = null;
this.inProgress = false;
}
Init (callbacks)
{
this.callbacks = callbacks;
}
LoadFromUrlList (urls)
{
if (this.inProgress) {
return;
}
let obj = this;
this.inProgress = true;
this.callbacks.onLoadStart ();
this.importer.LoadFilesFromUrls (urls, function () {
obj.OnFilesLoaded ();
});
}
LoadFromFileList (files)
{
if (this.inProgress) {
return;
}
let obj = this;
this.inProgress = true;
this.callbacks.onLoadStart ();
this.importer.LoadFilesFromFileObjects (files, function () {
obj.OnFilesLoaded ();
});
}
OnFilesLoaded ()
{
let obj = this;
this.callbacks.onFilesLoaded ();
let taskRunner = new OV.TaskRunner ();
taskRunner.Run (1, {
runTask : function (index, ready) {
obj.importer.Import ({
success : function (importResult) {
obj.OnModelImported (importResult);
ready ();
},
error : function (importerError) {
obj.callbacks.onLoadError (importerError);
obj.inProgress = false;
ready ();
}
});
}
});
}
OnModelImported (importResult)
{
let obj = this;
this.callbacks.onModelImported ();
OV.ConvertModelToThreeMeshes (importResult.model, {
onTextureLoaded : function () {
obj.callbacks.onTextureLoaded ();
},
onModelLoaded : function (meshes) {
obj.callbacks.onModelFinished (importResult, meshes);
obj.inProgress = false;
}
});
}
GetImporter ()
{
return this.importer;
}
};

View File

@ -0,0 +1,33 @@
OV.Coord2D = class
{
constructor (x, y)
{
this.x = x;
this.y = y;
}
Clone ()
{
return new OV.Coord2D (this.x, this.y);
}
};
OV.CoordIsEqual2D = function (a, b)
{
return OV.IsEqual (a.x, b.x) && OV.IsEqual (a.y, b.y);
};
OV.AddCoord2D = function (a, b)
{
return new OV.Coord2D (a.x + b.x, a.y + b.y);
};
OV.SubCoord2D = function (a, b)
{
return new OV.Coord2D (a.x - b.x, a.y - b.y);
};
OV.CoordDistance2D = function (a, b)
{
return Math.sqrt ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
};

119
source/geometry/coord3d.js Normal file
View File

@ -0,0 +1,119 @@
OV.Coord3D = class
{
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 OV.Coord3D (this.x, this.y, this.z);
}
};
OV.CoordIsEqual3D = function (a, b)
{
return OV.IsEqual (a.x, b.x) && OV.IsEqual (a.y, b.y) && OV.IsEqual (a.z, b.z);
};
OV.AddCoord3D = function (a, b)
{
return new OV.Coord3D (a.x + b.x, a.y + b.y, a.z + b.z);
};
OV.SubCoord3D = function (a, b)
{
return new OV.Coord3D (a.x - b.x, a.y - b.y, a.z - b.z);
};
OV.CoordDistance3D = function (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));
};
OV.VectorAngle3D = function (a, b)
{
let aDirection = a.Clone ().Normalize ();
let bDirection = b.Clone ().Normalize ();
if (OV.CoordIsEqual3D (aDirection, bDirection)) {
return 0.0;
}
let product = OV.DotVector3D (aDirection, bDirection);
return Math.acos (product);
};
OV.DotVector3D = function (a, b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
};
OV.CrossVector3D = function (a, b)
{
let result = new OV.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;
};
OV.VectorLength3D = function (x, y, z)
{
return Math.sqrt (x * x + y * y + z * z);
};

View File

@ -0,0 +1,40 @@
OV.Eps = 0.00000001;
OV.RadDeg = 57.29577951308232;
OV.DegRad = 0.017453292519943;
OV.IsZero = function (a)
{
return Math.abs (a) < OV.Eps;
};
OV.IsLower = function (a, b)
{
return b - a > OV.Eps;
};
OV.IsGreater = function (a, b)
{
return a - b > OV.Eps;
};
OV.IsEqual = function (a, b)
{
return Math.abs (b - a) < OV.Eps;
};
OV.IsPositive = function (a)
{
return a > OV.Eps;
};
OV.IsNegative = function (a)
{
return a < -OV.Eps;
};
OV.Direction =
{
X : 1,
Y : 2,
Z : 3
};

396
source/geometry/matrix.js Normal file
View File

@ -0,0 +1,396 @@
OV.Matrix = class
{
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 OV.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 OV.Matrix ().CreateIdentity ().Get ();
for (let i = 0; i < 16; i++) {
if (!OV.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;
}
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[0];
let ty = translation[1];
let tz = translation[2];
let qx = rotation[0];
let qy = rotation[1];
let qz = rotation[2];
let qw = rotation[3];
let sx = scale[0];
let sy = scale[1];
let sz = scale[2];
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 = [
this.matrix[12],
this.matrix[13],
this.matrix[14]
];
let sx = OV.VectorLength3D (this.matrix[0], this.matrix[1], this.matrix[2]);
let sy = OV.VectorLength3D (this.matrix[4], this.matrix[5], this.matrix[6]);
let sz = OV.VectorLength3D (this.matrix[8], this.matrix[9], this.matrix[10]);
let determinant = this.Determinant ();
if (OV.IsNegative (determinant)) {
sx *= -1.0;
}
let scale = [sx, sy, sz];
let m00 = this.matrix[0] / sx;
let m01 = this.matrix[4] / sy;
let m02 = this.matrix[8] / sz;
let m10 = this.matrix[1] / sx;
let m11 = this.matrix[5] / sy;
let m12 = this.matrix[9] / sz;
let m20 = this.matrix[2] / sx;
let m21 = this.matrix[6] / sy;
let m22 = this.matrix[10] / sz;
// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
let rotation = null;
let tr = m00 + m11 + m22;
if (tr > 0.0) {
let s = Math.sqrt (tr + 1.0) * 2.0;
rotation = [
(m21 - m12) / s,
(m02 - m20) / s,
(m10 - m01) / s,
0.25 * s
];
} else if ((m00 > m11) && (m00 > m22)) {
let s = Math.sqrt (1.0 + m00 - m11 - m22) * 2.0;
rotation = [
0.25 * s,
(m01 + m10) / s,
(m02 + m20) / s,
(m21 - m12) / s
];
} else if (m11 > m22) {
let s = Math.sqrt (1.0 + m11 - m00 - m22) * 2.0;
rotation = [
(m01 + m10) / s,
0.25 * s,
(m12 + m21) / s,
(m02 - m20) / s
];
} else {
let s = Math.sqrt (1.0 + m22 - m00 - m11) * 2.0;
rotation = [
(m02 + m20) / s,
(m12 + m21) / s,
0.25 * s,
(m10 - m01) / s
];
}
return {
translation : translation,
rotation : rotation,
scale : scale
};
}
Determinant ()
{
let a00 = this.matrix[0];
let a01 = this.matrix[1];
let a02 = this.matrix[2];
let a03 = this.matrix[3];
let a10 = this.matrix[4];
let a11 = this.matrix[5];
let a12 = this.matrix[6];
let a13 = this.matrix[7];
let a20 = this.matrix[8];
let a21 = this.matrix[9];
let a22 = this.matrix[10];
let a23 = this.matrix[11];
let a30 = this.matrix[12];
let a31 = this.matrix[13];
let a32 = this.matrix[14];
let a33 = this.matrix[15];
let b00 = a00 * a11 - a01 * a10;
let b01 = a00 * a12 - a02 * a10;
let b02 = a00 * a13 - a03 * a10;
let b03 = a01 * a12 - a02 * a11;
let b04 = a01 * a13 - a03 * a11;
let b05 = a02 * a13 - a03 * a12;
let b06 = a20 * a31 - a21 * a30;
let b07 = a20 * a32 - a22 * a30;
let b08 = a20 * a33 - a23 * a30;
let b09 = a21 * a32 - a22 * a31;
let b10 = a21 * a33 - a23 * a31;
let b11 = a22 * a33 - a23 * a32;
let determinant = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
return determinant;
}
Invert ()
{
let a00 = this.matrix[0];
let a01 = this.matrix[1];
let a02 = this.matrix[2];
let a03 = this.matrix[3];
let a10 = this.matrix[4];
let a11 = this.matrix[5];
let a12 = this.matrix[6];
let a13 = this.matrix[7];
let a20 = this.matrix[8];
let a21 = this.matrix[9];
let a22 = this.matrix[10];
let a23 = this.matrix[11];
let a30 = this.matrix[12];
let a31 = this.matrix[13];
let a32 = this.matrix[14];
let a33 = this.matrix[15];
let b00 = a00 * a11 - a01 * a10;
let b01 = a00 * a12 - a02 * a10;
let b02 = a00 * a13 - a03 * a10;
let b03 = a01 * a12 - a02 * a11;
let b04 = a01 * a13 - a03 * a11;
let b05 = a02 * a13 - a03 * a12;
let b06 = a20 * a31 - a21 * a30;
let b07 = a20 * a32 - a22 * a30;
let b08 = a20 * a33 - a23 * a30;
let b09 = a21 * a32 - a22 * a31;
let b10 = a21 * a33 - a23 * a31;
let b11 = a22 * a33 - a23 * a32;
let determinant = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
if (OV.IsEqual (determinant, 0.0)) {
return null;
}
let result = [
(a11 * b11 - a12 * b10 + a13 * b09) / determinant,
(a02 * b10 - a01 * b11 - a03 * b09) / determinant,
(a31 * b05 - a32 * b04 + a33 * b03) / determinant,
(a22 * b04 - a21 * b05 - a23 * b03) / determinant,
(a12 * b08 - a10 * b11 - a13 * b07) / determinant,
(a00 * b11 - a02 * b08 + a03 * b07) / determinant,
(a32 * b02 - a30 * b05 - a33 * b01) / determinant,
(a20 * b05 - a22 * b02 + a23 * b01) / determinant,
(a10 * b10 - a11 * b08 + a13 * b06) / determinant,
(a01 * b08 - a00 * b10 - a03 * b06) / determinant,
(a30 * b04 - a31 * b02 + a33 * b00) / determinant,
(a21 * b02 - a20 * b04 - a23 * b00) / determinant,
(a11 * b07 - a10 * b09 - a12 * b06) / determinant,
(a00 * b09 - a01 * b07 + a02 * b06) / determinant,
(a31 * b01 - a30 * b03 - a32 * b00) / determinant,
(a20 * b03 - a21 * b01 + a22 * b00) / determinant
];
return new OV.Matrix (result);
}
MultiplyVector (vector)
{
let a00 = vector[0];
let a01 = vector[1];
let a02 = vector[2];
let a03 = vector[3];
let b00 = this.matrix[0];
let b01 = this.matrix[1];
let b02 = this.matrix[2];
let b03 = this.matrix[3];
let b10 = this.matrix[4];
let b11 = this.matrix[5];
let b12 = this.matrix[6];
let b13 = this.matrix[7];
let b20 = this.matrix[8];
let b21 = this.matrix[9];
let b22 = this.matrix[10];
let b23 = this.matrix[11];
let b30 = this.matrix[12];
let b31 = this.matrix[13];
let b32 = this.matrix[14];
let b33 = this.matrix[15];
let result = [
a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30,
a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31,
a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32,
a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33
];
return result;
}
MultiplyMatrix (matrix)
{
let a00 = this.matrix[0];
let a01 = this.matrix[1];
let a02 = this.matrix[2];
let a03 = this.matrix[3];
let a10 = this.matrix[4];
let a11 = this.matrix[5];
let a12 = this.matrix[6];
let a13 = this.matrix[7];
let a20 = this.matrix[8];
let a21 = this.matrix[9];
let a22 = this.matrix[10];
let a23 = this.matrix[11];
let a30 = this.matrix[12];
let a31 = this.matrix[13];
let a32 = this.matrix[14];
let a33 = this.matrix[15];
let b00 = matrix.matrix[0];
let b01 = matrix.matrix[1];
let b02 = matrix.matrix[2];
let b03 = matrix.matrix[3];
let b10 = matrix.matrix[4];
let b11 = matrix.matrix[5];
let b12 = matrix.matrix[6];
let b13 = matrix.matrix[7];
let b20 = matrix.matrix[8];
let b21 = matrix.matrix[9];
let b22 = matrix.matrix[10];
let b23 = matrix.matrix[11];
let b30 = matrix.matrix[12];
let b31 = matrix.matrix[13];
let b32 = matrix.matrix[14];
let b33 = matrix.matrix[15];
let result = [
a00 * b00 + a01 * b10 + a02 * b20 + a03 * b30,
a00 * b01 + a01 * b11 + a02 * b21 + a03 * b31,
a00 * b02 + a01 * b12 + a02 * b22 + a03 * b32,
a00 * b03 + a01 * b13 + a02 * b23 + a03 * b33,
a10 * b00 + a11 * b10 + a12 * b20 + a13 * b30,
a10 * b01 + a11 * b11 + a12 * b21 + a13 * b31,
a10 * b02 + a11 * b12 + a12 * b22 + a13 * b32,
a10 * b03 + a11 * b13 + a12 * b23 + a13 * b33,
a20 * b00 + a21 * b10 + a22 * b20 + a23 * b30,
a20 * b01 + a21 * b11 + a22 * b21 + a23 * b31,
a20 * b02 + a21 * b12 + a22 * b22 + a23 * b32,
a20 * b03 + a21 * b13 + a22 * b23 + a23 * b33,
a30 * b00 + a31 * b10 + a32 * b20 + a33 * b30,
a30 * b01 + a31 * b11 + a32 * b21 + a33 * b31,
a30 * b02 + a31 * b12 + a32 * b22 + a33 * b32,
a30 * b03 + a31 * b13 + a32 * b23 + a33 * b33
];
return new OV.Matrix (result);
}
};

View File

@ -0,0 +1,41 @@
OV.Transformation = class
{
constructor (matrix)
{
if (matrix !== undefined && matrix !== null) {
this.matrix = matrix;
} else {
this.matrix = new OV.Matrix ();
this.matrix.CreateIdentity ();
}
}
SetMatrix (matrix)
{
this.matrix = matrix;
return this;
}
GetMatrix ()
{
return this.matrix;
}
IsIdentity ()
{
return this.matrix.IsIdentity ();
}
AppendMatrix (matrix)
{
this.matrix = this.matrix.MultiplyMatrix (matrix);
return this;
}
TransformCoord3D (coord)
{
let resultVector = this.matrix.MultiplyVector ([coord.x, coord.y, coord.z, 1.0]);
let result = new OV.Coord3D (resultVector[0], resultVector[1], resultVector[2]);
return result;
}
};

29
source/geometry/tween.js Normal file
View File

@ -0,0 +1,29 @@
OV.BezierTweenFunction = function (distance, index, count)
{
let t = index / count;
return distance * (t * t * (3.0 - 2.0 * t));
};
OV.LinearTweenFunction = function (distance, index, count)
{
return index * distance / count;
};
OV.ParabolicTweenFunction = function (distance, index, count)
{
let t = index / count;
let t2 = t * t;
return distance * (t2 / (2.0 * (t2 - t) + 1.0));
};
OV.TweenCoord3D = function (a, b, count, tweenFunc)
{
let dir = OV.SubCoord3D (b, a).Normalize ();
let distance = OV.CoordDistance3D (a, b);
let result = [];
for (let i = 0; i < count; i++) {
let step = tweenFunc (distance, i, count - 1);
result.push (a.Clone ().Offset (dir, step));
}
return result;
};

354
source/import/importer.js Normal file
View File

@ -0,0 +1,354 @@
OV.File = class
{
constructor (file, source)
{
this.source = source;
if (source === OV.FileSource.Url) {
this.fileUrl = file;
this.fileObject = null;
this.name = OV.GetFileName (file);
this.extension = OV.GetFileExtension (file);
} else if (source === OV.FileSource.File) {
this.fileUrl = null;
this.fileObject = file;
this.name = OV.GetFileName (file.name);
this.extension = OV.GetFileExtension (file.name);
}
this.content = null;
}
};
OV.FileList = class
{
constructor (importers)
{
this.files = [];
this.importers = importers;
}
FillFromFileUrls (fileList)
{
this.Fill (fileList, OV.FileSource.Url);
}
FillFromFileObjects (fileList)
{
this.Fill (fileList, OV.FileSource.File);
}
ExtendFromFileList (files)
{
for (let i = 0; i < files.length; i++) {
let file = files[i];
if (!this.ContainsFileByPath (file.name)) {
this.files.push (file);
}
}
}
GetFiles ()
{
return this.files;
}
GetContent (onReady)
{
let obj = this;
let taskRunner = new OV.TaskRunner ();
taskRunner.Run (this.files.length, {
runTask : function (index, complete) {
obj.GetFileContent (obj.files[index], complete);
},
onReady : onReady
});
}
ContainsFileByPath (filePath)
{
return this.FindFileByPath (filePath) !== null;
}
FindFileByPath (filePath)
{
let fileName = OV.GetFileName (filePath).toLowerCase ();
for (let fileIndex = 0; fileIndex < this.files.length; fileIndex++) {
let file = this.files[fileIndex];
if (file.name.toLowerCase () === fileName) {
return file;
}
}
return null;
}
HasMainFile ()
{
return this.GetMainFile () !== null;
}
GetMainFile ()
{
for (let fileIndex = 0; fileIndex < this.files.length; fileIndex++) {
let file = this.files[fileIndex];
let importer = this.FindImporter (file);
if (importer !== null) {
return {
file : file,
importer : importer
};
}
}
return null;
}
IsOnlySource (source)
{
if (this.files.length === 0) {
return false;
}
for (let i = 0; i < this.files.length; i++) {
let file = this.files[i];
if (file.source !== source) {
return false;
}
}
return true;
}
Fill (fileList, fileSource)
{
this.files = [];
for (let fileIndex = 0; fileIndex < fileList.length; fileIndex++) {
let fileObject = fileList[fileIndex];
let file = new OV.File (fileObject, fileSource);
this.AddFile (file);
}
}
AddFile (file)
{
this.files.push (file);
}
GetFileContent (file, complete)
{
let callbacks = {
success : function (content) {
file.content = content;
},
error : function () {
},
complete : function () {
complete ();
}
};
if (file.content !== null) {
complete ();
return;
}
let format = this.GetFileFormat (file);
if (file.source === OV.FileSource.Url) {
OV.RequestUrl (file.fileUrl, format, callbacks);
} else if (file.source === OV.FileSource.File) {
OV.ReadFile (file.fileObject, format, callbacks);
}
}
GetFileFormat (file)
{
for (let importerIndex = 0; importerIndex < this.importers.length; importerIndex++) {
let importer = this.importers[importerIndex];
let extension = file.extension.toLowerCase ();
let knownFormats = importer.GetKnownFileFormats ();
if (knownFormats[extension] !== undefined) {
return knownFormats[extension];
}
}
return OV.FileFormat.Binary;
}
FindImporter (file)
{
for (let importerIndex = 0; importerIndex < this.importers.length; importerIndex++) {
let importer = this.importers[importerIndex];
if (importer.CanImportExtension (file.extension.toLowerCase ())) {
return importer;
}
}
return null;
}
};
OV.ImporterErrorCode =
{
NoImportableFile : 1,
ImportFailed : 2,
UnknownError : 3
};
OV.ImporterError = class
{
constructor (code, message)
{
this.code = code;
this.message = message;
}
};
OV.ImportResult = class
{
constructor ()
{
this.model = null;
this.mainFile = null;
this.upVector = null;
this.usedFiles = [];
this.missingFiles = [];
}
};
OV.Importer = class
{
constructor ()
{
this.importers = [
new OV.ImporterObj (),
new OV.ImporterStl (),
new OV.ImporterOff (),
new OV.ImporterPly (),
new OV.Importer3ds (),
new OV.ImporterGltf ()
];
this.fileList = new OV.FileList (this.importers);
this.missingFiles = [];
}
LoadFilesFromUrls (fileList, onReady)
{
this.LoadFiles (fileList, OV.FileSource.Url, onReady);
}
LoadFilesFromFileObjects (fileList, onReady)
{
this.LoadFiles (fileList, OV.FileSource.File, onReady);
}
Import (callbacks)
{
let mainFile = this.fileList.GetMainFile ();
if (mainFile === null || mainFile.file.content === null) {
callbacks.error (new OV.ImporterError (OV.ImporterErrorCode.NoImportableFile, null));
return;
}
let result = new OV.ImportResult ();
result.mainFile = mainFile.file.name;
result.usedFiles.push (mainFile.file.name);
let fileNameToContent = {};
let textureNameToUrl = {};
let obj = this;
let importer = mainFile.importer;
importer.Import (mainFile.file.content, mainFile.file.extension, {
getDefaultMaterial : function () {
let material = new OV.Material ();
material.diffuse = new OV.Color (200, 200, 200);
return material;
},
getFileContent : function (filePath) {
let fileName = OV.GetFileName (filePath);
let fileContent = fileNameToContent[fileName];
if (fileContent === undefined) {
let file = obj.fileList.FindFileByPath (filePath);
if (file === null || file.content === null) {
result.missingFiles.push (fileName);
obj.missingFiles.push (fileName);
fileContent = null;
} else {
result.usedFiles.push (fileName);
fileContent = file.content;
}
fileNameToContent[fileName] = fileContent;
}
return fileContent;
},
getTextureObjectUrl : function (filePath) {
let fileName = OV.GetFileName (filePath);
let textureUrl = textureNameToUrl[fileName];
if (textureUrl === undefined) {
let file = obj.fileList.FindFileByPath (filePath);
if (file === null || file.content === null) {
result.missingFiles.push (fileName);
obj.missingFiles.push (fileName);
textureUrl = null;
} else {
result.usedFiles.push (fileName);
let blob = new Blob ([file.content]);
let blobURL = URL.createObjectURL (blob);
textureUrl = blobURL;
}
textureNameToUrl[fileName] = textureUrl;
}
return textureUrl;
}
});
if (importer.IsError ()) {
let message = importer.GetMessage ();
callbacks.error (new OV.ImporterError (OV.ImporterErrorCode.ImportFailed, message));
return;
}
result.model = importer.GetModel ();
result.model.SetName (mainFile.file.name);
result.upVector = importer.GetUpDirection ();
callbacks.success (result);
}
LoadFiles (fileList, fileSource, onReady)
{
let newFileList = new OV.FileList (this.importers);
if (fileSource === OV.FileSource.Url) {
newFileList.FillFromFileUrls (fileList);
} else if (fileSource === OV.FileSource.File) {
newFileList.FillFromFileObjects (fileList);
}
let reset = false;
if (newFileList.HasMainFile ()) {
reset = true;
} else {
let foundMissingFile = false;
for (let i = 0; i < this.missingFiles.length; i++) {
let missingFile = this.missingFiles[i];
if (newFileList.ContainsFileByPath (missingFile)) {
foundMissingFile = true;
}
}
if (!foundMissingFile) {
reset = true;
} else {
let newFiles = newFileList.GetFiles ();
this.fileList.ExtendFromFileList (newFiles);
reset = false;
}
}
if (reset) {
this.fileList = newFileList;
}
this.missingFiles = [];
this.fileList.GetContent (function () {
onReady ();
});
}
GetFileList ()
{
return this.fileList;
}
IsOnlyFileSource (source)
{
return this.fileList.IsOnlySource (source);
}
};

View File

@ -0,0 +1,679 @@
OV.CHUNK3DS =
{
MAIN3DS : 0x4D4D,
EDIT3DS : 0x3D3D,
EDIT_MATERIAL : 0xAFFF,
MAT_NAME : 0xA000,
MAT_AMBIENT : 0xA010,
MAT_DIFFUSE : 0xA020,
MAT_SPECULAR : 0xA030,
MAT_SHININESS : 0xA040,
MAT_SHININESS_STRENGTH : 0xA041,
MAT_TRANSPARENCY : 0xA050,
MAT_COLOR_F : 0x0010,
MAT_COLOR : 0x0011,
MAT_LIN_COLOR : 0x0012,
MAT_LIN_COLOR_F : 0x0013,
MAT_TEXMAP : 0xA200,
MAT_TEXMAP_NAME : 0xA300,
MAT_TEXMAP_UOFFSET : 0xA358,
MAT_TEXMAP_VOFFSET : 0xA35A,
MAT_TEXMAP_USCALE : 0xA354,
MAT_TEXMAP_VSCALE : 0xA356,
MAT_TEXMAP_ROTATION : 0xA35C,
PERCENTAGE : 0x0030,
PERCENTAGE_F : 0x0031,
EDIT_OBJECT : 0x4000,
OBJ_TRIMESH : 0x4100,
OBJ_LIGHT : 0x4600,
OBJ_CAMERA : 0x4700,
TRI_VERTEX : 0x4110,
TRI_TEXVERTEX : 0x4140,
TRI_FACE : 0x4120,
TRI_TRANSFORMATION : 0x4160,
TRI_MATERIAL : 0x4130,
TRI_SMOOTH : 0x4150,
KF3DS : 0xB000,
OBJECT_NODE : 0xB002,
OBJECT_HIERARCHY : 0xB010,
OBJECT_INSTANCE_NAME : 0xB011,
OBJECT_PIVOT : 0xB013,
OBJECT_POSITION : 0xB020,
OBJECT_ROTATION : 0xB021,
OBJECT_SCALE : 0xB022,
OBJECT_ID : 0xB030
};
OV.Importer3ds = class extends OV.ImporterBase
{
constructor ()
{
super ();
this.materialNameToIndex = null;
this.meshNameToIndex = null;
this.meshTransformations = null;
this.defaultMaterialIndex = null;
}
ResetState ()
{
this.materialNameToIndex = {};
this.meshNameToIndex = {};
this.meshTransformations = [];
this.defaultMaterialIndex = null;
}
CanImportExtension (extension)
{
return extension === '3ds';
}
GetKnownFileFormats ()
{
return {
'3ds' : OV.FileFormat.Binary
};
}
GetUpDirection ()
{
return OV.Direction.Z;
}
ImportContent (fileContent)
{
this.ProcessBinary (fileContent);
}
ProcessBinary (fileContent)
{
let obj = this;
let reader = new OV.BinaryReader (fileContent, true);
let endByte = reader.GetByteLength ();
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.MAIN3DS) {
obj.ReadMainChunk (reader, chunkLength);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
}
ReadMainChunk (reader, length)
{
let obj = this;
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.EDIT3DS) {
obj.ReadEditorChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.KF3DS) {
obj.ReadKeyFrameChunk (reader, chunkLength);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
}
ReadEditorChunk (reader, length)
{
let obj = this;
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.EDIT_MATERIAL) {
obj.ReadMaterialChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.EDIT_OBJECT) {
obj.ReadObjectChunk (reader, chunkLength);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
}
ReadMaterialChunk (reader, length)
{
let obj = this;
let material = new OV.Material ();
let endByte = this.GetChunkEnd (reader, length);
let shininess = null;
let shininessStrength = null;
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.MAT_NAME) {
material.name = obj.ReadName (reader);
} else if (chunkId === OV.CHUNK3DS.MAT_AMBIENT) {
material.ambient = obj.ReadColorChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.MAT_DIFFUSE) {
material.diffuse = obj.ReadColorChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.MAT_SPECULAR) {
material.specular = obj.ReadColorChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.MAT_SHININESS) {
shininess = obj.ReadPercentageChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.MAT_SHININESS_STRENGTH) {
shininessStrength = obj.ReadPercentageChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.MAT_TRANSPARENCY) {
material.opacity = 1.0 - obj.ReadPercentageChunk (reader, chunkLength);
material.transparent = OV.IsLower (material.opacity, 1.0);
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP) {
material.diffuseMap = obj.ReadTextureMapChunk (reader, chunkLength);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
if (shininess !== null || shininessStrength !== null) {
material.shininess = shininess * shininessStrength;
}
let materialIndex = this.model.AddMaterial (material);
this.materialNameToIndex[material.name] = materialIndex;
}
ReadTextureMapChunk (reader, length)
{
let obj = this;
let texture = new OV.TextureMap ();
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_NAME) {
let textureName = obj.ReadName (reader);
let textureObjectUrl = obj.callbacks.getTextureObjectUrl (textureName);
texture.name = textureName;
texture.url = textureObjectUrl;
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_UOFFSET) {
texture.offset.x = reader.ReadFloat32 ();
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_VOFFSET) {
texture.offset.y = reader.ReadFloat32 ();
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_USCALE) {
texture.scale.x = reader.ReadFloat32 ();
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_VSCALE) {
texture.scale.y = reader.ReadFloat32 ();
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_ROTATION) {
texture.rotation = reader.ReadFloat32 () * OV.DegRad;
} else {
obj.SkipChunk (reader, chunkLength);
}
});
return texture;
}
ReadColorChunk (reader, length)
{
let obj = this;
let color = new OV.Color (0, 0, 0);
let endByte = this.GetChunkEnd (reader, length);
let hasLinColor = false;
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.MAT_COLOR) {
if (!hasLinColor) {
color.r = reader.ReadUnsignedCharacter8 ();
color.g = reader.ReadUnsignedCharacter8 ();
color.b = reader.ReadUnsignedCharacter8 ();
}
} else if (chunkId === OV.CHUNK3DS.MAT_LIN_COLOR) {
color.r = reader.ReadUnsignedCharacter8 ();
color.g = reader.ReadUnsignedCharacter8 ();
color.b = reader.ReadUnsignedCharacter8 ();
hasLinColor = true;
} else if (chunkId === OV.CHUNK3DS.MAT_COLOR_F) {
if (!hasLinColor) {
color.r = parseInt (reader.ReadFloat32 () * 255.0, 10);
color.g = parseInt (reader.ReadFloat32 () * 255.0, 10);
color.b = parseInt (reader.ReadFloat32 () * 255.0, 10);
}
} else if (chunkId === OV.CHUNK3DS.MAT_LIN_COLOR_F) {
color.r = parseInt (reader.ReadFloat32 () * 255.0, 10);
color.g = parseInt (reader.ReadFloat32 () * 255.0, 10);
color.b = parseInt (reader.ReadFloat32 () * 255.0, 10);
hasLinColor = true;
} else {
obj.SkipChunk (reader, chunkLength);
}
});
return color;
}
ReadPercentageChunk (reader, length)
{
let obj = this;
let percentage = 0.0;
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.PERCENTAGE) {
percentage = reader.ReadUnsignedInteger16 () / 100.0;
} else if (chunkId === OV.CHUNK3DS.PERCENTAGE_F) {
percentage = reader.ReadFloat32 ();
} else {
obj.SkipChunk (reader, chunkLength);
}
});
return percentage;
}
ReadObjectChunk (reader, length)
{
let obj = this;
let endByte = this.GetChunkEnd (reader, length);
let objectName = this.ReadName (reader);
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.OBJ_TRIMESH) {
obj.ReadMeshChunk (reader, chunkLength, objectName);
} else if (chunkId === OV.CHUNK3DS.OBJ_LIGHT) {
obj.SkipChunk (reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.OBJ_CAMERA) {
obj.SkipChunk (reader, chunkLength);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
}
ReadMeshChunk (reader, length, objectName)
{
let obj = this;
let mesh = new OV.Mesh ();
mesh.SetName (objectName);
let endByte = this.GetChunkEnd (reader, length);
let transformation = null;
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.TRI_VERTEX) {
obj.ReadVerticesChunk (mesh, reader);
} else if (chunkId === OV.CHUNK3DS.TRI_TEXVERTEX) {
obj.ReadTextureVerticesChunk (mesh, reader);
} else if (chunkId === OV.CHUNK3DS.TRI_FACE) {
obj.ReadFacesChunk (mesh, reader, chunkLength);
} else if (chunkId === OV.CHUNK3DS.TRI_TRANSFORMATION) {
transformation = obj.ReadTransformationChunk (reader);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
if (mesh.VertexCount () === mesh.TextureUVCount ()) {
for (let i = 0; i < mesh.TriangleCount (); i++) {
let triangle = mesh.GetTriangle (i);
triangle.SetTextureUVs (
triangle.v0,
triangle.v1,
triangle.v2
);
}
}
let meshIndex = this.model.AddMesh (mesh);
this.meshNameToIndex[mesh.GetName ()] = meshIndex;
this.meshTransformations.push (new OV.Matrix (transformation));
}
ReadVerticesChunk (mesh, reader)
{
let vertexCount = reader.ReadUnsignedInteger16 ();
for (let i = 0; i < vertexCount; i++) {
let x = reader.ReadFloat32 ();
let y = reader.ReadFloat32 ();
let z = reader.ReadFloat32 ();
mesh.AddVertex (new OV.Coord3D (x, y, z));
}
}
ReadTextureVerticesChunk (mesh, reader)
{
let texVertexCount = reader.ReadUnsignedInteger16 ();
for (let i = 0; i < texVertexCount; i++) {
let x = reader.ReadFloat32 ();
let y = reader.ReadFloat32 ();
mesh.AddTextureUV (new OV.Coord2D (x, y));
}
}
ReadFacesChunk (mesh, reader, length)
{
let endByte = this.GetChunkEnd (reader, length);
let faceCount = reader.ReadUnsignedInteger16 ();
for (let i = 0; i < faceCount; i++) {
let v0 = reader.ReadUnsignedInteger16 ();
let v1 = reader.ReadUnsignedInteger16 ();
let v2 = reader.ReadUnsignedInteger16 ();
let flags = reader.ReadUnsignedInteger16 ();
mesh.AddTriangle (new OV.Triangle (v0, v1, v2));
}
let obj = this;
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.TRI_MATERIAL) {
obj.ReadFaceMaterialsChunk (mesh, reader);
} else if (chunkId === OV.CHUNK3DS.TRI_SMOOTH) {
obj.ReadFaceSmoothingGroupsChunk (mesh, faceCount, reader);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
}
ReadFaceMaterialsChunk (mesh, reader)
{
let materialName = this.ReadName (reader);
let materialIndex = this.materialNameToIndex[materialName];
let faceCount = reader.ReadUnsignedInteger16 ();
for (let i = 0; i < faceCount; i++) {
let faceIndex = reader.ReadUnsignedInteger16 ();
let triangle = mesh.GetTriangle (faceIndex);
if (materialIndex !== undefined) {
triangle.mat = materialIndex;
}
}
}
ReadFaceSmoothingGroupsChunk (mesh, faceCount, reader)
{
for (let i = 0; i < faceCount; i++) {
let smoothingGroup = reader.ReadUnsignedInteger32 ();
let triangle = mesh.GetTriangle (i);
triangle.curve = smoothingGroup;
}
}
ReadTransformationChunk (reader)
{
let matrix = [];
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 3; j++) {
matrix.push (reader.ReadFloat32 ());
}
if (i < 3) {
matrix.push (0);
} else {
matrix.push (1);
}
}
return matrix;
}
ReadKeyFrameChunk (reader, length)
{
let nodeHierarchy = {
nodes : [],
idToIndex : {},
meshIndexToNodes : {}
};
let endByte = this.GetChunkEnd (reader, length);
let obj = this;
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.OBJECT_NODE) {
obj.ReadObjectNodeChunk (nodeHierarchy, reader, chunkLength);
} else {
obj.SkipChunk (reader, chunkLength);
}
});
this.ApplyModelTransformations (nodeHierarchy);
}
ApplyModelTransformations (nodeHierarchy)
{
function GetNodeTransformation (nodeHierarchy, node)
{
function GetNodePosition (node)
{
if (node.positions.length === 0) {
return [0.0, 0.0, 0.0];
}
return node.positions[0];
}
function GetNodeRotation (node)
{
function GetQuaternionFromAxisAndAngle (rotation)
{
let result = [0.0, 0.0, 0.0, 1.0];
let length = Math.sqrt (rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2]);
if (length > 0.0) {
let omega = rotation[3] * -0.5;
let si = Math.sin (omega) / length;
result = [si * rotation[0], si * rotation[1], si * rotation[2], Math.cos (omega)];
}
return result;
}
if (node.rotations.length === 0) {
return [0.0, 0.0, 0.0, 1.0];
}
let rotation = node.rotations[0];
return GetQuaternionFromAxisAndAngle (rotation);
}
function GetNodeScale (node)
{
if (node.scales.length === 0) {
return [1.0, 1.0, 1.0];
}
return node.scales[0];
}
if (node.matrix !== null) {
return node.matrix;
}
let matrix = new OV.Matrix ();
matrix.ComposeTRS (
GetNodePosition (node),
GetNodeRotation (node),
GetNodeScale (node)
);
if (node.userId !== 65535) {
let parentIndex = nodeHierarchy.idToIndex[node.userId];
if (parentIndex !== undefined) {
let parentNode = nodeHierarchy.nodes[parentIndex];
let parentMatrix = GetNodeTransformation (nodeHierarchy, parentNode);
matrix = matrix.MultiplyMatrix (parentMatrix);
}
}
node.matrix = matrix;
return matrix;
}
function ApplyMeshTransformation (model, currentMeshIndex, meshMatrix, nodeHierarchy, node)
{
function GetNodePivotPoint (node)
{
if (node === null) {
return [0.0, 0.0, 0.0];
}
return node.pivot;
}
if (!meshMatrix.IsValid ()) {
return;
}
let nodeMatrix = meshMatrix;
if (node !== null) {
nodeMatrix = GetNodeTransformation (nodeHierarchy, node);
}
let determinant = meshMatrix.Determinant ();
if (OV.IsNegative (determinant)) {
// Mirror by x coordinates
let scaleMatrix = new OV.Matrix ().CreateScale (-1.0, 1.0, 1.0);
meshMatrix = scaleMatrix.MultiplyMatrix (meshMatrix);
}
let invMeshMatrix = meshMatrix.Invert ();
if (invMeshMatrix === null) {
return;
}
let pivotPoint = GetNodePivotPoint (node);
let pivotMatrix = new OV.Matrix ().CreateTranslation (-pivotPoint[0], -pivotPoint[1], -pivotPoint[2]);
let matrix = nodeMatrix.Clone ();
matrix = pivotMatrix.MultiplyMatrix (matrix);
matrix = invMeshMatrix.MultiplyMatrix (matrix);
let transformation = new OV.Transformation (matrix);
let mesh = model.GetMesh (currentMeshIndex);
OV.TransformMesh (mesh, transformation);
}
function AddDuplicatedMesh (model, meshIndex, toIndex)
{
let mesh = model.GetMesh (meshIndex);
let clonedMesh = OV.CloneMesh (mesh);
let clonedMeshIndex = model.AddMeshToIndex (clonedMesh, toIndex);
return clonedMeshIndex;
}
let newToOldMeshIndexOffset = 0;
for (let meshIndex = 0; meshIndex < this.model.MeshCount (); meshIndex++) {
let currentMeshIndex = meshIndex;
let originalMeshIndex = currentMeshIndex - newToOldMeshIndexOffset;
let meshTransformation = this.meshTransformations[originalMeshIndex];
let meshNodes = nodeHierarchy.meshIndexToNodes[originalMeshIndex];
if (meshNodes === undefined) {
ApplyMeshTransformation (this.model, currentMeshIndex, meshTransformation, nodeHierarchy, null);
} else {
for (let nodeIndex = 0; nodeIndex < meshNodes.length; nodeIndex++) {
let currentNode = meshNodes[nodeIndex];
let transformedMeshIndex = currentMeshIndex;
if (nodeIndex > 0) {
transformedMeshIndex = AddDuplicatedMesh (this.model, currentMeshIndex, currentMeshIndex + nodeIndex);
newToOldMeshIndexOffset += 1;
meshIndex += 1;
}
ApplyMeshTransformation (this.model, transformedMeshIndex, meshTransformation, nodeHierarchy, currentNode);
}
}
}
}
ReadObjectNodeChunk (nodeHierarchy, reader, length)
{
function ReadTrackVector (obj, reader, type)
{
let result = [];
reader.Skip (10);
let keyNum = reader.ReadInteger32 ();
for (let i = 0; i < keyNum; i++) {
reader.ReadInteger32 ();
let flags = reader.ReadUnsignedInteger16 ();
if (flags !== 0) {
reader.ReadFloat32 ();
}
let current = null;
if (type === OV.CHUNK3DS.OBJECT_ROTATION) {
let tmp = reader.ReadFloat32 ();
current = obj.ReadVector (reader);
current[3] = tmp;
} else {
current = obj.ReadVector (reader);
}
result.push (current);
}
return result;
}
let objectNode = {
name : '',
instanceName : '',
nodeId : -1,
flags : -1,
userId : -1,
pivot : [0.0, 0.0, 0.0],
positions : [],
rotations : [],
scales : [],
matrix : null
};
let obj = this;
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.OBJECT_HIERARCHY) {
objectNode.name = obj.ReadName (reader);
objectNode.flags = reader.ReadUnsignedInteger32 ();
objectNode.userId = reader.ReadUnsignedInteger16 ();
} else if (chunkId === OV.CHUNK3DS.OBJECT_INSTANCE_NAME) {
objectNode.instanceName = obj.ReadName (reader);
} else if (chunkId === OV.CHUNK3DS.OBJECT_PIVOT) {
objectNode.pivot = obj.ReadVector (reader);
} else if (chunkId === OV.CHUNK3DS.OBJECT_POSITION) {
objectNode.positions = ReadTrackVector (obj, reader, OV.CHUNK3DS.OBJECT_POSITION);
} else if (chunkId === OV.CHUNK3DS.OBJECT_ROTATION) {
objectNode.rotations = ReadTrackVector (obj, reader, OV.CHUNK3DS.OBJECT_ROTATION);
} else if (chunkId === OV.CHUNK3DS.OBJECT_SCALE) {
objectNode.scales = ReadTrackVector (obj, reader, OV.CHUNK3DS.OBJECT_SCALE);
} else if (chunkId === OV.CHUNK3DS.OBJECT_ID) {
objectNode.nodeId = reader.ReadUnsignedInteger16 ();
} else {
obj.SkipChunk (reader, chunkLength);
}
});
let nodeIndex = nodeHierarchy.nodes.length;
nodeHierarchy.nodes.push (objectNode);
nodeHierarchy.idToIndex[objectNode.nodeId] = nodeIndex;
let meshIndex = this.meshNameToIndex[objectNode.name];
if (meshIndex !== undefined) {
let meshNodes = nodeHierarchy.meshIndexToNodes[meshIndex];
if (meshNodes === undefined) {
nodeHierarchy.meshIndexToNodes[meshIndex] = [];
}
nodeHierarchy.meshIndexToNodes[meshIndex].push (objectNode);
}
}
ReadName (reader)
{
let name = '';
let char = 0;
let count = 0;
while (count < 64) {
char = reader.ReadCharacter8 ();
if (char === 0) {
break;
}
name = name + String.fromCharCode (char);
count = count + 1;
}
return name;
}
ReadVector (reader)
{
let result = [
reader.ReadFloat32 (),
reader.ReadFloat32 (),
reader.ReadFloat32 ()
];
return result;
}
ReadChunks (reader, endByte, onChunk)
{
while (reader.GetPosition () <= endByte - 6) {
let chunkId = reader.ReadUnsignedInteger16 ();
let chunkLength = reader.ReadUnsignedInteger32 ();
onChunk (chunkId, chunkLength);
}
}
GetChunkEnd (reader, length)
{
return reader.GetPosition () + length - 6;
}
SkipChunk (reader, length)
{
reader.Skip (length - 6);
}
};

View File

@ -0,0 +1,83 @@
OV.ImporterBase = class
{
constructor ()
{
this.extension = null;
this.callbacks = null;
this.model = null;
this.error = null;
this.message = null;
}
Import (content, extension, callbacks)
{
this.extension = extension;
this.callbacks = callbacks;
this.model = new OV.Model ();
this.error = false;
this.message = null;
this.ResetState ();
this.ImportContent (content);
if (this.error) {
return;
}
if (OV.IsModelEmpty (this.model)) {
this.error = true;
return;
}
OV.FinalizeModel (this.model, this.callbacks.getDefaultMaterial);
}
ResetState ()
{
}
ImportContent (content)
{
}
CanImportExtension (extension)
{
return false;
}
GetKnownFileFormats ()
{
return {};
}
GetUpDirection ()
{
return OV.Direction.Z;
}
GetModel ()
{
return this.model;
}
SetError ()
{
this.error = true;
}
IsError ()
{
return this.error;
}
SetMessage (message)
{
this.message = message;
}
GetMessage ()
{
return this.message;
}
};

View File

@ -0,0 +1,899 @@
OV.GltfComponentType =
{
BYTE : 5120,
UNSIGNED_BYTE : 5121,
SHORT : 5122,
UNSIGNED_SHORT : 5123,
UNSIGNED_INT : 5125,
FLOAT : 5126
};
OV.GltfDataType =
{
SCALAR : 0,
VEC2 : 1,
VEC3 : 2,
VEC4 : 3,
MAT2 : 4,
MAT3 : 5,
MAT4 : 6
};
OV.GltfRenderMode =
{
POINTS : 0,
LINES : 1,
LINE_LOOP : 2,
LINE_STRIP : 3,
TRIANGLES : 4,
TRIANGLE_STRIP : 5,
TRIANGLE_FAN : 6
};
OV.GltfConstants =
{
GLTF_STRING : 0x46546C67,
JSON_CHUNK_TYPE : 0x4E4F534A,
BINARY_CHUNK_TYPE : 0x004E4942
};
OV.GltfNodeTree = class
{
constructor ()
{
this.nodes = [];
this.nodeToParent = {};
this.nodeMatrices = {};
}
AddMeshNode (nodeIndex)
{
this.nodes.push (nodeIndex);
return this.nodes.length - 1;
}
AddNodeParent (nodeIndex, parentIndex)
{
this.nodeToParent[nodeIndex] = parentIndex;
}
GetNodeParent (nodeIndex)
{
let parentIndex = this.nodeToParent[nodeIndex];
if (parentIndex === undefined || parentIndex === -1) {
return null;
}
return parentIndex;
}
AddNodeMatrix (nodeIndex, matrix)
{
this.nodeMatrices[nodeIndex] = matrix;
}
GetNodeMatrix (nodeIndex)
{
let matrix = this.nodeMatrices[nodeIndex];
if (matrix === undefined) {
return null;
}
return matrix;
}
};
OV.GltfBufferReader = class
{
constructor (buffer)
{
this.reader = new OV.BinaryReader (buffer, true);
this.componentType = null;
this.dataType = null;
this.byteStride = null;
this.dataCount = null;
this.sparseReader = null;
}
SetComponentType (componentType)
{
this.componentType = componentType;
}
SetDataType (dataType)
{
if (dataType === 'SCALAR') {
this.dataType = OV.GltfDataType.SCALAR;
} else if (dataType === 'VEC2') {
this.dataType = OV.GltfDataType.VEC2;
} else if (dataType === 'VEC3') {
this.dataType = OV.GltfDataType.VEC3;
} else if (dataType === 'VEC4') {
this.dataType = OV.GltfDataType.VEC4;
} else if (dataType === 'MAT2') {
this.dataType = OV.GltfDataType.MAT2;
} else if (dataType === 'MAT3') {
this.dataType = OV.GltfDataType.MAT3;
} else if (dataType === 'MAT4') {
this.dataType = OV.GltfDataType.MAT4;
}
}
SetByteStride (byteStride)
{
this.byteStride = byteStride;
}
SetDataCount (dataCount)
{
this.dataCount = dataCount;
}
SetSparseReader (indexReader, valueReader)
{
this.sparseReader = {
indexReader : indexReader,
valueReader : valueReader
};
}
ReadArrayBuffer (byteLength)
{
return this.reader.ReadArrayBuffer (byteLength);
}
GetDataCount ()
{
return this.dataCount;
}
ReadData ()
{
if (this.dataType === null) {
return null;
}
if (this.dataType === OV.GltfDataType.SCALAR) {
let data = this.ReadComponent ();
this.SkipBytesByStride (1);
return data;
} else if (this.dataType === OV.GltfDataType.VEC2) {
let x = this.ReadComponent ();
let y = this.ReadComponent ();
this.SkipBytesByStride (2);
return new OV.Coord2D (x, y);
} else if (this.dataType === OV.GltfDataType.VEC3) {
let x = this.ReadComponent ();
let y = this.ReadComponent ();
let z = this.ReadComponent ();
this.SkipBytesByStride (3);
return new OV.Coord3D (x, y, z);
}
return null;
}
EnumerateData (onData)
{
if (this.sparseReader === null) {
for (let i = 0; i < this.dataCount; i++) {
onData (this.ReadData ());
}
} else {
let sparseData = [];
for (let i = 0; i < this.sparseReader.indexReader.GetDataCount (); i++) {
let index = this.sparseReader.indexReader.ReadData ();
let value = this.sparseReader.valueReader.ReadData ();
sparseData.push ({
index : index,
value : value
});
}
let sparseIndex = 0;
for (let i = 0; i < this.dataCount; i++) {
let data = this.ReadData ();
if (sparseIndex < sparseData.length && sparseData[sparseIndex].index === i) {
onData (sparseData[sparseIndex].value);
sparseIndex += 1;
} else {
onData (data);
}
}
}
}
SkipBytes (bytes)
{
this.reader.Skip (bytes);
}
ReadComponent ()
{
if (this.componentType === null) {
return null;
}
if (this.componentType === OV.GltfComponentType.BYTE) {
return this.reader.ReadCharacter8 ();
} else if (this.componentType === OV.GltfComponentType.UNSIGNED_BYTE) {
return this.reader.ReadUnsignedCharacter8 ();
} else if (this.componentType === OV.GltfComponentType.SHORT) {
return this.reader.ReadInteger16 ();
} else if (this.componentType === OV.GltfComponentType.UNSIGNED_SHORT) {
return this.reader.ReadUnsignedInteger16 ();
} else if (this.componentType === OV.GltfComponentType.UNSIGNED_INT) {
return this.reader.ReadInteger32 ();
} else if (this.componentType === OV.GltfComponentType.FLOAT) {
return this.reader.ReadFloat32 ();
}
return null;
}
SkipBytesByStride (componentCount)
{
if (this.byteStride === null) {
return;
}
let readBytes = componentCount * this.GetComponentSize ();
this.reader.Skip (this.byteStride - readBytes);
}
GetComponentSize ()
{
if (this.componentType === OV.GltfComponentType.BYTE) {
return 1;
} else if (this.componentType === OV.GltfComponentType.UNSIGNED_BYTE) {
return 1;
} else if (this.componentType === OV.GltfComponentType.SHORT) {
return 2;
} else if (this.componentType === OV.GltfComponentType.UNSIGNED_SHORT) {
return 2;
} else if (this.componentType === OV.GltfComponentType.UNSIGNED_INT) {
return 4;
} else if (this.componentType === OV.GltfComponentType.FLOAT) {
return 4;
}
return 0;
}
};
OV.GltfExtensions = class
{
constructor ()
{
this.supportedExtensions = [
'KHR_materials_pbrSpecularGlossiness',
'KHR_texture_transform',
];
}
GetUnsupportedExtensions (extensionsRequired)
{
let unsupportedExtensions = [];
if (extensionsRequired === undefined) {
return unsupportedExtensions;
}
for (let i = 0; i < extensionsRequired.length; i++) {
let requiredExtension = extensionsRequired[i];
if (this.supportedExtensions.indexOf (requiredExtension) === -1) {
unsupportedExtensions.push (requiredExtension);
}
}
return unsupportedExtensions;
}
ProcessMaterial (gltfMaterial, material, imporTextureFn)
{
function GetMaterialComponent (component)
{
return parseInt (Math.round (OV.LinearToSRGB (component) * 255.0), 10);
}
if (gltfMaterial.extensions === undefined) {
return;
}
let khrSpecularGlossiness = gltfMaterial.extensions.KHR_materials_pbrSpecularGlossiness;
if (khrSpecularGlossiness !== undefined) {
let diffuseColor = khrSpecularGlossiness.diffuseFactor;
if (diffuseColor !== undefined) {
material.diffuse = new OV.Color (
GetMaterialComponent (diffuseColor[0]),
GetMaterialComponent (diffuseColor[1]),
GetMaterialComponent (diffuseColor[2])
);
material.opacity = diffuseColor[3];
}
let diffuseTexture = khrSpecularGlossiness.diffuseTexture;
if (diffuseTexture !== undefined) {
material.diffuseMap = imporTextureFn (diffuseTexture);
}
let specularColor = khrSpecularGlossiness.specularFactor;
if (specularColor !== undefined) {
material.specular = new OV.Color (
GetMaterialComponent (specularColor[0]),
GetMaterialComponent (specularColor[1]),
GetMaterialComponent (specularColor[2])
);
}
let specularTexture = khrSpecularGlossiness.specularGlossinessTexture;
if (specularTexture !== undefined) {
material.specularMap = imporTextureFn (specularTexture);
}
let glossiness = khrSpecularGlossiness.glossinessFactor;
if (glossiness !== undefined) {
material.shininess = glossiness;
}
}
}
ProcessTexture (gltfTexture, texture)
{
if (gltfTexture.extensions === undefined) {
return;
}
let khrTextureTransform = gltfTexture.extensions.KHR_texture_transform;
if (khrTextureTransform !== undefined) {
if (khrTextureTransform.offset !== undefined) {
texture.offset.x = khrTextureTransform.offset[0];
texture.offset.y = -khrTextureTransform.offset[1];
}
if (khrTextureTransform.scale !== undefined) {
texture.scale.x = khrTextureTransform.scale[0];
texture.scale.y = khrTextureTransform.scale[1];
}
if (khrTextureTransform.rotation !== undefined) {
texture.rotation = -khrTextureTransform.rotation;
}
}
}
};
OV.ImporterGltf = class extends OV.ImporterBase
{
constructor ()
{
super ();
this.bufferContents = null;
this.gltfExtensions = null;
}
ResetState ()
{
this.bufferContents = [];
this.gltfExtensions = new OV.GltfExtensions ();
}
CanImportExtension (extension)
{
return extension === 'gltf' || extension === 'glb';
}
GetKnownFileFormats ()
{
return {
'gltf' : OV.FileFormat.Text,
'glb' : OV.FileFormat.Binary,
'bin' : OV.FileFormat.Binary
};
}
GetUpDirection ()
{
return OV.Direction.Y;
}
ImportContent (fileContent)
{
if (this.extension === 'gltf') {
this.ProcessGltf (fileContent);
} else if (this.extension === 'glb') {
this.ProcessBinaryGltf (fileContent);
}
}
ProcessGltf (fileContent)
{
let gltf = JSON.parse (fileContent);
if (gltf.asset.version !== '2.0') {
this.SetError ();
this.SetMessage ('Invalid glTF version.');
return;
}
for (let i = 0; i < gltf.buffers.length; i++) {
let buffer = null;
let gltfBuffer = gltf.buffers[i];
let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfBuffer.uri);
if (base64Buffer === null) {
buffer = this.callbacks.getFileContent (gltfBuffer.uri);
} else {
buffer = base64Buffer.buffer;
}
if (buffer === null) {
this.SetError ();
this.SetMessage ('One of the requested buffers is missing.');
return;
}
this.bufferContents.push (buffer);
}
this.ProcessMainFile (gltf);
}
ProcessBinaryGltf (fileContent)
{
function ReadChunk (reader)
{
let length = reader.ReadUnsignedInteger32 ();
let type = reader.ReadUnsignedInteger32 ();
let buffer = reader.ReadArrayBuffer (length);
return {
type : type,
buffer : buffer
};
}
let reader = new OV.BinaryReader (fileContent, true);
let magic = reader.ReadUnsignedInteger32 ();
if (magic !== OV.GltfConstants.GLTF_STRING) {
this.SetError ();
this.SetMessage ('Invalid glTF file.');
return;
}
let version = reader.ReadUnsignedInteger32 ();
if (version !== 2) {
this.SetError ();
this.SetMessage ('Invalid glTF version.');
return;
}
let length = reader.ReadUnsignedInteger32 ();
if (length !== reader.GetByteLength ()) {
this.SetError ();
this.SetMessage ('Invalid glTF file.');
return;
}
let gltfTextContent = null;
while (!reader.End ()) {
let chunk = ReadChunk (reader);
if (chunk.type === OV.GltfConstants.JSON_CHUNK_TYPE) {
gltfTextContent = OV.ArrayBufferToUtf8String (chunk.buffer);
} else if (chunk.type === OV.GltfConstants.BINARY_CHUNK_TYPE) {
this.bufferContents.push (chunk.buffer);
}
}
if (gltfTextContent !== null) {
let gltf = JSON.parse (gltfTextContent);
this.ProcessMainFile (gltf);
}
}
ProcessMainFile (gltf)
{
let unsupportedExtensions = this.gltfExtensions.GetUnsupportedExtensions (gltf.extensionsRequired);
if (unsupportedExtensions.length > 0) {
this.SetError ();
this.SetMessage ('Unsupported extension: ' + unsupportedExtensions.join (', ') + '.');
return;
}
let defaultScene = this.GetDefaultScene (gltf);
if (defaultScene === null) {
this.SetError ();
this.SetMessage ('No default scene found.');
return;
}
let materials = gltf.materials;
if (materials !== undefined) {
for (let i = 0; i < materials.length; i++) {
this.ImportMaterial (gltf, i);
}
}
let nodeTree = this.CollectMeshNodesForScene (gltf, defaultScene);
for (let i = 0; i < nodeTree.nodes.length; i++) {
let nodeIndex = nodeTree.nodes[i];
this.ImportMeshNode (gltf, nodeIndex, nodeTree);
}
}
GetDefaultScene (gltf)
{
let defaultSceneIndex = gltf.scene || 0;
if (defaultSceneIndex >= gltf.scenes.length) {
return null;
}
return gltf.scenes[defaultSceneIndex];
}
CollectMeshNodesForScene (gltf, scene)
{
function CollectMeshNodeIndices (gltf, parentIndex, nodeIndex, nodeTree, meshNodes)
{
let node = gltf.nodes[nodeIndex];
if (node.mesh !== undefined) {
nodeTree.AddMeshNode (nodeIndex);
meshNodes.push (nodeIndex);
}
nodeTree.AddNodeParent (nodeIndex, parentIndex);
if (node.children !== undefined) {
for (let i = 0; i < node.children.length; i++) {
let childNodeIndex = node.children[i];
CollectMeshNodeIndices (gltf, nodeIndex, childNodeIndex, nodeTree, meshNodes);
}
}
}
let nodeTree = new OV.GltfNodeTree ();
let meshNodes = [];
for (let i = 0; i < scene.nodes.length; i++) {
let nodeIndex = scene.nodes[i];
CollectMeshNodeIndices (gltf, -1, nodeIndex, nodeTree, meshNodes);
}
return nodeTree;
}
ImportMaterial (gltf, materialIndex)
{
function GetMaterialComponent (component)
{
return parseInt (Math.round (OV.LinearToSRGB (component) * 255.0), 10);
}
let gltfMaterial = gltf.materials[materialIndex];
let material = new OV.Material ();
if (gltfMaterial.name !== undefined) {
material.name = gltfMaterial.name;
}
material.diffuse = new OV.Color (
GetMaterialComponent (1.0),
GetMaterialComponent (1.0),
GetMaterialComponent (1.0)
);
if (gltfMaterial.pbrMetallicRoughness !== undefined) {
let baseColor = gltfMaterial.pbrMetallicRoughness.baseColorFactor;
if (baseColor !== undefined) {
material.diffuse = new OV.Color (
GetMaterialComponent (baseColor[0]),
GetMaterialComponent (baseColor[1]),
GetMaterialComponent (baseColor[2])
);
material.opacity = baseColor[3];
}
let emissiveColor = gltfMaterial.emissiveFactor;
if (emissiveColor !== undefined) {
material.emissive = new OV.Color (
GetMaterialComponent (emissiveColor[0]),
GetMaterialComponent (emissiveColor[1]),
GetMaterialComponent (emissiveColor[2])
);
}
material.diffuseMap = this.ImportTexture (gltf, gltfMaterial.pbrMetallicRoughness.baseColorTexture);
material.normalMap = this.ImportTexture (gltf, gltfMaterial.normalTexture);
material.emissiveMap = this.ImportTexture (gltf, gltfMaterial.emissiveTexture);
if (material.diffuseMap !== null) {
material.multiplyDiffuseMap = true;
}
let alphaMode = gltfMaterial.alphaMode;
if (alphaMode !== undefined) {
if (alphaMode === 'BLEND') {
material.transparent = true;
} else if (alphaMode === 'MASK') {
material.transparent = true;
material.alphaTest = gltfMaterial.alphaCutoff || 0.5;
}
}
}
let obj = this;
this.gltfExtensions.ProcessMaterial (gltfMaterial, material, function (textureRef) {
return obj.ImportTexture (gltf, textureRef);
});
this.model.AddMaterial (material);
}
ImportTexture (gltf, gltfTextureRef)
{
function GetTextureFileExtension (mimeType)
{
if (mimeType === undefined || mimeType === null) {
return '';
}
let mimeParts = mimeType.split ('/');
if (mimeParts.length === 0) {
return '';
}
return '.' + mimeParts[mimeParts.length - 1];
}
if (gltfTextureRef === undefined || gltfTextureRef === null) {
return null;
}
let texture = new OV.TextureMap ();
let gltfTexture = gltf.textures[gltfTextureRef.index];
let gltfImage = gltf.images[gltfTexture.source];
let textureIndexString = gltfTexture.source.toString ();
if (gltfImage.uri !== undefined) {
let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfImage.uri);
if (base64Buffer !== null) {
let blob = new Blob ([base64Buffer.buffer], { type : base64Buffer.mimeType });
texture.name = 'Embedded_' + textureIndexString + GetTextureFileExtension (base64Buffer.mimeType);
texture.url = URL.createObjectURL (blob);
} else {
let textureObjectUrl = this.callbacks.getTextureObjectUrl (gltfImage.uri);
texture.name = gltfImage.uri;
texture.url = textureObjectUrl;
}
} else if (gltfImage.bufferView !== undefined) {
let bufferView = gltf.bufferViews[gltfImage.bufferView];
let reader = this.GetReaderFromBufferView (bufferView);
if (reader !== null) {
let buffer = reader.ReadArrayBuffer (bufferView.byteLength);
let blob = new Blob ([buffer], { type: gltfImage.mimeType });
texture.name = 'Binary_' + textureIndexString + GetTextureFileExtension (gltfImage.mimeType);
texture.url = URL.createObjectURL (blob);
}
}
this.gltfExtensions.ProcessTexture (gltfTextureRef, texture);
return texture;
}
ImportMeshNode (gltf, nodeIndex, nodeTree)
{
function GetNodeTransformation (gltf, nodeIndex, nodeTree)
{
let node = gltf.nodes[nodeIndex];
let matrix = nodeTree.GetNodeMatrix (nodeIndex);
if (matrix !== null) {
return matrix;
}
matrix = new OV.Matrix ().CreateIdentity ();
if (node.matrix !== undefined) {
matrix.Set (node.matrix);
} else {
let hasTransformation = false;
let translation = [0.0, 0.0, 0.0];
let rotation = [0.0, 0.0, 0.0, 1.0];
let scale = [1.0, 1.0, 1.0];
if (node.translation !== undefined) {
translation = node.translation;
hasTransformation = true;
}
if (node.rotation !== undefined) {
rotation = node.rotation;
hasTransformation = true;
}
if (node.scale !== undefined) {
scale = node.scale;
hasTransformation = true;
}
if (hasTransformation) {
matrix.ComposeTRS (translation, rotation, scale);
}
}
let parentNodeIndex = nodeTree.GetNodeParent (nodeIndex);
if (parentNodeIndex !== null) {
let parentMatrix = GetNodeTransformation (gltf, parentNodeIndex, nodeTree);
matrix = matrix.MultiplyMatrix (parentMatrix);
}
nodeTree.AddNodeMatrix (nodeIndex, matrix);
return matrix;
}
let gltfNode = gltf.nodes[nodeIndex];
let gltfMeshIndex = gltfNode.mesh;
let gltfMesh = gltf.meshes[gltfMeshIndex];
let mesh = new OV.Mesh ();
this.model.AddMesh (mesh);
if (gltfMesh.name !== undefined) {
mesh.SetName (gltfMesh.name);
}
for (let i = 0; i < gltfMesh.primitives.length; i++) {
let primitive = gltfMesh.primitives[i];
this.ImportPrimitive (gltf, primitive, mesh);
}
let matrix = GetNodeTransformation (gltf, nodeIndex, nodeTree);
let transformation = new OV.Transformation (matrix);
OV.TransformMesh (mesh, transformation);
}
ImportPrimitive (gltf, primitive, mesh)
{
function AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset)
{
let triangle = new OV.Triangle (vertexOffset + v0, vertexOffset + v1, vertexOffset + v2);
if (hasNormals) {
triangle.SetNormals (
normalOffset + v0,
normalOffset + v1,
normalOffset + v2
);
}
if (hasUVs) {
triangle.SetTextureUVs (
uvOffset + v0,
uvOffset + v1,
uvOffset + v2
);
}
if (primitive.material !== undefined) {
triangle.mat = primitive.material;
}
mesh.AddTriangle (triangle);
}
if (primitive.attributes === undefined) {
return;
}
let hasVertices = (primitive.attributes.POSITION !== undefined);
let hasNormals = (primitive.attributes.NORMAL !== undefined);
let hasUVs = (primitive.attributes.TEXCOORD_0 !== undefined);
let hasIndices = (primitive.indices !== undefined);
if (!hasVertices) {
return;
}
let mode = OV.GltfRenderMode.TRIANGLES;
if (primitive.mode !== undefined) {
mode = primitive.mode;
}
if (mode !== OV.GltfRenderMode.TRIANGLES && mode !== OV.GltfRenderMode.TRIANGLE_STRIP && mode !== OV.GltfRenderMode.TRIANGLE_FAN) {
return;
}
let vertexOffset = mesh.VertexCount ();
let normalOffset = mesh.NormalCount ();
let uvOffset = mesh.TextureUVCount ();
if (hasVertices) {
let accessor = gltf.accessors[primitive.attributes.POSITION];
let reader = this.GetReaderFromAccessor (gltf, accessor);
if (reader === null) {
return;
}
reader.EnumerateData (function (data) {
mesh.AddVertex (data);
});
}
if (hasNormals) {
let accessor = gltf.accessors[primitive.attributes.NORMAL];
let reader = this.GetReaderFromAccessor (gltf, accessor);
if (reader === null) {
return;
}
reader.EnumerateData (function (data) {
mesh.AddNormal (data);
});
}
if (hasUVs) {
let accessor = gltf.accessors[primitive.attributes.TEXCOORD_0];
let reader = this.GetReaderFromAccessor (gltf, accessor);
if (reader === null) {
return;
}
reader.EnumerateData (function (data) {
data.y = -data.y;
mesh.AddTextureUV (data);
});
}
let vertexIndices = [];
if (hasIndices) {
let accessor = gltf.accessors[primitive.indices];
let reader = this.GetReaderFromAccessor (gltf, accessor);
if (reader === null) {
return;
}
reader.EnumerateData (function (data) {
vertexIndices.push (data);
});
} else {
for (let i = 0; i < mesh.VertexCount (); i++) {
vertexIndices.push (i);
}
}
if (mode === OV.GltfRenderMode.TRIANGLES) {
for (let i = 0; i < vertexIndices.length; i += 3) {
let v0 = vertexIndices[i];
let v1 = vertexIndices[i + 1];
let v2 = vertexIndices[i + 2];
AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset);
}
} else if (mode === OV.GltfRenderMode.TRIANGLE_STRIP) {
for (let i = 0; i < vertexIndices.length - 2; i++) {
let v0 = vertexIndices[i];
let v1 = vertexIndices[i + 1];
let v2 = vertexIndices[i + 2];
if (i % 2 === 1) {
let tmp = v1;
v1 = v2;
v2 = tmp;
}
AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset);
}
} else if (mode === OV.GltfRenderMode.TRIANGLE_FAN) {
for (let i = 1; i < vertexIndices.length - 1; i++) {
let v0 = vertexIndices[0];
let v1 = vertexIndices[i];
let v2 = vertexIndices[i + 1];
AddTriangle (mesh, v0, v1, v2, hasNormals, hasUVs, vertexOffset, normalOffset, uvOffset);
}
}
}
GetReaderFromBufferView (bufferView)
{
let bufferIndex = bufferView.buffer || 0;
let buffer = this.bufferContents[bufferIndex];
if (buffer === null) {
return null;
}
let reader = new OV.GltfBufferReader (buffer);
reader.SkipBytes (bufferView.byteOffset || 0);
let byteStride = bufferView.byteStride;
if (byteStride !== undefined) {
reader.SetByteStride (byteStride);
}
return reader;
}
GetReaderFromAccessor (gltf, accessor)
{
let bufferViewIndex = accessor.bufferView || 0;
let bufferView = gltf.bufferViews[bufferViewIndex];
let reader = this.GetReaderFromBufferView (bufferView);
if (reader === null) {
return null;
}
reader.SetComponentType (accessor.componentType);
reader.SetDataType (accessor.type);
reader.SetDataCount (accessor.count);
reader.SkipBytes (accessor.byteOffset || 0);
if (accessor.sparse !== undefined) {
let indexReader = this.GetReaderFromSparseAccessor (gltf, accessor.sparse.indices, accessor.sparse.indices.componentType, 'SCALAR', accessor.sparse.count);
let valueReader = this.GetReaderFromSparseAccessor (gltf, accessor.sparse.values, accessor.componentType, accessor.type, accessor.sparse.count);
if (indexReader !== null && valueReader !== null) {
reader.SetSparseReader (indexReader, valueReader);
}
}
return reader;
}
GetReaderFromSparseAccessor (gltf, sparseAccessor, componentType, type, count)
{
if (sparseAccessor.bufferView === undefined) {
return null;
}
let bufferView = gltf.bufferViews[sparseAccessor.bufferView];
let reader = this.GetReaderFromBufferView (bufferView);
if (reader === null) {
return null;
}
reader.SetComponentType (componentType);
reader.SetDataType (type);
reader.SetDataCount (count);
reader.SkipBytes (sparseAccessor.byteOffset || 0);
return reader;
}
};

View File

@ -0,0 +1,385 @@
OV.ImporterObj = class extends OV.ImporterBase
{
constructor ()
{
super ();
this.globalVertices = null;
this.globalNormals = null;
this.globalUvs = null;
this.currentMesh = null;
this.currentMeshData = null;
this.currentMaterial = null;
this.currentMaterialIndex = null;
this.meshNameToMeshData = null;
this.materialNameToIndex = null;
}
ResetState ()
{
this.globalVertices = [];
this.globalNormals = [];
this.globalUvs = [];
this.currentMesh = null;
this.currentMeshData = null;
this.currentMaterial = null;
this.currentMaterialIndex = null;
this.meshNameToMeshData = {};
this.materialNameToIndex = {};
}
CanImportExtension (extension)
{
return extension === 'obj';
}
GetKnownFileFormats ()
{
return {
'obj' : OV.FileFormat.Text,
'mtl' : OV.FileFormat.Text
};
}
GetUpDirection ()
{
return OV.Direction.Y;
}
ImportContent (fileContent)
{
let obj = this;
OV.ReadLines (fileContent, function (line) {
if (!obj.IsError ()) {
obj.ProcessLine (line);
}
});
}
ProcessLine (line)
{
if (line[0] === '#') {
return;
}
let parameters = OV.ParametersFromLine (line, '#');
if (parameters.length === 0) {
return;
}
let keyword = parameters[0].toLowerCase ();
parameters.shift ();
if (this.ProcessMeshParameter (keyword, parameters, line)) {
return;
}
if (this.ProcessMaterialParameter (keyword, parameters, line)) {
return;
}
}
AddNewMesh (name)
{
let meshData = this.meshNameToMeshData[name];
if (meshData === undefined) {
let mesh = new OV.Mesh ();
if (name !== null) {
mesh.SetName (name);
}
this.model.AddMesh (mesh);
meshData = {
mesh : mesh,
data : {
globalToCurrentVertices : {},
globalToCurrentNormals : {},
globalToCurrentUvs : {}
}
};
this.meshNameToMeshData[name] = meshData;
}
this.currentMesh = meshData.mesh;
this.currentMeshData = meshData.data;
}
ProcessMeshParameter (keyword, parameters, line)
{
if (keyword === 'g' || keyword === 'o') {
if (parameters.length === 0) {
return true;
}
let name = OV.NameFromLine (line, keyword.length, '#');
this.AddNewMesh (name);
return true;
} else if (keyword === 'v') {
if (parameters.length < 3) {
return true;
}
this.globalVertices.push (new OV.Coord3D (
parseFloat (parameters[0]),
parseFloat (parameters[1]),
parseFloat (parameters[2])
));
return true;
} else if (keyword === 'vn') {
if (parameters.length < 3) {
return true;
}
this.globalNormals.push (new OV.Coord3D (
parseFloat (parameters[0]),
parseFloat (parameters[1]),
parseFloat (parameters[2])
));
return true;
} else if (keyword === 'vt') {
if (parameters.length < 2) {
return true;
}
this.globalUvs.push (new OV.Coord2D (
parseFloat (parameters[0]),
parseFloat (parameters[1])
));
return true;
} else if (keyword === 'f') {
if (parameters.length < 3) {
return true;
}
this.ProcessFace (parameters);
return true;
}
return false;
}
ProcessMaterialParameter (keyword, parameters, line)
{
function CreateColor (parameters)
{
return new OV.Color (
parseInt (parseFloat (parameters[0] * 255.0), 10),
parseInt (parseFloat (parameters[1] * 255.0), 10),
parseInt (parseFloat (parameters[2] * 255.0), 10)
);
}
function CreateTexture (keyword, line, callbacks)
{
let texture = new OV.TextureMap ();
texture.name = OV.NameFromLine (line, keyword.length, '#');
let textureObjectUrl = callbacks.getTextureObjectUrl (texture.name);
if (textureObjectUrl !== null) {
texture.url = textureObjectUrl;
}
return texture;
}
let obj = this;
if (keyword === 'newmtl') {
if (parameters.length === 0) {
return true;
}
let material = new OV.Material ();
let materialName = OV.NameFromLine (line, keyword.length, '#');
let materialIndex = this.model.AddMaterial (material);
material.name = materialName;
this.currentMaterial = material;
this.materialNameToIndex[materialName] = materialIndex;
return true;
} else if (keyword === 'usemtl') {
if (parameters.length === 0) {
return true;
}
let materialName = OV.NameFromLine (line, keyword.length, '#');
let materialIndex = this.materialNameToIndex[materialName];
if (materialIndex !== undefined) {
this.currentMaterialIndex = materialIndex;
}
return true;
} else if (keyword === 'mtllib') {
if (parameters.length === 0) {
return true;
}
let fileName = OV.NameFromLine (line, keyword.length, '#');
let fileContent = this.callbacks.getFileContent (fileName);
if (fileContent !== null) {
OV.ReadLines (fileContent, function (line) {
if (!obj.IsError ()) {
obj.ProcessLine (line);
}
});
}
return true;
} else if (keyword === 'map_kd') {
if (this.currentMaterial === null || parameters.length === 0) {
return true;
}
this.currentMaterial.diffuseMap = CreateTexture (keyword, line, this.callbacks);
return true;
} else if (keyword === 'map_ks') {
if (this.currentMaterial === null || parameters.length === 0) {
return true;
}
this.currentMaterial.specularMap = CreateTexture (keyword, line, this.callbacks);
return true;
} else if (keyword === 'map_bump' || keyword === 'bump') {
if (this.currentMaterial === null || parameters.length === 0) {
return true;
}
this.currentMaterial.bumpMap = CreateTexture (keyword, line, this.callbacks);
return true;
} else if (keyword === 'ka') {
if (this.currentMaterial === null || parameters.length < 3) {
return true;
}
this.currentMaterial.ambient = CreateColor (parameters);
return true;
} else if (keyword === 'kd') {
if (this.currentMaterial === null || parameters.length < 3) {
return true;
}
this.currentMaterial.diffuse = CreateColor (parameters);
return true;
} else if (keyword === 'ks') {
if (this.currentMaterial === null || parameters.length < 3) {
return true;
}
this.currentMaterial.specular = CreateColor (parameters);
return true;
} else if (keyword === 'ns') {
if (this.currentMaterial === null || parameters.length < 1) {
return true;
}
this.currentMaterial.shininess = parseFloat (parameters[0]) / 100.0;
return true;
} else if (keyword === 'tr') {
if (this.currentMaterial === null || parameters.length < 1) {
return true;
}
this.currentMaterial.opacity = 1.0 - parseFloat (parameters[0]);
this.currentMaterial.transparent = OV.IsLower (this.currentMaterial.opacity, 1.0);
return true;
} else if (keyword === 'd') {
if (this.currentMaterial === null || parameters.length < 1) {
return true;
}
this.currentMaterial.opacity = parseFloat (parameters[0]);
this.currentMaterial.transparent = OV.IsLower (this.currentMaterial.opacity, 1.0);
return true;
}
return false;
}
ProcessFace (parameters)
{
function GetRelativeIndex (index, count)
{
if (index > 0) {
return index - 1;
} else {
return count + index;
}
}
function GetLocalIndex (globalValueArray, globalToCurrentIndices, globalIndex, valueAdderFunc)
{
if (isNaN (globalIndex) || globalIndex < 0 || globalIndex >= globalValueArray.length) {
return null;
}
let result = globalToCurrentIndices[globalIndex];
if (result === undefined) {
let globalValue = globalValueArray[globalIndex];
if (globalValue === undefined) {
return null;
}
result = valueAdderFunc (globalValue);
globalToCurrentIndices[globalIndex] = result;
}
return result;
}
function GetLocalVertexIndex (obj, mesh, globalIndex)
{
return GetLocalIndex (obj.globalVertices, obj.currentMeshData.globalToCurrentVertices, globalIndex, function (val) {
return mesh.AddVertex (new OV.Coord3D (val.x, val.y, val.z));
});
}
function GetLocalNormalIndex (obj, mesh, globalIndex)
{
return GetLocalIndex (obj.globalNormals, obj.currentMeshData.globalToCurrentNormals, globalIndex, function (val) {
return mesh.AddNormal (new OV.Coord3D (val.x, val.y, val.z));
});
}
function GetLocalUVIndex (obj, mesh, globalIndex)
{
return GetLocalIndex (obj.globalUvs, obj.currentMeshData.globalToCurrentUvs, globalIndex, function (val) {
return mesh.AddTextureUV (new OV.Coord2D (val.x, val.y));
});
}
let vertices = [];
let normals = [];
let uvs = [];
for (let i = 0; i < parameters.length; i++) {
let vertexParams = parameters[i].split ('/');
vertices.push (GetRelativeIndex (parseInt (vertexParams[0], 10), this.globalVertices.length));
if (vertexParams.length > 1 && vertexParams[1].length > 0) {
uvs.push (GetRelativeIndex (parseInt (vertexParams[1], 10), this.globalUvs.length));
}
if (vertexParams.length > 2 && vertexParams[2].length > 0) {
normals.push (GetRelativeIndex (parseInt (vertexParams[2], 10), this.globalNormals.length));
}
}
if (this.currentMesh === null) {
this.AddNewMesh ('');
}
for (let i = 0; i < vertices.length - 2; i++) {
let v0 = GetLocalVertexIndex (this, this.currentMesh, vertices[0]);
let v1 = GetLocalVertexIndex (this, this.currentMesh, vertices[i + 1]);
let v2 = GetLocalVertexIndex (this, this.currentMesh, vertices[i + 2]);
if (v0 === null || v1 === null || v2 === null) {
this.SetError ();
this.SetMessage ('Invalid vertex index.');
break;
}
let triangle = new OV.Triangle (v0, v1, v2);
if (normals.length === vertices.length) {
let n0 = GetLocalNormalIndex (this, this.currentMesh, normals[0]);
let n1 = GetLocalNormalIndex (this, this.currentMesh, normals[i + 1]);
let n2 = GetLocalNormalIndex (this, this.currentMesh, normals[i + 2]);
if (n0 === null || n1 === null || n2 === null) {
this.SetError ();
this.SetMessage ('Invalid normal index.');
break;
}
triangle.SetNormals (n0, n1, n2);
}
if (uvs.length === vertices.length) {
let u0 = GetLocalUVIndex (this, this.currentMesh, uvs[0]);
let u1 = GetLocalUVIndex (this, this.currentMesh, uvs[i + 1]);
let u2 = GetLocalUVIndex (this, this.currentMesh, uvs[i + 2]);
if (u0 === null || u1 === null || u2 === null) {
this.SetError ();
this.SetMessage ('Invalid uv index.');
break;
}
triangle.SetTextureUVs (u0, u1, u2);
}
if (this.currentMaterialIndex !== null) {
triangle.mat = this.currentMaterialIndex;
}
this.currentMesh.AddTriangle (triangle);
}
}
};

View File

@ -0,0 +1,103 @@
OV.ImporterOff = class extends OV.ImporterBase
{
constructor ()
{
super ();
this.model = null;
this.mesh = null;
this.status = null;
}
ResetState ()
{
this.mesh = new OV.Mesh ();
this.model.AddMesh (this.mesh);
this.status = {
vertexCount : 0,
faceCount : 0,
foundVertex : 0,
foundFace : 0
};
}
CanImportExtension (extension)
{
return extension === 'off';
}
GetKnownFileFormats ()
{
return {
'off' : OV.FileFormat.Text
};
}
GetUpDirection ()
{
return OV.Direction.Y;
}
ImportContent (fileContent)
{
let obj = this;
OV.ReadLines (fileContent, function (line) {
if (!obj.IsError ()) {
obj.ProcessLine (line);
}
});
}
ProcessLine (line)
{
if (line[0] === '#') {
return;
}
let parameters = OV.ParametersFromLine (line, '#');
if (parameters.length === 0) {
return;
}
if (parameters[0] === 'OFF') {
return;
}
if (this.status.vertexCount === 0 && this.status.faceCount === 0) {
if (parameters.length > 1) {
this.status.vertexCount = parseInt (parameters[0], 10);
this.status.faceCount = parseInt (parameters[1], 10);
}
return;
}
if (this.status.foundVertex < this.status.vertexCount) {
if (parameters.length >= 3) {
this.mesh.AddVertex (new OV.Coord3D (
parseFloat (parameters[0]),
parseFloat (parameters[1]),
parseFloat (parameters[2])
));
this.status.foundVertex += 1;
}
return;
}
if (this.status.foundFace < this.status.faceCount) {
if (parameters.length >= 4) {
let vertexCount = parseInt (parameters[0], 10);
if (parameters.length < vertexCount + 1) {
return;
}
for (let i = 0; i < vertexCount - 2; i++) {
let v0 = parseInt (parameters[1]);
let v1 = parseInt (parameters[i + 2]);
let v2 = parseInt (parameters[i + 3]);
let triangle = new OV.Triangle (v0, v1, v2);
this.mesh.AddTriangle (triangle);
}
this.status.foundFace += 1;
}
return;
}
}
};

View File

@ -0,0 +1,428 @@
OV.PlyHeader = class
{
constructor ()
{
this.format = null;
this.elements = [];
}
SetFormat (format)
{
this.format = format;
}
AddElement (name, count)
{
this.elements.push ({
name : name,
count : count,
format : []
});
}
GetElements ()
{
return this.elements;
}
AddSingleFormat (elemType, name)
{
let lastElement = this.elements[this.elements.length - 1];
lastElement.format.push ({
name : name,
isSingle : true,
elemType : elemType
});
}
AddListFormat (countType, elemType, name)
{
let lastElement = this.elements[this.elements.length - 1];
lastElement.format.push ({
name : name,
isSingle : false,
countType : countType,
elemType : elemType
});
}
GetElement (name)
{
for (let i = 0; i < this.elements.length; i++) {
let element = this.elements[i];
if (element.name === name) {
return element;
}
}
return null;
}
Check ()
{
let vertex = this.GetElement ('vertex');
if (vertex === null || vertex.length === 0 || vertex.format.length < 3) {
return false;
}
let face = this.GetElement ('face');
if (this.format === 'ascii') {
if (face === null || face.count === 0 || face.format.length < 0) {
return false;
}
} else if (this.format === 'binary_little_endian' || this.format === 'binary_big_endian') {
let triStrips = this.GetElement ('tristrips');
let hasFaces = (face !== null && face.count > 0 && face.format.length > 0);
let hasTriStrips = (triStrips !== null && triStrips.count > 0 && triStrips.format.length > 0);
if (!hasFaces && !hasTriStrips) {
return false;
}
} else {
return false;
}
return true;
}
};
OV.ImporterPly = class extends OV.ImporterBase
{
constructor ()
{
super ();
this.model = null;
this.mesh = null;
}
ResetState ()
{
this.mesh = new OV.Mesh ();
this.model.AddMesh (this.mesh);
}
CanImportExtension (extension)
{
return extension === 'ply';
}
GetKnownFileFormats ()
{
return {
'ply' : OV.FileFormat.Binary
};
}
GetUpDirection ()
{
return OV.Direction.Y;
}
ImportContent (fileContent)
{
let headerString = this.GetHeaderContent (fileContent);
let header = this.ReadHeader (headerString);
if (!header.Check ()) {
this.SetError ();
this.SetMessage ('Invalid header information.');
return;
}
if (header.format === 'ascii') {
let contentString = OV.ArrayBufferToUtf8String (fileContent);
contentString = contentString.substr (headerString.length);
this.ReadAsciiContent (header, contentString);
} else if (header.format === 'binary_little_endian' || header.format === 'binary_big_endian') {
this.ReadBinaryContent (header, fileContent, headerString.length);
}
}
GetHeaderContent (fileContent)
{
let headerContent = '';
let bufferView = new Uint8Array (fileContent);
let bufferIndex = 0;
for (bufferIndex = 0; bufferIndex < fileContent.byteLength; bufferIndex++) {
headerContent += String.fromCharCode (bufferView[bufferIndex]);
if (headerContent.endsWith ('end_header')) {
break;
}
}
bufferIndex += 1;
while (bufferIndex < fileContent.byteLength) {
let char = String.fromCharCode (bufferView[bufferIndex]);
headerContent += char;
bufferIndex += 1;
if (char === '\n') {
break;
}
}
return headerContent;
}
ReadHeader (headerContent)
{
let header = new OV.PlyHeader ();
OV.ReadLines (headerContent, function (line) {
let parameters = OV.ParametersFromLine (line, null);
if (parameters.length === 0 || parameters[0] === 'comment') {
return;
}
if (parameters[0] === 'ply') {
return;
} else if (parameters[0] === 'format' && parameters.length >= 2) {
header.SetFormat (parameters[1]);
} else if (parameters[0] === 'element' && parameters.length >= 3) {
header.AddElement (parameters[1], parseInt (parameters[2], 10));
} else if (parameters[0] === 'property' && parameters.length >= 3) {
if (parameters[1] === 'list' && parameters.length >= 5) {
header.AddListFormat (parameters[2], parameters[3], parameters[4]);
} else {
header.AddSingleFormat (parameters[1], parameters[2]);
}
}
});
return header;
}
ReadAsciiContent (header, fileContent)
{
let obj = this;
let vertex = header.GetElement ('vertex');
let face = header.GetElement ('face');
let foundVertex = 0;
let foundFace = 0;
OV.ReadLines (fileContent, function (line) {
if (obj.IsError ()) {
return;
}
let parameters = OV.ParametersFromLine (line, null);
if (parameters.length === 0 || parameters[0] === 'comment') {
return;
}
if (foundVertex < vertex.count) {
if (parameters.length >= 3) {
obj.mesh.AddVertex (new OV.Coord3D (
parseFloat (parameters[0]),
parseFloat (parameters[1]),
parseFloat (parameters[2])
));
foundVertex += 1;
}
return;
}
if (foundFace < face.count) {
if (parameters.length >= 4) {
let vertexCount = parseInt (parameters[0], 10);
if (parameters.length < vertexCount + 1) {
return;
}
for (let i = 0; i < vertexCount - 2; i++) {
let v0 = parseInt (parameters[1]);
let v1 = parseInt (parameters[i + 2]);
let v2 = parseInt (parameters[i + 3]);
let triangle = new OV.Triangle (v0, v1, v2);
obj.mesh.AddTriangle (triangle);
}
foundFace += 1;
}
return;
}
});
}
ReadBinaryContent (header, fileContent, headerLength)
{
function SkipAndGetColor (obj, reader, format, startIndex)
{
let r = null;
let g = null;
let b = null;
let a = 255;
for (let i = startIndex; i < format.length; i++) {
let currFormat = format[i];
let val = obj.ReadByFormat (reader, currFormat);
if (currFormat.name === 'red') {
r = val;
} else if (currFormat.name === 'green') {
g = val;
} else if (currFormat.name === 'blue') {
b = val;
} else if (currFormat.name === 'alpha') {
a = val;
}
}
if (r !== null && g !== null && b !== null) {
return [r, g, b, a];
}
return null;
}
function GetMaterialFromColor (obj, color, colorToMaterial)
{
function IntegerToHex (intVal)
{
let result = parseInt (intVal, 10).toString (16);
while (result.length < 2) {
result = '0' + result;
}
return result;
}
if (color === null) {
return null;
}
let materialName = '#' + IntegerToHex (color[0]) + IntegerToHex (color[1]) + IntegerToHex (color[2]) + IntegerToHex (color[3]);
let materialIndex = colorToMaterial[materialName];
if (materialIndex === undefined) {
let material = new OV.Material ();
material.name = materialName;
material.diffuse = new OV.Color (color[0], color[1], color[2]);
material.opacity = color[3] / 255.0;
material.transparent = OV.IsLower (material.opacity, 1.0);
materialIndex = obj.model.AddMaterial (material);
colorToMaterial[materialName] = materialIndex;
}
return materialIndex;
}
function GetFaceColorByVertexColors (obj, v0, v1, v2, vertexColors, colorToMaterial)
{
let c0 = vertexColors[v0];
let c1 = vertexColors[v1];
let c2 = vertexColors[v2];
let d0 = Math.abs (c0[0] - c1[0]) + Math.abs (c0[1] - c1[1]) + Math.abs (c0[2] - c1[2]);
let d1 = Math.abs (c1[0] - c2[0]) + Math.abs (c1[1] - c2[1]) + Math.abs (c1[2] - c2[2]);
let d2 = Math.abs (c0[0] - c2[0]) + Math.abs (c0[1] - c2[1]) + Math.abs (c0[2] - c2[2]);
let min = Math.min (d0, d1, d2);
let color = c0;
if (min === d1) {
color = c1;
}
return GetMaterialFromColor (obj, color, colorToMaterial);
}
let reader = null;
if (header.format === 'binary_little_endian') {
reader = new OV.BinaryReader (fileContent, true);
} else if (header.format === 'binary_big_endian') {
reader = new OV.BinaryReader (fileContent, false);
} else {
return;
}
reader.Skip (headerLength);
let vertexColors = [];
let colorToMaterial = {};
let elements = header.GetElements ();
for (let elementIndex = 0; elementIndex < elements.length; elementIndex++) {
let element = elements[elementIndex];
if (element.name === 'vertex') {
for (let vertexIndex = 0; vertexIndex < element.count; vertexIndex++) {
let x = this.ReadByFormat (reader, element.format[0]);
let y = this.ReadByFormat (reader, element.format[1]);
let z = this.ReadByFormat (reader, element.format[2]);
let color = SkipAndGetColor (this, reader, element.format, 3);
if (color !== null) {
vertexColors.push (color);
}
this.mesh.AddVertex (new OV.Coord3D (x, y, z));
}
} else if (element.name === 'face') {
for (let faceIndex = 0; faceIndex < element.count; faceIndex++) {
let vertices = this.ReadByFormat (reader, element.format[0]);
let faceColor = SkipAndGetColor (this, reader, element.format, 1);
for (let i = 0; i < vertices.length - 2; i++) {
let v0 = vertices[0];
let v1 = vertices[i + 1];
let v2 = vertices[i + 2];
let triangle = new OV.Triangle (v0, v1, v2);
if (faceColor !== null) {
triangle.mat = GetMaterialFromColor (this, faceColor, colorToMaterial);
} else if (vertexColors.length === this.model.VertexCount ()) {
triangle.mat = GetFaceColorByVertexColors (this, v0, v1, v2, vertexColors, colorToMaterial);
}
this.mesh.AddTriangle (triangle);
}
}
} else if (element.name === 'tristrips') {
for (let triStripIndex = 0; triStripIndex < element.count; triStripIndex++) {
let vertices = this.ReadByFormat (reader, element.format[0]);
this.SkipFormat (reader, element.format, 1);
let ccw = true;
for (let i = 0; i < vertices.length - 2; i++) {
let v0 = vertices[i];
let v1 = vertices[i + 1];
let v2 = vertices[i + 2];
if (v2 === -1) {
i += 2;
ccw = true;
continue;
}
if (!ccw) {
let tmp = v1;
v1 = v2;
v2 = tmp;
}
ccw = !ccw;
let triangle = new OV.Triangle (v0, v1, v2);
this.mesh.AddTriangle (triangle);
}
}
} else {
this.SkipFormat (reader, element.format, 0);
}
}
}
SkipFormat (reader, format, startIndex)
{
for (let i = startIndex; i < format.length; i++) {
this.ReadByFormat (reader, format[i]);
}
}
ReadByFormat (reader, format)
{
function ReadType (reader, type)
{
if (type === 'char' || type === 'int8') {
return reader.ReadCharacter8 ();
} else if (type === 'uchar' || type === 'uint8') {
return reader.ReadUnsignedCharacter8 ();
} else if (type === 'short' || type === 'int16') {
return reader.ReadInteger16 ();
} else if (type === 'ushort' || type === 'uint16') {
return reader.ReadUnsignedInteger16 ();
} else if (type === 'int' || type === 'int32') {
return reader.ReadInteger32 ();
} else if (type === 'uint' || type === 'uint32') {
return reader.ReadUnsignedInteger32 ();
} else if (type === 'float' || type === 'float32') {
return reader.ReadFloat32 ();
} else if (type === 'double' || type === 'double64') {
return reader.ReadDouble64 ();
}
return null;
}
if (format.isSingle) {
return ReadType (reader, format.elemType);
} else {
let list = [];
let count = ReadType (reader, format.countType);
for (let i = 0; i < count; i++) {
list.push (ReadType (reader, format.elemType));
}
return list;
}
}
};

View File

@ -0,0 +1,168 @@
OV.ImporterStl = class extends OV.ImporterBase
{
constructor ()
{
super ();
this.mesh = null;
this.triangle = null;
}
ResetState ()
{
this.mesh = new OV.Mesh ();
this.model.AddMesh (this.mesh);
this.triangle = null;
}
CanImportExtension (extension)
{
return extension === 'stl';
}
GetKnownFileFormats ()
{
return {
'stl' : OV.FileFormat.Binary
};
}
GetUpDirection ()
{
return OV.Direction.Z;
}
ImportContent (fileContent)
{
if (this.IsBinaryStlFile (fileContent)) {
this.ProcessBinary (fileContent);
} else {
let obj = this;
let textContent = OV.ArrayBufferToUtf8String (fileContent);
OV.ReadLines (textContent, function (line) {
if (!obj.IsError ()) {
obj.ProcessLine (line);
}
});
}
}
IsBinaryStlFile (fileContent)
{
let byteLength = fileContent.byteLength;
if (byteLength < 84) {
return false;
}
let reader = new OV.BinaryReader (fileContent, true);
reader.Skip (80);
let triangleCount = reader.ReadUnsignedInteger32 ();
if (byteLength !== triangleCount * 50 + 84) {
return false;
}
return true;
}
ProcessLine (line)
{
if (line[0] === '#') {
return;
}
let parameters = OV.ParametersFromLine (line, '#');
if (parameters.length === 0) {
return;
}
let keyword = parameters[0];
if (keyword === 'solid') {
if (parameters.length > 1) {
let name = OV.NameFromLine (line, keyword.length, '#');
this.mesh.SetName (name);
}
return;
}
if (keyword === 'facet') {
this.triangle = new OV.Triangle (-1, -1, -1);
if (parameters.length >= 5 && parameters[1] === 'normal') {
let normalVector = new OV.Coord3D (
parseFloat (parameters[2]),
parseFloat (parameters[3]),
parseFloat (parameters[4])
);
if (OV.IsPositive (normalVector.Length ())) {
let normalIndex = this.mesh.AddNormal (normalVector);
this.triangle.SetNormals (
normalIndex,
normalIndex,
normalIndex
);
}
}
return;
}
if (keyword === 'vertex' && this.triangle !== null) {
if (parameters.length >= 4) {
let vertexIndex = this.mesh.AddVertex (new OV.Coord3D (
parseFloat (parameters[1]),
parseFloat (parameters[2]),
parseFloat (parameters[3])
));
if (this.triangle.v0 === -1) {
this.triangle.v0 = vertexIndex;
} else if (this.triangle.v1 === -1) {
this.triangle.v1 = vertexIndex;
} else if (this.triangle.v2 === -1) {
this.triangle.v2 = vertexIndex;
}
}
return;
}
if (keyword === 'endfacet' && this.triangle !== null) {
if (this.triangle.v0 !== -1 && this.triangle.v1 !== -1 && this.triangle.v2 !== null) {
this.mesh.AddTriangle (this.triangle);
}
this.triangle = null;
return;
}
}
ProcessBinary (fileContent)
{
function ReadVector (reader)
{
let coord = new OV.Coord3D ();
coord.x = reader.ReadFloat32 ();
coord.y = reader.ReadFloat32 ();
coord.z = reader.ReadFloat32 ();
return coord;
}
function AddVertex (mesh, reader)
{
let coord = ReadVector (reader);
return mesh.AddVertex (coord);
}
let reader = new OV.BinaryReader (fileContent, true);
reader.Skip (80);
let triangleCount = reader.ReadUnsignedInteger32 ();
for (let i = 0; i < triangleCount; i++) {
let normalVector = ReadVector (reader);
let v0 = AddVertex (this.mesh, reader);
let v1 = AddVertex (this.mesh, reader);
let v2 = AddVertex (this.mesh, reader);
reader.Skip (2);
let triangle = new OV.Triangle (v0, v1, v2);
if (OV.IsPositive (normalVector.Length ())) {
let normal = this.mesh.AddNormal (normalVector);
triangle.SetNormals (normal, normal, normal);
}
this.mesh.AddTriangle (triangle);
}
}
};

View File

@ -0,0 +1,72 @@
OV.NameFromLine = function (line, startIndex, commentChar)
{
let name = line.substr (startIndex);
let commentStart = name.indexOf (commentChar);
if (commentStart !== -1) {
name = name.substr (0, commentStart);
}
return name.trim ();
};
OV.ParametersFromLine = function (line, commentChar)
{
if (commentChar !== null) {
let commentStart = line.indexOf (commentChar);
if (commentStart !== -1) {
line = line.substr (0, commentStart).trim ();
}
}
return line.split (/\s+/u);
};
OV.ReadLines = function (str, onLine)
{
function LineFound (line, onLine)
{
let trimmed = line.trim ();
if (trimmed.length > 0) {
onLine (trimmed);
}
}
let cursor = 0;
let next = str.indexOf ('\n', cursor);
while (next !== -1) {
LineFound (str.substr (cursor, next - cursor), onLine);
cursor = next + 1;
next = str.indexOf ('\n', cursor);
}
LineFound (str.substr (cursor), onLine);
};
OV.IsPowerOfTwo = function (x)
{
return (x & (x - 1)) === 0;
};
OV.NextPowerOfTwo = function (x)
{
if (OV.IsPowerOfTwo (x)) {
return x;
}
let npot = Math.pow (2, Math.ceil (Math.log (x) / Math.log (2)));
return parseInt (npot, 10);
};
OV.ResizeImageToPowerOfTwoSides = function (image)
{
if (OV.IsPowerOfTwo (image.width) && OV.IsPowerOfTwo (image.height)) {
return image;
}
let width = OV.NextPowerOfTwo (image.width);
let height = OV.NextPowerOfTwo (image.height);
let canvas = document.createElement ('canvas');
canvas.width = width;
canvas.height = height;
let context = canvas.getContext ('2d');
context.drawImage (image, 0, 0, width, height);
return context.getImageData (0, 0, width, height);
};

109
source/io/binaryreader.js Normal file
View File

@ -0,0 +1,109 @@
OV.BinaryReader = class
{
constructor (arrayBuffer, isLittleEndian)
{
this.arrayBuffer = arrayBuffer;
this.dataView = new DataView (arrayBuffer);
this.isLittleEndian = isLittleEndian;
this.position = 0;
}
GetPosition ()
{
return this.position;
}
SetPosition (position)
{
this.position = position;
}
GetByteLength ()
{
return this.arrayBuffer.byteLength;
}
Skip (bytes)
{
this.position = this.position + bytes;
}
End ()
{
return this.position >= this.arrayBuffer.byteLength;
}
ReadArrayBuffer (byteLength)
{
let originalBufferView = new Uint8Array (this.arrayBuffer);
let arrayBuffer = new ArrayBuffer (byteLength);
let bufferView = new Uint8Array (arrayBuffer);
let subArray = originalBufferView.subarray (this.position, this.position + byteLength);
bufferView.set (subArray, 0);
this.position += byteLength;
return arrayBuffer;
}
ReadBoolean8 ()
{
let result = this.dataView.getInt8 (this.position);
this.position = this.position + 1;
return result ? true : false;
}
ReadCharacter8 ()
{
let result = this.dataView.getInt8 (this.position);
this.position = this.position + 1;
return result;
}
ReadUnsignedCharacter8 ()
{
let result = this.dataView.getUint8 (this.position);
this.position = this.position + 1;
return result;
}
ReadInteger16 ()
{
let result = this.dataView.getInt16 (this.position, this.isLittleEndian);
this.position = this.position + 2;
return result;
}
ReadUnsignedInteger16 ()
{
let result = this.dataView.getUint16 (this.position, this.isLittleEndian);
this.position = this.position + 2;
return result;
}
ReadInteger32 ()
{
let result = this.dataView.getInt32 (this.position, this.isLittleEndian);
this.position = this.position + 4;
return result;
}
ReadUnsignedInteger32 ()
{
let result = this.dataView.getUint32 (this.position, this.isLittleEndian);
this.position = this.position + 4;
return result;
}
ReadFloat32 ()
{
let result = this.dataView.getFloat32 (this.position, this.isLittleEndian);
this.position = this.position + 4;
return result;
}
ReadDouble64 ()
{
let result = this.dataView.getFloat64 (this.position, this.isLittleEndian);
this.position = this.position + 8;
return result;
}
};

92
source/io/binarywriter.js Normal file
View File

@ -0,0 +1,92 @@
OV.BinaryWriter = class
{
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;
}
};

56
source/io/bufferutils.js Normal file
View File

@ -0,0 +1,56 @@
OV.ArrayBufferToUtf8String = function (buffer)
{
let decoder = new TextDecoder ('utf-8');
return decoder.decode (buffer);
};
OV.ArrayBufferToAsciiString = function (buffer)
{
let text = '';
let bufferView = new Uint8Array (buffer);
for (let i = 0; i < bufferView.byteLength; i++) {
text += String.fromCharCode (bufferView[i]);
}
return text;
};
OV.AsciiStringToArrayBuffer = function (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;
};
OV.Base64DataURIToArrayBuffer = function (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.substr (dataPrefix.length, mimeSeparator - 5);
let base64String = atob (uri.substr (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
};
};

125
source/io/fileutils.js Normal file
View File

@ -0,0 +1,125 @@
OV.FileSource =
{
Url : 1,
File : 2
};
OV.FileFormat =
{
Text : 1,
Binary : 2
};
OV.GetFileName = function (filePath)
{
let firstSeparator = filePath.lastIndexOf ('/');
if (firstSeparator === -1) {
firstSeparator = filePath.lastIndexOf ('\\');
}
let fileName = filePath;
if (firstSeparator !== -1) {
fileName = filePath.substr (firstSeparator + 1);
}
return decodeURI (fileName);
};
OV.GetFileExtension = function (fileName)
{
let firstPoint = fileName.lastIndexOf ('.');
if (firstPoint === -1) {
return '';
}
return fileName.substr (firstPoint + 1);
};
OV.RequestUrl = function (url, format, callbacks)
{
function OnSuccess (result)
{
if (callbacks.success) {
callbacks.success (result);
}
if (callbacks.complete) {
callbacks.complete ();
}
}
function OnError ()
{
if (callbacks.error) {
callbacks.error ();
}
if (callbacks.complete) {
callbacks.complete ();
}
}
let request = new XMLHttpRequest ();
request.open ('GET', url, true);
if (format === OV.FileFormat.Text) {
request.responseType = 'text';
} else if (format === OV.FileFormat.Binary) {
request.responseType = 'arraybuffer';
} else {
OnError ();
return;
}
request.onload = function () {
if (request.status === 200) {
let response = request.response;
OnSuccess (response);
} else {
OnError ();
}
};
request.onerror = function () {
OnError ();
};
request.send (null);
};
OV.ReadFile = function (file, format, callbacks)
{
function OnSuccess (result)
{
if (callbacks.success) {
callbacks.success (result);
}
if (callbacks.complete) {
callbacks.complete ();
}
}
function OnError ()
{
if (callbacks.error) {
callbacks.error ();
}
if (callbacks.complete) {
callbacks.complete ();
}
}
let reader = new FileReader ();
reader.onloadend = function (event) {
if (event.target.readyState === FileReader.DONE) {
OnSuccess (event.target.result);
}
};
reader.onerror = function () {
OnError ();
};
if (format === OV.FileFormat.Text) {
reader.readAsText (file);
} else if (format === OV.FileFormat.Binary) {
reader.readAsArrayBuffer (file);
} else {
OnError ();
}
};

41
source/io/textwriter.js Normal file
View File

@ -0,0 +1,41 @@
OV.TextWriter = class
{
constructor ()
{
this.text = '';
this.indentation = 0;
}
GetText ()
{
return this.text;
}
Indent (diff)
{
this.indentation += diff;
}
WriteArrayLine (arr)
{
this.WriteLine (arr.join (' '));
}
WriteLine (str)
{
this.WriteIndentation ();
this.Write (str + '\n');
}
WriteIndentation ()
{
for (let i = 0; i < this.indentation; i++) {
this.Write (' ');
}
}
Write (str)
{
this.text += str;
}
};

100
source/model/mesh.js Normal file
View File

@ -0,0 +1,100 @@
OV.Mesh = class
{
constructor ()
{
this.name = '';
this.vertices = [];
this.normals = [];
this.uvs = [];
this.triangles = [];
}
GetName ()
{
return this.name;
}
SetName (name)
{
this.name = name;
}
VertexCount ()
{
return this.vertices.length;
}
NormalCount ()
{
return this.normals.length;
}
TextureUVCount ()
{
return this.uvs.length;
}
TriangleCount ()
{
return this.triangles.length;
}
AddVertex (vertex)
{
this.vertices.push (vertex);
return this.vertices.length - 1;
}
SetVertex (index, vertex)
{
this.vertices[index] = vertex;
}
GetVertex (index)
{
return this.vertices[index];
}
AddNormal (normal)
{
this.normals.push (normal);
return this.normals.length - 1;
}
SetNormal (index, normal)
{
this.normals[index] = normal;
}
GetNormal (index)
{
return this.normals[index];
}
AddTextureUV (uv)
{
this.uvs.push (uv);
return this.uvs.length - 1;
}
SetTextureUV (index, uv)
{
this.uvs[index] = uv;
}
GetTextureUV (index)
{
return this.uvs[index];
}
AddTriangle (triangle)
{
this.triangles.push (triangle);
return this.triangles.length - 1;
}
GetTriangle (index)
{
return this.triangles[index];
}
};

144
source/model/meshbuffer.js Normal file
View File

@ -0,0 +1,144 @@
OV.MeshPrimitiveBuffer = class
{
constructor ()
{
this.indices = [];
this.vertices = [];
this.normals = [];
this.uvs = [];
this.material = null;
}
GetByteLength (indexTypeSize, numberTypeSize)
{
let indexCount = this.indices.length;
let numberCount = this.vertices.length + this.normals.length + this.uvs.length;
return indexCount * indexTypeSize + numberCount * numberTypeSize;
}
};
OV.MeshBuffer = class
{
constructor ()
{
this.primitives = [];
}
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;
}
};
OV.ConvertMeshToMeshBuffer = function (mesh)
{
function AddVertexToPrimitiveBuffer (mesh, vertexIndex, normalIndex, uvIndex, primitiveBuffer, primitiveMaps)
{
function GetUVOrDefault (mesh, uvIndex, forceUVs)
{
if (uvIndex !== null) {
return mesh.GetTextureUV (uvIndex);
} else if (forceUVs) {
return new OV.Coord2D (0.0, 0.0);
} else {
return null;
}
}
function AddVertex (mesh, vertexIndex, normalIndex, uvIndex, primitiveBuffer)
{
let forceUVs = mesh.TextureUVCount () > 0;
let vertex = mesh.GetVertex (vertexIndex);
let normal = mesh.GetNormal (normalIndex);
let primitiveVertexIndex = primitiveBuffer.vertices.length / 3;
primitiveBuffer.indices.push (primitiveVertexIndex);
primitiveBuffer.vertices.push (vertex.x, vertex.y, vertex.z);
primitiveBuffer.normals.push (normal.x, normal.y, normal.z);
let uv = GetUVOrDefault (mesh, uvIndex, forceUVs);
if (uv !== null) {
primitiveBuffer.uvs.push (uv.x, uv.y);
}
return {
index : primitiveVertexIndex,
normal : normal,
uv : uv
};
}
function FindMatchingPrimitiveVertex (mesh, primitiveVertices, normalIndex, uvIndex)
{
for (let i = 0; i < primitiveVertices.length; i++) {
let primitiveVertex = primitiveVertices[i];
let normal = mesh.GetNormal (normalIndex);
let equalNormal = OV.CoordIsEqual3D (primitiveVertex.normal, normal);
let equalUv = false;
if (primitiveVertex.uv === null && uvIndex === null) {
equalUv = true;
} else {
let uv = GetUVOrDefault (mesh, uvIndex, true);
equalUv = OV.CoordIsEqual2D (primitiveVertex.uv, uv);
}
if (equalNormal && equalUv) {
return primitiveVertex;
}
}
return null;
}
let primitiveVertices = primitiveMaps.meshToPrimitiveVertices[vertexIndex];
if (primitiveVertices === undefined) {
let primitiveVertex = AddVertex (mesh, vertexIndex, normalIndex, uvIndex, primitiveBuffer);
primitiveMaps.meshToPrimitiveVertices[vertexIndex] = [primitiveVertex];
} else {
let existingPrimitiveVertex = FindMatchingPrimitiveVertex (mesh, primitiveVertices, normalIndex, uvIndex);
if (existingPrimitiveVertex !== null) {
primitiveBuffer.indices.push (existingPrimitiveVertex.index);
} else {
let primitiveVertex = AddVertex (mesh, vertexIndex, normalIndex, uvIndex, primitiveBuffer);
primitiveVertices.push (primitiveVertex);
}
}
}
let meshBuffer = new OV.MeshBuffer ();
let triangleCount = mesh.TriangleCount ();
if (triangleCount === 0) {
return null;
}
let triangleIndices = [];
for (let i = 0; i < triangleCount; i++) {
triangleIndices.push (i);
}
triangleIndices.sort (function (a, b) {
let aTriangle = mesh.GetTriangle (a);
let bTriangle = mesh.GetTriangle (b);
return aTriangle.mat - bTriangle.mat;
});
let primitiveBuffer = null;
let primitiveMaps = 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 OV.MeshPrimitiveBuffer ();
primitiveBuffer.material = triangle.mat;
primitiveMaps = {
meshToPrimitiveVertices : {}
};
meshBuffer.primitives.push (primitiveBuffer);
}
AddVertexToPrimitiveBuffer (mesh, triangle.v0, triangle.n0, triangle.u0, primitiveBuffer, primitiveMaps);
AddVertexToPrimitiveBuffer (mesh, triangle.v1, triangle.n1, triangle.u1, primitiveBuffer, primitiveMaps);
AddVertexToPrimitiveBuffer (mesh, triangle.v2, triangle.n2, triangle.u2, primitiveBuffer, primitiveMaps);
}
return meshBuffer;
};

105
source/model/model.js Normal file
View File

@ -0,0 +1,105 @@
OV.Model = class
{
constructor ()
{
this.name = '';
this.materials = [];
this.meshes = [];
}
GetName ()
{
return this.name;
}
SetName (name)
{
this.name = name;
}
MaterialCount ()
{
return this.materials.length;
}
MeshCount ()
{
return this.meshes.length;
}
VertexCount ()
{
let count = 0;
for (let i = 0; i < this.meshes.length; i++) {
count += this.meshes[i].VertexCount ();
}
return count;
}
NormalCount ()
{
let count = 0;
for (let i = 0; i < this.meshes.length; i++) {
count += this.meshes[i].NormalCount ();
}
return count;
}
TextureUVCount ()
{
let count = 0;
for (let i = 0; i < this.meshes.length; i++) {
count += this.meshes[i].TextureUVCount ();
}
return count;
}
TriangleCount ()
{
let count = 0;
for (let i = 0; i < this.meshes.length; i++) {
count += this.meshes[i].TriangleCount ();
}
return count;
}
AddMaterial (material)
{
this.materials.push (material);
return this.materials.length - 1;
}
GetMaterial (index)
{
return this.materials[index];
}
AddMesh (mesh)
{
this.meshes.push (mesh);
return this.meshes.length - 1;
}
AddMeshToIndex (mesh, index)
{
this.meshes.splice (index, 0, mesh);
return index;
}
RemoveMesh (index)
{
this.meshes.splice (index, 1);
}
GetMesh (index)
{
return this.meshes[index];
}
Clear ()
{
this.name = '';
this.materials = [];
this.meshes = [];
}
};

View File

@ -0,0 +1,229 @@
OV.SRGBToLinear = function (component)
{
if (component < 0.04045) {
return component * 0.0773993808;
} else {
return Math.pow (component * 0.9478672986 + 0.0521327014, 2.4);
}
};
OV.LinearToSRGB = function (component)
{
if (component < 0.0031308) {
return component * 12.92;
} else {
return 1.055 * (Math.pow (component, 0.41666)) - 0.055;
}
};
OV.Color = class
{
constructor (r, g, b)
{
this.r = r; // 0 .. 255
this.g = g; // 0 .. 255
this.b = b; // 0 .. 255
}
ToHexString ()
{
function IntegerToHex (intVal)
{
let result = parseInt (intVal, 10).toString (16);
while (result.length < 2) {
result = '0' + result;
}
return result;
}
let r = IntegerToHex (this.r);
let g = IntegerToHex (this.g);
let b = IntegerToHex (this.b);
return r + g + b;
}
Clone ()
{
return new OV.Color (this.r, this.g, this.b);
}
};
OV.TextureMap = class
{
constructor ()
{
this.name = null;
this.url = null;
this.offset = new OV.Coord2D (0.0, 0.0);
this.scale = new OV.Coord2D (1.0, 1.0);
this.rotation = 0.0; // radians
}
HasTransformation ()
{
if (!OV.CoordIsEqual2D (this.offset, new OV.Coord2D (0.0, 0.0))) {
return true;
}
if (!OV.CoordIsEqual2D (this.scale, new OV.Coord2D (1.0, 1.0))) {
return true;
}
if (!OV.IsEqual (this.rotation, 0.0)) {
return true;
}
return false;
}
Clone ()
{
let cloned = new OV.TextureMap ();
cloned.name = this.name;
cloned.url = this.url;
cloned.offset = this.offset.Clone ();
cloned.scale = this.scale.Clone ();
cloned.rotation = this.rotation;
return cloned;
}
};
OV.Material = class
{
constructor ()
{
this.name = '';
this.ambient = new OV.Color (0, 0, 0);
this.diffuse = new OV.Color (0, 0, 0);
this.specular = new OV.Color (0, 0, 0);
this.emissive = new OV.Color (0, 0, 0);
this.shininess = 0.0; // 0.0 .. 1.0
this.opacity = 1.0; // 0.0 .. 1.0
this.diffuseMap = null;
this.specularMap = null;
this.bumpMap = null;
this.normalMap = null;
this.emissiveMap = null;
this.alphaTest = 0.0; // 0.0 .. 1.0
this.transparent = false;
this.multiplyDiffuseMap = false;
}
Clone ()
{
let cloned = new OV.Material ();
cloned.name = this.name;
cloned.ambient = this.ambient.Clone ();
cloned.diffuse = this.diffuse.Clone ();
cloned.specular = this.specular.Clone ();
cloned.emissive = this.emissive.Clone ();
cloned.shininess = this.shininess;
cloned.opacity = this.opacity;
cloned.diffuseMap = this.CloneTextureMap (this.diffuseMap);
cloned.specularMap = this.CloneTextureMap (this.specularMap);
cloned.bumpMap = this.CloneTextureMap (this.bumpMap);
cloned.normalMap = this.CloneTextureMap (this.normalMap);
cloned.emissiveMap = this.CloneTextureMap (this.emissiveMap);
cloned.alphaTest = this.alphaTest;
cloned.transparent = this.transparent;
cloned.multiplyDiffuseMap = this.multiplyDiffuseMap;
return cloned;
}
CloneTextureMap (textureMap)
{
if (textureMap === null) {
return null;
}
return textureMap.Clone ();
}
};
OV.Triangle = class
{
constructor (v0, v1, v2)
{
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
this.n0 = null;
this.n1 = null;
this.n2 = null;
this.u0 = null;
this.u1 = null;
this.u2 = null;
this.mat = null;
this.curve = null;
}
HasVertices ()
{
return this.v0 !== null && this.v1 !== null && this.v2 !== null;
}
HasNormals ()
{
return this.n0 !== null && this.n1 !== null && this.n2 !== null;
}
HasTextureUVs ()
{
return this.u0 !== null && this.u1 !== null && this.u2 !== null;
}
SetVertices (v0, v1, v2)
{
this.v0 = v0;
this.v1 = v1;
this.v2 = v2;
return this;
}
SetNormals (n0, n1, n2)
{
this.n0 = n0;
this.n1 = n1;
this.n2 = n2;
return this;
}
SetTextureUVs (u0, u1, u2)
{
this.u0 = u0;
this.u1 = u1;
this.u2 = u2;
return this;
}
SetMaterial (mat)
{
this.mat = mat;
return this;
}
SetCurve (curve)
{
this.curve = curve;
return this;
}
Clone ()
{
let cloned = new OV.Triangle (this.v0, this.v1, this.v2);
cloned.SetNormals (this.n0, this.n1, this.n2);
cloned.SetTextureUVs (this.u0, this.u1, this.u2);
cloned.SetMaterial (this.mat);
cloned.SetCurve (this.curve);
return cloned;
}
};

View File

@ -0,0 +1,241 @@
OV.FinalizeModel = function (model, getDefaultMaterial)
{
function FinalizeMesh (mesh, getDefaultMaterialIndex)
{
function CalculateCurveNormals (mesh)
{
function AddAverageNormal (mesh, triangle, vertexIndex, triangleNormals, vertexToTriangles)
{
let averageNormal = new OV.Coord3D (0.0, 0.0, 0.0);
let averageCount = 0;
let neigTriangles = vertexToTriangles[vertexIndex];
for (let i = 0; i < neigTriangles.length; i++) {
let neigIndex = neigTriangles[i];
let neigTriangle = mesh.GetTriangle (neigIndex);
if (triangle.curve === neigTriangle.curve) {
averageNormal = OV.AddCoord3D (averageNormal, triangleNormals[neigIndex]);
averageCount = averageCount + 1;
}
}
averageNormal.MultiplyScalar (1.0 / averageCount);
averageNormal.Normalize ();
return mesh.AddNormal (averageNormal);
}
let triangleNormals = [];
let vertexToTriangles = {};
for (let vertexIndex = 0; vertexIndex < mesh.VertexCount (); vertexIndex++) {
vertexToTriangles[vertexIndex] = [];
}
for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
let triangle = mesh.GetTriangle (triangleIndex);
let v0 = mesh.GetVertex (triangle.v0);
let v1 = mesh.GetVertex (triangle.v1);
let v2 = mesh.GetVertex (triangle.v2);
let normal = OV.CalculateTriangleNormal (v0, v1, v2);
triangleNormals.push (normal);
vertexToTriangles[triangle.v0].push (triangleIndex);
vertexToTriangles[triangle.v1].push (triangleIndex);
vertexToTriangles[triangle.v2].push (triangleIndex);
}
for (let triangleIndex = 0; triangleIndex < mesh.TriangleCount (); triangleIndex++) {
let triangle = mesh.GetTriangle (triangleIndex);
if (triangle.n0 === null || triangle.n1 === null || triangle.n2 === null) {
let n0 = AddAverageNormal (mesh, triangle, triangle.v0, triangleNormals, vertexToTriangles);
let n1 = AddAverageNormal (mesh, triangle, triangle.v1, triangleNormals, vertexToTriangles);
let n2 = AddAverageNormal (mesh, triangle, triangle.v2, triangleNormals, vertexToTriangles);
triangle.SetNormals (n0, n1, n2);
}
}
}
function FinalizeTriangle (mesh, triangle, status)
{
if (!triangle.HasNormals ()) {
if (triangle.curve === null || triangle.curve === 0) {
let v0 = mesh.GetVertex (triangle.v0);
let v1 = mesh.GetVertex (triangle.v1);
let v2 = mesh.GetVertex (triangle.v2);
let normal = OV.CalculateTriangleNormal (v0, v1, v2);
let normalIndex = mesh.AddNormal (normal);
triangle.SetNormals (normalIndex, normalIndex, normalIndex);
} else {
status.calculateCurveNormals = true;
}
}
if (triangle.mat === null) {
triangle.mat = status.getDefaultMaterialIndex ();
}
if (triangle.curve === null) {
triangle.curve = 0;
}
}
let status = {
getDefaultMaterialIndex : getDefaultMaterialIndex,
calculateCurveNormals : false
};
for (let i = 0; i < mesh.TriangleCount (); i++) {
let triangle = mesh.GetTriangle (i);
FinalizeTriangle (mesh, triangle, status);
}
if (status.calculateCurveNormals) {
CalculateCurveNormals (mesh);
}
}
let defaultMaterialIndex = null;
let getDefaultMaterialIndex = function () {
if (defaultMaterialIndex === null) {
defaultMaterialIndex = model.AddMaterial (getDefaultMaterial ());
}
return defaultMaterialIndex;
};
for (let i = 0; i < model.MeshCount (); i++) {
let mesh = model.GetMesh (i);
if (mesh.TriangleCount () === 0) {
model.RemoveMesh (i);
i = i - 1;
continue;
}
FinalizeMesh (mesh, getDefaultMaterialIndex);
}
};
OV.CheckModel = function (model)
{
function IsCorrectValue (val)
{
if (val === undefined || val === null) {
return false;
}
return true;
}
function IsCorrectNumber (val)
{
if (!IsCorrectValue (val)) {
return false;
}
if (isNaN (val)) {
return false;
}
return true;
}
function IsCorrectIndex (val, count)
{
if (!IsCorrectNumber (val)) {
return false;
}
if (val < 0 || val >= count) {
return false;
}
return true;
}
function CheckMesh (model, mesh)
{
function CheckTriangle (model, mesh, triangle)
{
if (!IsCorrectIndex (triangle.v0, mesh.VertexCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.v1, mesh.VertexCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.v2, mesh.VertexCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.n0, mesh.NormalCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.n1, mesh.NormalCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.n2, mesh.NormalCount ())) {
return false;
}
if (triangle.HasTextureUVs ()) {
if (!IsCorrectIndex (triangle.u0, mesh.TextureUVCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.u1, mesh.TextureUVCount ())) {
return false;
}
if (!IsCorrectIndex (triangle.u2, mesh.TextureUVCount ())) {
return false;
}
}
if (!IsCorrectIndex (triangle.mat, model.MaterialCount ())) {
return false;
}
if (!IsCorrectNumber (triangle.curve)) {
return false;
}
return true;
}
for (let i = 0; i < mesh.VertexCount (); i++) {
let vertex = mesh.GetVertex (i);
if (!IsCorrectNumber (vertex.x)) {
return false;
}
if (!IsCorrectNumber (vertex.y)) {
return false;
}
if (!IsCorrectNumber (vertex.z)) {
return false;
}
}
for (let i = 0; i < mesh.NormalCount (); i++) {
let normal = mesh.GetNormal (i);
if (!IsCorrectNumber (normal.x)) {
return false;
}
if (!IsCorrectNumber (normal.y)) {
return false;
}
if (!IsCorrectNumber (normal.z)) {
return false;
}
}
for (let i = 0; i < mesh.TextureUVCount (); i++) {
let uv = mesh.GetTextureUV (i);
if (!IsCorrectNumber (uv.x)) {
return false;
}
if (!IsCorrectNumber (uv.y)) {
return false;
}
}
for (let i = 0; i < mesh.TriangleCount (); i++) {
let triangle = mesh.GetTriangle (i);
if (!CheckTriangle (model, mesh, triangle)) {
return false;
}
}
return true;
}
for (let i = 0; i < model.MeshCount (); i++) {
let mesh = model.GetMesh (i);
if (!CheckMesh (model, mesh)) {
return false;
}
}
return true;
};

163
source/model/modelutils.js Normal file
View File

@ -0,0 +1,163 @@
OV.CalculateTriangleNormal = function (v0, v1, v2)
{
let v = OV.SubCoord3D (v1, v0);
let w = OV.SubCoord3D (v2, v0);
let normal = new OV.Coord3D (
v.y * w.z - v.z * w.y,
v.z * w.x - v.x * w.z,
v.x * w.y - v.y * w.x
);
normal.Normalize ();
return normal;
};
OV.TransformMesh = function (mesh, transformation)
{
if (transformation.IsIdentity ()) {
return;
}
for (let i = 0; i < mesh.VertexCount (); i++) {
let vertex = mesh.GetVertex (i);
let transformed = transformation.TransformCoord3D (vertex);
vertex.x = transformed.x;
vertex.y = transformed.y;
vertex.z = transformed.z;
}
if (mesh.NormalCount () > 0) {
let trs = transformation.GetMatrix ().DecomposeTRS ();
let normalMatrix = new OV.Matrix ().ComposeTRS ([0.0, 0.0, 0.0], trs.rotation, [1.0, 1.0, 1.0]);
let normalTransformation = new OV.Transformation (normalMatrix);
for (let i = 0; i < mesh.NormalCount (); i++) {
let normal = mesh.GetNormal (i);
let transformed = normalTransformation.TransformCoord3D (normal);
normal.x = transformed.x;
normal.y = transformed.y;
normal.z = transformed.z;
}
}
};
OV.GetMeshBoundingBox = function (mesh)
{
let min = new OV.Coord3D (Infinity, Infinity, Infinity);
let max = new OV.Coord3D (-Infinity, -Infinity, -Infinity);
for (let i = 0; i < mesh.VertexCount (); i++) {
let vertex = mesh.GetVertex (i);
min.x = Math.min (min.x, vertex.x);
min.y = Math.min (min.y, vertex.y);
min.z = Math.min (min.z, vertex.z);
max.x = Math.max (max.x, vertex.x);
max.y = Math.max (max.y, vertex.y);
max.z = Math.max (max.z, vertex.z);
}
return [min, max];
};
OV.IsModelEmpty = function (model)
{
for (let i = 0; i < model.MeshCount (); i++) {
let mesh = model.GetMesh (i);
if (mesh.TriangleCount () > 0) {
return false;
}
}
return true;
};
OV.CloneMesh = function (mesh)
{
let cloned = new OV.Mesh ();
cloned.SetName (mesh.GetName ());
for (let i = 0; i < mesh.VertexCount (); i++) {
let vertex = mesh.GetVertex (i);
cloned.AddVertex (vertex.Clone ());
}
for (let i = 0; i < mesh.NormalCount (); i++) {
let normal = mesh.GetNormal (i);
cloned.AddNormal (normal.Clone ());
}
for (let i = 0; i < mesh.TextureUVCount (); i++) {
let uv = mesh.GetTextureUV (i);
cloned.AddTextureUV (uv.Clone ());
}
for (let i = 0; i < mesh.TriangleCount (); i++) {
let triangle = mesh.GetTriangle (i);
cloned.AddTriangle (triangle.Clone ());
}
return cloned;
};
OV.CreateMergedModel = function (model)
{
function MergeMesh (mesh, mergedMesh)
{
let vertexOffset = mergedMesh.VertexCount ();
let normalOffset = mergedMesh.NormalCount ();
let uvOffset = mergedMesh.TextureUVCount ();
for (let i = 0; i < mesh.VertexCount (); i++) {
let vertex = mesh.GetVertex (i);
mergedMesh.AddVertex (vertex.Clone ());
}
for (let i = 0; i < mesh.NormalCount (); i++) {
let normal = mesh.GetNormal (i);
mergedMesh.AddNormal (normal.Clone ());
}
for (let i = 0; i < mesh.TextureUVCount (); i++) {
let uv = mesh.GetTextureUV (i);
mergedMesh.AddTextureUV (uv.Clone ());
}
for (let i = 0; i < mesh.TriangleCount (); i++) {
let triangle = mesh.GetTriangle (i);
let newTriangle = triangle.Clone ();
newTriangle.SetVertices (
triangle.v0 + vertexOffset,
triangle.v1 + vertexOffset,
triangle.v2 + vertexOffset
);
newTriangle.SetNormals (
triangle.n0 + normalOffset,
triangle.n1 + normalOffset,
triangle.n2 + normalOffset
);
if (newTriangle.HasTextureUVs ()) {
newTriangle.SetTextureUVs (
triangle.u0 + uvOffset,
triangle.u1 + uvOffset,
triangle.u2 + uvOffset
);
}
mergedMesh.AddTriangle (newTriangle);
}
}
let mergedModel = new OV.Model ();
mergedModel.SetName (model.GetName ());
for (let i = 0; i < model.MaterialCount (); i++) {
let material = model.GetMaterial (i);
mergedModel.AddMaterial (material.Clone ());
}
let mergedMesh = new OV.Mesh ();
for (let i = 0; i < model.MeshCount (); i++) {
let mesh = model.GetMesh (i);
MergeMesh (mesh, mergedMesh);
}
mergedModel.AddMesh (mergedMesh);
return mergedModel;
};

21
source/viewer/domutils.js Normal file
View File

@ -0,0 +1,21 @@
OV.GetInnerDimensions = function (element, outerWidth, outerHeight)
{
function GetInt (parameter)
{
return Math.round (parseFloat (parameter));
}
let style = getComputedStyle (element);
let width = outerWidth -
GetInt (style.borderLeftWidth) - GetInt (style.borderRightWidth) -
GetInt (style.marginLeft) - GetInt (style.marginRight) -
GetInt (style.paddingLeft) - GetInt (style.paddingRight);
let height = outerHeight -
GetInt (style.borderTopWidth) - GetInt (style.borderBottomWidth) -
GetInt (style.marginTop) - GetInt (style.marginBottom) -
GetInt (style.paddingTop) - GetInt (style.paddingBottom);
return {
width : width,
height : height
};
};

View File

@ -0,0 +1,83 @@
OV.Init3DViewerElements = function ()
{
function LoadElement (element)
{
let canvas = document.createElement ('canvas');
element.appendChild (canvas);
let viewer = new OV.Viewer ();
viewer.Init (canvas);
let width = parseInt (element.getAttribute ('width'));
let height = parseInt (element.getAttribute ('height'));
element.style.width = width + 'px';
element.style.height = height + 'px';
viewer.Resize (width, height);
let loader = new OV.ThreeModelLoader ();
let progressDiv = null;
loader.Init ({
onLoadStart : function () {
canvas.style.display = 'none';
progressDiv = document.createElement ('div');
element.appendChild (progressDiv);
progressDiv.innerHTML = 'Loading model...';
},
onFilesLoaded : function () {
progressDiv.innerHTML = 'Importing model...';
},
onModelImported : function () {
progressDiv.innerHTML = 'Visualizing model...';
},
onModelFinished : function (importResult, threeMeshes) {
element.removeChild (progressDiv);
canvas.style.display = 'initial';
viewer.AddMeshes (threeMeshes);
let boundingSphere = viewer.GetBoundingSphere (function (meshUserData) {
return true;
});
viewer.AdjustClippingPlanes (boundingSphere);
viewer.FitToWindow (boundingSphere, false);
},
onTextureLoaded : function () {
viewer.Render ();
},
onLoadError : function (importerError) {
progressDiv.innerHTML = 'Unknown error.';
},
});
let model = element.getAttribute ('model');
if (!model) {
return;
}
let modelUrls = model.split (',');
if (modelUrls.length === 0) {
return;
}
let camera = element.getAttribute ('camera');
if (camera) {
let cameraParams = camera.split (',');
if (cameraParams.length === 9) {
let camera = new OV.Camera (
new OV.Coord3D (parseFloat (cameraParams[0]), parseFloat (cameraParams[1]), parseFloat (cameraParams[2])),
new OV.Coord3D (parseFloat (cameraParams[3]), parseFloat (cameraParams[4]), parseFloat (cameraParams[5])),
new OV.Coord3D (parseFloat (cameraParams[6]), parseFloat (cameraParams[7]), parseFloat (cameraParams[8]))
);
viewer.SetCamera (camera);
}
}
loader.LoadFromUrlList (modelUrls);
}
window.addEventListener ('load', function () {
let elements = document.getElementsByClassName ('online_3d_viewer');
for (let i = 0; i < elements.length; i++) {
let element = elements[i];
LoadElement (element);
}
});
};

549
source/viewer/navigation.js Normal file
View File

@ -0,0 +1,549 @@
OV.GetClientCoordinates = function (canvas, clientX, clientY)
{
if (canvas.getBoundingClientRect) {
let clientRect = canvas.getBoundingClientRect ();
clientX -= clientRect.left;
clientY -= clientRect.top;
}
if (window.pageXOffset && window.pageYOffset) {
clientX += window.pageXOffset;
clientY += window.pageYOffset;
}
return (new OV.Coord2D (clientX, clientY));
};
OV.Camera = class
{
constructor (eye, center, up)
{
this.eye = eye;
this.center = center;
this.up = up;
}
Clone ()
{
return new OV.Camera (
this.eye.Clone (),
this.center.Clone (),
this.up.Clone ()
);
}
};
OV.MouseInteraction = class
{
constructor ()
{
this.prev = new OV.Coord2D (0.0, 0.0);
this.curr = new OV.Coord2D (0.0, 0,0);
this.diff = new OV.Coord2D (0.0, 0.0);
this.buttons = [];
}
Down (canvas, ev)
{
this.buttons.push (ev.which);
this.curr = this.GetCurrent (canvas, ev);
this.prev = this.curr.Clone ();
}
Move (canvas, ev)
{
this.curr = this.GetCurrent (canvas, ev);
this.diff = OV.SubCoord2D (this.curr, this.prev);
this.prev = this.curr.Clone ();
}
Up (canvas, ev)
{
let buttonIndex = this.buttons.indexOf (ev.which);
if (buttonIndex !== -1) {
this.buttons.splice (buttonIndex, 1);
}
this.curr = this.GetCurrent (canvas, ev);
}
Leave (canvas, ev)
{
this.buttons = [];
this.curr = this.GetCurrent (canvas, ev);
}
IsButtonDown ()
{
return this.buttons.length > 0;
}
GetButton ()
{
let length = this.buttons.length;
if (length === 0) {
return 0;
}
return this.buttons[length - 1];
}
GetMoveDiff ()
{
return this.diff;
}
GetCurrent (canvas, ev)
{
return OV.GetClientCoordinates (canvas, ev.clientX, ev.clientY);
}
};
OV.TouchInteraction = class
{
constructor ()
{
this.prevPos = new OV.Coord2D (0.0, 0.0);
this.currPos = new OV.Coord2D (0.0, 0,0);
this.diffPos = new OV.Coord2D (0.0, 0.0);
this.prevDist = 0.0;
this.currDist = 0.0;
this.diffDist = 0.0;
this.fingers = 0;
}
Start (canvas, ev)
{
if (ev.touches.length === 0) {
return;
}
this.fingers = ev.touches.length;
this.currPos = this.GetCurrent (canvas, ev);
this.prevPos = this.currPos.Clone ();
this.currDist = this.GetTouchDistance (canvas, ev);
this.prevDist = this.currDist;
}
Move (canvas, ev)
{
if (ev.touches.length === 0) {
return;
}
this.currPos = this.GetCurrent (canvas, ev);
this.diffPos = OV.SubCoord2D (this.currPos, this.prevPos);
this.prevPos = this.currPos.Clone ();
this.currDist = this.GetTouchDistance (canvas, ev);
this.diffDist = this.currDist - this.prevDist;
this.prevDist = this.currDist;
}
End (canvas, ev)
{
if (ev.touches.length === 0) {
return;
}
this.fingers = 0;
this.currPos = this.GetCurrent (canvas, ev);
this.currDist = this.GetTouchDistance (canvas, ev);
}
IsFingerDown ()
{
return this.fingers !== 0;
}
GetFingerCount ()
{
return this.fingers;
}
GetMoveDiff ()
{
return this.diffPos;
}
GetDistanceDiff ()
{
return this.diffDist;
}
GetCurrent (canvas, ev)
{
let coord = null;
if (ev.touches.length !== 0) {
let touchEv = ev.touches[0];
coord = OV.GetClientCoordinates (canvas, touchEv.pageX, touchEv.pageY);
}
return coord;
}
GetTouchDistance (canvas, ev)
{
if (ev.touches.length !== 2) {
return 0.0;
}
let touchEv1 = ev.touches[0];
let touchEv2 = ev.touches[1];
let distance = OV.CoordDistance2D (
OV.GetClientCoordinates (canvas, touchEv1.pageX, touchEv1.pageY),
OV.GetClientCoordinates (canvas, touchEv2.pageX, touchEv2.pageY)
);
return distance;
}
};
OV.ClickDetector = class
{
constructor ()
{
this.isClick = false;
this.button = 0;
}
Down (ev)
{
this.isClick = true;
this.button = ev.which;
}
Move ()
{
this.isClick = false;
}
Up (ev)
{
if (this.isClick && ev.which !== this.button) {
this.isClick = false;
}
}
Leave (ev)
{
this.isClick = false;
this.button = 0;
}
IsClick ()
{
return this.isClick;
}
};
OV.Navigation = class
{
constructor (canvas, camera)
{
this.canvas = canvas;
this.camera = camera;
this.orbitCenter = this.camera.center.Clone ();
this.fixUpVector = true;
this.mouse = new OV.MouseInteraction ();
this.touch = new OV.TouchInteraction ();
this.clickDetector = new OV.ClickDetector ();
this.onUpdate = null;
this.onClick = null;
if (this.canvas.addEventListener) {
this.canvas.addEventListener ('mousedown', this.OnMouseDown.bind (this));
this.canvas.addEventListener ('DOMMouseScroll', this.OnMouseWheel.bind (this));
this.canvas.addEventListener ('mousewheel', this.OnMouseWheel.bind (this));
this.canvas.addEventListener ('touchstart', this.OnTouchStart.bind (this));
this.canvas.addEventListener ('touchmove', this.OnTouchMove.bind (this));
this.canvas.addEventListener ('touchend', this.OnTouchEnd.bind (this));
this.canvas.addEventListener ('contextmenu', this.OnContextMenu.bind (this));
}
if (document.addEventListener) {
document.addEventListener ('mousemove', this.OnMouseMove.bind (this));
document.addEventListener ('mouseup', this.OnMouseUp.bind (this));
document.addEventListener ('mouseleave', this.OnMouseLeave.bind (this));
}
}
SetUpdateHandler (onUpdate)
{
this.onUpdate = onUpdate;
}
SetClickHandler (onClick)
{
this.onClick = onClick;
}
IsFixUpVector ()
{
return this.fixUpVector;
}
SetFixUpVector (fixUpVector)
{
this.fixUpVector = fixUpVector;
}
GetCamera ()
{
return this.camera;
}
SetCamera (camera)
{
this.camera = camera;
}
MoveCamera (newCamera, stepCount)
{
function Step (obj, steps, count, index)
{
obj.camera.eye = steps.eye[index];
obj.camera.center = steps.center[index];
obj.camera.up = steps.up[index];
obj.Update ();
if (index < count - 1) {
requestAnimationFrame (function () {
Step (obj, steps, count, index + 1);
});
}
}
if (stepCount === 0) {
this.SetCamera (newCamera);
return;
}
if (OV.CoordIsEqual3D (this.camera.eye, newCamera.eye) &&
OV.CoordIsEqual3D (this.camera.center, newCamera.center) &&
OV.CoordIsEqual3D (this.camera.up, newCamera.up))
{
return;
}
let tweenFunc = OV.ParabolicTweenFunction;
let obj = this;
let steps = {
eye : OV.TweenCoord3D (this.camera.eye, newCamera.eye, stepCount, tweenFunc),
center : OV.TweenCoord3D (this.camera.center, newCamera.center, stepCount, tweenFunc),
up : OV.TweenCoord3D (this.camera.up, newCamera.up, stepCount, tweenFunc)
};
requestAnimationFrame (function () {
Step (obj, steps, stepCount, 0);
});
this.Update ();
}
FitToSphere (center, radius, fov)
{
if (OV.IsZero (radius)) {
return;
}
let fitCamera = this.GetFitToSphereCamera (center, radius, fov);
this.camera = fitCamera;
this.orbitCenter = this.camera.center.Clone ();
this.Update ();
}
GetFitToSphereCamera (center, radius, fov)
{
if (OV.IsZero (radius)) {
return null;
}
let fitCamera = this.camera.Clone ();
let offsetToOrigo = OV.SubCoord3D (fitCamera.center, center);
fitCamera.eye = OV.SubCoord3D (fitCamera.eye, offsetToOrigo);
fitCamera.center = center.Clone ();
let centerEyeDirection = OV.SubCoord3D (fitCamera.eye, fitCamera.center).Normalize ();
let fieldOfView = fov / 2.0;
if (this.canvas.width < this.canvas.height) {
fieldOfView = fieldOfView * this.canvas.width / this.canvas.height;
}
let distance = radius / Math.sin (fieldOfView * OV.DegRad);
fitCamera.eye = fitCamera.center.Clone ().Offset (centerEyeDirection, distance);
this.orbitCenter = fitCamera.center.Clone ();
return fitCamera;
}
OnMouseDown (ev)
{
ev.preventDefault ();
this.mouse.Down (this.canvas, ev);
this.clickDetector.Down (ev);
}
OnMouseMove (ev)
{
this.mouse.Move (this.canvas, ev);
this.clickDetector.Move ();
if (!this.mouse.IsButtonDown ()) {
return;
}
let moveDiff = this.mouse.GetMoveDiff ();
let mouseButton = this.mouse.GetButton ();
if (mouseButton === 1) {
let orbitRatio = 0.5;
this.Orbit (moveDiff.x * orbitRatio, moveDiff.y * orbitRatio);
} else if (mouseButton === 3) {
let eyeCenterDistance = OV.CoordDistance3D (this.camera.eye, this.camera.center);
let panRatio = 0.001 * eyeCenterDistance;
this.Pan (moveDiff.x * panRatio, moveDiff.y * panRatio);
}
this.Update ();
}
OnMouseUp (ev)
{
this.mouse.Up (this.canvas, ev);
this.clickDetector.Up (ev);
if (this.clickDetector.IsClick ()) {
let isCtrlPressed = (ev.ctrlKey || ev.metaKey);
this.Click (ev.which, isCtrlPressed, ev.clientX, ev.clientY);
}
}
OnMouseLeave (ev)
{
this.mouse.Leave (this.canvas, ev);
this.clickDetector.Leave (ev);
}
OnTouchStart (ev)
{
ev.preventDefault ();
this.touch.Start (this.canvas, ev);
}
OnTouchMove (ev)
{
ev.preventDefault ();
this.touch.Move (this.canvas, ev);
if (!this.touch.IsFingerDown ()) {
return;
}
let moveDiff = this.touch.GetMoveDiff ();
let distanceDiff = this.touch.GetDistanceDiff ();
let fingerCount = this.touch.GetFingerCount ();
if (fingerCount === 1) {
let orbitRatio = 0.5;
this.Orbit (moveDiff.x * orbitRatio, moveDiff.y * orbitRatio);
} else if (fingerCount === 2) {
let zoomRatio = 0.005;
this.Zoom (distanceDiff * zoomRatio);
let panRatio = 0.001 * OV.CoordDistance3D (this.camera.eye, this.camera.center);
this.Pan (moveDiff.x * panRatio, moveDiff.y * panRatio);
}
this.Update ();
}
OnTouchEnd (ev)
{
ev.preventDefault ();
this.touch.End (this.canvas, ev);
}
OnMouseWheel (ev)
{
ev.preventDefault ();
let params = ev || window.event;
let delta = 0;
if (params.detail) {
delta = -params.detail;
} else if (params.wheelDelta) {
delta = params.wheelDelta / 40;
}
let ratio = 0.1;
if (delta < 0) {
ratio = ratio * -1.0;
}
this.Zoom (ratio);
this.Update ();
}
OnContextMenu (ev)
{
ev.preventDefault ();
}
Orbit (angleX, angleY)
{
let radAngleX = angleX * OV.DegRad;
let radAngleY = angleY * OV.DegRad;
let viewDirection = OV.SubCoord3D (this.camera.center, this.camera.eye).Normalize ();
let horizontalDirection = OV.CrossVector3D (viewDirection, this.camera.up).Normalize ();
let differentCenter = !OV.CoordIsEqual3D (this.orbitCenter, this.camera.center);
if (this.fixUpVector) {
let originalAngle = OV.VectorAngle3D (viewDirection, this.camera.up);
let newAngle = originalAngle + radAngleY;
if (OV.IsGreater (newAngle, 0.0) && OV.IsLower (newAngle, Math.PI)) {
this.camera.eye.Rotate (horizontalDirection, -radAngleY, this.orbitCenter);
if (differentCenter) {
this.camera.center.Rotate (horizontalDirection, -radAngleY, this.orbitCenter);
}
}
this.camera.eye.Rotate (this.camera.up, -radAngleX, this.orbitCenter);
if (differentCenter) {
this.camera.center.Rotate (this.camera.up, -radAngleX, this.orbitCenter);
}
} else {
let verticalDirection = OV.CrossVector3D (horizontalDirection, viewDirection).Normalize ();
this.camera.eye.Rotate (horizontalDirection, -radAngleY, this.orbitCenter);
this.camera.eye.Rotate (verticalDirection, -radAngleX, this.orbitCenter);
if (differentCenter) {
this.camera.center.Rotate (horizontalDirection, -radAngleY, this.orbitCenter);
this.camera.center.Rotate (verticalDirection, -radAngleX, this.orbitCenter);
}
this.camera.up = verticalDirection;
}
}
Pan (moveX, moveY)
{
let viewDirection = OV.SubCoord3D (this.camera.center, this.camera.eye).Normalize ();
let horizontalDirection = OV.CrossVector3D (viewDirection, this.camera.up).Normalize ();
let verticalDirection = OV.CrossVector3D (horizontalDirection, viewDirection).Normalize ();
this.camera.eye.Offset (horizontalDirection, -moveX);
this.camera.center.Offset (horizontalDirection, -moveX);
this.camera.eye.Offset (verticalDirection, moveY);
this.camera.center.Offset (verticalDirection, moveY);
}
Zoom (ratio)
{
let direction = OV.SubCoord3D (this.camera.center, this.camera.eye);
let distance = direction.Length ();
let move = distance * ratio;
this.camera.eye.Offset (direction, move);
}
Update ()
{
if (this.onUpdate) {
this.onUpdate ();
}
}
Click (button, isCtrlPressed, clientX, clientY)
{
if (this.onClick) {
let mouseCoords = OV.GetClientCoordinates (this.canvas, clientX, clientY);
this.onClick (button, isCtrlPressed, mouseCoords);
}
}
};

375
source/viewer/viewer.js Normal file
View File

@ -0,0 +1,375 @@
OV.GetDefaultCamera = function (direction)
{
if (direction === OV.Direction.X) {
return new OV.Camera (
new OV.Coord3D (2.0, -3.0, 1.5),
new OV.Coord3D (0.0, 0.0, 0.0),
new OV.Coord3D (1.0, 0.0, 0.0)
);
} else if (direction === OV.Direction.Y) {
return new OV.Camera (
new OV.Coord3D (-1.5, 2.0, 3.0),
new OV.Coord3D (0.0, 0.0, 0.0),
new OV.Coord3D (0.0, 1.0, 0.0)
);
} else if (direction === OV.Direction.Z) {
return new OV.Camera (
new OV.Coord3D (-1.5, -3.0, 2.0),
new OV.Coord3D (0.0, 0.0, 0.0),
new OV.Coord3D (0.0, 0.0, 1.0)
);
}
return null;
};
OV.UpVector = class
{
constructor ()
{
this.direction = OV.Direction.Z;
this.isFixed = true;
this.isFlipped = false;
}
SetDirection (newDirection, oldCamera)
{
this.direction = newDirection;
this.isFlipped = false;
let defaultCamera = OV.GetDefaultCamera (this.direction);
let defaultDir = OV.SubCoord3D (defaultCamera.eye, defaultCamera.center);
let distance = OV.CoordDistance3D (oldCamera.center, oldCamera.eye);
let newEye = oldCamera.center.Clone ().Offset (defaultDir, distance);
let newCamera = oldCamera.Clone ();
if (this.direction === OV.Direction.Y) {
newCamera.up = new OV.Coord3D (0.0, 1.0, 0.0);
newCamera.eye = newEye;
} else if (this.direction === OV.Direction.Z) {
newCamera.up = new OV.Coord3D (0.0, 0.0, 1.0);
newCamera.eye = newEye;
}
return newCamera;
}
SetFixed (isFixed, oldCamera)
{
this.isFixed = isFixed;
if (this.isFixed) {
return this.SetDirection (this.direction, oldCamera);
}
return null;
}
Flip (oldCamera)
{
this.isFlipped = !this.isFlipped;
let newCamera = oldCamera.Clone ();
newCamera.up.MultiplyScalar (-1.0);
return newCamera;
}
};
OV.Viewer = class
{
constructor ()
{
this.canvas = null;
this.renderer = null;
this.scene = null;
this.camera = null;
this.light = null;
this.navigation = null;
this.upVector = null;
this.settings = {
animationSteps : 40
};
}
Init (canvas)
{
this.canvas = canvas;
this.canvas.id = 'viewer';
this.canvas.addEventListener ('contenxtmenu', function (ev) {
ev.preventDefault ();
});
let parameters = {
canvas : this.canvas,
antialias : true
};
this.renderer = new THREE.WebGLRenderer (parameters);
this.renderer.setClearColor ('#ffffff', 1);
this.renderer.setSize (this.canvas.width, this.canvas.height);
this.scene = new THREE.Scene ();
this.InitCamera ();
this.InitLights ();
this.Render ();
}
SetClickHandler (onClick)
{
this.navigation.SetClickHandler (onClick);
}
GetCamera ()
{
return this.navigation.GetCamera ();
}
SetCamera (camera)
{
this.navigation.SetCamera (camera);
this.Render ();
}
Resize (width, height)
{
let innerSize = OV.GetInnerDimensions (this.canvas, width, height);
this.camera.aspect = innerSize.width / innerSize.height;
this.camera.updateProjectionMatrix ();
this.renderer.setSize (innerSize.width, innerSize.height);
this.Render ();
}
FitToWindow (boundingSphere, animation)
{
if (boundingSphere === null) {
return;
}
let center = new OV.Coord3D (boundingSphere.center.x, boundingSphere.center.y, boundingSphere.center.z);
let radius = boundingSphere.radius;
let fov = this.camera.fov;
if (animation) {
let newCamera = this.navigation.GetFitToSphereCamera (center, radius, fov);
this.navigation.MoveCamera (newCamera, this.settings.animationSteps);
} else {
this.navigation.FitToSphere (center, radius, fov);
}
}
AdjustClippingPlanes (boundingSphere)
{
if (boundingSphere === null) {
return;
}
if (boundingSphere.radius < 10.0) {
this.camera.near = 0.01;
this.camera.far = 100.0;
} else if (boundingSphere.radius < 100.0) {
this.camera.near = 0.1;
this.camera.far = 1000.0;
} else if (boundingSphere.radius < 1000.0) {
this.camera.near = 10.0;
this.camera.far = 10000.0;
} else {
this.camera.near = 100.0;
this.camera.far = 1000000.0;
}
this.camera.updateProjectionMatrix ();
this.Render ();
}
IsFixUpVector ()
{
return this.navigation.IsFixUpVector ();
}
SetFixUpVector (fixUpVector)
{
let oldCamera = this.navigation.GetCamera ();
let newCamera = this.upVector.SetFixed (fixUpVector, oldCamera);
this.navigation.SetFixUpVector (fixUpVector);
if (newCamera !== null) {
this.navigation.MoveCamera (newCamera, this.settings.animationSteps);
}
this.Render ();
}
SetUpVector (upDirection, animate)
{
let oldCamera = this.navigation.GetCamera ();
let newCamera = this.upVector.SetDirection (upDirection, oldCamera);
let animationSteps = animate ? this.settings.animationSteps : 0;
this.navigation.MoveCamera (newCamera, animationSteps);
this.Render ();
}
FlipUpVector ()
{
let oldCamera = this.navigation.GetCamera ();
let newCamera = this.upVector.Flip (oldCamera);
this.navigation.MoveCamera (newCamera, 0);
this.Render ();
}
Render ()
{
let navigationCamera = this.navigation.GetCamera ();
this.camera.position.set (navigationCamera.eye.x, navigationCamera.eye.y, navigationCamera.eye.z);
this.camera.up.set (navigationCamera.up.x, navigationCamera.up.y, navigationCamera.up.z);
this.camera.lookAt (new THREE.Vector3 (navigationCamera.center.x, navigationCamera.center.y, navigationCamera.center.z));
let lightDir = OV.SubCoord3D (navigationCamera.eye, navigationCamera.center);
this.light.position.set (lightDir.x, lightDir.y, lightDir.z);
this.renderer.render (this.scene, this.camera);
}
AddMeshes (meshes)
{
for (let i = 0; i < meshes.length; i++) {
let mesh = meshes[i];
this.scene.add (mesh);
}
this.Render ();
}
Clear ()
{
this.ClearMeshes ();
this.Render ();
}
SetMeshesVisibility (isVisible)
{
this.EnumerateMeshes (function (mesh) {
let visible = isVisible (mesh.userData);
if (mesh.visible !== visible) {
mesh.visible = visible;
}
});
this.Render ();
}
SetMeshesHighlight (highlightMaterial, isHighlighted)
{
function CreateHighlightMaterials (originalMaterials, highlightMaterial)
{
let highlightMaterials = [];
for (let i = 0; i < originalMaterials.length; i++) {
highlightMaterials.push (highlightMaterial);
}
return highlightMaterials;
}
this.EnumerateMeshes (function (mesh) {
let highlighted = isHighlighted (mesh.userData);
if (highlighted) {
if (mesh.userData.threeMaterials === null) {
mesh.userData.threeMaterials = mesh.material;
mesh.material = CreateHighlightMaterials (mesh.material, highlightMaterial);
}
} else {
if (mesh.userData.threeMaterials !== null) {
mesh.material = mesh.userData.threeMaterials;
mesh.userData.threeMaterials = null;
}
}
});
this.Render ();
}
GetMeshUnderMouse (mouseCoords)
{
let raycaster = new THREE.Raycaster ();
let mousePos = new THREE.Vector2 ();
mousePos.x = (mouseCoords.x / this.canvas.width) * 2 - 1;
mousePos.y = -(mouseCoords.y / this.canvas.height) * 2 + 1;
raycaster.setFromCamera (mousePos, this.camera);
let iSectObjects = raycaster.intersectObjects (this.scene.children);
for (let i = 0; i < iSectObjects.length; i++) {
let iSectObject = iSectObjects[i];
if (iSectObject.object.type === 'Mesh' && iSectObject.object.visible) {
return iSectObject.object.userData;
}
}
return null;
}
GetBoundingBox (needToProcess)
{
let hasMesh = false;
let boundingBox = new THREE.Box3 ();
this.EnumerateMeshes (function (mesh) {
if (needToProcess (mesh.userData)) {
boundingBox.union (new THREE.Box3 ().setFromObject (mesh));
hasMesh = true;
}
});
if (!hasMesh) {
return null;
}
return boundingBox;
}
GetBoundingSphere (needToProcess)
{
let boundingBox = this.GetBoundingBox (needToProcess);
if (boundingBox === null) {
return null;
}
let boundingSphere = new THREE.Sphere ();
boundingBox.getBoundingSphere (boundingSphere);
return boundingSphere;
}
EnumerateMeshesUserData (enumerator)
{
this.EnumerateMeshes (function (mesh) {
enumerator (mesh.userData);
});
}
EnumerateMeshes (enumerator)
{
this.scene.traverse (function (object) {
if (object.isMesh) {
enumerator (object);
}
});
}
ClearMeshes ()
{
for (let i = this.scene.children.length - 1; i >= 0; i--) {
let object = this.scene.children[i];
if (object.isMesh) {
object.geometry.dispose ();
this.scene.remove (object);
}
}
}
InitCamera ()
{
this.camera = new THREE.PerspectiveCamera (45.0, this.canvas.width / this.canvas.height, 0.1, 1000.0);
this.scene.add (this.camera);
let canvasElem = this.renderer.domElement;
let camera = OV.GetDefaultCamera (OV.Direction.Z);
let obj = this;
this.navigation = new OV.Navigation (canvasElem, camera);
this.navigation.SetUpdateHandler (function () {
obj.Render ();
});
this.upVector = new OV.UpVector ();
}
InitLights ()
{
let ambientLight = new THREE.AmbientLight (0x888888);
this.scene.add (ambientLight);
this.light = new THREE.DirectionalLight (0x888888);
this.scene.add (this.light);
}
};

View File

@ -0,0 +1,14 @@
module.exports =
{
AddNativeSourceFile : function (fileName)
{
var path = require ('path');
var rootDir = path.join (__dirname, '..');
var fullPath = path.resolve (rootDir, fileName);
var fs = require ('fs');
var content = fs.readFileSync (fullPath).toString ();
eval.apply (global, [content]);
}
}

8
test/package.json Normal file
View File

@ -0,0 +1,8 @@
{
"dependencies": {
"mocha": "*"
},
"scripts": {
"test": "mocha"
}
}

27
test/test.js Normal file
View File

@ -0,0 +1,27 @@
var fs = require ('fs');
process.chdir (__dirname);
global.atob = function (base64String) {
return Buffer.from (base64String, 'base64').toString('binary')
};
global.Blob = function () {
};
global.URL = {
createObjectURL : function () {
return 'ObjectUrl';
}
}
require ('./utils/importall.js');
var testDirName = './tests/';
var files = fs.readdirSync (testDirName, { withFileTypes: true });
var i, file;
for (i = 0; i < files.length; i++) {
file = files[i];
if (file.isFile ()) {
require (testDirName + file.name);
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

View File

@ -0,0 +1,46 @@
1 (bool, 1)
0 (bool, 1)
a (char, 1)
A (char, 1)
a (unsigned char, 1)
A (unsigned char, 1)
42 (short, 2)
-42 (short, 2)
32000 (short, 2)
-32000 (short, 2)
42 (unsigned short, 2)
65494 (unsigned short, 2)
32000 (unsigned short, 2)
33536 (unsigned short, 2)
42 (int, 4)
-42 (int, 4)
32000 (int, 4)
-32000 (int, 4)
2000000000 (int, 4)
-2000000000 (int, 4)
42 (unsigned int, 4)
4294967254 (unsigned int, 4)
32000 (unsigned int, 4)
4294935296 (unsigned int, 4)
2000000000 (unsigned int, 4)
2294967296 (unsigned int, 4)
42 (long, 4)
-42 (long, 4)
32000 (long, 4)
-32000 (long, 4)
2000000000 (long, 4)
-2000000000 (long, 4)
42 (unsigned long, 4)
4294967254 (unsigned long, 4)
32000 (unsigned long, 4)
4294935296 (unsigned long, 4)
2000000000 (unsigned long, 4)
2294967296 (unsigned long, 4)
42 (float, 4)
-42 (float, 4)
12345.7 (float, 4)
-12345.7 (float, 4)
42 (double, 8)
-42 (double, 8)
12345.7 (double, 8)
-12345.7 (double, 8)

View File

@ -0,0 +1,105 @@
#include <iostream>
#include <fstream>
#include <typeinfo>
#include <cxxabi.h>
std::string DemangleTypeName (const char* name)
{
int status = -4;
char* demangled = abi::__cxa_demangle (name, NULL, NULL, &status);
std::string result;
if (status == 0) {
result = demangled;
} else {
result = name;
}
delete[] demangled;
return result;
}
class BinaryWriter
{
public:
BinaryWriter (const std::string& logFileName, const std::string& binaryFileName)
{
logFile.open (logFileName.c_str (), std::ofstream::out);
binaryFile.open (binaryFileName.c_str (), std::ofstream::binary);
}
~BinaryWriter ()
{
logFile.close ();
binaryFile.close ();
}
template<class Type>
void Write (Type value)
{
logFile << value << " (" << DemangleTypeName (typeid (value).name ()) << ", " << sizeof (value) << ")" << std::endl;
binaryFile.write ((char*) &value, sizeof (value));
}
private:
std::ofstream logFile;
std::ofstream binaryFile;
};
int main ()
{
BinaryWriter bw ("result.txt", "result.bin");
bw.Write ((bool) true);
bw.Write ((bool) false);
bw.Write ((char) 'a');
bw.Write ((char) 'A');
bw.Write ((unsigned char) 'a');
bw.Write ((unsigned char) 'A');
bw.Write ((short) 42);
bw.Write ((short) -42);
bw.Write ((short) 32000);
bw.Write ((short) -32000);
bw.Write ((unsigned short) 42);
bw.Write ((unsigned short) -42);
bw.Write ((unsigned short) 32000);
bw.Write ((unsigned short) -32000);
bw.Write ((int) 42);
bw.Write ((int) -42);
bw.Write ((int) 32000);
bw.Write ((int) -32000);
bw.Write ((int) 2000000000);
bw.Write ((int) -2000000000);
bw.Write ((unsigned int) 42);
bw.Write ((unsigned int) -42);
bw.Write ((unsigned int) 32000);
bw.Write ((unsigned int) -32000);
bw.Write ((unsigned int) 2000000000);
bw.Write ((unsigned int) -2000000000);
bw.Write ((long) 42);
bw.Write ((long) -42);
bw.Write ((long) 32000);
bw.Write ((long) -32000);
bw.Write ((long) 2000000000);
bw.Write ((long) -2000000000);
bw.Write ((unsigned long) 42);
bw.Write ((unsigned long) -42);
bw.Write ((unsigned long) 32000);
bw.Write ((unsigned long) -32000);
bw.Write ((unsigned long) 2000000000);
bw.Write ((unsigned long) -2000000000);
bw.Write ((float) 42);
bw.Write ((float) -42);
bw.Write ((float) 12345.6789);
bw.Write ((float) -12345.6789);
bw.Write ((double) 42);
bw.Write ((double) -42);
bw.Write ((double) 12345.6789);
bw.Write ((double) -12345.6789);
return 0;
}

View File

@ -0,0 +1,10 @@
# Box
## Screenshot
![screenshot](screenshot/screenshot.png)
## License Information
Donated by [Cesium](http://cesiumjs.org/) for glTF testing.
This model is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,152 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1,
0,
0,
0,
0,
0,
-1,
0,
0,
1,
0,
0,
0,
0,
0,
1
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 4,
"material": 0,
"extensions": {
"KHR_draco_mesh_compression": {
"bufferView": 0,
"attributes": {
"NORMAL": 0,
"POSITION": 1
}
}
}
}
],
"name": "Mesh"
}
],
"accessors": [
{
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"componentType": 5126,
"count": 24,
"max": [
1.007843137254902,
1.007843137254902,
1.007843137254902
],
"min": [
-1.007843137254902,
-1.007843137254902,
-1.007843137254902
],
"type": "VEC3"
},
{
"componentType": 5126,
"count": 24,
"max": [
0.5004885197850513,
0.5004885197850513,
0.5004885197850513
],
"min": [
-0.5004885197850513,
-0.5004885197850513,
-0.5004885197850513
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0,
0,
1
],
"metallicFactor": 0,
"roughnessFactor": 1
},
"name": "Red",
"emissiveFactor": [
0,
0,
0
],
"alphaMode": "OPAQUE",
"doubleSided": false
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 118
}
],
"buffers": [
{
"name": "Box",
"byteLength": 120,
"uri": "Box.bin"
}
],
"extensionsRequired": [
"KHR_draco_mesh_compression"
],
"extensionsUsed": [
"KHR_draco_mesh_compression"
]
}

View File

@ -0,0 +1,142 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 288,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0.0,
0.0,
1.0
],
"metallicFactor": 0.0
},
"name": "Red"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 12,
"target": 34962
}
],
"buffers": [
{
"byteLength": 648,
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
}
]
}

View File

@ -0,0 +1,142 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 288,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0.0,
0.0,
1.0
],
"metallicFactor": 0.0
},
"name": "Red"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 12,
"target": 34962
}
],
"buffers": [
{
"byteLength": 648,
"uri": "Box0.bin"
}
]
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,10 @@
# Box with interleaved position and normal attributes
## Screenshot
![screenshot](screenshot/screenshot.png)
## License Information
Donated by [Cesium](http://cesiumjs.org/) and interleaved by [AngeloReppucci](https://github.com/AngeloReppucci) for glTF testing.
This model is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).

View File

@ -0,0 +1,140 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 12,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0.0,
0.0,
1.0
]
}
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 24,
"target": 34962
}
],
"buffers": [
{
"byteLength": 648,
"uri": "data:application/octet-stream;base64,AAAAAAAAAAAAAIA/AAAAvwAAAL8AAAA/AAAAAAAAAAAAAIA/AAAAPwAAAL8AAAA/AAAAAAAAAAAAAIA/AAAAvwAAAD8AAAA/AAAAAAAAAAAAAIA/AAAAPwAAAD8AAAA/AAAAAAAAgL8AAAAAAAAAPwAAAL8AAAA/AAAAAAAAgL8AAAAAAAAAvwAAAL8AAAA/AAAAAAAAgL8AAAAAAAAAPwAAAL8AAAC/AAAAAAAAgL8AAAAAAAAAvwAAAL8AAAC/AACAPwAAAAAAAAAAAAAAPwAAAD8AAAA/AACAPwAAAAAAAAAAAAAAPwAAAL8AAAA/AACAPwAAAAAAAAAAAAAAPwAAAD8AAAC/AACAPwAAAAAAAAAAAAAAPwAAAL8AAAC/AAAAAAAAgD8AAAAAAAAAvwAAAD8AAAA/AAAAAAAAgD8AAAAAAAAAPwAAAD8AAAA/AAAAAAAAgD8AAAAAAAAAvwAAAD8AAAC/AAAAAAAAgD8AAAAAAAAAPwAAAD8AAAC/AACAvwAAAAAAAAAAAAAAvwAAAL8AAAA/AACAvwAAAAAAAAAAAAAAvwAAAD8AAAA/AACAvwAAAAAAAAAAAAAAvwAAAL8AAAC/AACAvwAAAAAAAAAAAAAAvwAAAD8AAAC/AAAAAAAAAAAAAIC/AAAAvwAAAL8AAAC/AAAAAAAAAAAAAIC/AAAAvwAAAD8AAAC/AAAAAAAAAAAAAIC/AAAAPwAAAL8AAAC/AAAAAAAAAAAAAIC/AAAAPwAAAD8AAAC/AAABAAIAAwACAAEABAAFAAYABwAGAAUACAAJAAoACwAKAAkADAANAA4ADwAOAA0AEAARABIAEwASABEAFAAVABYAFwAWABUA"
}
]
}

View File

@ -0,0 +1,140 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 12,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0.0,
0.0,
1.0
]
}
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 24,
"target": 34962
}
],
"buffers": [
{
"byteLength": 648,
"uri": "BoxInterleaved.bin"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,10 @@
# Box Textured
## Screenshot
![screenshot](screenshot/screenshot.png)
## License Information
Donated by Cesium for glTF testing. Please follow the [Cesium Trademark Terms and Conditions](https://github.com/AnalyticalGraphicsInc/cesium/wiki/CesiumTrademark.pdf).
This model is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,181 @@
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2,
"TEXCOORD_0": 3
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 288,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
},
{
"bufferView": 2,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
6.0,
1.0
],
"min": [
0.0,
0.0
],
"type": "VEC2"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0
},
"metallicFactor": 0.0
},
"name": "Texture"
}
],
"textures": [
{
"sampler": 0,
"source": 0
}
],
"images": [
{
"uri": "CesiumLogoFlat.png"
}
],
"samplers": [
{
"magFilter": 9729,
"minFilter": 9986,
"wrapS": 10497,
"wrapT": 10497
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 768,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 12,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 192,
"byteStride": 8,
"target": 34962
}
],
"buffers": [
{
"byteLength": 840,
"uri": "BoxTextured0.bin"
}
]
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Khronos Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,71 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 1024,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_00.bin",
"byteLength": 12288
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 12288,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0,
"mode": 0
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

View File

@ -0,0 +1,71 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 8,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_01.bin",
"byteLength": 96
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 96,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0,
"mode": 1
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

View File

@ -0,0 +1,71 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 4,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_02.bin",
"byteLength": 48
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 48,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0,
"mode": 2
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

View File

@ -0,0 +1,71 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 5,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_03.bin",
"byteLength": 60
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 60,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0,
"mode": 3
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

View File

@ -0,0 +1,71 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 4,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_04.bin",
"byteLength": 48
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 48,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0,
"mode": 5
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

View File

@ -0,0 +1,71 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 4,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_05.bin",
"byteLength": 48
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 48,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0,
"mode": 6
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

View File

@ -0,0 +1,70 @@
{
"accessors": [
{
"bufferView": 0,
"componentType": 5126,
"count": 6,
"type": "VEC3",
"max": [
0.5,
0.5,
0.0
],
"min": [
-0.5,
-0.5,
0.0
],
"name": "Positions Accessor"
}
],
"asset": {
"generator": "glTF Asset Generator",
"version": "2.0"
},
"buffers": [
{
"uri": "Mesh_PrimitiveMode_06.bin",
"byteLength": 72
}
],
"bufferViews": [
{
"buffer": 0,
"byteLength": 72,
"name": "Positions"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"metallicFactor": 0.0
}
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"POSITION": 0
},
"material": 0
}
]
}
],
"nodes": [
{
"mesh": 0
}
],
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
]
}

Some files were not shown because too many files have changed in this diff Show More