diff --git a/package.json b/package.json index 1d7dcd12..fa5cd76d 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,7 @@ "packages/database", "packages/broker", "packages/backend", - "packages/engine", "packages/frontend", - "packages/worker", "packages/gateway" ], "scripts": { diff --git a/packages/engine/.eslintrc.json b/packages/engine/.eslintrc.json deleted file mode 100644 index 544b7b4d..00000000 --- a/packages/engine/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} \ No newline at end of file diff --git a/packages/engine/asconfig.json b/packages/engine/asconfig.json deleted file mode 100644 index 34a9b07a..00000000 --- a/packages/engine/asconfig.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "extends": "../../asconfig.json", - "entries": [ - "src/main.ts" - ], - "options": { - "importTable": true - }, - "targets": { - "release": { - "optimize": true, - "outFile": "bin/engine.wasm" , - "bindings": "esm" - }, - "debug": { - "debug": true, - "sourceMap": true, - "outFile": "bin/engine.wasm", - "bindings": "esm" - } - } -} \ No newline at end of file diff --git a/packages/engine/package.json b/packages/engine/package.json deleted file mode 100644 index 8a340044..00000000 --- a/packages/engine/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "productboard-engine", - "version": "0.0.1", - "description": "Worker thread for the collaborative design platform", - "license": "UNLICENSED", - "private": "true", - "type": "module", - "main": "bin/engine.js", - "types": "bin/engine.d.ts", - "author": { - "name": "Georg Hackenberg", - "email": "georg.hackenberg@fh-wels.at" - }, - "contributors": [ - { - "name": "Georg Hackenberg", - "email": "georg.hackenberg@fh-wels.at" - }, - { - "name": "Christian Zehetner", - "email": "christian.zehetner@fh-wels.at" - }, - { - "name": "Jürgen Humenberger", - "email": "juergen.humenberger@fh-wels.at" - }, - { - "name": "Dominik Frühwirth", - "email": "dominik.fruewirth@fh-wels.at" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/ghackenberg/caddrive.git", - "directory": "packages/engine" - }, - "scripts": { - "clean": "rm -rf bin", - "build": "asc --config asconfig.json --target release", - "lint": "eslint src", - "loc": "sloc src", - "dev": "onchange -v -i 'src/**/*.ts' -- asc --config asconfig.json --target debug" - }, - "dependencies": { - "mugl": "^0.3.0" - }, - "devDependencies": { - "@babel/preset-env": "^7.14.4", - "assemblyscript": "^0.27.22", - "onchange": "^7.1.0" - } -} diff --git a/packages/engine/src/glue.ts b/packages/engine/src/glue.ts deleted file mode 100644 index f8d1c1c3..00000000 --- a/packages/engine/src/glue.ts +++ /dev/null @@ -1,3 +0,0 @@ -//@ts-expect-error: host binding -@external("env", "console.log") -export declare function consoleLog(s: string): void \ No newline at end of file diff --git a/packages/engine/src/main.ts b/packages/engine/src/main.ts deleted file mode 100644 index c581da51..00000000 --- a/packages/engine/src/main.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { BindGroupLayout, BufferUsage, ColorTargetStates, DepthStencilState, MultisampleState, PrimitiveState, ShaderStage, VertexFormat, WebGL, WebGL2Device, getCanvasById, vertexBufferLayouts } from 'mugl/assembly' - -import { consoleLog } from './glue' -import { FRAGMENT_SHADER_CODE, FRAGMENT_SHADER_ENTRY_POINT, VERTEX_SHADER_CODE, VERTEX_SHADER_ENTRY_POINT } from './shader' -import { set2D } from './util' - -consoleLog('Getting canvas') -const canvas = getCanvasById(1, 'test') - -consoleLog('Requesting device') -const deviceOpt = WebGL.requestWebGL2Device(canvas) - -if (deviceOpt != null) { - const device = deviceOpt as WebGL2Device - - // Create pipeline - consoleLog('Creating pipeline') - const vertex = WebGL.createShader(device, { - code: VERTEX_SHADER_CODE, usage: ShaderStage.Vertex - }) - const vertexEntryPoint = VERTEX_SHADER_ENTRY_POINT - const fragment = WebGL.createShader(device, { - code: FRAGMENT_SHADER_CODE, usage: ShaderStage.Fragment - }) - const fragmentEntryPoint = FRAGMENT_SHADER_ENTRY_POINT - const buffers = vertexBufferLayouts([{ - attributes: [ - VertexFormat.F32x2 /* position */, - VertexFormat.F32x4 /* color */ - ], - instanced: false - }]) - const bindGroups: BindGroupLayout[] = [] - const targets: ColorTargetStates | null = null - const primitive: PrimitiveState = {} as PrimitiveState - const depthStencil: DepthStencilState | null = null - const multisample: MultisampleState = {} as MultisampleState - const pipeline = WebGL.createRenderPipeline(device, { - vertex, vertexEntryPoint, fragment, fragmentEntryPoint, buffers, bindGroups, targets, primitive, depthStencil, multisample - }) - - // Create triangle - consoleLog('Creating triangle') - const triangle = new Float32Array(6 * 3) - set2D(triangle, 0, +0.0, +0.5, 1.0, 0.0, 0.0, 1.0) - set2D(triangle, 1, +0.5, -0.5, 0.0, 1.0, 0.0, 1.0) - set2D(triangle, 2, -0.5, -0.5, 0.0, 0.0, 1.0, 1.0) - const buffer = WebGL.createBuffer(device, { - size: triangle.length, usage: BufferUsage.Vertex - }) - WebGL.writeBuffer(device, buffer, triangle) - - // Render scene - consoleLog('Rendering scene') - WebGL.beginRenderPass(device) - WebGL.setRenderPipeline(device, pipeline) - WebGL.setVertex(device, 0, buffer) - WebGL.draw(device, 3) - WebGL.submitRenderPass(device) - - // Destroy objects - consoleLog('Destroying objects') - vertex.destroy() - fragment.destroy() - pipeline.destroy() - buffer.destroy() - device.destroy() -} \ No newline at end of file diff --git a/packages/engine/src/shader.ts b/packages/engine/src/shader.ts deleted file mode 100644 index e77fbdfe..00000000 --- a/packages/engine/src/shader.ts +++ /dev/null @@ -1,24 +0,0 @@ -// VERTEX - -export const VERTEX_SHADER_CODE = `#version 300 es -layout (location=0) in vec2 position; -layout (location=1) in vec4 color; -out vec4 vColor; -void main () { - gl_Position = vec4(position, 0., 1.); - vColor = color; -}` - -export const VERTEX_SHADER_ENTRY_POINT = 'main' - -// FRAGMENT - -export const FRAGMENT_SHADER_CODE = `#version 300 es -precision mediump float; -in vec4 vColor; -out vec4 outColor; -void main () { - outColor = vColor; -}` - -export const FRAGMENT_SHADER_ENTRY_POINT = 'main' \ No newline at end of file diff --git a/packages/engine/src/util.ts b/packages/engine/src/util.ts deleted file mode 100644 index ece884cb..00000000 --- a/packages/engine/src/util.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function set2D(buffer: Float32Array, i: i32, x: f32, y: f32, r: f32, g: f32, b: f32, a: f32): void { - buffer[i * 6 + 0] = x - buffer[i * 6 + 1] = y - buffer[i * 6 + 2] = r - buffer[i * 6 + 3] = g - buffer[i * 6 + 4] = b - buffer[i * 6 + 5] = a -} \ No newline at end of file diff --git a/packages/engine/tsconfig.json b/packages/engine/tsconfig.json deleted file mode 100644 index 4bf21500..00000000 --- a/packages/engine/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "assemblyscript/std/assembly.json", - "compilerOptions": { - "rootDirs": ["src"], - "outDir": "bin" - } -} \ No newline at end of file diff --git a/packages/frontend/src/scripts/components/views/ProductVersionSetting.tsx b/packages/frontend/src/scripts/components/views/ProductVersionSetting.tsx index a235e385..a23d386f 100644 --- a/packages/frontend/src/scripts/components/views/ProductVersionSetting.tsx +++ b/packages/frontend/src/scripts/components/views/ProductVersionSetting.tsx @@ -16,7 +16,7 @@ import { useMembers, useVersions } from '../../hooks/list' import { render } from '../../functions/render' import { useAsyncHistory } from '../../hooks/history' import { parseGLTFModel } from '../../loaders/gltf' -import { parseLDrawModel } from '../../loaders/ldraw' +import { parseLDrawModel, pauseLoadLDrawPath } from '../../loaders/ldraw' import { ButtonInput } from '../inputs/ButtonInput' import { FileInput } from '../inputs/FileInput' import { GenericInput } from '../inputs/GenericInput' @@ -24,6 +24,7 @@ import { NumberInput } from '../inputs/NumberInput' import { TextareaInput } from '../inputs/TextareaInput' import { LegalFooter } from '../snippets/LegalFooter' import { ProductFooter, ProductFooterItem } from '../snippets/ProductFooter' +import { ModelGraph } from '../widgets/ModelGraph' import { clearModel } from '../widgets/FileView3D' import { ModelView3D } from '../widgets/ModelView3D' import { Column, Table } from '../widgets/Table' @@ -34,6 +35,7 @@ import EmptyIcon from '/src/images/empty.png' import LoadIcon from '/src/images/load.png' import LeftIcon from '/src/images/setting.png' import RightIcon from '/src/images/part.png' + const PREVIEW_WIDTH = 1000 const PREVIEW_HEIGHT = 1000 @@ -78,6 +80,9 @@ export const ProductVersionSettingView = () => { const [text, setText] = useState(null) const [model, setModel] = useState(null) const [group, setGroup] = useState(null) + const [update, setUpdate] = useState() + const [loaded, setLoaded] = useState() + const [total, setTotal] = useState() const [blob, setBlob] = useState(null) const [dataUrl, setDataUrl] = useState(null) const [active, setActive] = useState('left') @@ -116,22 +121,31 @@ export const ProductVersionSettingView = () => { useEffect(() => { let exec = true - text && parseLDrawModel(text).then(group => exec && setGroup(group)) - return () => { exec = false } + function update(_part: string, loaded: number, total: number) { + setUpdate(Date.now()) + setLoaded(loaded) + setTotal(total) + } + const path = `${Math.random()}` + text && parseLDrawModel(path, text, update).then(group => exec && setGroup(group)) + return () => { + exec = false + pauseLoadLDrawPath(path) + } }, [text]) useEffect(() => { model && setGroup(model.scene) }, [model]) useEffect(() => { let exec = true - group && render(group.clone(true), PREVIEW_WIDTH, PREVIEW_HEIGHT).then(result => { + group && loaded == total && render(group.clone(true), PREVIEW_WIDTH, PREVIEW_HEIGHT).then(result => { if (exec) { setBlob(result.blob) setDataUrl(result.dataUrl) } }) return () => { exec = false } - }, [group]) + }, [group, loaded, total]) // FUNCTIONS @@ -247,26 +261,64 @@ export const ProductVersionSettingView = () => { {version ? ( file ? (
- {!group ? ( - - ) : ( - - )} +
+ {!group ? ( + + ) : ( + <> + {loaded != total ? ( +
+ Loading ... +
+ ) : ( + + )} + + + + {loaded != total && ( +
+
+
{Math.floor(loaded / total * 100)}%
+
+ )} + + )} +
) : ( ) ) : (
- {!file ? ( - - ) : ( - !group ? ( - +
+ {!file ? ( + ) : ( - - ) - )} + !group ? ( + + ) : ( + <> + {loaded != total ? ( +
+ Loading ... +
+ ) : ( + + )} + + + + {loaded != total && ( +
+
+
{Math.floor(loaded / total * 100)}%
+
+ )} + + ) + )} +
)} diff --git a/packages/frontend/src/scripts/components/widgets/FileView3D.tsx b/packages/frontend/src/scripts/components/widgets/FileView3D.tsx index c1fbdb04..81a282d9 100644 --- a/packages/frontend/src/scripts/components/widgets/FileView3D.tsx +++ b/packages/frontend/src/scripts/components/widgets/FileView3D.tsx @@ -5,15 +5,21 @@ import { Group, Object3D } from 'three' import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader' import { loadGLTFModel } from '../../loaders/gltf' -import { loadLDrawModel } from '../../loaders/ldraw' +import { loadLDrawModel, pauseLoadLDrawPath, resumeLoadLDrawPath } from '../../loaders/ldraw' import { computePath } from '../../functions/path' import { ModelGraph } from './ModelGraph' import { ModelView3D } from './ModelView3D' import LoadIcon from '/src/images/load.png' +type Callback = (part: string, loaded: number, total: number) => void + const GLTF_MODEL_CACHE: {[path: string]: GLTF} = {} + const LDRAW_MODEL_CACHE: {[path: string]: Group} = {} +const LDRAW_LOADED_CACHE: {[path: string]: number} = {} +const LDRAW_TOTAL_CACHE: {[path: string]: number} = {} +const LDRAW_UPDATE_CACHE: {[path: string]: Callback[]} = {} async function getGLTFModel(path: string) { if (!(path in GLTF_MODEL_CACHE)) { @@ -23,9 +29,34 @@ async function getGLTFModel(path: string) { } +function trackLDrawModel(path: string, callback: Callback) { + // Subscribe + if (!(path in LDRAW_UPDATE_CACHE)) { + LDRAW_UPDATE_CACHE[path] = [] + } + LDRAW_UPDATE_CACHE[path].push(callback) + + // Unsubscribe + return () => { + LDRAW_UPDATE_CACHE[path].splice(LDRAW_UPDATE_CACHE[path].indexOf(callback), 1) + + if (LDRAW_UPDATE_CACHE[path].length == 0) { + pauseLoadLDrawPath(path) + } + } +} + async function getLDrawModel(path: string) { if (!(path in LDRAW_MODEL_CACHE)) { - LDRAW_MODEL_CACHE[path] = await loadLDrawModel(path) + LDRAW_MODEL_CACHE[path] = await loadLDrawModel(path, (part, loaded, total) => { + LDRAW_LOADED_CACHE[path] = loaded + LDRAW_TOTAL_CACHE[path] = total + for (const callback of LDRAW_UPDATE_CACHE[path] || []) { + callback(part, loaded, total) + } + }) + } else { + resumeLoadLDrawPath(path) } return LDRAW_MODEL_CACHE[path] } @@ -59,16 +90,38 @@ export const FileView3D = (props: { path: string, mouse: boolean, highlighted?: initialGroup = LDRAW_MODEL_CACHE[props.path] } + const initialLoaded = (props.path.endsWith('.ldr') || props.path.endsWith('.mpd')) && LDRAW_LOADED_CACHE[props.path] + const initialTotal = (props.path.endsWith('.ldr') || props.path.endsWith('.mpd')) && LDRAW_TOTAL_CACHE[props.path] + const initialUpdate = Date.now() + // STATES // Entities const [group, setGroup] = useState(initialGroup) + const [loaded, setLoaded] = useState(initialLoaded) + const [total, setTotal] = useState(initialTotal) + const [update, setUpdate] = useState(initialUpdate) // Interactions const [toggle, setToggle] = useState(false) const [selected, setSelected] = useState(props.selected) // EFFECTS + + useEffect(() => { + if (props.path) { + setLoaded(LDRAW_LOADED_CACHE[props.path]) + setTotal(LDRAW_TOTAL_CACHE[props.path]) + setUpdate(Date.now()) + return trackLDrawModel(props.path, (_part, loaded, total) => { + setLoaded(loaded) + setTotal(total) + setUpdate(Date.now()) + }) + } else { + return () => {/**/} + } + }, [props.path]) useEffect(() => { let exec = true @@ -93,8 +146,27 @@ export const FileView3D = (props: { path: string, mouse: boolean, highlighted?:
{group ? ( <> - - + {loaded != total ? ( +
+ Loading ... +
+ ) : ( + + )} + + {loaded != total ? ( + + ) : ( + + )} + + {loaded != total && ( +
+
+
{Math.floor(loaded / total * 100)}%
+
+ )} + setToggle(!toggle)} className='button fill lightgray'> diff --git a/packages/frontend/src/scripts/components/widgets/ModelView3D.tsx b/packages/frontend/src/scripts/components/widgets/ModelView3D.tsx index f3b90539..ec6f9fd1 100644 --- a/packages/frontend/src/scripts/components/widgets/ModelView3D.tsx +++ b/packages/frontend/src/scripts/components/widgets/ModelView3D.tsx @@ -8,6 +8,7 @@ import { initializeCamera, initializeOrbit, initializeRenderer, initializeScene, interface Props { model: Group + update?: number highlighted?: string[] marked?: string[] selected?: string[] @@ -20,6 +21,7 @@ export class ModelView3D extends React.Component { private div: React.RefObject + private update: number private scene: Scene private camera: PerspectiveCamera private renderer: WebGLRenderer @@ -185,10 +187,12 @@ export class ModelView3D extends React.Component { reload() { // Update - if (this.scene.children.length == 0 || this.scene.children[this.scene.children.length - 1] != this.props.model) { + if (this.scene.children.length == 0 || this.scene.children[this.scene.children.length - 1] != this.props.model || this.update != this.props.update) { // Cache this.highlight_cache = undefined this.select_cache = undefined + // Update + this.update = this.props.update // Scene this.scene.remove(this.scene.children[this.scene.children.length - 1]) this.scene.add(this.props.model) diff --git a/packages/frontend/src/scripts/loaders/LDrawLoader.ts b/packages/frontend/src/scripts/loaders/LDrawLoader.ts deleted file mode 100644 index dd7ede45..00000000 --- a/packages/frontend/src/scripts/loaders/LDrawLoader.ts +++ /dev/null @@ -1,2539 +0,0 @@ -/* eslint-disable no-case-declarations */ -import { - BufferAttribute, - BufferGeometry, - Color, - FileLoader, - Group, - LineBasicMaterial, - LineSegments, - Loader, - Matrix4, - Mesh, - MeshStandardMaterial, - ShaderMaterial, - UniformsLib, - UniformsUtils, - Vector3, - Ray, - ShaderMaterialParameters, - Material, - LoadingManager, - Object3D -} from 'three'; - -// Special surface finish tag types. -// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented -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; - -// State machine to search a subobject path. -// The LDraw standard establishes these various possible subfolders. -const FILE_LOCATION_TRY_PARTS = 0; -const FILE_LOCATION_TRY_P = 1; -const FILE_LOCATION_TRY_MODELS = 2; -const FILE_LOCATION_AS_IS = 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 = new Vector3(); -const _tempVec1 = new Vector3(); - -class LDrawConditionalLineMaterial extends ShaderMaterial { - - public isLDrawConditionalLineMaterial: boolean - - constructor( parameters: ShaderMaterialParameters & { color: string } ) { - - super( { - - uniforms: UniformsUtils.merge( [ - UniformsLib.fog, - { - diffuse: { - value: new Color() - }, - opacity: { - value: 1.0 - } - } - ] ), - - vertexShader: /* glsl */` - attribute vec3 control0; - attribute vec3 control1; - attribute vec3 direction; - varying float discardFlag; - - #include - #include - #include - #include - #include - void main() { - #include - - 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 - #include - #include - } - `, - - fragmentShader: /* glsl */` - uniform vec3 diffuse; - uniform float opacity; - varying float discardFlag; - - #include - #include - #include - #include - #include - void main() { - - if ( discardFlag > 0.5 ) discard; - - #include - vec3 outgoingLight = vec3( 0.0 ); - vec4 diffuseColor = vec4( diffuse, opacity ); - #include - #include - outgoingLight = diffuseColor.rgb; // simple shader - gl_FragColor = vec4( outgoingLight, diffuseColor.a ); - #include - #include - #include - #include - } - `, - - } ); - - 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 LineSegments { - - public isConditionalLine: boolean - - constructor( geometry: BufferGeometry, material: Material | Material[] ) { - - super( geometry, material ); - this.isConditionalLine = true; - - } - -} - -type Element = { colorCode: string, material: Material, vertices: Vector3[], controlPoints?: Vector3[], normals?: { norm: Vector3 }[], faceNormal?: Vector3 } -type Info1 = { ray: Ray, distances: number[] } -type Info2 = { index: number, tri: Element } - -function generateFaceNormals( faces: Element[] ) { - - 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 Vector3() - .crossVectors( _tempVec0, _tempVec1 ) - .normalize(); - - } - -} - -const _ray = new Ray(); -function smoothNormals( faces: Element[], lineSegments: Element[], checkSubSegments = false ) { - - // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because - // it allows edges to be smoothed as expected (see minifig arms). - // -- - // And the vector values are initialize multiplied by 1 + 1e-10 to account for floating - // point errors on vertices along quantization boundaries. Ie after matrix multiplication - // vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't - // get merged. This added epsilon attempts to push these error values to the same quantized - // value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169. - - const hashMultiplier = ( 1 + 1e-10 ) * 1e2; - function hashVertex( v: Vector3 ) { - - const x = ~ ~ ( v.x * hashMultiplier ); - const y = ~ ~ ( v.y * hashMultiplier ); - const z = ~ ~ ( v.z * hashMultiplier ); - - return `${ x },${ y },${ z }`; - - } - - function hashEdge( v0: Vector3, v1: Vector3 ) { - - return `${ hashVertex( v0 ) }_${ hashVertex( v1 ) }`; - - } - - // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected - // onto the original line. - function toNormalizedRay( v0: Vector3, v1: Vector3, targetRay: Ray ) { - - 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: Ray ) { - - return hashEdge( ray.origin, ray.direction ); - - } - - const hardEdges = new Set(); - const hardEdgeRays = new Map(); - const halfEdgeList: {[hash: string]: Info2} = {}; - const normals: Vector3[] = []; - - // Save the list of hard edges by hash - 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 ) ); - - // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check - // and requires more memory. - if ( checkSubSegments ) { - - // add both ray directions to the map - const ray = toNormalizedRay( v0, v1, new Ray() ); - const rh1 = hashRay( ray ); - if ( ! hardEdgeRays.has( rh1 ) ) { - - toNormalizedRay( v1, v0, ray ); - const rh2 = hashRay( ray ); - - const info: Info1 = { - ray, - distances: [] - }; - - hardEdgeRays.set( rh1, info ); - hardEdgeRays.set( rh2, info ); - - } - - // store both segments ends in min, max order in the distances array to check if a face edge is a - // subsegment later. - 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 ); - - } - - } - - // track the half edges associated with each triangle - 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 ); - - // don't add the triangle if the edge is supposed to be hard - if ( hardEdges.has( hash ) ) { - - continue; - - } - - // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds - if ( checkSubSegments ) { - - toNormalizedRay( v0, v1, _ray ); - - const rayHash = hashRay( _ray ); - if ( hardEdgeRays.has( rayHash ) ) { - - const info = hardEdgeRays.get( rayHash ); - const { ray, distances } = info; - let d0 = ray.direction.dot( v0 ); - let d1 = ray.direction.dot( v1 ); - - if ( d0 > d1 ) { - - [ d0, d1 ] = [ d1, d0 ]; - - } - - // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals - let found = false; - for ( let i = 0, l = distances.length; i < l; i += 2 ) { - - if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) { - - found = true; - break; - - } - - } - - if ( found ) { - - continue; - - } - - } - - } - - const info = { - index: index, - tri: tri - }; - halfEdgeList[ hash ] = info; - - } - - } - - // Iterate until we've tried to connect all faces to share normals - // eslint-disable-next-line no-constant-condition - while ( true ) { - - // Stop if there are no more faces left - let halfEdge = null; - for ( const key in halfEdgeList ) { - - halfEdge = halfEdgeList[ key ]; - break; - - } - - if ( halfEdge === null ) { - - break; - - } - - // Exhaustively find all connected faces - const queue = [ halfEdge ]; - while ( queue.length > 0 ) { - - // initialize all vertex normals in this triangle - const tri = queue.pop().tri; - const vertices = tri.vertices; - const vertNormals = tri.normals; - const faceNormal = tri.faceNormal; - - // Check if any edge is connected to another triangle edge - 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 ]; - - // delete this triangle from the list so it won't be found again - 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; - - // NOTE: If the angle between faces 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( otherInfo ); - delete halfEdgeList[ reverseHash ]; - - } - - // share the first normal - 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 ) { - - // it's possible to encounter an edge of a triangle that has already been traversed meaning - // both edges already have different normals defined and shared. To work around this we create - // a wrapper object so when those edges are merged the normals can be updated everywhere. - sharedNormal1 = { norm: new 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 ); - - } - - // share the second normal - 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 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 ); - - } - - } - - } - - } - - } - - // The normals of each face have been added up so now we average them by normalizing the vector. - for ( let i = 0, l = normals.length; i < l; i ++ ) { - - normals[ i ].normalize(); - - } - -} - -function isPartType( type: string ) { - - return type === 'Part' || type === 'Unofficial_Part'; - -} - -function isPrimitiveType( type: string ) { - - return /primitive/i.test( type ) || type === 'Subpart'; - -} - -class LineParser { - - public line: string - public lineLength: number - public currentCharIndex: number - public currentChar: string - public lineNumber: number - - constructor( line: string, lineNumber = 0 ) { - - 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 !== '\t' ) { - - return; - - } - - this.currentCharIndex ++; - - } - - } - - getToken() { - - const 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 ++; - - } - - const pos1 = this.currentCharIndex; - - this.seekNonSpace(); - - return this.line.substring( pos0, pos1 ); - - } - - getVector() { - - return new 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 : ''; - - } - -} - -type Subobject = { - material: Material - colorCode: string - matrix: Matrix4 - fileName: string - inverted: boolean - startingBuildingStep: boolean -} -type Result = { - type?: string - category?: string - keywords?: string[] - author?: string - fileName?: string - totalFaces?: number - startingBuildingStep?: boolean - group?: Group - materials?: {[colorCode: string]: Material} - subobjects?: Subobject[] - faces?: Element[] - lineSegments?: Element[] - conditionalSegments?: Element[] -} - -// Fetches and parses an intermediate representation of LDraw parts files. -class LDrawParsedCache { - - public loader: LDrawLoader - - private _cache: {[fileName: string]: Promise | Result} - - constructor( loader: LDrawLoader ) { - - this.loader = loader; - this._cache = {}; - - } - - cloneResult( original: Result ) { - - const result: Result = {}; - - // vertices are transformed and normals computed before being converted to geometry - // so these pieces must be cloned. - 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() ) - }; - - } ); - - // none if this is subsequently modified - result.type = original.type; - result.category = original.category; - result.keywords = original.keywords; - result.author = original.author; - result.subobjects = original.subobjects; - result.fileName = original.fileName; - result.totalFaces = original.totalFaces; - result.startingBuildingStep = original.startingBuildingStep; - result.materials = original.materials; - result.group = null; - return result; - - } - - async fetchData( fileName: string ) { - - let triedLowerCase = false; - let locationState = FILE_LOCATION_TRY_PARTS; - 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 ) { - - // Try absolute path - locationState = FILE_LOCATION_NOT_FOUND; - - } else { - - // Next attempt is lower case - fileName = fileName.toLowerCase(); - subobjectURL = fileName; - triedLowerCase = true; - locationState = FILE_LOCATION_TRY_PARTS; - - } - - break; - - } - - const loader = this.loader; - const fileLoader = new 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 { - - continue; - - } - - } - - throw new Error( 'LDrawLoader: Subobject "' + fileName + '" could not be loaded.' ); - - } - - parse( text: string, fileName: string = null ): Result { - - const loader = this.loader; - - // final results - const faces: Element[] = []; - const lineSegments: Element[] = []; - const conditionalSegments: Element[] = []; - const subobjects = []; - const materials: {[colorCode: string]: Material} = {}; - - const getLocalMaterial = (colorCode: string) => { - - return materials[ colorCode ] || null; - - }; - - let type = 'Model'; - let category: string = null; - let keywords: string[] = null; - let author: string = null; - let totalFaces = 0; - - // split into lines - 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' ); - - } - - const lines = text.split( '\n' ); - const numLines = lines.length; - - let parsingEmbeddedFiles = false; - let currentEmbeddedFileName: string = null; - let currentEmbeddedText: string = null; - - let bfcCertified = false; - let bfcCCW = true; - let bfcInverted = false; - let bfcCull = true; - - let startingBuildingStep = false; - - // Parse all line commands - for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) { - - const line = lines[ lineIndex ]; - - if ( line.length === 0 ) continue; - - if ( parsingEmbeddedFiles ) { - - if ( line.startsWith( '0 FILE ' ) ) { - - // Save previous embedded file in the cache - this.setData( currentEmbeddedFileName, currentEmbeddedText ); - - // New embedded text file - currentEmbeddedFileName = line.substring( 7 ); - currentEmbeddedText = ''; - - } else { - - currentEmbeddedText += line + '\n'; - - } - - continue; - - } - - const lp = new LineParser( line, lineIndex + 1 ); - lp.seekNonSpace(); - - if ( lp.isAtTheEnd() ) { - - // Empty line - continue; - - } - - // Parse the line type - const lineType = lp.getToken(); - - let material: Material; - let colorCode: string; - let segment: Element; - let ccw: boolean; - let doubleSided: boolean; - let v0: Vector3, v1: Vector3, v2: Vector3, v3: Vector3, c0: Vector3, c1: Vector3; - - switch ( lineType ) { - - // Line type 0: Comment or META - case '0': - - // Parse meta directive - 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 ) { - - // 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() ) { - - 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': - - startingBuildingStep = true; - - break; - - case 'Author:': - - author = lp.getToken(); - - break; - - default: - // Other meta directives are not implemented - break; - - } - - } - - break; - - // Line type 1: Sub-object file - 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 Matrix4().set( - m0, m1, m2, posX, - m3, m4, m5, posY, - m6, m7, m8, posZ, - 0, 0, 0, 1 - ); - - let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' ); - - if ( loader.fileMap[ fileName ] ) { - - // Found the subobject path in the preloaded file path map - fileName = loader.fileMap[ fileName ]; - - } else { - - // Standardized subfolders - if ( fileName.startsWith( 's/' ) ) { - - fileName = 'parts/' + fileName; - - } else if ( fileName.startsWith( '48/' ) ) { - - fileName = 'p/' + fileName; - - } - - } - - subobjects.push( { - material: material, - colorCode: colorCode, - matrix: matrix, - fileName: fileName, - inverted: bfcInverted, - startingBuildingStep: startingBuildingStep - } ); - - startingBuildingStep = false; - bfcInverted = false; - - break; - - // Line type 2: Line segment - case '2': - - colorCode = lp.getToken(); - material = getLocalMaterial( colorCode ); - v0 = lp.getVector(); - v1 = lp.getVector(); - - segment = { - material: material, - colorCode: colorCode, - vertices: [ v0, v1 ], - }; - - lineSegments.push( segment ); - - break; - - // Line type 5: Conditional Line segment - case '5': - - colorCode = lp.getToken(); - material = getLocalMaterial( colorCode ); - v0 = lp.getVector(); - v1 = lp.getVector(); - c0 = lp.getVector(); - c1 = lp.getVector(); - - segment = { - material: material, - colorCode: colorCode, - vertices: [ v0, v1 ], - controlPoints: [ c0, c1 ], - }; - - conditionalSegments.push( segment ); - - break; - - // Line type 3: Triangle - 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: material, - colorCode: colorCode, - faceNormal: null, - vertices: [ v0, v1, v2 ], - normals: [ null, null, null ], - } ); - totalFaces ++; - - if ( doubleSided === true ) { - - faces.push( { - material: material, - colorCode: colorCode, - faceNormal: null, - vertices: [ v2, v1, v0 ], - normals: [ null, null, null ], - } ); - totalFaces ++; - - } - - break; - - // Line type 4: Quadrilateral - 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(); - - } - - // specifically place the triangle diagonal in the v0 and v1 slots so we can - // account for the doubling of vertices later when smoothing normals. - faces.push( { - material: material, - colorCode: colorCode, - faceNormal: null, - vertices: [ v0, v1, v2, v3 ], - normals: [ null, null, null, null ], - } ); - totalFaces += 2; - - if ( doubleSided === true ) { - - faces.push( { - material: material, - colorCode: 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 ); - - } - - const group: Group = null - - return { - faces, - conditionalSegments, - lineSegments, - type, - category, - keywords, - author, - subobjects, - totalFaces, - startingBuildingStep, - materials, - fileName, - group - }; - - } - - // returns an (optionally cloned) instance of the data - getData( fileName: string, 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: string ) { - - const key = fileName.toLowerCase(); - if ( ! ( key in this._cache ) ) { - - // replace the promise with a copy of the parsed data for immediate processing - this._cache[ key ] = this.fetchData( fileName ).then( text => { - - const info = this.parse( text.toString(), fileName ); - this._cache[ key ] = info; - return info; - - } ); - - } - - await this._cache[ key ]; - - } - - // sets the data in the cache from parsed data - setData( fileName: string, text: string ) { - - const key = fileName.toLowerCase(); - this._cache[ key ] = this.parse( text, fileName ); - - } - -} - -// returns the material for an associated color code. If the color code is 16 for a face or 24 for -// an edge then the passthroughColorCode is used. -function getMaterialFromCode( colorCode: string, parentColorCode: string, materialHierarchy: {[colorCode: string]: Material}, forEdge: boolean ) { - - const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; - if ( isPassthrough ) { - - colorCode = parentColorCode; - - } - - return materialHierarchy[ colorCode ] || null; - -} - -// Class used to parse and build LDraw parts as three.js objects and cache them if they're a "Part" type. -class LDrawPartsGeometryCache { - - public loader: LDrawLoader - public parseCache: LDrawParsedCache - - private _cache: {[fileName: string]: Promise | Group} - - constructor( loader: LDrawLoader ) { - - this.loader = loader; - this.parseCache = new LDrawParsedCache( loader ); - this._cache = {}; - - } - - // Convert the given file information into a mesh by processing subobjects. - async processIntoMesh( info: Result ) { - - const loader = this.loader; - const parseCache = this.parseCache; - const faceMaterials = new Set(); - - // Processes the part subobject information to load child parts and merge geometry onto part - // piece object. - const processInfoSubobjects = async ( info: Result, subobject: Subobject = null ) => { - - const subobjects = info.subobjects; - const promises = []; - - // Trigger load of all subobjects. If a subobject isn't a primitive then load it as a separate - // group which lets instruction steps apply correctly. - for ( let i = 0, l = subobjects.length; i < l; i ++ ) { - - const subobject = subobjects[ i ]; - const promise = parseCache.ensureDataLoaded( subobject.fileName ).then( () => { - - const subobjectInfo = parseCache.getData( subobject.fileName, false ); - if ( ! isPrimitiveType( subobjectInfo.type ) ) { - - return this.loadModel( subobject.fileName ).catch( error => { - - console.warn( error ); - return null; - - } ); - - } - - return processInfoSubobjects( parseCache.getData( subobject.fileName ), subobject ); - - } ); - - promises.push( promise ); - - } - - const group = new Group(); - group.userData["category"] = info.category; - group.userData["keywords"] = info.keywords; - group.userData["author"] = info.author; - group.userData["type"] = info.type; - group.userData["fileName"] = info.fileName; - info.group = group; - - const subobjectInfos = await Promise.all( promises ); - for ( let i = 0, l = subobjectInfos.length; i < l; i ++ ) { - - const subobject = info.subobjects[ i ]; - const subobjectInfo = subobjectInfos[ i ]; - - if ( subobjectInfo === null ) { - - // the subobject failed to load - continue; - - } - - // if the subobject was loaded as a separate group then apply the parent scopes materials - if ( subobjectInfo.isGroup ) { - - const subobjectGroup = subobjectInfo; - subobject.matrix.decompose( subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale ); - subobjectGroup.userData.startingBuildingStep = subobject.startingBuildingStep; - subobjectGroup.name = subobject.fileName; - - loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials ); - subobjectGroup.userData.colorCode = subobject.colorCode; - - group.add( subobjectGroup ); - continue; - - } - - // add the subobject group if it has children in case it has both children and primitives - if ( subobjectInfo.group.children.length ) { - - group.add( subobjectInfo.group ); - - } - - // transform the primitives into the local space of the parent piece and append them to - // to the parent primitives list. - const parentLineSegments = info.lineSegments; - const parentConditionalSegments = info.conditionalSegments; - const parentFaces = info.faces; - - const lineSegments = subobjectInfo.lineSegments; - const conditionalSegments = subobjectInfo.conditionalSegments; - - const faces = subobjectInfo.faces; - const matrix = subobject.matrix; - const inverted = subobject.inverted; - const matrixScaleInverted = matrix.determinant() < 0; - const colorCode = subobject.colorCode; - - const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode; - for ( let i = 0, l = lineSegments.length; i < l; i ++ ) { - - const ls = lineSegments[ i ]; - 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, info.materials, true ); - - parentLineSegments.push( ls ); - - } - - for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) { - - const os = conditionalSegments[ i ]; - 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, info.materials, true ); - - parentConditionalSegments.push( os ); - - } - - for ( let i = 0, l = faces.length; i < l; i ++ ) { - - const tri = faces[ i ]; - const vertices = tri.vertices; - for ( let i = 0, l = vertices.length; i < l; i ++ ) { - - vertices[ i ].applyMatrix4( matrix ); - - } - - tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode; - tri.material = tri.material || getMaterialFromCode( tri.colorCode, colorCode, info.materials, false ); - faceMaterials.add( tri.colorCode ); - - // If the scale of the object is negated then the triangle winding order - // needs to be flipped. - if ( matrixScaleInverted !== inverted ) { - - vertices.reverse(); - - } - - parentFaces.push( tri ); - - } - - info.totalFaces += subobjectInfo.totalFaces; - - } - - // Apply the parent subobjects pass through material code to this object. This is done several times due - // to material scoping. - if ( subobject ) { - - loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials ); - group.userData["colorCode"] = subobject.colorCode; - - } - - return info; - - }; - - // Track material use to see if we need to use the normal smooth slow path for hard edges. - for ( let i = 0, l = info.faces.length; 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 ); - - } - - // Add the primitive objects and metadata. - 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: string ) { - - return fileName !== null && fileName.toLowerCase() in this._cache; - - } - - async getCachedModel( fileName: string ) { - - 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: string ) { - - const parseCache = this.parseCache; - const key = fileName.toLowerCase(); - if ( this.hasCachedModel( fileName ) ) { - - // Return cached model if available. - return this.getCachedModel( fileName ); - - } else { - - // Otherwise parse a new model. - // Ensure the file data is loaded and pre parsed. - await parseCache.ensureDataLoaded( fileName ); - - const info = parseCache.getData( fileName ); - const promise = this.processIntoMesh( info ); - - // Now that the file has loaded it's possible that another part parse has been waiting in parallel - // so check the cache again to see if it's been added since the last async operation so we don't - // do unnecessary work. - if ( this.hasCachedModel( fileName ) ) { - - return this.getCachedModel( fileName ); - - } - - // Cache object if it's a part so it can be reused later. - if ( isPartType( info.type ) ) { - - this._cache[ key ] = promise; - - } - - // return a copy - const group = await promise; - return group.clone(); - - } - - } - - // parses the given model text into a renderable object. Returns cached copy if available. - async parseModel( text: string ) { - - 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: Element, b: Element ) { - - if ( a.colorCode === b.colorCode ) { - - return 0; - - } - - if ( a.colorCode < b.colorCode ) { - - return - 1; - - } - - return 1; - -} - -function createObject( elements: Element[], elementSize: number, isConditionalSegments = false, totalElements: number = null ) { - - // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 ) - // With per face / segment material, implemented with mesh groups and materials array - - // Sort the faces or line segments by color code to make later the mesh groups - 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: Material[] = []; - - const quadArray = new Array( 6 ); - const bufferGeometry = new BufferGeometry(); - let prevMaterial: string = 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; - - } - - // create the normals array if this is a set of faces - 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 Vector3() - .crossVectors( _tempVec0, _tempVec1 ) - .normalize(); - - } - - const elemNormals = elem.normals; - if ( elemNormals.length === 4 ) { - - quadArray[ 0 ] = elemNormals[ 0 ] && elemNormals[ 0 ].norm; - quadArray[ 1 ] = elemNormals[ 1 ] && elemNormals[ 1 ].norm; - quadArray[ 2 ] = elemNormals[ 2 ] && elemNormals[ 2 ].norm; - quadArray[ 3 ] = elemNormals[ 0 ] && elemNormals[ 0 ].norm; - quadArray[ 4 ] = elemNormals[ 2 ] && elemNormals[ 2 ].norm; - quadArray[ 5 ] = elemNormals[ 3 ] && elemNormals[ 3 ].norm; - - elemNormals[ 0 ] = { norm: quadArray[ 0 ] } - elemNormals[ 1 ] = { norm: quadArray[ 1 ] } - elemNormals[ 2 ] = { norm: quadArray[ 2 ] } - elemNormals[ 3 ] = { norm: quadArray[ 3 ] } - elemNormals[ 4 ] = { norm: quadArray[ 4 ] } - elemNormals[ 5 ] = { norm: quadArray[ 5 ] } - - } - - for ( let j = 0, l = elemNormals.length; j < l; j ++ ) { - - // use face normal if a vertex normal is not provided - let n = elem.faceNormal; - if ( elemNormals[ j ] && elemNormals[ j ].norm ) { - - 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 ( isConditionalSegments ) { - - materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial ); - - } else { - - materials.push( material.userData.edgeMaterial ); - - } - - } - - } else { - - // If a material has not been made available yet then keep the color code string in the material array - // to save the spot for the material once a parent scopes materials are being applied to the object. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - materials.push( elem.colorCode as any ); - - } - - 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 BufferAttribute( positions, 3 ) ); - - if ( normals !== null ) { - - bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); - - } - - let object3d = null; - - if ( elementSize === 2 ) { - - if ( isConditionalSegments ) { - - object3d = new ConditionalLineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); - - } else { - - object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); - - } - - } else if ( elementSize === 3 ) { - - object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); - - } - - if ( isConditionalSegments ) { - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (object3d as any).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 BufferAttribute( controlArray0, 3, false ) ); - bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) ); - bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) ); - - } - - return object3d; - -} - -// - -export class LDrawLoader extends Loader { - - public materials: Material[] - public materialLibrary: {[colorCode: string]: Material} - - public partsCache: LDrawPartsGeometryCache - public fileMap: {[fileName: string]: string} - - public smoothNormals: boolean - public partsLibraryPath: string - - public missingColorMaterial: Material - public missingEdgeColorMaterial: Material - public missingConditionalEdgeColorMaterial: Material - - constructor( manager: LoadingManager ) { - - super( manager ); - - // Array of THREE.Material - this.materials = []; - this.materialLibrary = {}; - - // This also allows to handle the embedded text files ("0 FILE" lines) - this.partsCache = new LDrawPartsGeometryCache( this ); - - // 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 = {}; - - // Initializes the materials library with default materials - this.setMaterials( [] ); - - // If this flag is set to true the vertex normals will be smoothed. - this.smoothNormals = true; - - // The path to load parts from the LDraw parts library from. - this.partsLibraryPath = ''; - - // Material assigned to not available colors for meshes and edges - this.missingColorMaterial = new MeshStandardMaterial( { color: 0xFF00FF, roughness: 0.3, metalness: 0 } ); - this.missingColorMaterial.name = 'Missing material'; - this.missingEdgeColorMaterial = new LineBasicMaterial( { color: 0xFF00FF } ); - this.missingEdgeColorMaterial.name = 'Missing material - Edge'; - this.missingConditionalEdgeColorMaterial = new LDrawConditionalLineMaterial( { fog: true, color: '#FF00FF' } ); - this.missingConditionalEdgeColorMaterial.name = 'Missing material - Conditional Edge'; - this.missingColorMaterial.userData.edgeMaterial = this.missingEdgeColorMaterial; - this.missingEdgeColorMaterial.userData.conditionalEdgeMaterial = this.missingConditionalEdgeColorMaterial; - - } - - setPartsLibraryPath( path: string ) { - - this.partsLibraryPath = path; - return this; - - } - - async preloadMaterials( url: string ) { - - const fileLoader = new 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.toString().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: string, onLoad: (response: Group) => void, onProgress: (event: ProgressEvent) => void, onError: (event: ErrorEvent) => void ) { - - const fileLoader = new FileLoader( this.manager ); - fileLoader.setPath( this.path ); - fileLoader.setRequestHeader( this.requestHeader ); - fileLoader.setWithCredentials( this.withCredentials ); - fileLoader.load( url, text => { - - this.partsCache - .parseModel( text.toString() ) - .then( group => { - - this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true ); - this.computeBuildingSteps( group ); - group.userData["fileName"] = url; - onLoad( group ); - - } ) - .catch( onError ); - - }, onProgress, onError ); - - } - - parse( text: string, onLoad: (group: Group) => void ) { - - this.partsCache - .parseModel( text ) - .then( group => { - - this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true ); - this.computeBuildingSteps( group ); - group.userData["fileName"] = ''; - onLoad( group ); - - } ); - - } - - setMaterials( materials: Material[] ) { - - this.materialLibrary = {}; - this.materials = []; - for ( let i = 0, l = materials.length; i < l; i ++ ) { - - this.addMaterial( materials[ i ] ); - - } - - // Add default main triangle and line edge materials (used in pieces that can be colored with a main color) - 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: {[fileName: string]: string} ) { - - this.fileMap = fileMap; - - return this; - - } - - addMaterial( material: Material ) { - - // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array - - const matLib = this.materialLibrary; - if ( ! matLib[ material.userData.code ] ) { - - this.materials.push( material ); - matLib[ material.userData.code ] = material; - - } - - return this; - - } - - getMaterial( colorCode: string ) { - - if ( colorCode.startsWith( '0x2' ) ) { - - // Special 'direct' material value (RGB color) - 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: Group, parentColorCode: string, materialHierarchy: {[colorCode: string]: Material}, finalMaterialPass = false ) { - - // find any missing materials as indicated by a color code string and replace it with a material from the current material lib - // eslint-disable-next-line @typescript-eslint/no-this-alias - const loader = this; - const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE; - group.traverse( c => { - - if ( ('isMesh' in c && c.isMesh) || ('isLineSegments' in c && c.isLineSegments) ) { - - if ( 'material' in c && 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 ( 'material' in c && typeof c.material == 'string' ) { - - c.material = getMaterial( c, c.material ); - - } - - } - - } ); - - // Returns the appropriate material for the object (line or face) given color code. If the code is "pass through" - // (24 for lines, 16 for edges) then the pass through color code is used. If that is also pass through then it's - // simply returned for the subsequent material application. - function getMaterial( c: Object3D, colorCode: string ) { - - // if our parent is a passthrough color code and we don't have the current material color available then - // return early. - if ( parentIsPassthrough && ! ( colorCode in materialHierarchy ) && ! finalMaterialPass ) { - - return colorCode; - - } - - const forEdge = ('isLineSegments' in c && c.isLineSegments) || ('isConditionalLine' in c && 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 ) { - - // see if we can get the final material from from the "getMaterial" function which will attempt to - // parse the "direct" colors - material = loader.getMaterial( colorCode ); - if ( material === null ) { - - // otherwise throw a warning if this is final opportunity to set the material - console.warn( `LDrawLoader: Material properties for code ${ colorCode } not available.` ); - - // And return the 'missing color' material - material = loader.missingColorMaterial; - - } - - } else { - - return colorCode; - - } - - if ( 'isLineSegments' in c && c.isLineSegments ) { - - material = material.userData.edgeMaterial; - - if ( 'isConditionalLine' in c && c.isConditionalLine ) { - - material = material.userData.conditionalEdgeMaterial; - - } - - } - - return material; - - } - - } - - getMainMaterial() { - - return this.getMaterial( MAIN_COLOUR_CODE ); - - } - - getMainEdgeMaterial() { - - const mat = this.getMaterial( MAIN_EDGE_COLOUR_CODE ); - return mat ? mat.userData.edgeMaterial : null; - - } - - parseColorMetaDirective( lineParser: LineParser ) { - - // Parses a color definition and returns a THREE.Material - - let code: string = null; - - // Triangle and line colors - let color = "#FF00FF"; - let edgeColor = "#FF00FF"; - - // Transparency - let alpha = 1; - let isTransparent = false; - // Self-illumination: - let luminance = 0; - - let finishType = FINISH_TYPE_DEFAULT; - - let edgeMaterial: LineBasicMaterial = null; - - const name = lineParser.getToken(); - if ( ! name ) { - - throw new Error( 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.' ); - - } - - // Parse tag tokens and their parameters - let token: string = null; - // eslint-disable-next-line no-constant-condition - while ( true ) { - - token = lineParser.getToken(); - - if ( ! token ) { - - break; - - } - - if ( ! parseLuminance( token ) ) { - - 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( '#' ) ) { - - // Try to see if edge color is a color code - edgeMaterial = this.getMaterial( edgeColor ) as LineBasicMaterial; - if ( ! edgeMaterial ) { - - throw new Error( 'LDrawLoader: Invalid edge color 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 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': - - if ( ! parseLuminance( lineParser.getToken() ) ) { - - throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + lineParser.getLineNumberString() + '.' ); - - } - - 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': - // Not implemented - 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 MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0 } ); - break; - - case FINISH_TYPE_PEARLESCENT: - - // Try to imitate pearlescency by making the surface glossy - material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0.25 } ); - break; - - case FINISH_TYPE_CHROME: - - // Mirror finish surface - material = new MeshStandardMaterial( { color: color, roughness: 0, metalness: 1 } ); - break; - - case FINISH_TYPE_RUBBER: - - // Rubber finish - material = new MeshStandardMaterial( { color: color, roughness: 0.9, metalness: 0 } ); - break; - - case FINISH_TYPE_MATTE_METALLIC: - - // Brushed metal finish - material = new MeshStandardMaterial( { color: color, roughness: 0.8, metalness: 0.4 } ); - break; - - case FINISH_TYPE_METAL: - - // Average metal finish - material = new MeshStandardMaterial( { color: color, 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.color.convertSRGBToLinear(); - - material.polygonOffset = true; - material.polygonOffsetFactor = 1; - - if ( luminance !== 0 ) { - - material.emissive.set( material.color ).multiplyScalar( luminance ); - - } - - if ( ! edgeMaterial ) { - - // This is the material used for edges - edgeMaterial = new LineBasicMaterial( { - color: edgeColor, - transparent: isTransparent, - opacity: alpha, - depthWrite: ! isTransparent - } ); - edgeMaterial.userData.code = code; - edgeMaterial.name = name + ' - Edge'; - edgeMaterial.color.convertSRGBToLinear(); - - // This is the material used for conditional edges - edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( { - - fog: true, - transparent: isTransparent, - depthWrite: ! isTransparent, - color: edgeColor, - opacity: alpha, - - } ); - edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear(); - edgeMaterial.userData.conditionalEdgeMaterial.userData.code = code; - edgeMaterial.userData.conditionalEdgeMaterial.name = name + ' - Conditional Edge'; - - } - - material.userData.code = code; - material.name = name; - - material.userData.edgeMaterial = edgeMaterial; - - this.addMaterial( material ); - - return material; - - function parseLuminance( token: string ) { - - // Returns success - - let lum; - - if ( token.startsWith( 'LUMINANCE' ) ) { - - lum = parseInt( token.substring( 9 ) ); - - } else { - - lum = parseInt( token ); - - } - - if ( isNaN( lum ) ) { - - return false; - - } - - luminance = Math.max( 0, Math.min( 1, lum / 255 ) ); - - return true; - - } - - } - - computeBuildingSteps( model: Group ) { - - // Sets userdata.buildingStep number in Group objects and userData.numBuildingSteps number in the root Group object. - - let stepNumber = 0; - - model.traverse( c => { - - if ( 'isGroup' in c && c.isGroup ) { - - if ( c.userData["startingBuildingStep"] ) { - - stepNumber ++; - - } - - c.userData["buildingStep"] = stepNumber; - - } - - } ); - - model.userData["numBuildingSteps"] = stepNumber + 1; - - } - -} \ No newline at end of file diff --git a/packages/frontend/src/scripts/loaders/ldraw.ts b/packages/frontend/src/scripts/loaders/ldraw.ts index f56ab07d..17ba8a40 100644 --- a/packages/frontend/src/scripts/loaders/ldraw.ts +++ b/packages/frontend/src/scripts/loaders/ldraw.ts @@ -2,8 +2,12 @@ import axios from "axios" import * as THREE from "three" import { LDrawLoader } from 'three/examples/jsm/loaders/LDrawLoader' +import { Model, Parser } from "productboard-ldraw" + import { CacheAPI } from "../clients/cache" -//import { worker } from "../worker" + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const empty = (_part: string, _loaded: number, _total: number) => {/**/} const TEXT_DECODER = new TextDecoder() @@ -18,29 +22,144 @@ const LOADING_MANAGER = new THREE.LoadingManager().setURLModifier(url => { const LDRAW_LOADER = new LDrawLoader(LOADING_MANAGER) LDRAW_LOADER.preloadMaterials('/rest/parts/LDConfig.ldr').then(() => { - // console.log('Materials loaded!') + console.log('Materials loaded!') }).catch(error => { console.error(error) }) -export async function loadLDrawModel(path: string) { +export async function loadLDrawModel(path: string, update = empty) { const file = await CacheAPI.loadFile(path) const text = TEXT_DECODER.decode(file) - //worker.postMessage({ text, url: path }) - return parseLDrawModel(text) + return parseLDrawModel(path, text, update) +} + +const LDRAW_PAUSE: {[path: string]: boolean} = {} +const LDRAW_RESUME: {[path: string]: () => void} = {} +const LDRAW_ACTIVE: {[path: string]: () => Promise} = {} + +export function pauseLoadLDrawPath(path: string) { + LDRAW_PAUSE[path] = true +} + +export function resumeLoadLDrawPath(path: string) { + LDRAW_PAUSE[path] = false + + // Resume running load process + LDRAW_RESUME[path] && LDRAW_RESUME[path]() } -export async function loadLDrawPath(path: string) { +export async function loadLDrawPath(path: string, update = empty) { const response = await axios.get(path) - return parseLDrawModel(response.data) + + return parseLDrawModel(path, response.data, update) +} + +export async function parseLDrawModel(path: string, data: string, update = empty) { + LDRAW_PAUSE[path] = false + + LDRAW_ACTIVE[path] = async () => { + if (LDRAW_PAUSE[path]) { + // Wait for load process to be resumed + return new Promise(resolve => { + LDRAW_RESUME[path] = resolve + }) + } else { + // Continue load process + return + } + } + + const model = new Parser().parse(data) + + if (model.files.length > 0) { + const group = new THREE.Group() + const total = countParts(model, model.files[0]) + parseModel(path, group, model, model.files[0], 0, total, update) + group.rotation.x = Math.PI + return group + } else { + if (model.shapes.length > 0) { + const group = await parseFull(data) + group.rotation.x = Math.PI + return group + } else { + const group = new THREE.Group() + const total = countParts(model, model) + parseModel(path, group, model, model, 0, total, update) + group.rotation.x = Math.PI + return group + } + } +} + +async function pause(milliseconds: number) { + return new Promise(resolve => { + setTimeout(resolve, milliseconds) + }) +} + +function countParts(context: Model, model: Model) { + let count = 0 + + for (const reference of model.references) { + if (reference.file.endsWith('.dat')) { + count++ + } else if (reference.file in context.fileIndex) { + count += countParts(context, context.fileIndex[reference.file]) + } + } + + return count +} + +async function parseModel(path: string, group: THREE.Group, context: Model, model: Model, loaded: number, total: number, update = empty) { + + update(undefined, loaded, total) + + for (const reference of model.references) { + + // Check if load process is active and wait, if necessary, for load process to be resumed + await LDRAW_ACTIVE[path]() + + if (reference.file.endsWith('.dat')) { + + // Load part + + const submodel = await parseFull(reference.line) + + group.add(submodel.children[0]) + + loaded++ + + update(reference.file, loaded, total) + + await pause(0) + + } else if (reference.file in context.fileIndex) { + + // Load submodel + + const submodel = context.fileIndex[reference.file] + + const child = new THREE.Group() + + child.position.set(reference.position.x, reference.position.y, reference.position.z) + // TODO Set rotation + + group.add(child) + + // Continue loading submodel + loaded = await parseModel(path, child, context, submodel, loaded, total, update) + } + } + + return loaded } -export async function parseLDrawModel(data: string) { +function parseFull(data: string) { return new Promise(resolve => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (LDRAW_LOADER as any).parse(data, (group: THREE.Group) => { - // Fix coordinates - group.rotation.x = Math.PI // Resolve resolve(group) }) diff --git a/packages/frontend/src/scripts/main.tsx b/packages/frontend/src/scripts/main.tsx index 3ac0b76c..039854b8 100644 --- a/packages/frontend/src/scripts/main.tsx +++ b/packages/frontend/src/scripts/main.tsx @@ -9,7 +9,6 @@ import './clients/mqtt' import { PageHeaderBoot } from './components/snippets/PageHeaderBoot' import { LoadingView } from './components/views/Loading' import './plausible' -import './worker' import AppIcon from '/src/images/app.png' diff --git a/packages/frontend/src/scripts/worker.ts b/packages/frontend/src/scripts/worker.ts deleted file mode 100644 index 75daa7bb..00000000 --- a/packages/frontend/src/scripts/worker.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const worker = new Worker('/scripts/worker/main.js') - -worker.addEventListener('message', event => { - console.log(event.data) -}) - -worker.addEventListener('messageerror', event => { - console.error(event.data) -}) - -worker.addEventListener('error', event => { - console.error(event.error) -}) \ No newline at end of file diff --git a/packages/frontend/src/styles/widgets/FileView3D.css b/packages/frontend/src/styles/widgets/FileView3D.css index 6c322286..8be0ead8 100644 --- a/packages/frontend/src/styles/widgets/FileView3D.css +++ b/packages/frontend/src/styles/widgets/FileView3D.css @@ -7,7 +7,7 @@ div.widget.file_view_3d { background-color: rgb(215,215,215); } -div.widget.file_view_3d > div.model_graph { +div.widget.file_view_3d > div.widget.model_graph { position: absolute; box-sizing: border-box; @@ -23,11 +23,11 @@ div.widget.file_view_3d > div.model_graph { overflow: auto; } -div.widget.file_view_3d.toggle > div.model_graph { +div.widget.file_view_3d.toggle > div.widget.model_graph { z-index: 2; } -div.widget.file_view_3d > div.model_view_3d { +div.widget.file_view_3d > div.widget.model_view_3d { position: absolute; z-index: 0; @@ -41,6 +41,40 @@ div.widget.file_view_3d > div.model_view_3d { height: auto; } +div.widget.file_view_3d > div.progress_bar { + position: absolute; + + left: 1em; + right: 21em; + bottom: 1em; + + border-radius: 0.5em; + + overflow: hidden; + + background-color: white; + background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.1)); +} +div.widget.file_view_3d > div.progress_bar > div.indicator { + position: absolute; + + z-index: 0; + + top: 0; + left: 0; + bottom: 0; + + background-color: orange; + background-image: linear-gradient(rgba(255,255,255,0.1), rgba(255,255,255,0)); +} +div.widget.file_view_3d > div.progress_bar > div.text { + position: relative; + + z-index: 1; + + text-align: center; +} + div.widget.file_view_3d > a { display: none; @@ -74,27 +108,36 @@ div.widget.file_view_3d.toggle > a > span { } @media screen and (max-width: 100em) { - div.widget.file_view_3d > div.model_view_3d { + div.widget.file_view_3d > div.widget.model_view_3d { right: 0; } + div.widget.file_view_3d > div.progress_bar { + right: 1em; + } div.widget.file_view_3d > a { display: block; } } @media screen and (max-width: 70em) { - div.widget.file_view_3d > div.model_view_3d { + div.widget.file_view_3d > div.widget.model_view_3d { right: 20em; } + div.widget.file_view_3d > div.progress_bar { + right: 21em; + } div.widget.file_view_3d > a { display: none; } } @media screen and (max-width: 50em) { - div.widget.file_view_3d > div.model_view_3d { + div.widget.file_view_3d > div.widget.model_view_3d { right: 0; } + div.widget.file_view_3d > div.progress_bar { + right: 1em; + } div.widget.file_view_3d > a { display: block; } diff --git a/packages/frontend/webpack.common.js b/packages/frontend/webpack.common.js index 76b5fd3f..6d625670 100644 --- a/packages/frontend/webpack.common.js +++ b/packages/frontend/webpack.common.js @@ -2,7 +2,7 @@ import { resolve } from 'path' import HtmlWebpackPlugin from 'html-webpack-plugin' import webpack from 'webpack' -const { ProvidePlugin } = webpack +const { NormalModuleReplacementPlugin, ProvidePlugin } = webpack export default { stats: 'minimal', @@ -13,6 +13,12 @@ export default { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/, + },{ + test: /\.(wasm)$/i, + type: 'asset/resource', + generator: { + filename: 'modules/[hash][ext][query]' + } },{ test: /\.(css)$/i, use: ['style-loader', 'css-loader'] @@ -36,23 +42,27 @@ export default { 'three': resolve('../../node_modules/three') }, fallback: { - 'os': false, - 'stream': false, + 'crypto': false, 'http': false, 'https': false, - 'zlib': false, - 'util': false, + 'fs': false, + 'os': false, 'path': false, - 'crypto': false, - '@nestjs/core': false, + 'stream': false, + 'util': false, + 'zlib': false, '@nestjs/common': false, + '@nestjs/core': false, '@nestjs/mapped-types': false, - '@nestjs/swagger': false, - '@nestjs/microservices': false + '@nestjs/microservices': false, + '@nestjs/swagger': false }, extensions: ['.tsx', '.ts', '.js'], }, plugins: [ + new NormalModuleReplacementPlugin(/node:/, resource => { + return resource.request = resource.request.replace(/node:/, "") + }), new ProvidePlugin({ Buffer: ['buffer', 'Buffer'], process: 'process/browser.js' diff --git a/packages/ldraw/src/main.ts b/packages/ldraw/src/main.ts index c2ca35e5..f8691716 100644 --- a/packages/ldraw/src/main.ts +++ b/packages/ldraw/src/main.ts @@ -1,3 +1,2 @@ export * from './model' -export * from './parser' -export * from './manager' \ No newline at end of file +export * from './parser' \ No newline at end of file diff --git a/packages/ldraw/src/manager.ts b/packages/ldraw/src/manager.ts deleted file mode 100644 index c2df6abd..00000000 --- a/packages/ldraw/src/manager.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Model } from "./model" -import { Parser } from "./parser" - -export interface Loader { - load(url: string): Promise -} - -export class Manager { - - private readonly models: {[url: string]: Model} = {} - - private readonly parser = new Parser() - - constructor(private loader: Loader) {} - - async get(url: string) { - if (!(url in this.models)) { - const text = await this.loader.load(url) - const model = this.parser.parse(text, url) - // TODO this.parser.extend(model, 'color defs') - this.models[url] = model - } - return this.models[url] - } - -} \ No newline at end of file diff --git a/packages/ldraw/src/model.ts b/packages/ldraw/src/model.ts index 1ff749f3..6ff3b270 100644 --- a/packages/ldraw/src/model.ts +++ b/packages/ldraw/src/model.ts @@ -31,54 +31,56 @@ export class Matrix { } export abstract class Entry { + constructor(public line: string) { + } } export class Comment extends Entry { - constructor(public text: string) { - super() + constructor(line: string, public text: string) { + super(line) } } export class Command extends Entry { - constructor(public text: string) { - super() + constructor(line: string, public text: string) { + super(line) } } export class Reference extends Entry { - constructor(public color: number, public position: Vector, public orientation: Matrix, public file: string) { - super() + constructor(line: string, public color: number, public position: Vector, public orientation: Matrix, public file: string) { + super(line) } } export abstract class Shape extends Entry { - constructor(public color: number) { - super() + constructor(line: string, public color: number) { + super(line) } } export class Line extends Shape { - constructor(color: number, public firstPoint: Vector, public secondPoint: Vector) { - super(color) + constructor(line: string, color: number, public firstPoint: Vector, public secondPoint: Vector) { + super(line, color) } } export class Triangle extends Shape { - constructor(color: number, public firstPoint: Vector, public secondPoint: Vector, public thirdPoint: Vector) { - super(color) + constructor(line: string, color: number, public firstPoint: Vector, public secondPoint: Vector, public thirdPoint: Vector) { + super(line, color) } } export class Quadrilateral extends Shape { - constructor(color: number, public firstPoint: Vector, public secondPoint: Vector, public thirdPoint: Vector, public fourthVector: Vector) { - super(color) + constructor(line: string, color: number, public firstPoint: Vector, public secondPoint: Vector, public thirdPoint: Vector, public fourthVector: Vector) { + super(line, color) } } export class OptionalLine extends Shape { - constructor(color: number, public firstPoint: Vector, public secondPoint: Vector, public firstControlPoint: Vector, public secondControlPoint: Vector) { - super(color) + constructor(line: string, color: number, public firstPoint: Vector, public secondPoint: Vector, public firstControlPoint: Vector, public secondControlPoint: Vector) { + super(line, color) } } diff --git a/packages/ldraw/src/parser.ts b/packages/ldraw/src/parser.ts index 82a08755..46cd97b8 100644 --- a/packages/ldraw/src/parser.ts +++ b/packages/ldraw/src/parser.ts @@ -45,7 +45,7 @@ export class Parser { } private parseCommand(context: Context, data: string) { - context.current.addCommand(new Command(data.substring('0 !'.length))) + context.current.addCommand(new Command(data, data.substring('0 !'.length))) if (data.startsWith('0 !DATA ')) { const name = data.substring('0 !DATA '.length) const model = new Model(name, context.root) @@ -129,11 +129,11 @@ export class Parser { } else if (data.startsWith('0 NOFILE')) { context.current = context.root } else if (data.startsWith('0 // ')) { - context.current.addComment(new Comment(data.substring('0 // '.length))) + context.current.addComment(new Comment(data, data.substring('0 // '.length))) } else if (data.startsWith('0 //')) { - context.current.addComment(new Comment(data.substring('0 //'.length))) + context.current.addComment(new Comment(data, data.substring('0 //'.length))) } else { - context.current.addComment(new Comment(data.substring('0 '.length))) + context.current.addComment(new Comment(data, data.substring('0 '.length))) } } @@ -162,7 +162,7 @@ export class Parser { const file = item[13] - context.current.addReference(new Reference(color, position, orientation, file)) + context.current.addReference(new Reference(data, color, position, orientation, file)) } private parseLine(context: Context, data: string) { @@ -182,7 +182,7 @@ export class Parser { const secondPoint = new Vector(x2, y2, z2) - context.current.addLine(new Line(color, firstPoint, secondPoint)) + context.current.addLine(new Line(data, color, firstPoint, secondPoint)) } private parseTriangle(context: Context, data: string) { @@ -208,7 +208,7 @@ export class Parser { const thirdPoint = new Vector(x3, y3, z3) - context.current.addTriangle(new Triangle(color, firstPoint, secondPoint, thirdPoint)) + context.current.addTriangle(new Triangle(data, color, firstPoint, secondPoint, thirdPoint)) } private parseQuadrilateral(context: Context, data: string) { @@ -240,7 +240,7 @@ export class Parser { const fourthPoint = new Vector(x4, y4, z4) - context.current.addQuadliteral(new Quadrilateral(color, firstPoint, secondPoint, thirdPoint, fourthPoint)) + context.current.addQuadliteral(new Quadrilateral(data, color, firstPoint, secondPoint, thirdPoint, fourthPoint)) } private parseOptionalLine(context: Context, data: string) { @@ -272,7 +272,7 @@ export class Parser { const secondControlPoint = new Vector(x4, y4, z4) - context.current.addOptionalLine(new OptionalLine(color, firstPoint, secondPoint, firstControlPoint, secondControlPoint)) + context.current.addOptionalLine(new OptionalLine(data, color, firstPoint, secondPoint, firstControlPoint, secondControlPoint)) } } \ No newline at end of file diff --git a/packages/worker/.eslintrc.json b/packages/worker/.eslintrc.json deleted file mode 100644 index 544b7b4d..00000000 --- a/packages/worker/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} \ No newline at end of file diff --git a/packages/worker/package.json b/packages/worker/package.json deleted file mode 100644 index f72d4e6b..00000000 --- a/packages/worker/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "productboard-worker", - "version": "0.0.1", - "description": "Worker thread for the collaborative design platform", - "license": "UNLICENSED", - "private": "true", - "type": "module", - "author": { - "name": "Georg Hackenberg", - "email": "georg.hackenberg@fh-wels.at" - }, - "contributors": [ - { - "name": "Georg Hackenberg", - "email": "georg.hackenberg@fh-wels.at" - }, - { - "name": "Christian Zehetner", - "email": "christian.zehetner@fh-wels.at" - }, - { - "name": "Jürgen Humenberger", - "email": "juergen.humenberger@fh-wels.at" - }, - { - "name": "Dominik Frühwirth", - "email": "dominik.fruewirth@fh-wels.at" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/ghackenberg/caddrive.git", - "directory": "packages/worker" - }, - "scripts": { - "clean": "rm -rf public", - "build": "webpack --config webpack.prod.js", - "lint": "eslint src", - "loc": "sloc src", - "dev": "webpack serve --config webpack.dev.js", - "start": "http-server -p 3005 -s" - }, - "dependencies": { - "buffer": "^5.7.1", - "http-server": "^14.1.0", - "process": "^0.11.10", - "productboard-common": "^0.0.1", - "productboard-ldraw": "^0.0.1", - "url": "^0.11.0" - }, - "devDependencies": { - "@babel/preset-env": "^7.14.4", - "prop-types": "^15.7.2", - "ts-loader": "^9.4.1", - "webpack": "^5.38.1", - "webpack-cli": "^4.7.2", - "webpack-dev-server": "^4.8.1", - "webpack-merge": "^5.8.0" - } -} diff --git a/packages/worker/src/main.ts b/packages/worker/src/main.ts deleted file mode 100644 index b730679d..00000000 --- a/packages/worker/src/main.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Parser } from 'productboard-ldraw' - -const parser = new Parser() - -onmessage = event => { - postMessage(parser.parse(event.data.text, event.data.url)) -} \ No newline at end of file diff --git a/packages/worker/tsconfig.json b/packages/worker/tsconfig.json deleted file mode 100644 index 92c177c5..00000000 --- a/packages/worker/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDirs": ["src", "../common/src", "../ldraw/src"], - "outDir": "bin", - "lib": ["ES2015", "WebWorker"] - } -} \ No newline at end of file diff --git a/packages/worker/webpack.common.js b/packages/worker/webpack.common.js deleted file mode 100644 index 08d86be8..00000000 --- a/packages/worker/webpack.common.js +++ /dev/null @@ -1,49 +0,0 @@ -import { resolve } from 'path' - -import webpack from 'webpack' -const { ProvidePlugin } = webpack - -export default { - stats: 'minimal', - entry: './src/main.ts', - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/, - } - ], - }, - resolve: { - alias: { - 'three': resolve('../node_modules/three') - }, - fallback: { - 'os': false, - 'stream': false, - 'http': false, - 'https': false, - 'zlib': false, - 'util': false, - 'path': false, - 'crypto': false, - '@nestjs/core': false, - '@nestjs/common': false, - '@nestjs/mapped-types': false, - '@nestjs/swagger': false, - '@nestjs/microservices': false - }, - extensions: ['.tsx', '.ts', '.js'], - }, - plugins: [ - new ProvidePlugin({ - Buffer: ['buffer', 'Buffer'], - process: 'process/browser.js' - }) - ], - output: { - path: resolve('public'), - filename: 'scripts/worker/[name].js' - } -} \ No newline at end of file diff --git a/packages/worker/webpack.dev.js b/packages/worker/webpack.dev.js deleted file mode 100644 index 40f95279..00000000 --- a/packages/worker/webpack.dev.js +++ /dev/null @@ -1,14 +0,0 @@ -import { resolve } from 'path' - -import { merge } from 'webpack-merge' - -import common from './webpack.common.js' - -export default merge(common, { - mode: 'development', - devServer: { - static: resolve('public'), - port: 3005 - }, - devtool: 'inline-source-map' -}) \ No newline at end of file diff --git a/packages/worker/webpack.prod.js b/packages/worker/webpack.prod.js deleted file mode 100644 index 8593d3b9..00000000 --- a/packages/worker/webpack.prod.js +++ /dev/null @@ -1,8 +0,0 @@ -import { merge } from 'webpack-merge' - -import common from './webpack.common.js' - -export default merge(common, { - mode: 'production', - devtool: 'source-map' -}) \ No newline at end of file