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);
}
}
}