| @@ -43,7 +43,7 @@ jobs: | |||
| cache: 'npm' | |||
| 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 run docs-all | |||
| - run: npm run docs | |||
| - run: mkdir _site | |||
| - run: mv -t _site src docs dist examples README.md LICENSE index.html | |||
| - run: mkdir -p _site/plugins | |||
| @@ -18,8 +18,8 @@ async function init() { | |||
| dropzone: { | |||
| allowedExtensions: ['ktx2'], | |||
| }, | |||
| plugins: [KTX2LoadPlugin], | |||
| }) | |||
| viewer.addPluginSync(KTX2LoadPlugin) | |||
| viewer.scene.setBackgroundColor('#555555') | |||
| @@ -49,7 +49,7 @@ async function init() { | |||
| // Listen to when a file is dropped | |||
| viewer.assetManager.addEventListener('loadAsset', (e)=>{ | |||
| if (!e.data.isTexture) return | |||
| if (!e.data?.isTexture) return | |||
| const texture = e.data as ITexture | |||
| texture.colorSpace = SRGBColorSpace | |||
| const material = new UnlitMaterial({ | |||
| @@ -25,6 +25,12 @@ | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| #gridItemList{ | |||
| /*because of the code preview button*/ | |||
| top: 50px !important; | |||
| height: calc(100% - 50px) !important; | |||
| } | |||
| </style> | |||
| <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> | |||
| @@ -1,4 +1,12 @@ | |||
| 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 {MaterialConfiguratorPlugin} from '@threepipe/plugin-configurator' | |||
| @@ -7,7 +15,7 @@ async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||
| plugins: [LoadingScreenPlugin, PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||
| dropzone: { | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| @@ -25,6 +25,12 @@ | |||
| margin: 0; | |||
| overflow: hidden; | |||
| } | |||
| #gridItemList{ | |||
| /*because of the code preview button*/ | |||
| top: 50px !important; | |||
| height: calc(100% - 50px) !important; | |||
| } | |||
| </style> | |||
| <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> | |||
| @@ -1,4 +1,12 @@ | |||
| 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 {SwitchNodePlugin} from '@threepipe/plugin-configurator' | |||
| @@ -7,7 +15,7 @@ async function init() { | |||
| const viewer = new ThreeViewer({ | |||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||
| msaa: true, | |||
| plugins: [PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||
| plugins: [LoadingScreenPlugin, PickingPlugin, FrameFadePlugin, SSAAPlugin], | |||
| dropzone: { | |||
| addOptions: { | |||
| disposeSceneObjects: true, | |||
| @@ -446,7 +446,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data?: ImportResult | |||
| * 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} | |||
| */ | |||
| 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 | |||
| @@ -455,7 +455,7 @@ export class AssetManager extends EventDispatcher<BaseEvent&{data?: ImportResult | |||
| * @param path | |||
| * @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) | |||
| else this.processState.set(path, value) | |||
| this.dispatchEvent({type: 'processStateUpdate'}) | |||
| @@ -142,15 +142,15 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| const oldMaps = this._materialMaps.get(mat.uuid) || new Set<ITexture>() | |||
| for (const map of newMaps) { | |||
| 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) | |||
| } | |||
| for (const map of oldMaps) { | |||
| if (newMaps.has(map)) continue | |||
| 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) | |||
| if (!mats || map.userData.disposeOnIdle === false) continue | |||
| if (mats.size === 0) map.dispose() | |||
| @@ -11,7 +11,7 @@ import {GLTFPreparser} from '../import' | |||
| * @param options | |||
| */ | |||
| 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) { | |||
| options.encryptKey = window.prompt('GLTFEncryption: Enter encryption key/password') || '' | |||
| } | |||
| @@ -11,7 +11,6 @@ export interface ITextureUserData{ | |||
| * Works only after it's applied to a material once. | |||
| */ | |||
| disposeOnIdle?: boolean | |||
| __appliedMaterials?: Set<IMaterial> | |||
| } | |||
| export type ITextureEventTypes = 'dispose' | 'update' | |||
| export type ITextureEvent<T extends string = ITextureEventTypes> = Event & { | |||
| @@ -37,13 +36,15 @@ export interface ITexture extends Texture { | |||
| _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 | |||
| } | |||
| export function upgradeTexture(this: ITexture) { | |||
| this.assetType = 'texture' | |||
| 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 | |||
| // todo: uiconfig, dispose, etc | |||
| } | |||
| @@ -12,7 +12,6 @@ export class CanvasSnapshotPlugin extends AViewerPluginSync<''> { | |||
| constructor() { | |||
| super() | |||
| this.downloadSnapshot = this.downloadSnapshot.bind(this) | |||
| this.getDataUrl({}) | |||
| } | |||
| /** | |||
| @@ -13,7 +13,7 @@ import {uiFolderContainer, uiNumber, uiToggle} from 'uiconfig.js' | |||
| export class MeshOptSimplifyModifierPlugin extends SimplifyModifierPlugin { | |||
| public static readonly PluginType = 'MeshOptSimplifyModifierPlugin' | |||
| constructor(initialize = true) { | |||
| constructor(initialize = true, public readonly rootNode = document.head) { | |||
| super() | |||
| // todo: check if compatible? | |||
| if (initialize) this.initialize() | |||
| @@ -31,6 +31,7 @@ export class MeshOptSimplifyModifierPlugin extends SimplifyModifierPlugin { | |||
| } | |||
| protected _initializing?: Promise<void> = undefined | |||
| protected _script?: HTMLScriptElement | |||
| async initialize() { | |||
| if (this.initialized) return | |||
| @@ -46,14 +47,19 @@ window.dispatchEvent(new CustomEvent('${ev}')) | |||
| }); | |||
| ` | |||
| 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() | |||
| @@ -3,7 +3,7 @@ import {GLTFWriter2, ILoader, Importer, ImportResultExtras} from '../../assetman | |||
| import {KTX2Loader} from 'three/examples/jsm/loaders/KTX2Loader.js' | |||
| import {CompressedTexture} from 'three' | |||
| import {serializeTextureInExtras} from '../../utils' | |||
| import {ITexture} from '../../core' | |||
| import {ITexture, upgradeTexture} from '../../core' | |||
| import {BaseImporterPlugin} from '../base/BaseImporterPlugin' | |||
| /** | |||
| @@ -35,17 +35,26 @@ export class KTX2LoadPlugin extends BaseImporterPlugin { | |||
| } | |||
| 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> { | |||
| const buffer2 = new Uint8Array(buffer.slice(0)) // clones the buffer | |||
| 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.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 | |||
| } | |||
| @@ -5,8 +5,8 @@ import {IGeometry, IMaterial, IObject3D, IScene, ITexture} from '../../core' | |||
| /** | |||
| * 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() { | |||
| @@ -21,7 +21,7 @@ export function generateUUID() { | |||
| export function isInScene(...sceneObj: (IGeometry|IMaterial|IObject3D|ITexture)[]): boolean { | |||
| if (sceneObj.length > 1) return sceneObj.some((a)=>isInScene(a)) | |||
| 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 = | |||
| (<IObject3D>o).isObject3D ? [<IObject3D>o] : | |||