mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
add support for axes
This commit is contained in:
62
src/misc/helpers.ts
Normal file
62
src/misc/helpers.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import {Document, TypedArray} from '@gltf-transform/core'
|
||||||
|
import {Matrix4, Vector3} from 'three'
|
||||||
|
|
||||||
|
|
||||||
|
/** Exports the colors used for the axes, primary and secondary. They match the orientation gizmo. */
|
||||||
|
export const AxesColors = {
|
||||||
|
x: [[247, 60, 60], [148, 36, 36]],
|
||||||
|
z: [[108, 203, 38], [65, 122, 23]],
|
||||||
|
y: [[23, 140, 240], [14, 84, 144]]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Axes helper as a GLTF model, useful for debugging positions and orientations.
|
||||||
|
*/
|
||||||
|
export function newAxes(doc: Document, size: Vector3, transform: Matrix4) {
|
||||||
|
const buffer = doc.createBuffer()
|
||||||
|
const positions = doc.createAccessor('axesPosition')
|
||||||
|
.setArray(new Float32Array([
|
||||||
|
0, 0, 0,
|
||||||
|
size.x, 0, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, size.y, 0,
|
||||||
|
0, 0, 0,
|
||||||
|
0, 0, -size.z,
|
||||||
|
]) as TypedArray)
|
||||||
|
.setType('VEC3')
|
||||||
|
.setBuffer(buffer)
|
||||||
|
const indices = doc.createAccessor('axesIndices')
|
||||||
|
.setArray(new Uint32Array([0, 1, 2, 3, 4, 5]) as TypedArray)
|
||||||
|
.setType('SCALAR')
|
||||||
|
.setBuffer(buffer)
|
||||||
|
const colors = doc.createAccessor('axesColor')
|
||||||
|
.setArray(new Float32Array([
|
||||||
|
...(AxesColors.x[0]), ...(AxesColors.x[1]),
|
||||||
|
...(AxesColors.y[0]), ...(AxesColors.y[1]),
|
||||||
|
...(AxesColors.z[0]), ...(AxesColors.z[1]),
|
||||||
|
].map(x => x / 255.0)) as TypedArray)
|
||||||
|
.setType('VEC3')
|
||||||
|
.setBuffer(buffer)
|
||||||
|
const material = doc.createMaterial('axesMaterial')
|
||||||
|
.setAlphaMode('OPAQUE')
|
||||||
|
const geometry = doc.createPrimitive()
|
||||||
|
.setIndices(indices)
|
||||||
|
.setAttribute('POSITION', positions)
|
||||||
|
.setAttribute('COLOR_0', colors)
|
||||||
|
.setMode(WebGL2RenderingContext.LINES)
|
||||||
|
.setMaterial(material)
|
||||||
|
const mesh = doc.createMesh('axes').addPrimitive(geometry)
|
||||||
|
const node = doc.createNode('axes').setMesh(mesh).setMatrix(transform.elements as any)
|
||||||
|
doc.createScene('axesScene').addChild(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Grid helper as a GLTF model, useful for debugging sizes with an OrthographicCamera.
|
||||||
|
*
|
||||||
|
* The grid is built as a box of triangles (representing lines) looking to the inside of the box.
|
||||||
|
* This ensures that only the back of the grid is always visible, regardless of the camera position.
|
||||||
|
*/
|
||||||
|
export function newGrid(doc: Document, size: Vector3, transform: Matrix4 = new Matrix4(), divisions = 10) {
|
||||||
|
const buffer = doc.createBuffer();
|
||||||
|
// TODO: implement grid
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import {Ref, ShallowRef} from 'vue';
|
import {Ref, ShallowRef} from 'vue';
|
||||||
import {Document} from '@gltf-transform/core';
|
import {Document} from '@gltf-transform/core';
|
||||||
import {mergeFinalize, mergePartial, removeModel, toBuffer} from "./gltf";
|
import {mergeFinalize, mergePartial, removeModel, toBuffer} from "./gltf";
|
||||||
|
import {newAxes} from "./helpers";
|
||||||
|
import { Matrix4, Vector3 } from 'three';
|
||||||
|
|
||||||
/** This class helps manage SceneManagerData. All methods are static to support reactivity... */
|
/** This class helps manage SceneManagerData. All methods are static to support reactivity... */
|
||||||
export class SceneMgr {
|
export class SceneMgr {
|
||||||
@@ -15,6 +17,16 @@ export class SceneMgr {
|
|||||||
await this.showCurrentDoc(sceneUrl, document);
|
await this.showCurrentDoc(sceneUrl, document);
|
||||||
|
|
||||||
console.log("Model", name, "loaded in", performance.now() - loadStart, "ms");
|
console.log("Model", name, "loaded in", performance.now() - loadStart, "ms");
|
||||||
|
|
||||||
|
if (name !== "__helpers") {
|
||||||
|
// Add a helper axes to the scene
|
||||||
|
let helpersDoc = new Document();
|
||||||
|
// TODO: Get bounding box of the model and use it to set the size of the helpers
|
||||||
|
newAxes(helpersDoc, new Vector3(10, 10, 10), new Matrix4());
|
||||||
|
let helpersUrl = URL.createObjectURL(new Blob([await toBuffer(helpersDoc)]));
|
||||||
|
await SceneMgr.loadModel(sceneUrl, document, "__helpers", helpersUrl);
|
||||||
|
}
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {watch} from "vue";
|
|||||||
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
|
||||||
import {mdiCircleOpacity, mdiDelete, mdiRectangle, mdiRectangleOutline, mdiVectorRectangle} from '@mdi/js'
|
import {mdiCircleOpacity, mdiDelete, mdiRectangle, mdiRectangleOutline, mdiVectorRectangle} from '@mdi/js'
|
||||||
import SvgIcon from '@jamescoyle/vue-icon/lib/svg-icon.vue';
|
import SvgIcon from '@jamescoyle/vue-icon/lib/svg-icon.vue';
|
||||||
import type {WebGLProgramParametersWithUniforms, WebGLRenderer} from "three";
|
|
||||||
|
|
||||||
const props = defineProps<{ mesh: Mesh, viewer: InstanceType<typeof ModelViewerWrapper> | null, document: Document }>();
|
const props = defineProps<{ mesh: Mesh, viewer: InstanceType<typeof ModelViewerWrapper> | null, document: Document }>();
|
||||||
const emit = defineEmits<{ remove: [] }>()
|
const emit = defineEmits<{ remove: [] }>()
|
||||||
@@ -23,7 +22,7 @@ const emit = defineEmits<{ remove: [] }>()
|
|||||||
let modelName = props.mesh.getExtras()[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
let modelName = props.mesh.getExtras()[extrasNameKey] // + " blah blah blah blah blag blah blah blah"
|
||||||
|
|
||||||
let faceCount = props.mesh.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES).length
|
let faceCount = props.mesh.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.TRIANGLES).length
|
||||||
let edgeCount = props.mesh.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.LINE_STRIP).length
|
let edgeCount = props.mesh.listPrimitives().filter(p => p.getMode() in [WebGL2RenderingContext.LINE_STRIP, WebGL2RenderingContext.LINES]).length
|
||||||
let vertexCount = props.mesh.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.POINTS).length
|
let vertexCount = props.mesh.listPrimitives().filter(p => p.getMode() === WebGL2RenderingContext.POINTS).length
|
||||||
|
|
||||||
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
const enabledFeatures = defineModel<Array<number>>("enabledFeatures", {default: [0, 1, 2]});
|
||||||
@@ -40,7 +39,7 @@ function onEnabledFeaturesChange(newEnabledFeatures: Array<number>) {
|
|||||||
sceneModel.traverse((child) => {
|
sceneModel.traverse((child) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
let childIsFace = child.type == 'Mesh' || child.type == 'SkinnedMesh'
|
let childIsFace = child.type == 'Mesh' || child.type == 'SkinnedMesh'
|
||||||
let childIsEdge = child.type == 'Line'
|
let childIsEdge = child.type == 'Line' || child.type == 'LineSegments'
|
||||||
let childIsVertex = child.type == 'Points'
|
let childIsVertex = child.type == 'Points'
|
||||||
if (childIsFace || childIsEdge || childIsVertex) {
|
if (childIsFace || childIsEdge || childIsVertex) {
|
||||||
let visible = newEnabledFeatures.includes(childIsFace ? 0 : childIsEdge ? 1 : childIsVertex ? 2 : -1);
|
let visible = newEnabledFeatures.includes(childIsFace ? 0 : childIsEdge ? 1 : childIsVertex ? 2 : -1);
|
||||||
@@ -62,7 +61,7 @@ function onOpacityChange(newOpacity: number) {
|
|||||||
// Iterate all primitives of the mesh and set their opacity based on the enabled features
|
// Iterate all primitives of the mesh and set their opacity based on the enabled features
|
||||||
// Use the scene graph instead of the document to avoid reloading the same model, at the cost
|
// Use the scene graph instead of the document to avoid reloading the same model, at the cost
|
||||||
// of not actually removing the primitives from the scene graph
|
// of not actually removing the primitives from the scene graph
|
||||||
console.log('Opacity may have changed', newOpacity)
|
// console.log('Opacity may have changed', newOpacity)
|
||||||
sceneModel.traverse((child) => {
|
sceneModel.traverse((child) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
if (child.material && child.material.opacity !== newOpacity) {
|
if (child.material && child.material.opacity !== newOpacity) {
|
||||||
@@ -86,7 +85,7 @@ function onModelLoad() {
|
|||||||
// of not actually removing the primitives from the scene graph
|
// of not actually removing the primitives from the scene graph
|
||||||
sceneModel.traverse((child) => {
|
sceneModel.traverse((child) => {
|
||||||
if (child.userData[extrasNameKey] === modelName) {
|
if (child.userData[extrasNameKey] === modelName) {
|
||||||
// if (child.type == 'Line') {
|
// if (child.type == 'Line' || child.type == 'LineSegments') {
|
||||||
// child.material.linewidth = 3; // Not supported in WebGL2
|
// child.material.linewidth = 3; // Not supported in WebGL2
|
||||||
// If wide lines are really needed, we need https://threejs.org/examples/?q=line#webgl_lines_fat
|
// If wide lines are really needed, we need https://threejs.org/examples/?q=line#webgl_lines_fat
|
||||||
// }
|
// }
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function createGizmo(expectedParent: HTMLElement, scene: ModelScene): HTMLElemen
|
|||||||
bubbleSizeSeconday: expectedParent.clientWidth / 14,
|
bubbleSizeSeconday: expectedParent.clientWidth / 14,
|
||||||
fontSize: (expectedParent.clientWidth / 10) + "px"
|
fontSize: (expectedParent.clientWidth / 10) + "px"
|
||||||
});
|
});
|
||||||
// HACK: Swap axes to match A-Frame
|
// HACK: Swap axes to fake the CAD orientation
|
||||||
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
||||||
let indexA = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[0])
|
let indexA = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[0])
|
||||||
let indexB = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[1])
|
let indexB = gizmo.bubbles.findIndex((bubble) => bubble.axis == swap[1])
|
||||||
|
|||||||
Reference in New Issue
Block a user