| viewerConfig.resources = await viewer.loadConfigResources(viewerConfig.resources || {}, extraResources) | viewerConfig.resources = await viewer.loadConfigResources(viewerConfig.resources || {}, extraResources) | ||||
| if (resultScene) (resultScene as RootSceneImportResult).importedViewerConfig = viewerConfig | |||||
| } | } | ||||
| if (resultScene) (resultScene as RootSceneImportResult).importedViewerConfig = viewerConfig | |||||
| return viewerConfig | return viewerConfig | ||||
| } | } | ||||
| export type TCameraControlsMode = '' | 'orbit' | 'deviceOrientation' | 'threeFirstPerson' | 'pointerLock' | string | export type TCameraControlsMode = '' | 'orbit' | 'deviceOrientation' | 'threeFirstPerson' | 'pointerLock' | string | ||||
| export interface ICameraUserData extends IObject3DUserData { | export interface ICameraUserData extends IObject3DUserData { | ||||
| autoNearFar?: boolean // default = true | |||||
| minNearPlane?: number // default = 0.2 | |||||
| maxFarPlane?: number // default = 1000 | |||||
| autoLookAtTarget?: boolean // default = false, only for when controls and interactions are disabled | |||||
| /** | |||||
| * Automatically calculate near and far planes based on the scene bounding box. | |||||
| */ | |||||
| autoNearFar?: boolean | |||||
| /** | |||||
| * Minimum near plane distance. (when {@link autoNearFar} is true) | |||||
| * Or the near plane distance when {@link autoNearFar} is false. | |||||
| * @default 0.2 | |||||
| */ | |||||
| minNearPlane?: number | |||||
| /** | |||||
| * Maximum far plane distance. (when {@link autoNearFar} is true) | |||||
| * Or the far plane distance when {@link autoNearFar} is false. | |||||
| * @default 1000 | |||||
| */ | |||||
| maxFarPlane?: number | |||||
| /** | |||||
| * Automatically rotate camera to look at(lookAt) the target. | |||||
| * Only for when controls and interactions are disabled. | |||||
| * @default false | |||||
| */ | |||||
| autoLookAtTarget?: boolean | |||||
| /** | |||||
| * Disable jitter for this camera. (for {@link SSAAPlugin}) | |||||
| * @default false | |||||
| */ | |||||
| disableJitter?: boolean | |||||
| __lastScale?: Vector3, | __lastScale?: Vector3, | ||||
| __isMainCamera?: boolean, | __isMainCamera?: boolean, |
| import {uiButton, uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | import {uiButton, uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | ||||
| import {IGeometry} from '../IGeometry' | import {IGeometry} from '../IGeometry' | ||||
| export type TCamera = ICamera | |||||
| @uiFolderContainer('Root Scene') | @uiFolderContainer('Root Scene') | ||||
| export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements IScene<ISceneEvent, ISceneEventTypes> { | export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements IScene<ISceneEvent, ISceneEventTypes> { | ||||
| readonly isRootScene = true | readonly isRootScene = true | ||||
| // private _processors = new ObjectProcessorMap<'environment' | 'background'>() | // private _processors = new ObjectProcessorMap<'environment' | 'background'>() | ||||
| // private _sceneObjects: ISceneObject[] = [] | // private _sceneObjects: ISceneObject[] = [] | ||||
| private _mainCamera: ICamera | null = null | |||||
| private _mainCamera: TCamera | null = null | |||||
| /** | /** | ||||
| * The root object where all imported objects are added. | * The root object where all imported objects are added. | ||||
| */ | */ | ||||
| /** | /** | ||||
| * The default camera in the scene | * The default camera in the scene | ||||
| */ | */ | ||||
| @uiConfig() @serialize() readonly defaultCamera: ICamera | |||||
| @uiConfig() @serialize() readonly defaultCamera: TCamera | |||||
| // private _environmentLight?: IEnvironmentLight | // private _environmentLight?: IEnvironmentLight | ||||
| // required just because we don't want activeCamera to be null. | // required just because we don't want activeCamera to be null. | ||||
| private _dummyCam = new PerspectiveCamera2('') as ICamera | |||||
| private _dummyCam = new PerspectiveCamera2('') as TCamera | |||||
| get mainCamera(): ICamera { | |||||
| get mainCamera(): TCamera { | |||||
| return this._mainCamera || this._dummyCam | return this._mainCamera || this._dummyCam | ||||
| } | } | ||||
| set mainCamera(camera: ICamera | undefined) { | |||||
| set mainCamera(camera: TCamera | undefined) { | |||||
| const cam = this.mainCamera | const cam = this.mainCamera | ||||
| if (!camera) camera = this.defaultCamera | if (!camera) camera = this.defaultCamera | ||||
| if (cam === camera) return | if (cam === camera) return | ||||
| this.setDirty() | this.setDirty() | ||||
| } | } | ||||
| private _renderCamera: ICamera | undefined | |||||
| private _renderCamera: TCamera | undefined | |||||
| get renderCamera() { | get renderCamera() { | ||||
| return this._renderCamera ?? this.mainCamera | return this._renderCamera ?? this.mainCamera | ||||
| } | } | ||||
| set renderCamera(camera: ICamera) { | |||||
| set renderCamera(camera: TCamera) { | |||||
| const cam = this._renderCamera | const cam = this._renderCamera | ||||
| this._renderCamera = camera | this._renderCamera = camera | ||||
| this.dispatchEvent({type: 'renderCameraChange', lastCamera: cam, camera}) | this.dispatchEvent({type: 'renderCameraChange', lastCamera: cam, camera}) | ||||
| * @param camera | * @param camera | ||||
| * @param objectProcessor | * @param objectProcessor | ||||
| */ | */ | ||||
| constructor(camera: ICamera, objectProcessor?: IObjectProcessor) { | |||||
| constructor(camera: TCamera, objectProcessor?: IObjectProcessor) { | |||||
| super() | super() | ||||
| this.setDirty = this.setDirty.bind(this) | this.setDirty = this.setDirty.bind(this) | ||||
| * This is called automatically every time the camera is updated. | * This is called automatically every time the camera is updated. | ||||
| */ | */ | ||||
| refreshActiveCameraNearFar(): void { | refreshActiveCameraNearFar(): void { | ||||
| const camera = this.mainCamera as ICamera | |||||
| const camera = this.mainCamera as TCamera | |||||
| if (!camera) return | if (!camera) return | ||||
| if (!this.autoNearFarEnabled || camera.userData.autoNearFar === false) { | if (!this.autoNearFarEnabled || camera.userData.autoNearFar === false) { | ||||
| camera.near = camera.userData.minNearPlane ?? 0.5 | camera.near = camera.userData.minNearPlane ?? 0.5 | ||||
| /** | /** | ||||
| * @deprecated | * @deprecated | ||||
| */ | */ | ||||
| get activeCamera(): ICamera { | |||||
| get activeCamera(): TCamera { | |||||
| console.error('activeCamera is deprecated. Use mainCamera instead.') | console.error('activeCamera is deprecated. Use mainCamera instead.') | ||||
| return this.mainCamera | return this.mainCamera | ||||
| } | } | ||||
| /** | /** | ||||
| * @deprecated | * @deprecated | ||||
| */ | */ | ||||
| set activeCamera(camera: ICamera | undefined) { | |||||
| set activeCamera(camera: TCamera | undefined) { | |||||
| console.error('activeCamera is deprecated. Use mainCamera instead.') | console.error('activeCamera is deprecated. Use mainCamera instead.') | ||||
| this.mainCamera = camera | this.mainCamera = camera | ||||
| } | } |
| import { | import { | ||||
| BaseEvent, | BaseEvent, | ||||
| Color, | Color, | ||||
| ColorSpace, | |||||
| FloatType, | FloatType, | ||||
| HalfFloatType, | HalfFloatType, | ||||
| IUniform, | IUniform, | ||||
| get composerTarget2(): IRenderTarget { | get composerTarget2(): IRenderTarget { | ||||
| return this._composer.renderTarget2 | return this._composer.renderTarget2 | ||||
| } | } | ||||
| /** | |||||
| * The size set in the three.js renderer. | |||||
| * Final size is renderSize * renderScale | |||||
| */ | |||||
| get renderSize(): Vector2 { | get renderSize(): Vector2 { | ||||
| return this._renderSize | return this._renderSize | ||||
| } | } | ||||
| super(...ps) | super(...ps) | ||||
| this.uuid = generateUUID() | this.uuid = generateUUID() | ||||
| const ops = ps[ps.length - 1] as WebGLRenderTargetOptions | const ops = ps[ps.length - 1] as WebGLRenderTargetOptions | ||||
| const colorSpace = ops?.colorSpace | |||||
| this._initTexture(colorSpace) | |||||
| } | |||||
| private _initTexture(colorSpace?: ColorSpace) { | |||||
| if (Array.isArray(this.texture)) { | if (Array.isArray(this.texture)) { | ||||
| this.texture.forEach(t => { | this.texture.forEach(t => { | ||||
| if (ops.colorSpace !== undefined) t.colorSpace = ops.colorSpace | |||||
| if (colorSpace !== undefined) t.colorSpace = colorSpace | |||||
| t._target = this | t._target = this | ||||
| t.toJSON = () => { | t.toJSON = () => { | ||||
| console.warn('Multiple render target texture.toJSON not supported yet.') | console.warn('Multiple render target texture.toJSON not supported yet.') | ||||
| }) | }) | ||||
| } else { | } else { | ||||
| this.texture._target = this | this.texture._target = this | ||||
| // if (colorSpace !== undefined) this.texture.colorSpace = colorSpace | |||||
| this.texture.toJSON = () => ({ // todo use readRenderTargetPixels as data url or data buffer. | this.texture.toJSON = () => ({ // todo use readRenderTargetPixels as data url or data buffer. | ||||
| isRenderTargetTexture: true, | isRenderTargetTexture: true, | ||||
| }) // so that it doesn't get serialized | }) // so that it doesn't get serialized | ||||
| if (this.isTemporary) throw 'Cloning temporary render targets not supported' | if (this.isTemporary) throw 'Cloning temporary render targets not supported' | ||||
| if (Array.isArray(this.texture)) throw 'Cloning multiple render targets not supported' | if (Array.isArray(this.texture)) throw 'Cloning multiple render targets not supported' | ||||
| // Note: todo: webgl render target.clone messes up the texture, by not copying isRenderTargetTexture prop and maybe some other stuff. So its better to just create a new one | // Note: todo: webgl render target.clone messes up the texture, by not copying isRenderTargetTexture prop and maybe some other stuff. So its better to just create a new one | ||||
| const cloned = super.clone() as IRenderTarget | |||||
| // const cloned = super.clone() as IRenderTarget | |||||
| const cloned = new (this.constructor as Class<typeof this>)(this.renderManager) | |||||
| cloned.copy(this) | |||||
| cloned._initTexture((Array.isArray(this.texture) ? this.texture[0] : this.texture)?.colorSpace) | |||||
| const tex = cloned.texture | const tex = cloned.texture | ||||
| if (Array.isArray(tex)) tex.forEach(t => t.isRenderTargetTexture = true) | if (Array.isArray(tex)) tex.forEach(t => t.isRenderTargetTexture = true) | ||||
| else tex.isRenderTargetTexture = true | else tex.isRenderTargetTexture = true | ||||
| return processNewTarget(cloned, this.sizeMultiplier || 1, trackTarget) | return processNewTarget(cloned, this.sizeMultiplier || 1, trackTarget) | ||||
| } | } | ||||
| copy(source: IRenderTarget|WebGLRenderTarget|WebGLMultipleRenderTargets): this { | |||||
| super.copy(source as any) | |||||
| return this | |||||
| } | |||||
| }(this, ...size, options) | }(this, ...size, options) | ||||
| } | } | ||||
| UnsignedInt248Type, | UnsignedInt248Type, | ||||
| UnsignedIntType, | UnsignedIntType, | ||||
| UnsignedShortType, | UnsignedShortType, | ||||
| WebGLMultipleRenderTargets, | |||||
| WebGLRenderTarget, | |||||
| Wrapping, | Wrapping, | ||||
| } from 'three' | } from 'three' | ||||
| import {Vector4} from 'three/src/math/Vector4' | import {Vector4} from 'three/src/math/Vector4' | ||||
| targetKey?: string // for caching. | targetKey?: string // for caching. | ||||
| clone(trackTarget?: boolean): this | clone(trackTarget?: boolean): this | ||||
| setSize(width: number, height: number, depth?: number): void; | setSize(width: number, height: number, depth?: number): void; | ||||
| copy(source: IRenderTarget): this; | |||||
| copy(source: IRenderTarget|WebGLRenderTarget|WebGLMultipleRenderTargets): this; | |||||
| dispose(): void; | dispose(): void; | ||||
| scissor: Vector4; | scissor: Vector4; |
| set(newVal: any) { | set(newVal: any) { | ||||
| const {t, p} = getTarget(thisMat ? this : this.material) | const {t, p} = getTarget(thisMat ? this : this.material) | ||||
| if (processVal) newVal = processVal(newVal) | if (processVal) newVal = processVal(newVal) | ||||
| else if (typeof newVal === 'boolean') { // just in case | |||||
| console.error('Boolean values are not supported for defines. Use @matDefineBool instead.') | |||||
| newVal = newVal ? '1' : '0' | |||||
| } | |||||
| // boolean values are supported in material extender. | |||||
| // else if (typeof newVal === 'boolean') { // just in case | |||||
| // console.error('Boolean values are not supported for defines. Use @matDefineBool instead.') | |||||
| // newVal = newVal ? '1' : '0' | |||||
| // } | |||||
| safeSetProperty(t, p, newVal, true) | safeSetProperty(t, p, newVal, true) | ||||
| if (newVal === undefined) delete t[p] | if (newVal === undefined) delete t[p] | ||||
| if (onChange && typeof onChange === 'function') { | if (onChange && typeof onChange === 'function') { |
| export abstract class AViewerPlugin<T extends string = string, TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> extends EventDispatcher<Event, T|'serialize'|'deserialize'> implements IViewerPlugin<TViewer, IsSync> { | export abstract class AViewerPlugin<T extends string = string, TViewer extends ThreeViewer = ThreeViewer, IsSync extends boolean = boolean> extends EventDispatcher<Event, T|'serialize'|'deserialize'> implements IViewerPlugin<TViewer, IsSync> { | ||||
| declare ['constructor']: typeof AViewerPlugin | declare ['constructor']: typeof AViewerPlugin | ||||
| public static readonly PluginType: string = 'AViewerPlugin' | public static readonly PluginType: string = 'AViewerPlugin' | ||||
| public static readonly OldPluginType?: string | |||||
| protected _dirty = false | protected _dirty = false | ||||
| uiConfig?: UiObjectConfig = undefined // todo: this should work when uncommented, remove all get uiConfig and do it properly | uiConfig?: UiObjectConfig = undefined // todo: this should work when uncommented, remove all get uiConfig and do it properly | ||||
| } | } | ||||
| fromJSON(data: ISerializedConfig, meta?: SerializationMetaType): this|null|Promise<this|null> { | fromJSON(data: ISerializedConfig, meta?: SerializationMetaType): this|null|Promise<this|null> { | ||||
| if (data.type !== this.constructor.PluginType) | |||||
| if (data.type !== this.constructor.PluginType && data.type !== this.constructor.OldPluginType) | |||||
| return null | return null | ||||
| ThreeSerialization.Deserialize(data, this, meta, true) | ThreeSerialization.Deserialize(data, this, meta, true) | ||||
| this.dispatchEvent({type: 'deserialize', data, meta}) | this.dispatchEvent({type: 'deserialize', data, meta}) |
| // all classes must have this static property with a unique identifier value for this plugin | // all classes must have this static property with a unique identifier value for this plugin | ||||
| constructor: { | constructor: { | ||||
| PluginType: string | PluginType: string | ||||
| OldPluginType?: string // rename to type alias maybe | |||||
| } | } | ||||
| // these plugins will be added automatically(with default settings), if they are not added yet. | // these plugins will be added automatically(with default settings), if they are not added yet. |
| * Note: should be max (screen refresh rate / animation frame rate) like 60Hz / 30fps | * Note: should be max (screen refresh rate / animation frame rate) like 60Hz / 30fps | ||||
| * @type {number} | * @type {number} | ||||
| */ | */ | ||||
| public maxFramePerLoop = 1 | |||||
| maxFramePerLoop = 1 | |||||
| readonly debug: boolean | readonly debug: boolean | ||||
| /** | /** | ||||
| await this.removePlugin(this.plugins[type]) | await this.removePlugin(this.plugins[type]) | ||||
| } | } | ||||
| this.plugins[type] = p | this.plugins[type] = p | ||||
| const oldType = p.constructor.OldPluginType | |||||
| if (oldType && this.plugins[oldType]) this.console.error('Plugin type mismatch') | |||||
| if (oldType) this.plugins[oldType] = p | |||||
| await p.onAdded(this) | await p.onAdded(this) | ||||
| this.dispatchEvent({type: 'addPlugin', target: this, plugin: p}) | this.dispatchEvent({type: 'addPlugin', target: this, plugin: p}) | ||||
| this.setDirty(p) | this.setDirty(p) | ||||
| this.removePluginSync(this.plugins[type]) | this.removePluginSync(this.plugins[type]) | ||||
| } | } | ||||
| this.plugins[type] = p | this.plugins[type] = p | ||||
| const oldType = p.constructor.OldPluginType | |||||
| if (oldType && this.plugins[oldType]) this.console.error('Plugin type mismatch') | |||||
| if (oldType) this.plugins[oldType] = p | |||||
| p.onAdded(this) | p.onAdded(this) | ||||
| this.dispatchEvent({type: 'addPlugin', target: this, plugin: p}) | this.dispatchEvent({type: 'addPlugin', target: this, plugin: p}) | ||||
| this.setDirty(p) | this.setDirty(p) |
| export const VERSION = '0.0.26' | |||||
| export const VERSION = '0.0.27' |