| return blob | return blob | ||||
| } | } | ||||
| applyMaterial(material: IMaterial, nameOrUuid: string): boolean { | |||||
| applyMaterial(material: IMaterial, nameRegexOrUuid: string, regex = true): boolean { | |||||
| const mType = Object.getPrototypeOf(material).constructor.TYPE | const mType = Object.getPrototypeOf(material).constructor.TYPE | ||||
| let currentMats = this.findMaterialsByName(nameOrUuid, true) | |||||
| if (!currentMats || currentMats.length < 1) currentMats = [this.findMaterial(nameOrUuid) as any] | |||||
| let currentMats = this.findMaterialsByName(nameRegexOrUuid, regex) | |||||
| if (!currentMats || currentMats.length < 1) currentMats = [this.findMaterial(nameRegexOrUuid) as any] | |||||
| let applied = false | let applied = false | ||||
| for (const c of currentMats) { | for (const c of currentMats) { | ||||
| // console.log(c) | // console.log(c) |
| // if (ext.transparent !== undefined) o.transparent = ext.transparent // this is set by GLTFLoader based on alpha mode | // if (ext.transparent !== undefined) o.transparent = ext.transparent // this is set by GLTFLoader based on alpha mode | ||||
| // if (ext.envMapIntensity !== undefined) o.envMapIntensity = ext.envMapIntensity // this is set in global by rootscene | |||||
| if (ext.envMapIntensity !== undefined) o.envMapIntensity = ext.envMapIntensity // for when separateEnvMapIntensity is true | |||||
| // if (ext.stencilWrite !== undefined) o.stencilWrite = ext.stencilWrite | // if (ext.stencilWrite !== undefined) o.stencilWrite = ext.stencilWrite | ||||
| // if (ext.stencilWriteMask !== undefined) o.stencilWriteMask = ext.stencilWriteMask | // if (ext.stencilWriteMask !== undefined) o.stencilWriteMask = ext.stencilWriteMask | ||||
| if (material.vertexColors !== undefined) dat.vertexColors = material.vertexColors // this is override, it is also set in GLTFLoader if geometry has vertex colors, todo: check how to do this in a better way | if (material.vertexColors !== undefined) dat.vertexColors = material.vertexColors // this is override, it is also set in GLTFLoader if geometry has vertex colors, todo: check how to do this in a better way | ||||
| if (material.alphaTest !== undefined) dat.alphaTest = material.alphaTest | if (material.alphaTest !== undefined) dat.alphaTest = material.alphaTest | ||||
| // if (material.envMapIntensity !== undefined) dat.envMapIntensity = material.envMapIntensity | |||||
| if (material.envMapIntensity !== undefined) dat.envMapIntensity = material.envMapIntensity // for when separateEnvMapIntensity is true | |||||
| // if (material.stencilWrite !== undefined) dat.stencilWrite = material.stencilWrite | // if (material.stencilWrite !== undefined) dat.stencilWrite = material.stencilWrite | ||||
| // if (material.stencilWriteMask !== undefined) dat.stencilWriteMask = material.stencilWriteMask | // if (material.stencilWriteMask !== undefined) dat.stencilWriteMask = material.stencilWriteMask |
| setDirty(options?: ICameraSetDirtyOptions|Event): void { | setDirty(options?: ICameraSetDirtyOptions|Event): void { | ||||
| if (!this._positionWorld) return // class not initialized | if (!this._positionWorld) return // class not initialized | ||||
| if (options?.key === 'fov' || options?.key === 'zoom') this.updateProjectionMatrix() | |||||
| if (!options?.key || options?.key === 'fov' || options?.key === 'zoom') this.updateProjectionMatrix() | |||||
| this.getWorldPosition(this._positionWorld) | this.getWorldPosition(this._positionWorld) | ||||
| bumpNormal: (material: PhysicalMaterial|LegacyPhongMaterial): UiObjectConfig => ( | bumpNormal: (material: PhysicalMaterial|LegacyPhongMaterial): UiObjectConfig => ( | ||||
| { | { | ||||
| type: 'folder', | type: 'folder', | ||||
| // uuid: 'bump_normal', | |||||
| label: 'Bump/Normal', | label: 'Bump/Normal', | ||||
| children: [ | children: [ | ||||
| { | { |
| color: new Color(1, 1, 1), | color: new Color(1, 1, 1), | ||||
| }, | }, | ||||
| generator: (params) => { | generator: (params) => { | ||||
| // todo: option to convert Phong to Physical(for fbx)? or add it to PhysicalMaterial.MaterialTemplate | |||||
| return new LegacyPhongMaterial(params) | return new LegacyPhongMaterial(params) | ||||
| }, | }, | ||||
| } | } |
| refreshScene(event?: Partial<ISceneEvent> & ISceneSetDirtyOptions): this { | refreshScene(event?: Partial<ISceneEvent> & ISceneSetDirtyOptions): this { | ||||
| if (event && event.type === 'objectUpdate' && event.object === this) return this // ignore self | if (event && event.type === 'objectUpdate' && event.object === this) return this // ignore self | ||||
| if (event?.sceneUpdate === false || event?.refreshScene === false) return this.setDirty(event) // so that it doesn't trigger frame fade, shadow refresh etc | |||||
| // todo test the isCamera here. this is for animation object plugin | |||||
| 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) | // console.warn(event) | ||||
| this.refreshActiveCameraNearFar() | this.refreshActiveCameraNearFar() | ||||
| this._sceneBounds = this.getBounds(false, true) | this._sceneBounds = this.getBounds(false, true) | ||||
| } | } | ||||
| /** | /** | ||||
| * Returns the bounding box of the scene model root. | |||||
| * Returns the bounding box of the whole scene (model root and other meta objects). | |||||
| * To get the bounds of just the objects added by the user(not by plugins) use `new Box3B().expandByObject(scene.modelRoot)` | |||||
| * @param precise | * @param precise | ||||
| * @param ignoreInvisible | * @param ignoreInvisible | ||||
| * @param ignoreWidgets | * @param ignoreWidgets | ||||
| }) | }) | ||||
| } | } | ||||
| /** | |||||
| * Similar to {@link getBounds}, but returns the bounding box of just the {@link modelRoot}. | |||||
| * @param precise | |||||
| * @param ignoreInvisible | |||||
| * @param ignoreWidgets | |||||
| * @param ignoreObject | |||||
| * @returns {Box3B} | |||||
| */ | |||||
| getModelBounds(precise = false, ignoreInvisible = true, ignoreWidgets = true, ignoreObject?: (obj: Object3D)=>boolean): Box3B { | |||||
| if (this.modelRoot == undefined) | |||||
| return new Box3B() | |||||
| return new Box3B().expandByObject(this.modelRoot, precise, ignoreInvisible, (o: any)=>{ | |||||
| if (ignoreWidgets && o.assetType === 'widget') return true | |||||
| return ignoreObject?.(o) ?? false | |||||
| }) | |||||
| } | |||||
| private _v1 = new Vector3() | private _v1 = new Vector3() | ||||
| private _v2 = new Vector3() | private _v2 = new Vector3() | ||||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | import {AViewerPluginSync, ThreeViewer} from '../../viewer' | ||||
| import {IGeometry, iGeometryCommons, IMaterial, ISceneEvent, Mesh2, PhysicalMaterial, UnlitMaterial} from '../../core' | import {IGeometry, iGeometryCommons, IMaterial, ISceneEvent, Mesh2, PhysicalMaterial, UnlitMaterial} from '../../core' | ||||
| import {BufferAttribute, Euler, InterleavedBufferAttribute, PlaneGeometry, Vector3} from 'three' | |||||
| import {BufferAttribute, BufferGeometry, Euler, InterleavedBufferAttribute, PlaneGeometry, Vector3} from 'three' | |||||
| import {onChange, onChange2, serialize} from 'ts-browser-helpers' | import {onChange, onChange2, serialize} from 'ts-browser-helpers' | ||||
| import {OrbitControls3} from '../../three' | import {OrbitControls3} from '../../three' | ||||
| import {uiConfig, uiFolderContainer, uiNumber, uiToggle} from 'uiconfig.js' | import {uiConfig, uiFolderContainer, uiNumber, uiToggle} from 'uiconfig.js' | ||||
| } | } | ||||
| // not serialized. this can be controlled by other plugins like ModelStagePlugin and serialized there | |||||
| useModelBounds = false | |||||
| protected _refreshTransform() { | protected _refreshTransform() { | ||||
| if (!this._mesh) return | if (!this._mesh) return | ||||
| if (!this._viewer) return | if (!this._viewer) return | ||||
| } | } | ||||
| if (this.autoAdjustTransform) { | if (this.autoAdjustTransform) { | ||||
| this._mesh.userData.bboxVisible = false | this._mesh.userData.bboxVisible = false | ||||
| const bbox = this._viewer.scene.getBounds(true) | |||||
| const bbox = this.useModelBounds ? | |||||
| this._viewer.scene.getModelBounds(true, true, true) : | |||||
| this._viewer.scene.getBounds(true, true, true) | |||||
| this._mesh.userData.bboxVisible = true | this._mesh.userData.bboxVisible = true | ||||
| const v = bbox.getCenter( | const v = bbox.getCenter( | ||||
| new Vector3()).sub(new Vector3(0, | new Vector3()).sub(new Vector3(0, | ||||
| if (mesh) { | if (mesh) { | ||||
| mesh.userData.physicsMass = 0 | mesh.userData.physicsMass = 0 | ||||
| mesh.userData.userSelectable = false | mesh.userData.userSelectable = false | ||||
| mesh.userData.isGroundMesh = true | |||||
| mesh.castShadow = true | mesh.castShadow = true | ||||
| mesh.receiveShadow = true | mesh.receiveShadow = true | ||||
| mesh.name = 'Ground Plane' | mesh.name = 'Ground Plane' | ||||
| return mesh | return mesh | ||||
| } | } | ||||
| setGeometry(g: BufferGeometry) { | |||||
| if (this._geometry) this._geometry.dispose() | |||||
| this._geometry = iGeometryCommons.upgradeGeometry.call(g) | |||||
| if (!this._geometry.attributes.uv2) { | |||||
| this._geometry.attributes.uv2 = (this._geometry.attributes.uv as any as BufferAttribute | InterleavedBufferAttribute).clone() | |||||
| this._geometry.attributes.uv2.needsUpdate = true | |||||
| } | |||||
| if (this._mesh) this._mesh.geometry = g | |||||
| } | |||||
| protected _createMaterial(material?: PhysicalMaterial): PhysicalMaterial { | protected _createMaterial(material?: PhysicalMaterial): PhysicalMaterial { | ||||
| if (!material) material = new PhysicalMaterial({ | if (!material) material = new PhysicalMaterial({ | ||||
| name: 'BaseGroundMaterial', | name: 'BaseGroundMaterial', |
| onChange: ()=>this.uiConfig.uiRefresh?.(true), | onChange: ()=>this.uiConfig.uiRefresh?.(true), | ||||
| }, | }, | ||||
| { | { | ||||
| type: 'checkbox', | |||||
| type: 'input', | |||||
| label: 'Encrypt Password', | label: 'Encrypt Password', | ||||
| hidden: ()=>!this.exportOptions.encrypt, | hidden: ()=>!this.exportOptions.encrypt, | ||||
| property: [this.exportOptions, 'encryptKey'], | property: [this.exportOptions, 'encryptKey'], | ||||
| }, | }, | ||||
| { | { | ||||
| type: 'button', | type: 'button', | ||||
| label: 'Export Viewer Config', | |||||
| label: 'Export Config', | |||||
| value: async()=>{ | value: async()=>{ | ||||
| const blob = new Blob([JSON.stringify(this._viewer?.exportConfig(false), null, 2)], {type: 'application/json'}) | const blob = new Blob([JSON.stringify(this._viewer?.exportConfig(false), null, 2)], {type: 'application/json'}) | ||||
| if (blob) await this._viewer?.exportBlob(blob, this.exportOptions.name + '.' + ThreeViewer.ConfigTypeSlug) | if (blob) await this._viewer?.exportBlob(blob, this.exportOptions.name + '.' + ThreeViewer.ConfigTypeSlug) |
| if (!this.initialized) { | if (!this.initialized) { | ||||
| await this.initialize() | await this.initialize() | ||||
| if (!this.initialized) { | if (!this.initialized) { | ||||
| await this._viewer.dialog.alert('SimplifyModifierPlugin cannot be initialized') | |||||
| await this._viewer.dialog.alert('Simplify: SimplifyModifierPlugin cannot be initialized') | |||||
| return | return | ||||
| } | } | ||||
| } | } | ||||
| const selected = this._pickingPlugin?.getSelectedObject() | const selected = this._pickingPlugin?.getSelectedObject() | ||||
| if (!selected) { | if (!selected) { | ||||
| await this._viewer.dialog.alert('Nothing Selected') | |||||
| await this._viewer.dialog.alert('Simplify: Nothing Selected') | |||||
| return | return | ||||
| } | } | ||||
| let doAll = false | let doAll = false | ||||
| if (!selected.geometry) doAll = true | if (!selected.geometry) doAll = true | ||||
| else if (selected.children.length === 0) doAll = true | else if (selected.children.length === 0) doAll = true | ||||
| if (!doAll) { | if (!doAll) { | ||||
| const resp = await this._viewer.dialog.confirm('Simplify all in hierarchy?') | |||||
| const resp = await this._viewer.dialog.confirm('Simplify: Simplify all in hierarchy?') | |||||
| if (resp) doAll = true | if (resp) doAll = true | ||||
| } | } | ||||
| if (doAll) { | if (doAll) { |
| async loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<Mesh> { | async loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<Mesh> { | ||||
| this.currentUrl = url | this.currentUrl = url | ||||
| const res = await super.loadAsync(url, onProgress) | const res = await super.loadAsync(url, onProgress) | ||||
| // console.log(res) | |||||
| this.currentUrl = '' | this.currentUrl = '' | ||||
| if (!res.children.length) throw new Error('No mesh found in USDZ file, note that usdc files are not supported.') | |||||
| // todo see three-usdz-loader | |||||
| return res | return res | ||||
| } | } | ||||
| @uiInput('Logo Image') | @uiInput('Logo Image') | ||||
| @onChange(LoadingScreenPlugin.prototype.refresh) | @onChange(LoadingScreenPlugin.prototype.refresh) | ||||
| @serialize() logoImage = 'https://static.webgi.xyz/logo.svg' | |||||
| @serialize() logoImage = 'https://threepipe.org/logo.svg' | |||||
| private _isPreviewing = false | private _isPreviewing = false | ||||
| private _previewState = new Map([['file.glb', {state: 'downloading', progress: 50}], ['environment.hdr', {state: 'adding'}]]) | private _previewState = new Map([['file.glb', {state: 'downloading', progress: 50}], ['environment.hdr', {state: 'adding'}]]) |
| {r?: number, g?: number, b?: number, a?: number, target?: IRenderTarget, depth?: boolean, stencil?: boolean, viewport?: Vector4}): void { | {r?: number, g?: number, b?: number, a?: number, target?: IRenderTarget, depth?: boolean, stencil?: boolean, viewport?: Vector4}): void { | ||||
| const color = this._renderer.getClearColor(new Color()) | const color = this._renderer.getClearColor(new Color()) | ||||
| const alpha = this._renderer.getClearAlpha() | const alpha = this._renderer.getClearAlpha() | ||||
| this._renderer.setClearColor(new Color(r ?? color.r, g ?? color.g, b ?? color.b)) | |||||
| this._renderer.setClearAlpha(a ?? alpha) | |||||
| this._renderer.setClearColor(new Color(r ?? color.r, g ?? color.g, b ?? color.b), a ?? alpha) | |||||
| const lastTarget = this._renderer.getRenderTarget() | const lastTarget = this._renderer.getRenderTarget() | ||||
| const activeCubeFace = this._renderer.getActiveCubeFace() | const activeCubeFace = this._renderer.getActiveCubeFace() | ||||
| const activeMipLevel = this._renderer.getActiveMipmapLevel() | const activeMipLevel = this._renderer.getActiveMipmapLevel() | ||||
| this._renderer.setRenderTarget((target as WebGLRenderTarget) ?? null) | this._renderer.setRenderTarget((target as WebGLRenderTarget) ?? null) | ||||
| this._renderer.clear(true, depth, stencil) | this._renderer.clear(true, depth, stencil) | ||||
| if (target && typeof target.clear === 'function') { | |||||
| // WebGLCubeRenderTarget | |||||
| target.clear(this._renderer, true, depth, stencil) | |||||
| } else { | |||||
| this._renderer.setRenderTarget((target as any as WebGLRenderTarget) ?? null) | |||||
| this._renderer.clear(true, depth, stencil) | |||||
| } | |||||
| if (viewport) { | if (viewport) { | ||||
| if (!target) { | if (!target) { | ||||
| } | } | ||||
| this._renderer.setRenderTarget(lastTarget, activeCubeFace, activeMipLevel) | this._renderer.setRenderTarget(lastTarget, activeCubeFace, activeMipLevel) | ||||
| this._renderer.setClearColor(color) | |||||
| this._renderer.setClearAlpha(alpha) | |||||
| this._renderer.setClearColor(color, alpha) | |||||
| } | } | ||||
| } from 'three' | } from 'three' | ||||
| import {Vector4} from 'three/src/math/Vector4' | import {Vector4} from 'three/src/math/Vector4' | ||||
| import {DepthTexture} from 'three/src/textures/DepthTexture' | import {DepthTexture} from 'three/src/textures/DepthTexture' | ||||
| import type {IRenderManager} from '../core' | |||||
| import type {IRenderManager, IWebGLRenderer} from '../core' | |||||
| import {ValOrArr} from 'ts-browser-helpers' | import {ValOrArr} from 'ts-browser-helpers' | ||||
| export interface IRenderTarget extends EventDispatcher { | export interface IRenderTarget extends EventDispatcher { | ||||
| isWebGLCubeRenderTarget?: boolean | isWebGLCubeRenderTarget?: boolean | ||||
| isWebGLMultipleRenderTargets?: boolean | isWebGLMultipleRenderTargets?: boolean | ||||
| clear?(renderer: IWebGLRenderer, color: boolean, depth: boolean, stencil: boolean): void | |||||
| readonly renderManager?: IRenderManager | readonly renderManager?: IRenderManager | ||||
| } | } |
| scale?: number, | scale?: number, | ||||
| timeout?: number, // in ms, if not specified, will be based on progressive rendering or 200ms | timeout?: number, // in ms, if not specified, will be based on progressive rendering or 200ms | ||||
| displayPixelRatio?: number, | displayPixelRatio?: number, | ||||
| cloneCanvas?: boolean, // default = true | |||||
| } | } | ||||
| export class CanvasSnapshot { | export class CanvasSnapshot { | ||||
| } | } | ||||
| public static async GetDataUrl(canvas: HTMLCanvasElement, {mimeType = 'image/png', quality, ...options}: CanvasSnapshotOptions): Promise<string> { | public static async GetDataUrl(canvas: HTMLCanvasElement, {mimeType = 'image/png', quality, ...options}: CanvasSnapshotOptions): Promise<string> { | ||||
| const clone = await this.GetClonedCanvas(canvas, options) | |||||
| const clone = options.cloneCanvas === false ? canvas : await this.GetClonedCanvas(canvas, options) | |||||
| const url = clone.toDataURL(mimeType, quality) | const url = clone.toDataURL(mimeType, quality) | ||||
| if (!this.Debug) clone.remove() | |||||
| if (!this.Debug && clone !== canvas) clone.remove() | |||||
| return url | return url | ||||
| } | } | ||||
| } | } | ||||
| public static async GetBlob(canvas: HTMLCanvasElement, options: CanvasSnapshotOptions = {}): Promise<Blob> { | public static async GetBlob(canvas: HTMLCanvasElement, options: CanvasSnapshotOptions = {}): Promise<Blob> { | ||||
| const clone = await this.GetClonedCanvas(canvas, options) | |||||
| const clone = options.cloneCanvas === false ? canvas : await this.GetClonedCanvas(canvas, options) | |||||
| const blob = await new Promise<Blob>((resolve, reject) => { | const blob = await new Promise<Blob>((resolve, reject) => { | ||||
| clone.toBlob((b) => { | clone.toBlob((b) => { | ||||
| else reject() | else reject() | ||||
| }, options.mimeType ?? 'image/png', options.quality) | }, options.mimeType ?? 'image/png', options.quality) | ||||
| }) | }) | ||||
| if (!this.Debug) clone.remove() | |||||
| if (!this.Debug && clone !== canvas) clone.remove() | |||||
| return blob | return blob | ||||
| } | } |