?
<template>
<div class="modelsBox">
<div class="modelsBox_wrapper" v-if="f"></div>
<div :id="`sign${idx + 1}`" style="position: absolute;" v-for="(v, idx) in labels" :key="idx">
<div class="sign" :uuid="v.uuid" @click="dele">
<div class="name">设备名称{{idx + 1}}</div>
<div class="data">信号点1: {{parseFloat(v.x).toFixed(2)}}</div>
<div class="data">信号点2: {{parseFloat(v.y).toFixed(2)}}</div>
<div class="data">信号点3: {{parseFloat(v.z).toFixed(2)}}</div>
</div>
</div>
<div class="opara-pannel" @click.stop>
<div>
<button @click="save">保存</button>
<button @click="show">回显</button>
<button @click="clear">还原初始化</button>
<button @click="animate">动画</button>
</div>
<div>
三维坐标点信息
<p>{{point3d}}</p>
</div>
</div>
</div>
</template>
<script>
import { webglOBJ, labelTag, getPointRay, getFitScaleValue, deepCopyObject } from '@/utils/webGL/webGL.js';
import TWEEN from '@tweenjs/tween.js';
export default {
name: 'modelsBox',
data () {
return {
f: true,
point3d: {},
zoom: 1,
target: '',
sence: null,
camera: '',
renderer: '',
obj: {
sence: '',
camera: ''
},
labels: [
{x:?381.0111567102036,?y:?41.66598867957452,?z:?-248.63694417317873},
{x:?383.39332161333544,?y:?41.37982005491592,?z:?-380.9167972387805},
{x:?384.19846417704997,?y:?41.50664881726524,?z:?-466.0620455548741}
]
};
},
beforeDestroy () {
document.removeEventListener('click', this.get3D);
},
mounted () {
this.int();
this.bind3Dpoint();
},
methods: {
clear1 () {
this.f = false;
this.$nextTick(() => {
this.f = true;
setTimeout(() => {
this.int();
}, 0);
});
},
clear () {
new TWEEN.Tween(this.sence.position)
.to({ x: 0, y: 0, z: 0}, 1000)
.easing(TWEEN.Easing.Back.Out)
.start();
new TWEEN.Tween(this.camera.position)
.to({ x: 150.00000000000006, y: 350, z: -189.99999999999997}, 1000)
.easing(TWEEN.Easing.Back.Out)
.start();
new TWEEN.Tween(this.camera.rotation)
.to({ x: -2.068139043141719, y: 0.3602177483054216, z: 2.5657085518398413}, 1000)
.easing(TWEEN.Easing.Back.Out)
.start();
this.controls.update();
this.camera.lookAt(this.sence.position);
this.renderer.render(this.sence, this.camera);
},
// 点击模型获取三维坐标点的信息
bind3Dpoint() {
const vm = this;
document.addEventListener('dblclick', this.get3Dmode, false);
},
// 点击模型还原对应视角
get3Dmode () {
const point3d = getPointRay(this.sence, this.camera).point;
const time = 5000;
// 克隆相机用户计算点击后相机聚焦的位置
const cloneCamera = this.camera.clone();
// this.camera.lookAt(point3d);
cloneCamera.lookAt(point3d);
new TWEEN.Tween(this.camera.position)
.to({ x: cloneCamera.position.x, y: cloneCamera.position.y, z: cloneCamera.position.z}, 1000)
.easing(TWEEN.Easing.Back.Out).start();
new TWEEN.Tween(this.camera.rotation)
.to({ x: cloneCamera.rotation.x, y: cloneCamera.rotation.y, z: cloneCamera.rotation.z}, 1000)
.easing(TWEEN.Easing.Back.Out).start();
},
view (dir) {
if (dir == 'top') {
// this.sence.rotation.x = 10;
this.renderer.render(this.sence, this.camera);
}
},
show () {
const {x, y, z} = JSON.parse(window.sessionStorage.getItem('position'));
this.camera.position.set(x, y, z);
this.camera.lookAt(this.target);
this.renderer.render(this.sence, this.camera);
},
save () {
// this.oCamera = deepCopyObject(this.camera);
const {zoom, position, rotation} = this.camera;
this.target = {...this.controls.target};
window.sessionStorage.setItem('position', JSON.stringify(position));
window.sessionStorage.setItem('target', JSON.stringify(this.target));
},
animate () {
},
dele () {
// this.sence.remove(this.plane); // 删除模型
// this.sence.add(this.plane); // 添加模型
console.log(this.camera, this.renderer, 'this.renderer');
},
int () {
const position = window.sessionStorage.getItem('position');
const target = window.sessionStorage.getItem('target');
let mixer = null;
const imgBG = require('./img.jpg');
const mtl = '/static/models/beng.mtl';
const obj = '/static/models/beng.obj';
const gltf = '/static/models/盒子.gltf';
// const gltf = 'https://scqilin.github.io/learning-threejs-third/assets/models/CesiumMan/CesiumMan.gltf';
const loader = new THREE.GLTFLoader();
const webGLdom = document.querySelector('.modelsBox_wrapper');
const sence = webglOBJ.createSence(webGLdom);
const camera = webglOBJ.createCamera({
fov: 45,
aspect: 1,
near: 100,
far: 5000,
position: {
x: 150,
y: 350,
z: -190
}
});
// 初始化和相机的观察的位置一样
this.point3d = {x: 150, y: 350, z: -190};
if (position) {
camera.position.set(JSON.parse(position).x, JSON.parse(position).y, JSON.parse(position).z);
camera.lookAt(JSON.parse(target));
}
const renderer = webglOBJ.createRenderer();
const plane = webglOBJ.createPlane(imgBG, imgBG);
const spotLight = webglOBJ.createSpotLight();
const directionalLight = webglOBJ.createDirectionalLight({ x: 100000, y: 100000, z: 100000 });
const ambient = webglOBJ.createAmbient();
const datGui = webglOBJ.createDatGui();
const controls = webglOBJ.createControls();
const axisHelper = webglOBJ.createAxisHelper();
// loader.load(gltf, function (gltf) {
// let object = gltf.scene;
// console.log(gltf, 'gltf');
// object.scale.set(30, 30, 30);
// sence.add(object);
// console.log(sence.getObjectByName('root'), 'root');
// // 动画效果
// mixer = new THREE.AnimationMixer(gltf.scene);
// console.log(gltf.animations, 'gltf.animations[i]');
// //同时将这个外部模型的动画全部绑定到动画混合器里面
// for (var i = 0; i < gltf.animations.length; i++){
// mixer.clipAction(gltf.animations[i]).play();
// }
// });
// 滚动缩小获取比例
const vm = this;
controls.addEventListener('change', function(evt) {
console.log(controls.target, evt, 'zoom');
});
this.sence = sence;
this.controls = controls;
this.camera = camera;
this.plane = plane;
this.renderer = renderer;
webglOBJ.loadMIT(mtl, obj, (obj) => {
// console.log(getFitScaleValue(obj, camera), 'adasd');
});
// 将对象添加到场景中去
webglOBJ.senceAdd([ directionalLight, ambient, datGui, controls, axisHelper]);
// webglOBJ.webglRender(sence, camera, renderer);
// 动画显示
const clock = new THREE.Clock();
function render (html) {
vm.$nextTick(() => {
vm.labels.forEach((val, idx) => {
const {x, y, z} = val;
labelTag(camera, {x, y, z}, `sign${idx + 1}`, val, webGLdom);
});
});
// 动画显示
const time = clock.getDelta();
if (mixer) {
mixer.update(time);
}
TWEEN.update();
renderer.render(sence, camera);
vm.sence = sence;
vm.camera = camera;
requestAnimationFrame(render);
};
this.render = render;
render();
}
}
};
</script>
<style lang="scss" scoped>
.modelsBox_wrapper {
position: relative;
width: 100%;
height: 100vh;
border: 1px solid #ccc;
overflow: hidden;
}
.opara-pannel {
position: absolute;
right: 15px;
top: 100px;
width: 200px;
height: 400px;
background: rgba(0, 0, 0, 0.7);
div, p{
color: #fff;
}
}
.modelsBox {
position:relative;
overflow: hidden;
}
div[id *= "sign"] {
width: 250px;
height: 100px;
padding:10px 10px 10px 70px;
background: rgba(0, 0, 0, .65);
background: url('~assets/label-bg.png') center center no-repeat;
.sign{
div {
color: #fff;
text-align: left;
padding: 0 5px;
}
}
}
</style>
import { startLoading } from '@/axios/index.js';
const THREE = window.THREE;
// webGL对象配置
export const webglOBJ = {
renderDom: null,
Scene: null, // 场景
camera: null, // 摄像头
renderer: null, // 渲染器
senceAdd(objList = []) {
objList.forEach((v) => {
webglOBJ.Scene.add(v);
});
},
// 加载模型
loadMIT(mtl, obj, callBack) {
const $load = startLoading();
new THREE.MTLLoader().load(mtl, function(materials) {
materials.preload();
new THREE.OBJLoader().setMaterials(materials).load(
obj,
function(object) {
object.traverse((child) => {
child.castShadow = true;
child.$msg = 'man';
child.receiveShadow = true;
child.position.z = 0;
child.position.y = 0;
child.position.x = 0;
child.scale.x = 0.2;
child.scale.y = 0.2;
child.scale.z = 0.2;
});
webglOBJ.Scene.add(object);
},
function(load) {
if (load.loaded == load.total) {
$load.close();
}
}
);
});
},
// 创建场景
createSence(renderDom) {
this.renderDom = renderDom;
webglOBJ.Scene = new THREE.Scene();
webglOBJ.Scene.background = null;
return webglOBJ.Scene;
},
// 创建摄像机
createCamera({ fov, aspect, near, far, position } = {}) {
const { width, height } = this.renderDom.getBoundingClientRect();
let camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(position.x, position.y, position.z);
camera.lookAt(webglOBJ.Scene.position); // 视角
camera.enableDamping = true; // 视角
webglOBJ.camera = camera; // 视角
return webglOBJ.camera;
},
createRenderer() {
let renderer = new THREE.WebGLRenderer();
const { width, height } = this.renderDom.getBoundingClientRect();
renderer.setSize(width, height);
renderer.setClearColor(new THREE.Color(0xcccccc));
renderer.shadowMap.enabled = true;
this.renderDom.appendChild(renderer.domElement);
webglOBJ.renderer = renderer;
return webglOBJ.renderer;
},
createPlane(textureLoaderUrl, textureNormalUrl) {
let planeGeometry = new THREE.PlaneGeometry(5000, 5000, 5000, 1); // 平面网格
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load(textureLoaderUrl);
let textureNormal = textureLoader.load(textureNormalUrl);
// 加载高光贴图
let planeMaterial = new THREE.MeshPhongMaterial({
// specular: 0xff0000,//高光部分的颜色
shininess: 30, //高光部分的亮度,默认30
map: texture, // 普通纹理贴图
roughness: 0.3,
lightMap: textureNormal,
// normalMap: textureNormal, //法线贴图
bumpScale: 3,
}); // 材质对象Material
// let planeMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});
let plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 0;
plane.name = '平面物体ID=' + 1;
plane.position.y = 0;
plane.position.z = -1525;
plane.receiveShadow = true;
return plane;
},
createBoxGeometry(textureLoaderUrl, { x, y, z }) {
// 创建立方体
let textureLoader = new THREE.TextureLoader();
let textureNormal = textureLoader.load(textureLoaderUrl);
let boxGeometry = new THREE.BoxGeometry(10, 10, 10, 200);
let texture1 = textureLoader.load(textureLoaderUrl);
let boxGeometryMaterial = new THREE.MeshLambertMaterial({
// specular: 0xff0000,//高光部分的颜色
shininess: 10, //高光部分的亮度,默认30
normalScale: new THREE.Vector2(2.2, 2.2),
map: texture1, // 普通纹理贴图
normalMap: textureNormal, //法线贴图
bumpMap: textureNormal,
bumpScale: 0.3,
});
let box = new THREE.Mesh(boxGeometry, boxGeometryMaterial);
box.name = '正方物体ID=' + 2;
box.position.x = x;
box.position.y = y;
box.position.z = z;
box.castShadow = true;
return box;
},
// 点光源
createSpotLight() {
// 点光源
let spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(-60, 40, -20);
spotLight.castShadow = true;
return spotLight;
},
// 平行光
createDirectionalLight({ x, y, z }) {
// 平行光
let directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(x, y, z);
// 方向光指向对象网格模型mesh2,可以不设置,默认的位置是0,0,0
// directionalLight.target = target;
return directionalLight;
},
// 环境光
createAmbient(color = 0x444444) {
let ambient = new THREE.AmbientLight(color, 1);
// ambient.castShadow = true;
return ambient;
},
createDatGui() {
let gui = {
bump: 0.03,
animation: false,
};
let datGui = new dat.GUI();
//将设置属性添加到gui当中,gui.add(对象,属性,最小值,最大值)
datGui.add(gui, 'bump', -1, 1).onChange(function(e) {
box.material.bumpScale = e;
});
datGui.add(gui, 'animation');
return datGui;
},
// 创建控制轴
createControls() {
let controls = new THREE.OrbitControls(webglOBJ.camera, webglOBJ.renderDom);
return controls;
},
// 创建帮助
createAxisHelper() {
let axisHelper = new THREE.AxisHelper(250);
return axisHelper;
},
// 初始化webGL对象
webglRender(Scene, camera) {
webglOBJ.renderer.render(Scene, camera);
window.requestAnimationFrame(webglOBJ.webglRender);
},
};
/**
* 添加标签:dom方式
* @param {*} targePosition :需要传递当前标签的位置
* @param {*} targetId :标签对应的dom的唯一ID,暂且用时间戳代替,避免重复
* @param {*} innerHTML :标签对应html
*/
export function labelTag(camera, targePosition, targetId, innerHTML, webGLdom) {
const { width, height } = webGLdom.getBoundingClientRect();
let worldVector = new THREE.Vector3(
targePosition.x,
targePosition.y,
targePosition.z
);
let vector = worldVector.project(camera);
let halfWidth = width / 2,
halfHeight = height / 2;
let x = Math.round(vector.x * halfWidth + halfWidth);
let y = Math.round(-vector.y * halfHeight + halfHeight);
/**
* 更新立方体元素位置
*/
let div = document.getElementById(targetId);
let hg = div.getBoundingClientRect().height;
div.style.left = x + 'px';
div.style.top = y - hg + 'px';
// div.innerHTML = `uuid:${innerHTML.uuid}`;
}
// 获取模型表面点的空间三维坐标
export function getPointRay(scene, camera) {
const windowX = event.clientX; //鼠标单击位置横坐标
const windowY = event.clientY; //鼠标单击位置纵坐标
let res = { point: null, mesh: null };
let x = (windowX / window.innerWidth) * 2 - 1; //标准设备横坐标
let y = -(windowY / window.innerHeight) * 2 + 1; //标准设备纵坐标
let standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
//标准设备坐标转世界坐标
let worldVector = standardVector.unproject(camera);
let ray = worldVector.sub(camera.position).normalize();
//创建射线投射器对象
let raycaster = new THREE.Raycaster(camera.position, ray);
//返回射线选中的对象
let intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
if (intersects.length > 0) {
let point = intersects[0].point; //射线在模型表面拾取的点坐标
let mesh = intersects[0].object;
res = { point, mesh, intersects };
}
return res;
}
// 空间坐标转二维坐标
export function transPosition(position) {
let world_vector = new THREE.Vector3(position.x, position.y, position.z);
let vector = world_vector.project(camera);
let halfWidth = window.innerWidth / 2,
halfHeight = window.innerHeight / 2;
return {
x: Math.round(vector.x * halfWidth + halfWidth),
y: Math.round(-vector.y * halfHeight + halfHeight),
};
}
// 深拷贝对象
export function deepCopyObject(obj) {
var newObj = {};
if (obj && typeof obj == 'object') {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] == 'object') {
newObj[key] = deepFn(obj[key]);
} else {
newObj[key] = obj[key];
}
}
}
}
return newObj;
}
function deepCopyObject1(obj, parent = null) {
let result = Array.isArray(obj) ? [] : {};
let _parent = parent;
// 该字段有父级则需要追溯该字段的父级
while (_parent) {
// 如果该字段引用了它的父级,则为循环引用
if (_parent.originalParent === obj) {
// 循环引用返回同级的新对象
return _parent.currentParent;
}
_parent = _parent.parent;
}
if (obj && typeof obj === 'object') {
for (let i in obj) {
// 如果字段的值也是一个对象
if (obj[i] && typeof obj[i] === 'object') {
// 递归执行深拷,将同级的待拷贝对象传递给parent,方便追溯循环引用
result[i] = deepCopyObject(obj[i], {
originalParent: obj,
currentParent: result,
parent: parent,
});
} else {
result[i] = obj[i];
}
}
}
return result;
}
// 点击获取对应的3d模型
export function getClick3DModle () {
document.removeEventListener('click', clickEvent);
document.addEventListener('click', clickEvent, false);
// 监听点击事件查看点击的元素
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let modle = {};
// 点击了哪个模型
function clickEvent () {
if (event.target.tagName == 'CANVAS') {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
sence.updateMatrixWorld(true);
// 通过鼠标点的位置和当前相机的矩阵计算出raycaster
raycaster.setFromCamera(mouse, camera);
// 获取raycaster直线和所有模型相交的数组集合
let intersects = raycaster.intersectObjects(sence.children, true);
if (intersects[0]) {
console.log(intersects[0]);
modle = intersects[0];
}
}
}
return modle;
}
核心代码:克隆相机获取对应点击的三维空间的点,然后用tweenjs实现动画效果
// 点击模型还原对应视角
get3Dmode () {
const point3d = getPointRay(this.sence, this.camera).point;
const time = 5000;
// 克隆相机用户计算点击后相机聚焦的位置
const cloneCamera = this.camera.clone();
// this.camera.lookAt(point3d);
cloneCamera.lookAt(point3d);
new TWEEN.Tween(this.camera.position)
.to({ x: cloneCamera.position.x, y: cloneCamera.position.y, z: cloneCamera.position.z}, 1000)
.easing(TWEEN.Easing.Back.Out).start();
new TWEEN.Tween(this.camera.rotation)
.to({ x: cloneCamera.rotation.x, y: cloneCamera.rotation.y, z: cloneCamera.rotation.z}, 1000)
.easing(TWEEN.Easing.Back.Out).start();
},
// 获取模型表面点的空间三维坐标
export function getPointRay(scene, camera) {
const windowX = event.clientX; //鼠标单击位置横坐标
const windowY = event.clientY; //鼠标单击位置纵坐标
let res = { point: null, mesh: null };
let x = (windowX / window.innerWidth) * 2 - 1; //标准设备横坐标
let y = -(windowY / window.innerHeight) * 2 + 1; //标准设备纵坐标
let standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
//标准设备坐标转世界坐标
let worldVector = standardVector.unproject(camera);
let ray = worldVector.sub(camera.position).normalize();
//创建射线投射器对象
let raycaster = new THREE.Raycaster(camera.position, ray);
//返回射线选中的对象
let intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
if (intersects.length > 0) {
let point = intersects[0].point; //射线在模型表面拾取的点坐标
let mesh = intersects[0].object;
res = { point, mesh, intersects };
}
return res;
}
|