| @@ -318,10 +318,10 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| return blob | |||
| } | |||
| applyMaterial(material: IMaterial, nameOrUuid: string): boolean { | |||
| applyMaterial(material: IMaterial, nameRegexOrUuid: string, regex = true): boolean { | |||
| 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 | |||
| for (const c of currentMats) { | |||
| // console.log(c) | |||
| @@ -48,7 +48,7 @@ export class GLTFMaterialExtrasExtension { | |||
| // 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.stencilWriteMask !== undefined) o.stencilWriteMask = ext.stencilWriteMask | |||
| @@ -158,7 +158,7 @@ export class GLTFMaterialExtrasExtension { | |||
| 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.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.stencilWriteMask !== undefined) dat.stencilWriteMask = material.stencilWriteMask | |||
| @@ -199,7 +199,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| setDirty(options?: ICameraSetDirtyOptions|Event): void { | |||
| 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) | |||
| @@ -300,6 +300,7 @@ export const iMaterialUI = { | |||
| bumpNormal: (material: PhysicalMaterial|LegacyPhongMaterial): UiObjectConfig => ( | |||
| { | |||
| type: 'folder', | |||
| // uuid: 'bump_normal', | |||
| label: 'Bump/Normal', | |||
| children: [ | |||
| { | |||
| @@ -294,6 +294,7 @@ export class LegacyPhongMaterial extends MeshPhongMaterial<IMaterialEvent, Phong | |||
| color: new Color(1, 1, 1), | |||
| }, | |||
| generator: (params) => { | |||
| // todo: option to convert Phong to Physical(for fbx)? or add it to PhysicalMaterial.MaterialTemplate | |||
| return new LegacyPhongMaterial(params) | |||
| }, | |||
| } | |||
| @@ -359,7 +359,8 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| refreshScene(event?: Partial<ISceneEvent> & ISceneSetDirtyOptions): this { | |||
| 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) | |||
| this.refreshActiveCameraNearFar() | |||
| this._sceneBounds = this.getBounds(false, true) | |||
| @@ -396,7 +397,8 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| } | |||
| /** | |||
| * 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 ignoreInvisible | |||
| * @param ignoreWidgets | |||
| @@ -411,6 +413,23 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| }) | |||
| } | |||
| /** | |||
| * 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 _v2 = new Vector3() | |||
| @@ -1,6 +1,6 @@ | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| 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 {OrbitControls3} from '../../three' | |||
| import {uiConfig, uiFolderContainer, uiNumber, uiToggle} from 'uiconfig.js' | |||
| @@ -176,6 +176,9 @@ export class BaseGroundPlugin<TEvent extends string = ''> extends AViewerPluginS | |||
| } | |||
| // not serialized. this can be controlled by other plugins like ModelStagePlugin and serialized there | |||
| useModelBounds = false | |||
| protected _refreshTransform() { | |||
| if (!this._mesh) return | |||
| if (!this._viewer) return | |||
| @@ -190,7 +193,11 @@ export class BaseGroundPlugin<TEvent extends string = ''> extends AViewerPluginS | |||
| } | |||
| if (this.autoAdjustTransform) { | |||
| 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 | |||
| const v = bbox.getCenter( | |||
| new Vector3()).sub(new Vector3(0, | |||
| @@ -221,6 +228,7 @@ export class BaseGroundPlugin<TEvent extends string = ''> extends AViewerPluginS | |||
| if (mesh) { | |||
| mesh.userData.physicsMass = 0 | |||
| mesh.userData.userSelectable = false | |||
| mesh.userData.isGroundMesh = true | |||
| mesh.castShadow = true | |||
| mesh.receiveShadow = true | |||
| mesh.name = 'Ground Plane' | |||
| @@ -228,6 +236,17 @@ export class BaseGroundPlugin<TEvent extends string = ''> extends AViewerPluginS | |||
| 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 { | |||
| if (!material) material = new PhysicalMaterial({ | |||
| name: 'BaseGroundMaterial', | |||
| @@ -113,7 +113,7 @@ export class AssetExporterPlugin extends AViewerPluginSync<''> { | |||
| onChange: ()=>this.uiConfig.uiRefresh?.(true), | |||
| }, | |||
| { | |||
| type: 'checkbox', | |||
| type: 'input', | |||
| label: 'Encrypt Password', | |||
| hidden: ()=>!this.exportOptions.encrypt, | |||
| property: [this.exportOptions, 'encryptKey'], | |||
| @@ -138,7 +138,7 @@ export class AssetExporterPlugin extends AViewerPluginSync<''> { | |||
| }, | |||
| { | |||
| type: 'button', | |||
| label: 'Export Viewer Config', | |||
| label: 'Export Config', | |||
| value: async()=>{ | |||
| 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) | |||
| @@ -180,20 +180,20 @@ export abstract class SimplifyModifierPlugin extends AViewerPluginSync<''> { | |||
| if (!this.initialized) { | |||
| await this.initialize() | |||
| if (!this.initialized) { | |||
| await this._viewer.dialog.alert('SimplifyModifierPlugin cannot be initialized') | |||
| await this._viewer.dialog.alert('Simplify: SimplifyModifierPlugin cannot be initialized') | |||
| return | |||
| } | |||
| } | |||
| const selected = this._pickingPlugin?.getSelectedObject() | |||
| if (!selected) { | |||
| await this._viewer.dialog.alert('Nothing Selected') | |||
| await this._viewer.dialog.alert('Simplify: Nothing Selected') | |||
| return | |||
| } | |||
| let doAll = false | |||
| if (!selected.geometry) doAll = true | |||
| else if (selected.children.length === 0) doAll = true | |||
| 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 (doAll) { | |||
| @@ -17,7 +17,10 @@ export class USDZLoadPlugin extends BaseImporterPlugin { | |||
| async loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<Mesh> { | |||
| this.currentUrl = url | |||
| const res = await super.loadAsync(url, onProgress) | |||
| // console.log(res) | |||
| 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 | |||
| } | |||
| @@ -88,7 +88,7 @@ export class LoadingScreenPlugin extends AAssetManagerProcessStatePlugin { | |||
| @uiInput('Logo Image') | |||
| @onChange(LoadingScreenPlugin.prototype.refresh) | |||
| @serialize() logoImage = 'https://static.webgi.xyz/logo.svg' | |||
| @serialize() logoImage = 'https://threepipe.org/logo.svg' | |||
| private _isPreviewing = false | |||
| private _previewState = new Map([['file.glb', {state: 'downloading', progress: 50}], ['environment.hdr', {state: 'adding'}]]) | |||
| @@ -471,8 +471,7 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| {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 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 activeCubeFace = this._renderer.getActiveCubeFace() | |||
| const activeMipLevel = this._renderer.getActiveMipmapLevel() | |||
| @@ -494,6 +493,13 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| this._renderer.setRenderTarget((target as WebGLRenderTarget) ?? null) | |||
| 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 (!target) { | |||
| @@ -508,8 +514,7 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| } | |||
| this._renderer.setRenderTarget(lastTarget, activeCubeFace, activeMipLevel) | |||
| this._renderer.setClearColor(color) | |||
| this._renderer.setClearAlpha(alpha) | |||
| this._renderer.setClearColor(color, alpha) | |||
| } | |||
| @@ -17,7 +17,7 @@ import { | |||
| } from 'three' | |||
| import {Vector4} from 'three/src/math/Vector4' | |||
| import {DepthTexture} from 'three/src/textures/DepthTexture' | |||
| import type {IRenderManager} from '../core' | |||
| import type {IRenderManager, IWebGLRenderer} from '../core' | |||
| import {ValOrArr} from 'ts-browser-helpers' | |||
| export interface IRenderTarget extends EventDispatcher { | |||
| @@ -67,6 +67,7 @@ export interface IRenderTarget extends EventDispatcher { | |||
| isWebGLCubeRenderTarget?: boolean | |||
| isWebGLMultipleRenderTargets?: boolean | |||
| clear?(renderer: IWebGLRenderer, color: boolean, depth: boolean, stencil: boolean): void | |||
| readonly renderManager?: IRenderManager | |||
| } | |||
| @@ -19,6 +19,7 @@ export interface CanvasSnapshotOptions { | |||
| scale?: number, | |||
| timeout?: number, // in ms, if not specified, will be based on progressive rendering or 200ms | |||
| displayPixelRatio?: number, | |||
| cloneCanvas?: boolean, // default = true | |||
| } | |||
| export class CanvasSnapshot { | |||
| @@ -102,9 +103,9 @@ export class CanvasSnapshot { | |||
| } | |||
| 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) | |||
| if (!this.Debug) clone.remove() | |||
| if (!this.Debug && clone !== canvas) clone.remove() | |||
| return url | |||
| } | |||
| @@ -120,7 +121,7 @@ export class CanvasSnapshot { | |||
| } | |||
| 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) => { | |||
| clone.toBlob((b) => { | |||
| @@ -128,7 +129,7 @@ export class CanvasSnapshot { | |||
| else reject() | |||
| }, options.mimeType ?? 'image/png', options.quality) | |||
| }) | |||
| if (!this.Debug) clone.remove() | |||
| if (!this.Debug && clone !== canvas) clone.remove() | |||
| return blob | |||
| } | |||