774 lines
25 KiB
JavaScript
774 lines
25 KiB
JavaScript
"use strict";
|
|
|
|
const orbit = require("./orbit");
|
|
const cookie = require("./cookie")("bbctrl-");
|
|
const font = require("./helvetiker_regular.typeface.json");
|
|
|
|
module.exports = {
|
|
template: "#path-viewer-template",
|
|
props: [ "toolpath" ],
|
|
|
|
data: function () {
|
|
return {
|
|
enabled: false,
|
|
loading: false,
|
|
dirty: true,
|
|
snapView: cookie.get("snap-view", "isometric"),
|
|
small: cookie.get_bool("small-path-view", true),
|
|
surfaceMode: "cut",
|
|
showPath: cookie.get_bool("show-path", true),
|
|
showTool: cookie.get_bool("show-tool", true),
|
|
showBBox: cookie.get_bool("show-bbox", true),
|
|
showAxes: cookie.get_bool("show-axes", true),
|
|
showIntensity: cookie.get_bool("show-intensity", false)
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
target: function () {
|
|
return this.$el.querySelector(".path-viewer-content");
|
|
},
|
|
|
|
webglAvailable: function() {
|
|
// Create canvas element. The canvas is not added to the
|
|
// document itself, so it is never displayed in the
|
|
// browser window.
|
|
const canvas = document.createElement("canvas");
|
|
|
|
// Get WebGLRenderingContext from canvas element.
|
|
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
|
|
// Report the result.
|
|
return gl && gl instanceof WebGLRenderingContext;
|
|
}
|
|
},
|
|
|
|
watch: {
|
|
toolpath: function () {
|
|
Vue.nextTick(this.update);
|
|
},
|
|
|
|
surfaceMode: function (mode) {
|
|
this.update_surface_mode(mode);
|
|
},
|
|
|
|
small: function (enable) {
|
|
cookie.set_bool("small-path-view", enable);
|
|
Vue.nextTick(this.update_view);
|
|
},
|
|
|
|
showPath: function (enable) {
|
|
cookie.set_bool("show-path", enable);
|
|
this.set_visible(this.pathView, enable);
|
|
},
|
|
|
|
showTool: function (enable) {
|
|
cookie.set_bool("show-tool", enable);
|
|
this.set_visible(this.toolView, enable);
|
|
},
|
|
|
|
showAxes: function (enable) {
|
|
cookie.set_bool("show-axes", enable);
|
|
this.set_visible(this.axesView, enable);
|
|
},
|
|
|
|
showIntensity: function (enable) {
|
|
cookie.set_bool("show-intensity", enable);
|
|
Vue.nextTick(this.update);
|
|
},
|
|
|
|
showBBox: function (enable) {
|
|
cookie.set_bool("show-bbox", enable);
|
|
this.set_visible(this.bboxView, enable);
|
|
this.set_visible(this.envelopeView, enable);
|
|
},
|
|
|
|
x: function () {
|
|
this.axis_changed();
|
|
},
|
|
|
|
y: function () {
|
|
this.axis_changed();
|
|
},
|
|
|
|
z: function () {
|
|
this.axis_changed();
|
|
}
|
|
},
|
|
|
|
ready: function () {
|
|
this.graphics();
|
|
Vue.nextTick(this.update);
|
|
},
|
|
|
|
methods: {
|
|
update: async function () {
|
|
if (!this.webglAvailable) {
|
|
return;
|
|
}
|
|
|
|
if (!this.state.selected) {
|
|
this.dirty = true;
|
|
this.scene = new THREE.Scene();
|
|
|
|
} else if (!this.toolpath.filename && !this.loading) {
|
|
this.loading = true;
|
|
this.dirty = true;
|
|
this.draw_loading();
|
|
}
|
|
|
|
if (!this.enabled || !this.toolpath.filename) {
|
|
return;
|
|
}
|
|
|
|
async function get(url) {
|
|
const response = await fetch(`${url}`, { cache: "no-cache" });
|
|
const arrayBuffer = await response.arrayBuffer();
|
|
|
|
return new Float32Array(arrayBuffer);
|
|
}
|
|
|
|
const [ positions, speeds ] = await Promise.all([
|
|
get(`/api/path/${this.toolpath.filename}/positions`),
|
|
get(`/api/path/${this.toolpath.filename}/speeds`)
|
|
]);
|
|
|
|
this.positions = positions;
|
|
this.speeds = speeds;
|
|
this.loading = false;
|
|
|
|
// Update scene
|
|
this.scene = new THREE.Scene();
|
|
this.draw(this.scene);
|
|
this.snap(this.snapView);
|
|
|
|
this.update_view();
|
|
},
|
|
|
|
update_surface_mode: function (mode) {
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
|
|
if (typeof this.surfaceMaterial != "undefined") {
|
|
this.surfaceMaterial.wireframe = mode == "wire";
|
|
this.surfaceMaterial.needsUpdate = true;
|
|
}
|
|
|
|
this.set_visible(this.surfaceMesh, mode == "cut" || mode == "wire");
|
|
this.set_visible(this.workpieceMesh, mode == "solid");
|
|
},
|
|
|
|
load_surface: function (surface) {
|
|
if (typeof surface == "undefined") {
|
|
this.vertices = undefined;
|
|
this.normals = undefined;
|
|
return;
|
|
}
|
|
|
|
this.vertices = surface.vertices;
|
|
|
|
// Expand normals
|
|
this.normals = [];
|
|
for (let i = 0; i < surface.normals.length / 3; i++) {
|
|
for (let j = 0; j < 3; j++) {
|
|
for (let k = 0; k < 3; k++) {
|
|
this.normals.push(surface.normals[i * 3 + k]);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
set_visible: function (target, visible) {
|
|
if (typeof target != "undefined") {
|
|
target.visible = visible;
|
|
}
|
|
this.dirty = true;
|
|
},
|
|
|
|
get_dims: function () {
|
|
const computedStyle = window.getComputedStyle(this.target);
|
|
|
|
return {
|
|
width: parseInt(computedStyle.width),
|
|
height: parseInt(computedStyle.height)
|
|
};
|
|
},
|
|
|
|
update_view: function () {
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
|
|
const dims = this.get_dims();
|
|
|
|
this.camera.aspect = dims.width / dims.height;
|
|
this.camera.updateProjectionMatrix();
|
|
this.renderer.setSize(dims.width, dims.height);
|
|
|
|
if (this.loading) {
|
|
this.controls.reset();
|
|
this.camera.position.copy(new THREE.Vector3(0, 0, 600));
|
|
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
|
|
}
|
|
|
|
this.dirty = true;
|
|
},
|
|
|
|
update_tool: function (tool) {
|
|
if (!this.enabled) {
|
|
return;
|
|
}
|
|
|
|
if (typeof tool == "undefined") {
|
|
tool = this.toolView;
|
|
}
|
|
|
|
if (typeof tool == "undefined") {
|
|
return;
|
|
}
|
|
|
|
tool.position.x = this.x.pos;
|
|
tool.position.y = this.y.pos;
|
|
tool.position.z = this.z.pos;
|
|
},
|
|
|
|
update_envelope: function (envelope) {
|
|
if (!this.enabled || !this.axes.homed) {
|
|
return;
|
|
}
|
|
|
|
if (typeof envelope == "undefined") {
|
|
envelope = this.envelopeView;
|
|
}
|
|
|
|
if (typeof envelope == "undefined") {
|
|
return;
|
|
}
|
|
|
|
const min = new THREE.Vector3();
|
|
const max = new THREE.Vector3();
|
|
|
|
for (const axis of "xyz") {
|
|
min[axis] = this[axis].min - this[axis].off;
|
|
max[axis] = this[axis].max - this[axis].off;
|
|
}
|
|
|
|
const bounds = new THREE.Box3(min, max);
|
|
if (bounds.isEmpty()) {
|
|
envelope.geometry = this.create_empty_geom();
|
|
} else {
|
|
envelope.geometry = this.create_bbox_geom(bounds);
|
|
}
|
|
},
|
|
|
|
axis_changed: function () {
|
|
this.update_tool();
|
|
this.update_envelope();
|
|
this.dirty = true;
|
|
},
|
|
|
|
graphics: function () {
|
|
if (!this.webglAvailable) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Renderer
|
|
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
this.renderer.setPixelRatio(window.devicePixelRatio);
|
|
this.renderer.setClearColor(0, 0);
|
|
this.target.appendChild(this.renderer.domElement);
|
|
|
|
} catch (e) {
|
|
console.log("WebGL not supported: ", e);
|
|
return;
|
|
}
|
|
|
|
this.enabled = true;
|
|
|
|
// Camera
|
|
this.camera = new THREE.PerspectiveCamera(45, 4 / 3, 1, 10000);
|
|
|
|
// Lighting
|
|
this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
|
|
|
|
const keyLight = new THREE.DirectionalLight(new THREE.Color("hsl(30, 100%, 75%)"), 0.75);
|
|
keyLight.position.set(-100, 0, 100);
|
|
|
|
const fillLight = new THREE.DirectionalLight(new THREE.Color("hsl(240, 100%, 75%)"), 0.25);
|
|
fillLight.position.set(100, 0, 100);
|
|
|
|
const backLight = new THREE.DirectionalLight(0xffffff, 0.5);
|
|
backLight.position.set(100, 0, -100).normalize();
|
|
|
|
this.lights = new THREE.Group();
|
|
this.lights.add(keyLight);
|
|
this.lights.add(fillLight);
|
|
this.lights.add(backLight);
|
|
|
|
// Surface material
|
|
this.surfaceMaterial = this.create_surface_material();
|
|
|
|
// Controls
|
|
this.controls = new orbit(this.camera, this.renderer.domElement);
|
|
this.controls.enableDamping = true;
|
|
this.controls.dampingFactor = 0.2;
|
|
this.controls.rotateSpeed = 0.25;
|
|
this.controls.enableZoom = true;
|
|
|
|
// Move lights with scene
|
|
this.controls.addEventListener("change", function (scope) {
|
|
return function () {
|
|
keyLight.position.copy(scope.camera.position);
|
|
fillLight.position.copy(scope.camera.position);
|
|
backLight.position.copy(scope.camera.position);
|
|
keyLight.lookAt(scope.controls.target);
|
|
fillLight.lookAt(scope.controls.target);
|
|
backLight.lookAt(scope.controls.target);
|
|
scope.dirty = true;
|
|
};
|
|
}(this));
|
|
|
|
// Events
|
|
window.addEventListener("resize", this.update_view, false);
|
|
|
|
// Start it
|
|
this.render();
|
|
},
|
|
|
|
create_surface_material: function () {
|
|
return new THREE.MeshPhongMaterial({
|
|
specular: 0x111111,
|
|
shininess: 10,
|
|
side: THREE.FrontSide,
|
|
color: 0x0c2d53
|
|
});
|
|
},
|
|
|
|
draw_loading: function () {
|
|
this.scene = new THREE.Scene();
|
|
|
|
const geometry = new THREE.TextGeometry("Loading 3D View...", {
|
|
font: new THREE.Font(font),
|
|
size: 40,
|
|
height: 5,
|
|
curveSegments: 12,
|
|
bevelEnabled: true,
|
|
bevelThickness: 10,
|
|
bevelSize: 8,
|
|
bevelSegments: 5
|
|
});
|
|
geometry.computeBoundingBox();
|
|
|
|
const mesh = new THREE.Mesh(geometry, this.surfaceMaterial);
|
|
|
|
this.scene.add(mesh);
|
|
this.scene.add(this.ambient);
|
|
this.scene.add(this.lights);
|
|
this.update_view();
|
|
},
|
|
|
|
draw_workpiece: function (scene, material) {
|
|
if (typeof this.workpiece == "undefined") {
|
|
return;
|
|
}
|
|
|
|
let min = this.workpiece.min;
|
|
let max = this.workpiece.max;
|
|
|
|
min = new THREE.Vector3(min[0], min[1], min[2]);
|
|
max = new THREE.Vector3(max[0], max[1], max[2]);
|
|
const dims = max.clone().sub(min);
|
|
|
|
const geometry = new THREE.BoxGeometry(dims.x, dims.y, dims.z);
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
|
|
const offset = dims.clone();
|
|
offset.divideScalar(2);
|
|
offset.add(min);
|
|
|
|
mesh.position.add(offset);
|
|
|
|
geometry.computeBoundingBox();
|
|
|
|
scene.add(mesh);
|
|
|
|
return mesh;
|
|
},
|
|
|
|
draw_surface: function (scene, material) {
|
|
if (typeof this.vertices == "undefined") {
|
|
return;
|
|
}
|
|
|
|
const geometry = new THREE.BufferGeometry();
|
|
|
|
geometry.addAttribute("position", new THREE.Float32BufferAttribute(this.vertices, 3));
|
|
geometry.addAttribute("normal", new THREE.Float32BufferAttribute(this.normals, 3));
|
|
|
|
geometry.computeBoundingSphere();
|
|
geometry.computeBoundingBox();
|
|
|
|
return new THREE.Mesh(geometry, material);
|
|
},
|
|
|
|
draw_tool: function (scene, bbox) {
|
|
// Tool size is relative to bounds
|
|
const size = bbox.getSize(new THREE.Vector3());
|
|
let length = (size.x + size.y + size.z) / 24;
|
|
|
|
if (length < 1) {
|
|
length = 1;
|
|
}
|
|
|
|
const material = new THREE.MeshPhongMaterial({
|
|
transparent: true,
|
|
opacity: 0.75,
|
|
specular: 0x161616,
|
|
shininess: 10,
|
|
color: 0xffa500 // Orange
|
|
});
|
|
|
|
const geometry = new THREE.CylinderGeometry(length / 2, 0, length, 128);
|
|
geometry.translate(0, length / 2, 0);
|
|
geometry.rotateX(0.5 * Math.PI);
|
|
|
|
const mesh = new THREE.Mesh(geometry, material);
|
|
this.update_tool(mesh);
|
|
mesh.visible = this.showTool;
|
|
scene.add(mesh);
|
|
return mesh;
|
|
},
|
|
|
|
draw_axis: function (axis, up, length, radius) {
|
|
let color;
|
|
|
|
if (axis == 0) {
|
|
color = 0xff0000;
|
|
} else if (axis == 1) {
|
|
color = 0x00ff00;
|
|
} else if (axis == 2) {
|
|
color = 0x0000ff;
|
|
}
|
|
|
|
const group = new THREE.Group();
|
|
const material = new THREE.MeshPhongMaterial({
|
|
specular: 0x161616, shininess: 10, color: color
|
|
});
|
|
let geometry = new THREE.CylinderGeometry(radius, radius, length, 128);
|
|
geometry.translate(0, -length / 2, 0);
|
|
group.add(new THREE.Mesh(geometry, material));
|
|
|
|
geometry = new THREE.CylinderGeometry(1.5 * radius, 0, 2 * radius, 128);
|
|
geometry.translate(0, -length - radius, 0);
|
|
group.add(new THREE.Mesh(geometry, material));
|
|
|
|
if (axis == 0) {
|
|
group.rotateZ((up ? 0.5 : 1.5) * Math.PI);
|
|
} else if (axis == 1) {
|
|
group.rotateX((up ? 0 : 1 ) * Math.PI);
|
|
} else if (axis == 2) {
|
|
group.rotateX((up ? 1.5 : 0.5) * Math.PI);
|
|
}
|
|
|
|
return group;
|
|
},
|
|
|
|
draw_axes: function (scene, bbox) {
|
|
const size = bbox.getSize(new THREE.Vector3());
|
|
let length = (size.x + size.y + size.z) / 3;
|
|
length /= 10;
|
|
|
|
if (length < 1) {
|
|
length = 1;
|
|
}
|
|
|
|
const radius = length / 20;
|
|
|
|
const group = new THREE.Group();
|
|
|
|
for (let axis = 0; axis < 3; axis++) {
|
|
for (let up = 0; up < 2; up++) {
|
|
group.add(this.draw_axis(axis, up, length, radius));
|
|
}
|
|
}
|
|
|
|
group.visible = this.showAxes;
|
|
scene.add(group);
|
|
|
|
return group;
|
|
},
|
|
|
|
get_color: function (speed) {
|
|
if (isNaN(speed)) {
|
|
return [ 255, 0, 0 ];
|
|
} // Rapid
|
|
|
|
let intensity = speed / this.toolpath.maxSpeed;
|
|
if (typeof speed == "undefined" || !this.showIntensity) {
|
|
intensity = 1;
|
|
}
|
|
|
|
return [ 0, 255 * intensity, 127 * (1 - intensity) ];
|
|
},
|
|
|
|
draw_path: function (scene) {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const material = new THREE.LineBasicMaterial({
|
|
vertexColors: THREE.VertexColors,
|
|
linewidth: 1.5
|
|
});
|
|
|
|
const positions = new THREE.Float32BufferAttribute(this.positions, 3);
|
|
geometry.addAttribute("position", positions);
|
|
|
|
let colors = [];
|
|
for (let i = 0; i < this.speeds.length; i++) {
|
|
const color = this.get_color(this.speeds[i]);
|
|
Array.prototype.push.apply(colors, color);
|
|
}
|
|
|
|
colors = new THREE.Uint8BufferAttribute(colors, 3, true);
|
|
geometry.addAttribute("color", colors);
|
|
|
|
geometry.computeBoundingSphere();
|
|
geometry.computeBoundingBox();
|
|
|
|
const line = new THREE.Line(geometry, material);
|
|
|
|
line.visible = this.showPath;
|
|
scene.add(line);
|
|
|
|
return line;
|
|
},
|
|
|
|
create_empty_geom: function () {
|
|
const geometry = new THREE.BufferGeometry();
|
|
geometry.addAttribute("position",
|
|
new THREE.Float32BufferAttribute([], 3));
|
|
return geometry;
|
|
},
|
|
|
|
create_bbox_geom: function (bbox) {
|
|
const vertices = [];
|
|
|
|
if (!bbox.isEmpty()) {
|
|
// Top
|
|
vertices.push(bbox.min.x, bbox.min.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.min.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.min.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.min.y, bbox.max.z);
|
|
vertices.push(bbox.max.x, bbox.min.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.min.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.min.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.min.y, bbox.min.z);
|
|
|
|
// Bottom
|
|
vertices.push(bbox.min.x, bbox.max.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.max.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.max.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.max.y, bbox.max.z);
|
|
vertices.push(bbox.max.x, bbox.max.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.max.y, bbox.min.z);
|
|
|
|
// Sides
|
|
vertices.push(bbox.min.x, bbox.min.y, bbox.min.z);
|
|
vertices.push(bbox.min.x, bbox.max.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.min.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.max.y, bbox.min.z);
|
|
vertices.push(bbox.max.x, bbox.min.y, bbox.max.z);
|
|
vertices.push(bbox.max.x, bbox.max.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.min.y, bbox.max.z);
|
|
vertices.push(bbox.min.x, bbox.max.y, bbox.max.z);
|
|
}
|
|
|
|
const geometry = new THREE.BufferGeometry();
|
|
|
|
geometry.addAttribute("position",
|
|
new THREE.Float32BufferAttribute(vertices, 3));
|
|
|
|
return geometry;
|
|
},
|
|
|
|
draw_bbox: function (scene, bbox) {
|
|
const geometry = this.create_bbox_geom(bbox);
|
|
const material = new THREE.LineBasicMaterial({ color: 0xffffff });
|
|
const line = new THREE.LineSegments(geometry, material);
|
|
|
|
line.visible = this.showBBox;
|
|
|
|
scene.add(line);
|
|
|
|
return line;
|
|
},
|
|
|
|
draw_envelope: function (scene) {
|
|
const geometry = this.create_empty_geom();
|
|
const material = new THREE.LineBasicMaterial({ color: 0x00f7ff });
|
|
const line = new THREE.LineSegments(geometry, material);
|
|
|
|
line.visible = this.showBBox;
|
|
|
|
scene.add(line);
|
|
this.update_envelope(line);
|
|
|
|
return line;
|
|
},
|
|
|
|
draw: function (scene) {
|
|
// Lights
|
|
scene.add(this.ambient);
|
|
scene.add(this.lights);
|
|
|
|
// Model
|
|
this.pathView = this.draw_path(scene);
|
|
this.surfaceMesh = this.draw_surface(scene, this.surfaceMaterial);
|
|
this.workpieceMesh = this.draw_workpiece(scene, this.surfaceMaterial);
|
|
this.update_surface_mode(this.surfaceMode);
|
|
|
|
// Compute bounding box
|
|
const bbox = this.get_model_bounds();
|
|
|
|
// Tool, axes & bounds
|
|
this.toolView = this.draw_tool(scene, bbox);
|
|
this.axesView = this.draw_axes(scene, bbox);
|
|
this.bboxView = this.draw_bbox(scene, bbox);
|
|
this.envelopeView = this.draw_envelope(scene);
|
|
},
|
|
|
|
render: function () {
|
|
window.requestAnimationFrame(this.render);
|
|
|
|
if (typeof this.scene == "undefined") {
|
|
return;
|
|
}
|
|
|
|
if (this.controls.update() || this.dirty) {
|
|
this.dirty = false;
|
|
this.renderer.render(this.scene, this.camera);
|
|
}
|
|
},
|
|
|
|
get_model_bounds: function () {
|
|
const bbox = new THREE.Box3(new THREE.Vector3(0, 0, 0),
|
|
new THREE.Vector3(0.00001, 0.00001, 0.00001));
|
|
|
|
function add(o) {
|
|
if (typeof o != "undefined") {
|
|
const oBBox = new THREE.Box3();
|
|
oBBox.setFromObject(o);
|
|
bbox.union(oBBox);
|
|
}
|
|
}
|
|
|
|
add(this.pathView);
|
|
add(this.surfaceMesh);
|
|
add(this.workpieceMesh);
|
|
|
|
return bbox;
|
|
},
|
|
|
|
snap: function (view) {
|
|
if (this.loading) {
|
|
return;
|
|
}
|
|
|
|
if (view != this.snapView) {
|
|
this.snapView = view;
|
|
cookie.set("snap-view", view);
|
|
}
|
|
|
|
const bbox = this.get_model_bounds();
|
|
this.controls.reset();
|
|
bbox.getCenter(this.controls.target);
|
|
this.update_view();
|
|
|
|
// Compute new camera position
|
|
const center = bbox.getCenter(new THREE.Vector3());
|
|
const offset = new THREE.Vector3();
|
|
|
|
switch (view) {
|
|
case "isometric": offset.y -= 1; offset.z += 1; break;
|
|
case "front": offset.y -= 1; break;
|
|
case "back": offset.y += 1; break;
|
|
case "left": offset.x -= 1; break;
|
|
case "right": offset.x += 1; break;
|
|
case "top": offset.z += 1; break;
|
|
case "bottom": offset.z -= 1; break;
|
|
}
|
|
|
|
offset.normalize();
|
|
|
|
// Initial camera position
|
|
const position = new THREE.Vector3().copy(center).add(offset);
|
|
this.camera.position.copy(position);
|
|
this.camera.lookAt(center); // Get correct camera orientation
|
|
|
|
const theta = this.camera.fov / 180 * Math.PI; // View angle
|
|
const cameraLine = new THREE.Line3(center, position);
|
|
const cameraUp = new THREE.Vector3()
|
|
.copy(this.camera.up)
|
|
.applyQuaternion(this.camera.quaternion);
|
|
const cameraLeft = new THREE.Vector3()
|
|
.copy(offset)
|
|
.cross(cameraUp)
|
|
.normalize();
|
|
|
|
const corners = [
|
|
new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z),
|
|
new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.max.z),
|
|
new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.min.z),
|
|
new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.max.z),
|
|
new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.min.z),
|
|
new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.z),
|
|
new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.min.z),
|
|
new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.max.z),
|
|
];
|
|
|
|
let dist = this.camera.near; // Min camera dist
|
|
|
|
for (let i = 0; i < corners.length; i++) {
|
|
// Project on to camera line
|
|
const p1 = cameraLine.closestPointToPoint(corners[i], false, new THREE.Vector3());
|
|
|
|
// Compute distance from projection to center
|
|
let d = p1.distanceTo(center);
|
|
if (cameraLine.closestPointToPointParameter(p1, false) < 0) {
|
|
d = -d;
|
|
}
|
|
|
|
// Compute up line
|
|
const up = new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraUp));
|
|
|
|
// Project on to up line
|
|
const p2 = up.closestPointToPoint(corners[i], false, new THREE.Vector3());
|
|
|
|
// Compute length
|
|
let l = p1.distanceTo(p2);
|
|
|
|
// Update min camera distance
|
|
dist = Math.max(dist, d + l / Math.tan(theta / 2));
|
|
|
|
// Compute left line
|
|
const left = new THREE.Line3(p1, new THREE.Vector3().copy(p1).add(cameraLeft));
|
|
|
|
// Project on to left line
|
|
const p3 = left.closestPointToPoint(corners[i], false, new THREE.Vector3());
|
|
|
|
// Compute length
|
|
l = p1.distanceTo(p3);
|
|
|
|
// Update min camera distance
|
|
dist = Math.max(dist, d + l / Math.tan(theta / 2) / this.camera.aspect);
|
|
}
|
|
|
|
this.camera.position.copy(offset.multiplyScalar(dist * 1.2).add(center));
|
|
}
|
|
},
|
|
|
|
mixins: [ require("./axis-vars") ]
|
|
};
|