bytev-charts
Version:
基于echarts和JavaScript及ES6封装的一个可以直接调用的图表组件库,内置主题设计,简单快捷,且支持用户自定义配置; npm 安装方式: npm install bytev-charts 若启动提示还需额外install插件,则运行 npm install @babel/runtime-corejs2 即可;
1,368 lines (1,130 loc) • 53.9 kB
JavaScript
import _Set from "@babel/runtime-corejs2/core-js/set";
import _Object$keys from "@babel/runtime-corejs2/core-js/object/keys";
import _Object$assign from "@babel/runtime-corejs2/core-js/object/assign";
import _Object$create from "@babel/runtime-corejs2/core-js/object/create";
import _parseInt from "@babel/runtime-corejs2/core-js/parse-int";
import _parseFloat from "@babel/runtime-corejs2/core-js/parse-float";
import _Promise from "@babel/runtime-corejs2/core-js/promise";
import "core-js/modules/es.array.concat.js";
import "core-js/modules/es.regexp.exec.js";
import "core-js/modules/es.array.sort.js";
import "core-js/modules/es.array.iterator.js";
import "core-js/modules/es.array-buffer.slice.js";
import "core-js/modules/es.object.to-string.js";
import "core-js/modules/es.typed-array.float32-array.js";
import "core-js/modules/es.typed-array.copy-within.js";
import "core-js/modules/es.typed-array.every.js";
import "core-js/modules/es.typed-array.fill.js";
import "core-js/modules/es.typed-array.filter.js";
import "core-js/modules/es.typed-array.find.js";
import "core-js/modules/es.typed-array.find-index.js";
import "core-js/modules/es.typed-array.for-each.js";
import "core-js/modules/es.typed-array.includes.js";
import "core-js/modules/es.typed-array.index-of.js";
import "core-js/modules/es.typed-array.iterator.js";
import "core-js/modules/es.typed-array.join.js";
import "core-js/modules/es.typed-array.last-index-of.js";
import "core-js/modules/es.typed-array.map.js";
import "core-js/modules/es.typed-array.reduce.js";
import "core-js/modules/es.typed-array.reduce-right.js";
import "core-js/modules/es.typed-array.reverse.js";
import "core-js/modules/es.typed-array.set.js";
import "core-js/modules/es.typed-array.slice.js";
import "core-js/modules/es.typed-array.some.js";
import "core-js/modules/es.typed-array.sort.js";
import "core-js/modules/es.typed-array.subarray.js";
import "core-js/modules/es.typed-array.to-locale-string.js";
import "core-js/modules/es.typed-array.to-string.js";
import "core-js/modules/es.string.starts-with.js";
import "core-js/modules/es.function.name.js";
import "core-js/modules/es.array.index-of.js";
import "core-js/modules/es.string.replace.js";
import "core-js/modules/es.string.split.js";
import "core-js/modules/es.array.for-each.js";
import "core-js/modules/web.dom-collections.for-each.js";
import "core-js/modules/es.string.trim.js";
import "core-js/modules/es.array.last-index-of.js";
console.warn("THREE.LDrawLoader: As part of the transition to ES6 Modules, the files in 'examples/js' were deprecated in May 2020 (r117) and will be deleted in December 2020 (r124). You can find more information about developing using ES6 Modules in https://threejs.org/docs/#manual/en/introduction/Installation.");
THREE.LDrawLoader = function () {
var conditionalLineVertShader =
/* glsl */
"\n\tattribute vec3 control0;\n\tattribute vec3 control1;\n\tattribute vec3 direction;\n\tvarying float discardFlag;\n\n\t#include <common>\n\t#include <color_pars_vertex>\n\t#include <fog_pars_vertex>\n\t#include <logdepthbuf_pars_vertex>\n\t#include <clipping_planes_pars_vertex>\n\tvoid main() {\n\t\t#include <color_vertex>\n\n\t\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\t\tgl_Position = projectionMatrix * mvPosition;\n\n\t\t// Transform the line segment ends and control points into camera clip space\n\t\tvec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 );\n\t\tvec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 );\n\t\tvec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\t\tvec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 );\n\n\t\tc0.xy /= c0.w;\n\t\tc1.xy /= c1.w;\n\t\tp0.xy /= p0.w;\n\t\tp1.xy /= p1.w;\n\n\t\t// Get the direction of the segment and an orthogonal vector\n\t\tvec2 dir = p1.xy - p0.xy;\n\t\tvec2 norm = vec2( -dir.y, dir.x );\n\n\t\t// Get control point directions from the line\n\t\tvec2 c0dir = c0.xy - p1.xy;\n\t\tvec2 c1dir = c1.xy - p1.xy;\n\n\t\t// If the vectors to the controls points are pointed in different directions away\n\t\t// from the line segment then the line should not be drawn.\n\t\tfloat d0 = dot( normalize( norm ), normalize( c0dir ) );\n\t\tfloat d1 = dot( normalize( norm ), normalize( c1dir ) );\n\t\tdiscardFlag = float( sign( d0 ) != sign( d1 ) );\n\n\t\t#include <logdepthbuf_vertex>\n\t\t#include <clipping_planes_vertex>\n\t\t#include <fog_vertex>\n\t}\n\t";
var conditionalLineFragShader =
/* glsl */
"\n\tuniform vec3 diffuse;\n\tuniform float opacity;\n\tvarying float discardFlag;\n\n\t#include <common>\n\t#include <color_pars_fragment>\n\t#include <fog_pars_fragment>\n\t#include <logdepthbuf_pars_fragment>\n\t#include <clipping_planes_pars_fragment>\n\tvoid main() {\n\n\t\tif ( discardFlag > 0.5 ) discard;\n\n\t\t#include <clipping_planes_fragment>\n\t\tvec3 outgoingLight = vec3( 0.0 );\n\t\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t\t#include <logdepthbuf_fragment>\n\t\t#include <color_fragment>\n\t\toutgoingLight = diffuseColor.rgb; // simple shader\n\t\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t\t#include <tonemapping_fragment>\n\t\t#include <encodings_fragment>\n\t\t#include <fog_fragment>\n\t\t#include <premultiplied_alpha_fragment>\n\t}\n\t";
var tempVec0 = new THREE.Vector3();
var tempVec1 = new THREE.Vector3();
function smoothNormals(triangles, lineSegments) {
function hashVertex(v) {
// NOTE: 1e2 is pretty coarse but was chosen because it allows edges
// to be smoothed as expected (see minifig arms). The errors between edges
// could be due to matrix multiplication.
var x = ~~(v.x * 1e2);
var y = ~~(v.y * 1e2);
var z = ~~(v.z * 1e2);
return "".concat(x, ",").concat(y, ",").concat(z);
}
function hashEdge(v0, v1) {
return "".concat(hashVertex(v0), "_").concat(hashVertex(v1));
}
var hardEdges = new _Set();
var halfEdgeList = {};
var fullHalfEdgeList = {};
var normals = []; // Save the list of hard edges by hash
for (var i = 0, l = lineSegments.length; i < l; i++) {
var ls = lineSegments[i];
var v0 = ls.v0;
var v1 = ls.v1;
hardEdges.add(hashEdge(v0, v1));
hardEdges.add(hashEdge(v1, v0));
} // track the half edges associated with each triangle
for (var i = 0, l = triangles.length; i < l; i++) {
var tri = triangles[i];
for (var i2 = 0, l2 = 3; i2 < l2; i2++) {
var index = i2;
var next = (i2 + 1) % 3;
var v0 = tri["v".concat(index)];
var v1 = tri["v".concat(next)];
var hash = hashEdge(v0, v1); // don't add the triangle if the edge is supposed to be hard
if (hardEdges.has(hash)) continue;
halfEdgeList[hash] = tri;
fullHalfEdgeList[hash] = tri;
}
} // NOTE: Some of the normals wind up being skewed in an unexpected way because
// quads provide more "influence" to some vertex normals than a triangle due to
// the fact that a quad is made up of two triangles and all triangles are weighted
// equally. To fix this quads could be tracked separately so their vertex normals
// are weighted appropriately or we could try only adding a normal direction
// once per normal.
// Iterate until we've tried to connect all triangles to share normals
while (true) {
// Stop if there are no more triangles left
var halfEdges = _Object$keys(halfEdgeList);
if (halfEdges.length === 0) break; // Exhaustively find all connected triangles
var i = 0;
var queue = [fullHalfEdgeList[halfEdges[0]]];
while (i < queue.length) {
// initialize all vertex normals in this triangle
var tri = queue[i];
i++;
var faceNormal = tri.faceNormal;
if (tri.n0 === null) {
tri.n0 = faceNormal.clone();
normals.push(tri.n0);
}
if (tri.n1 === null) {
tri.n1 = faceNormal.clone();
normals.push(tri.n1);
}
if (tri.n2 === null) {
tri.n2 = faceNormal.clone();
normals.push(tri.n2);
} // Check if any edge is connected to another triangle edge
for (var i2 = 0, l2 = 3; i2 < l2; i2++) {
var index = i2;
var next = (i2 + 1) % 3;
var v0 = tri["v".concat(index)];
var v1 = tri["v".concat(next)]; // delete this triangle from the list so it won't be found again
var hash = hashEdge(v0, v1);
delete halfEdgeList[hash];
var reverseHash = hashEdge(v1, v0);
var otherTri = fullHalfEdgeList[reverseHash];
if (otherTri) {
// NOTE: If the angle between triangles is > 67.5 degrees then assume it's
// hard edge. There are some cases where the line segments do not line up exactly
// with or span multiple triangle edges (see Lunar Vehicle wheels).
if (Math.abs(otherTri.faceNormal.dot(tri.faceNormal)) < 0.25) {
continue;
} // if this triangle has already been traversed then it won't be in
// the halfEdgeList. If it has not then add it to the queue and delete
// it so it won't be found again.
if (reverseHash in halfEdgeList) {
queue.push(otherTri);
delete halfEdgeList[reverseHash];
} // Find the matching edge in this triangle and copy the normal vector over
for (var i3 = 0, l3 = 3; i3 < l3; i3++) {
var otherIndex = i3;
var otherNext = (i3 + 1) % 3;
var otherV0 = otherTri["v".concat(otherIndex)];
var otherV1 = otherTri["v".concat(otherNext)];
var otherHash = hashEdge(otherV0, otherV1);
if (otherHash === reverseHash) {
if (otherTri["n".concat(otherIndex)] === null) {
var norm = tri["n".concat(next)];
otherTri["n".concat(otherIndex)] = norm;
norm.add(otherTri.faceNormal);
}
if (otherTri["n".concat(otherNext)] === null) {
var norm = tri["n".concat(index)];
otherTri["n".concat(otherNext)] = norm;
norm.add(otherTri.faceNormal);
}
break;
}
}
}
}
}
} // The normals of each face have been added up so now we average them by normalizing the vector.
for (var i = 0, l = normals.length; i < l; i++) {
normals[i].normalize();
}
}
function isPrimitiveType(type) {
return /primitive/i.test(type) || type === 'Subpart';
}
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 seekNonSpace() {
while (this.currentCharIndex < this.lineLength) {
this.currentChar = this.line.charAt(this.currentCharIndex);
if (this.currentChar !== ' ' && this.currentChar !== '\t') {
return;
}
this.currentCharIndex++;
}
},
getToken: function getToken() {
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 getRemainingString() {
return this.line.substring(this.currentCharIndex, this.lineLength);
},
isAtTheEnd: function isAtTheEnd() {
return this.currentCharIndex >= this.lineLength;
},
setToEnd: function setToEnd() {
this.currentCharIndex = this.lineLength;
},
getLineNumberString: function getLineNumberString() {
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, isConditionalSegments) {
// 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 positions = [];
var normals = [];
var materials = [];
var bufferGeometry = new THREE.BufferGeometry();
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
positions.push(v0.x, v0.y, v0.z, v1.x, v1.y, v1.z);
if (elementSize === 3) {
positions.push(elem.v2.x, elem.v2.y, elem.v2.z);
var n0 = elem.n0 || elem.faceNormal;
var n1 = elem.n1 || elem.faceNormal;
var n2 = elem.n2 || elem.faceNormal;
normals.push(n0.x, n0.y, n0.z);
normals.push(n1.x, n1.y, n1.z);
normals.push(n2.x, n2.y, n2.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.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
if (elementSize === 3) {
bufferGeometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
}
var object3d = null;
if (elementSize === 2) {
object3d = new THREE.LineSegments(bufferGeometry, materials);
} else if (elementSize === 3) {
object3d = new THREE.Mesh(bufferGeometry, materials);
}
if (isConditionalSegments) {
object3d.isConditionalLine = true;
var controlArray0 = new Float32Array(elements.length * 3 * 2);
var controlArray1 = new Float32Array(elements.length * 3 * 2);
var directionArray = new Float32Array(elements.length * 3 * 2);
for (var i = 0, l = elements.length; i < l; i++) {
var os = elements[i];
var c0 = os.c0;
var c1 = os.c1;
var v0 = os.v0;
var v1 = os.v1;
var index = i * 3 * 2;
controlArray0[index + 0] = c0.x;
controlArray0[index + 1] = c0.y;
controlArray0[index + 2] = c0.z;
controlArray0[index + 3] = c0.x;
controlArray0[index + 4] = c0.y;
controlArray0[index + 5] = c0.z;
controlArray1[index + 0] = c1.x;
controlArray1[index + 1] = c1.y;
controlArray1[index + 2] = c1.z;
controlArray1[index + 3] = c1.x;
controlArray1[index + 4] = c1.y;
controlArray1[index + 5] = c1.z;
directionArray[index + 0] = v1.x - v0.x;
directionArray[index + 1] = v1.y - v0.y;
directionArray[index + 2] = v1.z - v0.z;
directionArray[index + 3] = v1.x - v0.x;
directionArray[index + 4] = v1.y - v0.y;
directionArray[index + 5] = v1.z - v0.z;
}
bufferGeometry.setAttribute('control0', new THREE.BufferAttribute(controlArray0, 3, false));
bufferGeometry.setAttribute('control1', new THREE.BufferAttribute(controlArray1, 3, false));
bufferGeometry.setAttribute('direction', new THREE.BufferAttribute(directionArray, 3, false));
}
return object3d;
} //
function LDrawLoader(manager) {
THREE.Loader.call(this, manager); // 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; // 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; // If this flag is set to true the vertex normals will be smoothed.
this.smoothNormals = true;
} // 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 = _Object$assign(_Object$create(THREE.Loader.prototype), {
constructor: LDrawLoader,
load: function load(url, onLoad, onProgress, onError) {
if (!this.fileMap) {
this.fileMap = {};
}
var scope = this;
var fileLoader = new THREE.FileLoader(this.manager);
fileLoader.setPath(this.path);
fileLoader.setRequestHeader(this.requestHeader);
fileLoader.load(url, function (text) {
scope.processObject(text, onLoad, null, url);
}, onProgress, onError);
},
parse: function parse(text, path, onLoad) {
// Async parse. This function calls onParse with the parsed THREE.Object3D as parameter
this.processObject(text, onLoad, null, path);
},
setMaterials: function setMaterials(materials) {
// Clears parse scopes stack, adds new scope with material library
this.parseScopesStack = [];
this.newParseScopeLevel(materials);
this.getCurrentParseScope().isFromParse = false;
this.materials = materials;
return this;
},
setFileMap: function setFileMap(fileMap) {
this.fileMap = fileMap;
return this;
},
newParseScopeLevel: function newParseScopeLevel(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 newParseScope = {
lib: matLib,
url: null,
// Subobjects
subobjects: null,
numSubobjects: 0,
subobjectIndex: 0,
inverted: false,
category: null,
keywords: null,
// Current subobject
currentFileName: null,
mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
currentMatrix: new THREE.Matrix4(),
matrix: new THREE.Matrix4(),
// If false, it is a root material scope previous to parse
isFromParse: true,
triangles: null,
lineSegments: null,
conditionalSegments: null,
// If true, this object is the start of a construction step
startingConstructionStep: false
};
this.parseScopesStack.push(newParseScope);
return newParseScope;
},
removeScopeLevel: function removeScopeLevel() {
this.parseScopesStack.pop();
return this;
},
addMaterial: function addMaterial(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 getMaterial(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 getParentParseScope() {
if (this.parseScopesStack.length > 1) {
return this.parseScopesStack[this.parseScopesStack.length - 2];
}
return null;
},
getCurrentParseScope: function getCurrentParseScope() {
if (this.parseScopesStack.length > 0) {
return this.parseScopesStack[this.parseScopesStack.length - 1];
}
return null;
},
parseColourMetaDirective: function parseColourMetaDirective(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:
material = new THREE.MeshStandardMaterial({
color: colour,
roughness: 0.3,
envMapIntensity: 0.3,
metalness: 0
});
break;
case LDrawLoader.FINISH_TYPE_PEARLESCENT:
// Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
var specular = new THREE.Color(colour);
var hsl = specular.getHSL({
h: 0,
s: 0,
l: 0
});
hsl.h = (hsl.h + 0.5) % 1;
hsl.l = Math.min(1, hsl.l + (1 - hsl.l) * 0.7);
specular.setHSL(hsl.h, hsl.s, hsl.l);
material = new THREE.MeshPhongMaterial({
color: colour,
specular: specular,
shininess: 10,
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 finish
material = new THREE.MeshStandardMaterial({
color: colour,
roughness: 0.9,
metalness: 0
});
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;
}
material.transparent = isTransparent;
material.premultipliedAlpha = true;
material.opacity = alpha;
material.depthWrite = !isTransparent;
material.polygonOffset = true;
material.polygonOffsetFactor = 1;
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,
transparent: isTransparent,
opacity: alpha,
depthWrite: !isTransparent
});
edgeMaterial.userData.code = code;
edgeMaterial.name = name + " - Edge";
edgeMaterial.userData.canHaveEnvMap = false; // This is the material used for conditional edges
edgeMaterial.userData.conditionalEdgeMaterial = new THREE.ShaderMaterial({
vertexShader: conditionalLineVertShader,
fragmentShader: conditionalLineFragShader,
uniforms: {
diffuse: {
value: new THREE.Color(edgeColour)
},
opacity: {
value: alpha
}
},
transparent: isTransparent,
depthWrite: !isTransparent
});
edgeMaterial.userData.conditionalEdgeMaterial.userData.canHaveEnvMap = false;
}
material.userData.code = code;
material.name = name;
material.userData.edgeMaterial = edgeMaterial;
return material;
},
//
objectParse: function objectParse(text) {
// 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 currentParseScope = this.getCurrentParseScope(); // Parse result variables
var triangles;
var lineSegments;
var conditionalSegments;
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 bfcCertified = false;
var bfcCCW = true;
var bfcInverted = false;
var bfcCull = true;
var type = '';
var startingConstructionStep = false;
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(currentParseScope.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.toLowerCase()] = 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 '!LDRAW_ORG':
type = lp.getToken();
currentParseScope.triangles = [];
currentParseScope.lineSegments = [];
currentParseScope.conditionalSegments = [];
currentParseScope.type = type;
var isRoot = !parentParseScope.isFromParse;
if (isRoot || scope.separateObjects && !isPrimitiveType(type)) {
currentParseScope.groupObject = new THREE.Group();
currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
} // If the scale of the object is negated then the triangle winding order
// needs to be flipped.
var matrix = currentParseScope.matrix;
if (matrix.determinant() < 0 && (scope.separateObjects && isPrimitiveType(type) || !scope.separateObjects)) {
currentParseScope.inverted = !currentParseScope.inverted;
}
triangles = currentParseScope.triangles;
lineSegments = currentParseScope.lineSegments;
conditionalSegments = currentParseScope.conditionalSegments;
break;
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 = '';
bfcCertified = false;
bfcCCW = true;
}
break;
case 'BFC':
// Changes to the backface culling state
while (!lp.isAtTheEnd()) {
var token = lp.getToken();
switch (token) {
case 'CERTIFY':
case 'NOCERTIFY':
bfcCertified = token === 'CERTIFY';
bfcCCW = true;
break;
case 'CW':
case 'CCW':
bfcCCW = token === 'CCW';
break;
case 'INVERTNEXT':
bfcInverted = true;
break;
case 'CLIP':
case 'NOCLIP':
bfcCull = token === 'CLIP';
break;
default:
console.warn('THREE.LDrawLoader: BFC directive "' + token + '" is unknown.');
break;
}
}
break;
case 'STEP':
startingConstructionStep = true;
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(/\\/g, "/");
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,
inverted: bfcInverted !== currentParseScope.inverted,
startingConstructionStep: startingConstructionStep
});
bfcInverted = false;
break;
// Line type 2: Line segment
case '2':
var material = parseColourCode(lp, true);
var segment = {
material: material.userData.edgeMaterial,
colourCode: material.userData.code,
v0: parseVector(lp),
v1: parseVector(lp)
};
lineSegments.push(segment);
break;
// Line type 5: Conditional Line segment
case '5':
var material = parseColourCode(lp, true);
var segment = {
material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
colourCode: material.userData.code,
v0: parseVector(lp),
v1: parseVector(lp),
c0: parseVector(lp),
c1: parseVector(lp)
};
conditionalSegments.push(segment);
break;
// Line type 3: Triangle
case '3':
var material = parseColourCode(lp);
var inverted = currentParseScope.inverted;
var ccw = bfcCCW !== inverted;
var doubleSided = !bfcCertified || !bfcCull;
var v0, v1, v2, faceNormal;
if (ccw === true) {
v0 = parseVector(lp);
v1 = parseVector(lp);
v2 = parseVector(lp);
} else {
v2 = parseVector(lp);
v1 = parseVector(lp);
v0 = parseVector(lp);
}
tempVec0.subVectors(v1, v0);
tempVec1.subVectors(v2, v1);
faceNormal = new THREE.Vector3().crossVectors(tempVec0, tempVec1).normalize();
triangles.push({
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v1,
v2: v2,
faceNormal: faceNormal,
n0: null,
n1: null,
n2: null
});
if (doubleSided === true) {
triangles.push({
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v2,
v2: v1,
faceNormal: faceNormal,
n0: null,
n1: null,
n2: null
});
}
break;
// Line type 4: Quadrilateral
case '4':
var material = parseColourCode(lp);
var inverted = currentParseScope.inverted;
var ccw = bfcCCW !== inverted;
var doubleSided = !bfcCertified || !bfcCull;
var v0, v1, v2, v3, faceNormal;
if (ccw === true) {
v0 = parseVector(lp);
v1 = parseVector(lp);
v2 = parseVector(lp);
v3 = parseVector(lp);
} else {
v3 = parseVector(lp);
v2 = parseVector(lp);
v1 = parseVector(lp);
v0 = parseVector(lp);
}
tempVec0.subVectors(v1, v0);
tempVec1.subVectors(v2, v1);
faceNormal = new THREE.Vector3().crossVectors(tempVec0, tempVec1).normalize();
triangles.push({
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v1,
v2: v2,
faceNormal: faceNormal,
n0: null,
n1: null,
n2: null
});
triangles.push({
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v2,
v2: v3,
faceNormal: faceNormal,
n0: null,
n1: null,
n2: null
});
if (doubleSided === true) {
triangles.push({
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v2,
v2: v1,
faceNormal: faceNormal,
n0: null,
n1: null,
n2: null
});
triangles.push({
material: material,
colourCode: material.userData.code,
v0: v0,
v1: v3,
v2: v2,
faceNormal: faceNormal,
n0: null,
n1: null,
n2: null
});
}
break;
default:
throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
break;
}
}
if (parsingEmbeddedFiles) {
this.subobjectCache[currentEmbeddedFileName.toLowerCase()] = currentEmbeddedText;
}
currentParseScope.category = category;
currentParseScope.keywords = keywords;
currentParseScope.subobjects = subobjects;
currentParseScope.numSubobjects = subobjects.length;
currentParseScope.subobjectIndex = 0;
},
computeConstructionSteps: function computeConstructionSteps(model) {
// Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object.
var stepNumber = 0;
model.traverse(function (c) {
if (c.isGroup) {
if (c.userData.startingConstructionStep) {
stepNumber++;
}
c.userData.constructionStep = stepNumber;
}
});
model.userData.numConstructionSteps = stepNumber + 1;
},
processObject: function processObject(text, onProcessed, subobject, url) {
var scope = this;
var parseScope = scope.newParseScopeLevel();
parseScope.url = url;
var parentParseScope = scope.getParentParseScope(); // Set current matrix
if (subobject) {
parseScope.currentMatrix.multiplyMatrices(parentParseScope.currentMatrix, subobject.matrix);
parseScope.matrix.copy(subobject.matrix);
parseScope.inverted = subobject.inverted;
parseScope.startingConstructionStep = subobject.startingConstructionStep;
} // Add to cache
var currentFileName = parentParseScope.currentFileName;
if (currentFileName !== null) {
currentFileName = parentParseScope.currentFileName.toLowerCase();
}
if (scope.subobjectCache[currentFileName] === undefined) {
scope.subobjectCache[currentFileName] = text;
} // Parse the object (returns a THREE.Group)
scope.objectParse(text);
var finishedCount = 0;
onSubobjectFinish();
function onSubobjectFinish() {
finishedCount++;
if (finishedCount === parseScope.subobjects.length + 1) {
finalizeObject();
} else {
// Once the previous subobject has finished we can start processing the next one in the list.
// The subobject processing shares scope in processing so it's important that they be loaded serially
// to avoid race conditions.
// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
var subobject = parseScope.subobjects[parseScope.subobjectIndex];
_Promise.resolve().then(function () {
loadSubobject(subobject);
});
parseScope.subobjectIndex++;
}
}
function finalizeObject() {
if (scope.smoothNormals && parseScope.type === 'Part') {
smoothNormals(parseScope.triangles, parseScope.lineSegments);
}
var isRoot = !parentParseScope.isFromParse;
if (scope.separateObjects && !isPrimitiveType(parseScope.type) || isRoot) {
var objGroup = parseScope.groupObject;
if (parseScope.triangles.length > 0) {
objGroup.add(createObject(parseScope.triangles, 3));
}
if (parseScope.lineSegments.length > 0) {
objGroup.add(createObject(parseScope.lineSegments, 2));
}
if (parseScope.conditionalSegments.length > 0) {
objGroup.add(createObject(parseScope.conditionalSegments, 2, true));
}
if (parentParseScope.groupObject) {
objGroup.name = parseScope.fileName;
objGroup.userData.category = parseScope.category;
objGroup.userData.keywords = parseScope.keywords;
parseScope.matrix.decompose(objGroup.position, objGroup.quaternion, objGroup.scale);
parentParseScope.groupObject.add(objGroup);
}
} else {
var separateObjects = scope.separateObjects;
var parentLineSegments = parentParseScope.lineSegments;
var parentConditionalSegments = parentParseScope.conditionalSegments;
var parentTriangles = parentParseScope.triangles;
var lineSegments = parseScope.lineSegments;
var conditionalSegments = parseScope.conditionalSegments;
var triangles = parseScope.triangles;
for (var i = 0, l = lineSegments.length; i < l; i++) {
var ls = lineSegments[i];
if (separateObjects) {
ls.v0.applyMatrix4(parseScope.matrix);
ls.v1.applyMatrix4(parseScope.matrix);
}
parentLineSegments.push(ls);
}
for (var i = 0, l = conditionalSegments.length; i < l; i++) {
var os = conditionalSegments[i];
if (separateObjects) {
os.v0.applyMatrix4(parseScope.matrix);
os.v1.applyMatrix4(parseScope.matrix);
os.c0.applyMatrix4(parseScope.matrix);
os.c1.applyMatrix4(parseScope.matrix);
}
parentConditionalSegments.push(os);
}
for (var i = 0, l = triangles.length; i < l; i++) {
var tri = triangles[i];
if (separateObjects) {
tri.v0 = tri.v0.clone().applyMatrix4(parseScope.matrix);
tri.v1 = tri.v1.clone().applyMatrix4(parseScope.matrix);
tri.v2 = tri.v2.clone().applyMatrix4(parseScope.matrix);
tempVec0.subVectors(tri.v1, tri.v0);
tempVec1.subVectors(tri.v2, tri.v1);
tri.faceNormal.crossVectors(tempVec0, tempVec1).normalize();
}
parentTriangles.push(tri);
}
}
scope.removeScopeLevel(); // If it is root object, compute construction steps
if (!parentParseScope.isFromParse) {
scope.computeConstructionSteps(parseScope.groupObject);
}
if (onProcessed) {
onProcessed(parseScope.groupObject);
}
}
function loadSubobject(subobject) {
parseScope.mainColourCode = subobject.ma