| @@ -1,4 +1,11 @@ | |||
| import {_testFinish, DropzonePlugin, LoadingScreenPlugin, ThreeViewer} from 'threepipe' | |||
| import { | |||
| _testFinish, | |||
| DropzonePlugin, | |||
| LoadingScreenPlugin, | |||
| PickingPlugin, | |||
| Rhino3dmLoadPlugin, | |||
| ThreeViewer, | |||
| } from 'threepipe' | |||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||
| async function init() { | |||
| @@ -6,7 +13,7 @@ async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| 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: { | |||
| disposeSceneObjects: true, // auto dispose of old scene objects | |||
| autoSetEnvironment: true, // when hdr is dropped | |||
| @@ -18,7 +25,7 @@ async function init() { | |||
| 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') | |||
| @@ -32,7 +39,9 @@ async function init() { | |||
| }) | |||
| const ui = viewer.addPluginSync(TweakpaneUiPlugin, true) | |||
| ui.appendChild(dropzone.uiConfig) | |||
| // ui.appendChild(dropzone.uiConfig) | |||
| ui.setupPluginUi(DropzonePlugin) | |||
| ui.setupPluginUi(PickingPlugin) | |||
| } | |||
| @@ -4,12 +4,12 @@ import json from '@rollup/plugin-json'; | |||
| import resolve from '@rollup/plugin-node-resolve'; | |||
| import typescript from '@rollup/plugin-typescript'; | |||
| 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 {fileURLToPath} from 'url'; | |||
| import postcss from 'rollup-plugin-postcss' | |||
| 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 __dirname = path.dirname(__filename); | |||
| @@ -20,11 +20,11 @@ const isProduction = process.env.NODE_ENV === 'production' | |||
| const settings = { | |||
| 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 { | |||
| @@ -45,8 +45,8 @@ export default { | |||
| name: name, | |||
| format: 'es', | |||
| plugins: [ | |||
| isProduction && terser() | |||
| ] | |||
| isProduction && terser(), | |||
| ], | |||
| }, | |||
| { | |||
| file: './dist/index.js', | |||
| @@ -54,9 +54,9 @@ export default { | |||
| name: name, | |||
| format: 'umd', | |||
| plugins: [ | |||
| isProduction && terser() | |||
| ] | |||
| } | |||
| isProduction && terser(), | |||
| ], | |||
| }, | |||
| ], | |||
| external: Object.keys(settings.globals), | |||
| plugins: [ | |||
| @@ -69,7 +69,7 @@ export default { | |||
| }), | |||
| postcss({ | |||
| 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, | |||
| minimize: isProduction, | |||
| // Or with custom options for `postcss-modules` | |||
| @@ -82,7 +82,7 @@ export default { | |||
| include: 'node_modules/**', | |||
| extensions: ['.js'], | |||
| ignoreGlobal: false, | |||
| sourceMap: false | |||
| sourceMap: false, | |||
| }), | |||
| license({ | |||
| banner: ` | |||
| @@ -96,6 +96,6 @@ export default { | |||
| output: path.join(__dirname, 'dist', 'dependencies.txt'), | |||
| includePrivate: true, // Default is false. | |||
| }, | |||
| }) | |||
| ] | |||
| }), | |||
| ], | |||
| } | |||
| @@ -12,7 +12,14 @@ | |||
| import {Color, Material, Mesh, Vector3} from 'three'; | |||
| 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'; | |||
| type ColorMaterial = Material & {color: Color}; | |||
| @@ -36,6 +43,15 @@ export interface SVGTexture { | |||
| 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. | |||
| * Wrapper class around three mesh object that duplicates geometry if needed (i.e. | |||
| @@ -206,14 +206,16 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent | |||
| * @param autoScaleRadius - optional (taken from userData.autoScaleRadius by default) | |||
| * @param isCentered - optional (taken from userData.isCentered 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 undo - undo any previous autoCenter operation | |||
| */ | |||
| autoCenter?(setDirty?: boolean): this | |||
| autoCenter?(setDirty?: boolean, undo?: boolean): this | |||
| /** | |||
| * @deprecated use object directly | |||
| @@ -305,7 +305,7 @@ export const iMaterialUI = { | |||
| children: [ | |||
| { | |||
| type: 'slider', | |||
| bounds: [-0.2, 0.2], | |||
| bounds: [-1, 1], | |||
| stepSize: 0.001, | |||
| property: [material, 'bumpScale'], | |||
| hidden: ()=>!material.bumpMap, | |||
| @@ -35,7 +35,7 @@ export function makeICameraCommonUiConfig(this: ICamera, config: UiObjectConfig) | |||
| type: 'checkbox', | |||
| label: 'Auto LookAt Target', | |||
| getValue: ()=>this.userData.autoLookAtTarget ?? false, | |||
| setValue: (v)=>{ | |||
| setValue: (v: boolean)=>{ | |||
| this.userData.autoLookAtTarget = v | |||
| config.uiRefresh?.(true, 'postFrame') | |||
| }, | |||
| @@ -43,7 +43,6 @@ export function makeICameraCommonUiConfig(this: ICamera, config: UiObjectConfig) | |||
| ] | |||
| } | |||
| export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjectConfig { | |||
| if (!this) return {} | |||
| if (this.uiConfig) return this.uiConfig | |||
| @@ -129,12 +128,15 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| label: 'Auto Scale', | |||
| hidden: ()=>!this.autoScale, | |||
| 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 res = prompt('Auto Scale Radius: Object will be scaled to the given radius', def) | |||
| if (res === null) return | |||
| 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) | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| @@ -144,6 +146,7 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| const res = confirm('Auto Center: Object will be centered, are you sure you want to proceed?') | |||
| if (!res) return | |||
| this.autoCenter?.(true) | |||
| return ()=>this.autoCenter?.(true, true) | |||
| }, | |||
| }, | |||
| { | |||
| @@ -20,25 +20,47 @@ export const iObjectCommons = { | |||
| upgradeObject3D: upgradeObject3D, | |||
| 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.userData.autoCentered = true | |||
| this.userData.isCentered = true | |||
| if (setDirty) this.setDirty({change: 'autoCenter'}) | |||
| if (setDirty) this.setDirty({change: 'autoCenter', undo}) | |||
| 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) { | |||
| this.children.forEach(child => { | |||
| @@ -47,6 +69,7 @@ export const iObjectCommons = { | |||
| } else | |||
| this.scale.multiplyScalar(scale) | |||
| if (isCentered || this.userData.isCentered) this.position.multiplyScalar(scale) | |||
| this.traverse((obj) => { | |||
| const l = obj as any | |||
| if (l.isLight && l.shadow?.camera?.right) { | |||
| @@ -64,9 +87,9 @@ export const iObjectCommons = { | |||
| 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 | |||
| }, | |||