浏览代码

Remove File.__loadedAsset caching, accept File objects while setting env, background maps in viewer, add file-load example

master
Palash Bansal 11 个月前
父节点
当前提交
fe3bf9e618
没有帐户链接到提交者的电子邮件

+ 18
- 14
examples/dispose-reimport-test/index.html 查看文件



const viewer = new ThreeViewer({canvas: document.getElementById('mcanvas')}) const viewer = new ThreeViewer({canvas: document.getElementById('mcanvas')})


async function init() {
async function init(loadAsFile = true) {
const url = 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'
let file
if(loadAsFile){
file = new File([await (await fetch(url)).blob()], url)
}
const load = async ()=> {
return viewer.load(file ?? url, {
autoCenter: true,
autoScale: true,
})
}


await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr') await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')
viewer.scene.background = viewer.scene.environment viewer.scene.background = viewer.scene.environment
const model = await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
const model = await load()
console.log(model.uuid) console.log(model.uuid)
await timeout(500) await timeout(500)
viewer.scene.clearSceneModels(false) viewer.scene.clearSceneModels(false)
await timeout(500) await timeout(500)
const model2 = await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
const model2 = await load()
console.log(model2.uuid) console.log(model2.uuid)
if(model !== model2) throw new Error('Error in Test - Models should be the same after clearing scene models'); if(model !== model2) throw new Error('Error in Test - Models should be the same after clearing scene models');
await timeout(500) await timeout(500)
viewer.scene.disposeSceneModels() viewer.scene.disposeSceneModels()
await timeout(500) await timeout(500)
const model3 = await viewer.load('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
const model3 = await load()
console.log(model3.uuid) console.log(model3.uuid)
if(model2 === model3) throw new Error('Error in Test - Models should not be the same after disposing scene models'); if(model2 === model3) throw new Error('Error in Test - Models should not be the same after disposing scene models');
await timeout(500) await timeout(500)
viewer.scene.addObject(model.translateX(-0.5)) // add back cleared model viewer.scene.addObject(model.translateX(-0.5)) // add back cleared model
viewer.scene.addObject(model2.translateX(-0.5)) // again, shouldn't change anything viewer.scene.addObject(model2.translateX(-0.5)) // again, shouldn't change anything
viewer.scene.addObject(model3.translateX(1)) // add back disposed model viewer.scene.addObject(model3.translateX(1)) // add back disposed model
await timeout(1000)
viewer.scene.disposeSceneModels()
} }


_testStart() _testStart()
init().finally(_testFinish)
init(false).then(()=>init(true)).finally(_testFinish)
</script> </script>
</head> </head>
<body> <body>

+ 36
- 0
examples/file-load/index.html 查看文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>File/Blob Load</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"threepipe": "./../../dist/index.mjs"
}
}

</script>
<style id="example-style">
html, body, #canvas-container, #mcanvas {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
</style>
<script type="module" src="../examples-utils/global-loading.mjs"></script>
<script type="module" src="../examples-utils/simple-code-preview.mjs"></script>
<script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script>
</head>
<body>
<div id="canvas-container">
<canvas id="mcanvas"></canvas>
</div>

</body>

+ 41
- 0
examples/file-load/script.ts 查看文件

import {_testFinish, _testStart, LoadingScreenPlugin, ThreeViewer} from 'threepipe'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
dropzone: {
addOptions: {
disposeSceneObjects: true,
autoSetEnvironment: true, // when hdr is dropped
autoSetBackground: true,
},
},
})

viewer.addPluginSync(LoadingScreenPlugin)

const env = 'https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'
const url = 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'

const responses = await Promise.all([
fetch(env),
fetch(url),
])
const envFile = new File([await responses[0].blob()], 'venice_sunset_1k.hdr')
await viewer.setEnvironmentMap(envFile, {
setBackground: true,
})
const blob = await responses[1].blob()
const file = new File([blob], url) // Set the file name to the URL, so that internal textures can be resolved correctly from the base path
const result = await viewer.load(file, {
autoCenter: true,
autoScale: true,
})
console.log(result)

}

_testStart()
init().finally(_testFinish)

+ 2
- 1
examples/index.html 查看文件

<li><a href="./slippy-map-tiles/">Slippy Map Tiles Load<br/>(OpenStreetMap ZYX) </a></li> <li><a href="./slippy-map-tiles/">Slippy Map Tiles Load<br/>(OpenStreetMap ZYX) </a></li>
<li><a href="./ogc-tiles-google-maps/">Google Maps Globe (OGC Tiles)</a></li> <li><a href="./ogc-tiles-google-maps/">Google Maps Globe (OGC Tiles)</a></li>
<li><a href="./ogc-tiles-google-maps-3d/">Google Maps 3D (OGC Tiles)</a></li> <li><a href="./ogc-tiles-google-maps-3d/">Google Maps 3D (OGC Tiles)</a></li>
<li><a href="./file-load/">File/Blob load</a></li>
</ul> </ul>
<h2 class="category">Export</h2> <h2 class="category">Export</h2>
<ul> <ul>
<li><a href="./uint8-rgbm-hdr-test/">Uint8 RGBM HDR Test </a></li> <li><a href="./uint8-rgbm-hdr-test/">Uint8 RGBM HDR Test </a></li>
<li><a href="./half-float-hdr-test/">Half-float HDR Test </a></li> <li><a href="./half-float-hdr-test/">Half-float HDR Test </a></li>
<li><a href="./sphere-rgbm-test/">RGBM Test </a></li> <li><a href="./sphere-rgbm-test/">RGBM Test </a></li>
<li><a href="./sphere-half-float-test/">Half Float Test </a></li>
<li><a href="./sphere-half-float-test/">Half-float Test </a></li>
<li><a href="./sphere-msaa-test/">MSAA Test </a></li> <li><a href="./sphere-msaa-test/">MSAA Test </a></li>
<li><a href="./z-prepass/">Z-Prepass Test </a></li> <li><a href="./z-prepass/">Z-Prepass Test </a></li>
<li><a href="./import-test/">Import Test</a></li> <li><a href="./import-test/">Import Test</a></li>

+ 19
- 19
src/assetmanager/AssetImporter.ts 查看文件

ImportFilesOptions, ImportFilesOptions,
ImportResult, ImportResult,
LoadFileOptions, LoadFileOptions,
ProcessRawOptions, RootSceneImportResult,
ProcessRawOptions,
RootSceneImportResult,
} from './IAssetImporter' } from './IAssetImporter'
import {IAsset, IFile} from './IAsset' import {IAsset, IFile} from './IAsset'
import {IImporter, ILoader} from './IImporter' import {IImporter, ILoader} from './IImporter'
import {Importer} from './Importer' import {Importer} from './Importer'
import {SimpleJSONLoader} from './import' import {SimpleJSONLoader} from './import'
import {parseFileExtension} from 'ts-browser-helpers' import {parseFileExtension} from 'ts-browser-helpers'
import {IObject3D} from '../core'


// export type IAssetImporterEvent = Event&{ // export type IAssetImporterEvent = Event&{
// type: IAssetImporterEventTypes, // type: IAssetImporterEventTypes,
console.error('AssetImporter: Invalid asset or path', assetOrPath) console.error('AssetImporter: Invalid asset or path', assetOrPath)
return [] return []
} }
async importSingle<T extends ImportResult|undefined = ImportResult>(asset?: IAsset | string, options?: ImportAssetOptions): Promise<T|undefined> {
async importSingle<T extends ImportResult|undefined = ImportResult>(asset?: string | IAsset | File, options?: ImportAssetOptions): Promise<T|undefined> {
return (await this.import<T>(asset, options))?.[0] return (await this.import<T>(asset, options))?.[0]
} }




// load a single file // load a single file
private async _loadFile(path: string, file?: IFile, options: LoadFileOptions = {}, onDownloadProgress?: (e: ProgressEvent)=>void): Promise<ImportResult | ImportResult[] | undefined> { private async _loadFile(path: string, file?: IFile, options: LoadFileOptions = {}, onDownloadProgress?: (e: ProgressEvent)=>void): Promise<ImportResult | ImportResult[] | undefined> {
if (file?.__loadedAsset) return file.__loadedAsset
// if (file?.__loadedAsset) return file.__loadedAsset


this.dispatchEvent({type: 'importFile', path, state:'downloading', progress: 0}) this.dispatchEvent({type: 'importFile', path, state:'downloading', progress: 0})
let res: ImportResult | ImportResult[] | undefined let res: ImportResult | ImportResult[] | undefined
return [] return []
} }
this.dispatchEvent({type: 'importFile', path, state: 'done'}) // todo: do this after processing? this.dispatchEvent({type: 'importFile', path, state: 'done'}) // todo: do this after processing?
if (file) {
file.__loadedAsset = res
// todo: recheck below code after dispose logic change
// Clear the reference __loadedAsset when any one asset is disposed.
// it's a bit hacky to do this here, but it works for now. todo: move to a better place
let ress: any[] = []
if (Array.isArray(res)) ress = res.flat(2)
else if ((<RootSceneImportResult>res)?.userData?.rootSceneModelRoot) ress.push(...(<IObject3D>res).children)
else ress.push(res)
for (const r of ress) r?.addEventListener?.('dispose', () => file.__loadedAsset = undefined)
}
// if (file) {
// file.__loadedAsset = res
//
//
// // todo: recheck below code after dispose logic change
//
// // Clear the reference __loadedAsset when any one asset is disposed.
// // it's a bit hacky to do this here, but it works for now. todo: move to a better place
// let ress: any[] = []
// if (Array.isArray(res)) ress = res.flat(2)
// else if ((<RootSceneImportResult>res)?.userData?.rootSceneModelRoot) ress.push(...(<IObject3D>res).children)
// else ress.push(res)
// for (const r of ress) r?.addEventListener?.('dispose', () => file.__loadedAsset = undefined)
//
// }
if (res && typeof res === 'object' && !Array.isArray(res)) { if (res && typeof res === 'object' && !Array.isArray(res)) {
res.__rootPath = path res.__rootPath = path
const f = file || this._fileDatabase.get(path) const f = file || this._fileDatabase.get(path)

+ 4
- 4
src/viewer/ThreeViewer.ts 查看文件

* @param obj * @param obj
* @param options * @param options
*/ */
async import<T extends ImportResult = ImportResult>(obj: string | IAsset | null, options?: ImportAddOptions) {
async import<T extends ImportResult = ImportResult>(obj: string | IAsset | File | null, options?: ImportAddOptions) {
if (!obj) return if (!obj) return
return await this.assetManager.importer.importSingle<T>(obj, options) return await this.assetManager.importer.importSingle<T>(obj, options)
} }
* @param setBackground - Set the background image of the scene from the same map. * @param setBackground - Set the background image of the scene from the same map.
* @param options - Options for importing the asset. See {@link ImportAssetOptions} * @param options - Options for importing the asset. See {@link ImportAssetOptions}
*/ */
async setEnvironmentMap(map: string | IAsset | null | ITexture | undefined, {setBackground = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<ITexture | null> {
this._scene.environment = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null
async setEnvironmentMap(map: string | IAsset | null | ITexture | File | undefined, {setBackground = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<ITexture | null> {
this._scene.environment = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset|File, options) || null : <ITexture>map || null
if (setBackground) return this.setBackgroundMap(this._scene.environment) if (setBackground) return this.setBackgroundMap(this._scene.environment)
return this._scene.environment return this._scene.environment
} }
* @param setEnvironment - Set the environment map of the scene from the same map. * @param setEnvironment - Set the environment map of the scene from the same map.
* @param options - Options for importing the asset. See {@link ImportAssetOptions} * @param options - Options for importing the asset. See {@link ImportAssetOptions}
*/ */
async setBackgroundMap(map: string | IAsset | null | ITexture | undefined, {setEnvironment = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<ITexture | null> {
async setBackgroundMap(map: string | IAsset | null | ITexture | File | undefined, {setEnvironment = false, ...options}: ImportAssetOptions&{setBackground?: boolean} = {}): Promise<ITexture | null> {
this._scene.background = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null this._scene.background = map && !(<ITexture>map).isTexture ? await this.assetManager.importer.importSingle<ITexture>(map as string|IAsset, options) || null : <ITexture>map || null
if (setEnvironment) return this.setEnvironmentMap(this._scene.background) if (setEnvironment) return this.setEnvironmentMap(this._scene.background)
return this._scene.background return this._scene.background

+ 24
- 3
website/guide/loading-files.md 查看文件

The AssetManager has support for loading files from URLs, local files and data URLs. The AssetManager has support for loading files from URLs, local files and data URLs.
The AssetManager also adds support for loading files from a zip archive. The zip files are automatically unzipped, and the files are loaded from the zip archive. The AssetManager also adds support for loading files from a zip archive. The zip files are automatically unzipped, and the files are loaded from the zip archive.


[`viewer.load()`](https://threepipe.org/docs/classes/ThreeViewer.html#load) is a simple wrapper for loading files from the AssetManager.
It automatically adds the loaded object to the scene(if possible) and returns a promise that resolves to the loaded object, the materials are also automatically registered to the material manager.
[`viewer.load()`](https://threepipe.org/docs/classes/ThreeViewer.html#load) is a simple helper for loading files from the AssetManager. It accepts urls, local `File`/`Blob`, data URLs, zip files, `IAsset`.
It automatically adds the loaded object to the scene(if possible) and returns a promise that resolves to the loaded object/texture/material/etc, the materials are also automatically registered to the material manager.

```typescript
const object = await viewer.load<IObject3D>('https://example.com/file.glb')
```


::: details AssetManager ::: details AssetManager
AssetManager internally uses [AssetImporter](https://threepipe.org/docs/classes/AssetImporter.html), which provides an API for managing three.js [LoadingManager](https://threejs.org/docs/#api/en/loaders/LoadingManager) and adding and registering loaders for different file types. AssetManager internally uses [AssetImporter](https://threepipe.org/docs/classes/AssetImporter.html), which provides an API for managing three.js [LoadingManager](https://threejs.org/docs/#api/en/loaders/LoadingManager) and adding and registering loaders for different file types.


```typescript ```typescript
const file: File|Blob = fileObject // create a new file, blob or get from input element const file: File|Blob = fileObject // create a new file, blob or get from input element
const text = await viewer.load<IObject>({
const res = await viewer.load(file)
const res2 = await viewer.load<IObject>({
// a path/name is required to determine the proper importer by extension. `file.name` can also be used if available // a path/name is required to determine the proper importer by extension. `file.name` can also be used if available
path: 'file.glb', path: 'file.glb',
file file


To load a `Map` of files(like when multiple files are dragged and dropped on the webpage) with internal references to other files, use `viewer.assetManager.importer.importFiles` method. Check the source for [DropzonePlugin](../plugin/DropzonePlugin) for an example. To load a `Map` of files(like when multiple files are dragged and dropped on the webpage) with internal references to other files, use `viewer.assetManager.importer.importFiles` method. Check the source for [DropzonePlugin](../plugin/DropzonePlugin) for an example.


## File/Blob with URL

Files with references can be loaded with path, by setting the path as the name of the `File` object, or by specifying the `path` parameter.

```typescript
const url = 'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf'
const blob = await fetch(url).then(res => res.blob())
const file = new File([blob], url) // Set the file name to the URL, so that internal textures can be resolved correctly from the base path
const result = await viewer.load(file, {
autoCenter: true,
autoScale: true,
})
```

Check the complete example - [file-load](https://threepipe.org/examples/#file-load/)

## Background, Environment maps ## Background, Environment maps


The background and environment maps can be set using the `viewer.setBackgroundMap` and `viewer.setEnvironmentMap` methods respectively. These accept both loaded textures from `viewer.load` and direct URLs. Files can be of any image format including hdr, exr. The background and environment maps can be set using the `viewer.setBackgroundMap` and `viewer.setEnvironmentMap` methods respectively. These accept both loaded textures from `viewer.load` and direct URLs. Files can be of any image format including hdr, exr.

正在加载...
取消
保存