| import {_testFinish, DropzonePlugin, LoadingScreenPlugin, ThreeViewer} from 'threepipe' | |||||
| import { | |||||
| _testFinish, | |||||
| DropzonePlugin, | |||||
| LoadingScreenPlugin, | |||||
| PickingPlugin, | |||||
| Rhino3dmLoadPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| async function init() { | async function init() { | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| dropzone: { // this can also be set to true and configured by getting a reference to the DropzonePlugin | dropzone: { // this can also be set to true and configured by getting a reference to the DropzonePlugin | ||||
| allowedExtensions: ['gltf', 'glb', 'hdr', 'bin', 'png', 'jpeg', 'webp', 'jpg', 'exr', 'fbx', 'obj'], // only allow these file types. If undefined, all files are allowed. | |||||
| allowedExtensions: ['gltf', 'glb', 'hdr', 'bin', 'png', 'jpeg', 'webp', 'jpg', 'exr', 'fbx', 'obj', '3dm'], // only allow these file types. If undefined, all files are allowed. | |||||
| addOptions: { | addOptions: { | ||||
| disposeSceneObjects: true, // auto dispose of old scene objects | disposeSceneObjects: true, // auto dispose of old scene objects | ||||
| autoSetEnvironment: true, // when hdr is dropped | autoSetEnvironment: true, // when hdr is dropped | ||||
| importConfig: true, // import config from file | importConfig: true, // import config from file | ||||
| }, | }, | ||||
| }, | }, | ||||
| plugins: [LoadingScreenPlugin], | |||||
| plugins: [LoadingScreenPlugin, PickingPlugin, Rhino3dmLoadPlugin], | |||||
| }) | }) | ||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | ||||
| }) | }) | ||||
| const ui = viewer.addPluginSync(TweakpaneUiPlugin, true) | const ui = viewer.addPluginSync(TweakpaneUiPlugin, true) | ||||
| ui.appendChild(dropzone.uiConfig) | |||||
| // ui.appendChild(dropzone.uiConfig) | |||||
| ui.setupPluginUi(DropzonePlugin) | |||||
| ui.setupPluginUi(PickingPlugin) | |||||
| } | } | ||||
| import resolve from '@rollup/plugin-node-resolve'; | import resolve from '@rollup/plugin-node-resolve'; | ||||
| import typescript from '@rollup/plugin-typescript'; | import typescript from '@rollup/plugin-typescript'; | ||||
| import license from 'rollup-plugin-license' | import license from 'rollup-plugin-license' | ||||
| import packageJson from './package.json' assert {type: 'json'}; | |||||
| import packageJson from './package.json' with {type: 'json'}; | |||||
| import path from 'path' | import path from 'path' | ||||
| import {fileURLToPath} from 'url'; | import {fileURLToPath} from 'url'; | ||||
| import postcss from 'rollup-plugin-postcss' | import postcss from 'rollup-plugin-postcss' | ||||
| import replace from '@rollup/plugin-replace' | import replace from '@rollup/plugin-replace' | ||||
| import terser from "@rollup/plugin-terser"; | |||||
| import terser from '@rollup/plugin-terser'; | |||||
| const __filename = fileURLToPath(import.meta.url); | const __filename = fileURLToPath(import.meta.url); | ||||
| const __dirname = path.dirname(__filename); | const __dirname = path.dirname(__filename); | ||||
| const settings = { | const settings = { | ||||
| globals: { | globals: { | ||||
| "three": "threepipe", // just incase someone uses three | |||||
| "threepipe": "threepipe", | |||||
| "@threepipe/plugin-tweakpane": "@threepipe/plugin-tweakpane" | |||||
| 'three': 'threepipe', // just incase someone uses three | |||||
| 'threepipe': 'threepipe', | |||||
| '@threepipe/plugin-tweakpane': '@threepipe/plugin-tweakpane', | |||||
| }, | }, | ||||
| sourcemap: true | |||||
| sourcemap: true, | |||||
| } | } | ||||
| export default { | export default { | ||||
| name: name, | name: name, | ||||
| format: 'es', | format: 'es', | ||||
| plugins: [ | plugins: [ | ||||
| isProduction && terser() | |||||
| ] | |||||
| isProduction && terser(), | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| file: './dist/index.js', | file: './dist/index.js', | ||||
| name: name, | name: name, | ||||
| format: 'umd', | format: 'umd', | ||||
| plugins: [ | plugins: [ | ||||
| isProduction && terser() | |||||
| ] | |||||
| } | |||||
| isProduction && terser(), | |||||
| ], | |||||
| }, | |||||
| ], | ], | ||||
| external: Object.keys(settings.globals), | external: Object.keys(settings.globals), | ||||
| plugins: [ | plugins: [ | ||||
| }), | }), | ||||
| postcss({ | postcss({ | ||||
| modules: false, | modules: false, | ||||
| autoModules: true, // todo; issues with typescript import css, because inject is false | |||||
| autoModules: true, // todo; issues with typescript import css, because inject is false | |||||
| inject: false, | inject: false, | ||||
| minimize: isProduction, | minimize: isProduction, | ||||
| // Or with custom options for `postcss-modules` | // Or with custom options for `postcss-modules` | ||||
| include: 'node_modules/**', | include: 'node_modules/**', | ||||
| extensions: ['.js'], | extensions: ['.js'], | ||||
| ignoreGlobal: false, | ignoreGlobal: false, | ||||
| sourceMap: false | |||||
| sourceMap: false, | |||||
| }), | }), | ||||
| license({ | license({ | ||||
| banner: ` | banner: ` | ||||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | output: path.join(__dirname, 'dist', 'dependencies.txt'), | ||||
| includePrivate: true, // Default is false. | includePrivate: true, // Default is false. | ||||
| }, | }, | ||||
| }) | |||||
| ] | |||||
| }), | |||||
| ], | |||||
| } | } |
| import {Color, Material, Mesh, Vector3} from 'three'; | import {Color, Material, Mesh, Vector3} from 'three'; | ||||
| import {HalfedgeDS} from '../../three-mesh-halfedge'; | import {HalfedgeDS} from '../../three-mesh-halfedge'; | ||||
| import {acceleratedRaycast, CENTER, MeshBVH, MeshBVHOptions} from 'three-mesh-bvh'; | |||||
| import { | |||||
| acceleratedRaycast, | |||||
| CENTER, | |||||
| computeBoundsTree, | |||||
| disposeBoundsTree, | |||||
| MeshBVH, | |||||
| MeshBVHOptions | |||||
| } from 'three-mesh-bvh'; | |||||
| import {computeMorphedGeometry, disposeMesh} from '../utils/buffergeometry'; | import {computeMorphedGeometry, disposeMesh} from '../utils/buffergeometry'; | ||||
| type ColorMaterial = Material & {color: Color}; | type ColorMaterial = Material & {color: Color}; | ||||
| url: string; | url: string; | ||||
| } | } | ||||
| declare module 'three/src/core/BufferGeometry' { | |||||
| export interface BufferGeometry { | |||||
| boundsTree?: MeshBVH; | |||||
| computeBoundsTree: typeof computeBoundsTree; | |||||
| disposeBoundsTree: typeof disposeBoundsTree; | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * Mesh object that can be rendered as SVG. | * Mesh object that can be rendered as SVG. | ||||
| * Wrapper class around three mesh object that duplicates geometry if needed (i.e. | * Wrapper class around three mesh object that duplicates geometry if needed (i.e. |
| * @param autoScaleRadius - optional (taken from userData.autoScaleRadius by default) | * @param autoScaleRadius - optional (taken from userData.autoScaleRadius by default) | ||||
| * @param isCentered - optional (taken from userData.isCentered by default) | * @param isCentered - optional (taken from userData.isCentered by default) | ||||
| * @param setDirty - true by default | * @param setDirty - true by default | ||||
| * @param undo - undo any previous autoScale operation | |||||
| */ | */ | ||||
| autoScale?(autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean): this | |||||
| autoScale?(autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean, undo?: boolean): this | |||||
| /** | /** | ||||
| * | * | ||||
| * @param setDirty - calls {@link setDirty} @default true | * @param setDirty - calls {@link setDirty} @default true | ||||
| * @param undo - undo any previous autoCenter operation | |||||
| */ | */ | ||||
| autoCenter?(setDirty?: boolean): this | |||||
| autoCenter?(setDirty?: boolean, undo?: boolean): this | |||||
| /** | /** | ||||
| * @deprecated use object directly | * @deprecated use object directly |
| children: [ | children: [ | ||||
| { | { | ||||
| type: 'slider', | type: 'slider', | ||||
| bounds: [-0.2, 0.2], | |||||
| bounds: [-1, 1], | |||||
| stepSize: 0.001, | stepSize: 0.001, | ||||
| property: [material, 'bumpScale'], | property: [material, 'bumpScale'], | ||||
| hidden: ()=>!material.bumpMap, | hidden: ()=>!material.bumpMap, |
| type: 'checkbox', | type: 'checkbox', | ||||
| label: 'Auto LookAt Target', | label: 'Auto LookAt Target', | ||||
| getValue: ()=>this.userData.autoLookAtTarget ?? false, | getValue: ()=>this.userData.autoLookAtTarget ?? false, | ||||
| setValue: (v)=>{ | |||||
| setValue: (v: boolean)=>{ | |||||
| this.userData.autoLookAtTarget = v | this.userData.autoLookAtTarget = v | ||||
| config.uiRefresh?.(true, 'postFrame') | config.uiRefresh?.(true, 'postFrame') | ||||
| }, | }, | ||||
| ] | ] | ||||
| } | } | ||||
| export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjectConfig { | export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjectConfig { | ||||
| if (!this) return {} | if (!this) return {} | ||||
| if (this.uiConfig) return this.uiConfig | if (this.uiConfig) return this.uiConfig | ||||
| label: 'Auto Scale', | label: 'Auto Scale', | ||||
| hidden: ()=>!this.autoScale, | hidden: ()=>!this.autoScale, | ||||
| prompt: ['Auto Scale Radius: Object will be scaled to the given radius', this.userData.autoScaleRadius || '2', true], | prompt: ['Auto Scale Radius: Object will be scaled to the given radius', this.userData.autoScaleRadius || '2', true], | ||||
| value: ()=>{ | |||||
| value: async()=>{ | |||||
| const def = (this.userData.autoScaleRadius || 2) + '' | const def = (this.userData.autoScaleRadius || 2) + '' | ||||
| const res = prompt('Auto Scale Radius: Object will be scaled to the given radius', def) | const res = prompt('Auto Scale Radius: Object will be scaled to the given radius', def) | ||||
| if (res === null) return | if (res === null) return | ||||
| const rad = parseFloat(res || def) | const rad = parseFloat(res || def) | ||||
| if (Math.abs(rad) > 0) this.autoScale?.(rad) | |||||
| if (Math.abs(rad) > 0) { | |||||
| this.autoScale?.(rad) | |||||
| return ()=>this.autoScale?.(rad, undefined, undefined, true) | |||||
| } | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { | ||||
| const res = confirm('Auto Center: Object will be centered, are you sure you want to proceed?') | const res = confirm('Auto Center: Object will be centered, are you sure you want to proceed?') | ||||
| if (!res) return | if (!res) return | ||||
| this.autoCenter?.(true) | this.autoCenter?.(true) | ||||
| return ()=>this.autoCenter?.(true, true) | |||||
| }, | }, | ||||
| }, | }, | ||||
| { | { |
| upgradeObject3D: upgradeObject3D, | upgradeObject3D: upgradeObject3D, | ||||
| makeUiConfig: makeIObject3DUiConfig, | makeUiConfig: makeIObject3DUiConfig, | ||||
| autoCenter: function<T extends IObject3D>(this: T, setDirty = true): T { | |||||
| const bb = new Box3B().expandByObject(this, true, true) | |||||
| const center = bb.getCenter(new Vector3()) | |||||
| this.position.sub(center) | |||||
| autoCenter: function<T extends IObject3D>(this: T, setDirty = true, undo = false): T { | |||||
| if (undo) { | |||||
| if (!this.userData.autoCentered || !this.userData._lastCenter) return this | |||||
| this.position.add(this.userData._lastCenter) | |||||
| delete this.userData.autoCentered | |||||
| delete this.userData.isCentered | |||||
| delete this.userData._lastCenter | |||||
| } else { | |||||
| const bb = new Box3B().expandByObject(this, true, true) | |||||
| const center = bb.getCenter(new Vector3()) | |||||
| this.userData._lastCenter = center/* .clone()*/ | |||||
| this.position.sub(center) | |||||
| this.userData.autoCentered = true | |||||
| this.userData.isCentered = true | |||||
| } | |||||
| this.updateMatrix() | this.updateMatrix() | ||||
| this.userData.autoCentered = true | |||||
| this.userData.isCentered = true | |||||
| if (setDirty) this.setDirty({change: 'autoCenter'}) | |||||
| if (setDirty) this.setDirty({change: 'autoCenter', undo}) | |||||
| return this | return this | ||||
| }, | }, | ||||
| autoScale: function<T extends IObject3D>(this: T, autoScaleRadius?: number, isCentered?: boolean, setDirty = true): T { | |||||
| const bbox = new Box3B().expandByObject(this, true, true) | |||||
| const radius = bbox.getSize(new Vector3()).length() * 0.5 | |||||
| if (autoScaleRadius === undefined) { | |||||
| autoScaleRadius = this.userData.autoScaleRadius || 1 | |||||
| autoScale: function<T extends IObject3D>(this: T, autoScaleRadius?: number, isCentered?: boolean, setDirty = true, undo = false): T { | |||||
| let scale = 1 | |||||
| if (undo) { // Note - undo only works for quick undo, not for multiple times | |||||
| if (!this.userData.autoScaled || !this.userData._lastScaleRadius) return this | |||||
| const rad = this.userData.autoScaleRadius || autoScaleRadius || 1 | |||||
| scale = this.userData._lastScaleRadius / rad | |||||
| if (!isFinite(scale)) return this // NaN when radius is 0 | |||||
| this.userData.autoScaled = true | |||||
| this.userData.autoScaleRadius = autoScaleRadius | |||||
| delete this.userData._lastScaleRadius | |||||
| } else { | |||||
| const bbox = new Box3B().expandByObject(this, true, true) | |||||
| const radius = bbox.getSize(new Vector3()).length() * 0.5 | |||||
| if (autoScaleRadius === undefined) { | |||||
| autoScaleRadius = this.userData.autoScaleRadius || 1 | |||||
| } | |||||
| scale = autoScaleRadius / radius | |||||
| if (!isFinite(scale)) return this // NaN when radius is 0 | |||||
| this.userData.autoScaled = true | |||||
| this.userData.autoScaleRadius = autoScaleRadius | |||||
| this.userData._lastScaleRadius = radius | |||||
| } | } | ||||
| const scale = autoScaleRadius / radius | |||||
| // this.scale.multiplyScalar(20 / radius) | |||||
| if (!isFinite(scale)) return this // NaN when radius is 0 | |||||
| if (this.userData.pseudoCentered) { | if (this.userData.pseudoCentered) { | ||||
| this.children.forEach(child => { | this.children.forEach(child => { | ||||
| } else | } else | ||||
| this.scale.multiplyScalar(scale) | this.scale.multiplyScalar(scale) | ||||
| if (isCentered || this.userData.isCentered) this.position.multiplyScalar(scale) | if (isCentered || this.userData.isCentered) this.position.multiplyScalar(scale) | ||||
| this.traverse((obj) => { | this.traverse((obj) => { | ||||
| const l = obj as any | const l = obj as any | ||||
| if (l.isLight && l.shadow?.camera?.right) { | if (l.isLight && l.shadow?.camera?.right) { | ||||
| obj.setDirty() | obj.setDirty() | ||||
| } | } | ||||
| }) | }) | ||||
| this.userData.autoScaled = true | |||||
| this.userData.autoScaleRadius = autoScaleRadius | |||||
| if (setDirty) this.setDirty({change: 'autoScale'}) | |||||
| if (setDirty) this.setDirty({change: 'autoScale', undo}) | |||||
| return this | return this | ||||
| }, | }, | ||||