浏览代码

Serialization fixes, other minor fixes.

master
Palash Bansal 2 年前
父节点
当前提交
09a80866da
没有帐户链接到提交者的电子邮件

+ 118
- 0
README.md 查看文件



Try them: https://threepipe.org/examples/ Try them: https://threepipe.org/examples/


View the source code by pressing the code button on the top left of the example page.

To make changes and run the example, click on the CodePen button on the top right of the source code.



## Getting Started ## Getting Started




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](#plugins) section below to learn how to add additional functionality to the viewer.

## License ## License
The core framework([src](https://github.com/repalash/threepipe/tree/master/src), [dist](https://github.com/repalash/threepipe/tree/master/dist), [examples](https://github.com/repalash/threepipe/tree/master/examples) folders) and any [plugins](https://github.com/repalash/threepipe/tree/master/plugins) without a separate license are under the [Apache 2.0 license](https://github.com/repalash/threepipe/tree/master/LICENSE). The core framework([src](https://github.com/repalash/threepipe/tree/master/src), [dist](https://github.com/repalash/threepipe/tree/master/dist), [examples](https://github.com/repalash/threepipe/tree/master/examples) folders) and any [plugins](https://github.com/repalash/threepipe/tree/master/plugins) without a separate license are under the [Apache 2.0 license](https://github.com/repalash/threepipe/tree/master/LICENSE).




## Contributing ## Contributing
Contributions to ThreePipe are welcome and encouraged! Feel free to open issues and pull requests on the GitHub repository. Contributions to ThreePipe are welcome and encouraged! Feel free to open issues and pull requests on the GitHub repository.

## File Formats

ThreePipe Asset Manager supports the import of following file formats out of the box:
* Models:
* gltf, glb
* obj, mtl
* fbx
* drc
* Materials
* mat, pmat, bmat (json based), registered material template slugs
* Images
* webp, png, jpeg, jpg, svg, ico
* hdr, exr
* ktx2, ktx, dds, pvr
* Misc
* json, vjson
* zip
* txt

Additional formats can be added by plugins:
* Models
* 3dm - Using [Rhino3dmLoadPlugin](#Rhino3dmLoadPlugin)

## Plugins

ThreePipe has a simple plugin system that allows you to easily add new features to the viewer. Plugins can be added to the viewer using the `addPlugin` and `addPluginSync` methods. The plugin system is designed to be modular and extensible. Plugins can be added to the viewer at any time and can be removed using the `removePlugin` and `removePluginSync` methods.

### DepthBufferPlugin

todo: image

Example: https://threepipe.org/examples/#depth-buffer-plugin/

Source Code: [src/plugins/pipeline/DepthBufferPlugin.ts](./src/plugins/pipeline/DepthBufferPlugin.ts)

Depth Buffer Plugin adds a pre-render pass to the render manager and renders a depth buffer to a target. The render target can be accessed by other plugins throughout the rendering pipeline to create effects like depth of field, SSAO, SSR, etc.

```typescript
import {ThreeViewer, DepthBufferPlugin} from 'threepipe'

const viewer = new ThreeViewer({...})

const depthPlugin = viewer.addPluginSync(new DepthBufferPlugin(HalfFloatType))

const depthTarget = depthPlugin.target;

// Use the depth target by accesing `depthTarget.texture`.
```

The depth values are based on camera near far values, which are controlled automatically by the viewer. To manually specify near, far values and limits, it can be set in the camera userData. Check the [example](https://threepipe.org/examples/#depth-buffer-plugin/) for more details.

### NormalBufferPlugin

todo: image

Example: https://threepipe.org/examples/#normal-buffer-plugin/

Source Code: [src/plugins/pipeline/NormalBufferPlugin.ts](./src/plugins/pipeline/NormalBufferPlugin.ts)

Normal Buffer Plugin adds a pre-render pass to the render manager and renders a normal buffer to a target. The render target can be accessed by other plugins throughout the rendering pipeline to create effects like SSAO, SSR, etc.

Note: Use [`DepthNormalBufferPlugin`](#DepthNormalBufferPlugin) if using both `DepthBufferPlugin` and `NormalBufferPlugin` to render both depth and normal buffers in a single pass.

```typescript
import {ThreeViewer, NormalBufferPlugin} from 'threepipe'

const viewer = new ThreeViewer({...})

const normalPlugin = viewer.addPluginSync(new NormalBufferPlugin())

const normalTarget = normalPlugin.target;

// Use the normal target by accessing `normalTarget.texture`.
```


### DepthNormalBufferPlugin

todo


### RenderTargetPreviewPlugin

todo: image

Example: https://threepipe.org/examples/#render-target-preview/

Source Code: [src/plugins/ui/RenderTargetPreviewPlugin.ts](./src/plugins/ui/RenderTargetPreviewPlugin.ts)

RenderTargetPreviewPlugin is a useful development and debugging plugin that renders any registered render-target to the screen in small collapsable panels.

```typescript
import {ThreeViewer, RenderTargetPreviewPlugin, NormalBufferPlugin} from 'threepipe'

const viewer = new ThreeViewer({...})

const normalPlugin = viewer.addPluginSync(new NormalBufferPlugin(HalfFloatType))

const previewPlugin = viewer.addPluginSync(new RenderTargetPreviewPlugin())

// Show the normal buffer in a panel
previewPlugin.addTarget(()=>normalPlugin.target, 'normal', false, false)
```

### Rhino3dmLoadPlugin

Example: https://threepipe.org/examples/#rhino3dm-load/

Source Code: [src/plugins/import/Rhino3dmLoadPlugin.ts](./src/plugins/import/Rhino3dmLoadPlugin.ts)

Adds support for loading .3dm files generated by [Rhino 3D](https://www.rhino3d.com/). This plugin includes some changes with how 3dm files are loaded in three.js. The changes are around loading layer and primitive properties when set as reference in the 3dm files.

+ 8
- 3
src/assetmanager/AssetExporter.ts 查看文件

this.dispatchEvent({type: 'exportFile', obj, state:'processing', exportOptions: options}) this.dispatchEvent({type: 'exportFile', obj, state:'processing', exportOptions: options})


const processed = await this.processBeforeExport(obj, options) const processed = await this.processBeforeExport(obj, options)
const ext = options.exportExt ?? processed?.typeExt ?? processed?.ext
if (!processed || !ext) throw new Error(`Unable to preprocess before export ${ext}`)
const ext = options.exportExt || processed?.typeExt || processed?.ext
if (!processed || !ext) {
console.error(processed, options, obj)
throw new Error(`Unable to preprocess before export ${ext}`)
}
if (processed.blob) res = processed.blob if (processed.blob) res = processed.blob
else { else {
const parser = this._getParser(ext) const parser = this._getParser(ext)
if (obj.isWebGLMultipleRenderTargets) console.error('AssetExporter: WebGLMultipleRenderTargets export not supported') if (obj.isWebGLMultipleRenderTargets) console.error('AssetExporter: WebGLMultipleRenderTargets export not supported')
else if (!obj.renderManager) return {obj, ext: 'exr'} else if (!obj.renderManager) return {obj, ext: 'exr'}
else { else {
const blob = obj.renderManager.exportRenderTarget(obj as WebGLRenderTarget, 'auto')
const blob = obj.renderManager.exportRenderTarget(obj as WebGLRenderTarget,
(options.exportExt || '' !== '') && options.exportExt !== 'auto' ?
options.exportExt === 'exr' ? 'image/x-exr' : 'image/' + options.exportExt : 'auto')
return { return {
obj, ext: blob.ext, blob, obj, ext: blob.ext, blob,
} }

+ 11
- 3
src/assetmanager/AssetManager.ts 查看文件



async loadImported<T extends ValOrArr<ImportResult|undefined> = ImportResult>(imported: T, {autoSetEnvironment = true, autoSetBackground = false, ...options}: AddAssetOptions = {}): Promise<T | never[]> { async loadImported<T extends ValOrArr<ImportResult|undefined> = ImportResult>(imported: T, {autoSetEnvironment = true, autoSetBackground = false, ...options}: AddAssetOptions = {}): Promise<T | never[]> {
const arr: (ImportResult|undefined)[] = Array.isArray(imported) ? imported : [imported] const arr: (ImportResult|undefined)[] = Array.isArray(imported) ? imported : [imported]
let ret: T = Array.isArray(imported) ? [] : undefined as any


for (const obj of arr) { for (const obj of arr) {
if (!obj) continue
if (!obj) {
if (Array.isArray(ret)) ret.push(undefined)
continue
}

let r = obj


switch (obj.assetType) { switch (obj.assetType) {
case 'material': case 'material':
case 'model': case 'model':
case 'light': case 'light':
case 'camera': case 'camera':
await this.viewer.addSceneObject(<IObject3D|RootSceneImportResult>obj, options) // todo update references in scene update event
r = await this.viewer.addSceneObject(<IObject3D|RootSceneImportResult>obj, options) // todo update references in scene update event
break break
case 'config': case 'config':
if (options?.importConfig !== false) await this.viewer.importConfig(<ISerializedConfig>obj) if (options?.importConfig !== false) await this.viewer.importConfig(<ISerializedConfig>obj)
break break
} }
this.dispatchEvent({type: 'loadAsset', data: obj}) this.dispatchEvent({type: 'loadAsset', data: obj})
if (Array.isArray(ret)) ret.push(r)
else ret = r as T
} }


return imported || []
return ret || []
} }


/** /**

+ 3
- 0
src/core/ICamera.ts 查看文件

removeControlsCtor(key: string): void; removeControlsCtor(key: string): void;
refreshCameraControls(setDirty?: boolean): void refreshCameraControls(setDirty?: boolean): void


updateProjectionMatrix(): void
fov?: number

// region inherited type fixes // region inherited type fixes
// re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936 // re-declaring from IObject3D because: https://github.com/microsoft/TypeScript/issues/16936



+ 4
- 1
src/core/IMaterial.ts 查看文件

import type {Event, IUniform, Material, MaterialParameters, Shader} from 'three'
import type {Color, Event, IUniform, Material, MaterialParameters, Shader} from 'three'
import type {IDisposable, IJSONSerializable} from 'ts-browser-helpers' import type {IDisposable, IJSONSerializable} from 'ts-browser-helpers'
import type {MaterialExtension} from '../materials' import type {MaterialExtension} from '../materials'
import type {ChangeEvent, IUiConfigContainer} from 'uiconfig.js' import type {ChangeEvent, IUiConfigContainer} from 'uiconfig.js'
transmissionMap?: ITexture | null transmissionMap?: ITexture | null
transmission?: number transmission?: number


color?: Color
wireframe?: boolean



isRawShaderMaterial?: boolean isRawShaderMaterial?: boolean
isPhysicalMaterial?: boolean isPhysicalMaterial?: boolean

+ 1
- 1
src/core/object/RootScene.ts 查看文件

this.modelRoot.animations.push(animation) this.modelRoot.animations.push(animation)
} }
} }
obj.children.forEach(c=>this.addObject(c, options))
return obj.children.map(c=>this.addObject(c, options))
} }


private _addObject3D(model: IObject3D|null, {autoCenter = false, autoScale = false, autoScaleRadius = 2., addToRoot = false, license}: AddObjectOptions = {}): void { private _addObject3D(model: IObject3D|null, {autoCenter = false, autoScale = false, autoScaleRadius = 2., addToRoot = false, license}: AddObjectOptions = {}): void {

+ 4
- 2
src/rendering/RenderManager.ts 查看文件

IWebGLRenderer, IWebGLRenderer,
upgradeWebGLRenderer, upgradeWebGLRenderer,
} from '../core' } from '../core'
import {base64ToArrayBuffer, Class, onChange2, serializable, serialize, ValOrArr} from 'ts-browser-helpers'
import {base64ToArrayBuffer, canvasFlipY, Class, onChange2, serializable, serialize, ValOrArr} from 'ts-browser-helpers'
import {uiConfig, uiFolderContainer, uiMonitor, uiSlider, uiToggle} from 'uiconfig.js' import {uiConfig, uiFolderContainer, uiMonitor, uiSlider, uiToggle} from 'uiconfig.js'
import {generateUUID, textureDataToImageData} from '../three' import {generateUUID, textureDataToImageData} from '../three'
import {BlobExt, EXRExporter2} from '../assetmanager' import {BlobExt, EXRExporter2} from '../assetmanager'


/** /**
* Converts a render target to a png/jpeg data url string. * Converts a render target to a png/jpeg data url string.
* Note: this will clamp the values to [0, 1] and converts to srgb for float and half-float render targets.
* @param target * @param target
* @param mimeType * @param mimeType
* @param quality * @param quality
} }


ctx.putImageData(imageData, 0, 0) ctx.putImageData(imageData, 0, 0)
const string = canvas.toDataURL(mimeType, quality)

const string = (target.texture.flipY ? canvas : canvasFlipY(canvas)).toDataURL(mimeType, quality) // intentionally inverted ternary
canvas.remove() canvas.remove()
return string return string
} }

+ 1
- 1
src/three/utils/index.ts 查看文件

export {uniform, matDefine} from './decorators' export {uniform, matDefine} from './decorators'
export {getEncodingComponents, getTexelEncoding, getTexelDecoding, getTexelDecoding2, getTexelDecodingFunction, getTexelEncodingFunction, getTextureColorSpaceFromMap} from './encoding' export {getEncodingComponents, getTexelEncoding, getTexelDecoding, getTexelDecoding2, getTexelDecodingFunction, getTexelEncodingFunction, getTextureColorSpaceFromMap} from './encoding'
export {generateUUID, toIndexedGeometry} from './misc' export {generateUUID, toIndexedGeometry} from './misc'
export {getTextureDataType, textureToCanvas, textureDataToImageData, textureToDataUrl, imageToCanvas} from './texture'
export {getTextureDataType, textureToCanvas, textureDataToImageData, textureToDataUrl, texImageToCanvas} from './texture'


// export {} from './constants' // export {} from './constants'

+ 9
- 7
src/three/utils/texture.ts 查看文件

WebGLRenderer, WebGLRenderer,
} from 'three' } from 'three'
import {TextureImageData} from 'three/src/textures/types' import {TextureImageData} from 'three/src/textures/types'
import {LinearToSRGB} from 'ts-browser-helpers'
import {canvasFlipY, LinearToSRGB} from 'ts-browser-helpers'


export function getTextureDataType(renderer?: WebGLRenderer): TextureDataType { export function getTextureDataType(renderer?: WebGLRenderer): TextureDataType {
if (!renderer) return UnsignedByteType if (!renderer) return UnsignedByteType
* @param flipY * @param flipY
* @param canvas * @param canvas
*/ */
export function textureToCanvas(texture: Texture|DataTexture, maxWidth: number, flipY = false, canvas?: HTMLCanvasElement) {
export function textureToCanvas(texture: Texture|DataTexture, maxWidth: number, flipY = false) {
let img let img
if ((texture as DataTexture).isDataTexture) img = textureDataToImageData(texture.image, texture.colorSpace) if ((texture as DataTexture).isDataTexture) img = textureDataToImageData(texture.image, texture.colorSpace)
else img = texture.image else img = texture.image
return imageToCanvas(img, maxWidth, flipY, canvas)
return texImageToCanvas(img, maxWidth, flipY)
} }


export function imageToCanvas(image: TexImageSource, maxWidth: number, flipY = false, canvas?: HTMLCanvasElement) {
canvas = canvas || document.createElement('canvas')
export function texImageToCanvas(image: TexImageSource, maxWidth: number, flipY = false) {
const canvas = document.createElement('canvas')
// resize it to the size of our image // resize it to the size of our image
canvas.width = Math.min(maxWidth, image.width as number) canvas.width = Math.min(maxWidth, image.width as number)
canvas.height = Math.floor(1.0 + canvas.width * (image.height as number) / (image.width as number)) canvas.height = Math.floor(1.0 + canvas.width * (image.height as number) / (image.width as number))


} }


let needsFlipY = false
if ((image as ImageData).data !== undefined) { // THREE.DataTexture if ((image as ImageData).data !== undefined) { // THREE.DataTexture
const imageData = image as ImageData const imageData = image as ImageData


console.error('textureToDataUrl: could not get temp canvas context') console.error('textureToDataUrl: could not get temp canvas context')
ctx.putImageData(imageData, 0, 0) ctx.putImageData(imageData, 0, 0)
} else { } else {
tempCtx.putImageData(imageData, 0, 0)
tempCtx.putImageData(imageData, 0, 0) // for resize
ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height) ctx.drawImage(tempCanvas, 0, 0, canvas.width, canvas.height)
} }
} else { } else {
ctx.putImageData(imageData, 0, 0) ctx.putImageData(imageData, 0, 0)
if (flipY) needsFlipY = true // because of putImageData
} }


} else { } else {
ctx.drawImage(image as any, 0, 0, canvas.width, canvas.height) ctx.drawImage(image as any, 0, 0, canvas.width, canvas.height)
} }
return canvas
return !needsFlipY ? canvas : canvasFlipY(canvas)
} }


export function textureToDataUrl(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) { export function textureToDataUrl(texture: Texture|DataTexture, maxWidth: number, flipY: boolean, mimeType?: string, quality?: number) {

+ 28
- 1
src/utils/browser-helpers.ts 查看文件

export {downloadBlob, blobToDataURL} from 'ts-browser-helpers'
export type {IEvent, IEventDispatcher} from 'ts-browser-helpers'
export type {ImageCanvasOptions} from 'ts-browser-helpers'
export type {AnyFunction, AnyOptions, Class, IDisposable, IJSONSerializable, PartialPick, PartialRecord, StringKeyOf, Fof, ValOrFunc, ValOrArr, ValOrArrOp} from 'ts-browser-helpers'
export type {Serializer} from 'ts-browser-helpers'
export {PointerDragHelper} from 'ts-browser-helpers'
export {Damper} from 'ts-browser-helpers'
export {SimpleEventDispatcher} from 'ts-browser-helpers'
export {createCanvasElement, createDiv, createImage, createStyles, createScriptFromURL} from 'ts-browser-helpers'
export {TYPED_ARRAYS, arrayBufferToBase64, base64ToArrayBuffer, getTypedArray} from 'ts-browser-helpers'
export {escapeRegExp, getFilenameFromPath, parseFileExtension, replaceAll, toTitleCase, longestCommonPrefix} from 'ts-browser-helpers'
export {prettyScrollbar} from 'ts-browser-helpers'
export {blobToDataURL, downloadBlob, downloadFile, uploadFile, mobileAndTabletCheck} from 'ts-browser-helpers'
export {LinearToSRGB, SRGBToLinear, colorToDataUrl} from 'ts-browser-helpers'
export {onChange, onChange2, onChange3, serialize, serializable} from 'ts-browser-helpers'
export {aesGcmDecrypt, aesGcmEncrypt} from 'ts-browser-helpers'
export {verifyPermission, writeFile, getFileHandle, getNewFileHandle, readFile} from 'ts-browser-helpers'
export {embedUrlRefs, htmlToCanvas, htmlToPng, htmlToSvg} from 'ts-browser-helpers'
export {imageToCanvas, imageBitmapToBase64, imageUrlToImageData, imageDataToCanvas, isWebpExportSupported, canvasFlipY} from 'ts-browser-helpers'
export {absMax, clearBit, updateBit} from 'ts-browser-helpers'
export {includesAll} from 'ts-browser-helpers'
export {copyProps, getOrCall, getPropertyDescriptor, isPropertyWritable, safeSetProperty} from 'ts-browser-helpers'
export {deepAccessObject, getKeyByValue, objectHasOwn} from 'ts-browser-helpers'
export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers'
export {timeout, now} from 'ts-browser-helpers'
export {pathJoin, getUrlQueryParam, setUrlQueryParam, remoteWorkerURL} from 'ts-browser-helpers'
export {css, glsl, html, svgUrl} from 'ts-browser-helpers'
export {Serialization} from 'ts-browser-helpers'


+ 0
- 1
src/utils/index.ts 查看文件

export {shaderReplaceString} from './shader-helpers' export {shaderReplaceString} from './shader-helpers'
export {makeGLBFile} from './gltf' export {makeGLBFile} from './gltf'


export {serialize, serializable, Serialization} from 'ts-browser-helpers'

+ 10
- 10
src/utils/serialization.ts 查看文件

}) })
} }


export function getEmptyMeta(): SerializationMetaType {
export function getEmptyMeta(res?: Partial<SerializationResourcesType>): SerializationMetaType {
return { // see Object3D.js toJSON for more details return { // see Object3D.js toJSON for more details
geometries: {},
materials: {},
textures: {},
images: {},
shapes: [],
skeletons: {},
animations: [],
extras: {},
geometries: {...res?.geometries},
materials: {...res?.materials},
textures: {...res?.textures},
images: {...res?.images},
shapes: {...res?.shapes},
skeletons: {...res?.skeletons},
animations: {...res?.animations},
extras: {...res?.extras},
_context: {}, _context: {},
} }
} }
} }
export function metaFromResources(resources?: Partial<SerializationResourcesType>, viewer?: ThreeViewer): SerializationMetaType { export function metaFromResources(resources?: Partial<SerializationResourcesType>, viewer?: ThreeViewer): SerializationMetaType {
return { return {
...getEmptyMeta(),
...resources, ...resources,
...getEmptyMeta(resources),
_context: { _context: {
assetManager: viewer?.assetManager, assetManager: viewer?.assetManager,
assetImporter: viewer?.assetManager.importer, assetImporter: viewer?.assetManager.importer,

+ 1
- 1
src/viewer/AViewerPlugin.ts 查看文件

// } // }


get dirty(): boolean { get dirty(): boolean {
return this._dirty
return this.enabled && this._dirty
} }


set dirty(value: boolean) { set dirty(value: boolean) {

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

const obj = <RootSceneImportResult>imported const obj = <RootSceneImportResult>imported
if (obj.importedViewerConfig && options?.importConfig !== false) await this.importConfig(obj.importedViewerConfig) if (obj.importedViewerConfig && options?.importConfig !== false) await this.importConfig(obj.importedViewerConfig)
this._scene.loadModelRoot(obj, options) this._scene.loadModelRoot(obj, options)
return imported
return this._scene.modelRoot as T
} }
this._scene.addObject(imported, options) this._scene.addObject(imported, options)
return imported return imported

正在加载...
取消
保存