| @@ -14,6 +14,7 @@ import { | |||
| GLTFObject3DExtrasExtension, | |||
| GLTFViewerConfigExtension, | |||
| } from '../gltf' | |||
| import {GLTFMeshGpuInstancingExporter} from '../../three/utils/gpu-instancing' | |||
| export interface GLTFExporter2Options { | |||
| /** | |||
| @@ -201,6 +202,7 @@ export class GLTFExporter2 extends GLTFExporter implements IExportParser { | |||
| GLTFMaterialsDisplacementMapExtension.Export, | |||
| GLTFMaterialsLightMapExtension.Export, | |||
| GLTFMaterialsAlphaMapExtension.Export, | |||
| (w)=>new GLTFMeshGpuInstancingExporter(w), | |||
| ] | |||
| setup(viewer: ThreeViewer, extraExtensions?: ((parser: GLTFWriter2) => GLTFExporterPlugin)[]): this { | |||
| @@ -1,4 +1,5 @@ | |||
| import { | |||
| BufferGeometry, | |||
| Color, | |||
| EquirectangularReflectionMapping, | |||
| EventListener, | |||
| @@ -10,7 +11,7 @@ import { | |||
| } from 'three' | |||
| import type {IObject3D, IObjectProcessor} from '../IObject' | |||
| import {type ICamera} from '../ICamera' | |||
| import {bindToValue, Box3B} from '../../three' | |||
| import {autoGPUInstanceMeshes, bindToValue, Box3B} from '../../three' | |||
| import {AnyOptions, onChange2, onChange3, serialize} from 'ts-browser-helpers' | |||
| import {PerspectiveCamera2} from '../camera/PerspectiveCamera2' | |||
| import {ThreeSerialization} from '../../utils' | |||
| @@ -282,7 +283,7 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| this.setDirty({refreshScene: true}) | |||
| } | |||
| @uiButton(undefined, {sendArgs: false}) | |||
| @uiButton('Center All Geometries', {sendArgs: false}) | |||
| centerAllGeometries(keepPosition = true, obj?: IObject3D) { | |||
| const geoms = new Set<IGeometry>() | |||
| ;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry)) | |||
| @@ -450,6 +451,13 @@ export class RootScene extends Scene<ISceneEvent, ISceneEventTypes> implements I | |||
| }) | |||
| } | |||
| @uiButton('Auto GPU Instance Meshes') | |||
| autoGPUInstanceMeshes() { | |||
| const geoms = new Set<BufferGeometry>() | |||
| this.modelRoot.traverse((o) => o.geometry && geoms.add(o.geometry)) | |||
| geoms.forEach((g: any) => autoGPUInstanceMeshes(g)) | |||
| } | |||
| private _v1 = new Vector3() | |||
| private _v2 = new Vector3() | |||
| @@ -10,13 +10,14 @@ import { | |||
| NormalBlending, | |||
| NoToneMapping, | |||
| PCFShadowMap, | |||
| ShadowMapType, | |||
| Texture, | |||
| Vector2, | |||
| Vector4, | |||
| WebGLMultipleRenderTargets, | |||
| WebGLRenderer, | |||
| WebGLRenderTarget, | |||
| WebGLRenderTargetOptions, | |||
| WebGLRenderTargetOptions, WebGLShadowMap, | |||
| } from 'three' | |||
| import {EffectComposer2, IPassID, IPipelinePass, sortPasses} from '../postprocessing' | |||
| import {IRenderTarget} from './RenderTarget' | |||
| @@ -42,8 +43,8 @@ import { | |||
| serialize, | |||
| ValOrArr, | |||
| } from 'ts-browser-helpers' | |||
| import {uiButton, uiConfig, uiFolderContainer, uiMonitor, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {generateUUID, textureDataToImageData} from '../three' | |||
| import {uiButton, uiConfig, uiDropdown, uiFolderContainer, uiMonitor, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {bindToValue, generateUUID, textureDataToImageData} from '../three' | |||
| import {BlobExt, EXRExporter2} from '../assetmanager' | |||
| import {RendererBlitOptions} from '../core/IRenderer' | |||
| @@ -68,6 +69,19 @@ export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEven | |||
| } | |||
| } | |||
| @serialize() | |||
| @uiDropdown('Shadow Map Type', ['BasicShadowMap', 'PCFShadowMap', 'PCFSoftShadowMap', 'VSMShadowMap'].map((v, i) => ({label: v, value: i}))) | |||
| @bindToValue({obj: 'shadowMap', key: 'type', onChange: RenderManager.prototype._shadowMapTypeChanged}) | |||
| shadowMapType: ShadowMapType | |||
| @bindToValue({obj: 'renderer', key: 'shadowMap'}) | |||
| shadowMap: WebGLShadowMap | |||
| private _shadowMapTypeChanged() { | |||
| this.resetShadows() | |||
| this.reset() | |||
| } | |||
| @uiConfig(undefined, {label: 'Passes'}) | |||
| private _passes: IPipelinePass[] = [] | |||
| private _pipeline: IPassID[] = [] | |||
| @@ -1,7 +1,9 @@ | |||
| import {IGeometry, IMaterial, IObject3D} from '../../core' | |||
| import {BufferAttribute, InstancedMesh} from 'three' | |||
| import {BufferAttribute, InstancedMesh, Matrix4, Quaternion, Vector3} from 'three' | |||
| // noinspection ES6PreferShortImport | |||
| import {copyObject3DUserData} from '../../utils/serialization' | |||
| import {GLTFWriter2} from '../../assetmanager' | |||
| import {GLTFExporterPlugin} from 'three/examples/jsm/exporters/GLTFExporter' | |||
| export function autoGPUInstanceMeshes(matOrGeom: IMaterial|IGeometry) { | |||
| if (!(<IMaterial>matOrGeom).isMaterial && !(<IGeometry>matOrGeom).isBufferGeometry) return | |||
| @@ -84,3 +86,61 @@ export function autoGPUInstanceMeshes(matOrGeom: IMaterial|IGeometry) { | |||
| ;(parent as any).setDirty() | |||
| } | |||
| } | |||
| export class GLTFMeshGpuInstancingExporter implements GLTFExporterPlugin { | |||
| name = 'EXT_mesh_gpu_instancing' | |||
| constructor(public writer: GLTFWriter2) { | |||
| } | |||
| writeNode(object: any, nodeDef: any): void { | |||
| if (!object.isInstancedMesh) return | |||
| const writer = this.writer | |||
| const mesh = object as InstancedMesh | |||
| // @ts-expect-error not in ts | |||
| let attributes: any = mesh.sourceTrs | |||
| if (!attributes) { | |||
| const translationAttr = new Float32Array(mesh.count * 3) | |||
| const rotationAttr = new Float32Array(mesh.count * 4) | |||
| const scaleAttr = new Float32Array(mesh.count * 3) | |||
| const matrix = new Matrix4() | |||
| const position = new Vector3() | |||
| const quaternion = new Quaternion() | |||
| const scale = new Vector3() | |||
| for (let i = 0; i < mesh.count; i++) { | |||
| mesh.getMatrixAt(i, matrix) | |||
| matrix.decompose(position, quaternion, scale) | |||
| position.toArray(translationAttr, i * 3) | |||
| quaternion.toArray(rotationAttr, i * 4) | |||
| scale.toArray(scaleAttr, i * 3) | |||
| } | |||
| attributes = { | |||
| TRANSLATION: new BufferAttribute(translationAttr, 3), | |||
| ROTATION: new BufferAttribute(rotationAttr, 4), | |||
| SCALE: new BufferAttribute(scaleAttr, 3), | |||
| } | |||
| } | |||
| attributes = { | |||
| // @ts-expect-error todo add to ts | |||
| TRANSLATION: writer.processAccessor(attributes.TRANSLATION), | |||
| ROTATION: (writer as any).processAccessor(attributes.ROTATION), | |||
| SCALE: (writer as any).processAccessor(attributes.SCALE), | |||
| } | |||
| if (mesh.instanceColor) | |||
| attributes._COLOR_0 = (writer as any).processAccessor(mesh.instanceColor) | |||
| writer.extensionsUsed[ this.name ] = true | |||
| // @ts-expect-error todo add to ts | |||
| writer.extensionsRequired[ this.name ] = true | |||
| nodeDef.extensions = nodeDef.extensions || {} | |||
| nodeDef.extensions[ this.name ] = {attributes} | |||
| } | |||
| } | |||
| @@ -4,6 +4,8 @@ export type {AnyFunction, AnyOptions, Class, IDisposable, IJSONSerializable, Par | |||
| export type {Serializer} from 'ts-browser-helpers' | |||
| export {PointerDragHelper} from 'ts-browser-helpers' | |||
| export {JSUndoManager} from 'ts-browser-helpers' | |||
| export type {JSUndoManagerCommand2, JSUndoManagerCommand, JSUndoManagerOptions, JSUndoManagerCommand1} from 'ts-browser-helpers' | |||
| export {Damper} from 'ts-browser-helpers' | |||
| export {SimpleEventDispatcher} from 'ts-browser-helpers' | |||