| <html lang="en"> | <html lang="en"> | ||||
| <head> | <head> | ||||
| <meta charset="UTF-8"> | <meta charset="UTF-8"> | ||||
| <title>Extra importer plugins</title> | |||||
| <title>Geometry Generator Plugin</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
| <!-- Import maps polyfill --> | <!-- Import maps polyfill --> | ||||
| <!-- Remove this when import maps will be widely supported --> | <!-- Remove this when import maps will be widely supported --> |
| import {_testFinish, CameraViewPlugin, PickingPlugin, ThreeViewer} from 'threepipe' | |||||
| import {_testFinish, CameraViewPlugin, Object3DGeneratorPlugin, PickingPlugin, ThreeViewer} from 'threepipe' | |||||
| import {GeometryGeneratorPlugin} from '@threepipe/plugin-geometry-generator' | import {GeometryGeneratorPlugin} from '@threepipe/plugin-geometry-generator' | ||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: true, | msaa: true, | ||||
| plugins: [PickingPlugin, CameraViewPlugin], | |||||
| plugins: [PickingPlugin, CameraViewPlugin, Object3DGeneratorPlugin], | |||||
| }) | }) | ||||
| const generator = viewer.addPluginSync(GeometryGeneratorPlugin) | const generator = viewer.addPluginSync(GeometryGeneratorPlugin) | ||||
| 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') | ||||
| console.log(generator.generators) | console.log(generator.generators) | ||||
| const sphere = generator.generateObject('sphere', {radius: 0.5, widthSegments: 32, heightSegments: 32}) | const sphere = generator.generateObject('sphere', {radius: 0.5, widthSegments: 32, heightSegments: 32}) | ||||
| viewer.scene.addObject(sphere) | viewer.scene.addObject(sphere) | ||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | ||||
| ui.setupPluginUi(GeometryGeneratorPlugin) | ui.setupPluginUi(GeometryGeneratorPlugin) | ||||
| ui.setupPluginUi(PickingPlugin) | ui.setupPluginUi(PickingPlugin) | ||||
| ui.setupPluginUi(Object3DGeneratorPlugin) | |||||
| } | } | ||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Object3D Generator Plugin</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": { | |||||
| "three": "./../../dist/index.mjs", | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs", | |||||
| "@threepipe/plugin-tweakpane-editor": "./../../plugins/tweakpane-editor/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, | |||||
| CameraViewPlugin, | |||||
| Object3DGeneratorPlugin, | |||||
| Object3DWidgetsPlugin, | |||||
| PickingPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| import {HierarchyUiPlugin} from '@threepipe/plugin-tweakpane-editor' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| plugins: [PickingPlugin, CameraViewPlugin, Object3DWidgetsPlugin, HierarchyUiPlugin], | |||||
| }) | |||||
| const generator = viewer.addPluginSync(Object3DGeneratorPlugin) | |||||
| viewer.scene.setBackgroundColor('#444466') | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| console.log(generator.generators) | |||||
| const object = generator.generate('light-directional', {color: 0x00ff00}) | |||||
| console.log(object) | |||||
| viewer.getPlugin(PickingPlugin)?.setSelectedObject(object) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| ui.setupPluginUi(Object3DGeneratorPlugin)!.expanded = true | |||||
| ui.setupPluginUi(HierarchyUiPlugin) | |||||
| ui.setupPluginUi(PickingPlugin) | |||||
| } | |||||
| init().then(_testFinish) | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>Object3D Widgets Plugin</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": { | |||||
| "three": "./../../dist/index.mjs", | |||||
| "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, | |||||
| CameraViewPlugin, | |||||
| Object3DGeneratorPlugin, | |||||
| Object3DWidgetsPlugin, | |||||
| ThreeViewer, | |||||
| Vector3, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| plugins: [CameraViewPlugin, Object3DGeneratorPlugin], | |||||
| }) | |||||
| const widgets = viewer.addPluginSync(Object3DWidgetsPlugin) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| ui.setupPluginUi(Object3DWidgetsPlugin) | |||||
| viewer.scene.setBackgroundColor('#444466') | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }) | |||||
| console.log(widgets.helpers) | |||||
| const generator = viewer.getPlugin(Object3DGeneratorPlugin)! | |||||
| let object | |||||
| object = generator.generate('camera-perspective', { | |||||
| position: new Vector3(5, 5, 0), | |||||
| }) | |||||
| ui.appendChild(object?.uiConfig) | |||||
| object = generator.generate('light-directional', { | |||||
| position: new Vector3(5, 0, 5), | |||||
| }) | |||||
| ui.appendChild(object?.uiConfig) | |||||
| object = generator.generate('light-spot', { | |||||
| position: new Vector3(-5, 0, 5), | |||||
| }) | |||||
| ui.appendChild(object?.uiConfig) | |||||
| object = generator.generate('light-point', { | |||||
| position: new Vector3(-5, 5, -5), | |||||
| }) | |||||
| ui.appendChild(object?.uiConfig) | |||||
| viewer.scene.mainCamera.position.z += 10 | |||||
| viewer.scene.mainCamera.setDirty() | |||||
| } | |||||
| init().then(_testFinish) | |||||
| KTXLoadPlugin, | KTXLoadPlugin, | ||||
| NoiseBumpMaterialPlugin, | NoiseBumpMaterialPlugin, | ||||
| NormalBufferPlugin, | NormalBufferPlugin, | ||||
| Object3DGeneratorPlugin, | |||||
| Object3DWidgetsPlugin, | |||||
| PickingPlugin, | PickingPlugin, | ||||
| PLYLoadPlugin, | PLYLoadPlugin, | ||||
| ProgressivePlugin, | ProgressivePlugin, | ||||
| BlendLoadPlugin, | BlendLoadPlugin, | ||||
| HierarchyUiPlugin, | HierarchyUiPlugin, | ||||
| GeometryGeneratorPlugin, | GeometryGeneratorPlugin, | ||||
| Object3DWidgetsPlugin, | |||||
| Object3DGeneratorPlugin, | |||||
| ...extraImportPlugins, | ...extraImportPlugins, | ||||
| ]) | ]) | ||||
| editor.loadPlugins({ | editor.loadPlugins({ | ||||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin], | ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin], | ||||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin], | |||||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin], | |||||
| ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | ['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin], | ||||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | ['Post-processing']: [TonemapPlugin, ProgressivePlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | ||||
| ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], | ['Animation']: [GLTFAnimationPlugin, CameraViewPlugin], |
| import {AViewerPluginSync} from '../../viewer' | |||||
| import { | |||||
| AmbientLight2, | |||||
| DirectionalLight2, | |||||
| HemisphereLight2, | |||||
| IObject3D, | |||||
| PerspectiveCamera2, | |||||
| PointLight2, | |||||
| RectAreaLight2, | |||||
| SpotLight2, | |||||
| } from '../../core' | |||||
| import {uiButton, uiDropdown, uiPanelContainer} from 'uiconfig.js' | |||||
| import {Vector3} from 'three' | |||||
| /** | |||||
| * Adds support for generating different types of lights and camera objects in the viewer. | |||||
| * @category Plugin | |||||
| */ | |||||
| @uiPanelContainer('Generate Scene Objects') | |||||
| export class Object3DGeneratorPlugin extends AViewerPluginSync<''> { | |||||
| public static readonly PluginType = 'Object3DGeneratorPlugin' | |||||
| enabled = true | |||||
| toJSON: any = undefined | |||||
| @uiDropdown('Type', undefined, (that)=>({ | |||||
| children: [()=>Object.keys(that.generators).map(label=>({label}))], | |||||
| })) | |||||
| protected _selectedType = '' | |||||
| @uiButton('Generate') | |||||
| generate(type?: string, params?: any, addToScene = true) { | |||||
| if (!this._viewer) throw new Error('No viewer') | |||||
| const obj = this.generators[type ?? this._selectedType]?.(params) | |||||
| addToScene && obj && this._viewer.scene.addObject(obj) | |||||
| return obj | |||||
| } | |||||
| generators: Record<string, (params?: any)=>IObject3D> = { | |||||
| ['camera-perspective']: (params: { | |||||
| controlsMode?: string, | |||||
| autoAspect?: boolean, | |||||
| fov?: number, | |||||
| aspect?: number, | |||||
| position?: Vector3, | |||||
| target?: Vector3, | |||||
| autoLookAtTarget?: boolean, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const camera = new PerspectiveCamera2( | |||||
| params.controlsMode ?? '', | |||||
| this._viewer?.canvas, | |||||
| params.autoAspect, | |||||
| params.fov, | |||||
| params.aspect, | |||||
| ) | |||||
| params.position ? camera.position.copy(params.position) : camera.position.set(0, 0, 5) | |||||
| params.target ? camera.target.copy(params.target) : camera.target.set(0, 0, 0) | |||||
| camera.autoLookAtTarget = params.autoLookAtTarget ?? true | |||||
| camera.setDirty() | |||||
| camera.name = params.name ?? 'Perspective Camera' | |||||
| return camera | |||||
| }, | |||||
| ['light-directional']: (params: { | |||||
| color?: number, | |||||
| intensity?: number, | |||||
| position?: Vector3, | |||||
| target?: Vector3, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const light = new DirectionalLight2(params.color ?? 0xff0000, params.intensity ?? 3) | |||||
| params.position ? light.position.copy(params.position) : light.position.set(5, 5, 5) | |||||
| light.lookAt(params.target ?? new Vector3(0, 0, 0)) | |||||
| light.name = 'Directional Light' | |||||
| return light | |||||
| }, | |||||
| ['light-ambient']: (params: { | |||||
| color?: number, | |||||
| intensity?: number, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const light = new AmbientLight2(params.color ?? 0xffffff, params.intensity ?? 1) | |||||
| light.name = 'Ambient Light' | |||||
| return light | |||||
| }, | |||||
| ['light-point']: (params: { | |||||
| color?: number, | |||||
| intensity?: number, | |||||
| position?: Vector3, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const light = new PointLight2(params.color ?? 0xff0000, params.intensity ?? 3) | |||||
| params.position ? light.position.copy(params.position) : light.position.set(5, 5, 5) | |||||
| light.name = 'Point Light' | |||||
| return light | |||||
| }, | |||||
| ['light-spot']: (params: { | |||||
| color?: number, | |||||
| intensity?: number, | |||||
| position?: Vector3, | |||||
| target?: Vector3, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const light = new SpotLight2(params.color ?? 0xff0000, params.intensity ?? 3) | |||||
| params.position ? light.position.copy(params.position) : light.position.set(5, 5, 5) | |||||
| light.lookAt(params.target ?? new Vector3(0, 0, 0)) | |||||
| light.name = 'Spot Light' | |||||
| return light | |||||
| }, | |||||
| ['light-hemisphere']: (params: { | |||||
| color?: number, | |||||
| intensity?: number, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const light = new HemisphereLight2(params.color ?? 0xaaaaff, 0x555443, params.intensity ?? 1) | |||||
| light.name = 'Hemisphere Light' | |||||
| return light | |||||
| }, | |||||
| ['light-rect-area']: (params: { | |||||
| color?: number, | |||||
| intensity?: number, | |||||
| position?: Vector3, | |||||
| target?: Vector3, | |||||
| name?: string, | |||||
| } = {})=>{ | |||||
| const light = new RectAreaLight2(params.color ?? 0x000ff, params.intensity ?? 3, 2, 2) | |||||
| params.position ? light.position.copy(params.position) : light.position.set(5, 5, 5) | |||||
| light.lookAt(params.target ?? new Vector3(0, 0, 0)) | |||||
| light.name = 'Rect Area Light' | |||||
| return light | |||||
| }, | |||||
| } | |||||
| constructor() { | |||||
| super() | |||||
| this._selectedType = Object.keys(this.generators)[0] | |||||
| } | |||||
| } |
| import {UiObjectConfig} from 'uiconfig.js' | |||||
| import {IWidget} from '../../core' | |||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||||
| import {IEvent, onChange} from 'ts-browser-helpers' | |||||
| import {Object3D} from 'three' | |||||
| import {CameraHelper2, DirectionalLightHelper2, PointLightHelper2, SpotLightHelper2} from '../../three' | |||||
| export interface IObject3DHelper<T extends Object3D&IWidget = Object3D&IWidget>{ | |||||
| Create: (o: Object3D)=>T, | |||||
| Check: (o: Object3D)=>boolean, | |||||
| } | |||||
| /** | |||||
| * Adds light and camera helpers/gizmos in the viewer. | |||||
| * A helper is automatically created when any supported light or camera is added to the scene. | |||||
| * @category Plugin | |||||
| */ | |||||
| export class Object3DWidgetsPlugin extends AViewerPluginSync<''> { | |||||
| @onChange(Object3DWidgetsPlugin.prototype.setDirty) | |||||
| enabled = true | |||||
| public static readonly PluginType = 'Object3DWidgetsPlugin' | |||||
| helpers: IObject3DHelper[] = [ | |||||
| DirectionalLightHelper2, | |||||
| SpotLightHelper2, | |||||
| PointLightHelper2, | |||||
| CameraHelper2, | |||||
| ] | |||||
| setDirty() { | |||||
| this.widgets?.forEach(w => w.visible = !this.isDisabled()) | |||||
| this._viewer?.setDirty() | |||||
| } | |||||
| toJSON: any = null | |||||
| constructor(enabled = true) { | |||||
| super() | |||||
| this.enabled = enabled | |||||
| } | |||||
| private _widgetRoot = new Object3D() | |||||
| onAdded(viewer: ThreeViewer) { | |||||
| super.onAdded(viewer) | |||||
| viewer.scene.addEventListener('addSceneObject', this._addSceneObject) | |||||
| viewer.scene.addObject(this._widgetRoot) | |||||
| } | |||||
| onRemove(viewer: ThreeViewer) { | |||||
| viewer.scene.removeEventListener('addSceneObject', this._addSceneObject) | |||||
| this.widgets.forEach(w => w.dispose && w.dispose()) | |||||
| this.widgets = [] | |||||
| this._widgetRoot.removeFromParent() | |||||
| this._widgetRoot.clear() | |||||
| super.onRemove(viewer) | |||||
| } | |||||
| private _addSceneObject = (e: any)=>{ | |||||
| this._createWidgets(e.object) | |||||
| } | |||||
| refresh() { | |||||
| this._createWidgets(this._viewer?.scene.modelRoot) | |||||
| } | |||||
| widgets: (IWidget&Object3D)[] = [] | |||||
| private _widgetDisposed = (e: IEvent<any>)=> this._unregisterWidget(e.target) | |||||
| private _registerWidget(w: IWidget&Object3D) { | |||||
| this.widgets.push(w) | |||||
| w.addEventListener('dispose', this._widgetDisposed) // todo: maybe unregister when removed from parent, dispose makes little sense. | |||||
| } | |||||
| private _unregisterWidget(w: IWidget&Object3D) { | |||||
| w.removeEventListener('dispose', this._widgetDisposed) | |||||
| const i = this.widgets.indexOf(w) | |||||
| if (i >= 0) this.widgets.splice(i, 1) | |||||
| } | |||||
| private _createWidgets(o?: Object3D) { | |||||
| o?.traverse((l: any) => { | |||||
| const widget = this.widgets.find(w => w.object === l) | |||||
| if (widget) { | |||||
| widget.update && widget.update() | |||||
| return | |||||
| } | |||||
| const helpers = this.helpers.filter(h => h.Check(l)) | |||||
| helpers.forEach(h => { | |||||
| const w = h.Create(l) | |||||
| w.visible = !this.isDisabled() | |||||
| this._widgetRoot.add(w) | |||||
| this._registerWidget(w) | |||||
| }) | |||||
| }) | |||||
| } | |||||
| uiConfig: UiObjectConfig = { | |||||
| type: 'folder', | |||||
| label: 'Widgets', | |||||
| children: [ | |||||
| { | |||||
| type: 'checkbox', | |||||
| label: 'Enabled', | |||||
| property: [this, 'enabled'], | |||||
| }, | |||||
| { | |||||
| type: 'button', | |||||
| label: 'Refresh', | |||||
| value: ()=>this.refresh(), | |||||
| }, | |||||
| ], | |||||
| } | |||||
| } |
| export {CustomBumpMapPlugin} from './material/CustomBumpMapPlugin' | export {CustomBumpMapPlugin} from './material/CustomBumpMapPlugin' | ||||
| export {FragmentClippingExtensionPlugin, FragmentClippingMode} from './material/FragmentClippingExtensionPlugin' | export {FragmentClippingExtensionPlugin, FragmentClippingMode} from './material/FragmentClippingExtensionPlugin' | ||||
| // extras | |||||
| // rendering | |||||
| export {VirtualCamerasPlugin} from './rendering/VirtualCamerasPlugin' | export {VirtualCamerasPlugin} from './rendering/VirtualCamerasPlugin' | ||||
| // extras | // extras | ||||
| export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin' | export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin' | ||||
| export {Object3DWidgetsPlugin} from './extras/Object3DWidgetsPlugin' | |||||
| export {Object3DGeneratorPlugin} from './extras/Object3DGeneratorPlugin' |