//导入模型库// 旋转中心点// 创建场景// 创建相机// 创建渲染器//设置背景颜色// 创建立方体//创建文字const texts = ['左', '右', '上', '下', '前', '后'];_threejs 模型交互">
赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
一、入门学习中文网threejs文件包下载和目录简介 | Three.js中文网 (webgl3d.cn)
3.导入模型时屏蔽了OrbitControls的旋转功(controls.enableRotate = false)所以重构了一下模型旋转
把canvas的坐标转换成webgl的坐标,简单讲一下,页面上的event拿到的xy跟webgl的坐标不太一样,webgl的坐标原点是画布canvas宽高的一半就是webgl的原点
鼠标移动事件 拿到的deltaRotationQuaternion相当于旋转角度,cube是左上角的组件,调用子组件函数设置camera旋转后的位置
原本想用div做一个正方体实现立方体于模型互动,但是html的xy轴于webgl的xy轴不一样,在考虑自身水平有限的情况下,最后还是决定用两个场景去实现一个交互效果,有bug功能也没完善。
webgl是前端不一样的赛道
代码如下(示例):
// 引入three.js
import * as THREE from 'three';
// 引入扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
//导入模型库
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
//交互插件
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader"
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
// ShaderPass功能:使用后处理Shader创建后处理通道
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { Line2 } from 'three/examples/jsm/lines/Line2'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import text from "three/examples/fonts/helvetiker_bold.typeface.json"
let that;
function init(x, y, z, bgColor, width, heigth, ref) {
this.x = x;
this.y = y;
this.z = z;
this.width = width;
this.height = heigth;
this.bgColor = bgColor;
this.distance = 0;
this.container = ref
this.startPoint = 0;
this.endPoint = 0;
this.points = []
this.line = null
this.target = new THREE.Vector3(0, 0, 0); // 旋转中心点
this.isDragging = false;
this.previousMousePosition = { x: 0, y: 0 };
const fontLoader = new FontLoader();
this.font = fontLoader.parse(text)
this.scene = new THREE.Scene();// 创建3D场景对象Scene
// 执行一个渲染函数
this.renderer = new THREE.WebGLRenderer();
// AxesHelper:辅助观察的坐标系
this.axesHelper = new THREE.AxesHelper(200);
this.scene.add(this.axesHelper);
this.s = 5;
this.k = (this.width / this.height)
this.reCamera()
//测量起始点
this.measureStart = [];
this.measureEnd = [];
this.isStart = true
//垂直向量
this.perpendicularVector = { x: 0, y: 0, z: 0 }
//网格
// const gridHelper = new THREE.GridHelper(300, 25, 0x004444, "#fff");
// this.scene.add(gridHelper)
//环境光
this.ambient = new THREE.AmbientLight(0xffffff, 1);
this.scene.add(this.ambient)
// 聚光源
// 0xffffff:光源颜色
// 1.0:光照强度intensity
const spotLight = new THREE.SpotLight(0xffffff, 1.0);
spotLight.intensity = 2.0;//光照强度
// 设置聚光光源发散角度
spotLight.angle = Math.PI / 2;//光锥角度的二分之一
// 设置聚光光源位置
spotLight.position.set(0, 0, -200);
this.scene.add(spotLight);//光源添加到场景中
//点光源:两个参数分别表示光源颜色和光照强度
// 参数1:0xffffff是纯白光,表示光源颜色
// 参数2:1.0,表示光照强度,可以根据需要调整
this.pointLight = new THREE.PointLight(0xffffff, 10);
// //点光源位置
this.pointLight.position.set(400, 0, 0);//点光源放在x轴上
this.scene.add(this.pointLight); //点光源添加到场景中
// 光源辅助观察
this.pointLightHelper = new THREE.PointLightHelper(this.pointLight, 10);
this.scene.add(this.pointLightHelper);
this.raycaster = new THREE.Raycaster();
this.mouse = new THREE.Vector2();
//半球光
const light = new THREE.HemisphereLight(0xB1E1FF, 0xB97A20, 1);
this.scene.add(light)
// 平行光
this.directionalLight = new THREE.DirectionalLight(0xffffff, 2);
this.directionalLight.position.set(200, 200, 200);
this.scene.add(this.directionalLight);
this.animate = this.animate.bind(this); // 添加这一行
this.animate()
this.clock = new THREE.Clock();
that = this
}
init.prototype.AddModel = function (path) {
return new Promise((resole, reject) => {
const loader = new GLTFLoader();
loader.load(path, gltf => {
gltf.scene.traverse(child => {
if (child.isMesh) {
// child是网格模型对象Mesh
this.mesh = child
console.log(child)
// 计算模型的边界框
child.geometry.computeBoundingBox();
let box = child.geometry.boundingBox;
this.box = box
// 计算模型的中心点
var center = box.getCenter(new THREE.Vector3());
// 将模型移动到辅助坐标中心
child.geometry.translate(-center.x, -center.y, -center.z);
}
})
let { scene } = gltf
// this.mesh = scene
scene.scale.set(1, 1, 1)
this.scene.add(scene)
this.renderer.setSize(this.width, this.height);
this.renderer.setClearColor(this.bgColor, 1); //设置背景颜色
// this.renderer.render(this.scene, this.camera);
resole(this.renderer)
}, undefined, function (error) {
reject(error)
});
})
}
init.prototype.loader = function (path, boxId) {
this.AddModel(path).then(res => {
document.getElementById(boxId).appendChild(this.renderer.domElement)
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableRotate = false
this.controls.addEventListener('change', this.iniTrenderer);//监听鼠标、键盘事件
}).catch(err => console.log(err))
}
init.prototype.setGuiObj = function (obj) {
this.guiObj = obj
}
init.prototype.setDirPosition = function () {
this.directionalLight.position.set(this.x, this.y, this.z)
this.iniTrenderer()
}
init.prototype.iniTrenderer = function () {
that.renderer.render(that.scene, that.camera);
}
init.prototype.getGui = function () {
this.gui = new GUI();
this.guiObj = {
color: "red"
}
// .addColor()生成颜色值改变的交互界面
this.gui.addColor(this.guiObj, 'color').onChange(value => {
this.mesh.material.color.set(value);
this.iniTrenderer()
});
// gui.addColor(obj, 'specular').onChange(function (value) {
// material.specular.set(value);
// });
// 环境光强度
if (this.ambient) {
this.gui.add(this.ambient, 'intensity', 0, 2).name("环境光强度").onChange(value => {
this.ambient.intensity = value
this.iniTrenderer()
});
}
if (this.directionalLight) {
// 平行光强度
this.gui.add(this.directionalLight, 'intensity', 0, 2).name("平行光强度").onChange(value => {
this.directionalLight.intensity = value
this.iniTrenderer()
});
// 平行光位置
this.gui.add(this.directionalLight.position, 'x', -400, 400).name("平行光位置x").onChange(value => {
this.x = value
this.setDirPosition()
});
this.gui.add(this.directionalLight.position, 'y', -400, 400).name("平行光位置y").onChange(value => {
this.y = value
this.setDirPosition()
});
this.gui.add(this.directionalLight.position, 'z', -400, 400).name("平行光位置z").onChange(value => {
this.z = value
this.setDirPosition()
});
}
}
init.prototype.setCanvas = function (width, height) {
this.width = width;
this.height = height;
this.renderer.setSize(this.width, this.height)
}
init.prototype.setStlModel = function (path, boxId) {
// 创建STL加载器
const loader = new STLLoader();
// 加载STL模型
loader.load(path, (geometry) => {
// 计算模型的边界框
geometry.computeBoundingBox();
let box = geometry.boundingBox;
this.box = box
// 计算模型的中心点
var center = box.getCenter(new THREE.Vector3());
// 将模型移动到辅助坐标中心
geometry.translate(-center.x, -center.y, -center.z);
this.geometry = geometry
const textureLoader = new THREE.TextureLoader();
const material = new THREE.MeshPhongMaterial({
map: textureLoader.load('/maps/textures/rust_coarse_01_disp_4k.png'),
});
// const material = new THREE.MeshStandardMaterial({
// color: 0xffffff, // 设置颜色
// metalness: 1, // 设置金属度
// roughness: 0.5, // 设置粗糙度 (0: 光滑, 1: 粗糙)
// });
this.mesh = new THREE.Mesh(this.geometry, material);
this.scene.add(this.mesh);
this.renderer.setSize(this.width, this.height);
this.s = 600
this.renderer.setClearColor(this.bgColor, 1); //设置背景颜色
this.reCamera()
document.getElementById(boxId).appendChild(this.renderer.domElement)
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableRotate = false
this.controls.addEventListener('change', () => {
this.renderer.render(this.scene, this.camera);
});//监听鼠标、键盘事件
});
}
init.prototype.reCamera = function () {
// 实例化一个透视投影相机对象
this.camera = new THREE.OrthographicCamera(-this.s * this.k, this.s * this.k, this.s, -this.s, 1, 2000);
//相机在Three.js三维坐标系中的位置
// 根据需要设置相机位置具体值
this.camera.position.set(0, 0, 200);
}
init.prototype.setWgcl = function () {
// 生成测量网格
var edges = new THREE.EdgesGeometry(this.geometry);
var lineMaterial = new THREE.LineBasicMaterial({ color: 0xff0000 });
var wireframe = new THREE.LineSegments(edges, lineMaterial);
this.scene.add(wireframe);
}
init.prototype.addObjModle = function (path, boxId) {
var loader = new OBJLoader();
loader.load(path, geometry => {
this.scene.add(geometry);
setTimeout(() => {
this.renderer.render(this.scene, this.camera);
}, 0)
document.getElementById(boxId).appendChild(this.renderer.domElement)
});
}
init.prototype.animate = function () {
requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
}
init.prototype.cancelAnmation = function () {
cancelAnimationFrame(this.req)
}
init.prototype.setContainer = function (ref) {
this.container = ref
}
init.prototype.getMousePosition = function (event) {
const container = this.container;
const rect = container.getBoundingClientRect();
const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
const vector = new THREE.Vector3(x, y, 0.5);
vector.unproject(this.camera);
const dir = vector.sub(this.camera.position).normalize();
const distance = -this.camera.position.z / dir.z;
return this.camera.position.clone().add(dir.multiplyScalar(distance));
}
init.prototype.renderMeasureTool = function () {
if (this.startPoint && this.endPoint) {
const geometry = new THREE.BufferGeometry();
const vertices = [
this.startPoint.x, this.startPoint.y, this.startPoint.z,
this.endPoint.x, this.endPoint.y, this.endPoint.z,
this.startPoint.x, this.startPoint.y, this.startPoint.z
];
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setIndex([0, 1, 2]);
// 创建三角形的材质
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// 创建三角形的网格
const triangle = new THREE.LineSegments(geometry, material);
this.scene.add(triangle);
// 创建距离标记的几何体
const textGeometry = new TextGeometry(
`Distance: ${this.distance.toFixed(2)}`,
{
// font: new THREE.Font(),
size: 0.1,
height: 0.01,
}
);
// 创建距离标记的材质
const textMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
// 创建距离标记的网格
const textMesh = new THREE.Mesh(textGeometry, textMaterial);
textMesh.position.copy(this.endPoint);
textMesh.position.z += 0.1;
this.scene.add(textMesh);
this.iniTrenderer()
}
}
init.prototype.displaning = function () {
this.scene.remove(this.mesh)
// 创建剖切平面
const clippingPlanes = [
new THREE.Plane(new THREE.Vector3(1, 0, 0), 0), // x轴方向的剖切平面
new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), // y轴方向的剖切平面
new THREE.Plane(new THREE.Vector3(0, 0, 1), 0), // z轴方向的剖切平面
];
this.mesh.material.clippingPlanes = clippingPlanes;
this.mesh.material.clipIntersection = true;
console.log(this.mesh)
this.scene.add(this.mesh)
this.iniTrenderer()
}
init.prototype.Faxx = function () {
// 设置设备像素比,避免canvas画布输出模糊
this.renderer.setPixelRatio(window.devicePixelRatio);
const composer = new EffectComposer(this.renderer);
const renderPass = new RenderPass(this.scene, this.camera);
composer.addPass(renderPass);
const FXAAPass = new ShaderPass(FXAAShader);
// `.getPixelRatio()`获取`renderer.setPixelRatio()`设置的值
const pixelRatio = this.renderer.getPixelRatio();//获取设备像素比
// width、height是canva画布的宽高度
FXAAPass.uniforms.resolution.value.x = 1 / (this.width * pixelRatio);
FXAAPass.uniforms.resolution.value.y = 1 / (this.height * pixelRatio);
composer.addPass(FXAAPass);
}
init.prototype.setBoxHelper = function () {
// 计算模型的长宽高
const boundingBox = new THREE.Box3().setFromObject(this.mesh);
const size = new THREE.Vector3();
boundingBox.getSize(size);
console.log(size)
const length = size.x;
const width = size.y;
const height = size.z;
// 计算模型中心点
var center = new THREE.Vector3();
boundingBox.getCenter(center);
console.log(center)
const vertices = [
new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z - height / 2),
new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z - height / 2),
new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z - height / 2),
new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z - height / 2),
new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y + width / 2, center.z + height / 2),
new THREE.Vector3(center.x - length / 2, center.y - width / 2, center.z + height / 2),
new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z + height / 2),
new THREE.Vector3(center.x + length / 2, center.y + width / 2, center.z + height / 2),
new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z + height / 2),
new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z - height / 2),
new THREE.Vector3(center.x + length / 2, center.y - width / 2, center.z + height / 2),
];
console.log(vertices, "vertices")
// 创建线段和标签
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
const lineGeometry = new THREE.BufferGeometry().setFromPoints(vertices);
const lineSegments = new THREE.LineSegments(lineGeometry, lineMaterial);
this.scene.add(lineSegments);
let arr = []
for (let i = 0; i < vertices.length; i++) {
if (i % 2 == 0) {
const start = vertices[i];
const end = vertices[i + 1];
const segmentLength = start.distanceTo(end);
let falg = deWeight(arr, segmentLength)
// if (!falg) {
// continue;
// } else {
// arr.push(segmentLength)
// }
// 创建长度标签
const labelMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const labelGeometry = new TextGeometry(` ${segmentLength.toFixed(2)}`, {
font: this.font, // 字体文件
size: 20, // 字体大小
height: 1, // 字体厚度
});
const labelMesh = new THREE.Mesh(labelGeometry, labelMaterial);
// 设置标签位置为线段的中点
const midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
labelMesh.position.copy(midpoint);
this.scene.add(labelMesh);
}
}
this.iniTrenderer()
}
init.prototype.setModleLine = function () {
// 获取模型的顶点数据
const vertices = [];
console.log(this.mesh)
const positionAttribute = this.mesh.geometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
const vertex = new THREE.Vector3();
vertex.fromBufferAttribute(positionAttribute, i);
vertices.push(vertex);
}
// 创建线段并贴合模型
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00 });
const lineGeometry = new THREE.BufferGeometry().setFromPoints(vertices);
const lineSegments = new THREE.LineSegments(lineGeometry, lineMaterial);
this.scene.add(lineSegments);
for (let i = 0; i < vertices.length; i++) {
if (i % 2 == 0) {
const start = vertices[i];
const end = vertices[i + 1];
const segmentLength = start.distanceTo(end);
// if (!falg) {
// continue;
// } else {
// arr.push(segmentLength)
// }
// 创建长度标签
const labelMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const labelGeometry = new TextGeometry(` ${segmentLength.toFixed(2)}`, {
font: this.font, // 字体文件
size: 6, // 字体大小
height: 1, // 字体厚度
});
const labelMesh = new THREE.Mesh(labelGeometry, labelMaterial);
// 设置标签位置为线段的中点
const midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
labelMesh.position.copy(midpoint);
this.scene.add(labelMesh);
}
}
}
init.prototype.setSpin = function (deltaRotationQuaternion) {
this.camera.position.sub(this.target); // 将相机位置减去旋转中心点
this.camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转
this.camera.position.add(this.target); // 将旋转后的相机位置加上旋转中心点
this.camera.lookAt(this.target); // 相机朝向旋转中心点
}
init.prototype.recordMouse = function (event) {
// 计算鼠标位置的归一化设备坐标
this.mouse.x = (event.clientX / this.width) * 2 - 1;
this.mouse.y = -(event.clientY / this.height) * 2 + 1;
if (!this.isStart) {
// 通过鼠标位置和相机创建射线
this.raycaster.setFromCamera(this.mouse, this.camera);
// 计算射线与模型的交叉情况
var intersects = this.raycaster.intersectObject(this.mesh);
if (intersects.length > 0) {
if (this.recorLine) {
this.scene.remove(this.recorLine)
}
// // 创建两个向量表示两个点的坐标
// var point1 = new THREE.Vector3(this.measureStart.x, this.measureStart.y, this.measureStart.z); // 第一个点的坐标
// var point2 = new THREE.Vector3(this.measureEnd.x, this.measureEnd.y, this.measureEnd.z); // 第二个点的坐标
// // 计算两个向量的差向量
// var differenceVector = point2.clone().sub(point1);
// // 计算垂直于差向量的向量(通过向量的叉乘操作)
// var perpendicularVector = new THREE.Vector3(-differenceVector.y, differenceVector.x, 0);
// 打印结果
this.measureEnd = intersects[0].point
const vertices = [...this.measureStart, ...this.measureEnd]
var geometry = new LineGeometry()
geometry.setPositions(vertices)
var material = new LineMaterial({
color: 0x00ff00,
linewidth: 6
})
material.resolution.set(window.innerWidth, window.innerHeight)
this.recorLine = new Line2(geometry, material)
this.recorLine.computeLineDistances()
this.scene.add(this.recorLine)
const start = this.measureStart;
const end = this.measureEnd;
const segmentLength = start.distanceTo(end);
const labelMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff });
const labelGeometry = new TextGeometry(` ${segmentLength.toFixed(2)}`, {
font: this.font, // 字体文件
size: 20, // 字体大小
height: 1, // 字体厚度
});
if (this.measureLabel) {
this.scene.remove(this.measureLabel)
}
this.measureLabel = new THREE.Mesh(labelGeometry, labelMaterial);
// 设置标签位置为线段的中点
const midpoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
this.measureLabel.position.copy(midpoint);
this.scene.add(this.measureLabel);
}
} else {
this.measureClick = false
}
}
init.prototype.recordClick = function () {
// 通过鼠标位置和相机创建射线
this.raycaster.setFromCamera(this.mouse, this.camera);
// 计算射线与模型的交叉情况
var intersects = this.raycaster.intersectObject(this.mesh);
if (intersects.length > 0) {
if (this.isStart) {
this.measureStart = intersects[0].point
}
this.isStart = !this.isStart
// 鼠标点击点在模型内部
console.log('模型内部');
if (!this.isStart) {
return true
} else {
return false
}
} else {
// 鼠标点击点在模型外部
console.log('模型外部!');
}
}
//鼠标高亮
init.prototype.highlight = function () {
this.renderer.domElement.addEventListener("mousedown", (event) => {
this.mouse.x = (event.clientX / this.width) * 2 - 1;
this.mouse.y = -(event.clientY / this.height) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
var intersects = this.raycaster.intersectObject(this.mesh);
if (intersects.length > 0) {
var intersectedObject = intersects[0].object;
var intersectedFaceIndex = intersects[0].faceIndex;
// 获取相交面的几何体
var geometry = intersectedObject.geometry;
if (geometry instanceof THREE.BufferGeometry) {
// 创建一个新的颜色属性数组
var colors = new Float32Array(geometry.attributes.position.count * 3);
// for (let i = 0; i <= colors.length; i += 3) {
// colors[i] = 1;
// colors[i + 1] = 1;
// colors[i + 2] = 0;
// }
// 将点击的面的颜色设置为红色
colors[500 * 3] = 0;
colors[500 * 3 + 1] = 1;
colors[500 * 3 + 2] = 0;
console.log(colors)
// 将新的颜色属性数组设置回几何体
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
var material = new THREE.MeshBasicMaterial({ vertexColors: THREE.VertexColors });
// 创建网格并将材质应用到几何体
this.mesh = new THREE.Mesh(geometry, material);
// 创建一个基于顶点颜色的材质
}
} else {
console.log("没点中")
}
})
}
function deWeight(arr, event) {
let falg = true
arr.forEach(item => {
if (event == item) {
falg = false
}
})
return falg
}
export default init
代码如下:
three = new initThree(50, 100, 50, "#e7e9e9", 800, 800)
three.setContainer(containerBox.value)
three.loader("/modle/scene.gltf", "test2")
three.renderer.domElement.addEventListener('mousemove', onMouseMove);
three.renderer.domElement.addEventListener('mousedown', onMouseDown);
three.renderer.domElement.addEventListener('mouseup', onMouseUp);
const onMouseDown = (event) => {
three.previousMousePosition = getMousePosInWebgl(event)//
};
function getMousePosInWebgl({ clientX, clientY }) {
//鼠标在画布中的css位置
const { left, top, width, height } = three.renderer.domElement.getBoundingClientRect();
const [cssX, cssY] = [clientX - left, clientY - top];
//解决坐标原点位置的差异
const [halfWidth, halfHeight] = [width / 2, height / 2];
const [xBaseCenter, yBaseCenter] = [
cssX - halfWidth,
cssY - halfHeight,
];
// 解决y 方向的差异
const yBaseCenterTop = -yBaseCenter;
//解决坐标基底的差异
return {
x: xBaseCenter / halfWidth,
y: yBaseCenterTop / halfHeight
}
}
const onMouseMove = (event) => {
if (three.isDragging) {
const deltaRotationQuaternion = text(event)
three.camera.position.sub(three.target); // 将相机位置减去旋转中心点
three.camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转
three.camera.position.add(three.target); // 将旋转后的相机位置加上旋转中心点
three.camera.lookAt(three.target); // 相机朝向旋转中心点
cube.value.setCamera(deltaRotationQuaternion)
}
}
const text = function (event) {
const { x, y } = getMousePosInWebgl(event)
const deltaMove = {
x: x - three.previousMousePosition.x,
y: y - three.previousMousePosition.y
};
// const deltaMove = {
// x: event.clientX - three.previousMousePosition.x,
// y: event.clientY - three.previousMousePosition.y
// };
const deltaRotationQuaternion = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
toRadians(deltaMove.y * 1.5),
toRadians(deltaMove.x * 1.5),
0,
'XYZ'
)
);
for (let key in deltaRotationQuaternion) {
// deltaRotationQuaternion[key] *= 1.01
}
return deltaRotationQuaternion
}
const onMouseUp = (event) => {
three.isDragging = false;
};
<template>
<div id="container" ref="container" style="
width: 200px;
height: 200px;
margin-left: 0px;
"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as THREE from 'three';
//导入模型库
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import text from "three/examples/fonts/helvetiker_bold.typeface.json"
import { FontLoader } from "three/examples/jsm/loaders/FontLoader"
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const container = ref(null);
let scene, camera, renderer, cube, model, font;
const props = defineProps(["width", "heigth", "bgcColor",])
const emit = defineEmits(["spin"])
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
const target = new THREE.Vector3(0, 0, 0); // 旋转中心点
let controls;
// 创建场景
scene = new THREE.Scene();
let fontLoader = new FontLoader();
font = fontLoader.parse(text)
// 创建相机
camera = new THREE.OrthographicCamera(-2 * 1, 2 * 1, 2, -2, 1, 2000);
camera.position.set(0, 0, 2);
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(200, 200);
renderer.setClearColor(props.bgcColor, 1); //设置背景颜色
const axesHelper = new THREE.AxesHelper(200);
scene.add(axesHelper);
// 创建立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
//创建文字
const materials = [];
const texts = ['左', '右', '上', '下', '前', '后'];
for (let i = 0; i < 6; i++) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 256;
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
context.font = 'Bold 48px Arial';
context.fillStyle = 'black';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(texts[i], canvas.width / 2, canvas.height / 2);
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.MeshBasicMaterial({ map: texture });
materials.push(material);
}
// 将材质应用到立方体的各个面上
cube = new THREE.Mesh(geometry, materials);
scene.add(cube);
// 鼠标拖动事件
const onMouseMove = (event, flag) => {
const { x, y } = getMousePosInWebgl(event)
const deltaMove = {
x: x - previousMousePosition.x,
y: y - previousMousePosition.y
};
// const deltaMove = {
// x: event.clientX - previousMousePosition.x,
// y: event.clientY - previousMousePosition.y
// };
if (isDragging || flag) {
const deltaRotationQuaternion = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
toRadians(deltaMove.y * 1.5),
toRadians(deltaMove.x * 1.5),
0,
'XYZ'
)
);
camera.position.sub(target); // 将相机位置减去旋转中心点
camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转
camera.position.add(target); // 将旋转后的相机位置加上旋转中心点
camera.lookAt(target); // 相机朝向旋转中心点
// cube.quaternion.multiplyQuaternions(deltaRotationQuaternion, cube.quaternion);
emit("spin", deltaRotationQuaternion, isDragging)
}
};
const onMouseDown = (event) => {
event.stopPropagation();
isDragging = true;
console.log(event)
previousMousePosition = getMousePosInWebgl(event);
// previousMousePosition = {
// x: event.clientX,
// y: event.clientY
// };
};
function getMousePosInWebgl({ clientX, clientY }) {
//鼠标在画布中的css位置
const { left, top, width, height } = renderer.domElement.getBoundingClientRect();
const [cssX, cssY] = [clientX - left, clientY - top];
//解决坐标原点位置的差异
const [halfWidth, halfHeight] = [width / 2, height / 2];
const [xBaseCenter, yBaseCenter] = [
cssX - halfWidth,
cssY - halfHeight,
];
// 解决y 方向的差异
const yBaseCenterTop = -yBaseCenter;
//解决坐标基底的差异
return {
x: xBaseCenter / halfWidth,
y: yBaseCenterTop / halfHeight
}
}
const onMouseUp = () => {
isDragging = false;
};
const setCamera = (deltaRotationQuaternion) => {
camera.position.sub(target); // 将相机位置减去旋转中心点
camera.position.applyQuaternion(deltaRotationQuaternion); // 应用旋转
camera.position.add(target); // 将旋转后的相机位置加上旋转中心点
camera.lookAt(target); // 相机朝向旋转中心点
}
// 输出组件的方法,让外部组件可以调用
defineExpose({
setCamera,
})
onMounted(() => {
container.value.appendChild(renderer.domElement);
renderer.domElement.addEventListener('mousemove', onMouseMove);
renderer.domElement.addEventListener('mousedown', onMouseDown);
renderer.domElement.addEventListener('mouseup', onMouseUp);
// 动画循环
const animate = () => {
requestAnimationFrame(animate);
renderer.render(scene, camera);
};
animate();
});
// 辅助函数,将角度转换为弧度
const toRadians = (angle) => {
return angle * (Math.PI / 180);
};
</script>
<style>
#container {
width: 100px;
height: 100px;
}
</style>
重复代码有点多,cube场景是可以new出来的,这旋转起始是摄像机围绕模型中心点转,不是模型本身的自旋转
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。