| @@ -20,7 +20,7 @@ async function init() { | |||
| rgbm: false, // The pass from three.js doesn't support RGBM encoded render targets | |||
| zPrepass: false, | |||
| renderScale: 1, | |||
| maxHDRIntensity: 8, | |||
| maxHDRIntensity: 100, | |||
| dropzone: { | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| @@ -10,6 +10,7 @@ | |||
| "exports": { | |||
| ".": { | |||
| "import": "./dist/index.mjs", | |||
| "types": "./dist/index.d.ts", | |||
| "require": "./dist/index.js" | |||
| }, | |||
| "./dist/": { | |||
| @@ -33,6 +33,13 @@ export interface ICameraUserData extends IObject3DUserData { | |||
| * @default false | |||
| */ | |||
| autoLookAtTarget?: boolean | |||
| /** | |||
| * Automatically move the camera(dolly) based on the scene size when the field of view(fov) changes. | |||
| * Works when controls are enabled or autoLookAtTarget is true. | |||
| * | |||
| * Note - The camera must be added to RootScene for this to work | |||
| */ | |||
| dollyFov?: boolean | |||
| /** | |||
| * Disable jitter for this camera. (for {@link SSAAPlugin}) | |||
| @@ -18,7 +18,21 @@ export interface IGeometry<Attributes extends NormalOrGLBufferAttributes = Norma | |||
| refreshUi(): void; | |||
| uiConfig?: UiObjectConfig | |||
| appliedMeshes: Set<IObject3D> | |||
| center(offset?: Vector3, keepWorldPosition?: boolean): this | |||
| /** | |||
| * Centers the geometry. | |||
| * @param offset - returns the offset applied to the geometry | |||
| * @param keepWorldPosition - Updates the attached meshes, so that the world position of the geometry remains the same. | |||
| * @param setDirty | |||
| */ | |||
| center(offset?: Vector3, keepWorldPosition?: boolean, setDirty?: boolean): this | |||
| /** | |||
| * Same as center but returns a function to undo the centering | |||
| * @param offset | |||
| * @param keepWorldPosition | |||
| */ | |||
| center2(offset?: Vector3, keepWorldPosition?: boolean): ()=>void | |||
| // Note: for userData: add _ in front of for private use, which is preserved while cloning but not serialisation, and __ for private use, which is not preserved while cloning and serialisation | |||
| userData: IGeometryUserData | |||
| @@ -1,6 +1,6 @@ | |||
| import {IDisposable} from 'ts-browser-helpers' | |||
| import {IMaterial} from './IMaterial' | |||
| import {Event, Object3D} from 'three' | |||
| import {Event, Object3D, Vector3} from 'three' | |||
| import {ChangeEvent, IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | |||
| import {IGeometry, IGeometryEvent} from './IGeometry' | |||
| import {IImportResultUserData} from '../assetmanager' | |||
| @@ -202,6 +202,7 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent | |||
| userData: IObject3DUserData | |||
| /** | |||
| * Scales the object to fit the given radius. | |||
| * | |||
| * @param autoScaleRadius - optional (taken from userData.autoScaleRadius by default) | |||
| * @param isCentered - optional (taken from userData.isCentered by default) | |||
| @@ -211,12 +212,34 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent | |||
| autoScale?(autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean, undo?: boolean): this | |||
| /** | |||
| * Moves the bounding box center of the object to the center of the world | |||
| * | |||
| * @param setDirty - calls {@link setDirty} @default true | |||
| * @param undo - undo any previous autoCenter operation | |||
| */ | |||
| autoCenter?(setDirty?: boolean, undo?: boolean): this | |||
| /** | |||
| * Moves the object pivot to the center of the bounding box. | |||
| * | |||
| * The object will rotate around the new pivot. | |||
| * | |||
| * @param setDirty - calls {@link setDirty} @default true | |||
| * @returns undo function | |||
| */ | |||
| pivotToBoundsCenter?(setDirty?: boolean): () => void | |||
| /** | |||
| * Moves the object pivot to the given point | |||
| * | |||
| * The object will rotate around the new pivot. | |||
| * | |||
| * @param point - point to move the pivot to | |||
| * @param setDirty - calls {@link setDirty} @default true | |||
| * @returns undo function | |||
| */ | |||
| pivotToPoint?(point: Vector3, setDirty?: boolean): this | |||
| /** | |||
| * @deprecated use object directly | |||
| */ | |||
| @@ -107,6 +107,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| */ | |||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | |||
| minNearPlane = 0.5 | |||
| /** | |||
| * Maximum far clipping plane allowed. (Distance from camera) | |||
| * Used in RootScene when {@link autoNearFar} is true. | |||
| @@ -114,6 +115,15 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | |||
| maxFarPlane = 1000 | |||
| /** | |||
| * Automatically move the camera(dolly) when the field of view(fov) changes. | |||
| * Works when controls are enabled or autoLookAtTarget is true. | |||
| * | |||
| * Note - this is not exact | |||
| */ | |||
| @bindToValue({obj: 'userData'}) | |||
| dollyFov = false // bound to userData so that it's saved in the glb. | |||
| constructor(controlsMode?: TCameraControlsMode, domElement?: HTMLCanvasElement, autoAspect?: boolean, fov?: number, aspect?: number) { | |||
| super(fov, aspect) | |||
| this._canvas = domElement | |||
| @@ -409,6 +419,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| } | |||
| // endregion | |||
| // region utils/others | |||
| // for shader prop updater | |||
| @@ -427,6 +438,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| return this | |||
| } | |||
| dispose(): void { | |||
| this._disposeCameraControls() | |||
| // todo: anything else? | |||
| @@ -454,6 +466,11 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| label: 'Auto Near Far', | |||
| property: [this, 'autoNearFar'], | |||
| }, | |||
| { | |||
| type: 'input', | |||
| label: 'Dolly FoV', | |||
| property: [this, 'dollyFov'], | |||
| }, | |||
| ()=>({ // because _controlsCtors can change | |||
| type: 'dropdown', | |||
| label: 'Controls Mode', | |||
| @@ -618,6 +635,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| export class PerspectiveCamera0 extends PerspectiveCamera2 { | |||
| constructor(fov?: number, aspect?: number, near?: number, far?: number) { | |||
| super(undefined, undefined, undefined, fov, aspect || 1) | |||
| this.dollyFov = false | |||
| if (near || far) { | |||
| this.autoNearFar = false | |||
| if (near) { | |||
| @@ -3,6 +3,7 @@ import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | |||
| import {autoGPUInstanceMeshes, isInScene, toIndexedGeometry} from '../../three/utils' | |||
| import {BufferGeometry, Vector3} from 'three' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {IObject3D} from '../IObject' | |||
| export const iGeometryCommons = { | |||
| setDirty: function(this: IGeometry, options?: IGeometrySetDirtyOptions): void { | |||
| @@ -23,7 +24,7 @@ export const iGeometryCommons = { | |||
| }, | |||
| upgradeGeometry: upgradeGeometry, | |||
| center: (superCenter: BufferGeometry['center']): IGeometry['center'] => | |||
| function(this: IGeometry, offset?: Vector3, keepWorldPosition = false): IGeometry { | |||
| function(this: IGeometry, offset?: Vector3, keepWorldPosition = false, setDirty = true): IGeometry { | |||
| if (keepWorldPosition) { | |||
| offset = offset ? offset.clone() : new Vector3() | |||
| superCenter.call(this, offset) | |||
| @@ -32,14 +33,50 @@ export const iGeometryCommons = { | |||
| for (const m of meshes) { | |||
| m.updateMatrix() | |||
| m.position.copy(offset).applyMatrix4(m.matrix) | |||
| m.setDirty() | |||
| if (setDirty) m.setDirty() | |||
| } | |||
| } else { | |||
| superCenter.call(this, offset) | |||
| } | |||
| this.setDirty() | |||
| if (setDirty) this.setDirty() | |||
| return this | |||
| }, | |||
| center2: function(this: IGeometry, offset?: Vector3, keepWorldPosition = false, setDirty = true): ()=>void { | |||
| const offset1 = offset ? offset : new Vector3() | |||
| if (keepWorldPosition) { | |||
| this.center(offset1, false, false) | |||
| const meshes = this.appliedMeshes | |||
| const positions = new WeakMap<IObject3D, Vector3>() | |||
| for (const m of meshes) { | |||
| m.updateMatrix() | |||
| positions.set(m, m.position.clone()) | |||
| m.position.set(-offset1.x, -offset1.y, -offset1.z).applyMatrix4(m.matrix) | |||
| if (setDirty) m.setDirty() | |||
| } | |||
| if (setDirty) this.setDirty() | |||
| return ()=>{ | |||
| // undo | |||
| for (const m of meshes) { | |||
| const pos = positions.get(m) | |||
| if (!pos) { | |||
| console.warn('GeometryCommons: No position found for mesh', m) | |||
| continue | |||
| } | |||
| m.position.copy(pos) | |||
| if (setDirty) m.setDirty() | |||
| } | |||
| if (setDirty) this.setDirty() | |||
| } | |||
| } else { | |||
| this.center(offset1, false, false) | |||
| if (setDirty) this.setDirty() | |||
| return ()=>{ | |||
| // undo | |||
| this.translate(-offset1.x, -offset1.y, -offset1.z) | |||
| if (setDirty) this.setDirty() | |||
| } | |||
| } | |||
| }, | |||
| makeUiConfig: function(this: IGeometry): UiObjectConfig { | |||
| if (this.uiConfig) return this.uiConfig | |||
| return { | |||
| @@ -59,16 +96,16 @@ export const iGeometryCommons = { | |||
| type: 'button', | |||
| label: 'Center Geometry', | |||
| value: async() => { | |||
| if (!await ThreeViewer.Dialog.confirm('This will move the objects based on the geometry center, do you want to continue?\nThis action cannot be undone.')) return | |||
| this.center() | |||
| if (!await ThreeViewer.Dialog.confirm('This will move the objects based on the geometry center, do you want to continue?')) return | |||
| return this.center2() | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Center Geometry (keep position)', | |||
| value: async() => { | |||
| if (!await ThreeViewer.Dialog.confirm('This will move the geometry center keeping the object position, do you want to continue?\nThis action cannot be undone.')) return | |||
| this.center(undefined, true) | |||
| if (!await ThreeViewer.Dialog.confirm('This will move the geometry center keeping the object position, do you want to continue?')) return | |||
| return this.center2(undefined, true) | |||
| }, | |||
| }, | |||
| { | |||
| @@ -174,6 +211,7 @@ function upgradeGeometry(this: IGeometry) { | |||
| this.dispose = iGeometryCommons.dispose(this.dispose) | |||
| this.center = iGeometryCommons.center(this.center) | |||
| this.clone = iGeometryCommons.clone(this.clone) | |||
| if (!this.center2) this.center2 = iGeometryCommons.center2 | |||
| if (!this.setDirty) this.setDirty = iGeometryCommons.setDirty | |||
| if (!this.refreshUi) this.refreshUi = iGeometryCommons.refreshUi | |||
| @@ -154,6 +154,15 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| } | |||
| }, | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Pivot to Node Center', | |||
| value: async()=>{ | |||
| const res = await ThreeViewer.Dialog.confirm('Pivot to Center: Adjust the pivot to bounding box center. The object will rotate around the new pivot, are you sure you want to proceed?') | |||
| if (!res) return | |||
| return this.pivotToBoundsCenter?.(true) // return value is the undo function | |||
| }, | |||
| }, | |||
| { | |||
| type: 'folder', | |||
| label: 'Rotate model', | |||
| @@ -21,6 +21,7 @@ import {iObjectCommons} from './iObjectCommons' | |||
| import {RootSceneImportResult} from '../../assetmanager' | |||
| import {uiButton, uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {IGeometry} from '../IGeometry' | |||
| import {getFittingDistance} from '../../three/utils/camera' | |||
| export type TCamera = ICamera | |||
| @@ -287,7 +288,9 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| centerAllGeometries(keepPosition = true, obj?: IObject3D) { | |||
| const geoms = new Set<IGeometry>() | |||
| ;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry)) | |||
| geoms.forEach(g => g.center(undefined, keepPosition)) | |||
| const undos: (()=>void)[] = [] | |||
| geoms.forEach(g => undos.push(g.center2(undefined, keepPosition))) | |||
| return ()=>undos.forEach(u=>u()) | |||
| } | |||
| clearSceneModels(dispose = false, setDirty = true): void { | |||
| @@ -365,6 +368,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| private _mainCameraUpdate = (e: any) => { | |||
| this.setDirty({refreshScene: false}) | |||
| this.refreshActiveCameraNearFar() | |||
| if (e.key === 'fov') this.dollyActiveCameraFov() | |||
| this.dispatchEvent({...e, type: 'mainCameraUpdate'}) | |||
| this.dispatchEvent({...e, type: 'activeCameraUpdate'}) // deprecated | |||
| } | |||
| @@ -384,6 +388,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| if (event?.sceneUpdate === false || event?.refreshScene === false || event?.object?.isCamera) return this.setDirty(event) // so that it doesn't trigger frame fade, shadow refresh etc | |||
| // console.warn(event) | |||
| this.refreshActiveCameraNearFar() | |||
| // this.dollyActiveCameraFov() | |||
| this._sceneBounds = this.getBounds(false, true) | |||
| // this.boxHelper?.boxHelper?.copy?.(this._sceneBounds) | |||
| this._sceneBoundingRadius = this._sceneBounds.getSize(new Vector3()).length() / 2. | |||
| @@ -506,6 +511,26 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| // camera.far = 20 | |||
| } | |||
| /** | |||
| * Refreshes the scene active camera near far values, based on the scene bounding box. | |||
| * This is called automatically every time the camera fov is updated. | |||
| */ | |||
| dollyActiveCameraFov(): void { | |||
| const camera = this.mainCamera as TCamera | |||
| if (!camera) return | |||
| if (!camera.userData.dollyFov) { | |||
| return | |||
| } | |||
| const bbox = this.getModelBounds(false, true, true) | |||
| // todo this is not exact because of 1.5, this needs to be calculated based on current position and last fov | |||
| const cameraZ = getFittingDistance(camera, bbox) * 1.5 | |||
| const direction = new Vector3().subVectors(camera.target, camera.position).normalize() | |||
| camera.position.copy(direction.multiplyScalar(-cameraZ).add(camera.target)) | |||
| camera.setDirty() | |||
| } | |||
| updateShaderProperties(material: {defines: Record<string, string|number|undefined>, uniforms: {[name: string]: IUniform}}): this { | |||
| if (material.uniforms.sceneBoundingRadius) material.uniforms.sceneBoundingRadius.value = this._sceneBoundingRadius | |||
| else console.warn('RootScene: no uniform: sceneBoundingRadius') | |||
| @@ -1,4 +1,4 @@ | |||
| import {Event, Mesh, Vector3} from 'three' | |||
| import {Event, Matrix4, Mesh, Vector3} from 'three' | |||
| import {IMaterial} from '../IMaterial' | |||
| import {objectHasOwn} from 'ts-browser-helpers' | |||
| import {IObject3D, IObject3DEvent, IObjectProcessor, IObjectSetDirtyOptions} from '../IObject' | |||
| @@ -39,6 +39,7 @@ export const iObjectCommons = { | |||
| if (setDirty) this.setDirty({change: 'autoCenter', undo}) | |||
| return this | |||
| }, | |||
| 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 | |||
| @@ -93,6 +94,55 @@ export const iObjectCommons = { | |||
| return this | |||
| }, | |||
| pivotToBoundsCenter: function<T extends IObject3D>(this: T, setDirty = true): ()=>void { | |||
| const bb = new Box3B().expandByObject(this, true, true) | |||
| const center = bb.getCenter(new Vector3()) | |||
| return iObjectCommons.pivotToPoint.call(this, center, setDirty) | |||
| }, | |||
| pivotToPoint: function<T extends IObject3D>(this: T, point: Vector3, setDirty = true): ()=>void { | |||
| const worldCenter = new Vector3().copy(point) | |||
| const localCenter = new Vector3().copy(worldCenter) | |||
| const worldMatrixInv = new Matrix4().copy(this.matrixWorld).invert() | |||
| const m = this.parent?.matrixWorld | |||
| const parentWorldMatrixInv = new Matrix4() | |||
| if (m !== undefined) | |||
| parentWorldMatrixInv.copy(m).invert() | |||
| // Get the center with respect to the parent | |||
| worldCenter.applyMatrix4(parentWorldMatrixInv) | |||
| const lastPosition = this.position.clone() | |||
| // Apply the new position | |||
| this.position.copy(worldCenter) | |||
| // local center | |||
| localCenter.applyMatrix4(worldMatrixInv).negate() | |||
| // Shift the geometry | |||
| if (this.geometry) { | |||
| this.geometry.translate(localCenter.x, localCenter.y, localCenter.z) | |||
| } | |||
| // Add offsets | |||
| this.children.forEach((object)=> { | |||
| object.position.add(localCenter) | |||
| }) | |||
| if (setDirty) this.setDirty({change: 'pivotToPoint', undo: false}) | |||
| return ()=>{ | |||
| // undo | |||
| this.position.copy(lastPosition) | |||
| if (this.geometry) { | |||
| this.geometry.translate(-localCenter.x, -localCenter.y, -localCenter.z) | |||
| } | |||
| this.children.forEach((object)=> { | |||
| object.position.sub(localCenter) | |||
| }) | |||
| if (setDirty) this.setDirty({change: 'pivotToPoint', undo: true}) | |||
| } | |||
| }, | |||
| eventCallbacks: { | |||
| onAddedToParent: function(this: IObject3D, e: Event): void { | |||
| // added to some parent | |||
| @@ -401,6 +451,8 @@ function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectPr | |||
| if (!this.refreshUi) this.refreshUi = iObjectCommons.refreshUi | |||
| if (!this.autoScale) this.autoScale = iObjectCommons.autoScale.bind(this) | |||
| if (!this.autoCenter) this.autoCenter = iObjectCommons.autoCenter.bind(this) | |||
| if (!this.pivotToBoundsCenter) this.pivotToBoundsCenter = iObjectCommons.pivotToBoundsCenter.bind(this) | |||
| if (!this.pivotToPoint) this.pivotToPoint = iObjectCommons.pivotToPoint.bind(this) | |||
| // fired from Object3D.js | |||
| this.addEventListener('added', iObjectCommons.eventCallbacks.onAddedToParent) | |||
| @@ -5,9 +5,10 @@ import {Box3B} from '../../three' | |||
| import {onChange, serialize, timeout} from 'ts-browser-helpers' | |||
| import {generateUiConfig, uiButton, uiDropdown, uiInput, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {EasingFunctions, EasingFunctionType} from '../../utils' | |||
| import {CameraView, ICamera, ICameraView, PerspectiveCamera2} from '../../core' | |||
| import {CameraView, ICamera, ICameraView} from '../../core' | |||
| import {AnimationResult, PopmotionPlugin} from './PopmotionPlugin' | |||
| import {InteractionPromptPlugin} from '../interaction/InteractionPromptPlugin' | |||
| import {getFittingDistance} from '../../three/utils/camera' | |||
| export interface CameraViewPluginOptions{duration?: number, ease?: EasingFunctionType, interpolateMode?: 'spherical'|'linear'} | |||
| @@ -338,22 +339,8 @@ export class CameraViewPlugin extends AViewerPluginSync<'viewChange'|'startViewC | |||
| public async animateToFitObject(selected?: Object3D, distanceMultiplier = 1.5, duration = 1000, ease?: Easing|EasingFunctionType, distanceBounds = {min: 0.5, max: 50.0}) { | |||
| if (!this._viewer) return | |||
| const bbox = new Box3B().expandByObject(selected || this._viewer.scene.modelRoot, false, true) | |||
| const cameraZ = getFittingDistance(this._viewer.scene.mainCamera, bbox) | |||
| const center = bbox.getCenter(new Vector3()) // world position | |||
| const size = bbox.getSize(new Vector3()) | |||
| const cam = this._viewer.scene.mainCamera | |||
| let cameraZ = 1 | |||
| if (cam.isPerspectiveCamera && size.length() > 0.0001) { | |||
| const aspect = isFinite(cam.aspect) ? cam.aspect : 1 | |||
| // get the max side of the bounding box (fits to width OR height as needed ) | |||
| const fov = Math.max(1, (cam as PerspectiveCamera2).fov) * (Math.PI / 180) | |||
| const fovh = 2 * Math.atan(Math.tan(fov / 2) * aspect) | |||
| const dx = size.z / 2 + Math.abs(size.x / 2 / Math.tan(fovh / 2)) | |||
| const dy = size.z / 2 + Math.abs(size.y / 2 / Math.tan(fov / 2)) | |||
| cameraZ = Math.max(dx, dy) | |||
| } | |||
| await this.animateToTarget(Math.min(distanceBounds.max, Math.max(distanceBounds.min, cameraZ * distanceMultiplier)), center, duration, ease) | |||
| } | |||
| @@ -162,7 +162,7 @@ export class TransformControlsPlugin extends AViewerPluginSync<''> { | |||
| @uiButton('Center All Meshes') | |||
| centerAllMeshes() { | |||
| this._viewer?.scene.centerAllGeometries(true) | |||
| return this._viewer?.scene.centerAllGeometries(true) | |||
| } | |||
| } | |||
| @@ -3,6 +3,7 @@ import {RenderPass} from 'three/examples/jsm/postprocessing/RenderPass.js' | |||
| import {IPassID, IPipelinePass} from './Pass' | |||
| import {ICamera, IMaterial, IRenderManager, IScene, IWebGLRenderer, PhysicalMaterial} from '../core' | |||
| import {uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js' | |||
| import {getOrCall, ValOrFunc} from 'ts-browser-helpers' | |||
| @uiFolderContainer<GBufferRenderPass>((c)=>c.passId + ' Render Pass') | |||
| export class GBufferRenderPass<TP extends IPassID=IPassID, T extends WebGLMultipleRenderTargets | WebGLRenderTarget=WebGLMultipleRenderTargets | WebGLRenderTarget> extends RenderPass implements IPipelinePass<TP> { // todo: extend from jittered? | |||
| @@ -16,7 +17,7 @@ export class GBufferRenderPass<TP extends IPassID=IPassID, T extends WebGLMultip | |||
| after?: IPassID[] | |||
| required?: IPassID[] | |||
| constructor(public readonly passId: TP, public target: T, material: Material, clearColor: Color = new Color(1, 1, 1), clearAlpha = 1) { | |||
| constructor(public readonly passId: TP, public target: ValOrFunc<T>, material: Material, clearColor: Color = new Color(1, 1, 1), clearAlpha = 1) { | |||
| super(undefined, undefined, material, clearColor, clearAlpha) | |||
| } | |||
| @@ -74,7 +75,7 @@ export class GBufferRenderPass<TP extends IPassID=IPassID, T extends WebGLMultip | |||
| transparentRender: false, | |||
| transmissionRender: false, | |||
| mainRenderPass: false, | |||
| }, ()=> super.render(renderer, null, this.target, deltaTime as any, maskActive as any)) // here this.target is the write-buffer, variable writeBuffer is ignored | |||
| }, ()=> super.render(renderer, null, getOrCall(this.target), deltaTime as any, maskActive as any)) // here this.target is the write-buffer, variable writeBuffer is ignored | |||
| this._transparentMats.forEach(m => m.transparent = !m.transparent) | |||
| this._transparentMats.clear() | |||
| @@ -0,0 +1,23 @@ | |||
| import {Vector3} from 'three' | |||
| import {Box3B} from '../math/Box3B' | |||
| import {ICamera, PerspectiveCamera2} from '../../core' | |||
| /** | |||
| * Find distance of camera at which the camera's fov fits the given bounding box dimensions | |||
| * @param cam | |||
| * @param box | |||
| */ | |||
| export function getFittingDistance(cam: ICamera, box: Box3B): number { | |||
| const size = box.getSize(new Vector3()) | |||
| let cameraZ = 1 | |||
| if (cam.isPerspectiveCamera && size.length() > 0.0001) { | |||
| const aspect = isFinite(cam.aspect) ? cam.aspect : 1 | |||
| // get the max side of the bounding box (fits to width OR height as needed ) | |||
| const fov = Math.max(1, (cam as PerspectiveCamera2).fov) * (Math.PI / 180) | |||
| const fovh = 2 * Math.atan(Math.tan(fov / 2) * aspect) | |||
| const dx = size.z / 2 + Math.abs(size.x / 2 / Math.tan(fovh / 2)) | |||
| const dy = size.z / 2 + Math.abs(size.y / 2 / Math.tan(fov / 2)) | |||
| cameraZ = Math.max(dx, dy) | |||
| } | |||
| return cameraZ | |||
| } | |||
| @@ -66,7 +66,6 @@ import {DropzonePlugin, DropzonePluginOptions} from '../plugins/interaction/Drop | |||
| // noinspection ES6PreferShortImport | |||
| import {TonemapPlugin} from '../plugins/postprocessing/TonemapPlugin' | |||
| import {VERSION} from './version' | |||
| import {Easing} from 'popmotion' | |||
| import {OrbitControls3} from '../three' | |||
| export interface IViewerEvent extends BaseEvent, Partial<IAnimationLoopEvent> { | |||
| @@ -126,8 +125,10 @@ export interface ThreeViewerOptions { | |||
| */ | |||
| rgbm?: boolean | |||
| /** | |||
| * Use rendered gbuffer as depth-prepass / z-prepass. (Requires DepthBufferPlugin/GBufferPlugin) | |||
| * todo: It will be disabled when there are any transparent/transmissive objects with render to depth buffer enabled. | |||
| * Use rendered gbuffer as depth-prepass / z-prepass. (Requires DepthBufferPlugin/GBufferPlugin). | |||
| * Set it to true if you only have opaque objects in the scene to get better performance. | |||
| * | |||
| * todo fix: It will be disabled when there are any transparent/transmissive objects with render to depth buffer enabled, see forceZPrepass | |||
| */ | |||
| zPrepass?: boolean | |||
| /** | |||
| @@ -183,7 +184,7 @@ export interface ThreeViewerOptions { | |||
| target?: Vector3, | |||
| } | |||
| // values above this might be clamped in post processing | |||
| maxHDRIntensity?: number | |||
| @@ -1320,7 +1321,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| // this.fromJSON(config, config.resources) | |||
| // } | |||
| public async fitToView(selected?: Object3D, distanceMultiplier = 1.5, duration?: number, ease?: Easing|EasingFunctionType) { | |||
| public async fitToView(selected?: Object3D, distanceMultiplier = 1.5, duration?: number, ease?: ((v: number) => number)|EasingFunctionType) { | |||
| const camViews = this.getPlugin<CameraViewPlugin>('CameraViews') | |||
| if (!camViews) { | |||
| this.console.error('ThreeViewer: CameraViewPlugin (CameraViews) is required for fitToView to work') | |||