| @@ -13,7 +13,7 @@ A new way to work with three.js, 3D models and rendering on the web. | |||
| [](https://opensource.org/license/apache-2-0/) | |||
| [](https://twitter.com/repalash) | |||
| ThreePipe is a 3D framework built on top of [three.js](https://threejs.org/) in TypeScript with a focus on rendering quality, modularity, and extensibility. | |||
| ThreePipe is a modern 3D framework built on top of [three.js](https://threejs.org/), written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable. | |||
| Key features include: | |||
| - Simple, intuitive API for creating 3D model viewers/configurators/editors on web pages, with many built-in presets for common workflows and use-cases. | |||
| @@ -28,6 +28,8 @@ Key features include: | |||
| - Automatic disposal of all three.js resources with built-in reference management. | |||
| - Realtime Realistic Rendering with screen-space post-processing effects from [webgi](https://webgi.dev/). | |||
| Checkout the documentation and guides on the [threepipe website](https://threepipe.org) for more details. | |||
| ## Examples | |||
| Code samples and demos covering various usecases and test are present in the [examples](./examples/) folder. | |||
| @@ -40,13 +42,23 @@ To make changes and run the example, click on the CodePen button on the top righ | |||
| ## Getting Started | |||
| Checkout the full [Getting Started Guide](https://threepipe.org/guide/getting-started.html) on [threepipe.com](https://threepipe.com) | |||
| ### Local Setup | |||
| To create a new project locally | |||
| ```npm create threepipe@latest``` | |||
| And follow the instructions to create a new project. | |||
| ### HTML/JS Quickstart (CDN) | |||
| ```html | |||
| <canvas id="three-canvas" style="width: 800px; height: 600px;"></canvas> | |||
| <script type="module"> | |||
| import {ThreeViewer, DepthBufferPlugin} from 'https://threepipe.org/dist/index.mjs' | |||
| import {ThreeViewer, DepthBufferPlugin} from 'https://unpkg.com/threepipe@latest/dist/index.mjs' | |||
| const viewer = new ThreeViewer({canvas: document.getElementById('three-canvas')}) | |||
| @@ -71,7 +83,9 @@ Check out the details about the [ThreeViewer API](https://threepipe.org/guide/vi | |||
| ### React | |||
| A sample [react](https://react.dev) component in tsx to render a model with an environment map. | |||
| The best way to use the viewer in react is to wrap it in a custom component. | |||
| Here is a sample [react](https://react.dev) component in tsx to render a model with an environment map. | |||
| ```tsx | |||
| import React from 'react' | |||
| @@ -133,7 +147,7 @@ Check it in action: https://threepipe.org/examples/#vue-html-sample/ | |||
| Another example with Vue SFC(Single file component): https://threepipe.org/examples/#vue-sfc-sample/ | |||
| ### Svelte | |||
| ### Svelte (4) | |||
| A sample [svelte](https://svelte.dev/) component in js to render a model with an environment map. | |||
| @@ -201,7 +215,7 @@ The 3D model can be opened in the [editor](https://threepipe.org/examples/tweakp | |||
| The viewer initializes with a Scene, Camera, Camera controls(Orbit Controls), several importers, exporters and a default rendering pipeline. Additional functionality can be added with plugins. | |||
| Check out the GLTF Load example to see it in action or to check the JS equivalent code: https://threepipe.org/examples/#gltf-load/ | |||
| Check out the glTF Load example to see it in action or to check the JS equivalent code: https://threepipe.org/examples/#gltf-load/ | |||
| Check out the [Plugins](https://threepipe.org/guide/features.html#plugin-system) section to learn how to add additional functionality to the viewer. | |||
| @@ -254,20 +268,20 @@ Many features will be added but the core API will not change significantly in fu | |||
| - [TonemapPlugin](https://threepipe.org/plugin/TonemapPlugin.html) - Add tonemap to the final screen pass | |||
| - [DropzonePlugin](https://threepipe.org/plugin/DropzonePlugin.html) - Drag and drop local files to import and load | |||
| - [ProgressivePlugin](https://threepipe.org/plugin/ProgressivePlugin.html) - Post-render pass to blend the last frame with the current frame | |||
| - [SSAAPlugin](https://threepipe.org/plugin/SSAAPlugin.html) - Add Super Sample Anti-Aliasing by applying jitter to the camera. | |||
| - [DepthBufferPlugin](https://threepipe.org/plugin/DepthBufferPlugin.html) - Pre-rendering of depth buffer | |||
| - [NormalBufferPlugin](https://threepipe.org/plugin/NormalBufferPlugin.html) - Pre-rendering of normal buffer | |||
| - [GBufferPlugin](https://threepipe.org/plugin/GBufferPlugin.html) - Pre-rendering of depth-normal and flags buffers in a single pass | |||
| - [SSAOPlugin](https://threepipe.org/plugin/SSAOPlugin.html) - Add SSAO(Screen Space Ambient Occlusion) for physical materials. | |||
| - [CanvasSnapshotPlugin](https://threepipe.org/plugin/CanvasSnapshotPlugin.html) - Add support for taking snapshots of the canvas | |||
| - [SSAAPlugin](https://threepipe.org/plugin/SSAAPlugin.html) - Add [Super Sample Anti-Aliasing](https://en.wikipedia.org/wiki/Supersampling) by applying jitter to the camera. | |||
| - [DepthBufferPlugin](https://threepipe.org/plugin/DepthBufferPlugin.html) - Pre-rendering of [depth buffer](https://en.wikipedia.org/wiki/Z-buffering) | |||
| - [NormalBufferPlugin](https://threepipe.org/plugin/NormalBufferPlugin.html) - Pre-rendering of normal buffer ([deferred shading](https://en.wikipedia.org/wiki/Deferred_shading)) | |||
| - [GBufferPlugin](https://threepipe.org/plugin/GBufferPlugin.html) - Pre-rendering of depth-normal and flags buffers in a single pass ([deferred shading](https://en.wikipedia.org/wiki/Deferred_shading)) | |||
| - [SSAOPlugin](https://threepipe.org/plugin/SSAOPlugin.html) - Add [SSAO(Screen Space Ambient Occlusion)](https://en.wikipedia.org/wiki/Screen_space_ambient_occlusion) for physical materials. | |||
| - [CanvasSnapshotPlugin](https://threepipe.org/plugin/CanvasSnapshotPlugin.html) - Add support for taking snapshots of the canvas with anti-aliasing and other options | |||
| - [PickingPlugin](https://threepipe.org/plugin/PickingPlugin.html) - Adds support for selecting objects in the viewer with user interactions and selection widgets | |||
| - [AssetExporterPlugin](https://threepipe.org/plugin/AssetExporterPlugin.html) - Provides options and methods to export the scene, object GLB or Viewer Configuration. | |||
| - [LoadingScreenPlugin](https://threepipe.org/plugin/LoadingScreenPlugin.html) - Shows a configurable loading screen overlay over the canvas. | |||
| - [AssetExporterPlugin](https://threepipe.org/plugin/AssetExporterPlugin.html) - Provides options and methods to export the scene/object GLB or Viewer Configuration JSON | |||
| - [LoadingScreenPlugin](https://threepipe.org/plugin/LoadingScreenPlugin.html) - Shows a configurable loading screen overlay over the canvas | |||
| - [FullScreenPlugin](https://threepipe.org/plugin/FullScreenPlugin.html) - Adds support for moving the canvas or the container fullscreen mode in browsers | |||
| - [InteractionPromptPlugin](https://threepipe.org/plugin/InteractionPromptPlugin.html) - Adds an animated hand icon over canvas to prompt the user to interact | |||
| - [TransformControlsPlugin](https://threepipe.org/plugin/TransformControlsPlugin.html) - Adds support for moving, rotating and scaling objects in the viewer with interactive widgets | |||
| - [ContactShadowGroundPlugin](https://threepipe.org/plugin/ContactShadowGroundPlugin.html) - Adds a ground plane at runtime with contact shadows | |||
| - [GLTFAnimationPlugin](https://threepipe.org/plugin/GLTFAnimationPlugin.html) - Add support for playing and seeking gltf animations | |||
| - [GLTFAnimationPlugin](https://threepipe.org/plugin/GLTFAnimationPlugin.html) - Add support for playing and seeking glTF animations | |||
| - [PopmotionPlugin](https://threepipe.org/plugin/PopmotionPlugin.html) - Integrates with popmotion.io library for animation/tweening | |||
| - [CameraViewPlugin](https://threepipe.org/plugin/CameraViewPlugin.html) - Add support for saving, loading, animating, looping between camera views | |||
| - [TransformAnimationPlugin](https://threepipe.org/plugin/TransformAnimationPlugin.html) - Add support for saving, loading, animating, between object transforms | |||
| @@ -275,39 +289,39 @@ Many features will be added but the core API will not change significantly in fu | |||
| - [GeometryUVPreviewPlugin](https://threepipe.org/plugin/GeometryUVPreviewPlugin.html) - Preview UVs of any geometry in a UI panel over the canvas | |||
| - [FrameFadePlugin](https://threepipe.org/plugin/FrameFadePlugin.html) - Post-render pass to smoothly fade to a new rendered frame over time | |||
| - [VignettePlugin](https://threepipe.org/plugin/VignettePlugin.html) - Add Vignette effect by patching the final screen pass | |||
| - [ChromaticAberrationPlugin](https://threepipe.org/plugin/ChromaticAberrationPlugin.html) - Add Chromatic Aberration effect by patching the final screen pass | |||
| - [FilmicGrainPlugin](https://threepipe.org/plugin/FilmicGrainPlugin.html) - Add Filmic Grain effect by patching the final screen pass | |||
| - [ChromaticAberrationPlugin](https://threepipe.org/plugin/ChromaticAberrationPlugin.html) - Add [Chromatic Aberration](https://en.wikipedia.org/wiki/Chromatic_aberration) effect by patching the final screen pass | |||
| - [FilmicGrainPlugin](https://threepipe.org/plugin/FilmicGrainPlugin.html) - Add [Filmic Grain](https://en.wikipedia.org/wiki/Film_grain) effect by patching the final screen pass | |||
| - [NoiseBumpMaterialPlugin](https://threepipe.org/plugin/NoiseBumpMaterialPlugin.html) - Sparkle Bump/Noise Bump material extension for PhysicalMaterial | |||
| - [CustomBumpMapPlugin](https://threepipe.org/plugin/CustomBumpMapPlugin.html) - Custom Bump Map material extension for PhysicalMaterial | |||
| - [CustomBumpMapPlugin](https://threepipe.org/plugin/CustomBumpMapPlugin.html) - Adds multiple bump map support and bicubic filtering material extension for PhysicalMaterial | |||
| - [ClearcoatTintPlugin](https://threepipe.org/plugin/ClearcoatTintPlugin.html) - Clearcoat Tint material extension for PhysicalMaterial | |||
| - [FragmentClippingExtensionPlugin](https://threepipe.org/plugin/FragmentClippingExtensionPlugin.html) - Fragment/SDF Clipping material extension for PhysicalMaterial | |||
| - [ParallaxMappingPlugin](https://threepipe.org/plugin/ParallaxMappingPlugin.html) - Relief Parallax Bump Mapping extension for PhysicalMaterial | |||
| - [HDRiGroundPlugin](https://threepipe.org/plugin/HDRiGroundPlugin.html) - Add support for ground projected hdri/skybox to the webgl background shader. | |||
| - [VirtualCamerasPlugin](https://threepipe.org/plugin/VirtualCamerasPlugin.html) - Add support for rendering virtual cameras before the main one every frame. | |||
| - [EditorViewWidgetPlugin](https://threepipe.org/plugin/EditorViewWidgetPlugin.html) - Adds an interactive ViewHelper/AxisHelper that syncs with the main camera. | |||
| - [EditorViewWidgetPlugin](https://threepipe.org/plugin/EditorViewWidgetPlugin.html) - Adds an interactive `ViewHelper`/`AxisHelper` that syncs with the main camera. | |||
| - [Object3DWidgetsPlugin](https://threepipe.org/plugin/Object3DWidgetsPlugin.html) - Automatically create light and camera helpers/gizmos when they are added to the scene. | |||
| - [Object3DGeneratorPlugin](https://threepipe.org/plugin/Object3DGeneratorPlugin.html) - Provides UI and API to create scene objects like lights, cameras, meshes, etc. | |||
| - [DeviceOrientationControlsPlugin](https://threepipe.org/plugin/DeviceOrientationControlsPlugin.html) - Adds a controlsMode to the mainCamera for device orientation controls(gyroscope rotation control). | |||
| - [PointerLockControlsPlugin](https://threepipe.org/plugin/PointerLockControlsPlugin.html) - Adds a controlsMode to the mainCamera for pointer lock controls. | |||
| - [ThreeFirstPersonControlsPlugin](https://threepipe.org/plugin/ThreeFirstPersonControlsPlugin.html) - Adds a controlsMode to the mainCamera for first person controls from threejs. | |||
| - [GLTFKHRMaterialVariantsPlugin](https://threepipe.org/plugin/GLTFKHRMaterialVariantsPlugin.html) - Support using for variants from KHR_materials_variants extension in gltf models. | |||
| - [Rhino3dmLoadPlugin](https://threepipe.org/plugin/Rhino3dmLoadPlugin.html) - Add support for loading .3dm files | |||
| - [Object3DGeneratorPlugin](https://threepipe.org/plugin/Object3DGeneratorPlugin.html) - Provides an API and UI to create scene objects like lights, cameras, meshes, etc. | |||
| - [DeviceOrientationControlsPlugin](https://threepipe.org/plugin/DeviceOrientationControlsPlugin.html) - Adds a `controlsMode` to the `mainCamera` for device orientation controls(gyroscope rotation control). | |||
| - [PointerLockControlsPlugin](https://threepipe.org/plugin/PointerLockControlsPlugin.html) - Adds a `controlsMode` to the `mainCamera` for pointer lock controls. | |||
| - [ThreeFirstPersonControlsPlugin](https://threepipe.org/plugin/ThreeFirstPersonControlsPlugin.html) - Adds a `controlsMode` to the `mainCamera` for first person controls from threejs. | |||
| - [GLTFKHRMaterialVariantsPlugin](https://threepipe.org/plugin/GLTFKHRMaterialVariantsPlugin.html) - Support using for variants from KHR_materials_variants extension in glTF models. | |||
| - [Rhino3dmLoadPlugin](https://threepipe.org/plugin/Rhino3dmLoadPlugin.html) - Add support for loading .3dm files ([Rhino 3D](https://www.rhino3d.com/)) | |||
| - [PLYLoadPlugin](https://threepipe.org/plugin/PLYLoadPlugin.html) - Add support for loading .ply files | |||
| - [STLLoadPlugin](https://threepipe.org/plugin/STLLoadPlugin.html) - Add support for loading .stl files | |||
| - [KTX2LoadPlugin](https://threepipe.org/plugin/KTX2LoadPlugin.html) - Add support for loading .ktx2 files | |||
| - [KTXLoadPlugin](https://threepipe.org/plugin/KTXLoadPlugin.html) - Add support for loading .ktx files | |||
| - [USDZLoadPlugin](https://threepipe.org/plugin/USDZLoadPlugin.html) - Add support for loading .usdz files | |||
| - [GLTFMeshOptDecodePlugin](https://threepipe.org/plugin/GLTFMeshOptDecodePlugin.html) - Decode gltf files with EXT_meshopt_compression extension. | |||
| - [STLLoadPlugin](https://threepipe.org/plugin/STLLoadPlugin.html) - Add support for loading .stl files ([STL](https://en.wikipedia.org/wiki/STL_(file_format))) | |||
| - [KTX2LoadPlugin](https://threepipe.org/plugin/KTX2LoadPlugin.html) - Add support for loading .ktx2 files ([KTX2 - GPU Compressed Textures](https://doc.babylonjs.com/features/featuresDeepDive/materials/using/ktx2Compression)) | |||
| - [KTXLoadPlugin](https://threepipe.org/plugin/KTXLoadPlugin.html) - Add support for loading .ktx files (Note - use ktx2) | |||
| - [USDZLoadPlugin](https://threepipe.org/plugin/USDZLoadPlugin.html) - Add partial support for loading .usdz, .usda files ([USDZ](https://en.wikipedia.org/wiki/Universal_Scene_Description)) | |||
| - [GLTFMeshOptDecodePlugin](https://threepipe.org/plugin/GLTFMeshOptDecodePlugin.html) - Decode glTF files with [EXT_meshopt_compression](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_meshopt_compression/README.md) extension. | |||
| - [SimplifyModifierPlugin](https://threepipe.org/plugin/SimplifyModifierPlugin.html) - Boilerplate for plugin to simplify geometries | |||
| - [MeshOptSimplifyModifierPlugin](https://threepipe.org/plugin/MeshOptSimplifyModifierPlugin.html) - Simplify geometries using meshoptimizer library | |||
| - [MeshOptSimplifyModifierPlugin](https://threepipe.org/plugin/MeshOptSimplifyModifierPlugin.html) - Simplify geometries using [meshoptimizer](https://github.com/zeux/meshoptimizer) library | |||
| - [Packages](https://threepipe.org/guide/threepipe-packages.html) | |||
| - [@threepipe/plugin-tweakpane](https://threepipe.org/package/plugin-tweakpane.html) Tweakpane UI Plugin | |||
| - [@threepipe/plugin-blueprintjs](https://threepipe.org/package/plugin-blueprintjs.html) BlueprintJs UI Plugin | |||
| - [@threepipe/plugin-tweakpane-editor](https://threepipe.org/package/plugin-tweakpane-editor.html) - Tweakpane Editor Plugin | |||
| - [@threepipe/plugin-configurator](https://threepipe.org/package/plugin-configurator.html) - Provides Material Configurator and Switch Node Plugin to allow users to select variations | |||
| - [@threepipe/plugin-gltf-transform](https://threepipe.org/package/plugin-gltf-transform.html) - Plugin to transform gltf models (draco compression) | |||
| - [@threepipe/plugin-tweakpane](https://threepipe.org/package/plugin-tweakpane.html) [Tweakpane](https://tweakpane.github.io/docs/) UI Plugin | |||
| - [@threepipe/plugin-blueprintjs](https://threepipe.org/package/plugin-blueprintjs.html) [BlueprintJs](https://blueprintjs.com/) UI Plugin | |||
| - [@threepipe/plugin-tweakpane-editor](https://threepipe.org/package/plugin-tweakpane-editor.html) - Editor Plugin using Tweakpane for plugin UI | |||
| - [@threepipe/plugin-configurator](../package/plugin-configurator) - Provides `MaterialConfiguratorPlugin` and `SwitchNodePlugin` to allow users to select variations | |||
| - [@threepipe/plugin-gltf-transform](https://threepipe.org/package/plugin-gltf-transform.html) - Plugin to transform glTF models (draco compression) | |||
| - [@threepipe/plugins-extra-importers](https://threepipe.org/package/plugins-extra-importers.html) - Plugin for loading more file types supported by loaders in three.js | |||
| - [@threepipe/plugin-blend-importer](https://threepipe.org/package/plugin-blend-importer.html) - Blender to add support for loading .blend file | |||
| - [@threepipe/plugin-blend-importer](https://threepipe.org/package/plugin-blend-importer.html) - Add support for loading .blend file. (Partial/WIP) ([Blender](https://www.blender.org/)) | |||
| - [@threepipe/plugin-geometry-generator](https://threepipe.org/package/plugin-geometry-generator.html) - Generate parametric geometry types that can be re-generated from UI/API. | |||
| - [@threepipe/plugin-gaussian-splatting](https://threepipe.org/package/plugin-gaussian-splatting.html) - Gaussian Splatting plugin for loading and rendering splat files | |||
| - [@threepipe/plugin-network](https://threepipe.org/package/plugin-network.html) - Network/Cloud related plugin implementations for Threepipe. | |||
| @@ -1,7 +1,7 @@ | |||
| { | |||
| "name": "threepipe", | |||
| "version": "0.0.40", | |||
| "description": "A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.", | |||
| "description": "A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.", | |||
| "main": "dist/index.js", | |||
| "module": "dist/index.mjs", | |||
| "types": "dist/index.d.ts", | |||
| @@ -286,7 +286,7 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| this.dispatchEvent({type: 'importFile', path, state:'downloading', progress: 0}) | |||
| let res: ImportResult | ImportResult[] | undefined | |||
| try { | |||
| const loader = this.registerFile(path, file) | |||
| const loader = this.registerFile(path, file, options.fileExtension, options.fileHandler) | |||
| // const url = this.resolveURL(path) // todo: why is this required? maybe for query string? | |||
| // const path2 = path.replace(/\?.*$/, '') // remove query string to find the handler properly | |||
| @@ -302,17 +302,20 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| // baseUrl: LoaderUtils.extractUrlBase(url), | |||
| } | |||
| loader.loadFileOptions = options | |||
| res = await loader.loadAsync(path + (options.queryString ? (path.includes('?') ? '&' : '?') + options.queryString : ''), (e)=>{ | |||
| if (onDownloadProgress) onDownloadProgress(e) | |||
| const total = e.lengthComputable ? e.total : undefined | |||
| this.dispatchEvent({ | |||
| type: 'importFile', path, | |||
| state:'downloading', | |||
| loadedBytes: e.loaded || undefined, | |||
| totalBytes: e.total || undefined, | |||
| progress: e.total > 0 ? e.loaded / e.total : 1, | |||
| totalBytes: total && total < e.loaded ? e.loaded : e.total || undefined, // sometimes total is more than e.loaded | |||
| progress: total && total > 0 && total > e.loaded ? e.loaded / total : 1, | |||
| }) | |||
| }) | |||
| if (loader.transform) res = await loader.transform(res, options) | |||
| delete loader.loadFileOptions | |||
| this._rootContext = undefined | |||
| @@ -369,12 +372,14 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| * Register a file in the database and return a loader for it. If the loader does not exist, it will be created. | |||
| * @param path | |||
| * @param file | |||
| * @param extension | |||
| * @param loader | |||
| */ | |||
| registerFile(path: string, file?: IFile): ILoader | undefined { | |||
| registerFile(path: string, file?: IFile, extension?: string, loader?: ILoader): ILoader | undefined { | |||
| const isData = path.startsWith('data:') || false | |||
| if (!isData) path = path.replace(/\?.*$/, '') // remove query string | |||
| const ext = isData ? undefined : file?.ext ?? parseFileExtension(file?.name ?? path.trim())?.toLowerCase() | |||
| const ext = extension || (isData ? undefined : file?.ext ?? parseFileExtension(file?.name ?? path.trim())?.toLowerCase()) | |||
| const mime = file?.mime ?? isData ? path.slice(0, path.indexOf(';')).split(':')[1] || undefined : undefined | |||
| if (file) { | |||
| @@ -388,7 +393,7 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| this._fileDatabase.set(path, file) | |||
| } | |||
| return this._getLoader(path) || this._createLoader(path, ext, mime) | |||
| return loader || this._getLoader(path, ext, mime) || this._createLoader(path, ext, mime) | |||
| } | |||
| /** | |||
| @@ -513,7 +518,17 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| return this._loadingManager.resolveURL(url) | |||
| } | |||
| protected _urlModifiers: ((url: string) => string)[] = [] | |||
| addURLModifier(modifier: (url: string) => string) { | |||
| this._urlModifiers.push(modifier) | |||
| } | |||
| removeURLModifier(modifier: (url: string) => string) { | |||
| const index = this._urlModifiers.indexOf(modifier) | |||
| if (index >= 0) this._urlModifiers.splice(index, 1) | |||
| } | |||
| protected _urlModifier(url: string) { | |||
| url = this._urlModifiers.reduce((acc, modifier) => modifier(acc), url) | |||
| let normalizedURL = decodeURI(url) | |||
| const rootUrl = this._rootContext?.rootUrl | |||
| if (!normalizedURL.includes('://') && rootUrl && !normalizedURL.startsWith(rootUrl)) | |||
| @@ -551,8 +566,7 @@ export class AssetImporter extends EventDispatcher<IAssetImporterEventMap> imple | |||
| if (isRoot && !importer.root) return false | |||
| if (mime && importer.mime?.find(m => mime === m)) return true | |||
| if (importer.ext.find(iext => | |||
| ext && iext === ext | |||
| || name?.toLowerCase()?.endsWith('.' + iext) | |||
| ext ? iext === ext : name?.toLowerCase()?.endsWith('.' + iext) | |||
| || iext?.startsWith('data:') && name?.startsWith(iext))) return true | |||
| return false | |||
| }) | |||
| @@ -116,10 +116,14 @@ export interface ProcessRawOptions { | |||
| } | |||
| export interface LoadFileOptions { | |||
| /** | |||
| * The file extension to use for the file. If not specified, the importer will try to determine the file extension from the file name/url. | |||
| */ | |||
| fileExtension?: string, | |||
| /** | |||
| * The custom {@link ILoader} to use for the file. If not specified, the importer will try to determine the loader from the file extension. | |||
| */ | |||
| fileHandler?: any, | |||
| fileHandler?: ILoader, | |||
| /** | |||
| * Query string to add to the url. Default = undefined | |||
| */ | |||
| @@ -234,5 +238,8 @@ export interface IAssetImporter<TE extends IAssetImporterEventMap = IAssetImport | |||
| */ | |||
| processRaw(res: any, options: ProcessRawOptions): Promise<any[]> | |||
| addURLModifier(modifier: (url: string) => string): void | |||
| removeURLModifier(modifier: (url: string) => string): void | |||
| } | |||
| @@ -1,15 +1,16 @@ | |||
| import {Loader} from 'three' | |||
| import {IAssetImporter} from './IAssetImporter' | |||
| import {AnyOptions, IDisposable} from 'ts-browser-helpers' | |||
| import {IAssetImporter, LoadFileOptions} from './IAssetImporter' | |||
| import {IDisposable} from 'ts-browser-helpers' | |||
| export interface ILoader<T = any, T2 = any> extends Loader, Partial<IDisposable> { | |||
| export interface ILoader<T = any, T2 = T> extends Loader, Partial<IDisposable> { | |||
| loadFileOptions?: LoadFileOptions | |||
| loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any>; | |||
| /** | |||
| * Transform after load, like convert geometry to mesh, etc. for reference see {@link DRACOLoader2} or {@link PLYLoadPlugin} | |||
| * @param res - result of load | |||
| * @param options | |||
| */ | |||
| transform?(res: T, options: AnyOptions): T2|Promise<T2> | |||
| transform?(res: T, options: LoadFileOptions): T2|Promise<T2> | |||
| } | |||
| export interface IImporter { | |||
| ext: string[]; | |||
| @@ -158,7 +158,7 @@ export class GLTFLoader2 extends GLTFLoader implements ILoader<GLTF, Object3D|un | |||
| this.setMeshoptDecoder(window.MeshoptDecoder) | |||
| parser.options.meshoptDecoder = window.MeshoptDecoder | |||
| } else { | |||
| console.error('Add GLTFMeshOptPlugin(and initialize it) to viewer to enable EXT_meshopt_compression decode') | |||
| console.error('Add GLTFMeshOptDecodePlugin(and initialize it) to viewer to enable EXT_meshopt_compression decode') | |||
| } | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| import {IMaterial, IMaterialEventMap} from './IMaterial' | |||
| import {EventListener2, Object3D, Object3DEventMap, Vector3} from 'three' | |||
| import {Box3, EventListener2, Object3D, Object3DEventMap, Sphere, Vector3} from 'three' | |||
| import {ChangeEvent, IUiConfigContainer, UiObjectConfig} from 'uiconfig.js' | |||
| import {IGeometry, IGeometryEventMap} from './IGeometry' | |||
| import {IImportResultUserData} from '../assetmanager' | |||
| @@ -373,6 +373,43 @@ export interface IObject3D<TE extends IObject3DEventMap = IObject3DEventMap> ext | |||
| */ | |||
| dispose(removeFromParent?: boolean): void; | |||
| /** | |||
| * A promise can be set by the object to indicate that the object is loading. | |||
| * This can be used by the scene, viewer, plugins to defer actions until the object is loaded. | |||
| */ | |||
| _loadingPromise?: Promise<void> | |||
| /** | |||
| * For InstancedMesh, SkinnedMesh etc | |||
| */ | |||
| boundingBox?: Box3 | null | |||
| /** | |||
| * For InstancedMesh, SkinnedMesh etc | |||
| */ | |||
| boundingSphere?: Sphere | null | |||
| /** | |||
| * For InstancedMesh, SkinnedMesh etc | |||
| * Computes bounding box, updating {@link boundingBox | .boundingBox} attribute. | |||
| * @remarks Bounding boxes aren't computed by default. They need to be explicitly computed, otherwise they are `null`. | |||
| */ | |||
| computeBoundingBox?(): void; | |||
| /** | |||
| * For InstancedMesh, SkinnedMesh etc | |||
| * Computes bounding sphere, updating {@link boundingSphere | .boundingSphere} attribute. | |||
| * @remarks bounding spheres aren't computed by default. They need to be explicitly computed, otherwise they are `null`. | |||
| */ | |||
| computeBoundingSphere?(): void; | |||
| /** | |||
| * Set to `false` to disable propagation of any events from its children. | |||
| */ | |||
| acceptChildEvents?: boolean | |||
| /** | |||
| * Set to `false` to disable automatic call of `upgradeObject3D` when a child is added. | |||
| */ | |||
| autoUpgradeChildren?: boolean | |||
| // region inherited type fixes | |||
| traverse(callback: (object: IObject3D) => void): void | |||
| @@ -29,6 +29,14 @@ export interface IRenderManagerEventMap { | |||
| contextLost: { | |||
| event: WebGLContextEvent | |||
| } | |||
| preRender: { | |||
| scene: IScene | |||
| renderToScreen: boolean | |||
| } | |||
| postRender: { | |||
| scene: IScene | |||
| renderToScreen: boolean | |||
| } | |||
| } | |||
| export interface RendererBlitOptions {source?: Texture, viewport?: Vector4, material?: ShaderMaterial, clear?: boolean, respectColorSpace?: boolean, blending?: Blending, transparent?: boolean} | |||
| @@ -1,5 +1,5 @@ | |||
| import {Camera, IUniform, Object3D, PerspectiveCamera, Vector3} from 'three' | |||
| import {generateUiConfig, uiInput, UiObjectConfig, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||
| import {generateUiConfig, uiInput, uiNumber, UiObjectConfig, uiSlider, uiToggle, uiVector} from 'uiconfig.js' | |||
| import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers' | |||
| import type {ICamera, ICameraEventMap, ICameraUserData, TCameraControlsMode} from '../ICamera' | |||
| import {ICameraSetDirtyOptions} from '../ICamera' | |||
| @@ -46,7 +46,7 @@ export class PerspectiveCamera2<TE extends ICameraEventMap = ICameraEventMap> ex | |||
| @serialize() declare focus: number | |||
| @onChange3(PerspectiveCamera2.prototype.setDirty) | |||
| @uiSlider('FoV Zoom', [0.001, 10], 0.001) | |||
| @uiNumber('FoV Zoom') | |||
| @serialize() declare zoom: number | |||
| @uiVector('Position', undefined, undefined, (that:PerspectiveCamera2)=>({onChange: ()=>that.setDirty()})) | |||
| @@ -218,7 +218,7 @@ export class PerspectiveCamera2<TE extends ICameraEventMap = ICameraEventMap> ex | |||
| this.getWorldPosition(this._positionWorld) | |||
| iCameraCommons.setDirty.call(this, options) | |||
| if (options?.last !== false) | |||
| this._camUi.forEach(u=>u?.uiRefresh?.(false, 'postFrame', 1)) // because camera changes a lot. so we dont want to deep refresh ui on every change | |||
| } | |||
| @@ -267,22 +267,26 @@ export class RootScene<TE extends ISceneEventMap = ISceneEventMap> extends Scene | |||
| if (addToRoot) this.add(obj) | |||
| else this.modelRoot.add(obj) | |||
| if (autoCenter && !obj.userData.isCentered && !obj.userData.pseudoCentered) { // pseudoCentered is legacy | |||
| obj.autoCenter?.() | |||
| } else { | |||
| obj.userData.isCentered = true // mark as centered, so that autoCenter is not called again when file is reloaded. | |||
| } | |||
| if (autoScale && !obj.userData.autoScaled) { | |||
| obj.autoScale?.(obj.userData.autoScaleRadius || autoScaleRadius) | |||
| } else { | |||
| obj.userData.autoScaled = true // mark as auto-scaled, so that autoScale is not called again when file is reloaded. | |||
| } | |||
| if (centerGeometries && !obj.userData.geometriesCentered) { | |||
| this.centerAllGeometries(centerGeometriesKeepPosition, obj) | |||
| obj.userData.geometriesCentered = true | |||
| } else { | |||
| obj.userData.geometriesCentered = true // mark as centered, so that geometry center is not called again when file is reloaded. | |||
| const process = () => { | |||
| if (autoCenter && !obj.userData.isCentered && !obj.userData.pseudoCentered) { // pseudoCentered is legacy | |||
| obj.autoCenter && obj.autoCenter() | |||
| } else { | |||
| obj.userData.isCentered = true // mark as centered, so that autoCenter is not called again when file is reloaded. | |||
| } | |||
| if (autoScale && !obj.userData.autoScaled) { | |||
| obj.autoScale && obj.autoScale(obj.userData.autoScaleRadius || autoScaleRadius) | |||
| } else { | |||
| obj.userData.autoScaled = true // mark as auto-scaled, so that autoScale is not called again when file is reloaded. | |||
| } | |||
| if (centerGeometries && !obj.userData.geometriesCentered) { | |||
| this.centerAllGeometries(centerGeometriesKeepPosition, obj) | |||
| obj.userData.geometriesCentered = true | |||
| } else { | |||
| obj.userData.geometriesCentered = true // mark as centered, so that geometry center is not called again when file is reloaded. | |||
| } | |||
| } | |||
| if (obj._loadingPromise) obj._loadingPromise.finally(process) | |||
| else process() | |||
| if (license) obj.userData.license = [obj.userData.license, license].filter(v=>v).join(', ') | |||
| @@ -502,19 +506,29 @@ export class RootScene<TE extends ISceneEventMap = ISceneEventMap> extends Scene | |||
| // todo check if this takes too much time with large scenes(when moving the camera and not animating), but we also need to support animations | |||
| const bbox = this.getBounds(false) // todo: can we use this._sceneBounds or will it have some issue with animation? | |||
| const size = bbox.getSize(this._v2).length() | |||
| if (size < 0.001) { | |||
| camera.near = camera.userData.minNearPlane ?? 0.5 | |||
| camera.far = camera.userData.maxFarPlane ?? 1000 | |||
| return | |||
| } | |||
| camera.getWorldPosition(this._v1).sub(bbox.getCenter(this._v2)) | |||
| const radius = 1.5 * bbox.getSize(this._v2).length() / 2. | |||
| const radius = 1.5 * Math.max(0.25, size) / 2. | |||
| const dist = this._v1.length() | |||
| // new way | |||
| const dist1 = Math.max(0.1, -this._v1.normalize().dot(camera.getWorldDirection(new Vector3()))) | |||
| const near = Math.max(Math.max(camera.userData.minNearPlane ?? 0.5, 0.001), dist1 * (dist - radius)) | |||
| const far = Math.min(Math.max(near + radius, dist1 * (dist + radius)), camera.userData.maxFarPlane ?? 1000) | |||
| let far = Math.min(Math.max(near + radius, dist1 * (dist + radius)), camera.userData.maxFarPlane ?? 1000) | |||
| // old way, has issues when panning very far from the camera target | |||
| // const near = Math.max(camera.userData.minNearPlane ?? 0.2, dist - radius) | |||
| // const far = Math.min(Math.max(near + 1, dist + radius), camera.userData.maxFarPlane ?? 1000) | |||
| if (far < near || far - near < 0.1) { | |||
| far = near + 0.1 | |||
| } | |||
| camera.near = near | |||
| camera.far = far | |||
| @@ -15,7 +15,7 @@ export const iCameraCommons = { | |||
| } | |||
| // } | |||
| this.dispatchEvent({...options, type: 'update', bubbleToParent: false, camera: this}) // does not bubble | |||
| this.dispatchEvent({...options, type: 'cameraUpdate', bubbleToParent: true}) // this sets dirty in the viewer | |||
| this.dispatchEvent({...options, type: 'cameraUpdate', bubbleToParent: true, camera: this}) // this sets dirty in the viewer | |||
| iObjectCommons.setDirty.call(this, {refreshScene: false, ...options}) | |||
| }, | |||
| activateMain: function(this: ICamera, options: Omit<ICameraEventMap['activateMain'], 'bubbleToParent'> = {}, _internal = false, _refresh = true, canvas?: HTMLCanvasElement): void { | |||
| @@ -1,6 +1,6 @@ | |||
| import {Event, Matrix4, Mesh, Vector3} from 'three' | |||
| import {IMaterial} from '../IMaterial' | |||
| import {objectHasOwn} from 'ts-browser-helpers' | |||
| import {IEvent, objectHasOwn} from 'ts-browser-helpers' | |||
| import {IObject3D, IObject3DEventMap, IObjectProcessor, IObjectSetDirtyOptions} from '../IObject' | |||
| import {copyObject3DUserData} from '../../utils' | |||
| import {IGeometry, IGeometryEventMap} from '../IGeometry' | |||
| @@ -24,8 +24,10 @@ export const iObjectCommons = { | |||
| makeUiConfig: makeIObject3DUiConfig, | |||
| autoCenter: function<T extends IObject3D>(this: T, setDirty = true, undo = false): T { | |||
| // todo use bounding sphere? | |||
| if (undo) { | |||
| if (!this.userData.autoCentered || !this.userData._lastCenter) return this | |||
| if (!isFinite(this.userData._lastCenter.lengthSq())) return this | |||
| this.position.add(this.userData._lastCenter) | |||
| delete this.userData.autoCentered | |||
| delete this.userData.isCentered | |||
| @@ -33,6 +35,7 @@ export const iObjectCommons = { | |||
| } else { | |||
| const bb = new Box3B().expandByObject(this, true, true) | |||
| const center = bb.getCenter(new Vector3()) | |||
| if (!isFinite(center.lengthSq())) return this | |||
| this.userData._lastCenter = center/* .clone()*/ | |||
| this.position.sub(center) | |||
| this.userData.autoCentered = true | |||
| @@ -352,6 +355,7 @@ export const iObjectCommons = { | |||
| dispatchEvent: (superDispatch: IObject3D['dispatchEvent']): IObject3D['dispatchEvent'] => | |||
| function(this: IObject3D, event): void { | |||
| if ((event as IEvent<any>).target && (event as IEvent<any>).target !== this && this.acceptChildEvents === false) return | |||
| if ((event as IObject3DEventMap['objectUpdate']).bubbleToParent || this.userData?.__autoBubbleToParentEvents?.includes(event.type)) { | |||
| // console.log('parent dispatch', e, this.parentRoot, this.parent) | |||
| const pRoot = this.parentRoot || this.parent | |||
| @@ -398,7 +402,9 @@ export const iObjectCommons = { | |||
| }, | |||
| add: (superAdd: IObject3D['add']): IObject3D['add'] => | |||
| function(this: IObject3D, ...args): IObject3D { | |||
| for (const a of args) iObjectCommons.upgradeObject3D.call(a, this.parentRoot || this, this.objectProcessor) | |||
| if (this.autoUpgradeChildren !== false) { | |||
| for (const a of args) iObjectCommons.upgradeObject3D.call(a, this.parentRoot || this, this.objectProcessor) | |||
| } | |||
| return superAdd.call(this, ...args) | |||
| }, | |||
| dispose: (superDispose?: IObject3D['dispose']) => | |||
| @@ -432,8 +438,8 @@ export const iObjectCommons = { | |||
| * @param parent | |||
| * @param objectProcessor | |||
| */ | |||
| function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectProcessor?: IObjectProcessor): void { | |||
| if (!this) return | |||
| function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectProcessor?: IObjectProcessor): IObject3D { | |||
| if (!this) return this | |||
| // console.log('upgradeObject3D', this, parent, objectProcessor) | |||
| // if (this.__disposed) { | |||
| // console.warn('re-init/re-add disposed object, things might not work as intended', this) | |||
| @@ -443,9 +449,9 @@ function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectPr | |||
| this.userData.uuid = this.uuid | |||
| // not checking assetType but custom var __objectSetup because its required in types sometimes, check PerspectiveCamera2 | |||
| // if (this.assetType) return | |||
| // if (this.assetType) return this | |||
| if (this.userData.__objectSetup) return | |||
| if (this.userData.__objectSetup) return this | |||
| this.userData.__objectSetup = true | |||
| if (!this.objectProcessor) this.objectProcessor = objectProcessor || this.parent?.objectProcessor || parent?.objectProcessor | |||
| @@ -540,8 +546,10 @@ function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectPr | |||
| // todo: serialization? | |||
| const children = [...this.children] | |||
| for (const c of children) upgradeObject3D.call(c, this) | |||
| if (this.autoUpgradeChildren !== false) { | |||
| const children = [...this.children] | |||
| for (const c of children) upgradeObject3D.call(c, this) | |||
| } | |||
| // region Legacy | |||
| @@ -567,4 +575,5 @@ function upgradeObject3D(this: IObject3D, parent?: IObject3D|undefined, objectPr | |||
| this.objectProcessor?.processObject(this) | |||
| return this | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| import {Quaternion, Vector3} from 'three' | |||
| import {EventListener2, Quaternion, Vector3} from 'three' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {PopmotionPlugin} from './PopmotionPlugin' | |||
| import {IObject3D} from '../../core' | |||
| import {IObject3D, IScene, ISceneEventMap} from '../../core' | |||
| // todo make a serializable object like CameraView for proper ui state management | |||
| export interface TSavedTransform { | |||
| @@ -40,8 +40,8 @@ export class TransformAnimationPlugin extends AViewerPluginSync { | |||
| viewer.scene.removeEventListener('addSceneObject', this._addSceneObject) | |||
| return super.onRemove(viewer) | |||
| } | |||
| private _addSceneObject = (e: any)=>{ | |||
| const object = e.object as IObject3D | |||
| private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (e)=>{ | |||
| const object = e.object | |||
| object?.traverse && object.traverse((o: IObject3D)=>{ | |||
| if (!o.userData[TransformAnimationPlugin.PluginType]) { | |||
| o.userData[TransformAnimationPlugin.PluginType] = { | |||
| @@ -1,8 +1,8 @@ | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {IWidget} from '../../core' | |||
| import {IScene, ISceneEventMap, IWidget} from '../../core' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {IEvent, onChange} from 'ts-browser-helpers' | |||
| import {Object3D} from 'three' | |||
| import {EventListener2, Object3D} from 'three' | |||
| import {CameraHelper2, DirectionalLightHelper2, PointLightHelper2, SpotLightHelper2} from '../../three' | |||
| export interface IObject3DHelper<T extends Object3D&IWidget = Object3D&IWidget>{ | |||
| @@ -54,7 +54,7 @@ export class Object3DWidgetsPlugin extends AViewerPluginSync { | |||
| this._widgetRoot.clear() | |||
| super.onRemove(viewer) | |||
| } | |||
| private _addSceneObject = (e: any)=>{ | |||
| private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (e)=>{ | |||
| this._createWidgets(e.object) | |||
| } | |||
| @@ -1,8 +1,9 @@ | |||
| import {Spherical, Vector3} from 'three' | |||
| import {EventListener2, Spherical, Vector3} from 'three' | |||
| import {IEvent, now, objectHasOwn, onChange, serialize} from 'ts-browser-helpers' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {uiButton, uiFolderContainer, uiInput, uiMonitor, uiToggle} from 'uiconfig.js' | |||
| import {OrbitControls3} from '../../three' | |||
| import {IScene, ISceneEventMap} from '../../core' | |||
| /** | |||
| * Interaction Prompt Plugin | |||
| @@ -174,7 +175,7 @@ export class InteractionPromptPlugin extends AViewerPluginSync { | |||
| this.lastActionTime = now() | |||
| } | |||
| } | |||
| private _addSceneObject = ()=>{ | |||
| private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = ()=>{ | |||
| if (this.autoStartOnObjectLoad) { | |||
| this.lastActionTime = now() - this.autoStartDelay + this.autoStartOnObjectLoadDelay | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| import {OrthographicCamera, PerspectiveCamera} from 'three' | |||
| import {EventListener2, OrthographicCamera, PerspectiveCamera} from 'three' | |||
| import {AViewerPluginSync, ThreeViewer} from '../../viewer' | |||
| import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js' | |||
| import {IEvent, onChange, serialize} from 'ts-browser-helpers' | |||
| import {ICamera, ILight} from '../../core' | |||
| import {onChange, serialize} from 'ts-browser-helpers' | |||
| import {ICamera, ILight, IScene, ISceneEventMap} from '../../core' | |||
| import {ProgressivePlugin} from './ProgressivePlugin' | |||
| export type TCamera = ICamera & (PerspectiveCamera|OrthographicCamera) | |||
| @@ -68,7 +68,7 @@ export class SSAAPlugin extends AViewerPluginSync { | |||
| this.uiConfig?.uiRefresh?.(true, 'postFrame') | |||
| } | |||
| private _addSceneObject = (event: IEvent<string>)=> { | |||
| private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (event)=>{ | |||
| event.object?.traverse((o: ILight)=>{ | |||
| if (o && o.shadow && o.shadow.camera && o.shadow.mapSize) { | |||
| this.trackedJitterCameras.add([o.shadow.camera as TCamera, o.shadow.mapSize]) | |||
| @@ -235,7 +235,9 @@ export class RenderManager<TE extends IRenderManagerEventMap = IRenderManagerEve | |||
| if (pass.enabled && pass.beforeRender) pass.beforeRender(scene, scene.renderCamera, this) | |||
| } | |||
| this._composer.renderToScreen = renderToScreen ?? this.defaultRenderToScreen | |||
| this.dispatchEvent({type: 'preRender', scene, renderToScreen: this._composer.renderToScreen}) | |||
| this._composer.render() | |||
| this.dispatchEvent({type: 'postRender', scene, renderToScreen: this._composer.renderToScreen}) | |||
| this._composer.renderToScreen = true | |||
| if (renderToScreen) { | |||
| this._frameCount += 1 | |||
| @@ -699,7 +701,7 @@ export class RenderManager<TE extends IRenderManagerEventMap = IRenderManagerEve | |||
| // required for uiconfig.js. see UiConfigMethods.getValue | |||
| // eslint-disable-next-line @typescript-eslint/naming-convention | |||
| _ui_isPrimitive = true | |||
| }(this, ...size, options) | |||
| } | |||
| @@ -18,18 +18,24 @@ export class Box3B extends Box3 { | |||
| object.updateWorldMatrix(false, false) | |||
| // InstancedMesh has boundingBox = null, so it can be computed | |||
| if ((object as any).boundingBox !== undefined) { | |||
| if ((object as IObject3D).boundingBox !== undefined) { | |||
| if ((object as any).boundingBox === null) { | |||
| if ((object as IObject3D).boundingBox === null && typeof (object as IObject3D).computeBoundingBox === 'function') { | |||
| (object as any).computeBoundingBox() | |||
| (object as IObject3D).computeBoundingBox!() | |||
| } | |||
| Box3B._box.copy((object as any).boundingBox) | |||
| Box3B._box.applyMatrix4(object.matrixWorld) | |||
| if ((object as IObject3D).boundingBox !== null) { | |||
| this.union(Box3B._box) | |||
| Box3B._box.copy((object as IObject3D).boundingBox!) | |||
| Box3B._box.applyMatrix4(object.matrixWorld) | |||
| this.union(Box3B._box) | |||
| } else { | |||
| console.warn('Box3B - Unable to compute bounds for', object) | |||
| } | |||
| } else { | |||
| @@ -4,13 +4,13 @@ import { | |||
| Color, | |||
| Event, | |||
| EventDispatcher, | |||
| EventListener2, | |||
| LinearSRGBColorSpace, | |||
| Object3D, | |||
| Quaternion, | |||
| Scene, | |||
| Vector2, | |||
| Vector3, | |||
| EventListener2, | |||
| } from 'three' | |||
| import {Class, createCanvasElement, downloadBlob, onChange, serialize, timeout, ValOrArr} from 'ts-browser-helpers' | |||
| import {TViewerScreenShader} from '../postprocessing' | |||
| @@ -135,6 +135,8 @@ export interface ThreeViewerOptions { | |||
| * Use rendered gbuffer as depth-prepass / z-prepass. (Requires DepthBufferPlugin/GBufferPlugin). | |||
| * Set it to true if you only have opaque objects in the scene to get better performance. | |||
| * | |||
| * @default false | |||
| * | |||
| * todo fix: It should be disabled when there are any transparent/transmissive objects with render to depth buffer enabled, see forceZPrepass | |||
| */ | |||
| zPrepass?: boolean | |||
| @@ -158,6 +160,12 @@ export interface ThreeViewerOptions { | |||
| */ | |||
| maxRenderScale?: number | |||
| /** | |||
| * Model Root Scale | |||
| * @default 1 | |||
| */ | |||
| modelRootScale?: number | |||
| debug?: boolean | |||
| /** | |||
| @@ -400,7 +408,7 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| }) | |||
| // if camera position or target changed in last frame, call setDirty on camera | |||
| this.addEventListener('preFrame', () => { // todo: move inside RootScene. | |||
| this.addEventListener('preFrame', () => { // todo: move inside RootScene. and maybe check the world matrix and target vector change | |||
| const cam = this._scene.mainCamera | |||
| if ( | |||
| cam.getWorldPosition(this._tempVec).sub(this._lastCameraPosition).lengthSq() // position is in local space | |||
| @@ -430,6 +438,7 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| this._lastCameraTarget.copy(this._scene.mainCamera.target) | |||
| this._scene.mainCamera.getWorldQuaternion(this._lastCameraQuat) | |||
| }) | |||
| this._scene.modelRoot.scale.setScalar(options.modelRootScale ?? 1) | |||
| // render manager | |||
| @@ -892,7 +901,6 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| this.resize() // this is also required in case the browwser doesnt support/fire observer | |||
| } | |||
| // todo make an example for this. | |||
| // todo make a constructor parameter for renderSize | |||
| // todo make getRenderSize or get renderSize | |||
| /** | |||
| @@ -916,6 +924,8 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| * 'fill': The canvas is stretched to completely fill the container, ignoring its aspect ratio. | |||
| * 'scale-down': The canvas is scaled down to fit within the container while maintaining its aspect ratio, but it won't be scaled up if it's smaller than the container. | |||
| * 'none': container size is ignored, but devicePixelRatio is used | |||
| * | |||
| * Check the example for more details - https://threepipe.org/examples/#viewer-render-size/ | |||
| * @param size - The size to set the render to. The canvas will render to this size. | |||
| * @param mode - 'contain', 'cover', 'fill', 'scale-down' or 'none'. Default is 'contain'. | |||
| * @param devicePixelRatio - typically set to `window.devicePixelRatio`, or `Math.min(1.5, window.devicePixelRatio)` for performance. Use this only when size is derived from dom elements. | |||
| @@ -1343,7 +1353,7 @@ export class ThreeViewer extends EventDispatcher<Record<IViewerEventTypes, IView | |||
| this.enabled = false | |||
| } | |||
| // private _addSceneObject = (e: IEvent<any>) => { | |||
| // private _addSceneObject: EventListener2<'addSceneObject', ISceneEventMap, IScene> = (e)=>{ | |||
| // if (!e || !e.object) return | |||
| // const config = e.object.__importedViewerConfig // this is set in gltf.ts when gltf file is imported. This is done here so that scene settings are applied whenever the imported object is added to scene. | |||
| // if (!config) return | |||
| @@ -186,9 +186,9 @@ Check it in action: [vue-html-sample](https://threepipe.org/examples/#vue-html-s | |||
| Another example with Vue SFC(Single file component): [Example: vue-sfc-sample](https://threepipe.org/examples/#vue-sfc-sample/) | |||
| ### Svelte | |||
| ### Svelte 4 | |||
| A sample [svelte](https://svelte.dev/) component in js to render a model with an environment map. | |||
| A sample [svelte](https://svelte.dev/) 4 component in js to render a model with an environment map. | |||
| ```html | |||
| <script> | |||
| @@ -26,6 +26,6 @@ Checkout the [model-viewer](https://threepipe.org/examples/#model-viewer) or [tw | |||
| - [@threepipe/plugin-gltf-transform](../package/plugin-gltf-transform) - Plugin to transform gltf models like adding draco compression while exporting gltf files. | |||
| - [@threepipe/plugins-extra-importers](../package/plugins-extra-importers) - Plugin for loading more file types supported by various types of loaders in three.js. | |||
| - [@threepipe/plugin-network](../package/plugin-network) - Network/Cloud related plugin implementations for Threepipe - `AWSClientPlugin` and `TransfrSharePlugin`. | |||
| - [@threepipe/plugin-blend-importer](../package/plugin-blend-importer) - Blender to add support for loading .blend file (WIP) | |||
| - [@threepipe/plugin-gaussian-splatting](../package/plugin-gaussian-splatting) - Gaussian Splatting plugin for loading and rendering splat files (WIP) | |||
| - [@threepipe/plugin-blend-importer](../package/plugin-blend-importer) - Add support for loading .blend file. (Partial/WIP) ([Blender](https://www.blender.org/)) | |||
| - [@threepipe/plugin-gaussian-splatting](../package/plugin-gaussian-splatting) - [3D Gaussian Splatting](https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/) plugin for loading and rendering splat files | |||
| - [@threepipe/plugin-svg-renderer](../package/plugin-svg-renderer) - Add support for exporting 3d scene as SVG (WIP) using [three-svg-renderer](https://www.npmjs.com/package/three-svg-renderer). | |||