| @@ -3,14 +3,14 @@ import json from '@rollup/plugin-json'; | |||
| import resolve from '@rollup/plugin-node-resolve'; | |||
| import typescript from '@rollup/plugin-typescript'; | |||
| import packageJson from './package.json' assert {type: 'json'}; | |||
| import path from 'path' | |||
| import {fileURLToPath} from 'url'; | |||
| import path from 'node:path' | |||
| import {fileURLToPath} from 'node:url'; | |||
| import postcss from 'rollup-plugin-postcss' | |||
| import glsl from "rollup-plugin-glsl" | |||
| import replace from "@rollup/plugin-replace"; | |||
| import terser from "@rollup/plugin-terser"; | |||
| import commonjs from "@rollup/plugin-commonjs"; | |||
| import license from "rollup-plugin-license"; | |||
| import terser from "@rollup/plugin-terser"; | |||
| const __filename = fileURLToPath(import.meta.url); | |||
| const __dirname = path.dirname(__filename); | |||
| @@ -59,6 +59,8 @@ export default { | |||
| plugins: [ | |||
| replace({ | |||
| 'process.env.NODE_ENV': JSON.stringify( 'production' ), | |||
| '.css?inline': '.css', | |||
| preventAssignment: true, | |||
| }), | |||
| // replace({ | |||
| // exclude: 'src/**', | |||
| @@ -71,6 +73,7 @@ export default { | |||
| include: "src/**/*.glsl" | |||
| }), | |||
| postcss({ | |||
| extensions: ['.css', '.css?inline'], | |||
| modules: false, | |||
| autoModules: true, // todo; issues with typescript import css, because inject is false | |||
| inject: false, | |||
| @@ -241,17 +241,27 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| // return object | |||
| // } | |||
| // protected abstract _processModel(object: any, options: AnyOptions): any | |||
| convertToIMaterial(material: Material&{assetType?:'material', iMaterial?: IMaterial}, options: {useSourceMaterial?:boolean, materialTemplate?: string} = {}): IMaterial|undefined { | |||
| /** | |||
| * Creates a new material if a compatible template is found or apply minimal upgrades and returns the original material. | |||
| * Also checks from the registered materials, if one with the same uuid is found, it is returned instead with the new parameters. | |||
| * Also caches the response. | |||
| * Returns the same material if its already upgraded. | |||
| * @param material - the material to upgrade/check | |||
| * @param useSourceMaterial - if false, will not use the source material parameters in the new material. default = true | |||
| * @param materialTemplate - any specific material template to use instead of detecting from the material type. | |||
| * @param createFromTemplate - if false, will not create a new material from the template, but will apply minimal upgrades to the material instead. default = true | |||
| */ | |||
| convertToIMaterial(material: Material&{assetType?:'material', iMaterial?: IMaterial}, {useSourceMaterial = true, materialTemplate, createFromTemplate = true}: {useSourceMaterial?:boolean, materialTemplate?: string, createFromTemplate?: boolean} = {}): IMaterial|undefined { | |||
| if (!material) return | |||
| if (material.assetType) return <IMaterial>material | |||
| if (material.iMaterial?.assetType) return material.iMaterial | |||
| const uuid = material.userData?.uuid || material.uuid | |||
| let mat = this.findMaterial(uuid) | |||
| if (!mat) { | |||
| const ignoreSource = options.useSourceMaterial === false || !material.isMaterial | |||
| const template = options.materialTemplate || (!ignoreSource && material.type ? material.type || 'physical' : 'physical') | |||
| if (!mat && createFromTemplate !== false) { | |||
| const ignoreSource = useSourceMaterial === false || !material.isMaterial | |||
| const template = materialTemplate || (!ignoreSource && material.type ? material.type || 'physical' : 'physical') | |||
| mat = this.create(template, ignoreSource ? undefined : material) | |||
| } else { | |||
| } else if (mat) { | |||
| // if ((mat as any).iMaterial) mat = (mat as any).iMaterial | |||
| console.warn('Material with the same uuid already exists, copying properties') | |||
| if (material.type !== mat!.type) console.error('Material type mismatch, delete previous material first?', material, mat) | |||
| @@ -262,7 +272,7 @@ export class MaterialManager<T = ''> extends EventDispatcher<BaseEvent, T> { | |||
| mat.userData.uuid = uuid | |||
| material.iMaterial = mat | |||
| } else { | |||
| console.warn('Failed to convert material to IMaterial, just upgrading', material, options) | |||
| console.warn('Failed to convert material to IMaterial, just upgrading', material, useSourceMaterial, materialTemplate) | |||
| mat = iMaterialCommons.upgradeMaterial.call(material) | |||
| } | |||
| return mat | |||
| @@ -484,7 +484,7 @@ class OBJLoader2 extends Loader implements ILoader{ | |||
| } | |||
| declare loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any> | |||
| loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any> | |||
| setMaterials( materials ) { | |||
| @@ -78,7 +78,7 @@ export interface IObject3DUserData extends IImportResultUserData { | |||
| autoScaleRadius?: number | |||
| autoScaled?: boolean | |||
| geometriesCentered?: boolean | |||
| /** | |||
| @@ -200,13 +200,13 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent | |||
| * @param isCentered - optional (taken from userData.isCentered by default) | |||
| * @param setDirty - true by default | |||
| */ | |||
| autoScale?<T extends IObject3D>(autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean): T | |||
| autoScale?(autoScaleRadius?: number, isCentered?: boolean, setDirty?: boolean): this | |||
| /** | |||
| * | |||
| * @param setDirty - calls {@link setDirty} @default true | |||
| */ | |||
| autoCenter?<T extends IObject3D>(setDirty?: boolean): T | |||
| autoCenter?(setDirty?: boolean): this | |||
| /** | |||
| * @deprecated use object directly | |||
| @@ -221,7 +221,6 @@ export interface IObject3D<E extends Event = IObject3DEvent, ET = IObject3DEvent | |||
| // __disposed?: boolean | |||
| /** | |||
| * | |||
| * @param removeFromParent - remove from parent. Default true | |||
| */ | |||
| dispose(removeFromParent?: boolean): void; | |||
| @@ -64,7 +64,7 @@ export interface IRenderManager<E extends IRenderManagerEvent = IRenderManagerEv | |||
| clock: Clock | |||
| blit(destination: IRenderTarget|undefined|null, options?: RendererBlitOptions): void | |||
| clearColor({r, g, b, a, target, depth = true, stencil = true, viewport}: | |||
| clearColor({r, g, b, a, target, depth, stencil, viewport}: | |||
| {r?: number, g?: number, b?: number, a?: number, target?: IRenderTarget, depth?: boolean, stencil?: boolean, viewport?: Vector4}): void | |||
| renderTargetToDataUrl(target: WebGLRenderTarget, mimeType?: string, quality?: number): string | |||
| @@ -86,6 +86,11 @@ export interface IWidget { | |||
| attach(object: any): this; | |||
| detach(): this; | |||
| isWidget: true; | |||
| object: any | |||
| update?(): void | |||
| dispose?(): void | |||
| } | |||
| export interface IScene<E extends ISceneEvent = ISceneEvent, ET extends ISceneEventTypes = ISceneEventTypes> | |||
| @@ -133,7 +138,7 @@ export interface IScene<E extends ISceneEvent = ISceneEvent, ET extends ISceneEv | |||
| // region deprecated | |||
| /** | |||
| @deprecated use {@link getObjectByName} instead | |||
| * @deprecated use {@link getObjectByName} instead | |||
| * @param name | |||
| * @param parent | |||
| */ | |||
| @@ -4,7 +4,7 @@ import {onChange, onChange2, onChange3, serialize} from 'ts-browser-helpers' | |||
| import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' | |||
| import {ICameraSetDirtyOptions} from '../ICamera' | |||
| import type {ICameraControls, TControlsCtor} from './ICameraControls' | |||
| import {OrbitControls3} from '../../three' | |||
| import {OrbitControls3} from '../../three/controls/OrbitControls3' | |||
| import {IObject3D} from '../IObject' | |||
| import {ThreeSerialization} from '../../utils' | |||
| import {iCameraCommons} from '../object/iCameraCommons' | |||
| @@ -53,7 +53,9 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| @serialize() readonly position: Vector3 | |||
| /** | |||
| * The target position of the camera (where the camera looks at). Also syncs with the controls.target, so it's not required to set that separately. | |||
| * Note: this is always in world-space | |||
| * Note: {@link autoLookAtTarget} must be set to trye to make the camera look at the target when no controls are enabled | |||
| */ | |||
| @uiVector('Target', undefined, undefined, (that:PerspectiveCamera2)=>({onChange: ()=>that.setDirty()})) | |||
| @serialize() readonly target: Vector3 = new Vector3(0, 0, 0) | |||
| @@ -85,6 +87,13 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| @onChange2(PerspectiveCamera2.prototype._nearFarChanged) | |||
| far = 50 | |||
| /** | |||
| * Automatically make the camera look at the {@link target} on {@link setDirty} call | |||
| * Defaults to false. Note that this must be set to true to make the camera look at the target without any controls | |||
| */ | |||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | |||
| autoLookAtTarget = false // bound to userData so that it's saved in the glb. | |||
| /** | |||
| * Automatically manage near and far clipping planes based on scene size. | |||
| */ | |||
| @@ -93,12 +102,14 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| /** | |||
| * Minimum near clipping plane allowed. (Distance from camera) | |||
| * Used in RootScene when {@link autoNearFar} is true. | |||
| * @default 0.2 | |||
| */ | |||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | |||
| minNearPlane = 0.5 | |||
| /** | |||
| * Maximum far clipping plane allowed. (Distance from camera) | |||
| * Used in RootScene when {@link autoNearFar} is true. | |||
| */ | |||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | |||
| maxFarPlane = 1000 | |||
| @@ -157,6 +168,12 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| private _interactionsDisabledBy = new Set<string>() | |||
| /** | |||
| * If interactions are enabled for this camera. It can be disabled by some code or plugin. | |||
| * see also {@link setInteractions} | |||
| * @deprecated use {@link canUserInteract} to check if the user can interact with this camera | |||
| * @readonly | |||
| */ | |||
| get interactionsEnabled(): boolean { | |||
| return this._interactionsDisabledBy.size === 0 | |||
| } | |||
| @@ -172,7 +189,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| } | |||
| get canUserInteract() { | |||
| return this.interactionsEnabled && this.isMainCamera && this.controlsMode !== '' | |||
| return this._interactionsDisabledBy.size === 0 && this.isMainCamera && this.controlsMode !== '' | |||
| } | |||
| // endregion | |||
| @@ -279,7 +296,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| // todo: only for orbit control like controls? | |||
| if (this._controls) { | |||
| const ce = this.interactionsEnabled | |||
| const ce = this.canUserInteract | |||
| this._controls.enabled = ce | |||
| if (ce) this.up.copy(Object3D.DEFAULT_UP) | |||
| } | |||
| @@ -574,7 +591,7 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | |||
| getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | |||
| getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: string) => T | undefined | |||
| copy: (source: ICamera|Camera, recursive?: boolean, distanceFromTarget?: number, worldSpace?: boolean) => this | |||
| copy: (source: ICamera|Camera|IObject3D, recursive?: boolean, distanceFromTarget?: number, worldSpace?: boolean) => this | |||
| clone: (recursive?: boolean) => this | |||
| add: (...object: IObject3D[]) => this | |||
| remove: (...object: IObject3D[]) => this | |||
| @@ -585,3 +602,24 @@ export class PerspectiveCamera2 extends PerspectiveCamera implements ICamera { | |||
| // endregion | |||
| } | |||
| /** | |||
| * Empty class with the constructor same as PerspectiveCamera in three.js. | |||
| * This can be used to remain compatible with three.js construct signature. | |||
| */ | |||
| export class PerspectiveCamera0 extends PerspectiveCamera2 { | |||
| constructor(fov?: number, aspect?: number, near?: number, far?: number) { | |||
| super(undefined, undefined, undefined, fov, aspect || 1) | |||
| if (near || far) { | |||
| this.autoNearFar = false | |||
| if (near) { | |||
| this.near = near | |||
| this.minNearPlane = near | |||
| } | |||
| if (far) { | |||
| this.far = far | |||
| this.maxFarPlane = far | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import {UiObjectConfig} from 'uiconfig.js' | |||
| import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | |||
| import {autoGPUInstanceMeshes, isInScene, toIndexedGeometry} from '../../three' | |||
| import {autoGPUInstanceMeshes, isInScene, toIndexedGeometry} from '../../three/utils' | |||
| import {BufferGeometry, Vector3} from 'three' | |||
| export const iGeometryCommons = { | |||
| @@ -29,7 +29,7 @@ import {downloadBlob, uploadFile} from 'ts-browser-helpers' | |||
| import {PhysicalMaterial} from './PhysicalMaterial' | |||
| import {getEmptyMeta} from '../../utils' | |||
| import {LegacyPhongMaterial} from './LegacyPhongMaterial' | |||
| import {generateUUID} from '../../three' | |||
| import {generateUUID} from '../../three/utils' | |||
| declare module '../IMaterial' { | |||
| interface IMaterial { | |||
| @@ -207,4 +207,5 @@ export class LineMaterial2 extends LineMaterial<IMaterialEvent, LineMaterial2Eve | |||
| return new LineMaterial2(params) | |||
| }, | |||
| } | |||
| } | |||
| @@ -19,7 +19,7 @@ import {copyMaterialUserData} from '../../utils/serialization' | |||
| import {MaterialExtender, MaterialExtension} from '../../materials' | |||
| import {IScene} from '../IScene' | |||
| import {IMaterial, IMaterialEvent, IMaterialSetDirtyOptions} from '../IMaterial' | |||
| import {isInScene} from '../../three' | |||
| import {isInScene} from '../../three/utils' | |||
| /** | |||
| * Map of all material properties and their default values in three.js - Material.js | |||
| @@ -209,25 +209,7 @@ export function makeIObject3DUiConfig(this: IObject3D, isMesh?:boolean): UiObjec | |||
| // todo: lights? | |||
| // todo: issue when selected object is moved to picking from SceneUI | |||
| // (config.children as UiObjectConfig[]).push(makeHierarchyUi(object)) | |||
| this.uiConfig = config | |||
| return config | |||
| } | |||
| // function makeHierarchyUi(object: Object3D, root?: Object3D): UiObjectConfig { | |||
| // const dispatch = ()=>(root || object).dispatchEvent({type: 'select', ui: true, value: object}) | |||
| // if (object.children.length === 0) return { | |||
| // type: 'button', | |||
| // label: 'Select ' + (object.name || 'unnamed'), | |||
| // value: dispatch, | |||
| // } | |||
| // return { | |||
| // type: 'folder', | |||
| // label: 'Select ' + (object.name || 'unnamed'), | |||
| // children: object.children.map((child)=>makeHierarchyUi(child, root || object)), | |||
| // value: dispatch, | |||
| // onExpand: dispatch, | |||
| // } | |||
| // } | |||
| @@ -19,12 +19,12 @@ declare module '*.module.scss' { | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.module.css' { | |||
| const content: any | |||
| export default content | |||
| export const stylesheet: string | |||
| } | |||
| declare module '*.css' { | |||
| // declare module '*.module.css' { | |||
| // const content: any | |||
| // export default content | |||
| // export const stylesheet: string | |||
| // } | |||
| declare module '*.css?inline' { | |||
| const content: string | |||
| export default content | |||
| } | |||
| @@ -3,7 +3,7 @@ import {getOrCall, objectMap} from 'ts-browser-helpers' | |||
| import {shaderReplaceString, shaderUtils} from '../utils' | |||
| import {Object3D, Shader, ShaderChunk, WebGLRenderer} from 'three' | |||
| import {MaterialExtension} from './MaterialExtension' | |||
| import {generateUUID} from '../three' | |||
| import {generateUUID} from '../three/utils' | |||
| export class MaterialExtender { | |||
| @@ -59,10 +59,20 @@ export function textureToCanvas(texture: Texture|DataTexture, maxWidth: number, | |||
| } | |||
| export function texImageToCanvas(image: TexImageSource, maxWidth: number, flipY = false) { | |||
| let width, height | |||
| if (!window.VideoFrame) window.VideoFrame = HTMLVideoElement as any | |||
| if (image instanceof window.VideoFrame) { | |||
| width = image.displayWidth | |||
| height = image.displayHeight | |||
| } else { | |||
| width = image.width | |||
| height = image.height | |||
| } | |||
| if (window.VideoFrame as any === HTMLVideoElement) delete (window as any).VideoFrame | |||
| const canvas = document.createElement('canvas') | |||
| // resize it to the size of our image | |||
| 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.width = Math.min(maxWidth, width) | |||
| canvas.height = Math.floor(1.0 + canvas.width * height / width) | |||
| const ctx = canvas.getContext('2d') | |||
| if (!ctx) { | |||
| @@ -81,10 +91,10 @@ export function texImageToCanvas(image: TexImageSource, maxWidth: number, flipY | |||
| if ((image as ImageData).data !== undefined) { // THREE.DataTexture | |||
| const imageData = image as ImageData | |||
| if (image.width !== canvas.width || image.height !== canvas.height) { | |||
| if (width !== canvas.width || height !== canvas.height) { | |||
| const tempCanvas = document.createElement('canvas') | |||
| tempCanvas.width = image.width | |||
| tempCanvas.height = image.height | |||
| tempCanvas.width = width | |||
| tempCanvas.height = height | |||
| const tempCtx = tempCanvas.getContext('2d') | |||
| if (!tempCtx) { | |||
| console.error('textureToDataUrl: could not get temp canvas context') | |||
| @@ -1,4 +1,4 @@ | |||
| import styles from './CustomContextMenu.css' | |||
| import styles from './CustomContextMenu.css?inline' | |||
| /** | |||
| * Represents a custom context menu that can be created and managed dynamically. | |||
| @@ -1,5 +1,5 @@ | |||
| import {Quaternion, Spherical, Vector3} from 'three' | |||
| import {worldToLocalQuaternion} from '../three' | |||
| import {worldToLocalQuaternion} from '../three/utils' | |||
| import {CameraView, ICamera, ICameraView} from '../core' | |||
| import {AnimationOptions} from 'popmotion' | |||
| import {lerp, lerpAngle} from './animation' | |||
| @@ -325,6 +325,7 @@ export class ThreeViewer extends EventDispatcher<IViewerEvent, IViewerEventTypes | |||
| const camera = new PerspectiveCamera2('orbit', this._canvas) | |||
| camera.name = 'Default Camera' | |||
| camera.position.set(0, 0, 5) | |||
| camera.target.set(0, 0, 0) | |||
| camera.userData.autoLookAtTarget = true // only for when controls are disabled / not available | |||
| // Update camera controls postFrame if allowed to interact | |||
| @@ -1 +1 @@ | |||
| export const VERSION = '0.0.19' | |||
| export const VERSION = '0.0.20-dev.1' | |||