three.js 9 - TransformControls
import * as THREE from 'three'; import { TransformControls } from "three/examples/jsm/controls/TransformControls"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; /** * 3d Controls 控制器,变换控制器(TransformControls) * https://threejs.org/docs/index.html#examples/zh/controls/TransformControls */ export class ThreeDoc9ControlTransform { constructor(canvasId) { this.work(canvasId); } work(canvasId) { // 创建 3d 场景 const scene = new THREE.Scene(); scene.background = new THREE.Color(0x9e9e9e); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); // 最后一步很重要,我们将renderer(渲染器)的dom元素(renderer.domElement)添加到我们的HTML文档中。这就是渲染器用来显示场景给我们看的<canvas>元素。 document.body.appendChild(renderer.domElement); // 点材质(PointsMaterial) this.getPointsMaterial(scene); // AxesHelper 3个坐标轴的对象. this.addAxesHelper(scene); // 半球光(HemisphereLight) this.addHemisphereLight(scene); // 网格辅助对象 let size = 20; const gridHelper = new THREE.GridHelper(size, 10, 0x444444, 0xffffff); scene.add(gridHelper); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); // 设置相机位置 camera.position.x = 5; camera.position.y = 6; camera.position.z = 15; camera.lookAt(0, 0, 0); // 添加物体 let geometry = new THREE.BoxGeometry(2, 2, 2); let material = new THREE.MeshStandardMaterial({ color: 0x049EF4 }); let obj = new THREE.Mesh(geometry, material); obj.position.set(0, 1, 0); scene.add(obj); // 添加鼠标操作视图 let orbC = this.initMouseControl(scene, camera, renderer); // 添加拖动事件,返回平移控件对象 let transformControls = this.initDragControl(scene, camera, renderer, orbC); // 添加操作面板,切换控制器类型 this.addControlPanel(renderer, scene, camera, transformControls); // 添加点击事件,设置物体控制器显示 this.addClickEvent(scene, camera, transformControls, renderer); renderer.render(scene, camera); } /** * AxesHelper * 用于简单模拟3个坐标轴的对象. * 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴. * AxesHelper( size : Number ) * size -- (可选的) 表示代表轴的线段长度. 默认为 1. */ addAxesHelper(scene) { const axesHelper = new THREE.AxesHelper(12); scene.add(axesHelper); } /** * 半球光(HemisphereLight) - 喜欢这个光 * 光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。 * 半球光不能投射阴影。 * * HemisphereLight( skyColor : Integer, groundColor : Integer, intensity : Float ) * skyColor - (可选参数) 天空中发出光线的颜色。 缺省值 0xffffff。 * groundColor - (可选参数) 地面发出光线的颜色。 缺省值 0xffffff。 * intensity - (可选参数) 光照强度。 缺省值 1。 */ addHemisphereLight(scene) { const light = new THREE.HemisphereLight(0xffffbb, 0x080820, 50); scene.add(light); light.position.set(0, -20, 0); // const helper = new THREE.HemisphereLightHelper(light, 3); // scene.add(helper); } /** * 点材质(PointsMaterial) * Points使用的默认材质。 * PointsMaterial( parameters : Object ) * parameters - (可选)用于定义材质外观的对象,具有一个或多个属性。 材质的任何属性都可以从此处传入(包括从Material继承的任何属性)。 * * 属性color例外,其可以作为十六进制字符串传递,默认情况下为 0xffffff(白色),内部调用Color.set(color)。 */ getPointsMaterial(scene) { const vertices = []; for (let i = 0; i < 10000; i++) { const x = THREE.MathUtils.randFloatSpread(2000); const y = THREE.MathUtils.randFloatSpread(2000); const z = THREE.MathUtils.randFloatSpread(2000); vertices.push(x, y, z); } const geometry = new THREE.BufferGeometry(); geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); const material = new THREE.PointsMaterial({ color: 0xffffff }); const points = new THREE.Points(geometry, material); scene.add(points); } /** * 鼠标操作 */ initMouseControl(scene, camera, renderer) { // 添加鼠标操作 let controls = new OrbitControls(camera, renderer.domElement); controls.addEventListener('change', () => { renderer.render(scene, camera); }); return controls; } // 添加拖拽控件 initDragControl(scene, camera, renderer, orbC) { /** * 变换控制器(TransformControls) * 该类可提供一种类似于在数字内容创建工具(例如Blender)中对模型进行交互的方式,来在3D空间中变换物体。 和其他控制器不同的是,变换控制器不倾向于对场景摄像机的变换进行改变。 * TransformControls 期望其所附加的3D对象是场景图的一部分。 * * TransformControls( camera : Camera, domElement : HTMLDOMElement ) * camera: 被控制的摄像机。 * domElement: 用于事件监听的HTML元素。 * 创建一个新的 TransformControls 实例。 * * 事件 * change : 如果发生了任何类型的改变(对象或属性的改变)则触发该事件。 属性改变是单独的事件,你也可以为此添加单独的事件监听; * 该事件类型为"propertyname-changed"(“属性名称-changed”)。 * mouseDown : 如果指针(鼠标/触摸)为活动状态则触发该事件。 * mouseUp : 如果指针(鼠标/触摸)不再为活动状态则触发该事件。 * objectChange : 如果被控制的3D对象发生改变则触发该事件。。 * * 属性 * 共有属性请参见其基类Object3D。 * .axis : String : 当前变换轴。 * .camera : Camera : 渲染场景的摄像机。 * * .domElement : HTMLDOMElement : 该 HTMLDOMElement 用于监听鼠标/触摸事件,该属性必须在构造函数中传入。 * 在此处改变它将不会设置新的事件监听。 * * .dragging : Boolean : 当前是否正在拖动。只读属性。 * .enabled : Boolean : 是否启用控制器。默认为true。 * .mode : String : 当前的变换模式。可能的值包括"translate"、"rotate" 和 "scale"。默认为translate。 * .object : Object3D : 正在被控制的3D对象。 * * .rotationSnap : Number : 默认情况下,3D对象是可以被连续旋转的。如果你将该值设为一个数值(弧度), * 则你将可以定义每次旋转3D对象时的步幅。 默认为null。 * * .showX : Boolean : x轴手柄是否显示。默认为true。 * .showY : Boolean : y轴手柄是否显示。默认为true。 * .showZ : Boolean : z轴手柄是否显示。默认为true。 * .size : Number : 手柄UI(轴/平面)的大小。默认为1。 * .space : String : 定义了在哪种坐标空间中进行变换。可能的值有"world" 和 "local"。默认为world。 * .translationSnap : Number : 默认情况下,3D对象是可以被连续平移的。如果你将该值设为一个数值(世界单位),则你将可以定义每次平移3D对象时的步幅。 默认为null。 * * 方法 * 共有方法请参见其基类Object3D。 * * .attach ( object : Object3D ) : TransformControls * object: 应当变换的3D对象。 * * 设置应当变换的3D对象,并确保控制器UI是可见的。 * * .detach () : TransformControls * 从控制器中移除当前3D对象,并确保控制器UI是不可见的。 * * .dispose () : undefined * 若不再需要该控制器,则应当调用此函数。 * * .getRaycaster () : Raycaster * 返回用于用户交互的 Raycaster 对象。 此对象在所有实例之间共享 变换控件。 如果您设置 TransformControls 的 .layers 属性,您还需要 使用匹配值设置 Raycaster 上的 .layers 属性,否则设置 TransformControls 不会按预期工作。 * * .getMode () : String * 返回变换模式。 * * .setMode ( mode : String ) : undefined * mode: 变换模式。 * * 设置变换模式。 * * .setRotationSnap ( rotationSnap : Number ) : undefined * rotationSnap: 旋转捕捉步幅。 * * 设置旋转捕捉。 * * .setSize ( size : Number ) : undefined * size: 手柄UI的大小。 * * 设置手柄UI的大小。 * * .setSpace ( space : String ) : undefined * space: 应用变换的坐标空间。 * * 设置应用变换的坐标空间。 * * .setTranslationSnap ( translationSnap : Number ) : undefined * translationSnap: 平移捕捉步幅。 * * 设置平移捕捉。 * @type {TransformControls} */ let transformControls = new TransformControls(camera, renderer.domElement); scene.add(transformControls); // 监听改变,则更新界面 transformControls.addEventListener("change", () => { renderer.render(scene, camera); }); // 变换控制器监听 mousedown,禁用 鼠标拖拽 transformControls.addEventListener("mouseDown", () => { orbC.enabled = false; }); // transformControls.addEventListener("mouseUp", () => { orbC.enabled = true; }); return transformControls; } /** * 添加操作面板,按钮控制光源对象切换,添加平移控件关联 * @param renderer * @param scene * @param camera * @param transformControls 平移控制器对象 */ addControlPanel(renderer, scene, camera, transformControls) { let ele = ` <div class="control-panel"> <button class="mode-translate">translate</button> <button class="mode-rotate">rotate</button> <button class="mode-scale">scale</button> </div> `; $('body').append(ele); let cols = ['translate', 'rotate', 'scale']; for(let i = 0; i< cols.length; i++){ // 绑定按钮事件 $('.mode-' + cols[i]).on('click', function (e) { // 控制模式改变:cols[i] transformControls.mode = cols[i]; // renderer.render(scene, camera); }); } } /** * 添加鼠标点击判断,是否点击了物体 * @param scene * @param camera * @param transformControls */ addClickEvent(scene, camera, transformControls, renderer) { /** * 使用官方的THREE.Raycaster * THREE.Raycaster是three.js中的射线类,其实现监听的原理是由相机位置为射线起点,由鼠标位置为射线方向发射射线, * 其穿过的所有几何体都会被监测到。 * @type {*[]} */ let intersects = []; //几何体合集 const pointer = new THREE.Vector2(); // 给 canvas 加监听点击事件 document.getElementsByTagName('canvas')[0].addEventListener('click', meshOnClick); let raycaster = new THREE.Raycaster(); function meshOnClick(event) { //geometrys 为需要监听的Mesh合集,可以通过这个集合来过滤掉不需要监听的元素例如地面天空 let geometrys = []; for (let i = 0; i < scene.children.length; i++) { console.log(scene.children[i].isLight); // 物体 和 光源都监听点击 if (scene.children[i].isMesh || scene.children[i].isLight) { geometrys.push(scene.children[i]); } } console.log(geometrys); pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(pointer, camera); // true为不拾取子对象 intersects = raycaster.intersectObjects(geometrys, true); // 被射线穿过的几何体为一个集合,越排在前面说明其位置离端点越近,所以直接取[0] console.log(intersects); if (intersects.length > 0) { transformControls.attach(intersects[0].object); } else { //若没有几何体被监听到,可以做一些取消操作,判断不到光源点击,直接移除控制不可行 // transformControls.detach(); // 当前控制器操控的对象 // console.log(transformControls.object); console.log(1); } renderer.render(scene, camera); } } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步