作者: 还是大剑师兰特,曾为美国某知名大学计算机专业研究生,现为国内GIS领域高级前端工程师,CSDN知名博主,深耕openlayers、leaflet、mapbox、cesium,canvas,echarts等技术开发,欢迎加微信(gis-dajianshi),一起交流。

查看本专栏目录 - 本文是第 046个示例

一、示例效果图

在这里插入图片描述

二、示例简介

本示例是vue+threeJS计划,实现物体自由落体运动。

  • 主要功能:
    初始化 Three.js 场景、相机、渲染器和控制器
    创建地面作为碰撞检测的基础
    提供按钮可以动态添加随机大小和颜色的立方体
    搭建了基础的重力模拟,使物体自由下落
    实现了物体与地面的碰撞检测和反弹效果
    实现了物体之间的容易碰撞检测和响应

三、配置说明

1)查看基础设置:https://dajianshi.blog.csdn.net/article/details/141936765
2)将示例源代码,粘贴到src/views/Home.vue中,npm run serve 运行即可。

四、示例源代码(共 250行)

/*
* @Author: 大剑师兰特(xiaozhuanlan),还是大剑师兰特(CSDN)
* @此源代码版权归大剑师兰特所有,可供学习或商业项目中借鉴,未经授权,不得重复地发表到博客、论坛,问答,git等公共空间或网站中。
* @Email: 2909222303@qq.com
* @First published in  CSDN
* @First published time: 2025-11-12
*/
<template>
  <div class="three-container">
    <div ref="canvasContainer" class="canvas-container"></div>
      <button @click="addBox" class="add-btn">添加立方体</button>
        </div>
          </template>
            <script>
              import * as THREE from 'three'
              import { OrbitControls } from 'three/examples/jsm//controls/OrbitControls.js'
              export default {
              data() {
              return {
              scene: null,
              camera: null,
              renderer: null,
              controls: null,
              boxes: [], // 存储所有下落的物体
              ground: null, // 地面
              gravity: 0.2, // 重力加速度
              animationId: null
              }
              },
              mounted() {
              this.initThree()
              this.createGround()
              this.animate()
              },
              beforeDestroy() {
              // 清理资源
              cancelAnimationFrame(this.animationId)
              this.renderer.dispose()
              this.scene.clear()
              },
              methods: {
              // 初始化Three.js基础组件
              initThree() {
              // 创建场景
              this.scene = new THREE.Scene()
              this.scene.background = new THREE.Color(0xf0f0f0)
              // 创建相机
              this.camera = new THREE.PerspectiveCamera(
              75,
              this.$refs.canvasContainer.clientWidth / this.$refs.canvasContainer.clientHeight,
              0.1,
              1000
              )
              this.camera.position.z = 20
              this.camera.position.y = 10
              this.camera.position.x = 10
              this.camera.lookAt(0, 0, 0)
              // 创建渲染器
              this.renderer = new THREE.WebGLRenderer({ antialias: true })
              this.renderer.setSize(
              this.$refs.canvasContainer.clientWidth,
              this.$refs.canvasContainer.clientHeight
              )
              this.$refs.canvasContainer.appendChild(this.renderer.domElement)
              // 添加轨道控制器
              this.controls = new OrbitControls(this.camera, this.renderer.domElement)
              this.controls.enableDamping = true
              // 添加环境光
              const ambientLight = new THREE.AmbientLight(0xffffff, 0.6)
              this.scene.add(ambientLight)
              // 添加平行光
              const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
              directionalLight.position.set(10, 20, 10)
              this.scene.add(directionalLight)
              // 监听窗口大小变化
              window.addEventListener('resize', this.onWindowResize)
              },
              // 创建地面
              createGround() {
              const groundGeometry = new THREE.PlaneGeometry(50, 50)
              const groundMaterial = new THREE.MeshStandardMaterial({
              color: 0xeeeeee,
              side: THREE.DoubleSide
              })
              this.ground = new THREE.Mesh(groundGeometry, groundMaterial)
              this.ground.rotation.x = Math.PI / 2 // 旋转90度使其水平
              this.ground.position.y = -5 // 地面位置
              this.scene.add(this.ground)
              },
              // 添加立方体
              addBox() {
              const size = Math.random() * 1 + 2.5 // 随机大小
              const geometry = new THREE.BoxGeometry(size, size, size)
              const material = new THREE.MeshStandardMaterial({
              color: Math.random() * 0xffffff,
              transparent: true,
              opacity: 0.9
              })
              const box = new THREE.Mesh(geometry, material)
              // 随机位置
              box.position.x = (Math.random() - 0.5) * 10
              box.position.z = (Math.random() - 0.5) * 10
              box.position.y = 15 // 初始高度
              // 添加物理属性
              box.velocity = new THREE.Vector3(0, 0, 0) // 速度
              box.rotationSpeed = new THREE.Vector3(
              Math.random() * 0.05,
              Math.random() * 0.05,
              Math.random() * 0.05
              ) // 旋转速度
              this.scene.add(box)
              this.boxes.push(box)
              },
              // 窗口大小变化处理
              onWindowResize() {
              this.camera.aspect = this.$refs.canvasContainer.clientWidth / this.$refs.canvasContainer.clientHeight
              this.camera.updateProjectionMatrix()
              this.renderer.setSize(
              this.$refs.canvasContainer.clientWidth,
              this.$refs.canvasContainer.clientHeight
              )
              },
              // 动画循环
              animate() {
              this.animationId = requestAnimationFrame(() => this.animate())
              // 更新控制器
              this.controls.update()
              // 更新所有物体的物理状态
              this.updatePhysics()
              // 渲染场景
              this.renderer.render(this.scene, this.camera)
              },
              // 更新物理效果
              updatePhysics() {
              this.boxes.forEach(box => {
              // 应用重力
              box.velocity.y -= this.gravity
              // 更新位置
              box.position.add(box.velocity)
              // 检测与地面的碰撞
              const boxHalfHeight = box.geometry.parameters.height / 2
              const groundY = this.ground.position.y
              if (box.position.y - boxHalfHeight < groundY) {
              // 碰撞后反弹 (损失一些能量)
              box.velocity.y = -box.velocity.y * 0.8
              // 防止物体陷入地面
              box.position.y = groundY + boxHalfHeight
              // 能量损失到一定程度后停止反弹
              if (Math.abs(box.velocity.y) < 0.1) {
              box.velocity.y = 0
              }
              }
              // 检测物体之间的碰撞
              this.detectBoxCollisions(box)
              })
              },
              // 检测物体之间的碰撞
              detectBoxCollisions(box) {
              const boxHalfHeight = box.geometry.parameters.height / 2
              const boxHalfWidth = box.geometry.parameters.width / 2
              const boxHalfDepth = box.geometry.parameters.depth / 2
              this.boxes.forEach(otherBox => {
              if (box === otherBox) return
              const otherHalfHeight = otherBox.geometry.parameters.height / 2
              const otherHalfWidth = otherBox.geometry.parameters.width / 2
              const otherHalfDepth = otherBox.geometry.parameters.depth / 2
              // 简单的AABB碰撞检测
              const collisionX = Math.abs(box.position.x - otherBox.position.x) < (boxHalfWidth + otherHalfWidth)
              const collisionY = Math.abs(box.position.y - otherBox.position.y) < (boxHalfHeight + otherHalfHeight)
              const collisionZ = Math.abs(box.position.z - otherBox.position.z) < (boxHalfDepth + otherHalfDepth)
              if (collisionX && collisionY && collisionZ) {
              // 交换y方向的速度 (简单碰撞响应)
              const tempVelocity = box.velocity.y
              box.velocity.y = otherBox.velocity.y * 0.8
              otherBox.velocity.y = tempVelocity * 0.8
              // 分离物体防止重叠
              if (box.position.y < otherBox.position.y) {
              box.position.y = otherBox.position.y - (boxHalfHeight + otherHalfHeight)
              } else {
              otherBox.position.y = box.position.y - (boxHalfHeight + otherHalfHeight)
              }
              }
              })
              }
              }
              }
              </script>
                <style scoped>
                  .three-container {
                  width: 100%;
                  height: 100vh;
                  position: relative;
                  }
                  .canvas-container {
                  width: 100%;
                  height: 100%;
                  }
                  .add-btn {
                  position: absolute;
                  top: 20px;
                  left: 20px;
                  padding: 10px 20px;
                  background-color: #42b983;
                  color: white;
                  border: none;
                  border-radius: 4px;
                  cursor: pointer;
                  z-index: 100;
                  }
                  .add-btn:hover {
                  background-color: #359e6d;
                  }
                  </style>

五、相关文章参考

https://dajianshi.blog.csdn.net/article/details/142059217