| @@ -54,9 +54,10 @@ export class GLTFViewerConfigExtension { | |||
| viewerConfig.resources = await viewer.loadConfigResources(viewerConfig.resources || {}, extraResources) | |||
| if (resultScene) (resultScene as RootSceneImportResult).importedViewerConfig = viewerConfig | |||
| } | |||
| if (resultScene) (resultScene as RootSceneImportResult).importedViewerConfig = viewerConfig | |||
| return viewerConfig | |||
| } | |||
| @@ -11,10 +11,34 @@ import {CameraView, ICameraView} from './camera/CameraView' | |||
| export type TCameraControlsMode = '' | 'orbit' | 'deviceOrientation' | 'threeFirstPerson' | 'pointerLock' | string | |||
| 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, | |||
| __isMainCamera?: boolean, | |||
| @@ -21,6 +21,8 @@ import {RootSceneImportResult} from '../../assetmanager' | |||
| import {uiButton, uiColor, uiConfig, uiFolderContainer, uiImage, UiObjectConfig, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {IGeometry} from '../IGeometry' | |||
| export type TCamera = ICamera | |||
| @uiFolderContainer('Root Scene') | |||
| export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements IScene<ISceneEvent, ISceneEventTypes> { | |||
| readonly isRootScene = true | |||
| @@ -30,7 +32,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| // private _processors = new ObjectProcessorMap<'environment' | 'background'>() | |||
| // private _sceneObjects: ISceneObject[] = [] | |||
| private _mainCamera: ICamera | null = null | |||
| private _mainCamera: TCamera | null = null | |||
| /** | |||
| * The root object where all imported objects are added. | |||
| */ | |||
| @@ -72,17 +74,17 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| /** | |||
| * The default camera in the scene | |||
| */ | |||
| @uiConfig() @serialize() readonly defaultCamera: ICamera | |||
| @uiConfig() @serialize() readonly defaultCamera: TCamera | |||
| // private _environmentLight?: IEnvironmentLight | |||
| // 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 | |||
| } | |||
| set mainCamera(camera: ICamera | undefined) { | |||
| set mainCamera(camera: TCamera | undefined) { | |||
| const cam = this.mainCamera | |||
| if (!camera) camera = this.defaultCamera | |||
| if (cam === camera) return | |||
| @@ -102,11 +104,11 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| this.setDirty() | |||
| } | |||
| private _renderCamera: ICamera | undefined | |||
| private _renderCamera: TCamera | undefined | |||
| get renderCamera() { | |||
| return this._renderCamera ?? this.mainCamera | |||
| } | |||
| set renderCamera(camera: ICamera) { | |||
| set renderCamera(camera: TCamera) { | |||
| const cam = this._renderCamera | |||
| this._renderCamera = camera | |||
| this.dispatchEvent({type: 'renderCameraChange', lastCamera: cam, camera}) | |||
| @@ -117,7 +119,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| * @param camera | |||
| * @param objectProcessor | |||
| */ | |||
| constructor(camera: ICamera, objectProcessor?: IObjectProcessor) { | |||
| constructor(camera: TCamera, objectProcessor?: IObjectProcessor) { | |||
| super() | |||
| this.setDirty = this.setDirty.bind(this) | |||
| @@ -415,7 +417,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| * This is called automatically every time the camera is updated. | |||
| */ | |||
| refreshActiveCameraNearFar(): void { | |||
| const camera = this.mainCamera as ICamera | |||
| const camera = this.mainCamera as TCamera | |||
| if (!camera) return | |||
| if (!this.autoNearFarEnabled || camera.userData.autoNearFar === false) { | |||
| camera.near = camera.userData.minNearPlane ?? 0.5 | |||
| @@ -614,7 +616,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| /** | |||
| * @deprecated | |||
| */ | |||
| get activeCamera(): ICamera { | |||
| get activeCamera(): TCamera { | |||
| console.error('activeCamera is deprecated. Use mainCamera instead.') | |||
| return this.mainCamera | |||
| } | |||
| @@ -622,7 +624,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| /** | |||
| * @deprecated | |||
| */ | |||
| set activeCamera(camera: ICamera | undefined) { | |||
| set activeCamera(camera: TCamera | undefined) { | |||
| console.error('activeCamera is deprecated. Use mainCamera instead.') | |||
| this.mainCamera = camera | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import { | |||
| BaseEvent, | |||
| Color, | |||
| ColorSpace, | |||
| FloatType, | |||
| HalfFloatType, | |||
| IUniform, | |||
| @@ -353,6 +354,11 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| get composerTarget2(): IRenderTarget { | |||
| return this._composer.renderTarget2 | |||
| } | |||
| /** | |||
| * The size set in the three.js renderer. | |||
| * Final size is renderSize * renderScale | |||
| */ | |||
| get renderSize(): Vector2 { | |||
| return this._renderSize | |||
| } | |||
| @@ -614,9 +620,14 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| super(...ps) | |||
| this.uuid = generateUUID() | |||
| const ops = ps[ps.length - 1] as WebGLRenderTargetOptions | |||
| const colorSpace = ops?.colorSpace | |||
| this._initTexture(colorSpace) | |||
| } | |||
| private _initTexture(colorSpace?: ColorSpace) { | |||
| if (Array.isArray(this.texture)) { | |||
| this.texture.forEach(t => { | |||
| if (ops.colorSpace !== undefined) t.colorSpace = ops.colorSpace | |||
| if (colorSpace !== undefined) t.colorSpace = colorSpace | |||
| t._target = this | |||
| t.toJSON = () => { | |||
| console.warn('Multiple render target texture.toJSON not supported yet.') | |||
| @@ -625,6 +636,7 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| }) | |||
| } else { | |||
| this.texture._target = this | |||
| // if (colorSpace !== undefined) this.texture.colorSpace = colorSpace | |||
| this.texture.toJSON = () => ({ // todo use readRenderTargetPixels as data url or data buffer. | |||
| isRenderTargetTexture: true, | |||
| }) // so that it doesn't get serialized | |||
| @@ -641,12 +653,20 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| if (this.isTemporary) throw 'Cloning temporary 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 | |||
| 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 | |||
| if (Array.isArray(tex)) tex.forEach(t => t.isRenderTargetTexture = true) | |||
| else tex.isRenderTargetTexture = true | |||
| return processNewTarget(cloned, this.sizeMultiplier || 1, trackTarget) | |||
| } | |||
| copy(source: IRenderTarget|WebGLRenderTarget|WebGLMultipleRenderTargets): this { | |||
| super.copy(source as any) | |||
| return this | |||
| } | |||
| }(this, ...size, options) | |||
| } | |||
| @@ -11,6 +11,8 @@ import { | |||
| UnsignedInt248Type, | |||
| UnsignedIntType, | |||
| UnsignedShortType, | |||
| WebGLMultipleRenderTargets, | |||
| WebGLRenderTarget, | |||
| Wrapping, | |||
| } from 'three' | |||
| import {Vector4} from 'three/src/math/Vector4' | |||
| @@ -33,7 +35,7 @@ export interface IRenderTarget extends EventDispatcher { | |||
| targetKey?: string // for caching. | |||
| clone(trackTarget?: boolean): this | |||
| setSize(width: number, height: number, depth?: number): void; | |||
| copy(source: IRenderTarget): this; | |||
| copy(source: IRenderTarget|WebGLRenderTarget|WebGLMultipleRenderTargets): this; | |||
| dispose(): void; | |||
| scissor: Vector4; | |||
| @@ -84,10 +84,11 @@ export function matDefine(key?: string|symbol, customDefines?: any, thisMat = fa | |||
| set(newVal: any) { | |||
| const {t, p} = getTarget(thisMat ? this : this.material) | |||
| 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) | |||
| if (newVal === undefined) delete t[p] | |||
| if (onChange && typeof onChange === 'function') { | |||
| @@ -12,6 +12,7 @@ import {UiObjectConfig} from 'uiconfig.js' | |||
| 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 | |||
| public static readonly PluginType: string = 'AViewerPlugin' | |||
| public static readonly OldPluginType?: string | |||
| protected _dirty = false | |||
| uiConfig?: UiObjectConfig = undefined // todo: this should work when uncommented, remove all get uiConfig and do it properly | |||
| @@ -38,7 +39,7 @@ export abstract class AViewerPlugin<T extends string = string, TViewer extends T | |||
| } | |||
| 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 | |||
| ThreeSerialization.Deserialize(data, this, meta, true) | |||
| this.dispatchEvent({type: 'deserialize', data, meta}) | |||
| @@ -12,6 +12,7 @@ export interface IViewerPlugin<TViewer extends ThreeViewer = ThreeViewer, IsSync | |||
| // all classes must have this static property with a unique identifier value for this plugin | |||
| constructor: { | |||
| PluginType: string | |||
| OldPluginType?: string // rename to type alias maybe | |||
| } | |||
| // these plugins will be added automatically(with default settings), if they are not added yet. | |||
| @@ -252,7 +252,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| * Note: should be max (screen refresh rate / animation frame rate) like 60Hz / 30fps | |||
| * @type {number} | |||
| */ | |||
| public maxFramePerLoop = 1 | |||
| maxFramePerLoop = 1 | |||
| readonly debug: boolean | |||
| /** | |||
| @@ -738,6 +738,10 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| await this.removePlugin(this.plugins[type]) | |||
| } | |||
| 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) | |||
| this.dispatchEvent({type: 'addPlugin', target: this, plugin: p}) | |||
| this.setDirty(p) | |||
| @@ -765,6 +769,9 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| this.removePluginSync(this.plugins[type]) | |||
| } | |||
| 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) | |||
| this.dispatchEvent({type: 'addPlugin', target: this, plugin: p}) | |||
| this.setDirty(p) | |||
| @@ -1 +1 @@ | |||
| export const VERSION = '0.0.26' | |||
| export const VERSION = '0.0.27' | |||