| - [RenderTargetPreviewPlugin](#rendertargetpreviewplugin) - Preview any render target in a UI panel over the canvas | - [RenderTargetPreviewPlugin](#rendertargetpreviewplugin) - Preview any render target in a UI panel over the canvas | ||||
| - [GeometryUVPreviewPlugin](#geometryuvpreviewplugin) - Preview UVs of any geometry in a UI panel over the canvas | - [GeometryUVPreviewPlugin](#geometryuvpreviewplugin) - Preview UVs of any geometry in a UI panel over the canvas | ||||
| - [FrameFadePlugin](#framefadeplugin) - Post-render pass to smoothly fade to a new rendered frame over time | - [FrameFadePlugin](#framefadeplugin) - Post-render pass to smoothly fade to a new rendered frame over time | ||||
| - [HDRiGroundPlugin](#hdrigroundplugin) - Add support for ground projected hdri/skybox to the webgl background shader. | |||||
| - [Rhino3dmLoadPlugin](#rhino3dmloadplugin) - Add support for loading .3dm files | - [Rhino3dmLoadPlugin](#rhino3dmloadplugin) - Add support for loading .3dm files | ||||
| - [PLYLoadPlugin](#plyloadplugin) - Add support for loading .ply files | - [PLYLoadPlugin](#plyloadplugin) - Add support for loading .ply files | ||||
| - [STLLoadPlugin](#stlloadplugin) - Add support for loading .stl files | - [STLLoadPlugin](#stlloadplugin) - Add support for loading .stl files | ||||
| ### Constructor | ### Constructor | ||||
| ```typescript | ```typescript | ||||
| import {ThreeViewer} from 'threepipe' | |||||
| import {ThreeViewer, CameraViewPlugin} from 'threepipe' | |||||
| // Create a viewer. All options except canvas/container are optional | |||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| // or a container like: | // or a container like: | ||||
| // domElement: document.body, | // domElement: document.body, | ||||
| // addOptions: { ... } | // addOptions: { ... } | ||||
| // importOptions: { ... } | // importOptions: { ... } | ||||
| } | |||||
| }, | |||||
| // By default its false | // By default its false | ||||
| // dropzone: false, | // dropzone: false, | ||||
| // To Enable without options | // To Enable without options | ||||
| // dropzone: true | // dropzone: true | ||||
| // Add some plugins after viewer creation. | |||||
| plugins: [CameraViewPlugin, new CustomPlugin()], | |||||
| // Shorthand to load files immediately after viewer initialization | |||||
| load: { | |||||
| src: 'https://example.com/file.glb', | |||||
| environment: 'https://example.com/file.hdr', | |||||
| background: 'https://example.com/file.png', | |||||
| }, | |||||
| onLoad: (viewer) => { | |||||
| // Called when all the files are loaded | |||||
| }, | |||||
| }) | }) | ||||
| ``` | ``` | ||||
| // Add a plugin | // Add a plugin | ||||
| const plugin = viewer.addPluginSync(new TonemapPlugin()) | const plugin = viewer.addPluginSync(new TonemapPlugin()) | ||||
| // plugins can be added with just the class also | // plugins can be added with just the class also | ||||
| const plugin = viewer.addPluginSync(TonemapPlugin) | |||||
| const plugin2 = viewer.addPluginSync(TonemapPlugin) | |||||
| // Add multiple plugins at once | // Add multiple plugins at once | ||||
| viewer.addPluginsSync([ | viewer.addPluginsSync([ | ||||
| ]) | ]) | ||||
| // Get a plugin | // Get a plugin | ||||
| const plugin = viewer.getPlugin(TonemapPlugin) | |||||
| const plugin3 = viewer.getPlugin(TonemapPlugin) | |||||
| // Get or add a plugin, when not sure if the plugin is already added | // Get or add a plugin, when not sure if the plugin is already added | ||||
| const plugin = viewer.getOrAddPluginSync(TonemapPlugin) | |||||
| const plugin4 = viewer.getOrAddPluginSync(TonemapPlugin) | |||||
| // Remove a plugin | // Remove a plugin | ||||
| viewer.removePluginSync(TonemapPlugin) | viewer.removePluginSync(TonemapPlugin) | ||||
| The plugin automatically tracks `setDirty()` function calls in objects, materials and the scene. It can be triggerred by calling `setDirty` on any material or object in the scene. Check the [example](https://threepipe.org/examples/#frame-fade-plugin/) for a demo. This can be disabled by options in the plugin. | The plugin automatically tracks `setDirty()` function calls in objects, materials and the scene. It can be triggerred by calling `setDirty` on any material or object in the scene. Check the [example](https://threepipe.org/examples/#frame-fade-plugin/) for a demo. This can be disabled by options in the plugin. | ||||
| ## HDRiGroundPlugin | |||||
| [//]: # (todo: image) | |||||
| Example: https://threepipe.org/examples/#hdri-ground-plugin/ | |||||
| Source Code: [src/plugins/pipeline/HDRiGroundPlugin.ts](./src/plugins/extras/HDRiGroundPlugin.ts) | |||||
| API Reference: [FrameFadePlugin](https://threepipe.org/docs/classes/HDRiGroundPlugin.html) | |||||
| HDRiGroundPlugin patches the background shader in the renderer to add support for ground projected environment map/skybox. Works simply by setting the background same as the environemnt and enabling the plugin. | |||||
| The world radius, tripod height, and origin position(center offset) can be set in the plugin. | |||||
| The plugin is disabled by default when added. Set `.enabled` to enable it or pass `true` in the constructor. | |||||
| If the background is not the same as the environment when enabled, the user will be prompted for this, unless `promptOnBackgroundMismatch` is set to `false` in the plugin. | |||||
| ```typescript | |||||
| import {ThreeViewer, FrameFadePlugin} from 'threepipe' | |||||
| const viewer = new ThreeViewer({...}) | |||||
| const hdriGround = viewer.addPluginSync(new HDRiGrounPlugin()) | |||||
| // Load an hdr environment map | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') | |||||
| // set background to environment | |||||
| viewer.scene.background = 'environment' | |||||
| // or | |||||
| // viewer.scene.background = viewer.scene.environemnt | |||||
| // enable the plugin | |||||
| hdriGround.enabled = true | |||||
| ``` | |||||
| Check the [example](https://threepipe.org/examples/#hdri-ground-plugin/) for a demo. | |||||
| ## Rhino3dmLoadPlugin | ## Rhino3dmLoadPlugin | ||||
| Example: https://threepipe.org/examples/#rhino3dm-load/ | Example: https://threepipe.org/examples/#rhino3dm-load/ |
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>HDRi Ground 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": { | |||||
| "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, HDRiGroundPlugin, ThreeViewer} from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| msaa: true, | |||||
| dropzone: { | |||||
| allowedExtensions: ['gltf', 'glb', 'hdr', 'bin', 'png', 'jpeg', 'webp', 'jpg', 'exr'], | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| autoSetEnvironment: true, // when hdr is dropped | |||||
| autoSetBackground: true, | |||||
| }, | |||||
| }, | |||||
| plugins: [CameraViewPlugin], | |||||
| }) | |||||
| const hdriGround = viewer.addPluginSync(HDRiGroundPlugin) | |||||
| await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', { | |||||
| setBackground: true, | |||||
| }) | |||||
| await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| autoScaleRadius: 10, | |||||
| }) | |||||
| viewer.scene.background = 'environment' // this is not really required since setBackground is also set to true above | |||||
| hdriGround.worldRadius = 50 | |||||
| hdriGround.tripodHeight = 10 | |||||
| const bounds = viewer.scene.getBounds(true, true) | |||||
| bounds.getCenter(hdriGround.originPosition) | |||||
| hdriGround.originPosition.y -= (bounds.max.y - bounds.min.y) / 2 | |||||
| hdriGround.enabled = true | |||||
| console.log(hdriGround) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| ui.setupPluginUi(HDRiGroundPlugin) | |||||
| await viewer.fitToView(undefined, 2.5) | |||||
| } | |||||
| init().then(_testFinish) |
| </ul> | </ul> | ||||
| <h2 class="category">Utils</h2> | <h2 class="category">Utils</h2> | ||||
| <ul> | <ul> | ||||
| <li><a href="./hdri-ground-plugin/">HDRi Ground Plugin <br/>(Projected Skybox)</a></li> | |||||
| <li><a href="./render-target-preview/">Render Target Preview Plugin </a></li> | <li><a href="./render-target-preview/">Render Target Preview Plugin </a></li> | ||||
| <li><a href="./geometry-uv-preview/">Geometry UV Preview Plugin </a></li> | <li><a href="./geometry-uv-preview/">Geometry UV Preview Plugin </a></li> | ||||
| <li><a href="./parallel-asset-import/">Parallel Asset Import </a></li> | <li><a href="./parallel-asset-import/">Parallel Asset Import </a></li> |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.14", | |||||
| "version": "0.0.16", | |||||
| "lockfileVersion": 2, | "lockfileVersion": 2, | ||||
| "requires": true, | "requires": true, | ||||
| "packages": { | "packages": { | ||||
| "": { | "": { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.14", | |||||
| "version": "0.0.16", | |||||
| "license": "Apache-2.0", | "license": "Apache-2.0", | ||||
| "dependencies": { | "dependencies": { | ||||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1017/package.tgz", | "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1017/package.tgz", |
| { | { | ||||
| "name": "threepipe", | "name": "threepipe", | ||||
| "version": "0.0.15", | |||||
| "version": "0.0.16", | |||||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | ||||
| "main": "src/index.ts", | "main": "src/index.ts", | ||||
| "module": "dist/index.mjs", | "module": "dist/index.mjs", |
| import {CameraView, ICamera, ICameraView, PerspectiveCamera2} from '../../core' | import {CameraView, ICamera, ICameraView, PerspectiveCamera2} from '../../core' | ||||
| import {AnimationResult, PopmotionPlugin} from './PopmotionPlugin' | import {AnimationResult, PopmotionPlugin} from './PopmotionPlugin' | ||||
| export interface CameraViewPluginOptions{duration?: number, ease?: EasingFunctionType, interpolateMode?: 'spherical'|'linear'} | |||||
| /** | /** | ||||
| * Camera View Plugin | * Camera View Plugin | ||||
| * | * | ||||
| // return this._animating | // return this._animating | ||||
| // } | // } | ||||
| constructor() { | |||||
| constructor(options: CameraViewPluginOptions = {}) { | |||||
| super() | super() | ||||
| this.addCurrentView = this.addCurrentView.bind(this) | this.addCurrentView = this.addCurrentView.bind(this) | ||||
| this.resetToFirstView = this.resetToFirstView.bind(this) | this.resetToFirstView = this.resetToFirstView.bind(this) | ||||
| // this._wheel = this._wheel.bind(this) | // this._wheel = this._wheel.bind(this) | ||||
| // this._pointerMove = this._pointerMove.bind(this) | // this._pointerMove = this._pointerMove.bind(this) | ||||
| // this._postFrame = this._postFrame.bind(this) | // this._postFrame = this._postFrame.bind(this) | ||||
| this.animDuration = options.duration ?? this.animDuration | |||||
| this.animEase = options.ease ?? this.animEase | |||||
| this.interpolateMode = options.interpolateMode ?? this.interpolateMode | |||||
| } | } | ||||
| @serialize('cameraViews') | @serialize('cameraViews') | ||||
| private _cameraViews: CameraView[] = [] | private _cameraViews: CameraView[] = [] | ||||
| get cameraViews(): CameraView[] { | get cameraViews(): CameraView[] { | ||||
| */ | */ | ||||
| @serialize() @uiDropdown('Ease', Object.keys(EasingFunctions).map((label:string)=>({label}))) animEase: EasingFunctionType = 'easeInOutSine' // ms | @serialize() @uiDropdown('Ease', Object.keys(EasingFunctions).map((label:string)=>({label}))) animEase: EasingFunctionType = 'easeInOutSine' // ms | ||||
| @serialize() @uiSlider('Duration', [10, 10000], 10) animDuration = 1000 // ms | @serialize() @uiSlider('Duration', [10, 10000], 10) animDuration = 1000 // ms | ||||
| @serialize() @uiSlider('RotationOffset', [0.2, 0.75], 0.01) rotationOffset = 0.25 | |||||
| @serialize() @uiDropdown('Interpolation', ['spherical', 'linear'].map((label:string)=>({label}))) | @serialize() @uiDropdown('Interpolation', ['spherical', 'linear'].map((label:string)=>({label}))) | ||||
| interpolateMode: 'spherical'|'linear' = 'spherical' | interpolateMode: 'spherical'|'linear' = 'spherical' | ||||
| // not used | |||||
| @serialize() | |||||
| // @uiSlider('RotationOffset', [0.2, 0.75], 0.01) | |||||
| rotationOffset = 0.25 | |||||
| private _animating = false | private _animating = false | ||||
| get animating(): boolean { | get animating(): boolean { | ||||
| return this._animating | return this._animating |
| #ifdef HDRi_GROUND_PROJ | |||||
| // assuming vectors are all normalized | |||||
| float intersectPlane1(const in vec3 r0, const in vec3 rd, const in vec3 n, const in vec3 p0) | |||||
| { | |||||
| float t = dot(p0 - r0, n) / (dot(n, rd)+1e-6); | |||||
| return t < 0. ? 1000. : t; | |||||
| } | |||||
| // slightly modified version | |||||
| float intersectSphere1(in vec3 ro, in vec3 rd, in vec3 sph, in float rad) { | |||||
| vec3 oc = ro - sph; | |||||
| float b = dot(oc, rd); | |||||
| float c = dot(oc, oc) - rad*rad; | |||||
| float t = b*b - c; | |||||
| return t < 0.0 ? t : -b + sqrt(t); | |||||
| } | |||||
| #define PI_HALF 1.5707963267948966 | |||||
| uniform float worldRadius; | |||||
| uniform float tripodHeight; | |||||
| uniform vec3 originPosition; | |||||
| vec3 hdriProject(){ | |||||
| vec3 p = normalize( vWorldDirection ); | |||||
| vec3 camPos = cameraPosition; | |||||
| camPos.y -= tripodHeight; | |||||
| float t = intersectSphere1(camPos, p, originPosition, worldRadius); | |||||
| if(t>0.0) { | |||||
| float t2 = intersectPlane1(camPos, p, vec3(0,-1,0), originPosition + vec3(0.0, -tripodHeight, 0.0)); | |||||
| p = (camPos + min(t, t2)*p)/worldRadius; | |||||
| /* | |||||
| if(t2 < t && tripodHeight < 0.001){ | |||||
| // float h = dot(p.xz, p.xz); | |||||
| //vertical | |||||
| // p.y = sqrt(1.-h); | |||||
| //sterographic // https://math.stackexchange.com/questions/1729012/mapping-the-unit-disc-to-the-hemisphere | |||||
| // p.x = p.x/(h+1.0); | |||||
| // p.z = p.z/(h+1.0); | |||||
| // p.y = (h-1.0)/(h+1.0); | |||||
| // polar | |||||
| float phi = atan(p.z, p.x); | |||||
| float p1 = 0.4; // lens projection fix // experimental for hdrihaven | |||||
| float l = length(p.xz); | |||||
| p1 = (1.-p1*l)/(1.-p1); | |||||
| float theta = sin(l*PI_HALF)*PI_HALF; // cancel out projection, map [0,1] to [0, PI/2] | |||||
| p.x = sin(theta)*cos(phi)*p1; | |||||
| p.y = -cos(theta); | |||||
| p.z = sin(theta)*sin(phi)*p1; | |||||
| } | |||||
| */ | |||||
| } | |||||
| else p = vec3(0.0, 1.0, 0.0); | |||||
| return p; | |||||
| } | |||||
| #endif |
| import {DataTexture, EquirectangularReflectionMapping, ShaderLib, Vector3} from 'three' | |||||
| import {onChange, serialize} from 'ts-browser-helpers' | |||||
| import hdriGroundProj from './HDRiGroundPlugin.glsl' | |||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||||
| import {shaderReplaceString} from '../../utils' | |||||
| import {uiPanelContainer, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||||
| @uiPanelContainer('HDRi Ground') | |||||
| export class HDRiGroundPlugin extends AViewerPluginSync<'', ThreeViewer> { | |||||
| static readonly PluginType = 'HDRiGroundPlugin' | |||||
| @serialize() | |||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @uiToggle('Enabled') | |||||
| enabled = false | |||||
| @serialize() | |||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @uiSlider('World Radius', [1, 1000], 0.01) | |||||
| worldRadius = 100 | |||||
| @serialize() | |||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @uiSlider('Tripod height', [0, 50], 0.01) | |||||
| tripodHeight = 10 | |||||
| @serialize() | |||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| @uiVector('Origin Position', undefined, 0.001, (t: HDRiGroundPlugin)=>({ | |||||
| onChange: t._paramsChanged, // this is for x, y, z values. | |||||
| })) | |||||
| originPosition = new Vector3(0, 0, 0) | |||||
| @serialize() | |||||
| @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| promptOnBackgroundMismatch = true | |||||
| // todo | |||||
| // /** | |||||
| // * Automatically set the origin position based on the ground position in GroundPlugin | |||||
| // */ | |||||
| // @serialize() | |||||
| // @onChange(HDRiGroundPlugin.prototype._paramsChanged) | |||||
| // @uiToggle('Auto Ground Position') | |||||
| // autoGroundPosition = false | |||||
| constructor(enabled = false, promptOnBackgroundMismatch = true) { | |||||
| super() | |||||
| this._paramsChanged = this._paramsChanged.bind(this) | |||||
| this.enabled = enabled | |||||
| this.promptOnBackgroundMismatch = promptOnBackgroundMismatch | |||||
| this.addEventListener('deserialize', this._paramsChanged) | |||||
| } | |||||
| private _paramsChanged() { | |||||
| if (!this._viewer) return | |||||
| const bg = this._viewer.scene.background | |||||
| if (this.enabled && bg !== this._viewer.scene.environment && bg !== 'environment') { | |||||
| if (bg && (bg as any).isDataTexture) (bg as DataTexture).mapping = EquirectangularReflectionMapping | |||||
| else { | |||||
| const change = this.promptOnBackgroundMismatch ? this._viewer.dialog.confirmSync('Background must be same as environment, do you want to change it?') : true | |||||
| if (change) { | |||||
| // const bgui = this._viewer.getPlugin<SimpleBackgroundEnvUiPlugin>('SimpleBackgroundEnvUiPlugin1') | |||||
| // if (bgui) { | |||||
| // bgui.envmapBg = true | |||||
| // bgui.uiConfig.uiRefresh?.('postFrame', true) | |||||
| // } else | |||||
| this._viewer.scene.background = 'environment' | |||||
| } else this.enabled = false | |||||
| } | |||||
| } | |||||
| const cubeMat = this._viewer.renderManager.renderer.background.getBoxMesh2()?.material | |||||
| const unif = cubeMat?.uniforms ?? ShaderLib.backgroundCube.uniforms | |||||
| if (!unif.tripodHeight) unif.tripodHeight = {value: 1.0} | |||||
| if (!unif.worldRadius) unif.worldRadius = {value: 1.0} | |||||
| if (!unif.originPosition) unif.originPosition = {value: new Vector3()} | |||||
| unif.tripodHeight.value = this.tripodHeight | |||||
| unif.worldRadius.value = this.worldRadius | |||||
| unif.originPosition.value.copy(this.originPosition) | |||||
| if (cubeMat) { | |||||
| if (!this.enabled && cubeMat.defines.HDRi_GROUND_PROJ) | |||||
| delete cubeMat.defines.HDRi_GROUND_PROJ | |||||
| else if (this.enabled) | |||||
| cubeMat.defines.HDRi_GROUND_PROJ = '1' | |||||
| cubeMat.needsUpdate = true | |||||
| } | |||||
| this._viewer.setDirty() | |||||
| // const m = this._viewer?.scene.modelRoot.children ?? [] | |||||
| // for (const m1 of m) { | |||||
| // m1.position.y = -this.tripodHeight + new Box3B().expandByObject(m1, true, true).getSize(new Vector3()).y / 2 | |||||
| // } | |||||
| } | |||||
| onAdded(viewer: ThreeViewer): void { | |||||
| super.onAdded(viewer) | |||||
| if (this._viewer?.renderManager.webglRenderer?.background.getBoxMesh()) | |||||
| viewer.console.error('HDRi Ground Plugin must be added before setting any cube or env map') | |||||
| if (!ShaderLib.backgroundCube.fragmentShader.includes('#ifdef HDRi_GROUND_PROJ')) { | |||||
| ShaderLib.backgroundCube.fragmentShader = shaderReplaceString(ShaderLib.backgroundCube.fragmentShader, 'void main() {', hdriGroundProj, {prepend: true}) | |||||
| ShaderLib.backgroundCube.fragmentShader = shaderReplaceString(ShaderLib.backgroundCube.fragmentShader, 'vec3 vReflect = vWorldDirection;', ` | |||||
| vec3 vReflect = | |||||
| #ifdef HDRi_GROUND_PROJ | |||||
| hdriProject() | |||||
| #else | |||||
| vWorldDirection | |||||
| #endif | |||||
| ; | |||||
| `) | |||||
| } | |||||
| viewer.scene.addEventListener('environmentChanged', this._paramsChanged) | |||||
| } | |||||
| } |
| // animation | // animation | ||||
| export {GLTFAnimationPlugin} from './animation/GLTFAnimationPlugin' | export {GLTFAnimationPlugin} from './animation/GLTFAnimationPlugin' | ||||
| export {PopmotionPlugin} from './animation/PopmotionPlugin' | export {PopmotionPlugin} from './animation/PopmotionPlugin' | ||||
| export {CameraViewPlugin} from './animation/CameraViewPlugin' | |||||
| export {CameraViewPlugin, type CameraViewPluginOptions} from './animation/CameraViewPlugin' | |||||
| // extras | |||||
| export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin' |
| import {Object3D} from 'three' | import {Object3D} from 'three' | ||||
| import {Class, serialize} from 'ts-browser-helpers' | import {Class, serialize} from 'ts-browser-helpers' | ||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | import {AViewerPluginSync, ThreeViewer} from '../../viewer' | ||||
| import {ObjectPicker} from '../../three/utils/ObjectPicker' | |||||
| import {BoxSelectionWidget, ObjectPicker, SelectionWidget} from '../../three' | |||||
| import {IObject3D, IObject3DEvent, ISceneEvent} from '../../core' | import {IObject3D, IObject3DEvent, ISceneEvent} from '../../core' | ||||
| import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | ||||
| import {BoxSelectionWidget, SelectionWidget} from '../../three/utils/SelectionWidget' | |||||
| export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { | export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { | ||||
| @serialize() enabled = true | @serialize() enabled = true | ||||
| this.dispatchEvent(e) | this.dispatchEvent(e) | ||||
| } | } | ||||
| // @ts-expect-error temporary | |||||
| public async focusObject(selected?: Object3D) { | public async focusObject(selected?: Object3D) { | ||||
| // const camViews = this._viewer?.getPluginByType<CameraViewPlugin>('CameraViews') | |||||
| // await camViews?.animateToFitObject(selected, 1.25, 1000, 'easeOut', {min: (this._viewer?.scene.activeCamera.getControls<OrbitControls3>()?.minDistance ?? 0.5) + 0.5, max: 50.0}) | |||||
| this._viewer?.fitToView(selected, 1.25, 1000, 'easeOut') | |||||
| } | } | ||||
| public enableWidget(enable: boolean): void { | public enableWidget(enable: boolean): void { |
| export {WebGLArrayRenderTarget} from 'three' | export {WebGLArrayRenderTarget} from 'three' | ||||
| export {WebGL3DRenderTarget} from 'three' | export {WebGL3DRenderTarget} from 'three' | ||||
| export {WebGLMultipleRenderTargets} from 'three' | export {WebGLMultipleRenderTargets} from 'three' | ||||
| export * from 'three/examples/jsm/libs/fflate.module.js' | export * from 'three/examples/jsm/libs/fflate.module.js' | ||||
| export {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | export {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | ||||
| export {Pass, FullScreenQuad} from 'three/examples/jsm/postprocessing/Pass.js' | |||||
| export {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass.js' | |||||
| export {ShaderPass} from 'three/examples/jsm/postprocessing/ShaderPass.js' | |||||
| export {EffectComposer} from 'three/examples/jsm/postprocessing/EffectComposer.js' | |||||
| export {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js' |
| export interface IDialogWrapper { | export interface IDialogWrapper { | ||||
| alert: (message?: string) => Promise<void> | alert: (message?: string) => Promise<void> | ||||
| confirm: (message?: string) => Promise<boolean> | |||||
| prompt: (message?: string, _default?: string, cancel?: boolean) => Promise<string | null> | prompt: (message?: string, _default?: string, cancel?: boolean) => Promise<string | null> | ||||
| confirm: (message?: string) => Promise<boolean> | |||||
| confirmSync: (message?: string) => boolean | |||||
| } | } | ||||
| export const windowDialogWrapper: IDialogWrapper = { | export const windowDialogWrapper: IDialogWrapper = { | ||||
| alert: async(message?: string) => window.alert(message), | alert: async(message?: string) => window.alert(message), | ||||
| confirm: async(message?: string) => window.confirm(message), | |||||
| prompt: async(message?: string, _default?: string, _?: boolean) => window.prompt(message, _default), | prompt: async(message?: string, _default?: string, _?: boolean) => window.prompt(message, _default), | ||||
| confirm: async(message?: string) => window.confirm(message), | |||||
| confirmSync: (message?: string) => window.confirm(message), | |||||
| } | } |
| Vector2, | Vector2, | ||||
| Vector3, | Vector3, | ||||
| } from 'three' | } from 'three' | ||||
| import {Class, createCanvasElement, onChange, serialize} from 'ts-browser-helpers' | |||||
| import {Class, createCanvasElement, onChange, serialize, ValOrArr} from 'ts-browser-helpers' | |||||
| import {TViewerScreenShader} from '../postprocessing' | import {TViewerScreenShader} from '../postprocessing' | ||||
| import { | import { | ||||
| AddObjectOptions, | AddObjectOptions, | ||||
| import {ViewerRenderManager} from './ViewerRenderManager' | import {ViewerRenderManager} from './ViewerRenderManager' | ||||
| import { | import { | ||||
| convertArrayBufferToStringsInMeta, | convertArrayBufferToStringsInMeta, | ||||
| EasingFunctionType, | |||||
| getEmptyMeta, | getEmptyMeta, | ||||
| GLStatsJS, | GLStatsJS, | ||||
| IDialogWrapper, | IDialogWrapper, | ||||
| import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | ||||
| import {uiConfig, uiFolderContainer, UiObjectConfig} from 'uiconfig.js' | import {uiConfig, uiFolderContainer, UiObjectConfig} from 'uiconfig.js' | ||||
| import {IRenderTarget} from '../rendering' | import {IRenderTarget} from '../rendering' | ||||
| import type {ProgressivePlugin} from '../plugins' | |||||
| import type {CameraViewPlugin, ProgressivePlugin} from '../plugins' | |||||
| // noinspection ES6PreferShortImport | // noinspection ES6PreferShortImport | ||||
| import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | ||||
| // noinspection ES6PreferShortImport | // noinspection ES6PreferShortImport | ||||
| import {TonemapPlugin} from '../plugins/postprocessing/TonemapPlugin' | import {TonemapPlugin} from '../plugins/postprocessing/TonemapPlugin' | ||||
| import {VERSION} from './version' | import {VERSION} from './version' | ||||
| import {Easing} from 'popmotion' | |||||
| import {OrbitControls3} from '../three' | |||||
| export type IViewerEvent = BaseEvent & { | export type IViewerEvent = BaseEvent & { | ||||
| type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin'|'renderEnabled'|'renderDisabled' | type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin'|'renderEnabled'|'renderDisabled' | ||||
| debug?: boolean | debug?: boolean | ||||
| /** | |||||
| * Add initial plugins. | |||||
| */ | |||||
| plugins?: (IViewerPluginSync | Class<IViewerPluginSync>)[] | |||||
| load?: { | |||||
| /** | |||||
| * Load one or more source files | |||||
| */ | |||||
| src?: ValOrArr<string | IAsset | null> | |||||
| /** | |||||
| * Load environment map | |||||
| */ | |||||
| environment?: string | IAsset | ITexture | undefined | null | |||||
| /** | |||||
| * Load background map | |||||
| */ | |||||
| background?: string | IAsset | ITexture | undefined | null | |||||
| } | |||||
| onLoad?: (results: any) => void | |||||
| /** | /** | ||||
| * TonemapPlugin is added to the viewer if this is true. | * TonemapPlugin is added to the viewer if this is true. | ||||
| * @default true | * @default true | ||||
| if (options.tonemap !== false) { | if (options.tonemap !== false) { | ||||
| this.addPluginSync(new TonemapPlugin()) | this.addPluginSync(new TonemapPlugin()) | ||||
| } | } | ||||
| for (const p of options.plugins ?? []) this.addPluginSync(p) | |||||
| this.console.log('ThreePipe Viewer instance initialized, version: ', ThreeViewer.VERSION) | this.console.log('ThreePipe Viewer instance initialized, version: ', ThreeViewer.VERSION) | ||||
| if (options.load) { | |||||
| const sources = [options.load.src].flat().filter(s=> s) | |||||
| const promises: Promise<any>[] = sources.map(async s=> s && this.load(s)) | |||||
| if (options.load.environment) promises.push(this.setEnvironmentMap(options.load.environment)) | |||||
| if (options.load.background) promises.push(this.setBackgroundMap(options.load.background)) | |||||
| Promise.all(promises).then(options.onLoad) | |||||
| } | |||||
| } | } | ||||
| /** | /** | ||||
| // this.fromJSON(config, config.resources) | // this.fromJSON(config, config.resources) | ||||
| // } | // } | ||||
| // todo | |||||
| // public async fitToView(selected?: Object3D, distanceMultiplier = 1.5, duration?: number, ease?: Easing|EasingFunctionType) { | |||||
| // const camViews = this.getPluginByType<CameraViewPlugin>('CameraViews') | |||||
| // if (!camViews) { | |||||
| // this.console.error('CameraViews plugin is required for fitToView to work') | |||||
| // return | |||||
| // } | |||||
| // await camViews?.animateToFitObject(selected, distanceMultiplier, duration, ease, {min: (this.scene.activeCamera.getControls<OrbitControls3>()?.minDistance ?? 0.5) + 0.5, max: 1000.0}) | |||||
| // } | |||||
| public async fitToView(selected?: Object3D, distanceMultiplier = 1.5, duration?: number, ease?: Easing|EasingFunctionType) { | |||||
| const camViews = this.getPlugin<CameraViewPlugin>('CameraViews') | |||||
| if (!camViews) { | |||||
| this.console.error('CameraViewPlugin (CameraViews) is required for fitToView to work') | |||||
| return | |||||
| } | |||||
| await camViews?.animateToFitObject(selected, distanceMultiplier, duration, ease, {min: ((<OrbitControls3> this.scene.mainCamera.controls)?.minDistance ?? 0.5) + 0.5, max: 1000.0}) | |||||
| } | |||||
| // todo: create/load texture utils | // todo: create/load texture utils | ||||
| export const VERSION = '0.0.15' | |||||
| export const VERSION = '0.0.16' |