修复喷头拆解视图裁切并验证完整部件取景
This commit is contained in:
parent
128152ead4
commit
b40c21bb53
@ -65,6 +65,9 @@ try {
|
||||
[1440, 900],
|
||||
[1280, 720],
|
||||
[1920, 1080],
|
||||
[2048, 990],
|
||||
[2560, 1238],
|
||||
[1366, 768],
|
||||
[390, 844],
|
||||
]) {
|
||||
await page.setViewportSize({ width, height });
|
||||
@ -75,6 +78,7 @@ try {
|
||||
width: document.documentElement.scrollWidth,
|
||||
height: document.documentElement.scrollHeight,
|
||||
scene: rect(document.querySelector("#scene")),
|
||||
parts: JSON.parse(document.querySelector("#scene").dataset.nozzleBounds),
|
||||
labels: [...document.querySelectorAll(".nozzle-label")].map(rect),
|
||||
panel: rect(document.querySelector('[data-panel="nozzle"]')),
|
||||
foot: rect(
|
||||
@ -83,6 +87,14 @@ try {
|
||||
};
|
||||
});
|
||||
assert.ok(layout.width <= width);
|
||||
assert.equal(layout.parts.length, 12);
|
||||
layout.parts.forEach((part, index) => {
|
||||
assert.ok(part.top >= 20 && part.bottom <= layout.scene.height - 28,
|
||||
`${width} 部件 ${index + 1} 上下未裁切:${JSON.stringify(part)}`);
|
||||
const inset = (layout.scene.width < 550 ? 90 : 144) + 8;
|
||||
assert.ok(part.left >= inset && part.right <= layout.scene.width - inset,
|
||||
`${width} 部件 ${index + 1} 不被标签遮挡`);
|
||||
});
|
||||
if (width >= 1100) {
|
||||
assert.ok(layout.height <= height);
|
||||
assert.ok(layout.foot.bottom <= layout.panel.bottom + 1);
|
||||
@ -111,6 +123,7 @@ try {
|
||||
height,
|
||||
scene: layout.scene,
|
||||
labels: layout.labels.length,
|
||||
parts: layout.parts,
|
||||
});
|
||||
}
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
|
||||
41
src/views/Monitor/NozzleFraming.js
Normal file
41
src/views/Monitor/NozzleFraming.js
Normal file
@ -0,0 +1,41 @@
|
||||
import * as THREE from "three";
|
||||
|
||||
export function boxCorners(box) {
|
||||
return [box.min.x, box.max.x].flatMap((x) =>
|
||||
[box.min.y, box.max.y].flatMap((y) =>
|
||||
[box.min.z, box.max.z].map((z) => new THREE.Vector3(x, y, z)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 将部件包围盒全部纳入透视视锥,并留出两侧标签与底部说明空间。
|
||||
export function fitNozzleCamera(camera, controls, boxes, width, height) {
|
||||
const bounds = boxes.reduce((result, box) => result.union(box), new THREE.Box3());
|
||||
const center = bounds.getCenter(new THREE.Vector3());
|
||||
const backward = camera.position.clone().sub(controls.target).normalize();
|
||||
const right = new THREE.Vector3().crossVectors(camera.up, backward).normalize();
|
||||
const up = new THREE.Vector3().crossVectors(backward, right).normalize();
|
||||
const labelWidth = width < 550 ? 90 : 144;
|
||||
const horizontal = Math.max(0.15, 1 - (2 * (labelWidth + 22)) / width);
|
||||
const vertical = Math.max(0.2, 1 - 80 / height);
|
||||
const tangent = Math.tan(THREE.MathUtils.degToRad(camera.getEffectiveFOV() / 2));
|
||||
let distance = controls.minDistance;
|
||||
for (const box of boxes) {
|
||||
for (const point of boxCorners(box)) {
|
||||
const relative = point.sub(center);
|
||||
const depth = relative.dot(backward);
|
||||
distance = Math.max(distance,
|
||||
depth + Math.abs(relative.dot(right)) / (tangent * camera.aspect * horizontal),
|
||||
depth + Math.abs(relative.dot(up)) / (tangent * vertical));
|
||||
}
|
||||
}
|
||||
distance *= 1.04;
|
||||
controls.maxDistance = Math.max(1000, distance * 2);
|
||||
controls.target.copy(center);
|
||||
camera.position.copy(center).addScaledVector(backward, distance);
|
||||
camera.far = Math.max(1600, distance * 4);
|
||||
camera.updateProjectionMatrix();
|
||||
camera.lookAt(center);
|
||||
controls.update();
|
||||
camera.updateMatrixWorld(true);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import * as THREE from "three";
|
||||
import "./Nozzle.css";
|
||||
import { boxCorners, fitNozzleCamera } from "./NozzleFraming.js";
|
||||
|
||||
const modules = [
|
||||
{
|
||||
@ -116,6 +117,10 @@ export class NozzleScene {
|
||||
return group;
|
||||
});
|
||||
this.build();
|
||||
this.partBounds = this.parts.map((part) => {
|
||||
const box = new THREE.Box3().setFromObject(part);
|
||||
return box.translate(part.position.clone().negate());
|
||||
});
|
||||
this.overlay = document.createElement("div");
|
||||
this.overlay.className = "nozzle-annotations";
|
||||
this.overlay.hidden = true;
|
||||
@ -287,30 +292,33 @@ export class NozzleScene {
|
||||
}
|
||||
|
||||
focus() {
|
||||
const { camera, controls } = this.owner;
|
||||
camera.position.copy(controls.target).add(new THREE.Vector3(0.32, 0.42, 1).multiplyScalar(340));
|
||||
this.fit();
|
||||
}
|
||||
|
||||
boxes() {
|
||||
this.group.updateMatrixWorld(true);
|
||||
return this.parts.map((part, index) => this.partBounds[index].clone().applyMatrix4(part.matrixWorld));
|
||||
}
|
||||
|
||||
fit() {
|
||||
const { camera, controls, container } = this.owner;
|
||||
const narrow = container.clientWidth < 550;
|
||||
const available = Math.max(
|
||||
0.3,
|
||||
(container.clientWidth - (narrow ? 180 : 300)) / container.clientWidth,
|
||||
);
|
||||
const distance = Math.max(340, 170 / (camera.aspect * available * 0.63));
|
||||
controls.target.set(0, 22, 0);
|
||||
camera.position.copy(
|
||||
new THREE.Vector3(0.32, 0.42, 1)
|
||||
.normalize()
|
||||
.multiplyScalar(distance)
|
||||
.add(controls.target),
|
||||
);
|
||||
controls.update();
|
||||
if (!container.clientWidth || !container.clientHeight) return;
|
||||
fitNozzleCamera(camera, controls, this.boxes(), container.clientWidth, container.clientHeight);
|
||||
}
|
||||
|
||||
update(disassembled) {
|
||||
this.amount += ((disassembled ? 1 : 0) - this.amount) * 0.09;
|
||||
const target = disassembled ? 1 : 0;
|
||||
const changing = Math.abs(target - this.amount) > 0.0001;
|
||||
this.amount = changing ? this.amount + (target - this.amount) * 0.09 : target;
|
||||
this.parts.forEach((part, i) => {
|
||||
part.position
|
||||
.fromArray(modules[i].base)
|
||||
.addScaledVector(new THREE.Vector3(...modules[i].offset), this.amount);
|
||||
});
|
||||
if (changing || this.lastDisassembled !== disassembled) this.fit();
|
||||
this.lastDisassembled = disassembled;
|
||||
this.owner.container.dataset.nozzleExplode = this.amount.toFixed(3);
|
||||
this.annotate();
|
||||
}
|
||||
@ -328,6 +336,16 @@ export class NozzleScene {
|
||||
labelWidth = narrow ? 90 : 144;
|
||||
const slots = [[], []];
|
||||
this.group.updateMatrixWorld(true);
|
||||
camera.updateMatrixWorld(true);
|
||||
this.owner.container.dataset.nozzleBounds = JSON.stringify(this.boxes().map((box) => {
|
||||
const points = boxCorners(box).map((point) => point.project(camera));
|
||||
return {
|
||||
left: Math.min(...points.map((point) => (point.x + 1) * w / 2)),
|
||||
right: Math.max(...points.map((point) => (point.x + 1) * w / 2)),
|
||||
top: Math.min(...points.map((point) => (1 - point.y) * h / 2)),
|
||||
bottom: Math.max(...points.map((point) => (1 - point.y) * h / 2)),
|
||||
};
|
||||
}));
|
||||
this.parts.forEach((part, i) => {
|
||||
const p = part.getWorldPosition(new THREE.Vector3()).project(camera);
|
||||
slots[i % 2].push({ i, x: ((p.x + 1) * w) / 2, y: ((1 - p.y) * h) / 2 });
|
||||
|
||||
Loading…
Reference in New Issue
Block a user