confirm perspective switch 初始化

本文介绍了一种配置视角切换的方法,此方法适用于代码与资源库同步更新的场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>3D文物模型标注平台</title> <style> body { margin: 0; padding: 0; font-family: Arial, sans-serif; overflow: hidden; background-color: #333; color: #eee; } #top-menu { height: 40px; background-color: #222; color: #ddd; display: flex; align-items: center; padding: 0 10px; border-bottom: 1px solid #444; } .menu-item { position: relative; padding: 10px 15px; cursor: pointer; } .menu-item:hover { background-color: #444; } .menu-item:hover .submenu { display: block; } .submenu { display: none; position: absolute; top: 100%; left: 0; background-color: #333; min-width: 200px; z-index: 1000; border: 1px solid #444; box-shadow: 0 2px 5px rgba(0,0,0,0.5); } .submenu button { display: block; width: 100%; padding: 8px 15px; text-align: left; background: none; border: none; color: #ddd; cursor: pointer; border-bottom: 1px solid #444; } .submenu button:hover { background-color: #444; } #container { display: flex; height: calc(100vh - 40px); } #sidebar { width: 250px; background-color: #2a2a2a; padding: 10px; overflow-y: auto; border-right: 1px solid #444; resize: horizontal; min-width: 200px; max-width: 500px; overflow-x: hidden; } #viewer { flex: 1; position: relative; background-color: #1a1a1a; } .view-toolbar { position: absolute; top: 10px; right: 10px; z-index: 100; background: rgba(40,40,40,0.8); padding: 5px; border-radius: 3px; display: flex; flex-direction: column; gap: 3px; border: 1px solid #444; } .annotation-list { margin-top: 10px; } .annotation-item { padding: 6px; margin-bottom: 3px; background: #333; border-radius: 3px; cursor: pointer; display: flex; justify-content: space-between; font-size: 13px; border: 1px solid #444; } .annotation-item:hover { background-color: #444; } .annotation-item.selected { background-color: #4CAF50; color: white; } .file-input { display: none; } .status-bar { position: absolute; bottom: 5px; left: 5px; background: rgba(0,0,0,0.7); color: #ddd; padding: 3px 8px; border-radius: 3px; font-size: 12px; border: 1px solid #444; } button { padding: 3px 8px; cursor: pointer; border: 1px solid #555; background: #333; border-radius: 3px; font-size: 12px; color: #ddd; } button:hover { background: #444; } button.active { background: #4CAF50; color: white; } .view-btn { width: 80px; margin: 2px 0; } h3 { color: #ddd; border-bottom: 1px solid #444; padding-bottom: 5px; } .resize-handle { width: 5px; height: 100%; position: absolute; right: 0; top: 0; cursor: col-resize; } </style> </head> <body> <div id="top-menu"> <div class="menu-item"> 文件 <div class="submenu"> <button id="open-file">打开模型...</button> <input type="file" id="file-input" class="file-input" accept=".obj,.gltf,.glb,.3ds,.fbx,.dae,.stl,.ply"> <button id="save-file">保存模型</button> <button id="export-model">导出模型...</button> <button id="save-annotations">保存标注</button> <button id="export-annotations">导出标注...</button> </div> </div> <div class="menu-item"> 编辑 <div class="submenu"> <button id="undo-btn">撤销</button> <button id="redo-btn">恢复</button> <button id="delete-btn">删除</button> <button id="clear-btn">清空标注</button> <button id="restore-colors">还原模型颜色</button> </div> </div> <div class="menu-item"> 视图 <div class="submenu"> <button id="fit-view">适应视图</button> <button id="toggle-grid">显示网格</button> <button id="toggle-axis">显示坐标轴</button> <button id="toggle-wireframe">线框模式</button> </div> </div> <div class="menu-item"> 标注工具 <div class="submenu"> <button id="box-tool">3D框标注</button> <button id="point-tool">点标注</button> <button id="polygon-tool">多边形标注</button> <button id="measure-tool">测量工具</button> <button id="text-tool">文字标注</button> </div> </div> </div> <div id="container"> <div id="sidebar"> <h3>标注列表</h3> <div class="annotation-list"> <div id="annotations-container"></div> </div> </div> <div id="viewer"> <div class="view-toolbar"> <button id="front-view" class="view-btn">前视图</button> <button id="back-view" class="view-btn">后视图</button> <button id="left-view" class="view-btn">左视图</button> <button id="right-view" class="view-btn">右视图</button> <button id="top-view" class="view-btn">顶视图</button> <button id="bottom-view" class="view-btn">底视图</button> <button id="perspective-view" class="view-btn">透视视图</button> <button id="orthographic-view" class="view-btn">正交视图</button> </div> <div class="status-bar" id="status-bar"> 就绪 </div> </div> </div> <!-- 引入Three.js库和相关插件 --> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/OBJLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/FBXLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/DRACOLoader.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/exporters/GLTFExporter.js"></script> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/exporters/OBJExporter.js"></script> <script> // 初始化3D场景 const scene = new THREE.Scene(); scene.background = new THREE.Color(0x1a1a1a); let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z = 5; const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('viewer').appendChild(renderer.domElement); const controls = new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.25; controls.screenSpacePanning = false; controls.maxPolarAngle = Math.PI; controls.minDistance = 0.1; controls.maxDistance = 1000; // 添加光源 const ambientLight = new THREE.AmbientLight(0x404040, 1.5); scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); // 添加辅助工具 const gridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x222222); gridHelper.visible = true; scene.add(gridHelper); // 添加垂直网格线 const verticalGridHelper = new THREE.GridHelper(20, 20, 0x444444, 0x222222); verticalGridHelper.rotation.x = Math.PI / 2; verticalGridHelper.visible = true; scene.add(verticalGridHelper); const axesHelper = new THREE.AxesHelper(5); axesHelper.visible = false; scene.add(axesHelper); // 模型和标注相关变量 let currentModel = null; let currentTool = null; let annotations = []; let history = []; let historyIndex = -1; let isWireframe = false; let selectedObject = null; let isMouseDown = false; let isRightMouseDown = false; let lastMousePosition = { x: 0, y: 0 }; let originalMaterials = new Map(); // 保存原始材质 // 文件操作功能 document.getElementById('open-file').addEventListener('click', function() { document.getElementById('file-input').click(); }); document.getElementById('file-input').addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; const fileName = file.name; const fileExtension = fileName.split('.').pop().toLowerCase(); const fileURL = URL.createObjectURL(file); updateStatus(`正在加载: ${fileName}`); // 根据文件类型选择不同的加载器 try { if (currentModel) { scene.remove(currentModel); } let loader; switch(fileExtension) { case 'gltf': case 'glb': loader = new THREE.GLTFLoader(); // 添加DRACO解码器支持 const dracoLoader = new THREE.DRACOLoader(); dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/'); loader.setDRACOLoader(dracoLoader); loader.load(fileURL, function(gltf) { currentModel = gltf.scene; // 保存原始材质 originalMaterials.clear(); currentModel.traverse(child => { if (child.isMesh) { originalMaterials.set(child, child.material); } }); // 自动垂直对齐模型 alignModelVertically(currentModel); scene.add(currentModel); fitViewToObject(currentModel); updateStatus(`已加载: ${fileName}`); saveHistory(); }, function(xhr) { const percentComplete = (xhr.loaded / xhr.total) * 100; updateStatus(`加载中: ${fileName} - ${percentComplete.toFixed(2)}%`); }, function(error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } ); break; case 'obj': loader = new THREE.OBJLoader(); loader.load(fileURL, function(object) { currentModel = object; // 保存原始材质 originalMaterials.clear(); currentModel.traverse(child => { if (child.isMesh) { originalMaterials.set(child, child.material); } }); // 自动垂直对齐模型 alignModelVertically(currentModel); scene.add(currentModel); fitViewToObject(currentModel); updateStatus(`已加载: ${fileName}`); saveHistory(); }, function(xhr) { const percentComplete = (xhr.loaded / xhr.total) * 100; updateStatus(`加载中: ${fileName} - ${percentComplete.toFixed(2)}%`); }, function(error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } ); break; case 'fbx': loader = new THREE.FBXLoader(); loader.load(fileURL, function(object) { currentModel = object; // 保存原始材质 originalMaterials.clear(); currentModel.traverse(child => { if (child.isMesh) { originalMaterials.set(child, child.material); } }); // 自动垂直对齐模型 alignModelVertically(currentModel); scene.add(currentModel); fitViewToObject(currentModel); updateStatus(`已加载: ${fileName}`); saveHistory(); }, function(xhr) { const percentComplete = (xhr.loaded / xhr.total) * 100; updateStatus(`加载中: ${fileName} - ${percentComplete.toFixed(2)}%`); }, function(error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } ); break; default: updateStatus(`不支持的文件格式: ${fileExtension}`); break; } } catch (error) { console.error('加载模型出错:', error); updateStatus(`加载失败: ${fileName}`); } }); // 自动垂直对齐模型 function alignModelVertically(model) { const box = new THREE.Box3().setFromObject(model); const size = box.getSize(new THREE.Vector3()); const center = box.getCenter(new THREE.Vector3()); // 假设模型高度方向是Y轴 // 计算模型需要旋转的角度,使最长边垂直 const maxDim = Math.max(size.x, size.y, size.z); // 如果最长边不是Y轴,则需要旋转模型 if (maxDim !== size.y) { // 找到最长边对应的轴 let rotationAxis = new THREE.Vector3(0, 0, 0); if (maxDim === size.x) { rotationAxis.set(0, 0, 1); // 绕Z轴旋转90度 } else if (maxDim === size.z) { rotationAxis.set(1, 0, 0); // 绕X轴旋转90度 } // 应用旋转 model.rotation.setFromVector3(rotationAxis.clone().multiplyScalar(Math.PI / 2)); // 重新计算边界框 box.setFromObject(model); const newCenter = box.getCenter(new THREE.Vector3()); // 调整位置使中心点不变 model.position.sub(newCenter).add(center); } } document.getElementById('save-file').addEventListener('click', function() { if (!currentModel) { updateStatus('没有可保存的模型'); return; } // 这里可以添加保存到服务器的逻辑 updateStatus('模型已保存'); }); document.getElementById('export-model').addEventListener('click', function() { if (!currentModel) { updateStatus('没有可导出的模型'); return; } const exporter = new THREE.GLTFExporter(); exporter.parse(currentModel, function(gltf) { const data = JSON.stringify(gltf); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'model.gltf'; a.click(); updateStatus('模型已导出为GLTF格式'); }); }); document.getElementById('save-annotations').addEventListener('click', function() { const data = JSON.stringify(annotations); localStorage.setItem('annotations', data); updateStatus('标注已保存到本地存储'); }); document.getElementById('export-annotations').addEventListener('click', function() { const data = JSON.stringify(annotations); const blob = new Blob([data], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'annotations.json'; a.click(); updateStatus('标注已导出为JSON文件'); }); // 视图控制功能 document.getElementById('fit-view').addEventListener('click', function() { if (currentModel) { fitViewToObject(currentModel); } }); document.getElementById('toggle-grid').addEventListener('click', function() { gridHelper.visible = !gridHelper.visible; verticalGridHelper.visible = gridHelper.visible; updateStatus(gridHelper.visible ? '网格已显示' : '网格已隐藏'); }); document.getElementById('toggle-axis').addEventListener('click', function() { axesHelper.visible = !axesHelper.visible; updateStatus(axesHelper.visible ? '坐标轴已显示' : '坐标轴已隐藏'); }); document.getElementById('toggle-wireframe').addEventListener('click', function() { if (!currentModel) return; isWireframe = !isWireframe; currentModel.traverse(function(child) { if (child.isMesh) { child.material.wireframe = isWireframe; } }); updateStatus(isWireframe ? '线框模式已启用' : '线框模式已禁用'); }); // 还原模型颜色功能 document.getElementById('restore-colors').addEventListener('click', function() { if (!currentModel) { updateStatus('没有可还原颜色的模型'); return; } currentModel.traverse(child => { if (child.isMesh && originalMaterials.has(child)) { child.material = originalMaterials.get(child); } }); updateStatus('已还原模型原始颜色'); }); // 标准视图 document.getElementById('front-view').addEventListener('click', function() { setStandardView('front'); }); document.getElementById('back-view').addEventListener('click', function() { setStandardView('back'); }); document.getElementById('left-view').addEventListener('click', function() { setStandardView('left'); }); document.getElementById('right-view').addEventListener('click', function() { setStandardView('right'); }); document.getElementById('top-view').addEventListener('click', function() { setStandardView('top'); }); document.getElementById('bottom-view').addEventListener('click', function() { setStandardView('bottom'); }); document.getElementById('perspective-view').addEventListener('click', function() { if (camera instanceof THREE.OrthographicCamera) { const aspect = window.innerWidth / window.innerHeight; camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000); camera.position.copy(controls.target.clone().add(new THREE.Vector3(5, 5, 5))); camera.lookAt(controls.target); controls.object = camera; controls.update(); updateStatus('切换到透视视图'); } }); document.getElementById('orthographic-view').addEventListener('click', function() { if (camera instanceof THREE.PerspectiveCamera) { const width = 10; const height = width * (window.innerHeight / window.innerWidth); camera = new THREE.OrthographicCamera( width / -2, width / 2, height / 2, height / -2, 0.1, 1000 ); camera.position.copy(controls.target.clone().add(new THREE.Vector3(5, 5, 5))); camera.lookAt(controls.target); controls.object = camera; controls.update(); updateStatus('切换到正交视图'); } }); // 编辑功能 document.getElementById('undo-btn').addEventListener('click', function() { if (historyIndex > 0) { historyIndex--; restoreFromHistory(); updateStatus('已撤销上一步操作'); } else { updateStatus('没有可撤销的操作'); } }); document.getElementById('redo-btn').addEventListener('click', function() { if (historyIndex < history.length - 1) { historyIndex++; restoreFromHistory(); updateStatus('已恢复撤销的操作'); } else { updateStatus('没有可恢复的操作'); } }); document.getElementById('delete-btn').addEventListener('click', function() { deleteSelectedObject(); }); document.getElementById('clear-btn').addEventListener('click', function() { if (confirm('确定要清空所有标注吗?')) { clearAnnotations(); updateStatus('已清空所有标注'); } }); // 标注工具功能 document.getElementById('box-tool').addEventListener('click', function() { setCurrentTool('box'); }); document.getElementById('point-tool').addEventListener('click', function() { setCurrentTool('point'); }); document.getElementById('polygon-tool').addEventListener('click', function() { setCurrentTool('polygon'); }); document.getElementById('measure-tool').addEventListener('click', function() { setCurrentTool('measure'); }); document.getElementById('text-tool').addEventListener('click', function() { setCurrentTool('text'); }); // 设置当前工具 function setCurrentTool(tool) { currentTool = tool; updateStatus(`${getToolName(tool)}工具已激活 - 点击模型表面添加标注`); // 更新按钮状态 document.querySelectorAll('.submenu button').forEach(btn => { btn.classList.remove('active'); }); document.getElementById(`${tool}-tool`).classList.add('active'); } // 获取工具名称 function getToolName(tool) { const names = { 'box': '3D框标注', 'point': '点标注', 'polygon': '多边形标注', 'measure': '测量', 'text': '文字标注' }; return names[tool] || tool; } // 鼠标交互 renderer.domElement.addEventListener('mousedown', onMouseDown, false); renderer.domElement.addEventListener('mouseup', onMouseUp, false); renderer.domElement.addEventListener('mousemove', onMouseMove, false); renderer.domElement.addEventListener('wheel', onMouseWheel, false); renderer.domElement.addEventListener('contextmenu', function(e) { e.preventDefault(); // 阻止右键菜单 }, false); // 键盘交互 document.addEventListener('keydown', function(e) { if (e.key === 'Delete' && selectedObject) { deleteSelectedObject(); } }); // 删除选中对象 function deleteSelectedObject() { if (selectedObject) { scene.remove(selectedObject); annotations = annotations.filter(anno => anno.object !== selectedObject); updateAnnotationsList(); saveHistory(); selectedObject = null; updateStatus('已删除选中对象'); } else { updateStatus('请先选择要删除的对象'); } } // 辅助函数 function fitViewToObject(object) { const box = new THREE.Box3().setFromObject(object); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); const fov = camera.fov * (Math.PI / 180); let cameraZ = Math.abs(maxDim / (2 * Math.tan(fov / 2))); cameraZ *= 1.5; // 稍微远一点 controls.target.copy(center); camera.position.copy(center.clone().add(new THREE.Vector3(0, 0, cameraZ))); controls.update(); updateStatus('视图已适应模型'); } function setStandardView(view) { if (!currentModel) return; const box = new THREE.Box3().setFromObject(currentModel); const center = box.getCenter(new THREE.Vector3()); const size = box.getSize(new THREE.Vector3()); const maxDim = Math.max(size.x, size.y, size.z); controls.target.copy(center); let position; switch(view) { case 'front': position = new THREE.Vector3(0, 0, maxDim * 1.5); updateStatus('切换到前视图'); break; case 'back': position = new THREE.Vector3(0, 0, -maxDim * 1.5); updateStatus('切换到后视图'); break; case 'left': position = new THREE.Vector3(-maxDim * 1.5, 0, 0); updateStatus('切换到左视图'); break; case 'right': position = new THREE.Vector3(maxDim * 1.5, 0, 0); updateStatus('切换到右视图'); break; case 'top': position = new THREE.Vector3(0, maxDim * 1.5, 0); updateStatus('切换到顶视图'); break; case 'bottom': position = new THREE.Vector3(0, -maxDim * 1.5, 0); updateStatus('切换到底视图'); break; } position.add(center); camera.position.copy(position); camera.lookAt(center); controls.update(); } function saveHistory() { // 简化版历史记录,实际应用中可能需要更复杂的实现 history = history.slice(0, historyIndex + 1); history.push({ annotations: JSON.parse(JSON.stringify(annotations)), model: currentModel ? currentModel.clone() : null }); historyIndex = history.length - 1; } function restoreFromHistory() { const state = history[historyIndex]; annotations = JSON.parse(JSON.stringify(state.annotations)); if (currentModel) scene.remove(currentModel); currentModel = state.model ? state.model.clone() : null; if (currentModel) scene.add(currentModel); updateAnnotationsList(); } function clearAnnotations() { // 移除所有标注对象 annotations.forEach(anno => { if (anno.object && anno.object.parent) { scene.remove(anno.object); } }); annotations = []; updateAnnotationsList(); saveHistory(); } function updateAnnotationsList() { const container = document.getElementById('annotations-container'); container.innerHTML = ''; annotations.forEach((annotation, index) => { const item = document.createElement('div'); item.className = 'annotation-item'; if (annotation.object === selectedObject) { item.classList.add('selected'); } item.innerHTML = ` <span>标注 ${index + 1} (${getToolName(annotation.type)})</span> <button class="delete-annotation" data-index="${index}">×</button> `; container.appendChild(item); item.querySelector('.delete-annotation').addEventListener('click', function(e) { e.stopPropagation(); scene.remove(annotation.object); annotations.splice(index, 1); updateAnnotationsList(); saveHistory(); updateStatus(`已删除标注 ${index + 1}`); }); item.addEventListener('click', function() { // 聚焦到该标注 controls.target.copy(annotation.object.position); controls.update(); selectedObject = annotation.object; updateAnnotationsList(); updateStatus(`已聚焦到标注 ${index + 1}`); }); }); } function updateStatus(message) { document.getElementById('status-bar').textContent = message; } // 鼠标交互函数 function onMouseDown(event) { if (event.button === 2) { // 右键 isRightMouseDown = true; return; } if (event.button !== 0) return; // 只处理左键 isMouseDown = true; lastMousePosition = { x: event.clientX, y: event.clientY }; // 检查是否点击了模型或标注 const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { selectedObject = intersects[0].object; // 如果是标注工具激活状态,创建新标注 if (currentTool) { createAnnotation(intersects[0].point); } else { updateStatus(`已选中对象: ${selectedObject.type}`); updateAnnotationsList(); } } else { selectedObject = null; updateAnnotationsList(); } } function onMouseUp(event) { if (event.button === 2) { // 右键 isRightMouseDown = false; } if (event.button === 0) { // 左键 isMouseDown = false; } } function onMouseMove(event) { if (isRightMouseDown) { // 右键拖动旋转视图 const deltaMove = { x: event.clientX - lastMousePosition.x, y: event.clientY - lastMousePosition.y }; controls.rotateLeft(2 * Math.PI * deltaMove.x / window.innerWidth); controls.rotateUp(2 * Math.PI * deltaMove.y / window.innerHeight); } else if (isMouseDown && !currentTool) { // 左键拖动旋转视图 const deltaMove = { x: event.clientX - lastMousePosition.x, y: event.clientY - lastMousePosition.y }; controls.rotateLeft(2 * Math.PI * deltaMove.x / window.innerWidth); controls.rotateUp(2 * Math.PI * deltaMove.y / window.innerHeight); } lastMousePosition = { x: event.clientX, y: event.clientY }; } function onMouseWheel(event) { // 缩放控制 const delta = Math.sign(event.deltaY); const zoomSpeed = 0.1; if (delta > 0) { controls.dollyOut(zoomSpeed); } else { controls.dollyIn(zoomSpeed); } controls.update(); } // 创建标注函数 function createAnnotation(position) { let annotation; switch(currentTool) { case 'point': annotation = createPointAnnotation(position); break; case 'box': annotation = createBoxAnnotation(position); break; case 'polygon': annotation = createPolygonAnnotation(position); break; case 'measure': annotation = createMeasureAnnotation(position); break; case 'text': annotation = createTextAnnotation(position); break; } if (annotation) { scene.add(annotation); annotations.push({ type: currentTool, object: annotation, position: position }); selectedObject = annotation; updateAnnotationsList(); saveHistory(); updateStatus(`已添加${getToolName(currentTool)}标注`); } } // 创建点标注 function createPointAnnotation(position) { const geometry = new THREE.SphereGeometry(0.05, 16, 16); const material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); const sphere = new THREE.Mesh(geometry, material); sphere.position.copy(position); return sphere; } // 创建3D框标注 function createBoxAnnotation(position) { const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.5, wireframe: false }); const box = new THREE.Mesh(geometry, material); box.position.copy(position); return box; } // 创建多边形标注 function createPolygonAnnotation(position) { // 简化实现,实际应用中需要更复杂的逻辑 const geometry = new THREE.CircleGeometry(0.2, 6); const material = new THREE.MeshBasicMaterial({ color: 0x0000ff, transparent: true, opacity: 0.7 }); const circle = new THREE.Mesh(geometry, material); circle.position.copy(position); return circle; } // 创建测量标注 function createMeasureAnnotation(position) { // 简化实现,实际应用中需要更复杂的逻辑 const geometry = new THREE.CylinderGeometry(0.05, 0.05, 0.5, 16); const material = new THREE.MeshBasicMaterial({ color: 0xffff00 }); const cylinder = new THREE.Mesh(geometry, material); cylinder.position.copy(position); return cylinder; } // 创建文字标注 function createTextAnnotation(position) { // 简化实现,实际应用中需要使用TextGeometry或CSS2D/3D渲染器 const geometry = new THREE.BoxGeometry(0.3, 0.1, 0.01); const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); const cube = new THREE.Mesh(geometry, material); cube.position.copy(position); return cube; } // 动画循环 function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); // 窗口大小调整 window.addEventListener('resize', function() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); // 在动画循环中添加标注对象的更新逻辑 function animate() { requestAnimationFrame(animate); // 更新标注对象的位置和状态 updateAnnotations(); controls.update(); renderer.render(scene, camera); } // 更新标注对象的状态 function updateAnnotations() { annotations.forEach(annotation => { // 可以在这里添加标注对象的动画效果或状态更新 if (annotation.type === 'point' && annotation.object) { // 点标注的脉动效果 annotation.object.scale.x = 1 + Math.sin(Date.now() * 0.005) * 0.2; annotation.object.scale.y = 1 + Math.sin(Date.now() * 0.005) * 0.2; annotation.object.scale.z = 1 + Math.sin(Date.now() * 0.005) * 0.2; } }); } // 添加标注编辑功能 function enableAnnotationEditing() { // 双击标注可以进入编辑模式 renderer.domElement.addEventListener('dblclick', function(event) { if (!currentTool && selectedObject) { const annotation = annotations.find(anno => anno.object === selectedObject); if (annotation) { editAnnotation(annotation); } } }); } // 编辑标注 function editAnnotation(annotation) { // 根据标注类型显示不同的编辑界面 switch(annotation.type) { case 'text': editTextAnnotation(annotation); break; case 'box': editBoxAnnotation(annotation); break; // 其他标注类型的编辑逻辑... } } // 编辑文字标注 function editTextAnnotation(annotation) { const text = prompt('请输入新的标注文字:', '标注内容'); if (text !== null) { // 更新标注内容 updateStatus(`文字标注已更新: ${text}`); saveHistory(); } } // 编辑3D框标注 function editBoxAnnotation(annotation) { const size = prompt('请输入新的尺寸 (宽,高,深):', '0.5,0.5,0.5'); if (size) { const dimensions = size.split(',').map(Number); if (dimensions.length === 3 && !dimensions.some(isNaN)) { annotation.object.geometry.dispose(); annotation.object.geometry = new THREE.BoxGeometry(...dimensions); updateStatus(`3D框标注尺寸已更新: ${dimensions.join('x')}`); saveHistory(); } else { updateStatus('无效的尺寸格式,请使用"宽,高,深"格式'); } } } // 添加标注分组功能 let annotationGroups = {}; function createAnnotationGroup(name) { if (!annotationGroups[name]) { annotationGroups[name] = []; updateStatus(`已创建标注分组: ${name}`); return true; } return false; } function addToAnnotationGroup(annotation, groupName) { if (annotationGroups[groupName]) { annotationGroups[groupName].push(annotation); annotation.group = groupName; updateStatus(`标注已添加到分组: ${groupName}`); return true; } return false; } // 添加模型测量工具 function setupMeasurementTools() { let startPoint = null; let endPoint = null; let measurementLine = null; document.getElementById('measure-tool').addEventListener('click', function() { setCurrentTool('measure'); startPoint = null; endPoint = null; if (measurementLine) { scene.remove(measurementLine); measurementLine = null; } }); renderer.domElement.addEventListener('click', function(event) { if (currentTool === 'measure') { const mouse = new THREE.Vector2( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 ); const raycaster = new THREE.Raycaster(); raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { if (!startPoint) { startPoint = intersects[0].point; updateStatus('测量: 已选择起点,请选择终点'); } else { endPoint = intersects[0].point; createMeasurementLine(startPoint, endPoint); startPoint = null; endPoint = null; } } } }); function createMeasurementLine(start, end) { if (measurementLine) { scene.remove(measurementLine); } const geometry = new THREE.BufferGeometry().setFromPoints([start, end]); const material = new THREE.LineBasicMaterial({ color: 0xffff00 }); measurementLine = new THREE.Line(geometry, material); scene.add(measurementLine); const distance = start.distanceTo(end); updateStatus(`测量结果: ${distance.toFixed(2)} 单位`); // 添加测量标注 const annotation = { type: 'measure', object: measurementLine, start: start, end: end, distance: distance }; annotations.push(annotation); updateAnnotationsList(); saveHistory(); } } // 初始化所有功能 function initFeatures() { enableAnnotationEditing(); setupMeasurementTools(); // 添加分组管理UI setupGroupManagementUI(); } // 添加分组管理UI function setupGroupManagementUI() { const sidebar = document.getElementById('sidebar'); const groupSection = document.createElement('div'); groupSection.innerHTML = ` <h3>标注分组</h3> <div id="group-management"> <input type="text" id="new-group-name" placeholder="新分组名称"> <button id="create-group">创建分组</button> <div id="groups-list"></div> </div> `; sidebar.insertBefore(groupSection, document.querySelector('.annotation-list')); document.getElementById('create-group').addEventListener('click', function() { const groupName = document.getElementById('new-group-name').value.trim(); if (groupName && createAnnotationGroup(groupName)) { updateGroupsList(); document.getElementById('new-group-name').value = ''; } }); // 右键菜单添加到标注项 document.addEventListener('contextmenu', function(e) { if (e.target.classList.contains('annotation-item')) { e.preventDefault(); showAnnotationContextMenu(e); } }); } function updateGroupsList() { const groupsList = document.getElementById('groups-list'); groupsList.innerHTML = ''; for (const groupName in annotationGroups) { const groupItem = document.createElement('div'); groupItem.className = 'group-item'; groupItem.innerHTML = ` <span>${groupName} (${annotationGroups[groupName].length})</span> <button class="delete-group" data-group="${groupName}">×</button> `; groupsList.appendChild(groupItem); groupItem.querySelector('.delete-group').addEventListener('click', function(e) { e.stopPropagation(); deleteAnnotationGroup(groupName); }); } } function deleteAnnotationGroup(groupName) { if (confirm(`确定要删除分组 "${groupName}" 吗?`)) { delete annotationGroups[groupName]; updateGroupsList(); updateStatus(`已删除分组: ${groupName}`); } } function showAnnotationContextMenu(event) { // 实现右键菜单功能 const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.position = 'absolute'; menu.style.left = `${event.clientX}px`; menu.style.top = `${event.clientY}px`; menu.style.backgroundColor = '#333'; menu.style.border = '1px solid #444'; menu.style.padding = '5px'; menu.style.zIndex = '1000'; const index = event.target.closest('.annotation-item').querySelector('.delete-annotation').dataset.index; const annotation = annotations[index]; menu.innerHTML = ` <div class="menu-item" data-action="rename">重命名</div> <div class="menu-item" data-action="change-color">更改颜色</div> <div class="menu-item" data-action="add-to-group">添加到分组</div> `; document.body.appendChild(menu); // 点击菜单项处理 menu.querySelectorAll('.menu-item').forEach(item => { item.addEventListener('click', function() { const action = this.dataset.action; switch(action) { case 'rename': renameAnnotation(annotation); break; case 'change-color': changeAnnotationColor(annotation); break; case 'add-to-group': addAnnotationToGroup(annotation); break; } menu.remove(); }); }); // 点击其他地方关闭菜单 document.addEventListener('click', function closeMenu() { menu.remove(); document.removeEventListener('click', closeMenu); }, { once: true }); } // 初始化所有功能 initFeatures();以这份代码为基础添加高级标注功能:1.标注层次结构允许用户创建嵌套的标注组,形成层次化的标注结构;2.标注样式自定义:提供自定义标注外观的选项,如颜色、大小、透明度等;3.标注模板:允许用户创建和应用标注模板,快速标注类似特征;4.标注注释:为每个标注添加详细的文字描述、标签和元数据;5.多点测量:支持多点测量,计算复杂形状的长度、角度、面积等;6.自动测量模式:识别模型的关键特征(如边缘、角落)并自动测量;7.测量结果导出:将测量数据导出为csv或PDF格式;8.多人实时协作:允许多人同时编辑一个模型,并实时同步标注;9.版本控制;记录标注和模型的历史版本,支持回滚到任意历史版本;10.标注评论和讨论:允许用户对标注添加评论,进行团队评论;11.自动标注推荐:利用AI识别模型对关键特征(如颜色、器物边缘),并建议标注位置;12.智能识别:支持基于AI的模型识别,自动分类和标注不同类型的文物。13.AR模式:允许用户通过移动设备在现实场景中查看和标注3D模型;14.VR标注:支持VR设备(如Oculus Quest),提供沉浸式标注体验;15.标注数据导出:支持将标注导出为多种格式(如OBJ、JSON、XML、CSV);16.3D打印准备:导出模型和标注,适配3D打印需求;17.AR/VR应用导出:将标注模型导出为AR/VR应用可用的格式;18.模型简化:为复杂模型提供简化选项,优化渲染性能;19.标注对象池:复用标注对象,减少内存分配和垃圾回收;20.LOD(细节层次):根据视距动态调整模型和标注的细节级别;21.插件架构:允许开发者扩展平台功能,如添加插件市场:集成插件市场,用户可以下载和安装第三方插件。自定义标注工具或分析插件;22.插件市场:集成插件市场,用户可以下载和安装第三方插件;23.暗/亮模式切换:提供界面主题切换功能;24.自定义布局:允许用户调整侧边栏、工具栏的位置和大小;25.快捷键支持:添加更多快捷键,提高操作效率;26.标注统计:提供标注数量、类型分布等统计信息;27.模型分析:计算模型体积、表面积等几何属性;28.标注热图:可视化显示标注密集区域;29.云存储集成:支持将模型和标注数据存储到云平台(如AWS、Google Drive);30.自动同步:实时同步本地和云端数据,防止数据丢失;31.孔洞填充:自动检测并填充模型中的孔洞(模型的修复工具);32.法线修复:自动修复模型法线方向,确保渲染效果正确(模型修复工具);33.UV展开:为模型生成UV映射,便于纹理标注(模型修复工具);34.标注筛选:按类型、颜色、注释内容等条件筛选标注(高级筛选与搜索);35.模型搜索:支持通过关键词搜索模型库中的文物(高级筛选与搜索);36.标注教程:内置标注指南和教程,帮助用户快速上手;37.标注练习:提供预设模型和任务,用户可以练习标注技能;38.用户权限:支持不同用户角色(如管理员、编辑、查看者),限制操作权限;39.数据加密:对存储和传输中的模型和标注数据进行加密。根据我的39个要求添加新功能,以完整的代码输出给我
05-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值