添加三维模型(未完成)
This commit is contained in:
parent
8f1eb56a47
commit
900ca4dd2c
BIN
frontend/src/renderer/public/Model.glb
Normal file
BIN
frontend/src/renderer/public/Model.glb
Normal file
Binary file not shown.
BIN
frontend/src/renderer/src/assets/new/Model.glb
Normal file
BIN
frontend/src/renderer/src/assets/new/Model.glb
Normal file
Binary file not shown.
@ -231,7 +231,6 @@ import { useAuthStore } from '../stores/index.js'
|
|||||||
import Header from '@/views/Header.vue'
|
import Header from '@/views/Header.vue'
|
||||||
import PatientCreate from '@/views/PatientCreate.vue'
|
import PatientCreate from '@/views/PatientCreate.vue'
|
||||||
import Detection from '@/views/Detection.vue'
|
import Detection from '@/views/Detection.vue'
|
||||||
import { color } from 'echarts'
|
|
||||||
import PatientProfile from '@/views/PatientProfile.vue'
|
import PatientProfile from '@/views/PatientProfile.vue'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|||||||
@ -85,7 +85,6 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="body-son-display">
|
<div class="body-son-display">
|
||||||
{{ }}
|
|
||||||
<img src="@/assets/new/refresh.svg" alt="" style="margin-right: 8px;cursor: pointer;" title="重启IMU"
|
<img src="@/assets/new/refresh.svg" alt="" style="margin-right: 8px;cursor: pointer;" title="重启IMU"
|
||||||
@click="refreshClick('imu')">
|
@click="refreshClick('imu')">
|
||||||
<div class="connecttext" :style="{ color: imuStatus == '已连接' ? '#00CC00' : '#808080' }">
|
<div class="connecttext" :style="{ color: imuStatus == '已连接' ? '#00CC00' : '#808080' }">
|
||||||
@ -128,7 +127,12 @@
|
|||||||
<span class="currencytext1">旋转角:</span>
|
<span class="currencytext1">旋转角:</span>
|
||||||
<span class="currencytext3">{{ headlist.rotation }}°</span>
|
<span class="currencytext3">{{ headlist.rotation }}°</span>
|
||||||
</div>
|
</div>
|
||||||
<img src="@/assets/new/testheader.png" style="width: 100%;height: 80%;" alt="">
|
<div style="width: 100%;height: 80%;" alt="">
|
||||||
|
<!-- <img src="@/assets/new/testheader.png" > -->
|
||||||
|
|
||||||
|
<Model />
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="body-header-bottombox-right">
|
<div class="body-header-bottombox-right">
|
||||||
<div class="body-header-bottombox-righttext" style="height: 20%;"></div>
|
<div class="body-header-bottombox-righttext" style="height: 20%;"></div>
|
||||||
@ -433,6 +437,7 @@ import noImageSvg from '@/assets/no-image.svg'
|
|||||||
import DiagnosticMessage from '@/views/DiagnosticMessage.vue'
|
import DiagnosticMessage from '@/views/DiagnosticMessage.vue'
|
||||||
import PatientCreate from '@/views/PatientCreate.vue'
|
import PatientCreate from '@/views/PatientCreate.vue'
|
||||||
import HistoryDashboard from '@/views/PatientProfile.vue'
|
import HistoryDashboard from '@/views/PatientProfile.vue'
|
||||||
|
import Model from './model.vue'
|
||||||
|
|
||||||
const emit = defineEmits([ 'endChange']);
|
const emit = defineEmits([ 'endChange']);
|
||||||
const asd = ref(0)
|
const asd = ref(0)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-page" :style="{ backgroundImage: `url(${bg})` }">
|
<div class="login-page" :style="{ backgroundImage: `url(${bg})` }">
|
||||||
|
<!-- <Model /> -->
|
||||||
<!-- 页面主内容 -->
|
<!-- 页面主内容 -->
|
||||||
<div class="login-content">
|
<div class="login-content">
|
||||||
<!-- 系统标题 -->
|
<!-- 系统标题 -->
|
||||||
@ -223,6 +224,7 @@ import { User, Lock, View, Hide, Phone } from '@element-plus/icons-vue'
|
|||||||
|
|
||||||
import bg from '@/assets/new/newbg.jpg'
|
import bg from '@/assets/new/newbg.jpg'
|
||||||
import { getBackendUrl,systemAPI } from '../services/api.js'
|
import { getBackendUrl,systemAPI } from '../services/api.js'
|
||||||
|
// import Model from './model.vue'
|
||||||
const BACKEND_URL = getBackendUrl()
|
const BACKEND_URL = getBackendUrl()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|||||||
@ -73,15 +73,15 @@
|
|||||||
<el-button v-if="selectedPatient" type="primary"
|
<el-button v-if="selectedPatient" type="primary"
|
||||||
:class="selectedData.length == 2? 'patientprofile-selectedbutotn':'patientprofile-butotn'"
|
:class="selectedData.length == 2? 'patientprofile-selectedbutotn':'patientprofile-butotn'"
|
||||||
@click="viewPatientProfile">
|
@click="viewPatientProfile">
|
||||||
<img v-if="selectedData.length == 2" src="@/assets/new/bi2.svg" alt="" style="margin-right: 8px;">
|
<img v-if="selectedData.length == 2" src="@/assets/new/bi2.png" alt="" style="margin-right: 8px;">
|
||||||
<img v-else src="@/assets/new/bi.svg" alt="" style="margin-right: 8px;">
|
<img v-else src="@/assets/new/bi.png" alt="" style="margin-right: 8px;">
|
||||||
报告对比
|
报告对比
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="selectedPatient" type="primary"
|
<el-button v-if="selectedPatient" type="primary"
|
||||||
:class="selectedData.length>0? 'patientprofile-selectedbutotn':'patientprofile-butotn'"
|
:class="selectedData.length>0? 'patientprofile-selectedbutotn':'patientprofile-butotn'"
|
||||||
@click="viewPatientProfile">
|
@click="viewPatientProfile">
|
||||||
<img v-if="selectedData.length>0" src="@/assets/new/del2.svg" alt="" style="margin-right: 8px;">
|
<img v-if="selectedData.length>0" src="@/assets/new/del2.png" alt="" style="margin-right: 8px;">
|
||||||
<img v-else src="@/assets/new/del.svg" alt="" style="margin-right: 8px;">
|
<img v-else src="@/assets/new/del.png" alt="" style="margin-right: 8px;">
|
||||||
删除记录
|
删除记录
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
609
frontend/src/renderer/src/views/model.vue
Normal file
609
frontend/src/renderer/src/views/model.vue
Normal file
@ -0,0 +1,609 @@
|
|||||||
|
<template>
|
||||||
|
<div id="containermodel" >
|
||||||
|
<div @click="asd" style="position: absolute;top: 0;">开始动画</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
|
// const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
// Three.js场景设置
|
||||||
|
let scene, camera, renderer, model;
|
||||||
|
let targetQuaternion = new THREE.Quaternion();
|
||||||
|
let currentQuaternion = new THREE.Quaternion();
|
||||||
|
|
||||||
|
// 调试控制参数
|
||||||
|
let axisMapping = {
|
||||||
|
roll: 'z', // 默认Roll对应Z轴
|
||||||
|
pitch: 'x', // 默认Pitch对应X轴
|
||||||
|
yaw: 'y' // 默认Yaw对应Y轴
|
||||||
|
};
|
||||||
|
|
||||||
|
let manualOffsets = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
initThreeJS()
|
||||||
|
// initDebugControls();
|
||||||
|
})
|
||||||
|
function initThreeJS() {
|
||||||
|
// 创建场景
|
||||||
|
scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0x282828); // 稍微提亮背景
|
||||||
|
let containermodel = document.getElementById('containermodel');
|
||||||
|
// 创建相机
|
||||||
|
camera = new THREE.PerspectiveCamera(75, containermodel.offsetWidth / containermodel.offsetHeight, 0.1, 1000);
|
||||||
|
camera.position.set(3, 3, 2);
|
||||||
|
camera.lookAt(0, 0, 0);
|
||||||
|
|
||||||
|
// 创建渲染器
|
||||||
|
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
renderer.setSize(containermodel.offsetWidth, containermodel.offsetHeight);
|
||||||
|
renderer.shadowMap.enabled = true;
|
||||||
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||||
|
renderer.outputColorSpace = THREE.SRGBColorSpace; // 改善颜色输出
|
||||||
|
renderer.toneMapping = THREE.ACESFilmicToneMapping; // 添加色调映射
|
||||||
|
renderer.toneMappingExposure = 1.2; // 增加曝光
|
||||||
|
document.getElementById('containermodel').appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
// 高强度照明系统
|
||||||
|
// 环境光 - 大幅提升整体亮度
|
||||||
|
const ambientLight = new THREE.AmbientLight(0x606060, 2.0);
|
||||||
|
scene.add(ambientLight);
|
||||||
|
|
||||||
|
// 主方向光 - 从右上角照射,高强度
|
||||||
|
const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
|
directionalLight1.position.set(10, 10, 5);
|
||||||
|
directionalLight1.castShadow = true;
|
||||||
|
directionalLight1.shadow.mapSize.width = 2048;
|
||||||
|
directionalLight1.shadow.mapSize.height = 2048;
|
||||||
|
directionalLight1.shadow.camera.near = 0.1;
|
||||||
|
directionalLight1.shadow.camera.far = 50;
|
||||||
|
directionalLight1.shadow.camera.left = -10;
|
||||||
|
directionalLight1.shadow.camera.right = 10;
|
||||||
|
directionalLight1.shadow.camera.top = 10;
|
||||||
|
directionalLight1.shadow.camera.bottom = -10;
|
||||||
|
scene.add(directionalLight1);
|
||||||
|
|
||||||
|
// 左侧补光 - 更强的强度
|
||||||
|
const directionalLight2 = new THREE.DirectionalLight(0xbbddff, 1.0);
|
||||||
|
directionalLight2.position.set(-8, 6, 4);
|
||||||
|
scene.add(directionalLight2);
|
||||||
|
|
||||||
|
// 右侧补光
|
||||||
|
const directionalLight3 = new THREE.DirectionalLight(0xffddbb, 0.8);
|
||||||
|
directionalLight3.position.set(8, 6, -4);
|
||||||
|
scene.add(directionalLight3);
|
||||||
|
|
||||||
|
// 顶部强光
|
||||||
|
const topLight = new THREE.DirectionalLight(0xffffff, 1.0);
|
||||||
|
topLight.position.set(0, 15, 0);
|
||||||
|
scene.add(topLight);
|
||||||
|
|
||||||
|
// 底部补光(避免底部过暗)
|
||||||
|
const bottomLight = new THREE.DirectionalLight(0x8899bb, 0.5);
|
||||||
|
bottomLight.position.set(0, -5, 0);
|
||||||
|
scene.add(bottomLight);
|
||||||
|
|
||||||
|
// 添加坐标系轴线
|
||||||
|
createCoordinateAxes();
|
||||||
|
|
||||||
|
// 加载3D模型
|
||||||
|
loadModel();
|
||||||
|
|
||||||
|
// 开始渲染循环
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadModel() {
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
|
||||||
|
// 尝试加载Model.glb文件
|
||||||
|
loader.load('Model.glb',
|
||||||
|
(gltf) => {
|
||||||
|
model = gltf.scene;
|
||||||
|
// 调整模型大小和位置
|
||||||
|
const box = new THREE.Box3().setFromObject(model);
|
||||||
|
const size = box.getSize(new THREE.Vector3());
|
||||||
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||||||
|
const scale = 2 / maxDim; // 调整为合适大小
|
||||||
|
model.scale.set(scale, scale, scale);
|
||||||
|
|
||||||
|
// 将模型置于中心
|
||||||
|
const center = box.getCenter(new THREE.Vector3());
|
||||||
|
model.position.set(-center.x * scale, -center.y * scale + 1, -center.z * scale);
|
||||||
|
|
||||||
|
// 启用阴影
|
||||||
|
model.traverse((child) => {
|
||||||
|
if (child.isMesh) {
|
||||||
|
child.castShadow = true;
|
||||||
|
child.receiveShadow = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(model);
|
||||||
|
console.log('3D模型加载成功');
|
||||||
|
},
|
||||||
|
(progress) => {
|
||||||
|
console.log('加载进度:', (progress.loaded / progress.total * 100) + '%');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('模型加载失败:', error);
|
||||||
|
// 如果模型加载失败,创建一个替代的立方体
|
||||||
|
createFallbackModel();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFallbackModel() {
|
||||||
|
console.log('使用默认立方体模型');
|
||||||
|
const geometry = new THREE.BoxGeometry(2, 2, 2);
|
||||||
|
const material = new THREE.MeshLambertMaterial({
|
||||||
|
color: 0x00ff00,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.8
|
||||||
|
});
|
||||||
|
model = new THREE.Mesh(geometry, material);
|
||||||
|
model.position.set(0, 1, 0);
|
||||||
|
model.castShadow = true;
|
||||||
|
model.receiveShadow = true;
|
||||||
|
scene.add(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
|
||||||
|
// 应用SLERP平滑插值到模型
|
||||||
|
if (model) {
|
||||||
|
currentQuaternion.slerp(targetQuaternion, 0.1);
|
||||||
|
model.quaternion.copy(currentQuaternion);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口大小调整
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
let containermodel = document.getElementById('containermodel');
|
||||||
|
|
||||||
|
camera.aspect = containermodel.offsetWidth / containermodel.offsetHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(containermodel.offsetWidth, containermodel.offsetHeight);
|
||||||
|
});
|
||||||
|
function asd(){
|
||||||
|
setInterval(()=>{
|
||||||
|
targetQuaternion.set(
|
||||||
|
Math.random(),
|
||||||
|
Math.random(),
|
||||||
|
Math.random(),
|
||||||
|
Math.random()
|
||||||
|
);
|
||||||
|
},500)
|
||||||
|
|
||||||
|
}
|
||||||
|
// // IPC通信处理
|
||||||
|
// ipcRenderer.on('orientation-data', (event, data) => {
|
||||||
|
// if (data.quaternion && model) {
|
||||||
|
// // 显示原始四元数
|
||||||
|
// updateDebugDisplay('raw-quaternion', data.quaternion);
|
||||||
|
|
||||||
|
// // 应用轴向映射
|
||||||
|
// const mappedQuaternion = applyAxisMapping(data.quaternion);
|
||||||
|
// updateDebugDisplay('mapped-quaternion', mappedQuaternion);
|
||||||
|
|
||||||
|
// // 应用手动偏移
|
||||||
|
// const finalQuaternion = applyManualOffsets(mappedQuaternion);
|
||||||
|
// updateDebugDisplay('final-quaternion', finalQuaternion);
|
||||||
|
|
||||||
|
// // 更新目标四元数
|
||||||
|
// targetQuaternion.set(
|
||||||
|
// finalQuaternion.x,
|
||||||
|
// finalQuaternion.y,
|
||||||
|
// finalQuaternion.z,
|
||||||
|
// finalQuaternion.w
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// ipcRenderer.on('device-connected', () => {
|
||||||
|
// updateConnectionStatus('已连接', true);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// ipcRenderer.on('device-disconnected', () => {
|
||||||
|
// updateConnectionStatus('已断开', false);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// ipcRenderer.on('server-info', (event, info) => {
|
||||||
|
// updateServerInfo(info);
|
||||||
|
// });
|
||||||
|
|
||||||
|
function updateConnectionStatus(status, connected) {
|
||||||
|
const statusElement = document.getElementById('connection-status');
|
||||||
|
statusElement.textContent = `设备状态: ${status}`;
|
||||||
|
statusElement.className = connected ? 'status connected' : 'status disconnected';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateServerInfo(info) {
|
||||||
|
const serverInfoElement = document.getElementById('server-info');
|
||||||
|
|
||||||
|
serverInfoElement.innerHTML = `
|
||||||
|
服务器: ${info.ip}:${info.port}<br>
|
||||||
|
<strong>手机访问:</strong><br>
|
||||||
|
<a href="${info.mobileUrl}" target="_blank" style="color: #4CAF50;">${info.mobileUrl}</a><br>
|
||||||
|
<small>或: <a href="${info.alternativeUrl}" target="_blank" style="color: #4CAF50;">${info.alternativeUrl}</a></small>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
generateQRCode(info.mobileUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateQRCode(url) {
|
||||||
|
const canvas = document.getElementById('qr-canvas');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用qrcode库生成真正的二维码
|
||||||
|
const QRCode = require('qrcode');
|
||||||
|
|
||||||
|
// 二维码选项
|
||||||
|
const options = {
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
margin: 1,
|
||||||
|
color: {
|
||||||
|
dark: '#000000',
|
||||||
|
light: '#FFFFFF'
|
||||||
|
},
|
||||||
|
errorCorrectionLevel: 'M'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成二维码到canvas
|
||||||
|
await QRCode.toCanvas(canvas, url, options);
|
||||||
|
console.log('二维码生成成功:', url);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('二维码生成失败:', error);
|
||||||
|
// 备用方案:显示文字
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.fillStyle = '#ffffff';
|
||||||
|
ctx.fillRect(0, 0, 120, 120);
|
||||||
|
ctx.fillStyle = '#000000';
|
||||||
|
ctx.font = '12px Arial';
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('QR生成失败', 60, 50);
|
||||||
|
ctx.font = '10px Arial';
|
||||||
|
ctx.fillText('请手动输入URL', 60, 70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 轴向映射函数
|
||||||
|
function applyAxisMapping(quaternion) {
|
||||||
|
// 将四元数转换为欧拉角进行映射
|
||||||
|
const euler = new THREE.Euler().setFromQuaternion(
|
||||||
|
new THREE.Quaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 映射轴向
|
||||||
|
const mappedEuler = {
|
||||||
|
x: getAxisValue(euler, axisMapping.pitch),
|
||||||
|
y: getAxisValue(euler, axisMapping.yaw),
|
||||||
|
z: getAxisValue(euler, axisMapping.roll)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 转换回四元数
|
||||||
|
const resultQuaternion = new THREE.Quaternion().setFromEuler(
|
||||||
|
new THREE.Euler(mappedEuler.x, mappedEuler.y, mappedEuler.z)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
w: resultQuaternion.w,
|
||||||
|
x: resultQuaternion.x,
|
||||||
|
y: resultQuaternion.y,
|
||||||
|
z: resultQuaternion.z
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAxisValue(euler, mapping) {
|
||||||
|
const value = {
|
||||||
|
'x': euler.x,
|
||||||
|
'-x': -euler.x,
|
||||||
|
'y': euler.y,
|
||||||
|
'-y': -euler.y,
|
||||||
|
'z': euler.z,
|
||||||
|
'-z': -euler.z
|
||||||
|
}[mapping];
|
||||||
|
|
||||||
|
return value || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用手动偏移
|
||||||
|
function applyManualOffsets(quaternion) {
|
||||||
|
// 创建偏移四元数
|
||||||
|
const offsetQuaternion = new THREE.Quaternion().setFromEuler(
|
||||||
|
new THREE.Euler(
|
||||||
|
manualOffsets.x * Math.PI / 180,
|
||||||
|
manualOffsets.y * Math.PI / 180,
|
||||||
|
manualOffsets.z * Math.PI / 180
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 应用偏移
|
||||||
|
const originalQuaternion = new THREE.Quaternion(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
|
||||||
|
const resultQuaternion = offsetQuaternion.multiply(originalQuaternion);
|
||||||
|
|
||||||
|
return {
|
||||||
|
w: resultQuaternion.w,
|
||||||
|
x: resultQuaternion.x,
|
||||||
|
y: resultQuaternion.y,
|
||||||
|
z: resultQuaternion.z
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新调试显示
|
||||||
|
function updateDebugDisplay(elementId, quaternion) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
if (element) {
|
||||||
|
element.textContent = `w:${quaternion.w.toFixed(3)}, x:${quaternion.x.toFixed(3)}, y:${quaternion.y.toFixed(3)}, z:${quaternion.z.toFixed(3)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建坐标系轴线
|
||||||
|
function createCoordinateAxes() {
|
||||||
|
const axesGroup = new THREE.Group();
|
||||||
|
|
||||||
|
// 轴线长度和粗细
|
||||||
|
const axisLength = 3;
|
||||||
|
const axisRadius = 0.02;
|
||||||
|
|
||||||
|
// X轴 - 亮红色,使用自发光材质
|
||||||
|
const xGeometry = new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, 8);
|
||||||
|
const xMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xff6666,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.9
|
||||||
|
});
|
||||||
|
const xAxis = new THREE.Mesh(xGeometry, xMaterial);
|
||||||
|
xAxis.rotation.z = -Math.PI / 2;
|
||||||
|
xAxis.position.x = axisLength / 2;
|
||||||
|
axesGroup.add(xAxis);
|
||||||
|
|
||||||
|
// X轴箭头
|
||||||
|
const xArrowGeometry = new THREE.ConeGeometry(axisRadius * 3, axisRadius * 8, 8);
|
||||||
|
const xArrow = new THREE.Mesh(xArrowGeometry, xMaterial);
|
||||||
|
xArrow.rotation.z = -Math.PI / 2;
|
||||||
|
xArrow.position.x = axisLength + axisRadius * 4;
|
||||||
|
axesGroup.add(xArrow);
|
||||||
|
|
||||||
|
// Y轴 - 亮绿色,使用自发光材质
|
||||||
|
const yGeometry = new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, 8);
|
||||||
|
const yMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x66ff66,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.9
|
||||||
|
});
|
||||||
|
const yAxis = new THREE.Mesh(yGeometry, yMaterial);
|
||||||
|
yAxis.position.y = axisLength / 2;
|
||||||
|
axesGroup.add(yAxis);
|
||||||
|
|
||||||
|
// Y轴箭头
|
||||||
|
const yArrowGeometry = new THREE.ConeGeometry(axisRadius * 3, axisRadius * 8, 8);
|
||||||
|
const yArrow = new THREE.Mesh(yArrowGeometry, yMaterial);
|
||||||
|
yArrow.position.y = axisLength + axisRadius * 4;
|
||||||
|
axesGroup.add(yArrow);
|
||||||
|
|
||||||
|
// Z轴 - 亮蓝色,使用自发光材质
|
||||||
|
const zGeometry = new THREE.CylinderGeometry(axisRadius, axisRadius, axisLength, 8);
|
||||||
|
const zMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x6666ff,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.9
|
||||||
|
});
|
||||||
|
const zAxis = new THREE.Mesh(zGeometry, zMaterial);
|
||||||
|
zAxis.rotation.x = Math.PI / 2;
|
||||||
|
zAxis.position.z = axisLength / 2;
|
||||||
|
axesGroup.add(zAxis);
|
||||||
|
|
||||||
|
// Z轴箭头
|
||||||
|
const zArrowGeometry = new THREE.ConeGeometry(axisRadius * 3, axisRadius * 8, 8);
|
||||||
|
const zArrow = new THREE.Mesh(zArrowGeometry, zMaterial);
|
||||||
|
zArrow.rotation.x = Math.PI / 2;
|
||||||
|
zArrow.position.z = axisLength + axisRadius * 4;
|
||||||
|
axesGroup.add(zArrow);
|
||||||
|
|
||||||
|
// 原点球 - 亮白色自发光
|
||||||
|
const originGeometry = new THREE.SphereGeometry(axisRadius * 2, 16, 16);
|
||||||
|
const originMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xffffff,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.8
|
||||||
|
});
|
||||||
|
const origin = new THREE.Mesh(originGeometry, originMaterial);
|
||||||
|
axesGroup.add(origin);
|
||||||
|
|
||||||
|
scene.add(axesGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化调试控制
|
||||||
|
function initDebugControls() {
|
||||||
|
// 轴向映射下拉框事件
|
||||||
|
document.getElementById('roll-mapping').addEventListener('change', (e) => {
|
||||||
|
axisMapping.roll = e.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('pitch-mapping').addEventListener('change', (e) => {
|
||||||
|
axisMapping.pitch = e.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('yaw-mapping').addEventListener('change', (e) => {
|
||||||
|
axisMapping.yaw = e.target.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 手动偏移滑动条事件
|
||||||
|
['x', 'y', 'z'].forEach(axis => {
|
||||||
|
const slider = document.getElementById(`offset-${axis}`);
|
||||||
|
const valueDisplay = document.getElementById(`offset-${axis}-value`);
|
||||||
|
|
||||||
|
slider.addEventListener('input', (e) => {
|
||||||
|
const value = parseInt(e.target.value);
|
||||||
|
manualOffsets[axis] = value;
|
||||||
|
valueDisplay.textContent = `${value}°`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重置偏移按钮
|
||||||
|
document.getElementById('reset-offsets').addEventListener('click', () => {
|
||||||
|
['x', 'y', 'z'].forEach(axis => {
|
||||||
|
manualOffsets[axis] = 0;
|
||||||
|
document.getElementById(`offset-${axis}`).value = 0;
|
||||||
|
document.getElementById(`offset-${axis}-value`).textContent = '0°';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initThreeJS();
|
||||||
|
initDebugControls();
|
||||||
|
|
||||||
|
// 请求服务器信息
|
||||||
|
ipcRenderer.invoke('get-server-info').then(info => {
|
||||||
|
updateServerInfo(info);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加键盘控制(可选)
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
switch(event.key) {
|
||||||
|
case 'r':
|
||||||
|
case 'R':
|
||||||
|
// 重置模型旋转
|
||||||
|
targetQuaternion.set(0, 0, 0, 1);
|
||||||
|
currentQuaternion.set(0, 0, 0, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#containermodel {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
left: 10px;
|
||||||
|
color: white;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 100;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
max-width: 350px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr-section {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid rgba(255,255,255,0.2);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr-canvas {
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#debug-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
color: white;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-section {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 8px;
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-section h4 {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
color: #4CAF50;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5px 0;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row label {
|
||||||
|
min-width: 20px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row select {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid rgba(255,255,255,0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row input[type="range"] {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row .value {
|
||||||
|
min-width: 40px;
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connected {
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disconnected {
|
||||||
|
color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
#qr-code {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: white;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue
Block a user