cleaning up

This commit is contained in:
Yeicor
2024-02-16 20:35:27 +01:00
parent 0545d8fbe2
commit c10e5f0896
14 changed files with 58 additions and 105 deletions

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import {onMounted, onUpdated, ref} from "vue";
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
// Optimized minimal dependencies from three to avoid more async imports
import {Vector3} from "three/src/math/Vector3.js";
import {Matrix4} from "three/src/math/Matrix4.js";
globalThis.THREE = {Vector3, Matrix4} as any // HACK: Required for the gizmo to work
const props = defineProps({
scene: Object
});
function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElement {
// noinspection SpellCheckingInspection
let gizmo = new OrientationGizmoRaw(scene.camera, {
size: expectedParent.clientWidth,
bubbleSizePrimary: expectedParent.clientWidth / 12,
bubbleSizeSeconday: expectedParent.clientWidth / 14,
fontSize: (expectedParent.clientWidth / 10) + "px"
});
// HACK: Swap axes to match A-Frame
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
let indexA = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[0])
let indexB = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[1])
let dirA = gizmo.bubbles[indexA].direction.clone();
let dirB = gizmo.bubbles[indexB].direction.clone();
gizmo.bubbles[indexA].direction.copy(dirB);
gizmo.bubbles[indexB].direction.copy(dirA);
}
// Append and listen for events
gizmo.onAxisSelected = (axis: { direction: { x: any; y: any; z: any; }; }) => {
let lookFrom = scene.getCamera().position.clone();
let lookAt = scene.getTarget().clone().add(scene.target.position);
let magnitude = lookFrom.clone().sub(lookAt).length()
let direction = new Vector3(axis.direction.x, axis.direction.y, axis.direction.z);
let newLookFrom = lookAt.clone().add(direction.clone().multiplyScalar(magnitude));
//console.log("New camera position", newLookFrom)
scene.getCamera().position.copy(newLookFrom);
scene.getCamera().lookAt(lookAt);
if ((scene as any).__perspectiveCamera) { // HACK: Make the hacky ortho also work
(scene as any).__perspectiveCamera.position.copy(newLookFrom);
(scene as any).__perspectiveCamera.lookAt(lookAt);
}
scene.queueRender();
}
return gizmo;
}
// Mount, unmount and listen for scene changes
let container = ref<HTMLElement | null>(null);
let gizmo: HTMLElement & { update: () => void }
function updateGizmo() {
if (gizmo.isConnected) {
gizmo.update();
requestIdleCallback(updateGizmo);
}
}
let reinstall = () => {
if (gizmo) container.value.removeChild(gizmo);
gizmo = createGizmo(container.value, props.scene as ModelScene) as typeof gizmo;
container.value.appendChild(gizmo);
requestIdleCallback(updateGizmo); // Low priority updates
}
onMounted(reinstall)
onUpdated(reinstall);
// onUnmounted is not needed because the gizmo is removed when the container is removed
</script>
<template>
<div ref="container" class="orientation-gizmo"/>
</template>

69
src/tools/Tools.vue Normal file
View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import {VBtn, VIcon} from "vuetify/lib/components";
import {ref} from "vue";
import OrientationGizmo from "./OrientationGizmo.vue";
import type {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
import {OrthographicCamera} from "three";
const props = defineProps({
modelViewerInfo: Object
});
function syncOrthoCamera(force: boolean) {
if (!props.modelViewerInfo) return;
let scene: ModelScene = props.modelViewerInfo.scene
let perspectiveCam = (scene as any).__perspectiveCamera;
if (force || perspectiveCam && scene.camera != perspectiveCam) {
// Get zoom level from perspective camera
let dist = scene.getTarget().distanceToSquared(perspectiveCam.position);
let w = scene.aspect * dist ** 1.1 / 4000;
let h = dist ** 1.1 / 4000;
(scene as any).camera = new OrthographicCamera(-w, w, h, -h, perspectiveCam.near, perspectiveCam.far);
scene.camera.position.copy(perspectiveCam.position);
scene.camera.lookAt(scene.getTarget().clone().add(scene.target.position));
requestAnimationFrame(() => syncOrthoCamera(false));
}
}
let toggleProjectionText = ref('PERSP'); // Default to perspective camera
function toggleProjection() {
if (!props.modelViewerInfo) return;
let scene: ModelScene = props.modelViewerInfo.scene
let prevCam = scene.camera;
let wasPerspectiveCamera = prevCam.isPerspectiveCamera;
if (wasPerspectiveCamera) {
(scene as any).__perspectiveCamera = prevCam; // Save the default perspective camera
// This hack also needs to sync the camera position and target
requestAnimationFrame(() => syncOrthoCamera(true));
} else {
// Restore the default perspective camera
scene.camera = (scene as any).__perspectiveCamera;
}
toggleProjectionText.value = wasPerspectiveCamera ? 'ORTHO' : 'PERSP';
}
</script>
<template>
<orientation-gizmo v-if="props.modelViewerInfo" :scene="props.modelViewerInfo.scene"/>
<v-btn icon="mdi-projector" @click="toggleProjection"><span class="icon-detail">{{ toggleProjectionText }}</span>
<v-icon icon="mdi-projector"></v-icon>
</v-btn>
</template>
<style>
.icon-detail {
position: absolute;
top: 10px;
left: 0;
font-size: xx-small;
width: 100%;
margin: auto;
}
.icon-detail + .v-icon {
position: relative;
top: 5px;
}
</style>

45
src/tools/settings.ts Normal file
View File

@@ -0,0 +1,45 @@
// These are the default values for the settings, which are overridden below
export const settings = {
// @ts-ignore
preloadModels: [new URL('../../assets/fox.glb', import.meta.url).href, new URL('../../assets/logo.glbs', import.meta.url).href, "ws://localhost:8080"],
// ModelViewer settings
autoplay: true,
arModes: 'webxr scene-viewer quick-look',
exposure: 1,
shadowIntensity: 0,
background: '',
}
const firstTimeNames = []; // Needed for array values, which clear the array when overridden
function parseSetting(name: string, value: string): any {
let arrayElem = name.endsWith(".0")
if (arrayElem) name = name.slice(0, -2);
let prevValue = settings[name];
if (prevValue === undefined) throw new Error(`Unknown setting: ${name}`);
if (Array.isArray(prevValue) && !arrayElem) {
let toExtend = []
if (!firstTimeNames.includes(name)) {
firstTimeNames.push(name);
} else {
toExtend = prevValue;
}
toExtend.push(parseSetting(name + ".0", value));
return toExtend;
}
switch (typeof prevValue) {
case 'boolean':
return value === 'true';
case 'number':
return Number(value);
case 'string':
return value;
default:
throw new Error(`Unknown setting type: ${typeof prevValue}`);
}
}
// Auto-override any settings from the URL
const url = new URL(window.location.href);
url.searchParams.forEach((value, key) => {
if (key in settings) settings[key] = parseSetting(key, value);
})