| @@ -98,6 +98,7 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| - [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 | |||
| - [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 | |||
| - [PLYLoadPlugin](#plyloadplugin) - Add support for loading .ply files | |||
| - [STLLoadPlugin](#stlloadplugin) - Add support for loading .stl files | |||
| @@ -1042,8 +1043,9 @@ More options can be passed in the constructor to configure various built-in plug | |||
| ### Constructor | |||
| ```typescript | |||
| import {ThreeViewer} from 'threepipe' | |||
| import {ThreeViewer, CameraViewPlugin} from 'threepipe' | |||
| // Create a viewer. All options except canvas/container are optional | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| // or a container like: | |||
| @@ -1080,11 +1082,24 @@ const viewer = new ThreeViewer({ | |||
| // domElement: document.body, | |||
| // addOptions: { ... } | |||
| // importOptions: { ... } | |||
| } | |||
| }, | |||
| // By default its false | |||
| // dropzone: false, | |||
| // To Enable without options | |||
| // 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 | |||
| }, | |||
| }) | |||
| ``` | |||
| @@ -1104,7 +1119,7 @@ const viewer = new ThreeViewer({...}) | |||
| // Add a plugin | |||
| const plugin = viewer.addPluginSync(new TonemapPlugin()) | |||
| // plugins can be added with just the class also | |||
| const plugin = viewer.addPluginSync(TonemapPlugin) | |||
| const plugin2 = viewer.addPluginSync(TonemapPlugin) | |||
| // Add multiple plugins at once | |||
| viewer.addPluginsSync([ | |||
| @@ -1115,10 +1130,10 @@ viewer.addPluginsSync([ | |||
| ]) | |||
| // 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 | |||
| const plugin = viewer.getOrAddPluginSync(TonemapPlugin) | |||
| const plugin4 = viewer.getOrAddPluginSync(TonemapPlugin) | |||
| // Remove a plugin | |||
| viewer.removePluginSync(TonemapPlugin) | |||
| @@ -2386,6 +2401,43 @@ To stop a transition, call `fadePlugin.stopTransition()`. This will immediately | |||
| 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 | |||
| Example: https://threepipe.org/examples/#rhino3dm-load/ | |||
| @@ -0,0 +1,36 @@ | |||
| <!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> | |||
| @@ -0,0 +1,50 @@ | |||
| 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) | |||
| @@ -278,6 +278,7 @@ | |||
| </ul> | |||
| <h2 class="category">Utils</h2> | |||
| <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="./geometry-uv-preview/">Geometry UV Preview Plugin </a></li> | |||
| <li><a href="./parallel-asset-import/">Parallel Asset Import </a></li> | |||
| @@ -1,12 +1,12 @@ | |||
| { | |||
| "name": "threepipe", | |||
| "version": "0.0.14", | |||
| "version": "0.0.16", | |||
| "lockfileVersion": 2, | |||
| "requires": true, | |||
| "packages": { | |||
| "": { | |||
| "name": "threepipe", | |||
| "version": "0.0.14", | |||
| "version": "0.0.16", | |||
| "license": "Apache-2.0", | |||
| "dependencies": { | |||
| "@types/three": "https://github.com/repalash/three-ts-types/releases/download/v0.152.1017/package.tgz", | |||
| @@ -1,6 +1,6 @@ | |||
| { | |||
| "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.", | |||
| "main": "src/index.ts", | |||
| "module": "dist/index.mjs", | |||
| @@ -8,6 +8,8 @@ import {EasingFunctions, EasingFunctionType} from '../../utils' | |||
| import {CameraView, ICamera, ICameraView, PerspectiveCamera2} from '../../core' | |||
| import {AnimationResult, PopmotionPlugin} from './PopmotionPlugin' | |||
| export interface CameraViewPluginOptions{duration?: number, ease?: EasingFunctionType, interpolateMode?: 'spherical'|'linear'} | |||
| /** | |||
| * Camera View Plugin | |||
| * | |||
| @@ -23,7 +25,7 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| // return this._animating | |||
| // } | |||
| constructor() { | |||
| constructor(options: CameraViewPluginOptions = {}) { | |||
| super() | |||
| this.addCurrentView = this.addCurrentView.bind(this) | |||
| this.resetToFirstView = this.resetToFirstView.bind(this) | |||
| @@ -32,8 +34,14 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| // this._wheel = this._wheel.bind(this) | |||
| // this._pointerMove = this._pointerMove.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') | |||
| private _cameraViews: CameraView[] = [] | |||
| get cameraViews(): CameraView[] { | |||
| @@ -58,10 +66,15 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| */ | |||
| @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('RotationOffset', [0.2, 0.75], 0.01) rotationOffset = 0.25 | |||
| @serialize() @uiDropdown('Interpolation', ['spherical', 'linear'].map((label:string)=>({label}))) | |||
| interpolateMode: 'spherical'|'linear' = 'spherical' | |||
| // not used | |||
| @serialize() | |||
| // @uiSlider('RotationOffset', [0.2, 0.75], 0.01) | |||
| rotationOffset = 0.25 | |||
| private _animating = false | |||
| get animating(): boolean { | |||
| return this._animating | |||
| @@ -0,0 +1,58 @@ | |||
| #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 | |||
| @@ -0,0 +1,117 @@ | |||
| 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) | |||
| } | |||
| } | |||
| @@ -36,4 +36,7 @@ export {TonemapPlugin} from './postprocessing/TonemapPlugin' | |||
| // animation | |||
| export {GLTFAnimationPlugin} from './animation/GLTFAnimationPlugin' | |||
| export {PopmotionPlugin} from './animation/PopmotionPlugin' | |||
| export {CameraViewPlugin} from './animation/CameraViewPlugin' | |||
| export {CameraViewPlugin, type CameraViewPluginOptions} from './animation/CameraViewPlugin' | |||
| // extras | |||
| export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin' | |||
| @@ -1,10 +1,9 @@ | |||
| import {Object3D} from 'three' | |||
| import {Class, serialize} from 'ts-browser-helpers' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {ObjectPicker} from '../../three/utils/ObjectPicker' | |||
| import {BoxSelectionWidget, ObjectPicker, SelectionWidget} from '../../three' | |||
| import {IObject3D, IObject3DEvent, ISceneEvent} from '../../core' | |||
| import {IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | |||
| import {BoxSelectionWidget, SelectionWidget} from '../../three/utils/SelectionWidget' | |||
| export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'hoverObjectChanged'|'hitObject'> { | |||
| @serialize() enabled = true | |||
| @@ -192,10 +191,8 @@ export class PickingPlugin extends AViewerPluginSync<'selectedObjectChanged'|'ho | |||
| this.dispatchEvent(e) | |||
| } | |||
| // @ts-expect-error temporary | |||
| 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 { | |||
| @@ -1,3 +1,4 @@ | |||
| export {WebGLArrayRenderTarget} from 'three' | |||
| export {WebGL3DRenderTarget} from 'three' | |||
| export {WebGLMultipleRenderTargets} from 'three' | |||
| @@ -221,3 +222,9 @@ export type {Shader} from 'three' | |||
| export * from 'three/examples/jsm/libs/fflate.module.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' | |||
| @@ -1,10 +1,12 @@ | |||
| export interface IDialogWrapper { | |||
| alert: (message?: string) => Promise<void> | |||
| confirm: (message?: string) => Promise<boolean> | |||
| prompt: (message?: string, _default?: string, cancel?: boolean) => Promise<string | null> | |||
| confirm: (message?: string) => Promise<boolean> | |||
| confirmSync: (message?: string) => boolean | |||
| } | |||
| export const windowDialogWrapper: IDialogWrapper = { | |||
| 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), | |||
| confirm: async(message?: string) => window.confirm(message), | |||
| confirmSync: (message?: string) => window.confirm(message), | |||
| } | |||
| @@ -9,7 +9,7 @@ import { | |||
| Vector2, | |||
| Vector3, | |||
| } 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 { | |||
| AddObjectOptions, | |||
| @@ -24,6 +24,7 @@ import { | |||
| import {ViewerRenderManager} from './ViewerRenderManager' | |||
| import { | |||
| convertArrayBufferToStringsInMeta, | |||
| EasingFunctionType, | |||
| getEmptyMeta, | |||
| GLStatsJS, | |||
| IDialogWrapper, | |||
| @@ -50,12 +51,14 @@ import { | |||
| import {IViewerPlugin, IViewerPluginSync} from './IViewerPlugin' | |||
| import {uiConfig, uiFolderContainer, UiObjectConfig} from 'uiconfig.js' | |||
| import {IRenderTarget} from '../rendering' | |||
| import type {ProgressivePlugin} from '../plugins' | |||
| import type {CameraViewPlugin, ProgressivePlugin} from '../plugins' | |||
| // noinspection ES6PreferShortImport | |||
| import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/DropzonePlugin' | |||
| // noinspection ES6PreferShortImport | |||
| import {TonemapPlugin} from '../plugins/postprocessing/TonemapPlugin' | |||
| import {VERSION} from './version' | |||
| import {Easing} from 'popmotion' | |||
| import {OrbitControls3} from '../three' | |||
| export type IViewerEvent = BaseEvent & { | |||
| type: 'update'|'preRender'|'postRender'|'preFrame'|'postFrame'|'dispose'|'addPlugin'|'renderEnabled'|'renderDisabled' | |||
| @@ -126,6 +129,31 @@ export interface ThreeViewerOptions { | |||
| 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. | |||
| * @default true | |||
| @@ -382,10 +410,17 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| if (options.tonemap !== false) { | |||
| this.addPluginSync(new TonemapPlugin()) | |||
| } | |||
| for (const p of options.plugins ?? []) this.addPluginSync(p) | |||
| 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) | |||
| } | |||
| } | |||
| /** | |||
| @@ -1080,15 +1115,14 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| // 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 | |||
| @@ -1 +1 @@ | |||
| export const VERSION = '0.0.15' | |||
| export const VERSION = '0.0.16' | |||