|
@@ -0,0 +1,190 @@
|
|
|
+import { DrawExpose } from "@/core/hook/use-expose";
|
|
|
+import { globalWatch, installGlobalVar } from "@/core/hook/use-global-vars";
|
|
|
+import { listener } from "@/utils/event";
|
|
|
+import { frameEebounce } from "@/utils/shared";
|
|
|
+import mitt, { Emitter } from "mitt";
|
|
|
+import {
|
|
|
+ Color,
|
|
|
+ Object3D,
|
|
|
+ PCFSoftShadowMap,
|
|
|
+ PerspectiveCamera,
|
|
|
+ Scene,
|
|
|
+ Vector3,
|
|
|
+ WebGLRenderer,
|
|
|
+} from "three";
|
|
|
+import { OrbitControls } from "three/addons";
|
|
|
+import {
|
|
|
+ computed,
|
|
|
+ getCurrentInstance,
|
|
|
+ onUnmounted,
|
|
|
+ Ref,
|
|
|
+ ref,
|
|
|
+ shallowRef,
|
|
|
+ watch,
|
|
|
+} from "vue";
|
|
|
+
|
|
|
+export const instanceName = "three-renderer";
|
|
|
+export const installThreeGlobalVar = <T>(
|
|
|
+ create: () => { var: T; onDestroy: () => void } | T,
|
|
|
+ key = Symbol("globalVar")
|
|
|
+) => installGlobalVar(create, key, instanceName);
|
|
|
+
|
|
|
+export const useContainer = installThreeGlobalVar(() => ref<HTMLDivElement>());
|
|
|
+
|
|
|
+export const useRenderer = installThreeGlobalVar(() => {
|
|
|
+ const container = useContainer();
|
|
|
+ const renderer = new WebGLRenderer({ antialias: true }) as WebGLRenderer & {
|
|
|
+ bus: Emitter<{ sizeChange: void }>;
|
|
|
+ };
|
|
|
+
|
|
|
+ renderer.bus = mitt();
|
|
|
+ renderer.shadowMap.enabled = true;
|
|
|
+ renderer.shadowMap.type = PCFSoftShadowMap
|
|
|
+
|
|
|
+ const init = (container: HTMLDivElement) => {
|
|
|
+ container.appendChild(renderer.domElement);
|
|
|
+ const resizeHandler = () => {
|
|
|
+ const w = container.offsetWidth;
|
|
|
+ const h = container.offsetHeight;
|
|
|
+ renderer.setSize(w, h);
|
|
|
+ renderer.bus.emit("sizeChange");
|
|
|
+ };
|
|
|
+
|
|
|
+ resizeHandler();
|
|
|
+ return listener(window, "resize", resizeHandler);
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ var: renderer,
|
|
|
+ onDestroy: globalWatch(
|
|
|
+ container,
|
|
|
+ (dom, _, onCleanup) => {
|
|
|
+ dom && onCleanup(init(dom));
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+ ),
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+export type StageProps = {
|
|
|
+ draw: DrawExpose
|
|
|
+ height?: number
|
|
|
+}
|
|
|
+export const useStageProps = installThreeGlobalVar(() => ref() as Ref<Required<StageProps>>)
|
|
|
+
|
|
|
+export type Loop = (time: number) => void;
|
|
|
+export const useAnimationLoop = installThreeGlobalVar(() => {
|
|
|
+ const renderer = useRenderer();
|
|
|
+ const loops = ref<Loop[]>([]);
|
|
|
+ const cleanup = globalWatch(
|
|
|
+ () => loops.value.length > 0,
|
|
|
+ (canLoop) => {
|
|
|
+ if (canLoop) {
|
|
|
+ renderer.setAnimationLoop((time) => {
|
|
|
+ for (const loop of loops.value) {
|
|
|
+ loop(time);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ renderer.setAnimationLoop(null);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const remove = (fn: Loop) => {
|
|
|
+ const ndx = loops.value.indexOf(fn);
|
|
|
+ if (~ndx) {
|
|
|
+ loops.value.splice(ndx, 1);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const add = (fn: Loop) => {
|
|
|
+ loops.value.push(fn);
|
|
|
+ return () => remove(fn);
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ var: { add, remove },
|
|
|
+ onDestroy: cleanup,
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+export const useCamera = installThreeGlobalVar(() => {
|
|
|
+ const camera = new PerspectiveCamera(75, 1, 0.1, 500000);
|
|
|
+ camera.position.set(0, 2, 0)
|
|
|
+ camera.position.multiplyScalar(800)
|
|
|
+ // camera.position.z = 180;
|
|
|
+ // camera.position.y = 200;
|
|
|
+ camera.lookAt(new Vector3(0, 1, 0));
|
|
|
+ return camera;
|
|
|
+});
|
|
|
+
|
|
|
+export const useScene = installThreeGlobalVar(() => {
|
|
|
+ const scene = new Scene()
|
|
|
+ scene.background = new Color('white')
|
|
|
+ return scene
|
|
|
+});
|
|
|
+
|
|
|
+export const useRender = installThreeGlobalVar(() => {
|
|
|
+ const renderer = useRenderer();
|
|
|
+ const scene = useScene();
|
|
|
+ const camera = useCamera();
|
|
|
+ const render = frameEebounce(() => renderer.render(scene, camera));
|
|
|
+
|
|
|
+ const container = useContainer();
|
|
|
+ watch(container, (container, _, onCleanup) => {
|
|
|
+ if (container) {
|
|
|
+ const controls = new OrbitControls(camera, container);
|
|
|
+ controls.target.set(0, 5, 0);
|
|
|
+ controls.update();
|
|
|
+ controls.addEventListener("change", render);
|
|
|
+ onCleanup(() => {
|
|
|
+ controls.removeEventListener("change", render);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return render;
|
|
|
+});
|
|
|
+
|
|
|
+export const useTree = () => {
|
|
|
+ const expose = shallowRef();
|
|
|
+ const current = getCurrentInstance() as any;
|
|
|
+ const render = useRender();
|
|
|
+
|
|
|
+ let parent = current.parent;
|
|
|
+ while (parent.type.name !== instanceName) {
|
|
|
+ if (parent.__three_instance) {
|
|
|
+ break;
|
|
|
+ } else if (parent.parent) {
|
|
|
+ parent = parent.parent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const scene = useScene();
|
|
|
+ const threeParent = computed(
|
|
|
+ () =>
|
|
|
+ (parent.__three_instance
|
|
|
+ ? parent.__three_instance.value
|
|
|
+ : scene) as Object3D
|
|
|
+ );
|
|
|
+
|
|
|
+ watch([threeParent, expose], ([parent, current], _, onCleanup) => {
|
|
|
+ if (parent && current) {
|
|
|
+ parent.add(current);
|
|
|
+ render();
|
|
|
+ onCleanup(() => {
|
|
|
+ parent.remove(current);
|
|
|
+ render();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ current.__three_instance = expose;
|
|
|
+ onUnmounted(() => {
|
|
|
+ expose.value = undefined;
|
|
|
+ });
|
|
|
+ return expose;
|
|
|
+};
|
|
|
+
|
|
|
+export const useDrawHook = <T extends () => any>(hook: T): ReturnType<T> => {
|
|
|
+ const draw = useStageProps().value.draw;
|
|
|
+ return draw.runHook(hook);
|
|
|
+}
|