Buckets:
| ; | |
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | |
| const THREE = require("three"); | |
| const constants = require("../_polyfill/constants.cjs"); | |
| const FINISH_TYPE_DEFAULT = 0; | |
| const FINISH_TYPE_CHROME = 1; | |
| const FINISH_TYPE_PEARLESCENT = 2; | |
| const FINISH_TYPE_RUBBER = 3; | |
| const FINISH_TYPE_MATTE_METALLIC = 4; | |
| const FINISH_TYPE_METAL = 5; | |
| const FILE_LOCATION_AS_IS = 0; | |
| const FILE_LOCATION_TRY_PARTS = 1; | |
| const FILE_LOCATION_TRY_P = 2; | |
| const FILE_LOCATION_TRY_MODELS = 3; | |
| const FILE_LOCATION_TRY_RELATIVE = 4; | |
| const FILE_LOCATION_TRY_ABSOLUTE = 5; | |
| const FILE_LOCATION_NOT_FOUND = 6; | |
| const MAIN_COLOUR_CODE = "16"; | |
| const MAIN_EDGE_COLOUR_CODE = "24"; | |
| const _tempVec0 = /* @__PURE__ */ new THREE.Vector3(); | |
| const _tempVec1 = /* @__PURE__ */ new THREE.Vector3(); | |
| class LDrawConditionalLineMaterial extends THREE.ShaderMaterial { | |
| constructor(parameters) { | |
| super({ | |
| uniforms: THREE.UniformsUtils.merge([ | |
| THREE.UniformsLib.fog, | |
| { | |
| diffuse: { | |
| value: new THREE.Color() | |
| }, | |
| opacity: { | |
| value: 1 | |
| } | |
| } | |
| ]), | |
| vertexShader: ( | |
| /* glsl */ | |
| ` | |
| attribute vec3 control0; | |
| attribute vec3 control1; | |
| attribute vec3 direction; | |
| varying float discardFlag; | |
| #include <common> | |
| #include <color_pars_vertex> | |
| #include <fog_pars_vertex> | |
| #include <logdepthbuf_pars_vertex> | |
| #include <clipping_planes_pars_vertex> | |
| void main() { | |
| #include <color_vertex> | |
| vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); | |
| gl_Position = projectionMatrix * mvPosition; | |
| // Transform the line segment ends and control points into camera clip space | |
| vec4 c0 = projectionMatrix * modelViewMatrix * vec4(control0, 1.0); | |
| vec4 c1 = projectionMatrix * modelViewMatrix * vec4(control1, 1.0); | |
| vec4 p0 = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| vec4 p1 = projectionMatrix * modelViewMatrix * vec4(position + direction, 1.0); | |
| c0.xy /= c0.w; | |
| c1.xy /= c1.w; | |
| p0.xy /= p0.w; | |
| p1.xy /= p1.w; | |
| // Get the direction of the segment and an orthogonal vector | |
| vec2 dir = p1.xy - p0.xy; | |
| vec2 norm = vec2(-dir.y, dir.x); | |
| // Get control point directions from the line | |
| vec2 c0dir = c0.xy - p1.xy; | |
| vec2 c1dir = c1.xy - p1.xy; | |
| // If the vectors to the controls points are pointed in different directions away | |
| // from the line segment then the line should not be drawn. | |
| float d0 = dot(normalize(norm), normalize(c0dir)); | |
| float d1 = dot(normalize(norm), normalize(c1dir)); | |
| discardFlag = float(sign(d0) != sign(d1)); | |
| #include <logdepthbuf_vertex> | |
| #include <clipping_planes_vertex> | |
| #include <fog_vertex> | |
| } | |
| ` | |
| ), | |
| fragmentShader: ( | |
| /* glsl */ | |
| ` | |
| uniform vec3 diffuse; | |
| uniform float opacity; | |
| varying float discardFlag; | |
| #include <common> | |
| #include <color_pars_fragment> | |
| #include <fog_pars_fragment> | |
| #include <logdepthbuf_pars_fragment> | |
| #include <clipping_planes_pars_fragment> | |
| void main() { | |
| if (discardFlag > 0.5) discard; | |
| #include <clipping_planes_fragment> | |
| vec3 outgoingLight = vec3(0.0); | |
| vec4 diffuseColor = vec4(diffuse, opacity); | |
| #include <logdepthbuf_fragment> | |
| #include <color_fragment> | |
| outgoingLight = diffuseColor.rgb; // simple shader | |
| gl_FragColor = vec4(outgoingLight, diffuseColor.a); | |
| #include <tonemapping_fragment> | |
| #include <${constants.version >= 154 ? "colorspace_fragment" : "encodings_fragment"}> | |
| #include <fog_fragment> | |
| #include <premultiplied_alpha_fragment> | |
| } | |
| ` | |
| ) | |
| }); | |
| Object.defineProperties(this, { | |
| opacity: { | |
| get: function() { | |
| return this.uniforms.opacity.value; | |
| }, | |
| set: function(value) { | |
| this.uniforms.opacity.value = value; | |
| } | |
| }, | |
| color: { | |
| get: function() { | |
| return this.uniforms.diffuse.value; | |
| } | |
| } | |
| }); | |
| this.setValues(parameters); | |
| this.isLDrawConditionalLineMaterial = true; | |
| } | |
| } | |
| class ConditionalLineSegments extends THREE.LineSegments { | |
| constructor(geometry, material) { | |
| super(geometry, material); | |
| this.isConditionalLine = true; | |
| } | |
| } | |
| function generateFaceNormals(faces) { | |
| for (let i = 0, l = faces.length; i < l; i++) { | |
| const face = faces[i]; | |
| const vertices = face.vertices; | |
| const v0 = vertices[0]; | |
| const v1 = vertices[1]; | |
| const v2 = vertices[2]; | |
| _tempVec0.subVectors(v1, v0); | |
| _tempVec1.subVectors(v2, v1); | |
| face.faceNormal = new THREE.Vector3().crossVectors(_tempVec0, _tempVec1).normalize(); | |
| } | |
| } | |
| const _ray = /* @__PURE__ */ new THREE.Ray(); | |
| function smoothNormals(faces, lineSegments, checkSubSegments = false) { | |
| const hashMultiplier = (1 + 1e-10) * 100; | |
| function hashVertex(v) { | |
| const x = ~~(v.x * hashMultiplier); | |
| const y = ~~(v.y * hashMultiplier); | |
| const z = ~~(v.z * hashMultiplier); | |
| return `${x},${y},${z}`; | |
| } | |
| function hashEdge(v0, v1) { | |
| return `${hashVertex(v0)}_${hashVertex(v1)}`; | |
| } | |
| function toNormalizedRay(v0, v1, targetRay) { | |
| targetRay.direction.subVectors(v1, v0).normalize(); | |
| const scalar = v0.dot(targetRay.direction); | |
| targetRay.origin.copy(v0).addScaledVector(targetRay.direction, -scalar); | |
| return targetRay; | |
| } | |
| function hashRay(ray) { | |
| return hashEdge(ray.origin, ray.direction); | |
| } | |
| const hardEdges = /* @__PURE__ */ new Set(); | |
| const hardEdgeRays = /* @__PURE__ */ new Map(); | |
| const halfEdgeList = {}; | |
| const normals = []; | |
| for (let i = 0, l = lineSegments.length; i < l; i++) { | |
| const ls = lineSegments[i]; | |
| const vertices = ls.vertices; | |
| const v0 = vertices[0]; | |
| const v1 = vertices[1]; | |
| hardEdges.add(hashEdge(v0, v1)); | |
| hardEdges.add(hashEdge(v1, v0)); | |
| if (checkSubSegments) { | |
| const ray = toNormalizedRay(v0, v1, new THREE.Ray()); | |
| const rh1 = hashRay(ray); | |
| if (!hardEdgeRays.has(rh1)) { | |
| toNormalizedRay(v1, v0, ray); | |
| const rh2 = hashRay(ray); | |
| const info2 = { | |
| ray, | |
| distances: [] | |
| }; | |
| hardEdgeRays.set(rh1, info2); | |
| hardEdgeRays.set(rh2, info2); | |
| } | |
| const info = hardEdgeRays.get(rh1); | |
| let d0 = info.ray.direction.dot(v0); | |
| let d1 = info.ray.direction.dot(v1); | |
| if (d0 > d1) { | |
| [d0, d1] = [d1, d0]; | |
| } | |
| info.distances.push(d0, d1); | |
| } | |
| } | |
| for (let i = 0, l = faces.length; i < l; i++) { | |
| const tri = faces[i]; | |
| const vertices = tri.vertices; | |
| const vertCount = vertices.length; | |
| for (let i2 = 0; i2 < vertCount; i2++) { | |
| const index = i2; | |
| const next = (i2 + 1) % vertCount; | |
| const v0 = vertices[index]; | |
| const v1 = vertices[next]; | |
| const hash = hashEdge(v0, v1); | |
| if (hardEdges.has(hash)) { | |
| continue; | |
| } | |
| if (checkSubSegments) { | |
| toNormalizedRay(v0, v1, _ray); | |
| const rayHash = hashRay(_ray); | |
| if (hardEdgeRays.has(rayHash)) { | |
| const info2 = hardEdgeRays.get(rayHash); | |
| const { ray, distances } = info2; | |
| let d0 = ray.direction.dot(v0); | |
| let d1 = ray.direction.dot(v1); | |
| if (d0 > d1) { | |
| [d0, d1] = [d1, d0]; | |
| } | |
| let found = false; | |
| for (let i3 = 0, l2 = distances.length; i3 < l2; i3 += 2) { | |
| if (d0 >= distances[i3] && d1 <= distances[i3 + 1]) { | |
| found = true; | |
| break; | |
| } | |
| } | |
| if (found) { | |
| continue; | |
| } | |
| } | |
| } | |
| const info = { | |
| index, | |
| tri | |
| }; | |
| halfEdgeList[hash] = info; | |
| } | |
| } | |
| while (true) { | |
| let halfEdge = null; | |
| for (const key in halfEdgeList) { | |
| halfEdge = halfEdgeList[key]; | |
| break; | |
| } | |
| if (halfEdge === null) { | |
| break; | |
| } | |
| const queue = [halfEdge]; | |
| while (queue.length > 0) { | |
| const tri = queue.pop().tri; | |
| const vertices = tri.vertices; | |
| const vertNormals = tri.normals; | |
| const faceNormal = tri.faceNormal; | |
| const vertCount = vertices.length; | |
| for (let i2 = 0; i2 < vertCount; i2++) { | |
| const index = i2; | |
| const next = (i2 + 1) % vertCount; | |
| const v0 = vertices[index]; | |
| const v1 = vertices[next]; | |
| const hash = hashEdge(v0, v1); | |
| delete halfEdgeList[hash]; | |
| const reverseHash = hashEdge(v1, v0); | |
| const otherInfo = halfEdgeList[reverseHash]; | |
| if (otherInfo) { | |
| const otherTri = otherInfo.tri; | |
| const otherIndex = otherInfo.index; | |
| const otherNormals = otherTri.normals; | |
| const otherVertCount = otherNormals.length; | |
| const otherFaceNormal = otherTri.faceNormal; | |
| if (Math.abs(otherTri.faceNormal.dot(tri.faceNormal)) < 0.25) { | |
| continue; | |
| } | |
| if (reverseHash in halfEdgeList) { | |
| queue.push(otherInfo); | |
| delete halfEdgeList[reverseHash]; | |
| } | |
| const otherNext = (otherIndex + 1) % otherVertCount; | |
| if (vertNormals[index] && otherNormals[otherNext] && vertNormals[index] !== otherNormals[otherNext]) { | |
| otherNormals[otherNext].norm.add(vertNormals[index].norm); | |
| vertNormals[index].norm = otherNormals[otherNext].norm; | |
| } | |
| let sharedNormal1 = vertNormals[index] || otherNormals[otherNext]; | |
| if (sharedNormal1 === null) { | |
| sharedNormal1 = { norm: new THREE.Vector3() }; | |
| normals.push(sharedNormal1.norm); | |
| } | |
| if (vertNormals[index] === null) { | |
| vertNormals[index] = sharedNormal1; | |
| sharedNormal1.norm.add(faceNormal); | |
| } | |
| if (otherNormals[otherNext] === null) { | |
| otherNormals[otherNext] = sharedNormal1; | |
| sharedNormal1.norm.add(otherFaceNormal); | |
| } | |
| if (vertNormals[next] && otherNormals[otherIndex] && vertNormals[next] !== otherNormals[otherIndex]) { | |
| otherNormals[otherIndex].norm.add(vertNormals[next].norm); | |
| vertNormals[next].norm = otherNormals[otherIndex].norm; | |
| } | |
| let sharedNormal2 = vertNormals[next] || otherNormals[otherIndex]; | |
| if (sharedNormal2 === null) { | |
| sharedNormal2 = { norm: new THREE.Vector3() }; | |
| normals.push(sharedNormal2.norm); | |
| } | |
| if (vertNormals[next] === null) { | |
| vertNormals[next] = sharedNormal2; | |
| sharedNormal2.norm.add(faceNormal); | |
| } | |
| if (otherNormals[otherIndex] === null) { | |
| otherNormals[otherIndex] = sharedNormal2; | |
| sharedNormal2.norm.add(otherFaceNormal); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| for (let i = 0, l = normals.length; i < l; i++) { | |
| normals[i].normalize(); | |
| } | |
| } | |
| function isPartType(type) { | |
| return type === "Part" || type === "Unofficial_Part"; | |
| } | |
| function isPrimitiveType(type) { | |
| return /primitive/i.test(type) || type === "Subpart"; | |
| } | |
| class LineParser { | |
| constructor(line, lineNumber) { | |
| this.line = line; | |
| this.lineLength = line.length; | |
| this.currentCharIndex = 0; | |
| this.currentChar = " "; | |
| this.lineNumber = lineNumber; | |
| } | |
| seekNonSpace() { | |
| while (this.currentCharIndex < this.lineLength) { | |
| this.currentChar = this.line.charAt(this.currentCharIndex); | |
| if (this.currentChar !== " " && this.currentChar !== " ") { | |
| return; | |
| } | |
| this.currentCharIndex++; | |
| } | |
| } | |
| getToken() { | |
| const pos0 = this.currentCharIndex++; | |
| while (this.currentCharIndex < this.lineLength) { | |
| this.currentChar = this.line.charAt(this.currentCharIndex); | |
| if (this.currentChar === " " || this.currentChar === " ") { | |
| break; | |
| } | |
| this.currentCharIndex++; | |
| } | |
| const pos1 = this.currentCharIndex; | |
| this.seekNonSpace(); | |
| return this.line.substring(pos0, pos1); | |
| } | |
| getVector() { | |
| return new THREE.Vector3(parseFloat(this.getToken()), parseFloat(this.getToken()), parseFloat(this.getToken())); | |
| } | |
| getRemainingString() { | |
| return this.line.substring(this.currentCharIndex, this.lineLength); | |
| } | |
| isAtTheEnd() { | |
| return this.currentCharIndex >= this.lineLength; | |
| } | |
| setToEnd() { | |
| this.currentCharIndex = this.lineLength; | |
| } | |
| getLineNumberString() { | |
| return this.lineNumber >= 0 ? " at line " + this.lineNumber : ""; | |
| } | |
| } | |
| class LDrawParsedCache { | |
| constructor(loader) { | |
| this.loader = loader; | |
| this._cache = {}; | |
| } | |
| cloneResult(original) { | |
| const result = {}; | |
| result.faces = original.faces.map((face) => { | |
| return { | |
| colorCode: face.colorCode, | |
| material: face.material, | |
| vertices: face.vertices.map((v) => v.clone()), | |
| normals: face.normals.map(() => null), | |
| faceNormal: null | |
| }; | |
| }); | |
| result.conditionalSegments = original.conditionalSegments.map((face) => { | |
| return { | |
| colorCode: face.colorCode, | |
| material: face.material, | |
| vertices: face.vertices.map((v) => v.clone()), | |
| controlPoints: face.controlPoints.map((v) => v.clone()) | |
| }; | |
| }); | |
| result.lineSegments = original.lineSegments.map((face) => { | |
| return { | |
| colorCode: face.colorCode, | |
| material: face.material, | |
| vertices: face.vertices.map((v) => v.clone()) | |
| }; | |
| }); | |
| result.type = original.type; | |
| result.category = original.category; | |
| result.keywords = original.keywords; | |
| result.subobjects = original.subobjects; | |
| result.totalFaces = original.totalFaces; | |
| result.startingConstructionStep = original.startingConstructionStep; | |
| result.materials = original.materials; | |
| result.group = null; | |
| return result; | |
| } | |
| async fetchData(fileName) { | |
| let triedLowerCase = false; | |
| let locationState = FILE_LOCATION_AS_IS; | |
| while (locationState !== FILE_LOCATION_NOT_FOUND) { | |
| let subobjectURL = fileName; | |
| switch (locationState) { | |
| case FILE_LOCATION_AS_IS: | |
| locationState = locationState + 1; | |
| break; | |
| case FILE_LOCATION_TRY_PARTS: | |
| subobjectURL = "parts/" + subobjectURL; | |
| locationState = locationState + 1; | |
| break; | |
| case FILE_LOCATION_TRY_P: | |
| subobjectURL = "p/" + subobjectURL; | |
| locationState = locationState + 1; | |
| break; | |
| case FILE_LOCATION_TRY_MODELS: | |
| subobjectURL = "models/" + subobjectURL; | |
| locationState = locationState + 1; | |
| break; | |
| case FILE_LOCATION_TRY_RELATIVE: | |
| subobjectURL = fileName.substring(0, fileName.lastIndexOf("/") + 1) + subobjectURL; | |
| locationState = locationState + 1; | |
| break; | |
| case FILE_LOCATION_TRY_ABSOLUTE: | |
| if (triedLowerCase) { | |
| locationState = FILE_LOCATION_NOT_FOUND; | |
| } else { | |
| fileName = fileName.toLowerCase(); | |
| subobjectURL = fileName; | |
| triedLowerCase = true; | |
| locationState = FILE_LOCATION_AS_IS; | |
| } | |
| break; | |
| } | |
| const loader = this.loader; | |
| const fileLoader = new THREE.FileLoader(loader.manager); | |
| fileLoader.setPath(loader.partsLibraryPath); | |
| fileLoader.setRequestHeader(loader.requestHeader); | |
| fileLoader.setWithCredentials(loader.withCredentials); | |
| try { | |
| const text = await fileLoader.loadAsync(subobjectURL); | |
| return text; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| throw new Error('LDrawLoader: Subobject "' + fileName + '" could not be loaded.'); | |
| } | |
| parse(text, fileName = null) { | |
| const loader = this.loader; | |
| const faces = []; | |
| const lineSegments = []; | |
| const conditionalSegments = []; | |
| const subobjects = []; | |
| const materials = {}; | |
| const getLocalMaterial = (colorCode) => { | |
| return materials[colorCode] || null; | |
| }; | |
| let type = "Model"; | |
| let category = null; | |
| let keywords = null; | |
| let totalFaces = 0; | |
| if (text.indexOf("\r\n") !== -1) { | |
| text = text.replace(/\r\n/g, "\n"); | |
| } | |
| const lines = text.split("\n"); | |
| const numLines = lines.length; | |
| let parsingEmbeddedFiles = false; | |
| let currentEmbeddedFileName = null; | |
| let currentEmbeddedText = null; | |
| let bfcCertified = false; | |
| let bfcCCW = true; | |
| let bfcInverted = false; | |
| let bfcCull = true; | |
| let startingConstructionStep = false; | |
| for (let lineIndex = 0; lineIndex < numLines; lineIndex++) { | |
| const line = lines[lineIndex]; | |
| if (line.length === 0) | |
| continue; | |
| if (parsingEmbeddedFiles) { | |
| if (line.startsWith("0 FILE ")) { | |
| this.setData(currentEmbeddedFileName, currentEmbeddedText); | |
| currentEmbeddedFileName = line.substring(7); | |
| currentEmbeddedText = ""; | |
| } else { | |
| currentEmbeddedText += line + "\n"; | |
| } | |
| continue; | |
| } | |
| const lp = new LineParser(line, lineIndex + 1); | |
| lp.seekNonSpace(); | |
| if (lp.isAtTheEnd()) { | |
| continue; | |
| } | |
| const lineType = lp.getToken(); | |
| let material; | |
| let colorCode; | |
| let segment; | |
| let ccw; | |
| let doubleSided; | |
| let v0, v1, v2, v3, c0, c1; | |
| switch (lineType) { | |
| case "0": | |
| const meta = lp.getToken(); | |
| if (meta) { | |
| switch (meta) { | |
| case "!LDRAW_ORG": | |
| type = lp.getToken(); | |
| break; | |
| case "!COLOUR": | |
| material = loader.parseColorMetaDirective(lp); | |
| if (material) { | |
| materials[material.userData.code] = material; | |
| } else { | |
| console.warn("LDrawLoader: Error parsing material" + lp.getLineNumberString()); | |
| } | |
| break; | |
| case "!CATEGORY": | |
| category = lp.getToken(); | |
| break; | |
| case "!KEYWORDS": | |
| const 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) { | |
| parsingEmbeddedFiles = true; | |
| currentEmbeddedFileName = lp.getRemainingString(); | |
| currentEmbeddedText = ""; | |
| bfcCertified = false; | |
| bfcCCW = true; | |
| } | |
| break; | |
| case "BFC": | |
| while (!lp.isAtTheEnd()) { | |
| const 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; | |
| } | |
| } | |
| break; | |
| case "1": | |
| colorCode = lp.getToken(); | |
| material = getLocalMaterial(colorCode); | |
| const posX = parseFloat(lp.getToken()); | |
| const posY = parseFloat(lp.getToken()); | |
| const posZ = parseFloat(lp.getToken()); | |
| const m0 = parseFloat(lp.getToken()); | |
| const m1 = parseFloat(lp.getToken()); | |
| const m2 = parseFloat(lp.getToken()); | |
| const m3 = parseFloat(lp.getToken()); | |
| const m4 = parseFloat(lp.getToken()); | |
| const m5 = parseFloat(lp.getToken()); | |
| const m6 = parseFloat(lp.getToken()); | |
| const m7 = parseFloat(lp.getToken()); | |
| const m8 = parseFloat(lp.getToken()); | |
| const matrix = new THREE.Matrix4().set(m0, m1, m2, posX, m3, m4, m5, posY, m6, m7, m8, posZ, 0, 0, 0, 1); | |
| let fileName2 = lp.getRemainingString().trim().replace(/\\/g, "/"); | |
| if (loader.fileMap[fileName2]) { | |
| fileName2 = loader.fileMap[fileName2]; | |
| } else { | |
| if (fileName2.startsWith("s/")) { | |
| fileName2 = "parts/" + fileName2; | |
| } else if (fileName2.startsWith("48/")) { | |
| fileName2 = "p/" + fileName2; | |
| } | |
| } | |
| subobjects.push({ | |
| material, | |
| colorCode, | |
| matrix, | |
| fileName: fileName2, | |
| inverted: bfcInverted, | |
| startingConstructionStep | |
| }); | |
| bfcInverted = false; | |
| break; | |
| case "2": | |
| colorCode = lp.getToken(); | |
| material = getLocalMaterial(colorCode); | |
| v0 = lp.getVector(); | |
| v1 = lp.getVector(); | |
| segment = { | |
| material, | |
| colorCode, | |
| vertices: [v0, v1] | |
| }; | |
| lineSegments.push(segment); | |
| break; | |
| case "5": | |
| colorCode = lp.getToken(); | |
| material = getLocalMaterial(colorCode); | |
| v0 = lp.getVector(); | |
| v1 = lp.getVector(); | |
| c0 = lp.getVector(); | |
| c1 = lp.getVector(); | |
| segment = { | |
| material, | |
| colorCode, | |
| vertices: [v0, v1], | |
| controlPoints: [c0, c1] | |
| }; | |
| conditionalSegments.push(segment); | |
| break; | |
| case "3": | |
| colorCode = lp.getToken(); | |
| material = getLocalMaterial(colorCode); | |
| ccw = bfcCCW; | |
| doubleSided = !bfcCertified || !bfcCull; | |
| if (ccw === true) { | |
| v0 = lp.getVector(); | |
| v1 = lp.getVector(); | |
| v2 = lp.getVector(); | |
| } else { | |
| v2 = lp.getVector(); | |
| v1 = lp.getVector(); | |
| v0 = lp.getVector(); | |
| } | |
| faces.push({ | |
| material, | |
| colorCode, | |
| faceNormal: null, | |
| vertices: [v0, v1, v2], | |
| normals: [null, null, null] | |
| }); | |
| totalFaces++; | |
| if (doubleSided === true) { | |
| faces.push({ | |
| material, | |
| colorCode, | |
| faceNormal: null, | |
| vertices: [v2, v1, v0], | |
| normals: [null, null, null] | |
| }); | |
| totalFaces++; | |
| } | |
| break; | |
| case "4": | |
| colorCode = lp.getToken(); | |
| material = getLocalMaterial(colorCode); | |
| ccw = bfcCCW; | |
| doubleSided = !bfcCertified || !bfcCull; | |
| if (ccw === true) { | |
| v0 = lp.getVector(); | |
| v1 = lp.getVector(); | |
| v2 = lp.getVector(); | |
| v3 = lp.getVector(); | |
| } else { | |
| v3 = lp.getVector(); | |
| v2 = lp.getVector(); | |
| v1 = lp.getVector(); | |
| v0 = lp.getVector(); | |
| } | |
| faces.push({ | |
| material, | |
| colorCode, | |
| faceNormal: null, | |
| vertices: [v0, v1, v2, v3], | |
| normals: [null, null, null, null] | |
| }); | |
| totalFaces += 2; | |
| if (doubleSided === true) { | |
| faces.push({ | |
| material, | |
| colorCode, | |
| faceNormal: null, | |
| vertices: [v3, v2, v1, v0], | |
| normals: [null, null, null, null] | |
| }); | |
| totalFaces += 2; | |
| } | |
| break; | |
| default: | |
| throw new Error('LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + "."); | |
| } | |
| } | |
| if (parsingEmbeddedFiles) { | |
| this.setData(currentEmbeddedFileName, currentEmbeddedText); | |
| } | |
| return { | |
| faces, | |
| conditionalSegments, | |
| lineSegments, | |
| type, | |
| category, | |
| keywords, | |
| subobjects, | |
| totalFaces, | |
| startingConstructionStep, | |
| materials, | |
| fileName, | |
| group: null | |
| }; | |
| } | |
| // returns an (optionally cloned) instance of the data | |
| getData(fileName, clone = true) { | |
| const key = fileName.toLowerCase(); | |
| const result = this._cache[key]; | |
| if (result === null || result instanceof Promise) { | |
| return null; | |
| } | |
| if (clone) { | |
| return this.cloneResult(result); | |
| } else { | |
| return result; | |
| } | |
| } | |
| // kicks off a fetch and parse of the requested data if it hasn't already been loaded. Returns when | |
| // the data is ready to use and can be retrieved synchronously with "getData". | |
| async ensureDataLoaded(fileName) { | |
| const key = fileName.toLowerCase(); | |
| if (!(key in this._cache)) { | |
| this._cache[key] = this.fetchData(fileName).then((text) => { | |
| const info = this.parse(text, fileName); | |
| this._cache[key] = info; | |
| return info; | |
| }); | |
| } | |
| await this._cache[key]; | |
| } | |
| // sets the data in the cache from parsed data | |
| setData(fileName, text) { | |
| const key = fileName.toLowerCase(); | |
| this._cache[key] = this.parse(text, fileName); | |
| } | |
| } | |
| function getMaterialFromCode(colorCode, parentColorCode, materialHierarchy, forEdge) { | |
| const isPassthrough = !forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; | |
| if (isPassthrough) { | |
| colorCode = parentColorCode; | |
| } | |
| return materialHierarchy[colorCode] || null; | |
| } | |
| class LDrawPartsGeometryCache { | |
| constructor(loader) { | |
| this.loader = loader; | |
| this.parseCache = new LDrawParsedCache(loader); | |
| this._cache = {}; | |
| } | |
| // Convert the given file information into a mesh by processing subobjects. | |
| async processIntoMesh(info) { | |
| const loader = this.loader; | |
| const parseCache = this.parseCache; | |
| const faceMaterials = /* @__PURE__ */ new Set(); | |
| const processInfoSubobjects = async (info2, subobject = null) => { | |
| const subobjects = info2.subobjects; | |
| const promises = []; | |
| for (let i = 0, l = subobjects.length; i < l; i++) { | |
| const subobject2 = subobjects[i]; | |
| const promise = parseCache.ensureDataLoaded(subobject2.fileName).then(() => { | |
| const subobjectInfo = parseCache.getData(subobject2.fileName, false); | |
| if (!isPrimitiveType(subobjectInfo.type)) { | |
| return this.loadModel(subobject2.fileName).catch((error) => { | |
| console.warn(error); | |
| return null; | |
| }); | |
| } | |
| return processInfoSubobjects(parseCache.getData(subobject2.fileName), subobject2); | |
| }); | |
| promises.push(promise); | |
| } | |
| const group2 = new THREE.Group(); | |
| group2.userData.category = info2.category; | |
| group2.userData.keywords = info2.keywords; | |
| info2.group = group2; | |
| const subobjectInfos = await Promise.all(promises); | |
| for (let i = 0, l = subobjectInfos.length; i < l; i++) { | |
| const subobject2 = info2.subobjects[i]; | |
| const subobjectInfo = subobjectInfos[i]; | |
| if (subobjectInfo === null) { | |
| continue; | |
| } | |
| if (subobjectInfo.isGroup) { | |
| const subobjectGroup = subobjectInfo; | |
| subobject2.matrix.decompose(subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale); | |
| subobjectGroup.userData.startingConstructionStep = subobject2.startingConstructionStep; | |
| subobjectGroup.name = subobject2.fileName; | |
| loader.applyMaterialsToMesh(subobjectGroup, subobject2.colorCode, info2.materials); | |
| group2.add(subobjectGroup); | |
| continue; | |
| } | |
| if (subobjectInfo.group.children.length) { | |
| group2.add(subobjectInfo.group); | |
| } | |
| const parentLineSegments = info2.lineSegments; | |
| const parentConditionalSegments = info2.conditionalSegments; | |
| const parentFaces = info2.faces; | |
| const lineSegments = subobjectInfo.lineSegments; | |
| const conditionalSegments = subobjectInfo.conditionalSegments; | |
| const faces = subobjectInfo.faces; | |
| const matrix = subobject2.matrix; | |
| const inverted = subobject2.inverted; | |
| const matrixScaleInverted = matrix.determinant() < 0; | |
| const colorCode = subobject2.colorCode; | |
| const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode; | |
| for (let i2 = 0, l2 = lineSegments.length; i2 < l2; i2++) { | |
| const ls = lineSegments[i2]; | |
| const vertices = ls.vertices; | |
| vertices[0].applyMatrix4(matrix); | |
| vertices[1].applyMatrix4(matrix); | |
| ls.colorCode = ls.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : ls.colorCode; | |
| ls.material = ls.material || getMaterialFromCode(ls.colorCode, ls.colorCode, info2.materials, true); | |
| parentLineSegments.push(ls); | |
| } | |
| for (let i2 = 0, l2 = conditionalSegments.length; i2 < l2; i2++) { | |
| const os = conditionalSegments[i2]; | |
| const vertices = os.vertices; | |
| const controlPoints = os.controlPoints; | |
| vertices[0].applyMatrix4(matrix); | |
| vertices[1].applyMatrix4(matrix); | |
| controlPoints[0].applyMatrix4(matrix); | |
| controlPoints[1].applyMatrix4(matrix); | |
| os.colorCode = os.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : os.colorCode; | |
| os.material = os.material || getMaterialFromCode(os.colorCode, os.colorCode, info2.materials, true); | |
| parentConditionalSegments.push(os); | |
| } | |
| for (let i2 = 0, l2 = faces.length; i2 < l2; i2++) { | |
| const tri = faces[i2]; | |
| const vertices = tri.vertices; | |
| for (let i3 = 0, l3 = vertices.length; i3 < l3; i3++) { | |
| vertices[i3].applyMatrix4(matrix); | |
| } | |
| tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode; | |
| tri.material = tri.material || getMaterialFromCode(tri.colorCode, colorCode, info2.materials, false); | |
| faceMaterials.add(tri.colorCode); | |
| if (matrixScaleInverted !== inverted) { | |
| vertices.reverse(); | |
| } | |
| parentFaces.push(tri); | |
| } | |
| info2.totalFaces += subobjectInfo.totalFaces; | |
| } | |
| if (subobject) { | |
| loader.applyMaterialsToMesh(group2, subobject.colorCode, info2.materials); | |
| } | |
| return info2; | |
| }; | |
| for (let i = 0, l = info.faces; i < l; i++) { | |
| faceMaterials.add(info.faces[i].colorCode); | |
| } | |
| await processInfoSubobjects(info); | |
| if (loader.smoothNormals) { | |
| const checkSubSegments = faceMaterials.size > 1; | |
| generateFaceNormals(info.faces); | |
| smoothNormals(info.faces, info.lineSegments, checkSubSegments); | |
| } | |
| const group = info.group; | |
| if (info.faces.length > 0) { | |
| group.add(createObject(info.faces, 3, false, info.totalFaces)); | |
| } | |
| if (info.lineSegments.length > 0) { | |
| group.add(createObject(info.lineSegments, 2)); | |
| } | |
| if (info.conditionalSegments.length > 0) { | |
| group.add(createObject(info.conditionalSegments, 2, true)); | |
| } | |
| return group; | |
| } | |
| hasCachedModel(fileName) { | |
| return fileName !== null && fileName.toLowerCase() in this._cache; | |
| } | |
| async getCachedModel(fileName) { | |
| if (fileName !== null && this.hasCachedModel(fileName)) { | |
| const key = fileName.toLowerCase(); | |
| const group = await this._cache[key]; | |
| return group.clone(); | |
| } else { | |
| return null; | |
| } | |
| } | |
| // Loads and parses the model with the given file name. Returns a cached copy if available. | |
| async loadModel(fileName) { | |
| const parseCache = this.parseCache; | |
| const key = fileName.toLowerCase(); | |
| if (this.hasCachedModel(fileName)) { | |
| return this.getCachedModel(fileName); | |
| } else { | |
| await parseCache.ensureDataLoaded(fileName); | |
| const info = parseCache.getData(fileName); | |
| const promise = this.processIntoMesh(info); | |
| if (this.hasCachedModel(fileName)) { | |
| return this.getCachedModel(fileName); | |
| } | |
| if (isPartType(info.type)) { | |
| this._cache[key] = promise; | |
| } | |
| const group = await promise; | |
| return group.clone(); | |
| } | |
| } | |
| // parses the given model text into a renderable object. Returns cached copy if available. | |
| async parseModel(text) { | |
| const parseCache = this.parseCache; | |
| const info = parseCache.parse(text); | |
| if (isPartType(info.type) && this.hasCachedModel(info.fileName)) { | |
| return this.getCachedModel(info.fileName); | |
| } | |
| return this.processIntoMesh(info); | |
| } | |
| } | |
| function sortByMaterial(a, b) { | |
| if (a.colorCode === b.colorCode) { | |
| return 0; | |
| } | |
| if (a.colorCode < b.colorCode) { | |
| return -1; | |
| } | |
| return 1; | |
| } | |
| function createObject(elements, elementSize, isConditionalSegments = false, totalElements = null) { | |
| elements.sort(sortByMaterial); | |
| if (totalElements === null) { | |
| totalElements = elements.length; | |
| } | |
| const positions = new Float32Array(elementSize * totalElements * 3); | |
| const normals = elementSize === 3 ? new Float32Array(elementSize * totalElements * 3) : null; | |
| const materials = []; | |
| const quadArray = new Array(6); | |
| const bufferGeometry = new THREE.BufferGeometry(); | |
| let prevMaterial = null; | |
| let index0 = 0; | |
| let numGroupVerts = 0; | |
| let offset = 0; | |
| for (let iElem = 0, nElem = elements.length; iElem < nElem; iElem++) { | |
| const elem = elements[iElem]; | |
| let vertices = elem.vertices; | |
| if (vertices.length === 4) { | |
| quadArray[0] = vertices[0]; | |
| quadArray[1] = vertices[1]; | |
| quadArray[2] = vertices[2]; | |
| quadArray[3] = vertices[0]; | |
| quadArray[4] = vertices[2]; | |
| quadArray[5] = vertices[3]; | |
| vertices = quadArray; | |
| } | |
| for (let j = 0, l = vertices.length; j < l; j++) { | |
| const v = vertices[j]; | |
| const index = offset + j * 3; | |
| positions[index + 0] = v.x; | |
| positions[index + 1] = v.y; | |
| positions[index + 2] = v.z; | |
| } | |
| if (elementSize === 3) { | |
| if (!elem.faceNormal) { | |
| const v0 = vertices[0]; | |
| const v1 = vertices[1]; | |
| const v2 = vertices[2]; | |
| _tempVec0.subVectors(v1, v0); | |
| _tempVec1.subVectors(v2, v1); | |
| elem.faceNormal = new THREE.Vector3().crossVectors(_tempVec0, _tempVec1).normalize(); | |
| } | |
| let elemNormals = elem.normals; | |
| if (elemNormals.length === 4) { | |
| quadArray[0] = elemNormals[0]; | |
| quadArray[1] = elemNormals[1]; | |
| quadArray[2] = elemNormals[2]; | |
| quadArray[3] = elemNormals[0]; | |
| quadArray[4] = elemNormals[2]; | |
| quadArray[5] = elemNormals[3]; | |
| elemNormals = quadArray; | |
| } | |
| for (let j = 0, l = elemNormals.length; j < l; j++) { | |
| let n = elem.faceNormal; | |
| if (elemNormals[j]) { | |
| n = elemNormals[j].norm; | |
| } | |
| const index = offset + j * 3; | |
| normals[index + 0] = n.x; | |
| normals[index + 1] = n.y; | |
| normals[index + 2] = n.z; | |
| } | |
| } | |
| if (prevMaterial !== elem.colorCode) { | |
| if (prevMaterial !== null) { | |
| bufferGeometry.addGroup(index0, numGroupVerts, materials.length - 1); | |
| } | |
| const material = elem.material; | |
| if (material !== null) { | |
| if (elementSize === 3) { | |
| materials.push(material); | |
| } else if (elementSize === 2) { | |
| if (material !== null) { | |
| if (isConditionalSegments) { | |
| materials.push(material.userData.edgeMaterial.userData.conditionalEdgeMaterial); | |
| } else { | |
| materials.push(material.userData.edgeMaterial); | |
| } | |
| } else { | |
| materials.push(null); | |
| } | |
| } | |
| } else { | |
| materials.push(elem.colorCode); | |
| } | |
| prevMaterial = elem.colorCode; | |
| index0 = offset / 3; | |
| numGroupVerts = vertices.length; | |
| } else { | |
| numGroupVerts += vertices.length; | |
| } | |
| offset += 3 * vertices.length; | |
| } | |
| if (numGroupVerts > 0) { | |
| bufferGeometry.addGroup(index0, Infinity, materials.length - 1); | |
| } | |
| bufferGeometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); | |
| if (normals !== null) { | |
| bufferGeometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3)); | |
| } | |
| let object3d = null; | |
| if (elementSize === 2) { | |
| if (isConditionalSegments) { | |
| object3d = new ConditionalLineSegments(bufferGeometry, materials.length === 1 ? materials[0] : materials); | |
| } else { | |
| object3d = new THREE.LineSegments(bufferGeometry, materials.length === 1 ? materials[0] : materials); | |
| } | |
| } else if (elementSize === 3) { | |
| object3d = new THREE.Mesh(bufferGeometry, materials.length === 1 ? materials[0] : materials); | |
| } | |
| if (isConditionalSegments) { | |
| object3d.isConditionalLine = true; | |
| const controlArray0 = new Float32Array(elements.length * 3 * 2); | |
| const controlArray1 = new Float32Array(elements.length * 3 * 2); | |
| const directionArray = new Float32Array(elements.length * 3 * 2); | |
| for (let i = 0, l = elements.length; i < l; i++) { | |
| const os = elements[i]; | |
| const vertices = os.vertices; | |
| const controlPoints = os.controlPoints; | |
| const c0 = controlPoints[0]; | |
| const c1 = controlPoints[1]; | |
| const v0 = vertices[0]; | |
| const v1 = vertices[1]; | |
| const 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; | |
| } | |
| class LDrawLoader extends THREE.Loader { | |
| constructor(manager) { | |
| super(manager); | |
| this.materials = []; | |
| this.materialLibrary = {}; | |
| this.partsCache = new LDrawPartsGeometryCache(this); | |
| this.fileMap = {}; | |
| this.setMaterials([]); | |
| this.smoothNormals = true; | |
| this.partsLibraryPath = ""; | |
| } | |
| setPartsLibraryPath(path) { | |
| this.partsLibraryPath = path; | |
| return this; | |
| } | |
| async preloadMaterials(url) { | |
| const fileLoader = new THREE.FileLoader(this.manager); | |
| fileLoader.setPath(this.path); | |
| fileLoader.setRequestHeader(this.requestHeader); | |
| fileLoader.setWithCredentials(this.withCredentials); | |
| const text = await fileLoader.loadAsync(url); | |
| const colorLineRegex = /^0 !COLOUR/; | |
| const lines = text.split(/[\n\r]/g); | |
| const materials = []; | |
| for (let i = 0, l = lines.length; i < l; i++) { | |
| const line = lines[i]; | |
| if (colorLineRegex.test(line)) { | |
| const directive = line.replace(colorLineRegex, ""); | |
| const material = this.parseColorMetaDirective(new LineParser(directive)); | |
| materials.push(material); | |
| } | |
| } | |
| this.setMaterials(materials); | |
| } | |
| load(url, onLoad, onProgress, onError) { | |
| const fileLoader = new THREE.FileLoader(this.manager); | |
| fileLoader.setPath(this.path); | |
| fileLoader.setRequestHeader(this.requestHeader); | |
| fileLoader.setWithCredentials(this.withCredentials); | |
| fileLoader.load( | |
| url, | |
| (text) => { | |
| this.partsCache.parseModel(text, this.materialLibrary).then((group) => { | |
| this.applyMaterialsToMesh(group, MAIN_COLOUR_CODE, this.materialLibrary, true); | |
| this.computeConstructionSteps(group); | |
| onLoad(group); | |
| }).catch(onError); | |
| }, | |
| onProgress, | |
| onError | |
| ); | |
| } | |
| parse(text, onLoad) { | |
| this.partsCache.parseModel(text, this.materialLibrary).then((group) => { | |
| this.computeConstructionSteps(group); | |
| onLoad(group); | |
| }); | |
| } | |
| setMaterials(materials) { | |
| this.materialLibrary = {}; | |
| this.materials = []; | |
| for (let i = 0, l = materials.length; i < l; i++) { | |
| this.addMaterial(materials[i]); | |
| } | |
| this.addMaterial(this.parseColorMetaDirective(new LineParser("Main_Colour CODE 16 VALUE #FF8080 EDGE #333333"))); | |
| this.addMaterial(this.parseColorMetaDirective(new LineParser("Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333"))); | |
| return this; | |
| } | |
| setFileMap(fileMap) { | |
| this.fileMap = fileMap; | |
| return this; | |
| } | |
| addMaterial(material) { | |
| const matLib = this.materialLibrary; | |
| if (!matLib[material.userData.code]) { | |
| this.materials.push(material); | |
| matLib[material.userData.code] = material; | |
| } | |
| return this; | |
| } | |
| getMaterial(colorCode) { | |
| if (colorCode.startsWith("0x2")) { | |
| const color = colorCode.substring(3); | |
| return this.parseColorMetaDirective( | |
| new LineParser("Direct_Color_" + color + " CODE -1 VALUE #" + color + " EDGE #" + color) | |
| ); | |
| } | |
| return this.materialLibrary[colorCode] || null; | |
| } | |
| // Applies the appropriate materials to a prebuilt hierarchy of geometry. Assumes that color codes are present | |
| // in the material array if they need to be filled in. | |
| applyMaterialsToMesh(group, parentColorCode, materialHierarchy, finalMaterialPass = false) { | |
| const loader = this; | |
| const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE; | |
| group.traverse((c) => { | |
| if (c.isMesh || c.isLineSegments) { | |
| if (Array.isArray(c.material)) { | |
| for (let i = 0, l = c.material.length; i < l; i++) { | |
| if (!c.material[i].isMaterial) { | |
| c.material[i] = getMaterial(c, c.material[i]); | |
| } | |
| } | |
| } else if (!c.material.isMaterial) { | |
| c.material = getMaterial(c, c.material); | |
| } | |
| } | |
| }); | |
| function getMaterial(c, colorCode) { | |
| if (parentIsPassthrough && !(colorCode in materialHierarchy) && !finalMaterialPass) { | |
| return colorCode; | |
| } | |
| const forEdge = c.isLineSegments || c.isConditionalLine; | |
| const isPassthrough = !forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; | |
| if (isPassthrough) { | |
| colorCode = parentColorCode; | |
| } | |
| let material = null; | |
| if (colorCode in materialHierarchy) { | |
| material = materialHierarchy[colorCode]; | |
| } else if (finalMaterialPass) { | |
| material = loader.getMaterial(colorCode); | |
| if (material === null) { | |
| throw new Error(`LDrawLoader: Material properties for code ${colorCode} not available.`); | |
| } | |
| } else { | |
| return colorCode; | |
| } | |
| if (c.isLineSegments) { | |
| material = material.userData.edgeMaterial; | |
| if (c.isConditionalLine) { | |
| material = material.userData.conditionalEdgeMaterial; | |
| } | |
| } | |
| return material; | |
| } | |
| } | |
| getMainMaterial() { | |
| return this.getMaterial(MAIN_COLOUR_CODE); | |
| } | |
| getMainEdgeMaterial() { | |
| return this.getMaterial(MAIN_EDGE_COLOUR_CODE); | |
| } | |
| parseColorMetaDirective(lineParser) { | |
| let code = null; | |
| let color = 16711935; | |
| let edgeColor = 16711935; | |
| let alpha = 1; | |
| let isTransparent = false; | |
| let luminance = 0; | |
| let finishType = FINISH_TYPE_DEFAULT; | |
| let edgeMaterial = null; | |
| const name = lineParser.getToken(); | |
| if (!name) { | |
| throw new Error( | |
| 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + "." | |
| ); | |
| } | |
| let token = null; | |
| while (true) { | |
| token = lineParser.getToken(); | |
| if (!token) { | |
| break; | |
| } | |
| switch (token.toUpperCase()) { | |
| case "CODE": | |
| code = lineParser.getToken(); | |
| break; | |
| case "VALUE": | |
| color = lineParser.getToken(); | |
| if (color.startsWith("0x")) { | |
| color = "#" + color.substring(2); | |
| } else if (!color.startsWith("#")) { | |
| throw new Error( | |
| "LDrawLoader: Invalid color while parsing material" + lineParser.getLineNumberString() + "." | |
| ); | |
| } | |
| break; | |
| case "EDGE": | |
| edgeColor = lineParser.getToken(); | |
| if (edgeColor.startsWith("0x")) { | |
| edgeColor = "#" + edgeColor.substring(2); | |
| } else if (!edgeColor.startsWith("#")) { | |
| edgeMaterial = this.getMaterial(edgeColor); | |
| if (!edgeMaterial) { | |
| throw new Error( | |
| "LDrawLoader: Invalid edge color while parsing material" + lineParser.getLineNumberString() + "." | |
| ); | |
| } | |
| edgeMaterial = edgeMaterial.userData.edgeMaterial; | |
| } | |
| break; | |
| case "ALPHA": | |
| alpha = parseInt(lineParser.getToken()); | |
| if (isNaN(alpha)) { | |
| throw new Error( | |
| "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 new Error( | |
| "LDrawLoader: Invalid luminance value in material definition" + LineParser.getLineNumberString() + "." | |
| ); | |
| } | |
| luminance = Math.max(0, Math.min(1, luminance / 255)); | |
| break; | |
| case "CHROME": | |
| finishType = FINISH_TYPE_CHROME; | |
| break; | |
| case "PEARLESCENT": | |
| finishType = FINISH_TYPE_PEARLESCENT; | |
| break; | |
| case "RUBBER": | |
| finishType = FINISH_TYPE_RUBBER; | |
| break; | |
| case "MATTE_METALLIC": | |
| finishType = FINISH_TYPE_MATTE_METALLIC; | |
| break; | |
| case "METAL": | |
| finishType = FINISH_TYPE_METAL; | |
| break; | |
| case "MATERIAL": | |
| lineParser.setToEnd(); | |
| break; | |
| default: | |
| throw new Error( | |
| 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + "." | |
| ); | |
| } | |
| } | |
| let material = null; | |
| switch (finishType) { | |
| case FINISH_TYPE_DEFAULT: | |
| material = new THREE.MeshStandardMaterial({ color, roughness: 0.3, metalness: 0 }); | |
| break; | |
| case FINISH_TYPE_PEARLESCENT: | |
| material = new THREE.MeshStandardMaterial({ color, roughness: 0.3, metalness: 0.25 }); | |
| break; | |
| case FINISH_TYPE_CHROME: | |
| material = new THREE.MeshStandardMaterial({ color, roughness: 0, metalness: 1 }); | |
| break; | |
| case FINISH_TYPE_RUBBER: | |
| material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0 }); | |
| break; | |
| case FINISH_TYPE_MATTE_METALLIC: | |
| material = new THREE.MeshStandardMaterial({ color, roughness: 0.8, metalness: 0.4 }); | |
| break; | |
| case FINISH_TYPE_METAL: | |
| material = new THREE.MeshStandardMaterial({ color, roughness: 0.2, metalness: 0.85 }); | |
| break; | |
| } | |
| material.transparent = isTransparent; | |
| material.premultipliedAlpha = true; | |
| material.opacity = alpha; | |
| material.depthWrite = !isTransparent; | |
| material.polygonOffset = true; | |
| material.polygonOffsetFactor = 1; | |
| if (luminance !== 0) { | |
| material.emissive.set(material.color).multiplyScalar(luminance); | |
| } | |
| if (!edgeMaterial) { | |
| edgeMaterial = new THREE.LineBasicMaterial({ | |
| color: edgeColor, | |
| transparent: isTransparent, | |
| opacity: alpha, | |
| depthWrite: !isTransparent | |
| }); | |
| edgeMaterial.userData.code = code; | |
| edgeMaterial.name = name + " - Edge"; | |
| edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial({ | |
| fog: true, | |
| transparent: isTransparent, | |
| depthWrite: !isTransparent, | |
| color: edgeColor, | |
| opacity: alpha | |
| }); | |
| } | |
| material.userData.code = code; | |
| material.name = name; | |
| material.userData.edgeMaterial = edgeMaterial; | |
| this.addMaterial(material); | |
| return material; | |
| } | |
| computeConstructionSteps(model) { | |
| let stepNumber = 0; | |
| model.traverse((c) => { | |
| if (c.isGroup) { | |
| if (c.userData.startingConstructionStep) { | |
| stepNumber++; | |
| } | |
| c.userData.constructionStep = stepNumber; | |
| } | |
| }); | |
| model.userData.numConstructionSteps = stepNumber + 1; | |
| } | |
| } | |
| exports.LDrawLoader = LDrawLoader; | |
| //# sourceMappingURL=LDrawLoader.cjs.map | |
Xet Storage Details
- Size:
- 49.3 kB
- Xet hash:
- d900df1588b67af7c2853d702d1b48d335c5da4e2f3241f59ce771b2d48ebf1b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.