julien-c's picture
julien-c HF staff
do not gitignore the builds
6cd9596
raw
history blame
29.6 kB
/**
* @author mrdoob / http://mrdoob.com/
* @author yomboprime / https://github.com/yomboprime/
*
*
*/
THREE.LDrawLoader = ( function () {
function LineParser( line, lineNumber ) {
this.line = line;
this.lineLength = line.length;
this.currentCharIndex = 0;
this.currentChar = ' ';
this.lineNumber = lineNumber;
}
LineParser.prototype = {
constructor: LineParser,
seekNonSpace: function () {
while ( this.currentCharIndex < this.lineLength ) {
this.currentChar = this.line.charAt( this.currentCharIndex );
if ( this.currentChar !== ' ' && this.currentChar !== '\t' ) {
return;
}
this.currentCharIndex ++;
}
},
getToken: function () {
var pos0 = this.currentCharIndex ++;
// Seek space
while ( this.currentCharIndex < this.lineLength ) {
this.currentChar = this.line.charAt( this.currentCharIndex );
if ( this.currentChar === ' ' || this.currentChar === '\t' ) {
break;
}
this.currentCharIndex ++;
}
var pos1 = this.currentCharIndex;
this.seekNonSpace();
return this.line.substring( pos0, pos1 );
},
getRemainingString: function () {
return this.line.substring( this.currentCharIndex, this.lineLength );
},
isAtTheEnd: function () {
return this.currentCharIndex >= this.lineLength;
},
setToEnd: function () {
this.currentCharIndex = this.lineLength;
},
getLineNumberString: function () {
return this.lineNumber >= 0 ? " at line " + this.lineNumber : "";
}
};
function sortByMaterial( a, b ) {
if ( a.colourCode === b.colourCode ) {
return 0;
}
if ( a.colourCode < b.colourCode ) {
return - 1;
}
return 1;
}
function createObject( elements, elementSize ) {
// Creates a THREE.LineSegments (elementSize = 2) or a THREE.Mesh (elementSize = 3 )
// With per face / segment material, implemented with mesh groups and materials array
// Sort the triangles or line segments by colour code to make later the mesh groups
elements.sort( sortByMaterial );
var vertices = [];
var materials = [];
var bufferGeometry = new THREE.BufferGeometry();
bufferGeometry.clearGroups();
var prevMaterial = null;
var index0 = 0;
var numGroupVerts = 0;
for ( var iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
var elem = elements[ iElem ];
var v0 = elem.v0;
var v1 = elem.v1;
// Note that LDraw coordinate system is rotated 180 deg. in the X axis w.r.t. Three.js's one
vertices.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
if ( elementSize === 3 ) {
vertices.push( elem.v2.x, elem.v2.y, elem.v2.z );
}
if ( prevMaterial !== elem.material ) {
if ( prevMaterial !== null ) {
bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
}
materials.push( elem.material );
prevMaterial = elem.material;
index0 = iElem * elementSize;
numGroupVerts = elementSize;
} else {
numGroupVerts += elementSize;
}
}
if ( numGroupVerts > 0 ) {
bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
}
bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
var object3d = null;
if ( elementSize === 2 ) {
object3d = new THREE.LineSegments( bufferGeometry, materials );
} else if ( elementSize === 3 ) {
bufferGeometry.computeVertexNormals();
object3d = new THREE.Mesh( bufferGeometry, materials );
}
return object3d;
}
//
function LDrawLoader( manager ) {
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
// This is a stack of 'parse scopes' with one level per subobject loaded file.
// Each level contains a material lib and also other runtime variables passed between parent and child subobjects
// When searching for a material code, the stack is read from top of the stack to bottom
// Each material library is an object map keyed by colour codes.
this.parseScopesStack = null;
this.path = '';
// Array of THREE.Material
this.materials = [];
// Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
// This also allows to handle the embedded text files ("0 FILE" lines)
this.subobjectCache = {};
// This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
this.fileMap = null;
// Add default main triangle and line edge materials (used in piecess that can be coloured with a main color)
this.setMaterials( [
this.parseColourMetaDirective( new LineParser( "Main_Colour CODE 16 VALUE #FF8080 EDGE #333333" ) ),
this.parseColourMetaDirective( new LineParser( "Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333" ) )
] );
// If this flag is set to true, each subobject will be a THREE.Object.
// If not (the default), only one object which contains all the merged primitives will be created.
this.separateObjects = false;
// Current merged object and primitives
this.currentGroupObject = null;
this.currentTriangles = null;
this.currentLineSegments = null;
}
// Special surface finish tag types.
// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented
LDrawLoader.FINISH_TYPE_DEFAULT = 0;
LDrawLoader.FINISH_TYPE_CHROME = 1;
LDrawLoader.FINISH_TYPE_PEARLESCENT = 2;
LDrawLoader.FINISH_TYPE_RUBBER = 3;
LDrawLoader.FINISH_TYPE_MATTE_METALLIC = 4;
LDrawLoader.FINISH_TYPE_METAL = 5;
// State machine to search a subobject path.
// The LDraw standard establishes these various possible subfolders.
LDrawLoader.FILE_LOCATION_AS_IS = 0;
LDrawLoader.FILE_LOCATION_TRY_PARTS = 1;
LDrawLoader.FILE_LOCATION_TRY_P = 2;
LDrawLoader.FILE_LOCATION_TRY_MODELS = 3;
LDrawLoader.FILE_LOCATION_TRY_RELATIVE = 4;
LDrawLoader.FILE_LOCATION_TRY_ABSOLUTE = 5;
LDrawLoader.FILE_LOCATION_NOT_FOUND = 6;
LDrawLoader.prototype = {
constructor: LDrawLoader,
load: function ( url, onLoad, onProgress, onError ) {
if ( ! this.fileMap ) {
this.fileMap = {};
}
var scope = this;
var fileLoader = new THREE.FileLoader( this.manager );
fileLoader.setPath( this.path );
fileLoader.load( url, function ( text ) {
processObject( text, onLoad );
}, onProgress, onError );
function processObject( text, onProcessed ) {
var parseScope = scope.newParseScopeLevel();
parseScope.url = url;
var parentParseScope = scope.getParentParseScope();
// Add to cache
var currentFileName = parentParseScope.currentFileName;
if ( scope.subobjectCache[ currentFileName ] === undefined ) {
scope.subobjectCache[ currentFileName ] = text;
}
// Parse the object (returns a THREE.Group)
var objGroup = scope.parse( text );
// Load subobjects
parseScope.subobjects = objGroup.userData.subobjects;
parseScope.numSubobjects = parseScope.subobjects.length;
parseScope.subobjectIndex = 0;
if ( parseScope.numSubobjects > 0 ) {
// Load the first subobject
var subobjectGroup = loadSubobject( parseScope.subobjects[ 0 ], true );
// Optimization for loading pack: If subobjects are obtained from cache, keep loading them iteratively rather than recursively
if ( subobjectGroup ) {
while ( subobjectGroup && parseScope.subobjectIndex < parseScope.numSubobjects - 1 ) {
subobjectGroup = loadSubobject( parseScope.subobjects[ ++ parseScope.subobjectIndex ], true );
}
if ( subobjectGroup ) {
finalizeObject();
}
}
} else {
// No subobjects, finish object
finalizeObject();
}
return objGroup;
function finalizeObject() {
if ( ! scope.separateObjects && ! parentParseScope.isFromParse ) {
// We are finalizing the root object and merging primitives is activated, so create the entire Mesh and LineSegments objects now
if ( scope.currentLineSegments.length > 0 ) {
objGroup.add( createObject( scope.currentLineSegments, 2 ) );
}
if ( scope.currentTriangles.length > 0 ) {
objGroup.add( createObject( scope.currentTriangles, 3 ) );
}
}
scope.removeScopeLevel();
if ( onProcessed ) {
onProcessed( objGroup );
}
}
function loadSubobject( subobject, sync ) {
parseScope.mainColourCode = subobject.material.userData.code;
parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
parseScope.currentFileName = subobject.originalFileName;
if ( ! scope.separateObjects ) {
// Set current matrix
parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
}
// If subobject was cached previously, use the cached one
var cached = scope.subobjectCache[ subobject.originalFileName ];
if ( cached ) {
var subobjectGroup = processObject( cached, sync ? undefined : onSubobjectLoaded );
if ( sync ) {
addSubobject( subobject, subobjectGroup );
return subobjectGroup;
}
return;
}
// Adjust file name to locate the subobject file path in standard locations (always under directory scope.path)
// Update also subobject.locationState for the next try if this load fails.
var subobjectURL = subobject.fileName;
var newLocationState = LDrawLoader.FILE_LOCATION_NOT_FOUND;
switch ( subobject.locationState ) {
case LDrawLoader.FILE_LOCATION_AS_IS:
newLocationState = subobject.locationState + 1;
break;
case LDrawLoader.FILE_LOCATION_TRY_PARTS:
subobjectURL = 'parts/' + subobjectURL;
newLocationState = subobject.locationState + 1;
break;
case LDrawLoader.FILE_LOCATION_TRY_P:
subobjectURL = 'p/' + subobjectURL;
newLocationState = subobject.locationState + 1;
break;
case LDrawLoader.FILE_LOCATION_TRY_MODELS:
subobjectURL = 'models/' + subobjectURL;
newLocationState = subobject.locationState + 1;
break;
case LDrawLoader.FILE_LOCATION_TRY_RELATIVE:
subobjectURL = url.substring( 0, url.lastIndexOf( "/" ) + 1 ) + subobjectURL;
newLocationState = subobject.locationState + 1;
break;
case LDrawLoader.FILE_LOCATION_TRY_ABSOLUTE:
if ( subobject.triedLowerCase ) {
// Try absolute path
newLocationState = LDrawLoader.FILE_LOCATION_NOT_FOUND;
} else {
// Next attempt is lower case
subobject.fileName = subobject.fileName.toLowerCase();
subobjectURL = subobject.fileName;
subobject.triedLowerCase = true;
newLocationState = LDrawLoader.FILE_LOCATION_AS_IS;
}
break;
case LDrawLoader.FILE_LOCATION_NOT_FOUND:
// All location possibilities have been tried, give up loading this object
console.warn( 'LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.' );
// Try to read the next subobject
parseScope.subobjectIndex ++;
if ( parseScope.subobjectIndex >= parseScope.numSubobjects ) {
// All subojects have been loaded. Finish parent object
scope.removeScopeLevel();
onProcessed( objGroup );
} else {
// Load next subobject
loadSubobject( parseScope.subobjects[ parseScope.subobjectIndex ] );
}
return;
}
subobject.locationState = newLocationState;
subobject.url = subobjectURL;
// Load the subobject
scope.load( subobjectURL, onSubobjectLoaded, undefined, onSubobjectError );
}
function onSubobjectLoaded( subobjectGroup ) {
var subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
if ( subobjectGroup === null ) {
// Try to reload
loadSubobject( subobject );
return;
}
// Add the subobject just loaded
addSubobject( subobject, subobjectGroup );
// Proceed to load the next subobject, or finish the parent object
parseScope.subobjectIndex ++;
if ( parseScope.subobjectIndex < parseScope.numSubobjects ) {
loadSubobject( parseScope.subobjects[ parseScope.subobjectIndex ] );
} else {
finalizeObject();
}
}
function addSubobject( subobject, subobjectGroup ) {
if ( scope.separateObjects ) {
subobjectGroup.name = subobject.fileName;
objGroup.add( subobjectGroup );
subobjectGroup.matrix.copy( subobject.matrix );
subobjectGroup.matrixAutoUpdate = false;
}
scope.fileMap[ subobject.originalFileName ] = subobject.url;
}
function onSubobjectError( err ) {
// Retry download from a different default possible location
loadSubobject( parseScope.subobjects[ parseScope.subobjectIndex ] );
}
}
},
setPath: function ( value ) {
this.path = value;
return this;
},
setMaterials: function ( materials ) {
// Clears parse scopes stack, adds new scope with material library
this.parseScopesStack = [];
this.newParseScopeLevel( materials );
this.getCurrentParseScope().isFromParse = false;
this.materials = materials;
this.currentGroupObject = null;
return this;
},
setFileMap: function ( fileMap ) {
this.fileMap = fileMap;
return this;
},
newParseScopeLevel: function ( materials ) {
// Adds a new scope level, assign materials to it and returns it
var matLib = {};
if ( materials ) {
for ( var i = 0, n = materials.length; i < n; i ++ ) {
var material = materials[ i ];
matLib[ material.userData.code ] = material;
}
}
var topParseScope = this.getCurrentParseScope();
var parentParseScope = this.getParentParseScope();
var newParseScope = {
lib: matLib,
url: null,
// Subobjects
subobjects: null,
numSubobjects: 0,
subobjectIndex: 0,
// Current subobject
currentFileName: null,
mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
currentMatrix: new THREE.Matrix4(),
// If false, it is a root material scope previous to parse
isFromParse: true
};
this.parseScopesStack.push( newParseScope );
return newParseScope;
},
removeScopeLevel: function () {
this.parseScopesStack.pop();
return this;
},
addMaterial: function ( material ) {
// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
var matLib = this.getCurrentParseScope().lib;
if ( ! matLib[ material.userData.code ] ) {
this.materials.push( material );
}
matLib[ material.userData.code ] = material;
return this;
},
getMaterial: function ( colourCode ) {
// Given a colour code search its material in the parse scopes stack
if ( colourCode.startsWith( "0x2" ) ) {
// Special 'direct' material value (RGB colour)
var colour = colourCode.substring( 3 );
return this.parseColourMetaDirective( new LineParser( "Direct_Color_" + colour + " CODE -1 VALUE #" + colour + " EDGE #" + colour + "" ) );
}
for ( var i = this.parseScopesStack.length - 1; i >= 0; i -- ) {
var material = this.parseScopesStack[ i ].lib[ colourCode ];
if ( material ) {
return material;
}
}
// Material was not found
return null;
},
getParentParseScope: function () {
if ( this.parseScopesStack.length > 1 ) {
return this.parseScopesStack[ this.parseScopesStack.length - 2 ];
}
return null;
},
getCurrentParseScope: function () {
if ( this.parseScopesStack.length > 0 ) {
return this.parseScopesStack[ this.parseScopesStack.length - 1 ];
}
return null;
},
parseColourMetaDirective: function ( lineParser ) {
// Parses a colour definition and returns a THREE.Material or null if error
var code = null;
// Triangle and line colours
var colour = 0xFF00FF;
var edgeColour = 0xFF00FF;
// Transparency
var alpha = 1;
var isTransparent = false;
// Self-illumination:
var luminance = 0;
var finishType = LDrawLoader.FINISH_TYPE_DEFAULT;
var canHaveEnvMap = true;
var edgeMaterial = null;
var name = lineParser.getToken();
if ( ! name ) {
throw 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + ".";
}
// Parse tag tokens and their parameters
var token = null;
while ( true ) {
token = lineParser.getToken();
if ( ! token ) {
break;
}
switch ( token.toUpperCase() ) {
case "CODE":
code = lineParser.getToken();
break;
case "VALUE":
colour = lineParser.getToken();
if ( colour.startsWith( '0x' ) ) {
colour = '#' + colour.substring( 2 );
} else if ( ! colour.startsWith( '#' ) ) {
throw 'LDrawLoader: Invalid colour while parsing material' + lineParser.getLineNumberString() + ".";
}
break;
case "EDGE":
edgeColour = lineParser.getToken();
if ( edgeColour.startsWith( '0x' ) ) {
edgeColour = '#' + edgeColour.substring( 2 );
} else if ( ! edgeColour.startsWith( '#' ) ) {
// Try to see if edge colour is a colour code
edgeMaterial = this.getMaterial( edgeColour );
if ( ! edgeMaterial ) {
throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + ".";
}
// Get the edge material for this triangle material
edgeMaterial = edgeMaterial.userData.edgeMaterial;
}
break;
case 'ALPHA':
alpha = parseInt( lineParser.getToken() );
if ( isNaN( alpha ) ) {
throw 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + ".";
}
alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
if ( alpha < 1 ) {
isTransparent = true;
}
break;
case 'LUMINANCE':
luminance = parseInt( lineParser.getToken() );
if ( isNaN( luminance ) ) {
throw 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + ".";
}
luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
break;
case 'CHROME':
finishType = LDrawLoader.FINISH_TYPE_CHROME;
break;
case 'PEARLESCENT':
finishType = LDrawLoader.FINISH_TYPE_PEARLESCENT;
break;
case 'RUBBER':
finishType = LDrawLoader.FINISH_TYPE_RUBBER;
break;
case 'MATTE_METALLIC':
finishType = LDrawLoader.FINISH_TYPE_MATTE_METALLIC;
break;
case 'METAL':
finishType = LDrawLoader.FINISH_TYPE_METAL;
break;
case 'MATERIAL':
// Not implemented
lineParser.setToEnd();
break;
default:
throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + ".";
break;
}
}
var material = null;
switch ( finishType ) {
case LDrawLoader.FINISH_TYPE_DEFAULT:
case LDrawLoader.FINISH_TYPE_PEARLESCENT:
var specular = new THREE.Color( colour );
var shininess = 35;
var hsl = specular.getHSL( { h: 0, s: 0, l: 0 } );
if ( finishType === LDrawLoader.FINISH_TYPE_DEFAULT ) {
// Default plastic material with shiny specular
hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.12 );
} else {
// Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
hsl.h = ( hsl.h + 0.5 ) % 1;
hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
shininess = 10;
}
specular.setHSL( hsl.h, hsl.s, hsl.l );
material = new THREE.MeshPhongMaterial( { color: colour, specular: specular, shininess: shininess, reflectivity: 0.3 } );
break;
case LDrawLoader.FINISH_TYPE_CHROME:
// Mirror finish surface
material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0, metalness: 1 } );
break;
case LDrawLoader.FINISH_TYPE_RUBBER:
// Rubber is best simulated with Lambert
material = new THREE.MeshLambertMaterial( { color: colour } );
canHaveEnvMap = false;
break;
case LDrawLoader.FINISH_TYPE_MATTE_METALLIC:
// Brushed metal finish
material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.8, metalness: 0.4 } );
break;
case LDrawLoader.FINISH_TYPE_METAL:
// Average metal finish
material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.2, metalness: 0.85 } );
break;
default:
// Should not happen
break;
}
// BFC (Back Face Culling) LDraw language meta extension is not implemented, so set all materials double-sided:
material.side = THREE.DoubleSide;
material.transparent = isTransparent;
material.opacity = alpha;
material.userData.canHaveEnvMap = canHaveEnvMap;
if ( luminance !== 0 ) {
material.emissive.set( material.color ).multiplyScalar( luminance );
}
if ( ! edgeMaterial ) {
// This is the material used for edges
edgeMaterial = new THREE.LineBasicMaterial( { color: edgeColour } );
edgeMaterial.userData.code = code;
edgeMaterial.name = name + " - Edge";
edgeMaterial.userData.canHaveEnvMap = false;
}
material.userData.code = code;
material.name = name;
material.userData.edgeMaterial = edgeMaterial;
return material;
},
//
parse: function ( text ) {
//console.time( 'LDrawLoader' );
// Retrieve data from the parent parse scope
var parentParseScope = this.getParentParseScope();
// Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
var mainColourCode = parentParseScope.mainColourCode;
var mainEdgeColourCode = parentParseScope.mainEdgeColourCode;
var url = parentParseScope.url;
var currentParseScope = this.getCurrentParseScope();
// Parse result variables
var triangles;
var lineSegments;
if ( this.separateObjects ) {
triangles = [];
lineSegments = [];
} else {
if ( this.currentGroupObject === null ) {
this.currentGroupObject = new THREE.Group();
this.currentTriangles = [];
this.currentLineSegments = [];
}
triangles = this.currentTriangles;
lineSegments = this.currentLineSegments;
}
var subobjects = [];
var category = null;
var keywords = null;
if ( text.indexOf( '\r\n' ) !== - 1 ) {
// This is faster than String.split with regex that splits on both
text = text.replace( /\r\n/g, '\n' );
}
var lines = text.split( '\n' );
var numLines = lines.length;
var lineIndex = 0;
var parsingEmbeddedFiles = false;
var currentEmbeddedFileName = null;
var currentEmbeddedText = null;
var scope = this;
function parseColourCode( lineParser, forEdge ) {
// Parses next colour code and returns a THREE.Material
var colourCode = lineParser.getToken();
if ( ! forEdge && colourCode === '16' ) {
colourCode = mainColourCode;
}
if ( forEdge && colourCode === '24' ) {
colourCode = mainEdgeColourCode;
}
var material = scope.getMaterial( colourCode );
if ( ! material ) {
throw 'LDrawLoader: Unknown colour code "' + colourCode + '" is used' + lineParser.getLineNumberString() + ' but it was not defined previously.';
}
return material;
}
function parseVector( lp ) {
var v = new THREE.Vector3( parseFloat( lp.getToken() ), parseFloat( lp.getToken() ), parseFloat( lp.getToken() ) );
if ( ! scope.separateObjects ) {
v.applyMatrix4( parentParseScope.currentMatrix );
}
return v;
}
// Parse all line commands
for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
var line = lines[ lineIndex ];
if ( line.length === 0 ) continue;
if ( parsingEmbeddedFiles ) {
if ( line.startsWith( '0 FILE ' ) ) {
// Save previous embedded file in the cache
this.subobjectCache[ currentEmbeddedFileName ] = currentEmbeddedText;
// New embedded text file
currentEmbeddedFileName = line.substring( 7 );
currentEmbeddedText = '';
} else {
currentEmbeddedText += line + '\n';
}
continue;
}
var lp = new LineParser( line, lineIndex + 1 );
lp.seekNonSpace();
if ( lp.isAtTheEnd() ) {
// Empty line
continue;
}
// Parse the line type
var lineType = lp.getToken();
switch ( lineType ) {
// Line type 0: Comment or META
case '0':
// Parse meta directive
var meta = lp.getToken();
if ( meta ) {
switch ( meta ) {
case '!COLOUR':
var material = this.parseColourMetaDirective( lp );
if ( material ) {
this.addMaterial( material );
} else {
console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
}
break;
case '!CATEGORY':
category = lp.getToken();
break;
case '!KEYWORDS':
var newKeywords = lp.getRemainingString().split( ',' );
if ( newKeywords.length > 0 ) {
if ( ! keywords ) {
keywords = [];
}
newKeywords.forEach( function ( keyword ) {
keywords.push( keyword.trim() );
} );
}
break;
case 'FILE':
if ( lineIndex > 0 ) {
// Start embedded text files parsing
parsingEmbeddedFiles = true;
currentEmbeddedFileName = lp.getRemainingString();
currentEmbeddedText = '';
}
break;
default:
// Other meta directives are not implemented
break;
}
}
break;
// Line type 1: Sub-object file
case '1':
var material = parseColourCode( lp );
var posX = parseFloat( lp.getToken() );
var posY = parseFloat( lp.getToken() );
var posZ = parseFloat( lp.getToken() );
var m0 = parseFloat( lp.getToken() );
var m1 = parseFloat( lp.getToken() );
var m2 = parseFloat( lp.getToken() );
var m3 = parseFloat( lp.getToken() );
var m4 = parseFloat( lp.getToken() );
var m5 = parseFloat( lp.getToken() );
var m6 = parseFloat( lp.getToken() );
var m7 = parseFloat( lp.getToken() );
var m8 = parseFloat( lp.getToken() );
var matrix = new THREE.Matrix4().set(
m0, m1, m2, posX,
m3, m4, m5, posY,
m6, m7, m8, posZ,
0, 0, 0, 1
);
var fileName = lp.getRemainingString().trim().replace( "\\", "/" );
if ( scope.fileMap[ fileName ] ) {
// Found the subobject path in the preloaded file path map
fileName = scope.fileMap[ fileName ];
} else {
// Standardized subfolders
if ( fileName.startsWith( 's/' ) ) {
fileName = 'parts/' + fileName;
} else if ( fileName.startsWith( '48/' ) ) {
fileName = 'p/' + fileName;
}
}
subobjects.push( {
material: material,
matrix: matrix,
fileName: fileName,
originalFileName: fileName,
locationState: LDrawLoader.FILE_LOCATION_AS_IS,
url: null,
triedLowerCase: false
} );
break;
// Line type 2: Line segment
case '2':
var material = parseColourCode( lp, true );
lineSegments.push( {
material: material.userData.edgeMaterial,
colourCode: material.userData.code,
v0: parseVector( lp ),
v1: parseVector( lp )
} );
break;
// Line type 3: Triangle
case '3':
var material = parseColourCode( lp );
triangles.push( {
material: material,
colourCode: material.userData.code,
v0: parseVector( lp ),
v1: parseVector( lp ),
v2: parseVector( lp )
} );
break;
// Line type 4: Quadrilateral
case '4':
var material = parseColourCode( lp );
var v0 = parseVector( lp );
var v1 = parseVector( lp );
var v2 = parseVector( lp );
var v3 = parseVector( lp );
triangles.push( {
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v1,
v2: v2
} );
triangles.push( {
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v2,
v2: v3
} );
break;
// Line type 5: Optional line
case '5':
// Line type 5 is not implemented
break;
default:
throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
break;
}
}
if ( parsingEmbeddedFiles ) {
this.subobjectCache[ currentEmbeddedFileName ] = currentEmbeddedText;
}
//
var groupObject = null;
if ( this.separateObjects ) {
groupObject = new THREE.Group();
if ( lineSegments.length > 0 ) {
groupObject.add( createObject( lineSegments, 2 ) );
}
if ( triangles.length > 0 ) {
groupObject.add( createObject( triangles, 3 ) );
}
} else {
groupObject = this.currentGroupObject;
}
groupObject.userData.category = category;
groupObject.userData.keywords = keywords;
groupObject.userData.subobjects = subobjects;
//console.timeEnd( 'LDrawLoader' );
return groupObject;
}
};
return LDrawLoader;
} )();