| import resolve from '@rollup/plugin-node-resolve'; | import resolve from '@rollup/plugin-node-resolve'; | ||||
| import typescript from '@rollup/plugin-typescript'; | import typescript from '@rollup/plugin-typescript'; | ||||
| import packageJson from './package.json' assert {type: 'json'}; | 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 postcss from 'rollup-plugin-postcss' | ||||
| import glsl from "rollup-plugin-glsl" | import glsl from "rollup-plugin-glsl" | ||||
| import replace from "@rollup/plugin-replace"; | import replace from "@rollup/plugin-replace"; | ||||
| import terser from "@rollup/plugin-terser"; | |||||
| import commonjs from "@rollup/plugin-commonjs"; | import commonjs from "@rollup/plugin-commonjs"; | ||||
| import license from "rollup-plugin-license"; | import license from "rollup-plugin-license"; | ||||
| import terser from "@rollup/plugin-terser"; | |||||
| const __filename = fileURLToPath(import.meta.url); | const __filename = fileURLToPath(import.meta.url); | ||||
| const __dirname = path.dirname(__filename); | const __dirname = path.dirname(__filename); | ||||
| plugins: [ | plugins: [ | ||||
| replace({ | replace({ | ||||
| 'process.env.NODE_ENV': JSON.stringify( 'production' ), | 'process.env.NODE_ENV': JSON.stringify( 'production' ), | ||||
| '.css?inline': '.css', | |||||
| preventAssignment: true, | |||||
| }), | }), | ||||
| // replace({ | // replace({ | ||||
| // exclude: 'src/**', | // exclude: 'src/**', | ||||
| include: "src/**/*.glsl" | include: "src/**/*.glsl" | ||||
| }), | }), | ||||
| postcss({ | postcss({ | ||||
| extensions: ['.css', '.css?inline'], | |||||
| modules: false, | modules: false, | ||||
| autoModules: true, // todo; issues with typescript import css, because inject is false | autoModules: true, // todo; issues with typescript import css, because inject is false | ||||
| inject: false, | inject: false, |
| // return object | // return object | ||||
| // } | // } | ||||
| // protected abstract _processModel(object: any, options: AnyOptions): any | // 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) return | ||||
| if (material.assetType) return <IMaterial>material | if (material.assetType) return <IMaterial>material | ||||
| if (material.iMaterial?.assetType) return material.iMaterial | if (material.iMaterial?.assetType) return material.iMaterial | ||||
| const uuid = material.userData?.uuid || material.uuid | const uuid = material.userData?.uuid || material.uuid | ||||
| let mat = this.findMaterial(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) | mat = this.create(template, ignoreSource ? undefined : material) | ||||
| } else { | |||||
| } else if (mat) { | |||||
| // if ((mat as any).iMaterial) mat = (mat as any).iMaterial | // if ((mat as any).iMaterial) mat = (mat as any).iMaterial | ||||
| console.warn('Material with the same uuid already exists, copying properties') | 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) | if (material.type !== mat!.type) console.error('Material type mismatch, delete previous material first?', material, mat) | ||||
| mat.userData.uuid = uuid | mat.userData.uuid = uuid | ||||
| material.iMaterial = mat | material.iMaterial = mat | ||||
| } else { | } 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) | mat = iMaterialCommons.upgradeMaterial.call(material) | ||||
| } | } | ||||
| return mat | return mat |
| } | } | ||||
| declare loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any> | |||||
| loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise<any> | |||||
| setMaterials( materials ) { | setMaterials( materials ) { | ||||
| autoScaleRadius?: number | autoScaleRadius?: number | ||||
| autoScaled?: boolean | autoScaled?: boolean | ||||
| geometriesCentered?: boolean | geometriesCentered?: boolean | ||||
| /** | /** | ||||
| * @param isCentered - optional (taken from userData.isCentered by default) | * @param isCentered - optional (taken from userData.isCentered by default) | ||||
| * @param setDirty - true 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 | * @param setDirty - calls {@link setDirty} @default true | ||||
| */ | */ | ||||
| autoCenter?<T extends IObject3D>(setDirty?: boolean): T | |||||
| autoCenter?(setDirty?: boolean): this | |||||
| /** | /** | ||||
| * @deprecated use object directly | * @deprecated use object directly | ||||
| // __disposed?: boolean | // __disposed?: boolean | ||||
| /** | /** | ||||
| * | |||||
| * @param removeFromParent - remove from parent. Default true | * @param removeFromParent - remove from parent. Default true | ||||
| */ | */ | ||||
| dispose(removeFromParent?: boolean): void; | dispose(removeFromParent?: boolean): void; |
| clock: Clock | clock: Clock | ||||
| blit(destination: IRenderTarget|undefined|null, options?: RendererBlitOptions): void | 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 | {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 | renderTargetToDataUrl(target: WebGLRenderTarget, mimeType?: string, quality?: number): string |
| attach(object: any): this; | attach(object: any): this; | ||||
| detach(): this; | detach(): this; | ||||
| isWidget: true; | isWidget: true; | ||||
| object: any | |||||
| update?(): void | |||||
| dispose?(): void | |||||
| } | } | ||||
| export interface IScene<E extends ISceneEvent = ISceneEvent, ET extends ISceneEventTypes = ISceneEventTypes> | export interface IScene<E extends ISceneEvent = ISceneEvent, ET extends ISceneEventTypes = ISceneEventTypes> | ||||
| // region deprecated | // region deprecated | ||||
| /** | /** | ||||
| @deprecated use {@link getObjectByName} instead | |||||
| * @deprecated use {@link getObjectByName} instead | |||||
| * @param name | * @param name | ||||
| * @param parent | * @param parent | ||||
| */ | */ |
| import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' | import type {ICamera, ICameraEvent, ICameraUserData, TCameraControlsMode} from '../ICamera' | ||||
| import {ICameraSetDirtyOptions} from '../ICamera' | import {ICameraSetDirtyOptions} from '../ICamera' | ||||
| import type {ICameraControls, TControlsCtor} from './ICameraControls' | import type {ICameraControls, TControlsCtor} from './ICameraControls' | ||||
| import {OrbitControls3} from '../../three' | |||||
| import {OrbitControls3} from '../../three/controls/OrbitControls3' | |||||
| import {IObject3D} from '../IObject' | import {IObject3D} from '../IObject' | ||||
| import {ThreeSerialization} from '../../utils' | import {ThreeSerialization} from '../../utils' | ||||
| import {iCameraCommons} from '../object/iCameraCommons' | import {iCameraCommons} from '../object/iCameraCommons' | ||||
| @serialize() readonly position: Vector3 | @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: 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()})) | @uiVector('Target', undefined, undefined, (that:PerspectiveCamera2)=>({onChange: ()=>that.setDirty()})) | ||||
| @serialize() readonly target: Vector3 = new Vector3(0, 0, 0) | @serialize() readonly target: Vector3 = new Vector3(0, 0, 0) | ||||
| @onChange2(PerspectiveCamera2.prototype._nearFarChanged) | @onChange2(PerspectiveCamera2.prototype._nearFarChanged) | ||||
| far = 50 | 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. | * Automatically manage near and far clipping planes based on scene size. | ||||
| */ | */ | ||||
| /** | /** | ||||
| * Minimum near clipping plane allowed. (Distance from camera) | * Minimum near clipping plane allowed. (Distance from camera) | ||||
| * Used in RootScene when {@link autoNearFar} is true. | |||||
| * @default 0.2 | * @default 0.2 | ||||
| */ | */ | ||||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | @bindToValue({obj: 'userData', onChange: 'setDirty'}) | ||||
| minNearPlane = 0.5 | minNearPlane = 0.5 | ||||
| /** | /** | ||||
| * Maximum far clipping plane allowed. (Distance from camera) | * Maximum far clipping plane allowed. (Distance from camera) | ||||
| * Used in RootScene when {@link autoNearFar} is true. | |||||
| */ | */ | ||||
| @bindToValue({obj: 'userData', onChange: 'setDirty'}) | @bindToValue({obj: 'userData', onChange: 'setDirty'}) | ||||
| maxFarPlane = 1000 | maxFarPlane = 1000 | ||||
| private _interactionsDisabledBy = new Set<string>() | 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 { | get interactionsEnabled(): boolean { | ||||
| return this._interactionsDisabledBy.size === 0 | return this._interactionsDisabledBy.size === 0 | ||||
| } | } | ||||
| } | } | ||||
| get canUserInteract() { | get canUserInteract() { | ||||
| return this.interactionsEnabled && this.isMainCamera && this.controlsMode !== '' | |||||
| return this._interactionsDisabledBy.size === 0 && this.isMainCamera && this.controlsMode !== '' | |||||
| } | } | ||||
| // endregion | // endregion | ||||
| // todo: only for orbit control like controls? | // todo: only for orbit control like controls? | ||||
| if (this._controls) { | if (this._controls) { | ||||
| const ce = this.interactionsEnabled | |||||
| const ce = this.canUserInteract | |||||
| this._controls.enabled = ce | this._controls.enabled = ce | ||||
| if (ce) this.up.copy(Object3D.DEFAULT_UP) | if (ce) this.up.copy(Object3D.DEFAULT_UP) | ||||
| } | } | ||||
| getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | getObjectById: <T extends IObject3D = IObject3D>(id: number) => T | undefined | ||||
| getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | getObjectByName: <T extends IObject3D = IObject3D>(name: string) => T | undefined | ||||
| getObjectByProperty: <T extends IObject3D = IObject3D>(name: string, value: 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 | clone: (recursive?: boolean) => this | ||||
| add: (...object: IObject3D[]) => this | add: (...object: IObject3D[]) => this | ||||
| remove: (...object: IObject3D[]) => this | remove: (...object: IObject3D[]) => this | ||||
| // endregion | // 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 | |||||
| } | |||||
| } | |||||
| } | |||||
| } |
| import {UiObjectConfig} from 'uiconfig.js' | import {UiObjectConfig} from 'uiconfig.js' | ||||
| import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | import {IGeometry, IGeometrySetDirtyOptions} from '../IGeometry' | ||||
| import {autoGPUInstanceMeshes, isInScene, toIndexedGeometry} from '../../three' | |||||
| import {autoGPUInstanceMeshes, isInScene, toIndexedGeometry} from '../../three/utils' | |||||
| import {BufferGeometry, Vector3} from 'three' | import {BufferGeometry, Vector3} from 'three' | ||||
| export const iGeometryCommons = { | export const iGeometryCommons = { |
| import {PhysicalMaterial} from './PhysicalMaterial' | import {PhysicalMaterial} from './PhysicalMaterial' | ||||
| import {getEmptyMeta} from '../../utils' | import {getEmptyMeta} from '../../utils' | ||||
| import {LegacyPhongMaterial} from './LegacyPhongMaterial' | import {LegacyPhongMaterial} from './LegacyPhongMaterial' | ||||
| import {generateUUID} from '../../three' | |||||
| import {generateUUID} from '../../three/utils' | |||||
| declare module '../IMaterial' { | declare module '../IMaterial' { | ||||
| interface IMaterial { | interface IMaterial { |
| return new LineMaterial2(params) | return new LineMaterial2(params) | ||||
| }, | }, | ||||
| } | } | ||||
| } | } |
| import {MaterialExtender, MaterialExtension} from '../../materials' | import {MaterialExtender, MaterialExtension} from '../../materials' | ||||
| import {IScene} from '../IScene' | import {IScene} from '../IScene' | ||||
| import {IMaterial, IMaterialEvent, IMaterialSetDirtyOptions} from '../IMaterial' | 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 | * Map of all material properties and their default values in three.js - Material.js |
| // todo: lights? | // todo: lights? | ||||
| // todo: issue when selected object is moved to picking from SceneUI | |||||
| // (config.children as UiObjectConfig[]).push(makeHierarchyUi(object)) | |||||
| this.uiConfig = config | this.uiConfig = config | ||||
| return 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, | |||||
| // } | |||||
| // } |
| export default content | export default content | ||||
| export const stylesheet: string | 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 | const content: string | ||||
| export default content | export default content | ||||
| } | } |
| import {shaderReplaceString, shaderUtils} from '../utils' | import {shaderReplaceString, shaderUtils} from '../utils' | ||||
| import {Object3D, Shader, ShaderChunk, WebGLRenderer} from 'three' | import {Object3D, Shader, ShaderChunk, WebGLRenderer} from 'three' | ||||
| import {MaterialExtension} from './MaterialExtension' | import {MaterialExtension} from './MaterialExtension' | ||||
| import {generateUUID} from '../three' | |||||
| import {generateUUID} from '../three/utils' | |||||
| export class MaterialExtender { | export class MaterialExtender { | ||||
| } | } | ||||
| export function texImageToCanvas(image: TexImageSource, maxWidth: number, flipY = false) { | 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') | 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.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') | const ctx = canvas.getContext('2d') | ||||
| if (!ctx) { | if (!ctx) { | ||||
| 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 | ||||
| if (image.width !== canvas.width || image.height !== canvas.height) { | |||||
| if (width !== canvas.width || height !== canvas.height) { | |||||
| const tempCanvas = document.createElement('canvas') | const tempCanvas = document.createElement('canvas') | ||||
| tempCanvas.width = image.width | |||||
| tempCanvas.height = image.height | |||||
| tempCanvas.width = width | |||||
| tempCanvas.height = height | |||||
| const tempCtx = tempCanvas.getContext('2d') | const tempCtx = tempCanvas.getContext('2d') | ||||
| if (!tempCtx) { | if (!tempCtx) { | ||||
| console.error('textureToDataUrl: could not get temp canvas context') | console.error('textureToDataUrl: could not get temp canvas context') |
| import styles from './CustomContextMenu.css' | |||||
| import styles from './CustomContextMenu.css?inline' | |||||
| /** | /** | ||||
| * Represents a custom context menu that can be created and managed dynamically. | * Represents a custom context menu that can be created and managed dynamically. |
| import {Quaternion, Spherical, Vector3} from 'three' | import {Quaternion, Spherical, Vector3} from 'three' | ||||
| import {worldToLocalQuaternion} from '../three' | |||||
| import {worldToLocalQuaternion} from '../three/utils' | |||||
| import {CameraView, ICamera, ICameraView} from '../core' | import {CameraView, ICamera, ICameraView} from '../core' | ||||
| import {AnimationOptions} from 'popmotion' | import {AnimationOptions} from 'popmotion' | ||||
| import {lerp, lerpAngle} from './animation' | import {lerp, lerpAngle} from './animation' |
| const camera = new PerspectiveCamera2('orbit', this._canvas) | const camera = new PerspectiveCamera2('orbit', this._canvas) | ||||
| camera.name = 'Default Camera' | camera.name = 'Default Camera' | ||||
| camera.position.set(0, 0, 5) | camera.position.set(0, 0, 5) | ||||
| camera.target.set(0, 0, 0) | |||||
| camera.userData.autoLookAtTarget = true // only for when controls are disabled / not available | camera.userData.autoLookAtTarget = true // only for when controls are disabled / not available | ||||
| // Update camera controls postFrame if allowed to interact | // Update camera controls postFrame if allowed to interact |
| export const VERSION = '0.0.19' | |||||
| export const VERSION = '0.0.20-dev.1' |