diff --git a/README.md b/README.md index 523a63c..19dba9a 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ I wanted to code something similar to *react-three-fiber* but for VueJS. I started from scratch, I will rewrite some of my [WebGL demos](https://codepen.io/collection/AGZywR) to see if this little toy can do the job. +Next version (*v0.3*, rewrited with Typescript) will be soon released ! + *Trois* is a french word, it means *Three*. ## Usage (CDN) @@ -80,6 +82,8 @@ Thanks to VueJS/ViteJS, **TroisJS use watchers and HMR to update ThreeJS objects ## Features +Take a look at examples : https://troisjs.github.io/ + - [ ] Lights - [x] AmbientLight - [x] DirectionalLight @@ -93,6 +97,7 @@ Thanks to VueJS/ViteJS, **TroisJS use watchers and HMR to update ThreeJS objects - [x] Mapcap - [x] Phong - [x] Physical + - [x] Shader - [x] Standard - [x] SubSurface - [x] Toon diff --git a/src/core/Object3D.ts b/src/core/Object3D.ts index 0863c8f..8b636a5 100644 --- a/src/core/Object3D.ts +++ b/src/core/Object3D.ts @@ -1,6 +1,6 @@ import { Object3D, Scene } from 'three' import { ComponentPublicInstance, defineComponent, PropType, watch } from 'vue' -import { bindOptions, bindProp } from '../tools' +import { bindObjectProp, bindProp } from '../tools' import { RendererInjectionKey, RendererInterface } from './Renderer' import { SceneInjectionKey } from './Scene' @@ -83,7 +83,7 @@ export default defineComponent({ bindProp(this, 'userData', o3d.userData) bindProp(this, 'visible', o3d) - bindOptions(o3d, this.props) + bindObjectProp(this, 'props', o3d) this.$emit('created', o3d) diff --git a/src/core/Raycaster.ts b/src/core/Raycaster.ts index f9d9efb..89a987d 100644 --- a/src/core/Raycaster.ts +++ b/src/core/Raycaster.ts @@ -20,6 +20,7 @@ export default defineComponent({ onPointerLeave: { type: Function as PropType, default: emptyCallBack }, onClick: { type: Function as PropType, default: emptyCallBack }, intersectMode: { type: String, default: 'move' }, + intersectRecursive: { type: Boolean, default: false }, }, setup(): RaycasterSetupInterface { const renderer = inject(RendererInjectionKey) @@ -39,6 +40,7 @@ export default defineComponent({ camera: renderer.camera, domElement: renderer.canvas, intersectObjects: this.getIntersectObjects(), + intersectRecursive: this.intersectRecursive, onIntersectEnter: this.onPointerEnter, onIntersectOver: this.onPointerOver, onIntersectMove: this.onPointerMove, diff --git a/src/core/Renderer.ts b/src/core/Renderer.ts index e4f38df..a97fda5 100644 --- a/src/core/Renderer.ts +++ b/src/core/Renderer.ts @@ -1,8 +1,8 @@ /* eslint-disable no-use-before-define */ -import { Camera, NoToneMapping, PCFShadowMap, Scene, WebGLRenderer } from 'three' +import { Camera, Scene, WebGLRenderer } from 'three' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer' import { ComponentPublicInstance, defineComponent, InjectionKey, PropType } from 'vue' -import { bindOptions } from '../tools' +import { bindObjectProp } from '../tools' import { PointerPublicConfigInterface } from './usePointer' import useThree, { SizeInterface, ThreeConfigInterface, ThreeInterface } from './useThree' @@ -136,7 +136,7 @@ export default defineComponent({ if (props.height) config.height = parseInt(props.height) const three = useThree(config) - bindOptions(three.renderer, props.props) + bindObjectProp(props, 'props', three.renderer) const renderFn: {(): void} = () => {} @@ -196,7 +196,6 @@ export default defineComponent({ if (this.shadow) { this.renderer.shadowMap.enabled = true - this.renderer.shadowMap.type = this.shadowType } this.renderFn = this.three.composer ? this.three.renderC : this.three.render diff --git a/src/core/usePointer.ts b/src/core/usePointer.ts index f4d1de5..1938cdf 100644 --- a/src/core/usePointer.ts +++ b/src/core/usePointer.ts @@ -22,6 +22,7 @@ export type IntersectObject = Mesh | InstancedMesh export interface PointerPublicConfigInterface { intersectMode?: 'frame' + intersectRecursive?: boolean touch?: boolean resetOnEnd?: boolean resetPosition?: Vector2 @@ -59,6 +60,7 @@ export default function usePointer(options: PointerConfigInterface): PointerInte camera, domElement, intersectObjects, + intersectRecursive = false, touch = true, resetOnEnd = false, resetPosition = new Vector2(0, 0), @@ -119,7 +121,7 @@ export default function usePointer(options: PointerConfigInterface): PointerInte function intersect() { if (intersectObjects.length) { - const intersects = raycaster.intersect(positionN, intersectObjects) + const intersects = raycaster.intersect(positionN, intersectObjects, intersectRecursive) const offObjects: IntersectObject[] = [...intersectObjects] const iMeshes: InstancedMesh[] = [] diff --git a/src/core/useRaycaster.ts b/src/core/useRaycaster.ts index d28e862..2ebf662 100644 --- a/src/core/useRaycaster.ts +++ b/src/core/useRaycaster.ts @@ -4,7 +4,7 @@ import { IntersectObject } from './usePointer' export interface RaycasterInterface { position: Vector3 updatePosition(coords: Vector2): void - intersect(coords: Vector2, objects: IntersectObject[]): Intersection[], + intersect(coords: Vector2, objects: IntersectObject[], recursive: boolean): Intersection[], } export interface RaycasterConfigInterface { @@ -28,9 +28,9 @@ export default function useRaycaster(options: RaycasterConfigInterface): Raycast raycaster.ray.intersectPlane(plane, position) } - const intersect = (coords: Vector2, objects: IntersectObject[]) => { + const intersect = (coords: Vector2, objects: IntersectObject[], recursive = false) => { raycaster.setFromCamera(coords, camera) - return raycaster.intersectObjects(objects) + return raycaster.intersectObjects(objects, recursive) } return { diff --git a/src/materials/BasicMaterial.ts b/src/materials/BasicMaterial.ts deleted file mode 100644 index 83a66d5..0000000 --- a/src/materials/BasicMaterial.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineComponent } from 'vue' -import { MeshBasicMaterial } from 'three' -import { bindProps, propsValues } from '../tools' -import Material, { wireframeProps } from './Material' - -export default defineComponent({ - extends: Material, - props: { - ...wireframeProps, - }, - methods: { - createMaterial() { - const material = new MeshBasicMaterial(propsValues(this.$props)) - bindProps(this, Object.keys(wireframeProps), material) - return material - }, - }, - __hmrId: 'BasicMaterial', -}) diff --git a/src/materials/LambertMaterial.ts b/src/materials/LambertMaterial.ts deleted file mode 100644 index 084d212..0000000 --- a/src/materials/LambertMaterial.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineComponent } from 'vue' -import { MeshLambertMaterial } from 'three' -import { bindProps, propsValues } from '../tools' -import Material, { wireframeProps } from './Material' - -export default defineComponent({ - extends: Material, - props: { - ...wireframeProps, - }, - methods: { - createMaterial() { - const material = new MeshLambertMaterial(propsValues(this.$props)) - bindProps(this, Object.keys(wireframeProps), material) - return material - }, - }, - __hmrId: 'LambertMaterial', -}) diff --git a/src/materials/MatcapMaterial.ts b/src/materials/MatcapMaterial.ts index 4a12d39..17e2e0e 100644 --- a/src/materials/MatcapMaterial.ts +++ b/src/materials/MatcapMaterial.ts @@ -1,22 +1,17 @@ -import { defineComponent } from 'vue' import { MeshMatcapMaterial, TextureLoader } from 'three' import { propsValues, getMatcapUrl } from '../tools' -import Material from './Material' +import { materialComponent } from './Material' -export default defineComponent({ - extends: Material, - props: { +export default materialComponent( + 'MatcapMaterial', + { src: String, name: { type: String, default: '0404E8_0404B5_0404CB_3333FC' }, - flatShading: Boolean, }, - methods: { - createMaterial() { - const src = this.src ? this.src : getMatcapUrl(this.name) - const opts = propsValues(this.$props, ['src', 'name']) - opts.matcap = new TextureLoader().load(src) - return new MeshMatcapMaterial(opts) - }, - }, - __hmrId: 'MatcapMaterial', -}) + (opts) => { + const src = opts.src ? opts.src : getMatcapUrl(opts.name) + const props = propsValues(opts, ['src', 'name']) + props.matcap = new TextureLoader().load(src) + return new MeshMatcapMaterial(props) + } +) diff --git a/src/materials/Material.ts b/src/materials/Material.ts index e01847a..f2270f3 100644 --- a/src/materials/Material.ts +++ b/src/materials/Material.ts @@ -1,7 +1,7 @@ -import { ComponentPublicInstance, defineComponent, InjectionKey, PropType, watch } from 'vue' -import { FrontSide, Material, NormalBlending, Texture } from 'three' +import { ComponentPropsOptions, ComponentPublicInstance, defineComponent, InjectionKey, watch } from 'vue' +import { Color, Material, MeshBasicMaterial, MeshLambertMaterial, MeshPhongMaterial, MeshPhysicalMaterial, MeshStandardMaterial, MeshToonMaterial, PointsMaterial as TPointsMaterial, Texture } from 'three' import { MeshInjectionKey, MeshInterface } from '../meshes/Mesh' -import { bindOptions } from '../tools' +import { bindObjectProp } from '../tools' export interface MaterialSetupInterface { mesh?: MeshInterface @@ -10,7 +10,6 @@ export interface MaterialSetupInterface { } export interface MaterialInterface extends MaterialSetupInterface { - setProp(key: string, value: unknown, needsUpdate: boolean): void setTexture(texture: Texture | null, key: string): void } @@ -18,24 +17,14 @@ export interface MaterialPublicInterface extends ComponentPublicInstance, Materi export const MaterialInjectionKey: InjectionKey = Symbol('Material') -export default defineComponent({ - // inject for sub components +const BaseMaterial = defineComponent({ + props: { + color: { type: String, default: '#ffffff' }, + props: { type: Object, default: () => ({}) }, + }, inject: { mesh: MeshInjectionKey as symbol, }, - props: { - color: { type: [String, Number] as PropType, default: '#ffffff' }, - blending: { type: Number, default: NormalBlending }, - alphaTest: { type: Number, default: 0 }, - depthTest: { type: Boolean, default: true }, - depthWrite: { type: Boolean, default: true }, - fog: { type: Boolean, default: true }, - opacity: { type: Number, default: 1 }, - side: { type: Number, default: FrontSide }, - transparent: Boolean, - vertexColors: Boolean, - props: { type: Object, default: () => ({}) }, - }, setup(): MaterialSetupInterface { return {} }, @@ -51,39 +40,28 @@ export default defineComponent({ } if (this.createMaterial) { - this.material = this.createMaterial() - bindOptions(this.material, this.props) - this.mesh.setMaterial(this.material) - this.addWatchers() + const material = this.material = this.createMaterial() + // @ts-ignore + watch(() => this.color, (value) => { material.color.set(value) }) + bindObjectProp(this, 'props', material, false, this.setProp) + this.mesh.setMaterial(material) } }, unmounted() { this.material?.dispose() }, methods: { - setProp(key: string, value: any, needsUpdate = false) { - if (this.material) { - // @ts-ignore - this.material[key] = value - this.material.needsUpdate = needsUpdate - } + getMaterialParams() { + return { color: this.color, ...this.props } + }, + setProp(material: any, key: string, value: any, needsUpdate = false) { + const dstVal = material[key] + if (dstVal instanceof Color) dstVal.set(value) + else material[key] = value + material.needsUpdate = needsUpdate }, setTexture(texture: Texture | null, key = 'map') { - this.setProp(key, texture, true) - }, - addWatchers() { - ['color', 'alphaTest', 'blending', 'depthTest', 'depthWrite', 'fog', 'opacity', 'side', 'transparent'].forEach(p => { - // @ts-ignore - watch(() => this[p], (value) => { - if (p === 'color') { - // @ts-ignore - this.material.color.set(value) - } else { - // @ts-ignore - this.material[p] = value - } - }) - }) + this.setProp(this, key, texture, true) }, }, render() { @@ -92,10 +70,30 @@ export default defineComponent({ __hmrId: 'Material', }) -export const wireframeProps = { - wireframe: { type: Boolean, default: false }, - // not needed for WebGL - // wireframeLinecap: { type: String, default: 'round' }, - // wireframeLinejoin: { type: String, default: 'round' }, - wireframeLinewidth: { type: Number, default: 1 }, // not really useful +export default BaseMaterial + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export function materialComponent

>( + name: string, + props: P, + createMaterial: {(opts: any): Material} +) { + return defineComponent({ + name, + extends: BaseMaterial, + props, + methods: { + createMaterial() { + return createMaterial(this.getMaterialParams()) + }, + }, + }) } + +export const BasicMaterial = materialComponent('BasicMaterial', {}, (opts) => new MeshBasicMaterial(opts)) +export const LambertMaterial = materialComponent('LambertMaterial', {}, (opts) => new MeshLambertMaterial(opts)) +export const PhongMaterial = materialComponent('PhongMaterial', {}, (opts) => new MeshPhongMaterial(opts)) +export const PhysicalMaterial = materialComponent('PhysicalMaterial', {}, (opts) => new MeshPhysicalMaterial(opts)) +export const PointsMaterial = materialComponent('PointsMaterial', {}, (opts) => new TPointsMaterial(opts)) +export const StandardMaterial = materialComponent('StandardMaterial', {}, (opts) => new MeshStandardMaterial(opts)) +export const ToonMaterial = materialComponent('ToonMaterial', {}, (opts) => new MeshToonMaterial(opts)) diff --git a/src/materials/PhongMaterial.ts b/src/materials/PhongMaterial.ts deleted file mode 100644 index 51b7a4d..0000000 --- a/src/materials/PhongMaterial.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { defineComponent, watch } from 'vue' -import { MeshPhongMaterial } from 'three' -import { bindProps, propsValues } from '../tools' -import Material, { wireframeProps } from './Material' - -export default defineComponent({ - extends: Material, - props: { - emissive: { type: [Number, String], default: 0 }, - emissiveIntensity: { type: Number, default: 1 }, - reflectivity: { type: Number, default: 1 }, - shininess: { type: Number, default: 30 }, - specular: { type: [String, Number], default: 0x111111 }, - flatShading: Boolean, - ...wireframeProps, - }, - methods: { - createMaterial() { - const material = new MeshPhongMaterial(propsValues(this.$props)) - - // TODO : handle flatShading ? - const watchProps = ['emissive', 'emissiveIntensity', 'reflectivity', 'shininess', 'specular'] - watchProps.forEach(p => { - // @ts-ignore - watch(() => this[p], (value) => { - if (p === 'emissive' || p === 'specular') { - material[p].set(value) - } else { - // @ts-ignore - material[p] = value - } - }) - }) - bindProps(this, Object.keys(wireframeProps), material) - - return material - }, - }, - __hmrId: 'PhongMaterial', -}) diff --git a/src/materials/PhysicalMaterial.ts b/src/materials/PhysicalMaterial.ts deleted file mode 100644 index 8f6681f..0000000 --- a/src/materials/PhysicalMaterial.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineComponent } from 'vue' -import { MeshPhysicalMaterial } from 'three' -import { propsValues } from '../tools' -import StandardMaterial from './StandardMaterial' - -export default defineComponent({ - extends: StandardMaterial, - props: { - flatShading: Boolean, - }, - methods: { - createMaterial() { - return new MeshPhysicalMaterial(propsValues(this.$props)) - }, - }, - __hmrId: 'PhysicalMaterial', -}) diff --git a/src/materials/PointsMaterial.ts b/src/materials/PointsMaterial.ts deleted file mode 100644 index eaaa38a..0000000 --- a/src/materials/PointsMaterial.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineComponent } from 'vue' -import { PointsMaterial } from 'three' -import { propsValues } from '../tools' -import Material from './Material' - -export default defineComponent({ - extends: Material, - props: { - size: { type: Number, default: 10 }, - sizeAttenuation: { type: Boolean, default: true }, - }, - methods: { - createMaterial() { - const material = new PointsMaterial(propsValues(this.$props)) - return material - }, - }, - __hmrId: 'PointsMaterial', -}) diff --git a/src/materials/ShaderMaterial.ts b/src/materials/ShaderMaterial.ts index 88a3f4f..e707a4f 100644 --- a/src/materials/ShaderMaterial.ts +++ b/src/materials/ShaderMaterial.ts @@ -1,6 +1,5 @@ -import { defineComponent, watch } from 'vue' import { ShaderMaterial } from 'three' -import Material from './Material' +import { materialComponent } from './Material' import { propsValues } from '../tools' const defaultVertexShader = ` @@ -18,24 +17,14 @@ const defaultFragmentShader = ` } ` -export default defineComponent({ - extends: Material, - props: { - uniforms: { type: Object, default: () => ({}) }, - vertexShader: { type: String, default: defaultVertexShader }, - fragmentShader: { type: String, default: defaultFragmentShader }, +export default materialComponent( + 'ShaderMaterial', + { + props: { type: Object, default: () => ({ + uniforms: {}, + vertexShader: defaultVertexShader, + fragmentShader: defaultFragmentShader, + }) }, }, - methods: { - createMaterial() { - const material = new ShaderMaterial(propsValues(this.$props, ['color'])); - - ['vertexShader', 'fragmentShader'].forEach(p => { - // @ts-ignore - watch(() => this[p], (value) => { material[p] = value; material.needsUpdate = true }) - }) - - return material - }, - }, - __hmrId: 'ShaderMaterial', -}) + (opts) => new ShaderMaterial(propsValues(opts, ['color'])) +) diff --git a/src/materials/StandardMaterial.ts b/src/materials/StandardMaterial.ts deleted file mode 100644 index 5b33cfe..0000000 --- a/src/materials/StandardMaterial.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { defineComponent, PropType, watch } from 'vue' -import { MeshStandardMaterial } from 'three' -import { bindProp, bindProps, propsValues } from '../tools' -import Material, { wireframeProps } from './Material' -import { Vector2PropInterface } from '../core/Object3D' - -const props = { - aoMapIntensity: { type: Number, default: 1 }, - bumpScale: { type: Number, default: 1 }, - displacementBias: { type: Number, default: 0 }, - displacementScale: { type: Number, default: 1 }, - emissive: { type: [String, Number] as PropType, default: 0 }, - emissiveIntensity: { type: Number, default: 1 }, - envMapIntensity: { type: Number, default: 1 }, - lightMapIntensity: { type: Number, default: 1 }, - metalness: { type: Number, default: 0 }, - normalScale: { type: Object as PropType, default: () => ({ x: 1, y: 1 }) }, - roughness: { type: Number, default: 1 }, - refractionRatio: { type: Number, default: 0.98 }, - flatShading: Boolean, -} as const - -export default defineComponent({ - extends: Material, - props: { - ...props, - ...wireframeProps, - }, - methods: { - createMaterial() { - const material = new MeshStandardMaterial(propsValues(this.$props, ['normalScale'])) - - // TODO : use setProp, handle flatShading ? - Object.keys(props).forEach(p => { - if (p === 'normalScale') return - // @ts-ignore - watch(() => this[p], (value) => { - if (p === 'emissive') { - material[p].set(value) - } else { - // @ts-ignore - material[p] = value - } - }) - }) - - bindProp(this, 'normalScale', material) - bindProps(this, Object.keys(wireframeProps), material) - - return material - }, - }, - __hmrId: 'StandardMaterial', -}) diff --git a/src/materials/SubSurfaceMaterial.ts b/src/materials/SubSurfaceMaterial.ts index 7ae05bc..f4bedf3 100644 --- a/src/materials/SubSurfaceMaterial.ts +++ b/src/materials/SubSurfaceMaterial.ts @@ -2,47 +2,50 @@ import { defineComponent, PropType } from 'vue' import { Color, ShaderMaterial, UniformsUtils } from 'three' import SubsurfaceScatteringShader from './SubsurfaceScatteringShader' import Material from './Material' -// import { bindProps, propsValues } from '../tools' +import { bindObjectProp } from '../tools' -const props = { - color: { type: [String, Number] as PropType, default: '#ffffff' }, - thicknessColor: { type: [String, Number] as PropType, default: '#ffffff' }, - thicknessDistortion: { type: Number, default: 0.4 }, - thicknessAmbient: { type: Number, default: 0.01 }, - thicknessAttenuation: { type: Number, default: 0.7 }, - thicknessPower: { type: Number, default: 2 }, - thicknessScale: { type: Number, default: 4 }, -} as const +export interface SubSurfaceMaterialUniformsInterface { + diffuse?: string | number + thicknessColor?: string | number + thicknessDistortion?: number + thicknessAmbient?: number + thicknessAttenuation?: number + thicknessPower?: number + thicknessScale?: number +} export default defineComponent({ extends: Material, - props, + props: { + uniforms: { type: Object as PropType, default: () => ({ + diffuse: '#ffffff', + thicknessColor: '#ffffff', + thicknessDistortion: 0.4, + thicknessAmbient: 0.01, + thicknessAttenuation: 0.7, + thicknessPower: 2, + thicknessScale: 4, + }) }, + }, methods: { createMaterial() { const params = SubsurfaceScatteringShader const uniforms = UniformsUtils.clone(params.uniforms) - Object.keys(props).forEach((key) => { - // @ts-ignore - const value = this[key] - let _key = key, _value = value - if (['color', 'thicknessColor'].includes(key)) { - if (key === 'color') _key = 'diffuse' - _value = new Color(value) - } - uniforms[_key].value = _value + bindObjectProp(this, 'uniforms', uniforms, true, (dst: any, key: string, value: any) => { + const dstVal = dst[key].value + if (dstVal instanceof Color) dstVal.set(value) + else dst[key].value = value }) const material = new ShaderMaterial({ ...params, - uniforms, lights: true, - transparent: this.transparent, - vertexColors: this.vertexColors, + ...this.props, + uniforms, }) return material }, }, - __hmrId: 'SubSurfaceMaterial', }) diff --git a/src/materials/Texture.ts b/src/materials/Texture.ts index a73a8d7..c0036b8 100644 --- a/src/materials/Texture.ts +++ b/src/materials/Texture.ts @@ -1,6 +1,6 @@ import { defineComponent, PropType, watch } from 'vue' import { ShaderMaterial, Texture, TextureLoader } from 'three' -import { bindOptions } from '../tools' +import { bindObjectProp } from '../tools' import { MaterialInjectionKey, MaterialInterface } from './Material' export interface TexureInterface { @@ -40,7 +40,7 @@ export default defineComponent({ initTexture() { if (!this.texture) return - bindOptions(this.texture, this.props) + bindObjectProp(this, 'props', this.texture) if (!this.material) return this.material.setTexture(this.texture, this.name) diff --git a/src/materials/ToonMaterial.ts b/src/materials/ToonMaterial.ts deleted file mode 100644 index 25c4078..0000000 --- a/src/materials/ToonMaterial.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { defineComponent } from 'vue' -import { MeshToonMaterial } from 'three' -import { bindProps, propsValues } from '../tools' -import Material, { wireframeProps } from './Material' - -export default defineComponent({ - extends: Material, - props: { - ...wireframeProps, - }, - methods: { - createMaterial() { - const material = new MeshToonMaterial(propsValues(this.$props)) - bindProps(this, Object.keys(wireframeProps), material) - return material - }, - }, - __hmrId: 'ToonMaterial', -}) diff --git a/src/materials/index.ts b/src/materials/index.ts index dca9f74..c2403bd 100644 --- a/src/materials/index.ts +++ b/src/materials/index.ts @@ -1,17 +1,9 @@ -export { default as Material, MaterialInjectionKey } from './Material' -export { default as BasicMaterial } from './BasicMaterial' -export { default as LambertMaterial } from './LambertMaterial' +export { default as Material, BasicMaterial, LambertMaterial, PhongMaterial, PhysicalMaterial, PointsMaterial, StandardMaterial, ToonMaterial, MaterialInjectionKey } from './Material' export { default as MatcapMaterial } from './MatcapMaterial' -export { default as PhongMaterial } from './PhongMaterial' -export { default as PhysicalMaterial } from './PhysicalMaterial' export { default as ShaderMaterial } from './ShaderMaterial' -export { default as StandardMaterial } from './StandardMaterial' export { default as SubSurfaceMaterial } from './SubSurfaceMaterial' -export { default as ToonMaterial } from './ToonMaterial' export { default as Texture } from './Texture' export { default as CubeTexture } from './CubeTexture' -export { default as PointsMaterial } from './PointsMaterial' - export type { MaterialPublicInterface } from './Material' diff --git a/src/tools.ts b/src/tools.ts index d8f1d72..15e9aa1 100644 --- a/src/tools.ts +++ b/src/tools.ts @@ -1,16 +1,30 @@ -import { toRef, watch } from 'vue' +import { toRef, watch, WatchStopHandle } from 'vue' -export function applyOptions(dst: any, options: Record): void { +type OptionSetter = (dst: any, key: string, value: any) => void + +export function applyObjectProps( + dst: any, + options: Record, + setter?: OptionSetter +): void { if (options instanceof Object) { Object.entries(options).forEach(([key, value]) => { - dst[key] = value + if (setter) setter(dst, key, value) + else dst[key] = value }) } } -export function bindOptions(dst: any, options: Record): void { - applyOptions(dst, options) - watch(() => options, (value) => { applyOptions(dst, value) }, { deep: true }) +export function bindObjectProp( + src: any, + prop: string, + dst: any, + apply = true, + setter?: OptionSetter +): WatchStopHandle { + if (apply) applyObjectProps(dst, src[prop], setter) + const r = toRef(src, prop) + return watch(r, (value) => { applyObjectProps(dst, value, setter) }) } export function setFromProp(o: Record, prop: Record): void {