【工作记录】Threejs学习笔记-引入OrbitControls

news2024/10/6 1:13:49

前言

前一篇文章我们介绍了three.js中的基础概念,并给出了展示整体流程的一个简单示例, 本文我们继续研究。

问题

我们在很多3d效果图上都能看到鼠标移动或者缩进实现旋转或者放大缩小的效果,这个在three.js中是通过OrbitControls这个组件实现的。

早上在使用threejs引入OrbitControls的时候发现新版本(大约r159以后)的引入方式都是通过import来引入的,而我想在纯html中做简单测试,

然而如果在html中直接使用import又会提示出各种各样的错误,经过反复的踩坑和实验以及百度,终于解决了这个模块化导入的问题。

所以本文姑且算是一篇踩坑记录吧。

期望目标

本文我们还是通过简单示例来展示要描述的问题和解决方案。

最终实现的效果是两个虚线圆环,自动转动,也可以根据鼠标拖拽或者缩放来触发旋转和缩放效果。

关于虚线圆环的实现方式和参数设置不是本文重点,可参考本文后面的代码进行实现和测试。

开始

前面提到要实现鼠标控制旋转和缩放需要用到OrbitControls这个组件,这个组件在three.js的源码中可以找到。

下载three.js源码

下载地址: https://gitee.com/mirrors/three.js

可以在标签中选择版本进行下载,截止发文时的最新版本是r162

源码下载

新建目录

目录结构如下图:
目录结构
可以直接把下载下来的源码文件中的对应文件夹直接搬过来,也可以只在对应目录放置需要的文件(本文中主要是用到了three.js或者three.module.js/OrbitControls.js这两个文件)。

引入js

这里需要说明一下,在r159版本以前是提供纯js版本的OrbitControls的代码的,位置在examples/js/controls文件夹中,但是后面新出的版本(r160+)删除了js目录,新增了jsm目录,存放的是以模块化的方式实现的组件代码,这个模块化代码直接在html中引用的话控制台会报错。

这里直接说解决方案:

  • 使用旧版本的OrbitControls.js文件,可以直接在html中引入。

    <script src="static/r159/build/three.min.js"></script>
    <script src="static/r159/examples/js/OrbitControls.js"></script>
    <script>
    ....
    // 创建并初始化OrbitControls
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    </script>
    
  • 使用新版本的模块化方式引入,需要调整引入方式

    <script type="importmap">
            {
                "imports": {
                    "three": "../static/latest/build/three.module.js",
                    "three/addons/": "../static/latest/examples/jsm/"
                }
            }
        </script>
    
    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        ....
        // 创建并初始化OrbitControls
        const controls = new OrbitControls(camera, renderer.domElement);
    	....
    </script>
    

PS: 注意以上两种方案在实例化OrbitControls对象时是有区别的。

OrbitControls的介绍和属性配置

THREE.OrbitControls是Three.js库中一个非常实用的相机控制器,它允许用户通过鼠标或触摸设备以直观的方式旋转、缩放和平移3D场景。这个控件主要是为了让用户能够交互式地查看3D模型或场景,模拟类似3D建模软件中的轨道摄像机操作。

以下是关键参数配置和示例:

用途代码示例
新建对象const controls = new OrbitControls(camera, renderer.domElement);
设置相机围绕的目标点(焦点)controls.target.set(x, y, z); // 默认为(0, 0, 0)
是否启用控件controls.enabled = true //默认为true
控制功能开关controls.enableRotate = true; // 是否允许旋转,默认为true
controls.enableZoom = true; // 是否允许缩放,默认为true
controls.enablePan = true; // 是否允许平移,默认为true
限制范围controls.minDistance = 1; // 相机离目标的最小距离,默认值通常取决于场景大小
controls.maxDistance = 1000; // 相机离目标的最大距离
controls.minZoom = 0.1; // 最小缩放比例(如果支持)
controls.maxZoom = 100; // 最大缩放比例(如果支持)
旋转速度与灵敏度controls.rotateSpeed = 1.0; // 旋转速度,默认值为1
controls.zoomSpeed = 1.2; // 缩放速度,默认值为1
controls.panSpeed = 0.8; // 平移速度,默认值为0.8
旋转限制controls.minAzimuthAngle = -Math.PI / 2; // 最小左旋角度
controls.maxAzimuthAngle = Math.PI / 2; // 最大右旋角度
controls.minPolarAngle = 0; // 最小抬头角度(防止相机倒置)
controls.maxPolarAngle = Math.PI / 2; // 最大低头角度(比如只允许鸟瞰视角)

OrbitControls本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的360度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围。

controls.addEventListener('change', function () {
    // 浏览器控制台查看相机位置变化
    console.log('camera.position',camera.position);
});

补充知识

1. importMap的使用场景

import map 是一种Web平台的原生模块加载映射机制,它允许开发者在浏览器中自定义模块导入路径与实际加载地址之间的映射关系。通过 <script type="importmap"> 标签在HTML文档中定义一个 import map,可以解决以下使用场景:

  1. 模块路径重定向
    • 当项目依赖了多个库,而这些库可能因为版本更新、CDN地址变化或者内部模块结构调整等原因需要修改其导入路径时,import map 可以集中管理这些映射关系,无需更改代码中的 import 语句。
  2. 命名空间或包结构支持
    • 在Node.js环境和一些构建工具中,开发者习惯于使用类似 npm 的包管理和导入方式(如 import { someModule } from 'package-name')。import map 提供了一种在浏览器环境中模拟这种行为的方法,使得大型项目能够更好地组织和维护模块间的依赖关系。
  3. 多版本共存与按需加载
    • 同一项目中可能需要同时使用不同版本的库,import map 可以将不同的模块版本映射到不同的URL上,实现多个版本的同时加载与使用,避免版本冲突。
  4. 优化加载策略
    • 开发者可以通过 import map 将模块的源码映射到经过编译、压缩或缓存优化后的URL上,从而提升加载速度和用户体验。
  5. 本地开发与生产环境切换
    • 在开发阶段,可能需要从本地文件系统加载模块;而在部署上线后,则需要从CDN或其他远程服务器加载。import map 可以灵活配置这些差异化的加载路径。

以下为示例代码

<script type="importmap">
{
  "imports": {
    "module-a": "/path/to/module-a.js",
    "module-b": "//cdn.example.com/module-b.js",
    "package-name": "/local/path/to/package-name/index.js"
  }
}
</script>

然后在JavaScript模块中就可以按照映射关系来导入模块:

import { someFunction } from 'module-a';
import * as packageApi from 'package-name';

2. type=“module”的使用场景

type="module" 属性在HTML <script> 标签中使用,用于指示浏览器按照ECMAScript模块(ES6 Modules)的规范来加载和执行JavaScript代码。以下是 type="module" 使用的主要场景:

  1. 模块化开发
    • 当你的项目采用了ES6模块化机制,通过 importexport 语句导入和导出模块时,需要在引用这些模块的 <script> 标签中设置 type="module"
  2. 异步加载与依赖管理
    • ES6模块支持异步加载,浏览器会并行加载多个模块,然后根据模块间的依赖关系按序执行。
    • 这种方式可以避免传统的脚本阻塞页面渲染,提升页面加载性能。
  3. 代码组织与复用
    • 随着项目规模扩大,将代码拆分成多个模块进行管理和复用是非常必要的。type="module" 允许开发者定义独立的、可维护的模块,并确保每个模块有自己独立的作用域。
  4. 避免全局命名空间污染
    • 在模块内部定义的变量、函数等不会自动添加到全局作用域,这有助于减少不同模块之间的命名冲突问题。
  5. 现代前端框架配合
    • 现代前端框架如Vue.js、React.js等,在构建工具配置下通常默认采用模块化开发,即便在实际应用中不直接写 <script type="module">,但在构建后的产物或运行环境支持模块化的现代浏览器上,依然受益于模块化机制。
  6. 跨域限制
    • 注意,当在本地文件系统(file://)上直接打开带有 type="module" 的HTML文件时,由于浏览器安全策略,默认不允许跨域请求本地文件,因此可能无法正常加载模块。解决办法是使用像VSCode的Live Server插件或者部署到支持HTTP协议的本地服务器环境来预览和调试。

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>3D Map with Three.js</title>
    <style>
        body {
            margin: 0;
        }

        canvas {
            display: block;
        }
    </style>
</head>

<body>
    <script type="importmap">
        {
            "imports": {
                "three": "../static/latest/build/three.module.js",
                "three/addons/": "../static/latest/examples/jsm/"
            }
        }
    </script>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
        // //场景
        const scene = new THREE.Scene();
        //透视投影相机
        const camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
        //创建渲染器 设置抗锯齿属性为true
        const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        //作为元素添加到html中
        document.body.appendChild(renderer.domElement);
        camera.position.z = 5;

        // resize 事件
        window.addEventListener("resize", () => {
            let width = window.innerWidth;
            let height = window.innerHeight;
            renderer.setSize(width, height);
            camera.aspect = width / height;
            /**
             * updateProjectionMatrix() 是 Three.js 中的一个方法,
             * 通常用于相机(Camera)对象。在Three.js中,当你更改了相机的投影参数(如透视相机的视场角、近裁剪面或远裁剪面等),
             * 或者更改了相机的位置、朝向等影响其投影矩阵的因素时,需要调用此方法来更新相机的内部投影矩阵。
             */
            camera.updateProjectionMatrix();
        });

        // 定义旋转速度
        const clockwiseRotationSpeed = -0.005; // 顺时针旋转速度
        const counterclockwiseRotationSpeed = 0.005; // 逆时针旋转速度

        // 创建虚线圆圈的函数(这里简化为8段虚线)
        function createDashedCircle(radius, segments, dashSize, gapSize) {
            const vertices = [];
            const indices = [];

            for (let i = 0; i <= segments * 4; i++) { // 每个点分割为dashSize和gapSize两部分
                const angle = i / (segments * 4) * Math.PI * 2;
                const x = radius * Math.cos(angle);
                const y = radius * Math.sin(angle);

                if (i % 4 === 0) {
                    vertices.push(x, y, 0); // dash起点
                    vertices.push(x + dashSize, y, 0); // dash终点
                    if (i > 0 && i !== segments * 12) { // 添加索引以形成线条
                        indices.push(i - 4, i - 3, i - 2, i - 1);
                    }
                } else if (i % 4 === 2) {
                    vertices.push(x + dashSize + gapSize, y, 0); // gap终点同时也是下一个dash的起点
                }
            }

            const geometry = new THREE.BufferGeometry();
            geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
            geometry.setIndex(indices);

            const material = new THREE.LineBasicMaterial({ color: 0x00ff00 });

            return new THREE.LineSegments(geometry, material);
        }

        // 创建并添加两个虚线圆圈到场景
        const outerDashedCircle = createDashedCircle(3, 120, 0.01, 0.01);
        outerDashedCircle.position.z = -1;
        scene.add(outerDashedCircle);

        const innerDashedCircle = createDashedCircle(2.5, 120, 0.01, 0.01);
        innerDashedCircle.position.z = -2;
        scene.add(innerDashedCircle);
        
         // 创建并初始化OrbitControls
        const controls = new OrbitControls(camera, renderer.domElement);
        // 设置OrbitControls的一些参数,例如只允许沿着X轴旋转
        controls.enableDamping = true; // 使动画更平滑
        controls.dampingFactor = 0.05;
        controls.screenSpacePanning = false; // 禁止屏幕空间平移
        controls.minDistance = 2;
        controls.maxDistance = 10;
        //设置横向和纵向可旋转角度范围 可以通过尝试并调整
        controls.maxAzimuthAngle = (45*Math.PI)/100
        controls.minAzimuthAngle = (0*Math.PI)/100;
        controls.maxPolarAngle = (90*Math.PI)/100
        controls.minPolarAngle = (0*Math.PI)/100
        controls.update()

        // 环境光
        /**
         * AmbientLight 是 Three.js 中的一种光源类型,
         * 它模拟环境光的效果,即场景中的每个点都会受到相同强度和颜色的光照。
         * 在三维场景中添加 AmbientLight 可以提供全局的基础照明。
         * @type {AmbientLight}
         */
        const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
        scene.add(ambientLight);
        // const helper = new THREE.CameraHelper(camera)
        // scene.add(helper)

        // 每帧更新场景和控制
        function animate() {
            requestAnimationFrame(animate);
            controls.update(); // 更新OrbitControls的状态
            outerDashedCircle.rotation.z += clockwiseRotationSpeed;
            innerDashedCircle.rotation.z += counterclockwiseRotationSpeed;
            renderer.render(scene, camera);
        }

        animate();

    </script>
</body>

</html>

整体代码结构跟上篇文章介绍的差不多,中间多了OrbitControls的引入和配置,再就是虚线双环的实现。

最终效果

threejs实现的双圆环效果图

问题记录

  1. 控制台报错: Uncaught SyntaxError: Cannot use import statement outside a module

    这个问题主要是在script中引入了模块化的组件,但是没有配置参数type=“module”

    解决方案就是:

    <script type="module">
        ...
    </script>
    
  2. 控制台报错:

Refused to execute script from 'http://127.0.0.1:5500/demo/static/latest/build/three.module.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
circle_r159_module.html:1  Refused to execute script from 'http://127.0.0.1:5500/demo/static/latest/examples/jsm/controls/OrbitControls.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.
circle_r159_module.html:36  Uncaught ReferenceError: THREE is not defined
    at circle_r159_module.html:36:23

这个问题就是上面提到的模块化实现直接在html中引用导致的问题,解决方案如上文所述,推荐使用importMap解决。

<script type="importmap">
    {
        "imports": {
            "three": "../static/latest/build/three.module.js",
            "three/addons/": "../static/latest/examples/jsm/"
        }
    }
</script>

同时也要注意在引用的时候script标签添加type=”module“属性,然后引用的时候使用import关键字即可。

  1. 控制台报错找不到js文件等问题

    从以下几个方向检查:

    • 检查路径是否正确,路径下是否存在目标文件
    • 检查路径是否包含中文字符
    • 检查路径是否包含.等特殊字符

总结

本文记录了在不依赖vue等模块化开发框架的基础上实现OrbitControls.js的引入过程,解决了模块化组件引入的问题,希望能帮助到需要的朋友。

针对以上问题有任何问题或者建议欢迎留言交流。

创作不易欢迎一键三连~~~

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

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

相关文章

计网面试题整理上

1. 计算机网络的各层协议及作用&#xff1f; 计算机网络体系可以大致分为一下三种&#xff0c;OSI七层模型、TCP/IP四层模型和五层模型。 OSI七层模型&#xff1a;大而全&#xff0c;但是比较复杂、而且是先有了理论模型&#xff0c;没有实际应用。TCP/IP四层模型&#xff1a…

应急响应实战笔记03权限维持篇(6)

第6篇&#xff1a;三大渗透测试框架权限维持技术 0x00 前言 在渗透测试中&#xff0c;有三个非常经典的渗透测试框架----Metasploit、Empire、Cobalt Strike。 那么&#xff0c;通过漏洞获取到目标主机权限后&#xff0c;如何利用框架获得持久性权限呢&#xff1f; 0x01 MS…

【递归搜索回溯专栏】专题一递归:快速幂

本专栏内容为&#xff1a;递归&#xff0c;搜索与回溯算法专栏。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;递归搜索回溯专栏 &#x1f69a;代码仓库&#xff1a;小小unicorn的代…

折线图 温度变化曲线图

代码详情介绍 导入必要的库&#xff1a; matplotlib.pyplot&#xff1a;用于绘图。 matplotlib.font_manager&#xff1a;用于设置中文字体。 datetime&#xff1a;用于处理日期和时间。 random&#xff1a;用于生成随机数。 numpy&#xff1a;用于生成arange函数的刻度。 设置…

递推与递归DFS

&#xff1b;例题引入&#xff1a; 在跳楼梯问题中&#xff0c;我们假设每次可以跳1级或2级。如果我们想跳到第N级台阶&#xff0c;那么我们的最后一次跳跃只能是1级或2级。 如果我们最后一次跳1级&#xff0c;那么我们必须先跳到第N-1级台阶。由于跳到第N-1级台阶有f(N-1)种方…

【web安全】实战 批量横扫springboot命令执行漏洞

天命&#xff1a;这次目标批量横扫&#xff0c;但是没完全成功&#xff0c;也没完全失败 步骤1&#xff1a;磨刀准备 这次先针对漏洞来寻找目标&#xff0c;所以寻找这种 springboot 的目标 利用CVE漏洞&#xff0c;进行命令执行攻击 先找靶场训练一波&#xff0c;叠加反弹sh…

【Linux】Linux网络故障排查与解决指南

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 检查网络连接状态&#xff1a; 检查路由表&#xff1a; 检查DNS配置&#xff1a; 检查网络连接状态&#xff1a; 检查防火墙设…

用python和pygame库实现刮刮乐游戏

用python和pygame库实现刮刮乐游戏 首先&#xff0c;确保你已经安装了pygame库。如果没有安装&#xff0c;可以通过以下命令安装&#xff1a; pip install pygame 示例有两个。 一、简单刮刮乐游戏 准备两张图片&#xff0c;一张作为背景bottom_image.png&#xff0c;一张作…

C++ 链表OJ

目录 1、2. 两数相加 2、24. 两两交换链表中的节点 3、143. 重排链表 4、23. 合并 K 个升序链表 5、25. K 个一组翻转链表 解决链表题目常用方法&#xff1a; 1、画图 2、引入虚拟"头”结点 便于处理边界情况方便我们对链表操作 3、大胆定义变量&#xff0c;减少连接…

使用nvidia-ml-py事实监控GPU状态

平时监控GPU状态最常用的是watch配合nvidia-smi指令&#xff0c;但有时可能不仅仅需要监控&#xff0c;还需要记录状态数据&#xff0c;比如GPU的显存变化以及利用率变化等等。本文提供了一个使用nvidia-ml-py包编写的简易Demo&#xff0c;该Demo能够实现简易版的nvidia-smi功能…

Linux网络隧道协议IPIP认知(基于Linux network namespace 的 IPIP 隧道通信)

写在前面 博文内容为 Linux 隧道通信 IPIP认知内容涉及&#xff1a;ipip 介绍&#xff0c;一个 ipip 通信 Demo 以及数据帧流转分析理解不足小伙伴帮忙指正 某些人和事&#xff0c;哪怕没有缘分&#xff0c;是路边的风景&#xff0c;可是只要看一眼&#xff0c;依然会让人觉得…

Java中使用Jsoup实现网页内容爬取与Html内容解析并使用EasyExcel实现导出为Excel文件

场景 Pythont通过request以及BeautifulSoup爬取几千条情话&#xff1a; Pythont通过request以及BeautifulSoup爬取几千条情话_爬取情话-CSDN博客 Node-RED中使用html节点爬取HTML网页资料之爬取Node-RED的最新版本&#xff1a; Node-RED中使用html节点爬取HTML网页资料之爬…

mybatis单表curd笔记(尚硅谷

Mybatis 11111ibatis和mybatis不同 查询文档mybatis的日志输出id赋值输入&#xff08;向sql语句传入数据单个简单类型单个实体对象多个简单类型map类型 输出数据的指定单个简单类型单个实体类型输出map类型输出list输出类型主键回显&#xff08;自增长类型主键回显&#xff08;…

强大的ps 命令 -o 自定义输出内容选项

强大的ps 命令 -o 自定义输出内容选项 1、ps命令介绍和作用2、问题描述 1、ps命令介绍和作用 ps 是一个 Unix 和类 Unix 操作系统中常用的命令&#xff0c;用于显示当前运行的进程信息。ps 命令的作用包括&#xff1a; 查看进程信息&#xff1a; ps 命令可以列出当前系统中正…

【自然语言处理】【大模型】BitNet:用1-bit Transformer训练LLM

BitNet&#xff1a;用1-bit Transformer训练LLM 《BitNet: Scaling 1-bit Transformers for Large Language Models》 论文地址&#xff1a;https://arxiv.org/pdf/2310.11453.pdf 相关博客 【自然语言处理】【大模型】BitNet&#xff1a;用1-bit Transformer训练LLM 【自然语言…

Matlab梁单元有限元编程 | 铁木辛柯梁 | 欧拉梁 | Matlab源码 | 理论文本

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

【网络安全】漏洞挖掘入门教程(非常详细),小白是如何挖漏洞(技巧篇)0基础入门到精通!

温馨提示&#xff1a; 初学者最好不要上手就去搞漏洞挖掘&#xff0c;因为漏洞挖掘需要很多的系统基础知识和一些理论知识做铺垫&#xff0c;而且难度较大…… 较合理的途径应该从漏洞利用入手&#xff0c;不妨分析一些公开的CVE漏洞。很多漏洞都有比较好的资料&#xff0c;分…

手写分布式配置中心(四)增加实时刷新功能(长轮询)

上一篇文章中实现了短轮询&#xff0c;不过短轮询的弊端也很明显&#xff0c;如果请求的频率较高&#xff0c;那么就会导致服务端压力大&#xff08;并发高&#xff09;&#xff1b;如果请求的频率放低&#xff0c;那么客户端感知变更的及时性就会降低。所以我们来看另一种轮询…

总结归纳Kubernetes | 一站式速查知识,助您轻松驾驭容器编排技术(水平扩展控制)

Kubernetes&#xff0c;亦被称为K8s&#xff0c;是业界公认的容器编排巨擘&#xff0c;以其卓越的能力简化了容器化应用的部署、扩展和管理流程。通过其强大的功能&#xff0c;Kubernetes不仅提升了应用的可靠性和可伸缩性&#xff0c;还优化了资源利用率&#xff0c;为开发者和…

MM配置1-定义、复制、删除、检查工厂

配置步骤&#xff0c;如下图&#xff1a; 双击“复制&#xff0c;删除&#xff0c;检查工厂选项” 点击“复制”按钮&#xff0c;输入参考工厂&#xff0c;和要新建的工厂 复制完成后&#xff0c;返回到上一界面&#xff0c;双击“定义工厂”选项 选中新建的1070工厂&#xff0…