@threlte/core
Version:
A 3D framework for the web, built on top of Svelte and Three.js
109 lines (108 loc) • 4.03 kB
JavaScript
import { fromStore } from 'svelte/store';
import { useThrelte } from '../../../context/compounds/useThrelte.js';
import { createParentContext, useParent } from '../../../context/fragments/parent.js';
import { createParentObject3DContext, useParentObject3D } from '../../../context/fragments/parentObject3D.js';
import { isInstanceOf, resolvePropertyPath } from '../../../utilities/index.js';
const isObject = (ref) => {
return typeof ref === 'object' && ref !== null;
};
export const useAttach = (getRef, getAttach) => {
const { invalidate } = useThrelte();
const ref = $derived(getRef());
const attach = $derived(getAttach());
const parent = fromStore(useParent());
const parentObject3D = fromStore(useParentObject3D());
const currentRef = createParentContext();
const object3D = createParentObject3DContext();
$effect.pre(() => {
currentRef.set(ref);
if (isInstanceOf(ref, 'Object3D')) {
object3D.set(ref);
}
invalidate();
});
$effect.pre(() => {
invalidate();
// Save the current ref in case it is destroyed / changed
const current = ref;
// Most common: auto-attach to parent Object3D
if (attach === undefined && isInstanceOf(current, 'Object3D')) {
parentObject3D.current?.add(current);
return () => {
invalidate();
parentObject3D.current?.remove(current);
};
}
// Auto-attach to parent material or geometry
if (attach === undefined && isObject(parent.current)) {
const p = parent.current;
if (isInstanceOf(current, 'Material')) {
const originalMaterial = p.material;
p.material = current;
return () => {
invalidate();
p.material = originalMaterial;
};
}
else if (isInstanceOf(current, 'BufferGeometry')) {
const originalGeometry = p.geometry;
p.geometry = current;
return () => {
invalidate();
p.geometry = originalGeometry;
};
}
}
// Explicitly do not attach
if (attach === false) {
return () => {
invalidate();
};
}
// Custom attach function
if (typeof attach === 'function') {
const cleanup = attach({
ref: current,
parent: parent.current,
parentObject3D: parentObject3D.current
});
return () => {
invalidate();
cleanup?.();
};
}
// Attach to parent prop
if (typeof attach === 'string') {
const { target, key } = resolvePropertyPath(parent.current, attach);
if (key in target) {
// If the key is already in the target, we need to save
// the value before attaching …
const valueBeforeAttach = target[key];
target[key] = current;
return () => {
invalidate();
// … and restore it when the component unmounts
target[key] = valueBeforeAttach;
};
}
else {
// If the key is not in the target, we need to add it …
target[key] = current;
return () => {
invalidate();
// … and delete it when the component unmounts
delete target[key];
};
}
}
// Attach to parent Object3D
if (isInstanceOf(attach, 'Object3D') && isInstanceOf(current, 'Object3D')) {
attach.add(current);
return () => {
invalidate();
attach.remove(current);
};
}
return;
});
};