feat: test
This commit is contained in:
parent
2ec9c401ba
commit
3d1afd7a18
|
@ -23,11 +23,13 @@
|
|||
"js-cookie": "3.0.5",
|
||||
"three": "0.156.1",
|
||||
"path-to-regexp": "6.2.1",
|
||||
"@cornerstonejs/core": "1.16.5",
|
||||
"cornerstone-core": "2.6.1",
|
||||
"dicom-parser": "1.8.21",
|
||||
"cornerstone-wado-image-loader": "4.13.2",
|
||||
"@msgpack/msgpack": "3.0.0-beta2",
|
||||
"pako": "2.1.0"
|
||||
"pako": "2.1.0",
|
||||
"@kitware/vtk.js": "29.2.0",
|
||||
"@cornerstonejs/dicom-image-loader": "1.41.0",
|
||||
"cornerstone-wado-image-loader": "4.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.21.8",
|
||||
|
|
|
@ -40,11 +40,6 @@ const baseConfig: Configuration = {
|
|||
"@": path.join(__dirname, "../src"),
|
||||
"@@": path.join(__dirname, "../core"),
|
||||
},
|
||||
// fallback: {
|
||||
// crypto: require.resolve('crypto-browserify'),
|
||||
// buffer: require.resolve("buffer/"),
|
||||
// http: require.resolve("stream-http"),
|
||||
// },
|
||||
},
|
||||
// plugins
|
||||
plugins: [
|
||||
|
|
|
@ -83,8 +83,8 @@ export const LoginForm = (props: LoginFormProps) => {
|
|||
rules={[
|
||||
{
|
||||
required: true,
|
||||
pattern: REG.password,
|
||||
min: 6,
|
||||
// pattern: REG.password,
|
||||
// min: 6,
|
||||
max: 20,
|
||||
message: "6-20位,包含 大写、小写、字母、特殊字符",
|
||||
},
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
min-height: 6rem;
|
||||
cursor: pointer;
|
||||
img{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.active {
|
||||
&::after {
|
||||
|
|
|
@ -38,18 +38,20 @@ export const ImageItem = (props: ImageItemProps) => {
|
|||
|
||||
const handleFileChange = (event: any) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const fd = new FormData();
|
||||
fd.append("file", event.target.files[0]);
|
||||
axios
|
||||
.post("/api/report/upload", fd, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
})
|
||||
.then((res) => {
|
||||
const { objectName } = res.data;
|
||||
setImgSrc(`/api/report/img/${objectName}`);
|
||||
});
|
||||
|
||||
|
||||
// const fd = new FormData();
|
||||
// fd.append("file", event.target.files[0]);
|
||||
// axios
|
||||
// .post("/api/report/upload", fd, {
|
||||
// headers: { "Content-Type": "multipart/form-data" },
|
||||
// })
|
||||
// .then((res) => {
|
||||
// const { objectName } = res.data;
|
||||
// setImgSrc(`/api/report/img/${objectName}`);
|
||||
// });
|
||||
//TODO API接口上传,THEN
|
||||
setImgSrc(event.target.result);
|
||||
setImgSrc(URL.createObjectURL(event.target.files[0]));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -63,8 +65,10 @@ export const ImageItem = (props: ImageItemProps) => {
|
|||
onClick={() => setActionVisible(true)}
|
||||
className={`img ${actionVisible ? "active" : ""}`}
|
||||
ref={imgRef}
|
||||
style={{ ...props.imgStyle, backgroundImage: `url(${imgSrc})` }}
|
||||
></div>
|
||||
style={{ ...props.imgStyle }}
|
||||
>
|
||||
<img src={imgSrc} />
|
||||
</div>
|
||||
)}
|
||||
{actionVisible && (
|
||||
<section ref={actionRef}>
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
.page-head {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
line-height: 1;
|
||||
padding-bottom: 0.57rem;
|
||||
border-bottom: 0.07rem solid var(--color-bg-primary);
|
||||
margin-bottom: 2rem;
|
||||
|
||||
>span{
|
||||
color: red;
|
||||
}
|
||||
|
||||
.logo-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ interface PaperHeadProps {
|
|||
export const PaperHead = (props: PaperHeadProps) => {
|
||||
return (
|
||||
<div className="page-head">
|
||||
<span>本报告为demo版本,仅供参考,可提供定制</span>
|
||||
<div className="logo-group">
|
||||
<svg
|
||||
viewBox="0 0 45 47"
|
||||
|
|
|
@ -31,14 +31,14 @@ interface DoctorProps {
|
|||
}
|
||||
|
||||
export const ReportFullVersion = (props: DoctorProps) => {
|
||||
const [date, totalPageNum] = ["2023-06-14", 21];
|
||||
const [date, totalPageNum] = ["2023-12-12", 18];
|
||||
const [completed, setCompleted] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get("/api/aorta/report/root").then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
}, []);
|
||||
// useEffect(() => {
|
||||
// axios.get("/api/aorta/report/root").then((res) => {
|
||||
// console.log(res);
|
||||
// });
|
||||
// }, []);
|
||||
|
||||
/**
|
||||
* 下载pdf
|
||||
|
|
|
@ -48,7 +48,6 @@ export const LeftCoronarySinus = (props: LeftCoronarySinusProps) => {
|
|||
<ImageItem
|
||||
title="左冠窦瓣叶长度"
|
||||
src={src}
|
||||
imgStyle={{ height: "6.43rem" }}
|
||||
/>
|
||||
<Reference
|
||||
style={{ position: "absolute", bottom: "3rem" }}
|
||||
|
|
|
@ -31,7 +31,6 @@ export const NoCoronarySinus = (props: NoCoronarySinusProps) => {
|
|||
<ImageItem
|
||||
title="无冠窦瓣叶长度"
|
||||
src={src}
|
||||
imgStyle={{ height: "6.43rem" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -32,7 +32,6 @@ export const RightCoronarySinus = (props: RightCoronarySinusProps) => {
|
|||
<ImageItem
|
||||
title="左冠窦瓣叶长度"
|
||||
src={src}
|
||||
imgStyle={{ height: "6.43rem" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
133
apps/aorta/src/modules/Root/Viewer/MprViewer/index.tsx
Normal file
133
apps/aorta/src/modules/Root/Viewer/MprViewer/index.tsx
Normal file
|
@ -0,0 +1,133 @@
|
|||
import { relative } from "path";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import * as THREE from "three";
|
||||
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls";
|
||||
|
||||
type ThreeJSComponentProps = {
|
||||
// 你可以在这里添加任何需要的props
|
||||
};
|
||||
|
||||
export const MprViewer: React.FC<ThreeJSComponentProps> = () => {
|
||||
const mountRef = useRef<HTMLDivElement>(null);
|
||||
const [imageSrc, setImageSrc] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
const width = window.innerWidth - 300;
|
||||
const height = window.innerHeight;
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x808080);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(width, height);
|
||||
|
||||
// 添加一个可移动和旋转的平面
|
||||
const planeGeometry = new THREE.PlaneGeometry(200, 200);
|
||||
const planeMaterial = new THREE.MeshBasicMaterial({
|
||||
color: 0xffff00,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
|
||||
plane.position.set(0, 0, 0); // 初始位置
|
||||
scene.add(plane);
|
||||
|
||||
// 创建一个与平面对齐的剪裁平面
|
||||
const clipPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
|
||||
renderer.clippingPlanes = [clipPlane];
|
||||
|
||||
// 使剪裁平面跟随你的移动平面
|
||||
const updateClipPlane = () => {
|
||||
clipPlane.normal.copy(plane.position).normalize();
|
||||
};
|
||||
|
||||
// 添加多个3D物体
|
||||
const geometry1 = new THREE.BoxGeometry(100, 100, 100);
|
||||
const material1 = new THREE.MeshPhongMaterial({
|
||||
color: 0x00ff00,
|
||||
transparent: true,
|
||||
opacity: 0.5,
|
||||
});
|
||||
const cube = new THREE.Mesh(geometry1, material1);
|
||||
cube.position.x = -50; // 调整位置以确保重叠
|
||||
scene.add(cube);
|
||||
|
||||
const geometry2 = new THREE.SphereGeometry(50, 32, 32);
|
||||
const material2 = new THREE.MeshPhongMaterial({
|
||||
color: 0xff0000,
|
||||
transparent: true,
|
||||
opacity: 0.5,
|
||||
});
|
||||
const sphere = new THREE.Mesh(geometry2, material2);
|
||||
sphere.position.x = 0; // 调整位置以确保重叠
|
||||
scene.add(sphere);
|
||||
|
||||
const geometry3 = new THREE.ConeGeometry(50, 100, 32);
|
||||
const material3 = new THREE.MeshPhongMaterial({
|
||||
color: 0x0000ff,
|
||||
transparent: true,
|
||||
opacity: 0.5,
|
||||
});
|
||||
const cone = new THREE.Mesh(geometry3, material3);
|
||||
cone.position.x = 50; // 调整位置以确保重叠
|
||||
scene.add(cone);
|
||||
|
||||
camera.position.set(0, 0, 500);
|
||||
camera.lookAt(scene.position);
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const mount = mountRef.current;
|
||||
if (mount) {
|
||||
mount.appendChild(renderer.domElement);
|
||||
}
|
||||
|
||||
const controls = new TrackballControls(camera, renderer.domElement);
|
||||
|
||||
const animate = () => {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update(); // 只有在使用 TrackballControls 时才需要
|
||||
// camera.position.y += 5;
|
||||
camera.updateMatrixWorld();
|
||||
|
||||
updateClipPlane(); // 更新剪裁平面的位置和方向
|
||||
|
||||
|
||||
renderer.render(scene, camera);
|
||||
|
||||
// 从渲染器的 canvas 中获取图像
|
||||
const canvas = renderer.domElement;
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const imageUrl = URL.createObjectURL(blob);
|
||||
setImageSrc(imageUrl);
|
||||
}
|
||||
}, "image/jpeg");
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
return () => {
|
||||
controls.dispose();
|
||||
mount?.removeChild(renderer.domElement);
|
||||
URL.revokeObjectURL(imageSrc);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<div ref={mountRef} />
|
||||
<img
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
}}
|
||||
src={imageSrc}
|
||||
alt="Rendered Scene"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
63
apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx
Normal file
63
apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import axios from "axios";
|
||||
import { useEffect, useRef } from "react";
|
||||
import * as cornerstone from "cornerstone-core";
|
||||
import dicomParser from "dicom-parser";
|
||||
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
|
||||
|
||||
interface VolumeViewerProps {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
const StudyInstanceUID =
|
||||
"1.2.840.113619.2.416.200043823236217877797891016883696407563"; // 你的 studyInstanceUID
|
||||
const SeriesInstanceUID =
|
||||
"1.2.840.113619.6.80.114374075765625.22940.1553237925965.1"; // 你的 seriesInstanceUID
|
||||
|
||||
export const VolumeViewer = (props: VolumeViewerProps) => {
|
||||
const dicomRef = useRef(null);
|
||||
const url = `/dicom-web/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances`;
|
||||
|
||||
useEffect(() => {
|
||||
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
|
||||
cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
|
||||
|
||||
// cornerstoneWADOImageLoader.configure({ useWebWorkers: true });
|
||||
|
||||
const config = {
|
||||
maxWebWorkers: navigator.hardwareConcurrency || 1,
|
||||
startWebWorkersOnDemand: true,
|
||||
taskConfiguration: {
|
||||
decodeTask: {
|
||||
initializeCodecsOnStartup: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
cornerstoneDICOMImageLoader.webWorkerManager.initialize(config);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
cornerstone.enable(dicomRef.current!);
|
||||
axios
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
const objectUIDs = response.data.map(
|
||||
(i: any) => i["00080018"].Value[0]
|
||||
);
|
||||
objectUIDs.forEach((i: any) => {
|
||||
const imageId = `wadors:/dicom-web/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${i}/frames/1`;
|
||||
cornerstone.loadImage(imageId).then((image: any) => {
|
||||
cornerstone.displayImage(dicomRef.current!, image);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error fetching instances:", error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={dicomRef} style={{ width: 200, height: 200 }}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,6 @@
|
|||
import { DiffViewer } from "./DiffViewer";
|
||||
import { MprViewer } from "./MprViewer";
|
||||
import { VolumeViewer } from "./VolumeViewer";
|
||||
|
||||
interface RootViewerProps {
|
||||
children?: JSX.Element;
|
||||
|
@ -7,7 +9,7 @@ interface RootViewerProps {
|
|||
export const RootViewer = (props: RootViewerProps) => {
|
||||
return (
|
||||
<div>
|
||||
<DiffViewer />
|
||||
<VolumeViewer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||
import {
|
||||
autoScale,
|
||||
createEnclosedGeometry,
|
||||
getOffsetTriangles,
|
||||
getTriangleVertexs,
|
||||
} from "./util";
|
||||
|
||||
export const STLViewer: React.FC = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
const width = containerRef.current.clientWidth;
|
||||
const height = containerRef.current.clientHeight;
|
||||
|
||||
// 创建场景、相机和渲染器
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
||||
camera.position.z = 5;
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(width, height);
|
||||
containerRef.current.appendChild(renderer.domElement);
|
||||
|
||||
// 加载STL文件
|
||||
const loader = new STLLoader();
|
||||
loader.load("/1.stl", (geometry) => {
|
||||
const s = autoScale(geometry);
|
||||
// geometry.center();
|
||||
// const material = new THREE.MeshPhongMaterial({
|
||||
// color: "lightgrey", // 使用更亮的颜色
|
||||
// shininess: 100, // 增加亮度
|
||||
// specular: 0x222222, // 调整镜面高光的颜色
|
||||
// // side: THREE.DoubleSide,
|
||||
// // wireframe: true,
|
||||
// });
|
||||
// const mesh = new THREE.Mesh(geometry, material);
|
||||
// mesh.scale.set(scale, scale, scale);
|
||||
// scene.add(mesh);
|
||||
|
||||
// 输出三角形顶点信息
|
||||
const triangles = getTriangleVertexs(geometry);
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
const newTriangles = getOffsetTriangles(triangles, -2);
|
||||
|
||||
console.log("newTriangles", newTriangles);
|
||||
|
||||
// 使用上面定义的函数
|
||||
const enclosedGeometry = createEnclosedGeometry(
|
||||
triangles,
|
||||
newTriangles
|
||||
);
|
||||
|
||||
enclosedGeometry.center();
|
||||
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: "lightgrey",
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(enclosedGeometry, material);
|
||||
mesh.scale.set(s, s, s);
|
||||
scene.add(mesh);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
});
|
||||
|
||||
// 添加环境光源
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff, 1); // 第二个参数为强度
|
||||
scene.add(ambientLight);
|
||||
|
||||
// 添加方向光源
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
|
||||
directionalLight.position.set(1, 1, 1).normalize();
|
||||
scene.add(directionalLight);
|
||||
|
||||
// 添加XYZ轴
|
||||
const axesHelper = new THREE.AxesHelper(2);
|
||||
scene.add(axesHelper);
|
||||
|
||||
// 添加OrbitControls
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
|
||||
// 渲染函数
|
||||
const animate = () => {
|
||||
requestAnimationFrame(animate);
|
||||
renderer.render(scene, camera);
|
||||
};
|
||||
|
||||
animate();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return <div ref={containerRef} style={{ width: "100vw", height: "100vh" }} />;
|
||||
};
|
|
@ -1,13 +0,0 @@
|
|||
import { STLViewer } from "./STLViewer";
|
||||
|
||||
interface RootViewerProps {
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
export const RootViewer = (props: RootViewerProps) => {
|
||||
return (
|
||||
<div>
|
||||
123
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,202 +0,0 @@
|
|||
import * as THREE from "three";
|
||||
|
||||
export const autoScale = (geometry: THREE.BufferGeometry) => {
|
||||
// 计算模型的边界
|
||||
geometry.computeBoundingBox();
|
||||
// 获取模型的大小
|
||||
const modelSize = geometry.boundingBox!.getSize(new THREE.Vector3());
|
||||
const maxSize = Math.max(modelSize.x, modelSize.y, modelSize.z);
|
||||
// 根据模型的大小设置一个缩放因子
|
||||
const scaleFactor = 2 / maxSize; // 假设我们希望模型最大为2单位
|
||||
return scaleFactor;
|
||||
};
|
||||
|
||||
export const getTriangleVertexs = (
|
||||
geometry: THREE.BufferGeometry
|
||||
): THREE.Vector3[][] => {
|
||||
const positions = geometry.attributes.position;
|
||||
const triangles: THREE.Vector3[][] = [];
|
||||
|
||||
const getTriangleVertices = (
|
||||
index1: number,
|
||||
index2: number,
|
||||
index3: number
|
||||
) => [
|
||||
new THREE.Vector3(
|
||||
positions.getX(index1),
|
||||
positions.getY(index1),
|
||||
positions.getZ(index1)
|
||||
),
|
||||
new THREE.Vector3(
|
||||
positions.getX(index2),
|
||||
positions.getY(index2),
|
||||
positions.getZ(index2)
|
||||
),
|
||||
new THREE.Vector3(
|
||||
positions.getX(index3),
|
||||
positions.getY(index3),
|
||||
positions.getZ(index3)
|
||||
),
|
||||
];
|
||||
|
||||
if (geometry.index) {
|
||||
const indices = geometry.index.array;
|
||||
for (let i = 0; i < indices.length; i += 3) {
|
||||
triangles.push(
|
||||
getTriangleVertices(indices[i], indices[i + 1], indices[i + 2])
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < positions.count; i += 3) {
|
||||
triangles.push(getTriangleVertices(i, i + 1, i + 2));
|
||||
}
|
||||
}
|
||||
|
||||
return triangles;
|
||||
};
|
||||
|
||||
export const getOffsetTriangles = (
|
||||
triangles: THREE.Vector3[][],
|
||||
offset: number
|
||||
): THREE.Vector3[][] => {
|
||||
const newTriangles: THREE.Vector3[][] = [];
|
||||
|
||||
triangles.forEach((triangle) => {
|
||||
// 计算三角形的法线
|
||||
const normal = new THREE.Vector3();
|
||||
const edge1 = new THREE.Vector3().subVectors(triangle[1], triangle[0]);
|
||||
const edge2 = new THREE.Vector3().subVectors(triangle[2], triangle[0]);
|
||||
normal.crossVectors(edge1, edge2).normalize();
|
||||
|
||||
// 使用法线和偏移量计算新的三角形顶点
|
||||
const offsetNormal = normal.clone().multiplyScalar(offset);
|
||||
const newTriangle = triangle.map((vertex) =>
|
||||
vertex.clone().sub(offsetNormal)
|
||||
);
|
||||
newTriangles.push(newTriangle);
|
||||
});
|
||||
|
||||
return newTriangles;
|
||||
};
|
||||
|
||||
export const createEnclosedGeometry = (
|
||||
triangles: THREE.Vector3[][],
|
||||
newTriangles: THREE.Vector3[][]
|
||||
): THREE.BufferGeometry => {
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const vertices: THREE.Vector3[] = [];
|
||||
const indices: number[] = [];
|
||||
|
||||
const processedEdges: Set<string> = new Set();
|
||||
|
||||
function edgeId(v1: THREE.Vector3, v2: THREE.Vector3): string {
|
||||
return `${v1.x},${v1.y},${v1.z}->${v2.x},${v2.y},${v2.z}`;
|
||||
}
|
||||
|
||||
triangles.forEach((triangle, index) => {
|
||||
const innerTriangle = newTriangles[index];
|
||||
|
||||
triangle.forEach((vertex) => {
|
||||
vertices.push(vertex);
|
||||
});
|
||||
|
||||
innerTriangle.forEach((vertex) => {
|
||||
vertices.push(vertex);
|
||||
});
|
||||
|
||||
// Process the three edges for each triangle
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const next = (i + 1) % 3;
|
||||
const edge = edgeId(triangle[i], triangle[next]);
|
||||
|
||||
if (!processedEdges.has(edge)) {
|
||||
processedEdges.add(edge);
|
||||
|
||||
// Add the vertices for the quad (two triangles)
|
||||
const quadVertices = [
|
||||
triangle[i],
|
||||
triangle[next],
|
||||
innerTriangle[i],
|
||||
innerTriangle[next],
|
||||
];
|
||||
|
||||
quadVertices.forEach((vertex) => {
|
||||
vertices.push(vertex);
|
||||
});
|
||||
|
||||
const baseIndex = vertices.length - 4;
|
||||
|
||||
// First triangle of the quad
|
||||
indices.push(baseIndex, baseIndex + 2, baseIndex + 1);
|
||||
// Second triangle of the quad
|
||||
indices.push(baseIndex + 1, baseIndex + 2, baseIndex + 3);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
geometry.setFromPoints(vertices);
|
||||
geometry.setIndex(indices);
|
||||
|
||||
return geometry;
|
||||
};
|
||||
|
||||
// export const createEnclosedGeometry = (
|
||||
// triangles: THREE.Vector3[][],
|
||||
// newTriangles: THREE.Vector3[][]
|
||||
// ): THREE.BufferGeometry => {
|
||||
// const geometry = new THREE.BufferGeometry();
|
||||
|
||||
// const vertices: number[] = [];
|
||||
// const indices: number[] = [];
|
||||
|
||||
// // 添加原始三角形和新三角形的顶点到vertices数组
|
||||
// triangles.forEach((triangle) => {
|
||||
// triangle.forEach((v) => {
|
||||
// vertices.push(v.x, v.y, v.z);
|
||||
// });
|
||||
// });
|
||||
|
||||
// newTriangles.forEach((triangle) => {
|
||||
// triangle.forEach((v) => {
|
||||
// vertices.push(v.x, v.y, v.z);
|
||||
// });
|
||||
// });
|
||||
|
||||
// const totalTriangles = triangles.length;
|
||||
|
||||
// // 创建索引来连接triangles和newTriangles中的三角形
|
||||
// for (let i = 0; i < totalTriangles; i++) {
|
||||
// const offset = totalTriangles * 3; // newTriangles在vertices中的偏移量
|
||||
|
||||
// const a1 = i * 3;
|
||||
// const b1 = a1 + 1;
|
||||
// const c1 = a1 + 2;
|
||||
|
||||
// const a2 = offset + i * 3;
|
||||
// const b2 = a2 + 1;
|
||||
// const c2 = a2 + 2;
|
||||
|
||||
// // 添加原始和新三角形的索引
|
||||
// indices.push(a1, b1, c1);
|
||||
// indices.push(a2, c2, b2);
|
||||
|
||||
// // 为每一对相邻的三角形添加边的索引
|
||||
// indices.push(a1, b1, a2);
|
||||
// indices.push(b1, b2, a2);
|
||||
|
||||
// indices.push(b1, c1, b2);
|
||||
// indices.push(c1, c2, b2);
|
||||
|
||||
// indices.push(c1, a1, c2);
|
||||
// indices.push(a1, a2, c2);
|
||||
// }
|
||||
|
||||
// geometry.setIndex(indices);
|
||||
// geometry.setAttribute(
|
||||
// "position",
|
||||
// new THREE.Float32BufferAttribute(vertices, 3)
|
||||
// );
|
||||
// geometry.computeVertexNormals(); // 计算顶点法线
|
||||
|
||||
// return geometry;
|
||||
// };
|
|
@ -17,6 +17,10 @@ export const baseRoutes: (RouteObject & ExpandRouteProps)[] = [
|
|||
element: <Login />,
|
||||
title: "登录 - CVPILOT Viewer",
|
||||
},
|
||||
{
|
||||
path: "root/report/full",
|
||||
element: <ReportFullVersion />,
|
||||
},
|
||||
];
|
||||
|
||||
export const commonRoutes: (RouteObject & ExpandRouteProps)[] = [
|
||||
|
@ -41,10 +45,7 @@ export const commonRoutes: (RouteObject & ExpandRouteProps)[] = [
|
|||
path: "root/viewer",
|
||||
element: <RootViewer />,
|
||||
},
|
||||
{
|
||||
path: "root/report/full",
|
||||
element: <ReportFullVersion />,
|
||||
},
|
||||
|
||||
{
|
||||
path: "peripheral/viewer",
|
||||
element: <PeripheralViewer />,
|
||||
|
|
|
@ -21,10 +21,11 @@ export class AuthController {
|
|||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
const { username, password, phoneNumber } = userLoginDto;
|
||||
// TODO: isEnabled、phoneNumber,用于后续验证
|
||||
//* Step1: 询问认证中心服务,用户是否合法
|
||||
const { isLegal, data, msg } = await firstValueFrom(
|
||||
this.client.send('cert.user.account', { username, password }),
|
||||
);
|
||||
//* Step2: 上报登录日志
|
||||
await firstValueFrom(
|
||||
this.client.send('logger.user.signIn', {
|
||||
platform: 'dmp',
|
||||
|
@ -35,10 +36,11 @@ export class AuthController {
|
|||
}),
|
||||
);
|
||||
if (isLegal) {
|
||||
// 签发token
|
||||
//* Step3: 签发token
|
||||
const { token } = await firstValueFrom(
|
||||
this.client.send('cert.token.create', { username }),
|
||||
);
|
||||
//* Step4: 询问认证中心token存在cookie中的key,统一个业务的token的key,以及过期时间
|
||||
const { tokenKeyInCookie, expires } = await firstValueFrom(
|
||||
this.client.send('cert.token.config', []),
|
||||
);
|
||||
|
|
|
@ -15,10 +15,11 @@ export class AuthController {
|
|||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
const { username, password } = userLoginDto;
|
||||
//* Step1: 询问认证中心服务,用户是否合法
|
||||
const { isLegal, data, msg } = await firstValueFrom(
|
||||
this.client.send('cert.user.account', { username, password }),
|
||||
);
|
||||
// 日志
|
||||
//* Step2: 上报登录日志
|
||||
await firstValueFrom(
|
||||
this.client.send('logger.user.signIn', {
|
||||
platform: 'dmp',
|
||||
|
@ -29,11 +30,12 @@ export class AuthController {
|
|||
}),
|
||||
);
|
||||
if (isLegal) {
|
||||
// 签发token,签用户的角色,前端UI路由鉴权
|
||||
//* Step3: 签发token,签用户的角色,前端UI路由鉴权
|
||||
const { roles } = data;
|
||||
const { token } = await firstValueFrom(
|
||||
this.client.send('cert.token.create', { username, roles }),
|
||||
);
|
||||
//* Step4: 询问认证中心token存在cookie中的key,统一个业务的token的key,以及过期时间
|
||||
const { tokenKeyInCookie, expires } = await firstValueFrom(
|
||||
this.client.send('cert.token.config', []),
|
||||
);
|
||||
|
|
3951
pnpm-lock.yaml
3951
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user