| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Fat Lines/Mesh Lines</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| import { | |||||
| _testFinish, | |||||
| Color, | |||||
| GLTFLoader2, | |||||
| IObject3D, | |||||
| LineMaterial2, | |||||
| LoadingScreenPlugin, | |||||
| PickingPlugin, PopmotionPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| plugins: [LoadingScreenPlugin, PickingPlugin], | |||||
| dropzone: true, | |||||
| }) | |||||
| viewer.scene.autoNearFarEnabled = false | |||||
| GLTFLoader2.UseMeshLines = true | |||||
| viewer.scene.backgroundColor = new Color(0x333333) | |||||
| await viewer.load<IObject3D>('https://asset-samples.threepipe.org/demos/temple-lines.glb.zip', { | |||||
| autoScale: true, | |||||
| autoCenter: true, | |||||
| }) | |||||
| const popmotion = viewer.addPluginSync(PopmotionPlugin) | |||||
| const material = viewer.materialManager.findMaterialsByName('Stone')[0] as LineMaterial2 | |||||
| popmotion.animate({ | |||||
| from: 0, | |||||
| to: 1, | |||||
| duration: 1000, | |||||
| repeat: Infinity, | |||||
| repeatType: 'mirror', | |||||
| onUpdate: (v) => { | |||||
| material.linewidth = Math.sin(v * Math.PI) * 3.5 + 1 | |||||
| material.color.setHSL(v, 0.5, 0.7) | |||||
| material.setDirty() | |||||
| }, | |||||
| }) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| ui.setupPluginUi(PickingPlugin) | |||||
| } | |||||
| init().finally(_testFinish) |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>glTF Mesh(Fat) Lines Import</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| import { | |||||
| _testFinish, | |||||
| BufferGeometry, | |||||
| BufferGeometry2, | |||||
| Color, | |||||
| GLTFLoader2, | |||||
| IMaterial, | |||||
| IObject3D, | |||||
| LineSegmentsGeometry, | |||||
| LineSegmentsGeometry2, | |||||
| LoadingScreenPlugin, | |||||
| Object3D, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| plugins: [LoadingScreenPlugin], | |||||
| dropzone: true, | |||||
| }) | |||||
| viewer.scene.autoNearFarEnabled = false | |||||
| GLTFLoader2.UseMeshLines = true | |||||
| viewer.scene.backgroundColor = new Color(0x333333) | |||||
| // await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| const obj1 = await viewer.load<IObject3D>('https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/Models/MeshPrimitiveModes/glTF/MeshPrimitiveModes.gltf', { | |||||
| autoScale: true, | |||||
| autoCenter: true, | |||||
| useMeshLines: true, | |||||
| }) | |||||
| const obj2 = await viewer.load<IObject3D>('https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/Models/MeshPrimitiveModes/glTF/MeshPrimitiveModes.gltf', { | |||||
| autoScale: true, | |||||
| autoCenter: true, | |||||
| useMeshLines: false, | |||||
| }) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| const mats: IMaterial[] = [] | |||||
| const o1: IObject3D[] = [] | |||||
| const o2: IObject3D[] = [] | |||||
| const p1 = viewer.scene.addObject(new Object3D()).translateY(0.75) | |||||
| const p2 = viewer.scene.addObject(new Object3D()).translateY(-0.75) | |||||
| p1.scale.setScalar(0.6) | |||||
| p2.scale.setScalar(0.6) | |||||
| obj1?.traverse(o=>{ | |||||
| if (o.materials?.[0].isLineMaterial) { | |||||
| mats.push(o.materials[0]) | |||||
| o1.push(o) | |||||
| } | |||||
| }) | |||||
| obj2?.traverse(o=>{ | |||||
| if (o.materials?.[0].isUnlitLineMaterial) { | |||||
| mats.push(o.materials[0]) | |||||
| o2.push(o) | |||||
| } | |||||
| }) | |||||
| o1.forEach(o=>p1.add(o)) | |||||
| o2.forEach(o=>p2.add(o)) | |||||
| obj1?.dispose(true) | |||||
| obj2?.dispose(true) | |||||
| mats.map(m=>{ | |||||
| if (!m.appliedMeshes.size) return | |||||
| m.linewidth = 10 | |||||
| ui.appendChild(m.uiConfig) | |||||
| }) | |||||
| console.log(LineSegmentsGeometry) | |||||
| console.log(LineSegmentsGeometry2) | |||||
| console.log(BufferGeometry) | |||||
| console.log(BufferGeometry2) | |||||
| } | |||||
| init().finally(_testFinish) |
| <li><a href="./splat-load/">SPLAT Load<br/>(Gaussian Splatting) </a></li> | <li><a href="./splat-load/">SPLAT Load<br/>(Gaussian Splatting) </a></li> | ||||
| <li><a href="./extra-importer-plugins/">Extra (3ds, 3mf, collada, amf, bvh, vox, gcode, mdd, pcd, tilt, wrl, ldraw, vtk, xyz) Load </a></li> | <li><a href="./extra-importer-plugins/">Extra (3ds, 3mf, collada, amf, bvh, vox, gcode, mdd, pcd, tilt, wrl, ldraw, vtk, xyz) Load </a></li> | ||||
| <li><a href="./gltf-meshopt-compression/">glTF MeshOpt Decode (Compression Extension) </a></li> | <li><a href="./gltf-meshopt-compression/">glTF MeshOpt Decode (Compression Extension) </a></li> | ||||
| <li><a href="./gltf-mesh-lines/">glTF Mesh Lines <br/>(Fat Lines) </a></li> | |||||
| <li><a href="./b3dm-load/">B3DM Load (3D Tile) </a></li> | <li><a href="./b3dm-load/">B3DM Load (3D Tile) </a></li> | ||||
| <li><a href="./i3dm-load/">I3DM Load (3D Tile) </a></li> | <li><a href="./i3dm-load/">I3DM Load (3D Tile) </a></li> | ||||
| <li><a href="./pnts-load/">PNTS Load (3D Points) </a></li> | <li><a href="./pnts-load/">PNTS Load (3D Points) </a></li> | ||||
| <li><a href="./obj-to-glb/">Convert OBJ to GLB </a></li> | <li><a href="./obj-to-glb/">Convert OBJ to GLB </a></li> | ||||
| <li><a href="./3dm-to-glb/">Convert 3DM to GLB </a></li> | <li><a href="./3dm-to-glb/">Convert 3DM to GLB </a></li> | ||||
| <li><a href="./hdr-to-exr/">Convert HDR to EXR </a></li> | <li><a href="./hdr-to-exr/">Convert HDR to EXR </a></li> | ||||
| <li><a href="./fat-lines/">Fat Lines <br/>(Mesh Lines) </a></li> | |||||
| </ul> | </ul> | ||||
| <h2 class="category">Experiments</h2> | <h2 class="category">Experiments</h2> | ||||
| <ul> | <ul> |
| "version": "0.0.41", | "version": "0.0.41", | ||||
| "license": "Apache-2.0", | "license": "Apache-2.0", | ||||
| "dependencies": { | "dependencies": { | ||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1005/package.tgz", | |||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1006/package.tgz", | |||||
| "@types/webxr": "^0.5.1", | "@types/webxr": "^0.5.1", | ||||
| "@types/wicg-file-system-access": "^2020.9.5", | "@types/wicg-file-system-access": "^2020.9.5", | ||||
| "popmotion": "^11.0.5", | "popmotion": "^11.0.5", | ||||
| "license": "MIT" | "license": "MIT" | ||||
| }, | }, | ||||
| "node_modules/@types/three": { | "node_modules/@types/three": { | ||||
| "version": "0.157.1005", | |||||
| "resolved": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1005/package.tgz", | |||||
| "integrity": "sha512-Qb6XlwUyDAoNUi9IxS5lYmkShRsVFCAsG4KAXTPlmi6G2V/GIzmvXZ5Ut2SV1L+7nCEFwtvENkzP3/MVQ9cWUQ==", | |||||
| "version": "0.157.1006", | |||||
| "resolved": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1006/package.tgz", | |||||
| "integrity": "sha512-WiV0kRvPyPK3KEOGshnPDbgJzfa/YozkFSSfVu32WgFYp9hiSSVQK8wROq5We/DTWs8BmnyWuh7lzbz/4DC3jQ==", | |||||
| "dependencies": { | "dependencies": { | ||||
| "fflate": "~0.6.10", | "fflate": "~0.6.10", | ||||
| "meshoptimizer": "~0.18.1" | "meshoptimizer": "~0.18.1" |
| "vitepress-plugin-nprogress": "^0.0.4" | "vitepress-plugin-nprogress": "^0.0.4" | ||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1005/package.tgz", | |||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1006/package.tgz", | |||||
| "@types/webxr": "^0.5.1", | "@types/webxr": "^0.5.1", | ||||
| "@types/wicg-file-system-access": "^2020.9.5", | "@types/wicg-file-system-access": "^2020.9.5", | ||||
| "popmotion": "^11.0.5", | "popmotion": "^11.0.5", | ||||
| "ts-browser-helpers": "^0.16.2", | "ts-browser-helpers": "^0.16.2", | ||||
| "three": "https://github.com/repalash/three.js-modded/releases/download/v0.157.1007/package.tgz", | "three": "https://github.com/repalash/three.js-modded/releases/download/v0.157.1007/package.tgz", | ||||
| "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.157.1007.tar.gz", | "three-f": "https://github.com/repalash/three.js-modded/archive/refs/tags/v0.157.1007.tar.gz", | ||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1005/package.tgz", | |||||
| "@types/three-f": "https://github.com/repalash/three-ts-types/archive/refs/tags/v0.157.1005.tar.gz", | |||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.157.1006/package.tgz", | |||||
| "@types/three-f": "https://github.com/repalash/three-ts-types/archive/refs/tags/v0.157.1006.tar.gz", | |||||
| "@types/three-pkg": "https://gitpkg.now.sh/repalash/three-ts-types/types/three?modded_three" | "@types/three-pkg": "https://gitpkg.now.sh/repalash/three-ts-types/types/three?modded_three" | ||||
| }, | }, | ||||
| "local_dependencies": { | "local_dependencies": { |
| * Pass a custom file to use for the import. This will be used in the importer, and nothing will be fetched from the path | * Pass a custom file to use for the import. This will be used in the importer, and nothing will be fetched from the path | ||||
| */ | */ | ||||
| importedFile?: IFile, | importedFile?: IFile, | ||||
| /** | |||||
| * Use {@link MeshLine}(an extension of three.js `Line2`) instead of default `Line` for lines. This allows changing line width(fat lines) and other properties. | |||||
| * | |||||
| * Note - Only for gltf, glb files or files loaded with {@link GLTFLoader2}. If this flag is not passed, the default value is the value of the static property `GLTFLoader2.UseMeshLines`. | |||||
| */ | |||||
| useMeshLines?: boolean, | |||||
| } | } | ||||
| // export type IAssetImporterEventTypes = 'onLoad' | 'onProgress' | 'onStop' | 'onError' | 'onStart' | 'loaderCreate' | 'importFile' | 'importFiles' | 'processRaw' | 'processRawStart' | // export type IAssetImporterEventTypes = 'onLoad' | 'onProgress' | 'onStop' | 'onError' | 'onStart' | 'loaderCreate' | 'importFile' | 'importFiles' | 'processRaw' | 'processRawStart' |
| import type {GLTF, GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader.js' | import type {GLTF, GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader.js' | ||||
| import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js' | import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader.js' | ||||
| import {LoadingManager, Object3D, OrthographicCamera, Texture} from 'three' | |||||
| import {Line, LineLoop, LineSegments, LoadingManager, Object3D, OrthographicCamera, Texture} from 'three' | |||||
| import {AnyOptions, safeSetProperty} from 'ts-browser-helpers' | import {AnyOptions, safeSetProperty} from 'ts-browser-helpers' | ||||
| import {ThreeViewer} from '../../viewer' | import {ThreeViewer} from '../../viewer' | ||||
| import {generateUUID} from '../../three' | import {generateUUID} from '../../three' | ||||
| import {ThreeSerialization} from '../../utils' | import {ThreeSerialization} from '../../utils' | ||||
| import { | import { | ||||
| DirectionalLight2, | DirectionalLight2, | ||||
| LineGeometry2, | |||||
| LineMaterial2, | |||||
| LineSegmentsGeometry2, | |||||
| MeshLine, | |||||
| MeshLineSegments, | |||||
| PerspectiveCamera0, | PerspectiveCamera0, | ||||
| PhysicalMaterial, | PhysicalMaterial, | ||||
| PointLight2, | PointLight2, | ||||
| UnlitMaterial, | UnlitMaterial, | ||||
| } from '../../core' | } from '../../core' | ||||
| import {AssetImporter} from '../AssetImporter' | import {AssetImporter} from '../AssetImporter' | ||||
| import {ImportAddOptions} from '../AssetManager' | |||||
| // todo move somewhere | // todo move somewhere | ||||
| const supportedEmbeddedFiles = ['hdr', 'exr', 'webp', 'avif', 'ktx', 'hdrpng', 'svg', 'cube'] // ktx2, drc handled separately | |||||
| const supportedEmbeddedFiles = ['hdr', 'exr', 'webp', 'avif', 'ktx', 'hdrpng', 'svg', 'cube', 'ico', 'bmp', 'gif', 'tiff'] // ktx2, drc handled separately | |||||
| export class GLTFLoader2 extends GLTFLoader implements ILoader<GLTF, Object3D|undefined> { | export class GLTFLoader2 extends GLTFLoader implements ILoader<GLTF, Object3D|undefined> { | ||||
| isGLTFLoader2 = true | isGLTFLoader2 = true | ||||
| importOptions?: ImportAddOptions | |||||
| constructor(manager: LoadingManager) { | constructor(manager: LoadingManager) { | ||||
| super(manager) | super(manager) | ||||
| this.preparsers.push(glbEncryptionPreparser) | this.preparsers.push(glbEncryptionPreparser) | ||||
| GLTFMaterialsAlphaMapExtension.Import, | GLTFMaterialsAlphaMapExtension.Import, | ||||
| ] | ] | ||||
| /** | |||||
| * Use {@link MeshLine}(an extension of three.js `Line2`) instead of default `Line` for lines. This allows changing line width and other properties. | |||||
| * | |||||
| * This is the default value for the flag, it can also be controlled by using the `useMeshLines` in the import options. | |||||
| * | |||||
| * Note - Lines may not export correctly when loaded with this. | |||||
| */ | |||||
| static UseMeshLines = false | |||||
| /** | /** | ||||
| * Preparsers are run on the arraybuffer/string before parsing to read the glb/gltf data | * Preparsers are run on the arraybuffer/string before parsing to read the glb/gltf data | ||||
| */ | */ | ||||
| // todo save the path of invalid textures, check if they can be found in the loaded libs, and ask the user in UI to remap it to something else manually | // todo save the path of invalid textures, check if they can be found in the loaded libs, and ask the user in UI to remap it to something else manually | ||||
| if (!Texture.DEFAULT_IMAGE) Texture.DEFAULT_IMAGE = AssetImporter.WHITE_IMAGE_DATA | if (!Texture.DEFAULT_IMAGE) Texture.DEFAULT_IMAGE = AssetImporter.WHITE_IMAGE_DATA | ||||
| const useMeshLines = this.importOptions?.useMeshLines ?? GLTFLoader2.UseMeshLines | |||||
| GLTFLoader.ObjectConstructors.LineBasicMaterial = useMeshLines ? LineMaterial2 as any : UnlitLineMaterial as any | |||||
| return res ? super.parse(res, path, (ret)=>{ | return res ? super.parse(res, path, (ret)=>{ | ||||
| Texture.DEFAULT_IMAGE = val | Texture.DEFAULT_IMAGE = val | ||||
| GLTFLoader.ObjectConstructors.LineBasicMaterial = useMeshLines ? LineMaterial2 as any : UnlitLineMaterial as any | |||||
| onLoad && onLoad(ret) | onLoad && onLoad(ret) | ||||
| }, onError) : onError && onError(new ErrorEvent('no data')) | }, onError) : onError && onError(new ErrorEvent('no data')) | ||||
| }) | }) | ||||
| const scene: RootSceneImportResult|undefined = res ? res.scene || !!res.scenes && res.scenes.length > 0 && res.scenes[0] : undefined as any | const scene: RootSceneImportResult|undefined = res ? res.scene || !!res.scenes && res.scenes.length > 0 && res.scenes[0] : undefined as any | ||||
| if (!scene) return undefined | if (!scene) return undefined | ||||
| if (res.animations.length > 0) scene.animations = res.animations | if (res.animations.length > 0) scene.animations = res.animations | ||||
| scene.traverse((node: Object3D) => { | |||||
| if (node.userData.gltfUUID) { // saved in GLTFExporter2 | |||||
| safeSetProperty(node, 'uuid', node.userData.gltfUUID, true, true) | |||||
| delete node.userData.gltfUUID // have issue with cloning if we don't dispose. | |||||
| const useMeshLines = this.importOptions?.useMeshLines ?? GLTFLoader2.UseMeshLines | |||||
| // todo: move out and put the chosen setting in userData. | |||||
| if (useMeshLines) { | |||||
| const lines: Line[] = [] | |||||
| scene.traverse((node: Object3D) => { | |||||
| if (node.userData.gltfUUID) { // saved in GLTFExporter2 | |||||
| safeSetProperty(node, 'uuid', node.userData.gltfUUID, true, true) | |||||
| delete node.userData.gltfUUID // have issue with cloning if we don't dispose. | |||||
| } | |||||
| if ((node as Line).isLine) lines.push(node as Line) | |||||
| }) | |||||
| // convert lines to mesh/fat lines | |||||
| for (const line of lines) { | |||||
| convertToFatLine(line) | |||||
| } | } | ||||
| }) | |||||
| } | |||||
| // todo: replacing lights and camera, todo: remove and change constructors in GLTFLoader.js | // todo: replacing lights and camera, todo: remove and change constructors in GLTFLoader.js | ||||
| if (!scene.userData) scene.userData = {} | if (!scene.userData) scene.userData = {} | ||||
| if (res.userData) scene.userData.gltfExtras = res.userData // todo: put back in gltf in GLTFExporter2 | if (res.userData) scene.userData.gltfExtras = res.userData // todo: put back in gltf in GLTFExporter2 | ||||
| process(data: string | ArrayBuffer, path: string): Promise<string | ArrayBuffer> | process(data: string | ArrayBuffer, path: string): Promise<string | ArrayBuffer> | ||||
| [key: string]: any | [key: string]: any | ||||
| } | } | ||||
| // sample test model - https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/Models/MeshPrimitiveModes/glTF/MeshPrimitiveModes.gltf | |||||
| // todo maybe do the same as others inside GLTFLoader.js | |||||
| function convertToFatLine(line: Line) { | |||||
| const parent = line.parent | |||||
| if (!parent) { | |||||
| console.warn('GLTFLoader2: Line has no parent', line) | |||||
| return | |||||
| } | |||||
| if (line.geometry.index) line.geometry = line.geometry.toNonIndexed() // Line2 requires non indexed | |||||
| const line2 = | |||||
| (line as LineSegments).isLineSegments ? | |||||
| new MeshLineSegments(new LineSegmentsGeometry2(), line.material as LineMaterial2) : | |||||
| new MeshLine(new LineGeometry2(), line.material as LineMaterial2) | |||||
| let positions = line.geometry.attributes.position.array as Float32Array | |||||
| if ((line as LineLoop).isLineLoop) { | |||||
| // add first pos as last. | |||||
| const pos = new Float32Array(positions.length + 3) | |||||
| pos.set(positions) | |||||
| pos.set(positions.subarray(0, 3), positions.length) | |||||
| positions = pos | |||||
| } | |||||
| line2.geometry.setPositions(positions) | |||||
| line2.computeLineDistances() | |||||
| const index = parent.children.indexOf(line) | |||||
| parent.add(line2) | |||||
| const {geometry, material} = line2 | |||||
| const ud = line.userData | |||||
| line.userData = {} | |||||
| line2.copy(line as any, false) | |||||
| line2.geometry = geometry | |||||
| line2.material = material | |||||
| ;[...line.children].map(c => { | |||||
| line2.add(c) | |||||
| }) | |||||
| line2.userData = {...line2.userData, ...ud} | |||||
| material.userData.renderToGBuffer = false // this is set in LineMaterial2 | |||||
| material.userData.renderToDepth = false | |||||
| line.removeFromParent() | |||||
| // put at the same index | |||||
| const index2 = parent.children.indexOf(line2) | |||||
| if (index2 >= 0 && index2 !== index) { | |||||
| parent.children.splice(index2, 1) | |||||
| parent.children.splice(index, 0, line2) | |||||
| } | |||||
| } |
| */ | */ | ||||
| dispose(force?: boolean): void | dispose(force?: boolean): void | ||||
| /** | |||||
| * Clones the Material. | |||||
| * This is a shallow clone, so the properties are copied by reference. | |||||
| * | |||||
| * @param track - if true, the clone id and count will be tracked in the userData and a suffix will be appended to the name. default - false | |||||
| */ | |||||
| clone(track?: boolean): this; | |||||
| // optional from subclasses, added here for autocomplete | // optional from subclasses, added here for autocomplete | ||||
| flatShading?: boolean | flatShading?: boolean | ||||
| map?: ITexture | null | map?: ITexture | null | ||||
| isRawShaderMaterial?: boolean | isRawShaderMaterial?: boolean | ||||
| isPhysicalMaterial?: boolean | isPhysicalMaterial?: boolean | ||||
| isLineMaterial?: boolean | |||||
| isUnlitMaterial?: boolean | isUnlitMaterial?: boolean | ||||
| isGBufferMaterial?: boolean | isGBufferMaterial?: boolean | ||||
| isLineMaterial2?: boolean | |||||
| isUnlitLineMaterial?: boolean | |||||
| // [key: string]: any | // [key: string]: any | ||||
| } | } |
| } | } | ||||
| } | } | ||||
| import {NormalBufferAttributes, NormalOrGLBufferAttributes} from 'three' | |||||
| import type {IGeometry, IGeometryEventMap, IGeometryUserData} from '../IGeometry' | |||||
| import {LineGeometry} from 'three/examples/jsm/lines/LineGeometry' | |||||
| import {iGeometryCommons} from './iGeometryCommons' | |||||
| import type {IObject3D} from '../IObject' | |||||
| export class LineGeometry2<Attributes extends NormalOrGLBufferAttributes = NormalBufferAttributes, TE extends IGeometryEventMap = IGeometryEventMap> extends LineGeometry<Attributes, TE> implements IGeometry<Attributes, TE> { | |||||
| assetType: 'geometry' // dont set the value here since its checked in upgradeGeometry | |||||
| center2 = iGeometryCommons.center2 | |||||
| setDirty = iGeometryCommons.setDirty | |||||
| refreshUi = iGeometryCommons.refreshUi | |||||
| appliedMeshes = new Set<IObject3D>() | |||||
| declare userData: IGeometryUserData | |||||
| constructor() { | |||||
| super() | |||||
| iGeometryCommons.upgradeGeometry.call(this) | |||||
| } | |||||
| } |
| import {NormalBufferAttributes, NormalOrGLBufferAttributes} from 'three' | |||||
| import type {IGeometry, IGeometryEventMap, IGeometryUserData} from '../IGeometry' | |||||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry' | |||||
| import {iGeometryCommons} from './iGeometryCommons' | |||||
| import type {IObject3D} from '../IObject' | |||||
| export class LineSegmentsGeometry2<Attributes extends NormalOrGLBufferAttributes = NormalBufferAttributes, TE extends IGeometryEventMap = IGeometryEventMap> extends LineSegmentsGeometry<Attributes, TE> implements IGeometry<Attributes, TE> { | |||||
| assetType: 'geometry' // dont set the value here since its checked in upgradeGeometry | |||||
| center2 = iGeometryCommons.center2 | |||||
| setDirty = iGeometryCommons.setDirty | |||||
| refreshUi = iGeometryCommons.refreshUi | |||||
| appliedMeshes = new Set<IObject3D>() | |||||
| declare userData: IGeometryUserData | |||||
| constructor() { | |||||
| super() | |||||
| iGeometryCommons.upgradeGeometry.call(this) | |||||
| } | |||||
| } |
| import { | |||||
| BufferGeometry, | |||||
| NormalBufferAttributes, | |||||
| NormalOrGLBufferAttributes, | |||||
| } from 'three' | |||||
| import type {IGeometry, IGeometryEventMap, IGeometryUserData} from '../IGeometry' | |||||
| import {WireframeGeometry2} from 'three/examples/jsm/lines/WireframeGeometry2' | |||||
| import {iGeometryCommons} from './iGeometryCommons' | |||||
| import type {IObject3D} from '../IObject' | |||||
| export class WireframeGeometry3<Attributes extends NormalOrGLBufferAttributes = NormalBufferAttributes, TE extends IGeometryEventMap = IGeometryEventMap> extends WireframeGeometry2<Attributes, TE> implements IGeometry<Attributes, TE> { | |||||
| assetType: 'geometry' // dont set the value here since its checked in upgradeGeometry | |||||
| center2 = iGeometryCommons.center2 | |||||
| setDirty = iGeometryCommons.setDirty | |||||
| refreshUi = iGeometryCommons.refreshUi | |||||
| appliedMeshes = new Set<IObject3D>() | |||||
| declare userData: IGeometryUserData | |||||
| constructor(geometry: BufferGeometry) { | |||||
| super(geometry) | |||||
| iGeometryCommons.upgradeGeometry.call(this) | |||||
| } | |||||
| } |
| export {LineMaterial2} from './material/LineMaterial2' | export {LineMaterial2} from './material/LineMaterial2' | ||||
| export {LegacyPhongMaterial} from './material/LegacyPhongMaterial' | export {LegacyPhongMaterial} from './material/LegacyPhongMaterial' | ||||
| export {Mesh2} from './object/Mesh2' | export {Mesh2} from './object/Mesh2' | ||||
| export {MeshLine} from './object/MeshLine' | |||||
| export {MeshLineSegments} from './object/MeshLineSegments' | |||||
| export {BufferGeometry2} from './geometry/BufferGeometry2' | export {BufferGeometry2} from './geometry/BufferGeometry2' | ||||
| export {LineGeometry2} from './geometry/LineGeometry2' | |||||
| export {LineSegmentsGeometry2} from './geometry/LineSegmentsGeometry2' | |||||
| export {WireframeGeometry3} from './geometry/WireframeGeometry3' | |||||
| export {AmbientLight2} from './light/AmbientLight2' | export {AmbientLight2} from './light/AmbientLight2' | ||||
| export {DirectionalLight2} from './light/DirectionalLight2' | export {DirectionalLight2} from './light/DirectionalLight2' | ||||
| export {HemisphereLight2} from './light/HemisphereLight2' | export {HemisphereLight2} from './light/HemisphereLight2' |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator | ||||
| import {generateUiConfig, uiColor, uiInput, uiNumber, UiObjectConfig, uiToggle, uiVector} from 'uiconfig.js' | import {generateUiConfig, uiColor, uiInput, uiNumber, UiObjectConfig, uiToggle, uiVector} from 'uiconfig.js' | ||||
| import {BaseEvent, Color, IUniform, Material, Shader, Vector2, WebGLRenderer} from 'three' | |||||
| import { | |||||
| BaseEvent, | |||||
| BufferGeometry, | |||||
| Camera, | |||||
| Color, | |||||
| IUniform, | |||||
| Material, | |||||
| Object3D, | |||||
| Scene, | |||||
| Shader, | |||||
| Vector2, | |||||
| WebGLRenderer, | |||||
| } from 'three' | |||||
| import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | import {SerializationMetaType, shaderReplaceString, ThreeSerialization} from '../../utils' | ||||
| import {IMaterial, IMaterialEventMap, IMaterialGenerator, IMaterialParameters, IMaterialTemplate} from '../IMaterial' | |||||
| import { | |||||
| IMaterial, | |||||
| IMaterialEventMap, | |||||
| IMaterialGenerator, | |||||
| IMaterialParameters, | |||||
| IMaterialTemplate, | |||||
| IMaterialUserData, | |||||
| } from '../IMaterial' | |||||
| import {MaterialExtension} from '../../materials' | import {MaterialExtension} from '../../materials' | ||||
| import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | import {iMaterialCommons, threeMaterialPropList} from './iMaterialCommons' | ||||
| import {IObject3D} from '../IObject' | import {IObject3D} from '../IObject' | ||||
| public static readonly TYPE = 'LineMaterial2' // not using .type because it is used by three.js | public static readonly TYPE = 'LineMaterial2' // not using .type because it is used by three.js | ||||
| assetType = 'material' as const | assetType = 'material' as const | ||||
| declare userData: IMaterialUserData | |||||
| public readonly isLineMaterial2 = true | public readonly isLineMaterial2 = true | ||||
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator | ||||
| if (customMaterialExtensions) this.registerMaterialExtensions(customMaterialExtensions) | if (customMaterialExtensions) this.registerMaterialExtensions(customMaterialExtensions) | ||||
| iMaterialCommons.upgradeMaterial.call(this) | iMaterialCommons.upgradeMaterial.call(this) | ||||
| this.setValues(parameters) | this.setValues(parameters) | ||||
| // this.userData.renderToGBuffer = false | |||||
| // this.userData.renderToDepth = false | |||||
| } | } | ||||
| // region Material Extension | // region Material Extension | ||||
| super.onBeforeCompile(shader, renderer) | super.onBeforeCompile(shader, renderer) | ||||
| } | } | ||||
| onAfterRender = iMaterialCommons.onAfterRenderOverride(super.onAfterRender) | |||||
| autoUpdateResolution = true | |||||
| onBeforeRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D): void { | |||||
| if (this.autoUpdateResolution) renderer.getSize(this.resolution) | |||||
| super.onBeforeRender(renderer, scene, camera, geometry, object) | |||||
| iMaterialCommons.onBeforeRender.call(this, renderer, scene, camera, geometry, object) | |||||
| } | |||||
| onAfterRender(renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D): void { | |||||
| super.onAfterRender(renderer, scene, camera, geometry, object) | |||||
| iMaterialCommons.onAfterRender.call(this, renderer, scene, camera, geometry, object) | |||||
| } | |||||
| // endregion | // endregion | ||||
| expanded: true, | expanded: true, | ||||
| onChange: (ev)=>{ | onChange: (ev)=>{ | ||||
| if (!ev.config || ev.config.onChange) return | if (!ev.config || ev.config.onChange) return | ||||
| // this.uniformsNeedUpdate = true | |||||
| // this.appliedMeshes.forEach(m=>{ | |||||
| // if ((m.isLineSegments2 || m.isLineSegments) && m.computeLineDistances) { | |||||
| // m.computeLineDistances() | |||||
| // } | |||||
| // }) | |||||
| // todo set needsUpdate true only for properties that require it like maps. | // todo set needsUpdate true only for properties that require it like maps. | ||||
| this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | this.setDirty({uiChangeEvent: ev, needsUpdate: !!ev.last, refreshUi: !!ev.last}) | ||||
| }, | }, | ||||
| */ | */ | ||||
| setValues(parameters: Material|(LineMaterialParameters&{type?:string}), allowInvalidType = true, clearCurrentUserData: boolean|undefined = undefined): this { | setValues(parameters: Material|(LineMaterialParameters&{type?:string}), allowInvalidType = true, clearCurrentUserData: boolean|undefined = undefined): this { | ||||
| if (!parameters) return this | if (!parameters) return this | ||||
| if (parameters.type && !allowInvalidType && !['LineMaterial', this.constructor.TYPE].includes(parameters.type)) { | |||||
| if (parameters.type && !allowInvalidType && !['LineMaterial', this.constructor.TYPE].includes(parameters.type) && !(parameters as LineMaterial2).isLineMaterial && !(parameters as LineMaterial2).isLineMaterial2) { | |||||
| console.error('Material type is not supported:', parameters.type) | console.error('Material type is not supported:', parameters.type) | ||||
| return this | return this | ||||
| } | } |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator | ||||
| } | } | ||||
| } | } | ||||
| // todo gltf material extension | |||||
| // todo gltf material extension |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator |
| readonly appliedMeshes: Set<any> = new Set() | readonly appliedMeshes: Set<any> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| readonly isRawShaderMaterial: boolean | readonly isRawShaderMaterial: boolean |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator |
| readonly appliedMeshes: Set<IObject3D> = new Set() | readonly appliedMeshes: Set<IObject3D> = new Set() | ||||
| readonly setDirty = iMaterialCommons.setDirty | readonly setDirty = iMaterialCommons.setDirty | ||||
| dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | dispose(): this {return iMaterialCommons.dispose(super.dispose).call(this)} | ||||
| clone(): this {return iMaterialCommons.clone(super.clone).call(this)} | |||||
| clone(track = false): this {return iMaterialCommons.clone(super.clone).call(this, track)} | |||||
| dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | dispatchEvent<T extends Extract<keyof (TE&IMaterialEventMap), string>>(event: BaseEvent<T> & (TE&IMaterialEventMap)[T]): void {iMaterialCommons.dispatchEvent(super.dispatchEvent).call(this, event)} | ||||
| generator?: IMaterialGenerator | generator?: IMaterialGenerator |
| superDispose.call(this) | superDispose.call(this) | ||||
| }, | }, | ||||
| clone: (superClone: Material['clone']): IMaterial['clone'] => | clone: (superClone: Material['clone']): IMaterial['clone'] => | ||||
| function(this: IMaterial): IMaterial { | |||||
| if (!this.userData.cloneId) { | |||||
| this.userData.cloneId = '0' | |||||
| } | |||||
| if (!this.userData.cloneCount) { | |||||
| this.userData.cloneCount = 0 | |||||
| function(this: IMaterial, track = false): IMaterial { | |||||
| if (track) { | |||||
| if (!this.userData.cloneId) { | |||||
| this.userData.cloneId = '0' | |||||
| } | |||||
| if (!this.userData.cloneCount) { | |||||
| this.userData.cloneCount = 0 | |||||
| } | |||||
| this.userData.cloneCount += 1 | |||||
| } | } | ||||
| this.userData.cloneCount += 1 | |||||
| const material: IMaterial = this.generator?.({})?.setValues(this, false) ?? superClone.call(this) | const material: IMaterial = this.generator?.({})?.setValues(this, false) ?? superClone.call(this) | ||||
| material.userData.cloneId = material.userData.cloneId + '_' + this.userData.cloneCount | |||||
| material.userData.cloneCount = 0 | |||||
| material.name = material.name + '_' + material.userData.cloneId | |||||
| if (track) { | |||||
| material.userData.cloneId = material.userData.cloneId + '_' + this.userData.cloneCount | |||||
| material.userData.cloneCount = 0 | |||||
| material.name = (material.name || 'mat') + '_' + material.userData.cloneId | |||||
| } | |||||
| return material | return material | ||||
| }, | }, |
| import {ThreeViewer} from '../../viewer' | import {ThreeViewer} from '../../viewer' | ||||
| import {UnlitMaterial} from '../material/UnlitMaterial' | import {UnlitMaterial} from '../material/UnlitMaterial' | ||||
| import {PhysicalMaterial} from '../material/PhysicalMaterial' | import {PhysicalMaterial} from '../material/PhysicalMaterial' | ||||
| import {LineMaterial2} from '../material/LineMaterial2' | |||||
| import {UnlitLineMaterial} from '../material/UnlitLineMaterial' | |||||
| import {IMaterial} from '../IMaterial' | |||||
| // todo move somewhere? | // todo move somewhere? | ||||
| const defaultMaterial = new UnlitMaterial() | const defaultMaterial = new UnlitMaterial() | ||||
| defaultMaterial.name = 'Default Unlit Material' | defaultMaterial.name = 'Default Unlit Material' | ||||
| defaultMaterial.uiConfig = undefined as any | defaultMaterial.uiConfig = undefined as any | ||||
| const defaultUnlitLineMaterial = new UnlitLineMaterial() | |||||
| defaultUnlitLineMaterial.name = 'Default Unlit Line Material' | |||||
| defaultUnlitLineMaterial.uiConfig = undefined as any | |||||
| const defaultLineMaterial = new LineMaterial2() | |||||
| defaultLineMaterial.name = 'Default Line Material' | |||||
| defaultLineMaterial.uiConfig = undefined as any | |||||
| export function makeICameraCommonUiConfig(this: ICamera, config: UiObjectConfig): UiObjectConfig[] { | export function makeICameraCommonUiConfig(this: ICamera, config: UiObjectConfig): UiObjectConfig[] { | ||||
| return [ | return [ | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Visible', | label: 'Visible', | ||||
| property: [this, 'visible'], | property: [this, 'visible'], | ||||
| onChange: (e)=>{ | |||||
| this.setDirty?.({uiChangeEvent: e, refreshScene: true, refreshUi: true, change: 'visible'}) | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'button', | type: 'button', | ||||
| type: 'input', | type: 'input', | ||||
| label: 'Name', | label: 'Name', | ||||
| property: [this, 'name'], | property: [this, 'name'], | ||||
| onChange: (e: any)=>{ | |||||
| if (e.last) this.setDirty?.({refreshScene: true, frameFade: false, refreshUi: true}) | |||||
| onChange: (e)=>{ | |||||
| if (e.last) this.setDirty?.({uiChangeEvent: e, refreshScene: true, frameFade: false, refreshUi: true}) | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Casts Shadow', | label: 'Casts Shadow', | ||||
| hidden: () => !(this as any).isMesh, | |||||
| hidden: () => !this.isMesh, | |||||
| property: [this, 'castShadow'], | property: [this, 'castShadow'], | ||||
| onChange: (e)=>{ | |||||
| this.setDirty?.({uiChangeEvent: e, refreshScene: true, refreshUi: true, change: 'castShadow'}) | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Receive Shadow', | label: 'Receive Shadow', | ||||
| hidden: () => !(this as any).isMesh, | |||||
| hidden: () => !this.isMesh, | |||||
| property: [this, 'receiveShadow'], | property: [this, 'receiveShadow'], | ||||
| onChange: (e)=>{ | |||||
| this.setDirty?.({uiChangeEvent: e, refreshScene: true, refreshUi: true, change: 'receiveShadow'}) | |||||
| }, | |||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | type: 'checkbox', | ||||
| { | { | ||||
| label: 'Remove Material(s)', | label: 'Remove Material(s)', | ||||
| type: 'button', | type: 'button', | ||||
| hidden: ()=>!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultMaterial, | |||||
| hidden: ()=>!this.materials?.length || this.materials.length === 1 && (<IMaterial[]>[defaultMaterial, defaultLineMaterial, defaultUnlitLineMaterial]).includes(this.materials[0]), | |||||
| value: ()=>{ | |||||
| const mat = this.materials | |||||
| this.material = this.isLineSegments2 ? | |||||
| [defaultLineMaterial] : this.isLineSegments ? | |||||
| [defaultUnlitLineMaterial] : [defaultMaterial] | |||||
| return ()=> this.material = mat | |||||
| }, | |||||
| }, | |||||
| { | |||||
| label: 'New Line Material', | |||||
| type: 'button', | |||||
| hidden: ()=>!this.isLineSegments2 || !(!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultLineMaterial), | |||||
| value: ()=>{ | |||||
| const mat = this.materials | |||||
| this.material = [new LineMaterial2()] | |||||
| return ()=> this.material = mat | |||||
| }, | |||||
| }, | |||||
| { | |||||
| label: 'New Unlit Line Material', | |||||
| type: 'button', | |||||
| hidden: ()=>!this.isLineSegments || !(!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultUnlitLineMaterial), | |||||
| value: ()=>{ | value: ()=>{ | ||||
| const mat = this.materials | const mat = this.materials | ||||
| this.material = [defaultMaterial] | |||||
| this.material = [new UnlitLineMaterial()] | |||||
| return ()=> this.material = mat | return ()=> this.material = mat | ||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| label: 'New Physical Material', | label: 'New Physical Material', | ||||
| type: 'button', | type: 'button', | ||||
| hidden: ()=>!(!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultMaterial), | |||||
| hidden: ()=>!(!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultMaterial) || !!this.isLineSegments2 || !!this.isLineSegments, | |||||
| value: ()=>{ | value: ()=>{ | ||||
| const mat = this.materials | const mat = this.materials | ||||
| this.material = [new PhysicalMaterial()] | this.material = [new PhysicalMaterial()] | ||||
| { | { | ||||
| label: 'New Unlit Material', | label: 'New Unlit Material', | ||||
| type: 'button', | type: 'button', | ||||
| hidden: ()=>!(!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultMaterial), | |||||
| hidden: ()=>!(!this.materials?.length || this.materials.length === 1 && this.materials[0] === defaultMaterial) || !!this.isLineSegments2 || !!this.isLineSegments, | |||||
| value: ()=>{ | value: ()=>{ | ||||
| const mat = this.materials | const mat = this.materials | ||||
| this.material = [new UnlitMaterial()] | this.material = [new UnlitMaterial()] |
| // endregion | // endregion | ||||
| } | } | ||||
| import {LineGeometry2} from '../geometry/LineGeometry2' | |||||
| import {LineMaterial2} from '../material/LineMaterial2' | |||||
| import {IObject3D, IObject3DEventMap, IObject3DUserData} from '../IObject' | |||||
| import {Line2} from 'three/examples/jsm/lines/Line2' | |||||
| import {iObjectCommons} from './iObjectCommons' | |||||
| import {IMaterial} from '../IMaterial' | |||||
| export class MeshLine< | |||||
| TGeometry extends LineGeometry2 = LineGeometry2, | |||||
| TMaterial extends LineMaterial2 = LineMaterial2, | |||||
| TE extends IObject3DEventMap = IObject3DEventMap | |||||
| > extends Line2<TGeometry, TMaterial, TE> implements IObject3D<TE> { | |||||
| assetType = 'model' as const | |||||
| setDirty = iObjectCommons.setDirty | |||||
| refreshUi = iObjectCommons.refreshUi | |||||
| public readonly isMeshLine = true | |||||
| declare material: TMaterial | |||||
| declare readonly materials: IMaterial[] | |||||
| declare geometry: TGeometry | |||||
| /** | |||||
| * @deprecated use `this` instead | |||||
| */ | |||||
| get modelObject(): this { | |||||
| return this | |||||
| } | |||||
| constructor(geometry?: TGeometry, material?: TMaterial) { | |||||
| super(geometry, material) | |||||
| iObjectCommons.upgradeObject3D.call(this) | |||||
| } | |||||
| declare userData: IObject3DUserData | |||||
| // region inherited type fixes | |||||
| // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 | |||||
| traverse: (callback: (object: IObject3D) => void) => void | |||||
| traverseVisible: (callback: (object: IObject3D) => void) => void | |||||
| traverseAncestors: (callback: (object: IObject3D) => void) => void | |||||
| getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | |||||
| getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | |||||
| getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined | |||||
| copy: (source: MeshLine | IObject3D, recursive?: boolean, ...args: any[]) => this | |||||
| clone: (recursive?: boolean) => this | |||||
| remove: (...object: IObject3D[]) => this | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | |||||
| dispose: (removeFromParent?: boolean) => void | |||||
| // endregion | |||||
| } | |||||
| import {LineSegmentsGeometry2} from '../geometry/LineSegmentsGeometry2' | |||||
| import {LineMaterial2} from '../material/LineMaterial2' | |||||
| import {IObject3D, IObject3DEventMap, IObject3DUserData} from '../IObject' | |||||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2' | |||||
| import {iObjectCommons} from './iObjectCommons' | |||||
| import {IMaterial} from '../IMaterial' | |||||
| import {MeshLine} from './MeshLine' | |||||
| export class MeshLineSegments< | |||||
| TGeometry extends LineSegmentsGeometry2 = LineSegmentsGeometry2, | |||||
| TMaterial extends LineMaterial2= LineMaterial2, | |||||
| TE extends IObject3DEventMap = IObject3DEventMap | |||||
| > extends LineSegments2<TGeometry, TMaterial, TE> implements IObject3D<TE> { | |||||
| assetType = 'model' as const | |||||
| setDirty = iObjectCommons.setDirty | |||||
| refreshUi = iObjectCommons.refreshUi | |||||
| public readonly isMeshLineSegments = true | |||||
| declare material: TMaterial | |||||
| declare readonly materials: IMaterial[] | |||||
| declare geometry: TGeometry | |||||
| /** | |||||
| * @deprecated use `this` instead | |||||
| */ | |||||
| get modelObject(): this { | |||||
| return this | |||||
| } | |||||
| constructor(geometry?: TGeometry, material?: TMaterial) { | |||||
| super(geometry, material) | |||||
| iObjectCommons.upgradeObject3D.call(this) | |||||
| } | |||||
| declare userData: IObject3DUserData | |||||
| // region inherited type fixes | |||||
| // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 | |||||
| traverse: (callback: (object: IObject3D) => void) => void | |||||
| traverseVisible: (callback: (object: IObject3D) => void) => void | |||||
| traverseAncestors: (callback: (object: IObject3D) => void) => void | |||||
| getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | |||||
| getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | |||||
| getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined | |||||
| copy: (source: MeshLine | IObject3D, recursive?: boolean, ...args: any[]) => this | |||||
| clone: (recursive?: boolean) => this | |||||
| remove: (...object: IObject3D[]) => this | |||||
| declare parent: IObject3D | null | |||||
| declare children: IObject3D[] | |||||
| dispose: (removeFromParent?: boolean) => void | |||||
| // endregion | |||||
| } |
| export type {GLTFLoaderPlugin, GLTF, GLTFReference, GLTFReferenceType} from 'three/examples/jsm/loaders/GLTFLoader' | export type {GLTFLoaderPlugin, GLTF, GLTFReference, GLTFReferenceType} from 'three/examples/jsm/loaders/GLTFLoader' | ||||
| export {GLTFParser, GLTFBinaryExtension, GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader' | export {GLTFParser, GLTFBinaryExtension, GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader' | ||||
| export {GLTFExporter} from 'three/examples/jsm/exporters/GLTFExporter' | export {GLTFExporter} from 'three/examples/jsm/exporters/GLTFExporter' | ||||
| export {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | |||||
| export {Line2} from 'three/examples/jsm/lines/Line2.js' | |||||
| export {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | |||||
| export {LineGeometry} from 'three/examples/jsm/lines/LineGeometry.js' | |||||
| export {LineMaterial} from 'three/examples/jsm/lines/LineMaterial.js' | |||||
| export {Wireframe} from 'three/examples/jsm/lines/Wireframe.js' | |||||
| export {WireframeGeometry2} from 'three/examples/jsm/lines/WireframeGeometry2.js' | |||||
| export {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' | export {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' | ||||
| export * from 'three/examples/jsm/utils/BufferGeometryUtils.js' | export * from 'three/examples/jsm/utils/BufferGeometryUtils.js' | ||||
| export type {Event, EventListener, EventListener2} from 'three' | |||||
| export type {Event, EventListener, EventListener2, Event2} from 'three' | |||||
| export type {MeshPhysicalMaterialParameters, MeshBasicMaterialParameters, MaterialParameters} from 'three' | export type {MeshPhysicalMaterialParameters, MeshBasicMaterialParameters, MaterialParameters} from 'three' |
| throttleUpdate = 60 // throttle to 60 updates per second (implemented in OrbitControls.js.update() method) | throttleUpdate = 60 // throttle to 60 updates per second (implemented in OrbitControls.js.update() method) | ||||
| // todo add to three-ts-types | |||||
| stopDamping!: () => void | stopDamping!: () => void | ||||
| } | } |
| import {Box2, Box3, BufferAttribute, Camera, InterleavedBufferAttribute, Mesh, Object3D, Vector3} from 'three' | |||||
| import { | |||||
| Box2, | |||||
| Box3, | |||||
| BufferAttribute, | |||||
| BufferGeometry, | |||||
| Camera, | |||||
| InterleavedBufferAttribute, | |||||
| Mesh, | |||||
| Object3D, | |||||
| Vector3, | |||||
| } from 'three' | |||||
| import type {IObject3D} from '../../core' | import type {IObject3D} from '../../core' | ||||
| export class Box3B extends Box3 { | export class Box3B extends Box3 { | ||||
| if (!object.visible && ignoreInvisible) return this | if (!object.visible && ignoreInvisible) return this | ||||
| if (ignoreObject && ignoreObject(object)) return this | if (ignoreObject && ignoreObject(object)) return this | ||||
| // copied the whole function from three.js to pass in ignoreInvisible | |||||
| // copied the whole function from three.js to pass in ignoreInvisible, support precise | |||||
| // Computes the world-axis-aligned bounding box of an object (including its children), | // Computes the world-axis-aligned bounding box of an object (including its children), | ||||
| // accounting for both the object's, and children's, world transforms | // accounting for both the object's, and children's, world transforms | ||||
| // InstancedMesh has boundingBox = null, so it can be computed | // InstancedMesh has boundingBox = null, so it can be computed | ||||
| if ((object as IObject3D).boundingBox !== undefined) { | if ((object as IObject3D).boundingBox !== undefined) { | ||||
| if ((object as IObject3D).boundingBox === null && typeof (object as IObject3D).computeBoundingBox === 'function') { | |||||
| if (/* (object as IObject3D).boundingBox === null && */typeof (object as IObject3D).computeBoundingBox === 'function') { | |||||
| (object as IObject3D).computeBoundingBox!() | (object as IObject3D).computeBoundingBox!() | ||||
| const geometry = (object as Mesh).geometry | const geometry = (object as Mesh).geometry | ||||
| if (geometry !== undefined) { | if (geometry !== undefined) { | ||||
| if (precise && geometry.attributes != undefined && geometry.attributes.position !== undefined) { | |||||
| // checking for computeBoundingBox to support when overridden in subclass. | |||||
| if (precise && geometry.attributes != undefined && geometry.attributes.position !== undefined && Object.getPrototypeOf(geometry).computeBoundingBox === BufferGeometry.prototype.computeBoundingBox) { | |||||
| // in case of precise, apply the matrix to positions before expanding the box | |||||
| // todo add precise option to computeBoundingBox | |||||
| const position = geometry.attributes.position as any as BufferAttribute | InterleavedBufferAttribute | const position = geometry.attributes.position as any as BufferAttribute | InterleavedBufferAttribute | ||||
| for (let i = 0, l = position.count; i < l; i++) { | for (let i = 0, l = position.count; i < l; i++) { | ||||
| this._vector.fromBufferAttribute(position, i).applyMatrix4(object.matrixWorld) | this._vector.fromBufferAttribute(position, i).applyMatrix4(object.matrixWorld) | ||||
| } else { | } else { | ||||
| if (geometry.boundingBox === null) | if (geometry.boundingBox === null) | ||||
| geometry.computeBoundingBox() | geometry.computeBoundingBox() | ||||
| Box3B._box.copy(geometry.boundingBox!) | |||||
| Box3B._box.applyMatrix4(object.matrixWorld) | |||||
| if (geometry.boundingBox) { | |||||
| Box3B._box.copy(geometry.boundingBox) | |||||
| Box3B._box.applyMatrix4(object.matrixWorld) | |||||
| this.union(Box3B._box) | |||||
| this.union(Box3B._box) | |||||
| } else { | |||||
| console.warn('Box3B - Unable to compute bounds for', object, geometry) | |||||
| } | |||||
| } | } | ||||
| } | } |
| color: '#ff2222' as any, transparent: true, opacity: 0.9, | color: '#ff2222' as any, transparent: true, opacity: 0.9, | ||||
| linewidth: 5, // in pixels | linewidth: 5, // in pixels | ||||
| resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | resolution: new Vector2(1024, 1024), // to be set by renderer, eventually | ||||
| worldUnits: false, | |||||
| dashed: false, | dashed: false, | ||||
| toneMapped: false, | toneMapped: false, | ||||
| }) | }) | ||||
| matLine.userData.renderToGBuffer = false | |||||
| matLine.userData.renderToDepth = false | |||||
| this.lineMaterial = matLine | this.lineMaterial = matLine | ||||
| const ls = new LineSegmentsGeometry() | const ls = new LineSegmentsGeometry() | ||||
| if (selected) { | if (selected) { | ||||
| const bbox = new Box3B().expandByObject(selected, false) | const bbox = new Box3B().expandByObject(selected, false) | ||||
| // const scale = bbox.getBoundingSphere(new Sphere()).radius | // const scale = bbox.getBoundingSphere(new Sphere()).radius | ||||
| bbox.getSize(this.scale).multiplyScalar(this.boundingScaleMultiplier).clampScalar(0.1, 100) | |||||
| bbox.getSize(this.scale).multiplyScalar(this.boundingScaleMultiplier).clampScalar(0.1, 1e8) | |||||
| this.setVisible(true) | this.setVisible(true) | ||||
| } | } | ||||
| } | } |
| depthTest: false, | depthTest: false, | ||||
| depthWrite: false, | depthWrite: false, | ||||
| }) | }) | ||||
| material.userData.renderToGBuffer = false | |||||
| material.userData.renderToDepth = false | |||||
| const {vertices, colors, pointMap} = generateVertices() | const {vertices, colors, pointMap} = generateVertices() | ||||
| depthTest: false, | depthTest: false, | ||||
| depthWrite: false, | depthWrite: false, | ||||
| }) | }) | ||||
| this.material.userData.renderToGBuffer = false | |||||
| this.material.userData.renderToDepth = false | |||||
| this.lightPlane = new Line2(geometry, this.material) | this.lightPlane = new Line2(geometry, this.material) | ||||
| this.add(this.lightPlane) | this.add(this.lightPlane) |
| depthTest: false, | depthTest: false, | ||||
| depthWrite: false, | depthWrite: false, | ||||
| }) | }) | ||||
| this.material.userData.renderToGBuffer = false | |||||
| this.material.userData.renderToDepth = false | |||||
| this.lightSphere = new Wireframe(geometry, this.material) | this.lightSphere = new Wireframe(geometry, this.material) | ||||
| this.lightSphere.computeLineDistances() | this.lightSphere.computeLineDistances() |
| import {ColorRepresentation, Object3D, SpotLight, Vector3} from 'three' | import {ColorRepresentation, Object3D, SpotLight, Vector3} from 'three' | ||||
| import {LineGeometry} from 'three/examples/jsm/lines/LineGeometry.js' | |||||
| import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | import {LineSegments2} from 'three/examples/jsm/lines/LineSegments2.js' | ||||
| import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | import {LineSegmentsGeometry} from 'three/examples/jsm/lines/LineSegmentsGeometry.js' | ||||
| import {onChange} from 'ts-browser-helpers' | import {onChange} from 'ts-browser-helpers' | ||||
| if (size === undefined) size = 0.5 | if (size === undefined) size = 0.5 | ||||
| let geometry = new LineSegmentsGeometry() | |||||
| const geometry = new LineSegmentsGeometry() | |||||
| const positions = [ | const positions = [ | ||||
| 0, 0, 0, 0, 0, 1, | 0, 0, 0, 0, 0, 1, | ||||
| 0, 0, 0, 1, 0, 1, | 0, 0, 0, 1, 0, 1, | ||||
| depthTest: false, | depthTest: false, | ||||
| depthWrite: false, | depthWrite: false, | ||||
| }) | }) | ||||
| this.material.userData.renderToGBuffer = false | |||||
| this.material.userData.renderToDepth = false | |||||
| this.cone = new LineSegments2(geometry, this.material) | this.cone = new LineSegments2(geometry, this.material) | ||||
| this.add(this.cone) | this.add(this.cone) | ||||
| geometry = new LineGeometry() | |||||
| geometry.setPositions([0, 0, 0, 0, 0, 1]) | |||||
| this.update() | this.update() | ||||
| this.traverse(o => { | this.traverse(o => { |