Преглед изворни кода

模型预览界面添加光照调整

WQQ пре 6 дана
родитељ
комит
6b3544945c

+ 446 - 57
RuoYi-Vue3/src/views/front/content/ModelPreview.vue

@@ -7,7 +7,96 @@
     destroy-on-close
   >
     <div class="model-preview-container">
-      <div ref="modelContainer" class="model-container"></div>
+      <div ref="modelContainer" class="model-container">
+        <!-- 光照控制按钮 -->
+        <div class="light-control-btn" @click="toggleLightControl">
+          <el-icon><Sunny /></el-icon>
+        </div>
+        
+        <!-- 光照控制面板 -->
+        <div v-if="showLightControl" class="light-control-panel">
+          <h4>光照控制</h4>
+          
+          <!-- 环境光强度 -->
+          <div class="control-item">
+            <label>环境光强度</label>
+            <el-slider 
+              v-model="lightSettings.ambientIntensity" 
+              :min="0" 
+              :max="5" 
+              :step="0.1" 
+              @change="updateLights"
+            />
+            <span class="value">{{ lightSettings.ambientIntensity.toFixed(1) }}</span>
+          </div>
+          
+          <!-- 主平行光强度 -->
+          <div class="control-item">
+            <label>主光源强度</label>
+            <el-slider 
+              v-model="lightSettings.directionalIntensity" 
+              :min="0" 
+              :max="10" 
+              :step="0.1" 
+              @change="updateLights"
+            />
+            <span class="value">{{ lightSettings.directionalIntensity.toFixed(1) }}</span>
+          </div>
+          
+          <!-- 辅助光强度 -->
+          <div class="control-item">
+            <label>辅助光强度</label>
+            <el-slider 
+              v-model="lightSettings.directionalIntensity2" 
+              :min="0" 
+              :max="5" 
+              :step="0.1" 
+              @change="updateLights"
+            />
+            <span class="value">{{ lightSettings.directionalIntensity2.toFixed(1) }}</span>
+          </div>
+          
+          <!-- 主光源角度 -->
+          <div class="control-item">
+            <label>主光源角度</label>
+            <div class="angle-controls">
+              <div>
+                <span>X轴</span>
+                <el-slider 
+                  v-model="lightSettings.directionalPosition.x" 
+                  :min="-5" 
+                  :max="5" 
+                  :step="0.1" 
+                  @change="updateLights"
+                />
+                <span class="value">{{ lightSettings.directionalPosition.x.toFixed(1) }}</span>
+              </div>
+              <div>
+                <span>Y轴</span>
+                <el-slider 
+                  v-model="lightSettings.directionalPosition.y" 
+                  :min="-5" 
+                  :max="5" 
+                  :step="0.1" 
+                  @change="updateLights"
+                />
+                <span class="value">{{ lightSettings.directionalPosition.y.toFixed(1) }}</span>
+              </div>
+              <div>
+                <span>Z轴</span>
+                <el-slider 
+                  v-model="lightSettings.directionalPosition.z" 
+                  :min="-5" 
+                  :max="5" 
+                  :step="0.1" 
+                  @change="updateLights"
+                />
+                <span class="value">{{ lightSettings.directionalPosition.z.toFixed(1) }}</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
       <div class="model-info">
         <h3>模型信息</h3>
         <el-descriptions :column="1" border>
@@ -36,6 +125,7 @@ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'
 import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'
 // 导入项目的request实例
 import request from '@/utils/request'
+import { Sunny } from '@element-plus/icons-vue'
 
 const props = defineProps({
   visible: {
@@ -59,6 +149,22 @@ let controls = null
 let animationId = null
 const dialogVisible = ref(false)
 
+// 光照控制相关变量
+const showLightControl = ref(false)
+let ambientLight = null
+let directionalLight = null
+let directionalLight2 = null
+const lightSettings = ref({
+  ambientIntensity: 2.0,
+  directionalIntensity: 4.0,
+  directionalIntensity2: 2.0,
+  directionalPosition: {
+    x: 1,
+    y: 1,
+    z: 1
+  }
+})
+
 // 获取类型显示名称
 const getTypeDisplayName = (type) => {
   const typeMapping = {
@@ -97,12 +203,21 @@ const initScene = () => {
   modelContainer.value.appendChild(renderer.domElement)
 
   // 添加光源
-  const ambientLight = new THREE.AmbientLight(0xffffff, 0.5)
+  ambientLight = new THREE.AmbientLight(0xffffff, lightSettings.value.ambientIntensity)
   scene.add(ambientLight)
 
-  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
-  directionalLight.position.set(1, 1, 1)
+  directionalLight = new THREE.DirectionalLight(0xffffff, lightSettings.value.directionalIntensity)
+  directionalLight.position.set(
+    lightSettings.value.directionalPosition.x,
+    lightSettings.value.directionalPosition.y,
+    lightSettings.value.directionalPosition.z
+  )
   scene.add(directionalLight)
+  
+  // 添加第二个平行光,从不同角度照射,增强照明效果
+  directionalLight2 = new THREE.DirectionalLight(0xffffff, lightSettings.value.directionalIntensity2)
+  directionalLight2.position.set(-1, 1, -1)
+  scene.add(directionalLight2)
 
   // 添加网格辅助线
   const gridHelper = new THREE.GridHelper(10, 10)
@@ -124,12 +239,16 @@ const initScene = () => {
 
   // 监听窗口大小变化
   window.addEventListener('resize', handleResize)
+  
+  // 添加光照控制面板事件阻止
+  addLightControlEventPrevent()
 }
 
 // 初始化控制器
 const initControls = () => {
   // 简单的鼠标控制实现 - 控制相机视角
   let isDragging = false
+  let isPanning = false // 中键平移状态
   let previousMousePosition = { x: 0, y: 0 }
   
   // 相机目标点(看向的点)
@@ -149,21 +268,70 @@ const initControls = () => {
   updateCameraTarget()
 
   const container = modelContainer.value
+  
+  // 检查事件是否来自光照控制面板
+  const isFromLightControl = (e) => {
+    let target = e.target
+    while (target) {
+      if (target.classList && (target.classList.contains('light-control-panel') || target.classList.contains('light-control-btn'))) {
+        return true
+      }
+      target = target.parentElement
+    }
+    return false
+  }
+
   container.addEventListener('mousedown', (e) => {
-    isDragging = true
+    // 如果事件来自光照控制面板,不处理
+    if (isFromLightControl(e)) return
+    
+    // 检查是否是中键
+    if (e.button === 1) { // 中键
+      isPanning = true
+    } else if (e.button === 0) { // 左键
+      isDragging = true
+    }
+    
     previousMousePosition = { x: e.offsetX, y: e.offsetY }
   })
 
   container.addEventListener('mousemove', (e) => {
-    if (!isDragging) return
-
+    // 如果事件来自光照控制面板,不处理
+    if (isFromLightControl(e)) return
+    
     const deltaMove = {
       x: e.offsetX - previousMousePosition.x,
       y: e.offsetY - previousMousePosition.y
     }
 
-    // 控制相机围绕目标点旋转
-    if (camera) {
+    // 中键平移相机
+    if (isPanning && camera) {
+      // 计算平移距离
+      const distance = camera.position.distanceTo(cameraTarget)
+      const panSpeed = distance * 0.001
+      
+      // 计算相机的右方向和上方向
+      const right = new THREE.Vector3()
+      const up = new THREE.Vector3()
+      camera.getWorldDirection(right)
+      right.crossVectors(camera.up, right).normalize()
+      up.copy(camera.up).normalize()
+      
+      // 计算平移向量
+      const pan = new THREE.Vector3()
+      pan.addScaledVector(right, deltaMove.x * panSpeed)
+      pan.addScaledVector(up, deltaMove.y * panSpeed)
+      
+      // 平移相机和目标点
+      camera.position.add(pan)
+      cameraTarget.add(pan)
+      
+      // 相机看向目标点
+      camera.lookAt(cameraTarget)
+    }
+    // 左键旋转相机
+    else if (isDragging && camera) {
+      // 控制相机围绕目标点旋转
       // 计算相机到目标点的距离
       const distance = camera.position.distanceTo(cameraTarget)
       
@@ -192,16 +360,27 @@ const initControls = () => {
     }
   })
 
-  container.addEventListener('mouseup', () => {
+  container.addEventListener('mouseup', (e) => {
+    // 如果事件来自光照控制面板,不处理
+    if (isFromLightControl(e)) return
+    
     isDragging = false
+    isPanning = false
   })
 
-  container.addEventListener('mouseleave', () => {
+  container.addEventListener('mouseleave', (e) => {
+    // 如果事件来自光照控制面板,不处理
+    if (isFromLightControl(e)) return
+    
     isDragging = false
+    isPanning = false
   })
 
   // 滚轮缩放 - 控制相机距离目标点的距离
   container.addEventListener('wheel', (e) => {
+    // 如果事件来自光照控制面板,不处理
+    if (isFromLightControl(e)) return
+    
     e.preventDefault()
     if (camera) {
       // 计算相机到目标点的向量
@@ -225,30 +404,90 @@ const initControls = () => {
   })
 }
 
+// 为光照控制面板添加事件阻止
+const addLightControlEventPrevent = () => {
+  const container = modelContainer.value
+  if (!container) return
+  
+  // 为光照控制面板添加事件监听器,阻止事件传播
+  const lightControlPanel = container.querySelector('.light-control-panel')
+  const lightControlBtn = container.querySelector('.light-control-btn')
+  
+  const preventEvents = (element) => {
+    if (element) {
+      element.addEventListener('mousedown', (e) => {
+        e.stopPropagation()
+      })
+      element.addEventListener('mousemove', (e) => {
+        e.stopPropagation()
+      })
+      element.addEventListener('mouseup', (e) => {
+        e.stopPropagation()
+      })
+      element.addEventListener('wheel', (e) => {
+        e.stopPropagation()
+        e.preventDefault()
+      })
+    }
+  }
+  
+  preventEvents(lightControlPanel)
+  preventEvents(lightControlBtn)
+}
+
 // 添加三维坐标轴
 const addAxesHelper = () => {
   // 移除现有的坐标轴
   if (scene) {
     scene.traverse((object) => {
-      if (object.isAxesHelper) {
+      if (object.isAxesHelper || object.userData.isCustomAxes) {
         scene.remove(object)
       }
     })
   }
   
-  // 创建新的坐标轴辅助器
+  // 创建带箭头的坐标轴
   if (scene) {
-    const axesHelper = new THREE.AxesHelper(2)
-    // 设置坐标轴位置在世界空间的固定位置
-    // 这个位置会显示在视口左下角
-    axesHelper.position.set(-4, -4, 0)
-    axesHelper.scale.set(1, 1, 1)
+    const axisLength = 1
+    const arrowSize = 0.1
+    
+    // X轴 - 红色
+    const xArrow = new THREE.ArrowHelper(
+      new THREE.Vector3(1, 0, 0),  // 方向
+      new THREE.Vector3(0, 0, 0),  // 起点
+      axisLength,                  // 长度
+      0xff0000,                    // 颜色
+      arrowSize,                   // 箭头大小
+      arrowSize                    // 箭头宽度
+    )
+    xArrow.userData.isCustomAxes = true
+    scene.add(xArrow)
     
-    // 确保坐标轴不会受到场景其他变换的影响
-    axesHelper.updateMatrixWorld(true)
+    // Y轴 - 绿色
+    const yArrow = new THREE.ArrowHelper(
+      new THREE.Vector3(0, 1, 0),
+      new THREE.Vector3(0, 0, 0),
+      axisLength,
+      0x00ff00,
+      arrowSize,
+      arrowSize
+    )
+    yArrow.userData.isCustomAxes = true
+    scene.add(yArrow)
     
-    scene.add(axesHelper)
-    console.log('三维坐标轴已添加到视口左下角')
+    // Z轴 - 蓝色
+    const zArrow = new THREE.ArrowHelper(
+      new THREE.Vector3(0, 0, 1),
+      new THREE.Vector3(0, 0, 0),
+      axisLength,
+      0x0000ff,
+      arrowSize,
+      arrowSize
+    )
+    zArrow.userData.isCustomAxes = true
+    scene.add(zArrow)
+    
+    console.log('带箭头的三维坐标轴已添加到场景原点')
   }
 }
 
@@ -563,28 +802,29 @@ const processFBXModel = (fbxModel) => {
   console.log('处理FBX模型缩放...')
   console.log('原始模型缩放:', fbxModel.scale)
   
-  // 计算模型的整体缩放因子
-  let maxScale = 1
-  fbxModel.traverse((child) => {
-    if (child.isMesh) {
-      maxScale = Math.max(maxScale, child.scale.x, child.scale.y, child.scale.z)
-    }
-  })
+  // 计算模型的边界框,用于确定合适的缩放
+  let tempBox = new THREE.Box3().setFromObject(fbxModel)
+  let tempSize = tempBox.getSize(new THREE.Vector3())
+  console.log('原始模型边界框大小:', tempSize)
   
-  console.log('最大缩放因子:', maxScale)
+  // 计算模型的整体尺寸
+  const maxDimension = Math.max(tempSize.x, tempSize.y, tempSize.z)
+  console.log('模型最大尺寸:', maxDimension)
   
-  // 如果缩放因子过大或过小,进行统一调整
-  if (maxScale > 10 || maxScale < 0.1) {
-    const scaleFactor = 1 / maxScale
-    console.log('需要调整缩放,缩放因子:', scaleFactor)
-    
-    fbxModel.traverse((child) => {
-      if (child.isMesh) {
-        child.scale.multiplyScalar(scaleFactor)
-      }
-    })
-    
-    console.log('缩放调整完成')
+  // 计算合适的缩放因子,确保模型大小适中
+  let scaleFactor = 1
+  if (maxDimension > 10) {
+    scaleFactor = 5 / maxDimension
+    console.log('模型过大,需要缩小,缩放因子:', scaleFactor)
+  } else if (maxDimension < 1) {
+    scaleFactor = 2 / maxDimension
+    console.log('模型过小,需要放大,缩放因子:', scaleFactor)
+  }
+  
+  // 应用缩放因子到整个模型
+  if (scaleFactor !== 1) {
+    fbxModel.scale.multiplyScalar(scaleFactor)
+    console.log('缩放调整完成,新的模型缩放:', fbxModel.scale)
   }
   
   // 3. 强制更新所有矩阵
@@ -614,26 +854,25 @@ const processFBXModel = (fbxModel) => {
       console.log('处理网格:', child.name)
       
       try {
-        if (!child.material || 
-            child.material.type === 'unknown' || 
-            child.material.type === 'MeshPhongMaterial') {
-          console.log('为', child.name, '添加默认材质')
-          const defaultMaterial = new THREE.MeshStandardMaterial({
-            color: 0x409eff,
-            metalness: 0.3,
-            roughness: 0.7,
-            transparent: false,
-            opacity: 1
-          })
-          child.material = defaultMaterial
-          materialCount++
-        }
+        // 为所有FBX网格添加默认材质,确保每个网格都有材质
+        console.log('为', child.name, '添加默认材质')
+        const defaultMaterial = new THREE.MeshStandardMaterial({
+          color: 0x409eff,
+          metalness: 0.3,
+          roughness: 0.7,
+          transparent: false,
+          opacity: 1,
+          side: THREE.FrontSide
+        })
+        child.material = defaultMaterial
+        materialCount++
       } catch (error) {
         console.error('处理材质时出错:', error.message)
         const fallbackMaterial = new THREE.MeshStandardMaterial({
           color: 0x409eff,
           metalness: 0.3,
-          roughness: 0.7
+          roughness: 0.7,
+          side: THREE.FrontSide
         })
         child.material = fallbackMaterial
         materialCount++
@@ -1106,6 +1345,48 @@ watch(() => props.modelData, (newVal) => {
     modelName.value = newVal.name || '模型预览'
   }
 }, { immediate: true })
+
+// 光照控制方法
+const toggleLightControl = () => {
+  showLightControl.value = !showLightControl.value
+}
+
+// 更新光源设置
+const updateLights = () => {
+  // 保存相机当前位置和目标点
+  let cameraPosition = null
+  let cameraTarget = null
+  if (camera) {
+    cameraPosition = camera.position.clone()
+    // 获取相机看向的点
+    const vector = new THREE.Vector3()
+    camera.getWorldDirection(vector)
+    cameraTarget = camera.position.clone().add(vector)
+  }
+  
+  // 更新光源属性
+  if (ambientLight) {
+    ambientLight.intensity = lightSettings.value.ambientIntensity
+  }
+  if (directionalLight) {
+    directionalLight.intensity = lightSettings.value.directionalIntensity
+    directionalLight.position.set(
+      lightSettings.value.directionalPosition.x,
+      lightSettings.value.directionalPosition.y,
+      lightSettings.value.directionalPosition.z
+    )
+  }
+  if (directionalLight2) {
+    directionalLight2.intensity = lightSettings.value.directionalIntensity2
+  }
+  
+  // 恢复相机位置和目标点
+  if (camera && cameraPosition) {
+    camera.position.copy(cameraPosition)
+    camera.lookAt(cameraTarget)
+    camera.updateProjectionMatrix()
+  }
+}
 </script>
 
 <style scoped>
@@ -1120,6 +1401,7 @@ watch(() => props.modelData, (newVal) => {
   background-color: #fafafa;
   border-radius: 8px;
   overflow: hidden;
+  position: relative;
 }
 
 .model-info {
@@ -1149,4 +1431,111 @@ watch(() => props.modelData, (newVal) => {
     max-height: 300px;
   }
 }
+
+/* 光照控制样式 */
+.light-control-btn {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  width: 40px;
+  height: 40px;
+  background-color: rgba(255, 255, 255, 0.9);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  z-index: 1000;
+  transition: all 0.3s ease;
+}
+
+.light-control-btn:hover {
+  background-color: #ffffff;
+  transform: scale(1.1);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+.light-control-panel {
+  position: absolute;
+  top: 60px;
+  right: 10px;
+  width: 300px;
+  background-color: rgba(255, 255, 255, 0.95);
+  border-radius: 8px;
+  padding: 20px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+  z-index: 1000;
+  backdrop-filter: blur(5px);
+  pointer-events: auto;
+  user-select: none;
+}
+
+.light-control-panel h4 {
+  margin: 0 0 15px 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.control-item {
+  margin-bottom: 20px;
+}
+
+.control-item label {
+  display: block;
+  margin-bottom: 8px;
+  font-size: 14px;
+  font-weight: 500;
+  color: #606266;
+}
+
+.control-item .value {
+  position: absolute;
+  right: 10px;
+  top: 50%;
+  transform: translateY(-50%);
+  font-size: 12px;
+  color: #909399;
+  min-width: 40px;
+  text-align: right;
+}
+
+.angle-controls > div {
+  position: relative;
+  margin-bottom: 12px;
+}
+
+.angle-controls > div > span {
+  display: inline-block;
+  width: 40px;
+  font-size: 12px;
+  color: #909399;
+  margin-right: 10px;
+}
+
+.angle-controls .el-slider {
+  display: inline-block;
+  width: calc(100% - 100px);
+  vertical-align: middle;
+}
+
+.angle-controls .value {
+  position: static;
+  display: inline-block;
+  transform: none;
+  vertical-align: middle;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .light-control-panel {
+    width: 250px;
+    right: 5px;
+  }
+  
+  .angle-controls .el-slider {
+    width: calc(100% - 90px);
+  }
+}
 </style>

BIN
ruoyi-admin/target/classes/com/ruoyi/web/controller/watershed/WatershedModelController.class


BIN
ruoyi-admin/target/ruoyi-admin/uploads/models/2026/01/22/hair_20260122151932A001.fbx


BIN
ruoyi-admin/target/ruoyi-admin/uploads/models/2026/01/22/北斗_20260122153214A003.glb


BIN
ruoyi-admin/target/ruoyi-admin/uploads/models/2026/01/22/救生器材箱_20260122165132A004.glb


BIN
ruoyi-admin/target/ruoyi-admin/uploads/models/2026/01/22/断面桩_20260122165734A005.glb


BIN
ruoyi-admin/target/ruoyi-admin/uploads/models/2026/01/22/氨氮仪器_20260122170557A009.glb