angular-3d-viewer
Version:
660 lines (601 loc) • 23 kB
JavaScript
import { Coord2D } from '../geometry/coord2d.js';
import { ArrayToCoord3D, Coord3D } from '../geometry/coord3d.js';
import { DegRad, Direction, IsNegative } from '../geometry/geometry.js';
import { Matrix } from '../geometry/matrix.js';
import { ArrayToQuaternion } from '../geometry/quaternion.js';
import { Transformation } from '../geometry/transformation.js';
import { BinaryReader } from '../io/binaryreader.js';
import { RGBColor, ColorComponentFromFloat } from '../model/color.js';
import { PhongMaterial, TextureMap } from '../model/material.js';
import { Mesh } from '../model/mesh.js';
import { FlipMeshTrianglesOrientation, TransformMesh } from '../model/meshutils.js';
import { Node } from '../model/node.js';
import { Triangle } from '../model/triangle.js';
import { ImporterBase } from './importerbase.js';
import { UpdateMaterialTransparency } from './importerutils.js';
const 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
};
class Importer3dsNode
{
constructor ()
{
this.id = -1;
this.name = '';
this.flags = -1;
this.parentId = -1;
this.instanceName = '';
this.pivot = [0.0, 0.0, 0.0];
this.positions = [];
this.rotations = [];
this.scales = [];
}
}
class Importer3dsNodeList
{
constructor ()
{
this.nodes = [];
this.nodeIdToNode = new Map ();
}
IsEmpty ()
{
return this.nodes.length === 0;
}
AddNode (node)
{
this.nodes.push (node);
this.nodeIdToNode.set (node.nodeId, node);
}
GetNodes ()
{
return this.nodes;
}
}
export class Importer3ds extends ImporterBase
{
constructor ()
{
super ();
}
CanImportExtension (extension)
{
return extension === '3ds';
}
GetUpDirection ()
{
return Direction.Z;
}
ClearContent ()
{
this.materialNameToIndex = null;
this.meshNameToIndex = null;
this.nodeList = null;
}
ResetContent ()
{
this.materialNameToIndex = new Map ();
this.meshNameToIndex = new Map ();
this.nodeList = new Importer3dsNodeList ();
}
ImportContent (fileContent, onFinish)
{
this.ProcessBinary (fileContent);
onFinish ();
}
ProcessBinary (fileContent)
{
let reader = new BinaryReader (fileContent, true);
let endByte = reader.GetByteLength ();
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.MAIN3DS) {
this.ReadMainChunk (reader, chunkLength);
} else {
this.SkipChunk (reader, chunkLength);
}
});
}
ReadMainChunk (reader, length)
{
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.EDIT3DS) {
this.ReadEditorChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.KF3DS) {
this.ReadKeyFrameChunk (reader, chunkLength);
} else {
this.SkipChunk (reader, chunkLength);
}
});
this.BuildNodeHierarchy ();
}
ReadEditorChunk (reader, length)
{
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.EDIT_MATERIAL) {
this.ReadMaterialChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.EDIT_OBJECT) {
this.ReadObjectChunk (reader, chunkLength);
} else {
this.SkipChunk (reader, chunkLength);
}
});
}
ReadMaterialChunk (reader, length)
{
let material = new PhongMaterial ();
let endByte = this.GetChunkEnd (reader, length);
let shininess = null;
let shininessStrength = null;
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.MAT_NAME) {
material.name = this.ReadName (reader);
} else if (chunkId === CHUNK3DS.MAT_AMBIENT) {
material.ambient = this.ReadColorChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.MAT_DIFFUSE) {
material.color = this.ReadColorChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.MAT_SPECULAR) {
material.specular = this.ReadColorChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.MAT_SHININESS) {
shininess = this.ReadPercentageChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.MAT_SHININESS_STRENGTH) {
shininessStrength = this.ReadPercentageChunk (reader, chunkLength);
} else if (chunkId === CHUNK3DS.MAT_TRANSPARENCY) {
material.opacity = 1.0 - this.ReadPercentageChunk (reader, chunkLength);
UpdateMaterialTransparency (material);
} else if (chunkId === CHUNK3DS.MAT_TEXMAP) {
material.diffuseMap = this.ReadTextureMapChunk (reader, chunkLength);
UpdateMaterialTransparency (material);
} else {
this.SkipChunk (reader, chunkLength);
}
});
if (shininess !== null && shininessStrength !== null) {
material.shininess = shininess * shininessStrength / 10.0;
}
let materialIndex = this.model.AddMaterial (material);
this.materialNameToIndex.set (material.name, materialIndex);
}
ReadTextureMapChunk (reader, length)
{
let texture = new TextureMap ();
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.MAT_TEXMAP_NAME) {
let textureName = this.ReadName (reader);
let textureBuffer = this.callbacks.getFileBuffer (textureName);
texture.name = textureName;
texture.buffer = textureBuffer;
} else if (chunkId === CHUNK3DS.MAT_TEXMAP_UOFFSET) {
texture.offset.x = reader.ReadFloat32 ();
} else if (chunkId === CHUNK3DS.MAT_TEXMAP_VOFFSET) {
texture.offset.y = reader.ReadFloat32 ();
} else if (chunkId === CHUNK3DS.MAT_TEXMAP_USCALE) {
texture.scale.x = reader.ReadFloat32 ();
} else if (chunkId === CHUNK3DS.MAT_TEXMAP_VSCALE) {
texture.scale.y = reader.ReadFloat32 ();
} else if (chunkId === CHUNK3DS.MAT_TEXMAP_ROTATION) {
texture.rotation = reader.ReadFloat32 () * DegRad;
} else {
this.SkipChunk (reader, chunkLength);
}
});
return texture;
}
ReadColorChunk (reader, length)
{
let color = new RGBColor (0, 0, 0);
let endByte = this.GetChunkEnd (reader, length);
let hasLinColor = false;
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.MAT_COLOR) {
if (!hasLinColor) {
color.r = reader.ReadUnsignedCharacter8 ();
color.g = reader.ReadUnsignedCharacter8 ();
color.b = reader.ReadUnsignedCharacter8 ();
}
} else if (chunkId === CHUNK3DS.MAT_LIN_COLOR) {
color.r = reader.ReadUnsignedCharacter8 ();
color.g = reader.ReadUnsignedCharacter8 ();
color.b = reader.ReadUnsignedCharacter8 ();
hasLinColor = true;
} else if (chunkId === CHUNK3DS.MAT_COLOR_F) {
if (!hasLinColor) {
color.r = ColorComponentFromFloat (reader.ReadFloat32 ());
color.g = ColorComponentFromFloat (reader.ReadFloat32 ());
color.b = ColorComponentFromFloat (reader.ReadFloat32 ());
}
} else if (chunkId === CHUNK3DS.MAT_LIN_COLOR_F) {
color.r = ColorComponentFromFloat (reader.ReadFloat32 ());
color.g = ColorComponentFromFloat (reader.ReadFloat32 ());
color.b = ColorComponentFromFloat (reader.ReadFloat32 ());
hasLinColor = true;
} else {
this.SkipChunk (reader, chunkLength);
}
});
return color;
}
ReadPercentageChunk (reader, length)
{
let percentage = 0.0;
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.PERCENTAGE) {
percentage = reader.ReadUnsignedInteger16 () / 100.0;
} else if (chunkId === CHUNK3DS.PERCENTAGE_F) {
percentage = reader.ReadFloat32 ();
} else {
this.SkipChunk (reader, chunkLength);
}
});
return percentage;
}
ReadObjectChunk (reader, length)
{
let endByte = this.GetChunkEnd (reader, length);
let objectName = this.ReadName (reader);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.OBJ_TRIMESH) {
this.ReadMeshChunk (reader, chunkLength, objectName);
} else {
this.SkipChunk (reader, chunkLength);
}
});
}
ReadMeshChunk (reader, length, objectName)
{
function ApplyMeshTransformation (mesh, meshMatrix)
{
if (!meshMatrix.IsValid ()) {
return;
}
let determinant = meshMatrix.Determinant ();
let mirrorByX = IsNegative (determinant);
if (mirrorByX) {
let scaleMatrix = new Matrix ().CreateScale (-1.0, 1.0, 1.0);
meshMatrix = scaleMatrix.MultiplyMatrix (meshMatrix);
}
let invMeshMatrix = meshMatrix.Invert ();
if (invMeshMatrix === null) {
return;
}
let transformation = new Transformation (invMeshMatrix);
TransformMesh (mesh, transformation);
if (mirrorByX) {
FlipMeshTrianglesOrientation (mesh);
}
}
let mesh = new Mesh ();
mesh.SetName (objectName);
let endByte = this.GetChunkEnd (reader, length);
let matrixElements = null;
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.TRI_VERTEX) {
this.ReadVerticesChunk (mesh, reader);
} else if (chunkId === CHUNK3DS.TRI_TEXVERTEX) {
this.ReadTextureVerticesChunk (mesh, reader);
} else if (chunkId === CHUNK3DS.TRI_FACE) {
this.ReadFacesChunk (mesh, reader, chunkLength);
} else if (chunkId === CHUNK3DS.TRI_TRANSFORMATION) {
matrixElements = this.ReadTransformationChunk (reader);
} else {
this.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 meshMatrix = new Matrix (matrixElements);
ApplyMeshTransformation (mesh, meshMatrix);
let meshIndex = this.model.AddMesh (mesh);
this.meshNameToIndex.set (mesh.GetName (), meshIndex);
}
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 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 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 ();
reader.ReadUnsignedInteger16 (); // flags
mesh.AddTriangle (new Triangle (v0, v1, v2));
}
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.TRI_MATERIAL) {
this.ReadFaceMaterialsChunk (mesh, reader);
} else if (chunkId === CHUNK3DS.TRI_SMOOTH) {
this.ReadFaceSmoothingGroupsChunk (mesh, faceCount, reader);
} else {
this.SkipChunk (reader, chunkLength);
}
});
}
ReadFaceMaterialsChunk (mesh, reader)
{
let materialName = this.ReadName (reader);
let materialIndex = this.materialNameToIndex.get (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 endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.OBJECT_NODE) {
this.ReadObjectNodeChunk (reader, chunkLength);
} else {
this.SkipChunk (reader, chunkLength);
}
});
}
BuildNodeHierarchy ()
{
function GetNodeTransformation (node3ds, isMeshNode)
{
function GetNodePosition (node3ds)
{
if (node3ds.positions.length === 0) {
return [0.0, 0.0, 0.0];
}
return node3ds.positions[0];
}
function GetNodeRotation (node3ds)
{
function GetQuaternionFromAxisAndAngle (axisAngle)
{
let result = [0.0, 0.0, 0.0, 1.0];
let length = Math.sqrt (axisAngle[0] * axisAngle[0] + axisAngle[1] * axisAngle[1] + axisAngle[2] * axisAngle[2]);
if (length > 0.0) {
let omega = axisAngle[3] * -0.5;
let si = Math.sin (omega) / length;
result = [si * axisAngle[0], si * axisAngle[1], si * axisAngle[2], Math.cos (omega)];
}
return result;
}
if (node3ds.rotations.length === 0) {
return [0.0, 0.0, 0.0, 1.0];
}
let rotation = node3ds.rotations[0];
return GetQuaternionFromAxisAndAngle (rotation);
}
function GetNodeScale (node3ds)
{
if (node3ds.scales.length === 0) {
return [1.0, 1.0, 1.0];
}
return node3ds.scales[0];
}
let matrix = new Matrix ();
matrix.ComposeTRS (
ArrayToCoord3D (GetNodePosition (node3ds)),
ArrayToQuaternion (GetNodeRotation (node3ds)),
ArrayToCoord3D (GetNodeScale (node3ds))
);
if (isMeshNode) {
let pivotPoint = node3ds.pivot;
let pivotMatrix = new Matrix ().CreateTranslation (-pivotPoint[0], -pivotPoint[1], -pivotPoint[2]);
matrix = pivotMatrix.MultiplyMatrix (matrix);
}
return new Transformation (matrix);
}
let rootNode = this.model.GetRootNode ();
if (this.nodeList.IsEmpty ()) {
for (let meshIndex = 0; meshIndex < this.model.MeshCount (); meshIndex++) {
rootNode.AddMeshIndex (meshIndex);
}
} else {
let nodeIdToModelNode = new Map ();
for (let node3ds of this.nodeList.GetNodes ()) {
let node = new Node ();
if (node3ds.name.length > 0 && node3ds.name !== '$$$DUMMY') {
node.SetName (node3ds.name);
if (node3ds.instanceName.length > 0) {
node.SetName (node.GetName () + ' ' + node3ds.instanceName);
}
}
if (node3ds.parentId === 65535 || !nodeIdToModelNode.has (node3ds.parentId)) {
rootNode.AddChildNode (node);
} else {
let parentNode = nodeIdToModelNode.get (node3ds.parentId);
parentNode.AddChildNode (node);
}
nodeIdToModelNode.set (node3ds.id, node);
let isMeshNode = this.meshNameToIndex.has (node3ds.name);
node.SetTransformation (GetNodeTransformation (node3ds, isMeshNode));
if (isMeshNode) {
node.AddMeshIndex (this.meshNameToIndex.get (node3ds.name));
}
}
}
}
ReadObjectNodeChunk (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 === 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 node3ds = new Importer3dsNode ();
let endByte = this.GetChunkEnd (reader, length);
this.ReadChunks (reader, endByte, (chunkId, chunkLength) => {
if (chunkId === CHUNK3DS.OBJECT_HIERARCHY) {
node3ds.name = this.ReadName (reader);
node3ds.flags = reader.ReadUnsignedInteger32 ();
node3ds.parentId = reader.ReadUnsignedInteger16 ();
} else if (chunkId === CHUNK3DS.OBJECT_INSTANCE_NAME) {
node3ds.instanceName = this.ReadName (reader);
} else if (chunkId === CHUNK3DS.OBJECT_PIVOT) {
node3ds.pivot = this.ReadVector (reader);
} else if (chunkId === CHUNK3DS.OBJECT_POSITION) {
node3ds.positions = ReadTrackVector (this, reader, CHUNK3DS.OBJECT_POSITION);
} else if (chunkId === CHUNK3DS.OBJECT_ROTATION) {
node3ds.rotations = ReadTrackVector (this, reader, CHUNK3DS.OBJECT_ROTATION);
} else if (chunkId === CHUNK3DS.OBJECT_SCALE) {
node3ds.scales = ReadTrackVector (this, reader, CHUNK3DS.OBJECT_SCALE);
} else if (chunkId === CHUNK3DS.OBJECT_ID) {
node3ds.id = reader.ReadUnsignedInteger16 ();
} else {
this.SkipChunk (reader, chunkLength);
}
});
this.nodeList.AddNode (node3ds);
}
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);
}
}