| cache: 'npm' | cache: 'npm' | ||||
| cache-dependency-path: '**/package-lock.json' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-data | cache-dependency-path: '**/package-lock.json' # https://github.com/actions/setup-node/blob/main/docs/advanced-usage.md#caching-packages-data | ||||
| - run: npm ci # this will also run `npm run prepare` which will build # todo use --cache .npm | - run: npm ci # this will also run `npm run prepare` which will build # todo use --cache .npm | ||||
| - run: npm run docs-all | |||||
| - run: npm run docs | |||||
| - run: mkdir _site | - run: mkdir _site | ||||
| - run: mv -t _site src docs dist examples README.md LICENSE index.html | - run: mv -t _site src docs dist examples README.md LICENSE index.html | ||||
| - run: mkdir -p _site/plugins | - run: mkdir -p _site/plugins |
| dropzone: { | dropzone: { | ||||
| allowedExtensions: ['ktx2'], | allowedExtensions: ['ktx2'], | ||||
| }, | }, | ||||
| plugins: [KTX2LoadPlugin], | |||||
| }) | }) | ||||
| viewer.addPluginSync(KTX2LoadPlugin) | |||||
| viewer.scene.setBackgroundColor('#555555') | viewer.scene.setBackgroundColor('#555555') | ||||
| // Listen to when a file is dropped | // Listen to when a file is dropped | ||||
| viewer.assetManager.addEventListener('loadAsset', (e)=>{ | viewer.assetManager.addEventListener('loadAsset', (e)=>{ | ||||
| if (!e.data.isTexture) return | |||||
| if (!e.data?.isTexture) return | |||||
| const texture = e.data as ITexture | const texture = e.data as ITexture | ||||
| texture.colorSpace = SRGBColorSpace | texture.colorSpace = SRGBColorSpace | ||||
| const material = new UnlitMaterial({ | const material = new UnlitMaterial({ |
| margin: 0; | margin: 0; | ||||
| overflow: hidden; | overflow: hidden; | ||||
| } | } | ||||
| #gridItemList{ | |||||
| /*because of the code preview button*/ | |||||
| top: 50px !important; | |||||
| height: calc(100% - 50px) !important; | |||||
| } | |||||
| </style> | </style> | ||||
| <script type="module" src="../examples-utils/simple-code-preview.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> | <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> |
| import {_testFinish, FrameFadePlugin, IObject3D, PickingPlugin, SSAAPlugin, ThreeViewer} from 'threepipe' | |||||
| import { | |||||
| _testFinish, | |||||
| FrameFadePlugin, | |||||
| IObject3D, | |||||
| LoadingScreenPlugin, | |||||
| PickingPlugin, | |||||
| SSAAPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| import {MaterialConfiguratorPlugin} from '@threepipe/plugin-configurator' | import {MaterialConfiguratorPlugin} from '@threepipe/plugin-configurator' | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: true, | msaa: true, | ||||
| plugins: [PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||||
| plugins: [LoadingScreenPlugin, PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||||
| dropzone: { | dropzone: { | ||||
| addOptions: { | addOptions: { | ||||
| disposeSceneObjects: true, | disposeSceneObjects: true, |
| margin: 0; | margin: 0; | ||||
| overflow: hidden; | overflow: hidden; | ||||
| } | } | ||||
| #gridItemList{ | |||||
| /*because of the code preview button*/ | |||||
| top: 50px !important; | |||||
| height: calc(100% - 50px) !important; | |||||
| } | |||||
| </style> | </style> | ||||
| <script type="module" src="../examples-utils/simple-code-preview.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> | <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> |
| import {_testFinish, FrameFadePlugin, IObject3D, PickingPlugin, SSAAPlugin, ThreeViewer} from 'threepipe' | |||||
| import { | |||||
| _testFinish, | |||||
| FrameFadePlugin, | |||||
| IObject3D, | |||||
| LoadingScreenPlugin, | |||||
| PickingPlugin, | |||||
| SSAAPlugin, | |||||
| ThreeViewer, | |||||
| } from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| import {SwitchNodePlugin} from '@threepipe/plugin-configurator' | import {SwitchNodePlugin} from '@threepipe/plugin-configurator' | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: true, | msaa: true, | ||||
| plugins: [PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||||
| plugins: [LoadingScreenPlugin, PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||||
| dropzone: { | dropzone: { | ||||
| addOptions: { | addOptions: { | ||||
| disposeSceneObjects: true, | disposeSceneObjects: true, |
| * State of download/upload/process/other processes in the viewer. | * State of download/upload/process/other processes in the viewer. | ||||
| * Subscribes to importer and exporter by default, more can be added by plugins like {@link FileTransferPlugin} | * Subscribes to importer and exporter by default, more can be added by plugins like {@link FileTransferPlugin} | ||||
| */ | */ | ||||
| processState: Map<string, {state: string, progress: number | undefined}> = new Map() | |||||
| processState: Map<string, {state: string, progress?: number | undefined}> = new Map() | |||||
| /** | /** | ||||
| * Set process state for a path | * Set process state for a path | ||||
| * @param path | * @param path | ||||
| * @param value | * @param value | ||||
| */ | */ | ||||
| setProcessState(path: string, value: {state: string, progress: number | undefined} | undefined) { | |||||
| setProcessState(path: string, value: {state: string, progress?: number | undefined} | undefined) { | |||||
| if (value === undefined) this.processState.delete(path) | if (value === undefined) this.processState.delete(path) | ||||
| else this.processState.set(path, value) | else this.processState.set(path, value) | ||||
| this.dispatchEvent({type: 'processStateUpdate'}) | this.dispatchEvent({type: 'processStateUpdate'}) |
| const oldMaps = this._materialMaps.get(mat.uuid) || new Set<ITexture>() | const oldMaps = this._materialMaps.get(mat.uuid) || new Set<ITexture>() | ||||
| for (const map of newMaps) { | for (const map of newMaps) { | ||||
| if (oldMaps.has(map)) continue | if (oldMaps.has(map)) continue | ||||
| if (!map.userData.__appliedMaterials) map.userData.__appliedMaterials = new Set<IMaterial>() | |||||
| map.userData.__appliedMaterials.add(mat) | |||||
| if (!map._appliedMaterials) map._appliedMaterials = new Set<IMaterial>() | |||||
| map._appliedMaterials.add(mat) | |||||
| map.addEventListener('update', mat.__textureUpdate) | map.addEventListener('update', mat.__textureUpdate) | ||||
| } | } | ||||
| for (const map of oldMaps) { | for (const map of oldMaps) { | ||||
| if (newMaps.has(map)) continue | if (newMaps.has(map)) continue | ||||
| map.removeEventListener('update', mat.__textureUpdate) | map.removeEventListener('update', mat.__textureUpdate) | ||||
| if (!map.userData.__appliedMaterials) continue | |||||
| const mats = map.userData.__appliedMaterials | |||||
| if (!map._appliedMaterials) continue | |||||
| const mats = map._appliedMaterials | |||||
| mats?.delete(mat) | mats?.delete(mat) | ||||
| if (!mats || map.userData.disposeOnIdle === false) continue | if (!mats || map.userData.disposeOnIdle === false) continue | ||||
| if (mats.size === 0) map.dispose() | if (mats.size === 0) map.dispose() |
| * @param options | * @param options | ||||
| */ | */ | ||||
| export const glbEncryptionProcessor = async(gltf: ArrayBuffer|any, options: GLTFExporter2Options) => { | export const glbEncryptionProcessor = async(gltf: ArrayBuffer|any, options: GLTFExporter2Options) => { | ||||
| if (!gltf || typeof gltf === 'object' || !gltf.byteLength) return gltf | |||||
| if (!gltf || !(gltf instanceof ArrayBuffer) || !gltf.byteLength || !options.encrypt) return gltf | |||||
| if (!options.encryptKey && window && window.prompt) { | if (!options.encryptKey && window && window.prompt) { | ||||
| options.encryptKey = window.prompt('GLTFEncryption: Enter encryption key/password') || '' | options.encryptKey = window.prompt('GLTFEncryption: Enter encryption key/password') || '' | ||||
| } | } |
| * Works only after it's applied to a material once. | * Works only after it's applied to a material once. | ||||
| */ | */ | ||||
| disposeOnIdle?: boolean | disposeOnIdle?: boolean | ||||
| __appliedMaterials?: Set<IMaterial> | |||||
| } | } | ||||
| export type ITextureEventTypes = 'dispose' | 'update' | export type ITextureEventTypes = 'dispose' | 'update' | ||||
| export type ITextureEvent<T extends string = ITextureEventTypes> = Event & { | export type ITextureEvent<T extends string = ITextureEventTypes> = Event & { | ||||
| _sourceImgBuffer?: ArrayBuffer // see KTX2LoadPlugin and serializeTextureInExtras | _sourceImgBuffer?: ArrayBuffer // see KTX2LoadPlugin and serializeTextureInExtras | ||||
| } | } | ||||
| _appliedMaterials?: Set<IMaterial> // for internal use only. refers to the materials that this texture is applied to | |||||
| _target?: IRenderTarget // for internal use only. refers to the render target that this texture is attached to | _target?: IRenderTarget // for internal use only. refers to the render target that this texture is attached to | ||||
| } | } | ||||
| export function upgradeTexture(this: ITexture) { | export function upgradeTexture(this: ITexture) { | ||||
| this.assetType = 'texture' | this.assetType = 'texture' | ||||
| if (!this.userData) this.userData = {} | if (!this.userData) this.userData = {} | ||||
| if (!this.userData.__appliedMaterials) this.userData.__appliedMaterials = new Set() | |||||
| if (!this._appliedMaterials) this._appliedMaterials = new Set() | |||||
| if (!this.setDirty) this.setDirty = ()=>this.needsUpdate = true | if (!this.setDirty) this.setDirty = ()=>this.needsUpdate = true | ||||
| // todo: uiconfig, dispose, etc | // todo: uiconfig, dispose, etc | ||||
| } | } |
| constructor() { | constructor() { | ||||
| super() | super() | ||||
| this.downloadSnapshot = this.downloadSnapshot.bind(this) | this.downloadSnapshot = this.downloadSnapshot.bind(this) | ||||
| this.getDataUrl({}) | |||||
| } | } | ||||
| /** | /** |
| export class MeshOptSimplifyModifierPlugin extends SimplifyModifierPlugin { | export class MeshOptSimplifyModifierPlugin extends SimplifyModifierPlugin { | ||||
| public static readonly PluginType = 'MeshOptSimplifyModifierPlugin' | public static readonly PluginType = 'MeshOptSimplifyModifierPlugin' | ||||
| constructor(initialize = true) { | |||||
| constructor(initialize = true, public readonly rootNode = document.head) { | |||||
| super() | super() | ||||
| // todo: check if compatible? | // todo: check if compatible? | ||||
| if (initialize) this.initialize() | if (initialize) this.initialize() | ||||
| } | } | ||||
| protected _initializing?: Promise<void> = undefined | protected _initializing?: Promise<void> = undefined | ||||
| protected _script?: HTMLScriptElement | |||||
| async initialize() { | async initialize() { | ||||
| if (this.initialized) return | if (this.initialized) return | ||||
| }); | }); | ||||
| ` | ` | ||||
| this._initializing = new Promise<void>((res) => { | this._initializing = new Promise<void>((res) => { | ||||
| const l = () => { | |||||
| window.removeEventListener(ev, l) | |||||
| res() // todo timeout? | |||||
| } | |||||
| window.addEventListener(ev, l) | |||||
| document.head.appendChild(s) // todo remove later? | |||||
| // this._script = s | |||||
| window.addEventListener(ev, ()=>res(), {once: true}) | |||||
| this.rootNode.appendChild(s) | |||||
| this._script = s | |||||
| }) | }) | ||||
| return await this._initializing | |||||
| } | |||||
| dispose() { | |||||
| if (this._script) { | |||||
| this._script.remove() | |||||
| delete window.MeshoptSimplifier | |||||
| } | |||||
| this._script = undefined | |||||
| } | } | ||||
| @uiNumber() | @uiNumber() |
| import {KTX2Loader} from 'three/examples/jsm/loaders/KTX2Loader.js' | import {KTX2Loader} from 'three/examples/jsm/loaders/KTX2Loader.js' | ||||
| import {CompressedTexture} from 'three' | import {CompressedTexture} from 'three' | ||||
| import {serializeTextureInExtras} from '../../utils' | import {serializeTextureInExtras} from '../../utils' | ||||
| import {ITexture} from '../../core' | |||||
| import {ITexture, upgradeTexture} from '../../core' | |||||
| import {BaseImporterPlugin} from '../base/BaseImporterPlugin' | import {BaseImporterPlugin} from '../base/BaseImporterPlugin' | ||||
| /** | /** | ||||
| } | } | ||||
| export class KTX2Loader2 extends KTX2Loader implements ILoader { | export class KTX2Loader2 extends KTX2Loader implements ILoader { | ||||
| private _initTexture(t: CompressedTexture & ITexture) { | |||||
| upgradeTexture.call(t) | |||||
| t.userData.mimeType = 'image/ktx2' | |||||
| t.toJSON = (meta?: any)=>{ | |||||
| return serializeTextureInExtras(t, meta, t.name, 'image/ktx2') | |||||
| } | |||||
| const cloneFn = t.clone | |||||
| t.clone = ()=>{ | |||||
| const res = cloneFn.call(t) | |||||
| if (res.source !== t.source) // in case something changes | |||||
| res.source._sourceImgBuffer = t.source._sourceImgBuffer | |||||
| return this._initTexture(res) | |||||
| } | |||||
| return t | |||||
| } | |||||
| async createTexture(buffer: ArrayBuffer, config: any): Promise<CompressedTexture> { | async createTexture(buffer: ArrayBuffer, config: any): Promise<CompressedTexture> { | ||||
| const buffer2 = new Uint8Array(buffer.slice(0)) // clones the buffer | const buffer2 = new Uint8Array(buffer.slice(0)) // clones the buffer | ||||
| const texture = (await super.createTexture(buffer, config)) as CompressedTexture & ITexture | const texture = (await super.createTexture(buffer, config)) as CompressedTexture & ITexture | ||||
| texture.source._sourceImgBuffer = buffer2 // keep the same buffer when cloned and all, used in serializeTextureInExtras | texture.source._sourceImgBuffer = buffer2 // keep the same buffer when cloned and all, used in serializeTextureInExtras | ||||
| texture.userData.mimeType = 'image/ktx2' | |||||
| texture.toJSON = (meta?: any)=>{ | |||||
| return serializeTextureInExtras(texture, meta, texture.name, 'image/ktx2') | |||||
| } | |||||
| texture.clone = ()=>{ | |||||
| throw new Error('ktx2 texture cloning not supported') | |||||
| } | |||||
| this._initTexture(texture) | |||||
| return texture | return texture | ||||
| } | } | ||||
| /** | /** | ||||
| * Convert geometry to BufferGeometry with indexed attributes. | * Convert geometry to BufferGeometry with indexed attributes. | ||||
| */ | */ | ||||
| export function toIndexedGeometry(geometry: BufferGeometry<any, any, any>, tolerance = -1) { | |||||
| return mergeVertices(geometry, tolerance) | |||||
| export function toIndexedGeometry<T extends BufferGeometry<any, any, any> = BufferGeometry<any, any, any>>(geometry: T, tolerance = -1): T { | |||||
| return mergeVertices(geometry, tolerance) as T | |||||
| } | } | ||||
| export function generateUUID() { | export function generateUUID() { | ||||
| export function isInScene(...sceneObj: (IGeometry|IMaterial|IObject3D|ITexture)[]): boolean { | export function isInScene(...sceneObj: (IGeometry|IMaterial|IObject3D|ITexture)[]): boolean { | ||||
| if (sceneObj.length > 1) return sceneObj.some((a)=>isInScene(a)) | if (sceneObj.length > 1) return sceneObj.some((a)=>isInScene(a)) | ||||
| const o = sceneObj[0] | const o = sceneObj[0] | ||||
| if ((<ITexture>o).isTexture) return Array.from((<ITexture>o).userData.__appliedMaterials || []).some((m) => isInScene(m)) ?? false | |||||
| if ((<ITexture>o).isTexture) return Array.from((<ITexture>o)._appliedMaterials || []).some((m) => isInScene(m)) ?? false | |||||
| const objects = | const objects = | ||||
| (<IObject3D>o).isObject3D ? [<IObject3D>o] : | (<IObject3D>o).isObject3D ? [<IObject3D>o] : |