mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
Initial commit
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Include your project-specific ignores in this file
|
||||||
|
# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
|
||||||
|
# Useful .gitignore templates: https://github.com/github/gitignore
|
||||||
|
/node_modules/
|
||||||
|
/dist/
|
||||||
|
/.cache/
|
||||||
|
/.parcel-cache/
|
||||||
|
/.idea/
|
||||||
20
package.json
Normal file
20
package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "yet-another-cad-viewer",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Yeicor",
|
||||||
|
"scripts": {
|
||||||
|
"start": "parcel src/index.html",
|
||||||
|
"build": "parcel build src/index.html"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"stats.js": "^0.17.0",
|
||||||
|
"three": "^0.160.1",
|
||||||
|
"three-orientation-gizmo": "https://github.com/jrj2211/three-orientation-gizmo"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/three": "^0.160.0",
|
||||||
|
"parcel": "^2.11.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/app.ts
Normal file
105
src/app.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import {Box3, Matrix4, Vector3} from "three";
|
||||||
|
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
|
||||||
|
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
|
||||||
|
import {OrientationGizmo} from "./orientation";
|
||||||
|
import * as Stats from "stats.js";
|
||||||
|
|
||||||
|
export class App {
|
||||||
|
renderer = new THREE.WebGLRenderer({antialias: true});
|
||||||
|
//camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 1000);
|
||||||
|
camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.01, 1000);
|
||||||
|
private controls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||||
|
// CAD has Z up, so rotate the scene to match
|
||||||
|
scene = new THREE.Scene();
|
||||||
|
private helperGroup = new THREE.Group();
|
||||||
|
private modelGroup = new THREE.Group();
|
||||||
|
loader = new GLTFLoader();
|
||||||
|
private gizmo = new OrientationGizmo(this.camera, this.controls);
|
||||||
|
private stats = new Stats();
|
||||||
|
|
||||||
|
install() {
|
||||||
|
// Prepare camera and scene
|
||||||
|
//this.setupSceneHelpers(new THREE.Box3().setFromCenterAndSize(new THREE.Vector3(), new THREE.Vector3(10, 10, 10)));
|
||||||
|
// this.helperGroup.setRotationFromMatrix(this.threeToCad)
|
||||||
|
this.scene.add(this.helperGroup);
|
||||||
|
// this.modelGroup.setRotationFromMatrix(this.threeToCad)
|
||||||
|
this.scene.add(this.modelGroup);
|
||||||
|
// Set up renderer
|
||||||
|
document.body.appendChild(this.renderer.domElement);
|
||||||
|
this.renderer.setAnimationLoop(this._loop.bind(this));
|
||||||
|
// On window resize, also resize the renderer
|
||||||
|
let onResize = () => {
|
||||||
|
this.renderer.setSize(window.innerWidth, window.innerHeight);
|
||||||
|
if (this.camera instanceof THREE.PerspectiveCamera) {
|
||||||
|
this.camera.aspect = window.innerWidth / window.innerHeight;
|
||||||
|
} else {
|
||||||
|
const aspect = window.innerWidth / window.innerHeight;
|
||||||
|
const frustumSize = 2
|
||||||
|
this.camera.left = - frustumSize * aspect / 2;
|
||||||
|
this.camera.right = frustumSize * aspect / 2;
|
||||||
|
this.camera.top = frustumSize / 2;
|
||||||
|
this.camera.bottom = - frustumSize / 2;
|
||||||
|
}
|
||||||
|
this.camera.updateProjectionMatrix();
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', onResize);
|
||||||
|
onResize()
|
||||||
|
// Misc installation
|
||||||
|
this.gizmo.install();
|
||||||
|
document.body.appendChild(this.stats.dom)
|
||||||
|
this.stats.dom.style.left = '';
|
||||||
|
this.stats.dom.style.right = '0px';
|
||||||
|
this.stats.dom.style.top = '120px';
|
||||||
|
this.stats.showPanel(1); // 0: fps, 1: ms, 2: mb, 3+: custom
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupSceneHelpers(bb: Box3) { // The bounding box in three.js coordinates
|
||||||
|
this.helperGroup.clear();
|
||||||
|
let center = bb.getCenter(new THREE.Vector3());
|
||||||
|
this.helperGroup.applyMatrix4(new Matrix4().makeTranslation(center))
|
||||||
|
let size = bb.getSize(new THREE.Vector3());
|
||||||
|
console.log(center, size)
|
||||||
|
this.controls.target.set(center.x, center.y, center.z);
|
||||||
|
this.camera.position.set(center.x, center.y, center.z);
|
||||||
|
this.camera.position.x += size.x * 0.75;
|
||||||
|
this.camera.position.y += size.y * 0.5;
|
||||||
|
this.camera.position.z += size.z;
|
||||||
|
this.controls.update()
|
||||||
|
this.helperGroup.add(new THREE.HemisphereLight(0xffffff, 0x444444))
|
||||||
|
let gridXZ = new THREE.GridHelper(1, 10);
|
||||||
|
gridXZ.applyMatrix4(new Matrix4().makeTranslation(new Vector3(0, -size.y / 2, 0)))
|
||||||
|
gridXZ.scale.set(size.x, 1, size.z)
|
||||||
|
this.helperGroup.add(gridXZ)
|
||||||
|
let gridXY = new THREE.GridHelper(1, 10);
|
||||||
|
gridXY.applyMatrix4(new Matrix4().makeRotationX(Math.PI / 2))
|
||||||
|
gridXY.applyMatrix4(new Matrix4().makeTranslation(new Vector3(0, 0, -size.z / 2)))
|
||||||
|
gridXY.scale.set(size.x, 1, size.y)
|
||||||
|
this.helperGroup.add(gridXY)
|
||||||
|
let gridYZ = new THREE.GridHelper(1, 10);
|
||||||
|
gridYZ.applyMatrix4(new Matrix4().makeRotationZ(Math.PI / 2))
|
||||||
|
gridYZ.applyMatrix4(new Matrix4().makeTranslation(new Vector3(-size.x / 2, 0, 0)))
|
||||||
|
// noinspection JSSuspiciousNameCombination
|
||||||
|
gridYZ.scale.set(size.y, 1, size.z)
|
||||||
|
this.helperGroup.add(gridYZ)
|
||||||
|
let axes = new THREE.AxesHelper(size.length() / 4);
|
||||||
|
axes.applyMatrix4(new THREE.Matrix4().makeRotationX(-Math.PI / 2)) // Y-up to Z-up (reference-only)
|
||||||
|
this.helperGroup.add(axes)
|
||||||
|
}
|
||||||
|
|
||||||
|
addModel(url: string) {
|
||||||
|
this.loader.loadAsync(url, console.log).then((model) => {
|
||||||
|
this.modelGroup.add(model.scene)
|
||||||
|
this.setupSceneHelpers(new THREE.Box3().setFromObject(model.scene));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_loop(time) {
|
||||||
|
this.stats.begin();
|
||||||
|
this.controls.update();
|
||||||
|
this.gizmo.update();
|
||||||
|
this.renderer.render(this.scene, this.camera);
|
||||||
|
this.stats.end();
|
||||||
|
this.stats.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/index.css
Normal file
5
src/index.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
body {
|
||||||
|
background: black;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
12
src/index.html
Normal file
12
src/index.html
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Yet Another CAD Viewer</title>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no' />
|
||||||
|
<link rel="stylesheet" type="text/css" href="./index.css">
|
||||||
|
<script type="module" src="./index.ts"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
src/index.ts
Normal file
5
src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import {App} from "./app";
|
||||||
|
|
||||||
|
const app = new App()
|
||||||
|
app.install();
|
||||||
|
app.addModel(`https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/Duck/glTF-Binary/Duck.glb`)
|
||||||
49
src/orientation.ts
Normal file
49
src/orientation.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import {Camera} from "three";
|
||||||
|
import * as OrientationGizmoRaw from "three-orientation-gizmo/src/OrientationGizmo";
|
||||||
|
import THREE = require("three");
|
||||||
|
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
|
||||||
|
|
||||||
|
window.THREE = THREE // HACK: Required for the gizmo to work
|
||||||
|
|
||||||
|
export class OrientationGizmo {
|
||||||
|
element: OrientationGizmoRaw
|
||||||
|
|
||||||
|
constructor(camera: Camera, controls: OrbitControls) {
|
||||||
|
this.element = new OrientationGizmoRaw(camera, {
|
||||||
|
size: 120,
|
||||||
|
bubbleSizePrimary: 12,
|
||||||
|
bubbleSizeSeconday: 10,
|
||||||
|
fontSize: "14px"
|
||||||
|
});
|
||||||
|
// Place in the top right corner
|
||||||
|
this.element.style.position = "absolute";
|
||||||
|
this.element.style.top = "0px";
|
||||||
|
this.element.style.right = "0px";
|
||||||
|
this.element.style.zIndex = "1000";
|
||||||
|
// HACK: Swap axes to match A-Frame
|
||||||
|
for (let swap of [["y", "-z"], ["z", "-y"], ["z", "-z"]]) {
|
||||||
|
let indexA = this.element.bubbles.findIndex((bubble) => bubble.axis == swap[0])
|
||||||
|
let indexB = this.element.bubbles.findIndex((bubble) => bubble.axis == swap[1])
|
||||||
|
let dirA = this.element.bubbles[indexA].direction.clone();
|
||||||
|
let dirB = this.element.bubbles[indexB].direction.clone();
|
||||||
|
this.element.bubbles[indexA].direction.copy(dirB);
|
||||||
|
this.element.bubbles[indexB].direction.copy(dirA);
|
||||||
|
}
|
||||||
|
// Append and listen for events
|
||||||
|
this.element.onAxisSelected = (axis) => {
|
||||||
|
let magnitude = camera.position.clone().sub(controls.target).length()
|
||||||
|
let direction = new THREE.Vector3(axis.direction.x, axis.direction.y, axis.direction.z);
|
||||||
|
direction.normalize();
|
||||||
|
console.log(controls.target, direction, magnitude)
|
||||||
|
camera.position.copy(controls.target.clone().add(direction.multiplyScalar(magnitude)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install() {
|
||||||
|
document.body.appendChild(this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.element.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user