ThreeJS-3D教学七-交互

news2024/11/24 8:35:50

在threejs中想要选中一个物体,点击或者鼠标悬浮,又或者移动端的touch事件,核心都是通过new THREE.Raycaster完成的。这里用到了一个概念,即我们点击时的 屏幕坐标 转换为 three中的3D坐标。
先看效果图:
在这里插入图片描述
代码是:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    body {
      width: 100%;
      height: 100%;
    }
    * {
      margin: 0;
      padding: 0;
    }
    .label {
      font-size: 20px;
      color: #000;
      font-weight: 700;
    }
  </style>
</head>
<body>
<div id="container"></div>
<script type="importmap">
  {
    "imports": {
      "three": "../three-155/build/three.module.js",
      "three/addons/": "../three-155/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
let stats, labelRenderer, gpuPanel, temporaryKeep;
let camera, scene, renderer, controls;
const group = new THREE.Group();
let widthImg = 200;
let heightImg = 200;
const mouse = new THREE.Vector2();
init();
initHelp();
initLight();
axesHelperWord();
animate();
// 添加平面
addPlane();

let geometry = new THREE.BoxGeometry(20, 20, 10);
let material = new THREE.MeshLambertMaterial({color: 0xcccccc});
let cube = new THREE.Mesh(geometry, material);
cube.position.y = 10;
cube.name = 'BoxGeometry';
scene.add(cube);

/**
 * CylinderGeometry(radiusTop : Float, radiusBottom : Float, height : Float, radialSegments : Integer, heightSegments : Integer)
 radiusTop—顶部圆柱体的半径。默认值为1。
 radiusBottom—底部圆柱体的半径。默认值为1。
 height——圆柱体的高度。默认值为1。
 radialSegments—圆柱体圆周上的分段面数。默认值为32
 heightSegments—沿圆柱体高度的面行数。默认值为1。
 */
let geometry1 = new THREE.CylinderGeometry(15, 15, 10, 32, 1);
let material1 = new THREE.MeshLambertMaterial({color: 0xffff00});
let cylinder = new THREE.Mesh(geometry1, material1);
cylinder.position.set(30, 5, -50);
cylinder.name = 'CylinderGeometry';
scene.add(cylinder);

function onDocumentMouseMove(event) {

  event.preventDefault();

  // 将鼠标点击位置的屏幕坐标转成threejs中的标准坐标,具体解释见代码释义 如果 canvas有左边距 和 上边距 需要减去
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
  // 新建一个三维单位向量 假设z方向就是1
  // 根据照相机,把这个向量转换到视点坐标系
  const vector = new THREE.Vector3(mouse.x, mouse.y, 1).unproject(camera);

  // 在视点坐标系中形成射线,射线的起点向量是照相机, 射线的方向向量是照相机到点击的点,这个向量应该归一标准化。
  const raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());

  //射线和模型求交,选中一系列直线
  const intersects = raycaster.intersectObjects(scene.children);

  console.log('imtersrcts=', intersects);

  /**
   * 不是所有的 Material 都有这个属性
   * emissive.getHex
   * emissive.setHex
   */
  if (intersects.length > 0) {
    //选中第一个射线相交的物体
    if (temporaryKeep !== intersects[0].object) {
      if (temporaryKeep) temporaryKeep.material.emissive?.setHex(temporaryKeep.currentHex);
      temporaryKeep = intersects[0].object;
      temporaryKeep.currentHex = temporaryKeep.material.emissive?.getHex();
      temporaryKeep.material.emissive?.setHex(0xff0000);
    }
  } else {
    if (temporaryKeep) temporaryKeep.material.emissive?.setHex(temporaryKeep.currentHex);
    temporaryKeep = null;
  }
}

function addPlane() {
  // 创建一个平面 PlaneGeometry(width, height, widthSegments, heightSegments)
  const planeGeometry = new THREE.PlaneGeometry(widthImg, heightImg, 1, 1);
  // 创建 Lambert 材质:会对场景中的光源作出反应,但表现为暗淡,而不光亮。
  const planeMaterial = new THREE.MeshPhongMaterial({
    color: 0xb2d3e6,
    side: THREE.DoubleSide
  });
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  // 以自身中心为旋转轴,绕 x 轴顺时针旋转 45 度
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, -4, 0);
  scene.add(plane);
}

function init() {

  camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 10, 2000 );
  camera.up.set(0, 1, 0);
  camera.position.set(60, 40, 60);
  camera.lookAt(0, 0, 0);

  scene = new THREE.Scene();
  scene.background = new THREE.Color( '#ccc' );

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  labelRenderer = new CSS2DRenderer();
  labelRenderer.setSize( window.innerWidth, window.innerHeight );
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0px';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.getElementById( 'container' ).appendChild( labelRenderer.domElement );

  controls = new OrbitControls( camera, renderer.domElement );
  controls.mouseButtons = {
    LEFT: THREE.MOUSE.PAN,
    MIDDLE: THREE.MOUSE.DOLLY,
    RIGHT: THREE.MOUSE.ROTATE
  };
  controls.enablePan = true;
  // 设置最大最小视距
  controls.minDistance = 20;
  controls.maxDistance = 1000;

  window.addEventListener( 'resize', onWindowResize );

  stats = new Stats();
  stats.setMode(1); // 0: fps, 1: ms
  document.body.appendChild( stats.dom );

  gpuPanel = new GPUStatsPanel( renderer.getContext() );
  stats.addPanel( gpuPanel );
  stats.showPanel( 0 );

  scene.add( group );

  document.addEventListener('click', onDocumentMouseMove, false);
}

function initLight() {
  const light = new THREE.DirectionalLight(new THREE.Color('rgb(253,253,253)'));
  light.position.set(100, 100, -10);
  light.intensity = 3; // 光线强度
  light.castShadow = true; // 是否有阴影
  light.shadow.mapSize.width = 2048; // 阴影像素
  light.shadow.mapSize.height = 2048;
  // 阴影范围
  const d = 80;
  light.shadow.camera.left = -d;
  light.shadow.camera.right = d;
  light.shadow.camera.top = d;
  light.shadow.camera.bottom = -d;
  light.shadow.bias = -0.0005; // 解决条纹阴影的出现
  // 最大可视距和最小可视距
  light.shadow.camera.near = 0.01;
  light.shadow.camera.far = 2000;
  const AmbientLight = new THREE.AmbientLight(new THREE.Color('rgb(255, 255, 255)'));
  scene.add( light );
  scene.add( AmbientLight );
}

function initHelp() {
  // const size = 100;
  // const divisions = 5;
  // const gridHelper = new THREE.GridHelper( size, divisions );
  // scene.add( gridHelper );

  // The X axis is red. The Y axis is green. The Z axis is blue.
  const axesHelper = new THREE.AxesHelper( 100 );
  scene.add( axesHelper );
}

function axesHelperWord() {
  let xP = addWord('X轴');
  let yP = addWord('Y轴');
  let zP = addWord('Z轴');
  xP.position.set(50, 0, 0);
  yP.position.set(0, 50, 0);
  zP.position.set(0, 0, 50);
}

function addWord(word) {
  let name = `<span>${word}</span>`;
  let moonDiv = document.createElement( 'div' );
  moonDiv.className = 'label';
  // moonDiv.textContent = 'Moon';
  // moonDiv.style.marginTop = '-1em';
  moonDiv.innerHTML = name;
  const label = new CSS2DObject( moonDiv );
  group.add( label );
  return label;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
  requestAnimationFrame( animate );

  stats.update();
  controls.update();
  labelRenderer.render( scene, camera );
  renderer.render( scene, camera );
}
</script>
</body>
</html>

具体思路代码中都有注释,实在不懂也没关系,函数一封装先用着就是,后面慢慢理解!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1069252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用postman 调用 Webservice 接口

1. 先在浏览器地址栏 访问你的webService地址 地址格式: http://127.0.0.1:8092/xxxx/ws(这个自己的决定)/xxxxXccv?wsdl 2. post man POST 访问wwebService接口 地址格式: http://127.0.0.1:8092/xxxx/ws(这个自己的决定)/xxxxXccv <soapenv:Envelope xmlns:soapenv…

优优嗨聚集团:OTC药品能否纳入报销成为各方关注焦点,对OTC医疗有何影响

随着医疗费用的不断上涨&#xff0c;各方对于非处方药&#xff08;OTC&#xff09;能否纳入报销的关注度也在不断提升。OTC药品是指无需医生处方即可购买的药品&#xff0c;具有方便快捷、安全有效的特点。然而&#xff0c;对于是否将OTC药品纳入报销范围&#xff0c;各方看法不…

【Redis】缓存穿透、击穿和雪崩

文章目录 一、Redis缓存概念二、缓存穿透&#xff08;查不到&#xff09;1、概念2、解决方案2.1、布隆过滤器2.2、缓存空对象 三、缓存击穿&#xff08;量太大&#xff0c;缓存过期&#xff01;&#xff09;1、概述2、解决方案 四、缓存雪崩1、概念2、解决方案 面试高频 服务的…

Win11 安装安卓子系统方法教程

WIN11安装安卓子系统 准备工作下载安装安装完成使用adb连接子系统结束 准备工作 开启电脑中的 控制面板>>>>程序和功能>>启用或关闭Windows功能>>>找到“Hyper-V”&#xff0c;把勾都勾上&#xff0c;确定&#xff0c;完成安装&#xff0c;并重启电…

【数据库问题】删除数据库失败,提示:there is 1 other session using the database

删除数据库失败&#xff0c;提示&#xff1a;there is 1 other session using the database 解决办法&#xff1a; SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE datnametest_database AND pid<>pg_backend_pid(); 使用上述命令先关…

0101idea运行scala-基础入门-scala

1 前言 scala基于不同应用&#xff0c;可以以不同方式运行。本人目前从事Java开发&#xff0c;开发工具idea&#xff0c;这里就介绍idea创建工程运行scala的方式。环境如下&#xff1a; 软件&#xff08;工具&#xff09;版本描述scala2.12.11idea2022.3开发工具Javajdk1.8ma…

ASPICE标准快速掌握「2.2. 过程参考模型(Process Reference Model,PRM)」

ASPICE归纳了大量的历史经验,分门别类总结出了适用于所有项目的过程。并将所有过程依据过程类别进行分组,并根据他们所处的活动类别在过程组内进一步划分。总共有 3 个过程类别: 主要生命周期过程组织生命周期过程支持生命周期过程上面的每个过程类别都又往下细分为1-N个子过…

聊聊分布式架构01——http通信基础

目录 web通信的简单结构 网络通信基础TCP/IP TCP/IP 通信传输流 HTTP中的三剑客 负责传输的IP协议 确保可靠性的TCP协议 SYN攻击&#xff08;SYN Flood Attack&#xff09; 四次挥手 负责域名解析的DNS服务 基于 TCP 协议实现通信 TCP 协议的通信过程 Web通信的简单…

【面试经典150 | 哈希表】赎金信

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;哈希表方法二&#xff1a;数组模拟哈希表 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带…

Node.js代码漏洞扫描工具介绍——npm audit

npm audit 运行安全检查 主要作用&#xff1a;检查命令将项目中配置的依赖项的描述提交到默认注册中心&#xff0c;并要求报告已知漏洞。如果发现任何漏洞&#xff0c;则将计算影响和适当的补救措施。如果 fix 提供了参数&#xff0c;则将对包树应用补救措施。 具体参考&#x…

php 安装mongodb扩展模块,rdkafka模块

mongodb mongodb扩展下载 选择php版本&#xff0c;根据报错提示&#xff0c;选择扩展对应的版本选择非安全进程将php_mongodb.dll放到php/ext目录下修改php.ini配置&#xff0c;添加extensionphp_mongodb.dll开启php_mongodb扩展&#xff0c;重启服务php -m 查看是否开启成功…

vue3 -- ts封装 Turf.js地图常用方法

Turf.js中文网 地理空间分析库,处理各种地图算法 文档地址 安装 Turf 库 npm install @turf/turf创建src/hooks/useTurf.ts 文件1:获取线中心点 效果: 代码: useTurf.ts import * as turf from @turf/turf// 获取线中心点 export class CenterPointOfLine {

使用springboot服务端远程调试? 试试HTTP实现服务监听

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《初阶数据结构》《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 wi…

ws升级为wss

需求&#xff1a;项目中需要使用netty&#xff0c;本地测试的时候使用的是ws&#xff0c;然后要部署到服务器上&#xff0c;使用https连接&#xff0c;https下就不能用ws了&#xff0c;必须升级到wss 1.阿里云申请免费证书 2.保存证书到本地目录 3.修改代码 SslUtil 工具类…

kt6368A双模蓝牙芯片无法透传 可能是什么问题呢

一、问题简介- kt6368A蓝牙芯片无法透传 可能是什么问题呢&#xff1f; KT6368A蓝牙芯片&#xff0c;在使用上还是非常的简单&#xff0c;总共也就8个腿&#xff0c;焊接也是很容易的事情 出现不能透传&#xff0c;大概率有如下2点原因 硬件问题&#xff0c;比如&#xff1…

学习笔记|串口通信的基础知识|同步/异步|RS232|常见的串口软件的参数|STC32G单片机视频开发教程(冲哥)|第二十集:串口通信基础

目录 1.串口通信的基础知识串口通信(Serial Communication)同步/异步&#xff1f;全双工&#xff1f;常见的串口软件的参数 2.STC32的串口通信实现原理引脚选择&#xff1a;实现分时复用模式选择串口1模式1&#xff0c;模式1波特率计算公式 3.串口通信代码实现编写串口1通信程序…

C++深入学习part_2

面向对象思想 这一节Java里面都有类似的概念&#xff0c;比较简单&#xff0c;就不写的太啰嗦了。 首先是认识以前面向过程的思想&#xff1a; 而现在再来感受一下面向对象的思想&#xff1a; 这玩意儿学过Java的话应该是比较简单的&#xff0c;就不再赘述了。 类的定义 代…

数据安全防护:云访问安全代理(CASB)

云访问安全代理&#xff08;Cloud Access Security Broker&#xff0c;CASB&#xff09;&#xff0c;是一款面向应用的数据防护服务&#xff0c;基于免应用开发改造的配置方式&#xff0c;提供数据加密、数据脱敏功能。数据加密支持国密算法&#xff0c;提供面向服务侧的字段级…

玄子Share- IDEA 2023 Web 热部署

玄子Share- IDEA 2023 热部署 添加热部署依赖 普通 Web 项目使用热部署插件需指定版本 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><version>3.1.3</version><!-…

nrm 安装教程(图文教程)

序&#xff1a; 1、都知道nvm解决的是node版本切换的问题&#xff0c;nrm 解决的是则是npm指向的问题。 2、雪狼的公众号&#xff1a;“程序员野区”&#xff0c;也许未来的莫一天&#xff0c;你会用到&#xff0c;不凡先进来瞅瞅公众号都分享了啥内容 正文&#xff1a; 1、安…