如何让立方体模型旋转到指定的面
父页面
<b-modal ref="modal_mini" size="lg" centered
static
:hide-footer="true"
:dialog-class="['modal_mini']"
:content-class="'position-static'"
:body-class="'p-0'"
:header-class="['modal_headers','justify-content-between','align-items-center']" >
<template v-slot:modal-header="{close}" style=" color: #FFF; background-color: #343a40;">
<h6 class="m-0 text-truncate">{{ Module.name }}</h6>
<div class="btn-group">
<button class="btn mr-2" @click="maximize($refs['threeD'+Module.id])">
<font-awesome-icon :icon="['far', 'window-maximize']"/>
</button>
<button class="btn" @click="close()">
<font-awesome-icon icon="times-circle"/>
</button>
</div>
</template>
<div class="modal_body">
<div class="row col-auto justify-content-between align-items-center"
style="padding: 5px 20px">
<div class="btn-group col-auto">
<b-button variant="primary" class="btn" size="sm"
@click="showModalSave(Module.id, Module.rawPath)">
<font-awesome-icon icon="cogs"/>
{{ $t('设置') }}
</b-button>
<b-button variant="primary" class="btn" size="sm"
@click="capture($refs['threeD'+Module.id])">
<font-awesome-icon icon="save"/>
{{ $t('保存截图') }}
</b-button>
</div>
<b-input-group :key="Module.id" size="sm" class="col flex-shrink-1 ml-4">
<b-input-group-prepend>
<b-input-group-text class="px-3">x</b-input-group-text>
</b-input-group-prepend>
<b-form-input style="height: 38px" v-model="Module.x" :formatter="format_positiveNumber" type="number"
min="0.00"></b-form-input>
<b-input-group-prepend>
<b-input-group-text class="px-3">y</b-input-group-text>
</b-input-group-prepend>
<b-form-input style="height: 38px" v-model="Module.y" :formatter="format_positiveNumber" type="number"
min="0.00"></b-form-input>
<b-input-group-prepend>
<b-input-group-text class="px-3">z</b-input-group-text>
</b-input-group-prepend>
<b-form-input style="height: 38px" v-model="Module.z" :formatter="format_positiveNumber" type="number"
min="0.00"></b-form-input>
<b-input-group-append>
<b-input-group-text class="px-3 cursor-pointer"
@click="XYZ($refs['threeD'+Module.id],0,Module)">{{ $t('确定') }}
</b-input-group-text>
<b-input-group-text class="px-3 cursor-pointer"
@click="reset($refs['threeD'+Module.id],Module)">{{ $t('还原') }}
</b-input-group-text>
</b-input-group-append>
</b-input-group>
</div>
<div class="row col-auto justify-content-between align-items-center"
style="padding: 5px 20px">
<b-input-group-prepend>
<img :src="modelPath" alt="" class="modelePathImg">
<el-select v-model="value" placeholder="" @change="takeAxialDirectionType($event,Module)" style="width: 156px;">
<el-option
v-for="item in modelImgList"
:key="item.value"
:label="item.label"
:value="item.value">
<div class="modeleBox">
<img :src="item.img" alt="" class="modelImg">
</div>
</el-option>
</el-select>
<!-- <b-form-select v-model="axialDirection" @change="takeAxialDirectionType($event,Module)" style="width: 156px;">
<option value="top" style="width:10px;height: 10px;">
<div style="width:10px;height: 20px;">
<img src="../assets/3D/顶部视角.png" alt="" style="display: block;width:10px;height: 10px;">
</div>
</option>
<option value="left">{{ $t('固定Y轴') }}</option>
<option value="right">{{ $t('固定Z轴') }}</option>
<option value="bottom">{{ $t('固定Z轴') }}</option>
<option value="ago">{{ $t('固定Z轴') }}</option>
<option value="after">{{ $t('固定Z轴') }}</option>
</b-form-select> -->
</b-input-group-prepend>
<b-input-group :key="Module.id" size="sm" class="col flex-shrink-1 ml-4">
<b-input-group-prepend>
<b-input-group-text class="px-3">x</b-input-group-text>
</b-input-group-prepend>
<b-form-input style="height: 38px" v-model="Module.scaleX" :formatter="bigZeroNumAndFloat" type="number"
min="0"></b-form-input>
<b-input-group-prepend>
<b-input-group-text class="px-3">y</b-input-group-text>
</b-input-group-prepend>
<b-form-input style="height: 38px" v-model="Module.scaleY" :formatter="bigZeroNumAndFloat" type="number"
min="0.00"></b-form-input>
<b-input-group-prepend>
<b-input-group-text class="px-3">z</b-input-group-text>
</b-input-group-prepend>
<b-form-input style="height: 38px" v-model="Module.scaleZ" :formatter="bigZeroNumAndFloat" type="number"
min="0.00"></b-form-input>
<b-input-group-append>
<b-input-group-text class="px-5 cursor-pointer"
@click="takeScaleType('big',Module)">确定
</b-input-group-text>
<!-- <b-input-group-text class="px-3 cursor-pointer"
@click="takeScaleType('big',Module)">{{ $t('放大') }}
</b-input-group-text>
<b-input-group-text class="px-3 cursor-pointer"
@click="takeScaleType('small',Module)">{{ $t('缩小') }}
</b-input-group-text> -->
</b-input-group-append>
</b-input-group>
</div>
<div class="row cards" style="padding: 5px 20px">
<three-d
v-if="Module.isShow"
:ref="'threeD'+Module.id"
:xyz="Module.xyz"
:imgs="Module.imgs"
:key="Module.id"
:axialDirection="axialDirection"
:scaleType="scaleType"
:scaleXYZ="[Module.scaleX,Module.scaleY,Module.scaleZ]"
:aspect_w="794"
:aspect_h="450"></three-d>
</div>
<!-- <div class="select_color" @click="getRatio" :style="{'background':colorBar}">
<el-color-picker v-model="selectColor" show-alpha :predefine="predefineColors"
@change="setColor($event,0,Module.id,$refs['threeD'+Module.id],Module)"></el-color-picker>
</div> -->
</div>
</b-modal>
效果:
<template>
<div :class="{fullModal:fullScreen}">
<font-awesome-icon v-show="fullScreen" icon="times-circle" class="fullModal_close" @click="mini()"/>
<div ref="container" id="container"></div>
</div>
</template>
<script>
import {
BoxGeometry,
BufferAttribute,
Color,
DirectionalLight,
DoubleSide,
EdgesGeometry,
Fog,
Group,
LineBasicMaterial,
LineSegments,
Mesh,
MeshBasicMaterial,
PerspectiveCamera,
PlaneBufferGeometry,
Scene,
TextureLoader,
WebGLRenderer
} from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import Stats from "three/examples/jsm/libs/stats.module.js";
import dat from "three/examples/jsm/libs/dat.gui.module.js";
export default {
name: "ThreeD",
data() {
return {
rotation:'',
rotationType:null,
scene: null,
box: {
scale:{
x:null,
y:null,
z:null
}
}, // 立方体Group
plane:{
scale:{
x:null,
y:null,
z:null
}
}, // 三个平面Group
plane_x: null, // x方向的面
plane_y: null, // y方向的面
plane_z: null, // z方向的面
camera: null,
container: null,
renderer: null,
stats: null,
// aspect_w: 600, // 宽高比_宽
// aspect_h: 450, // 宽高比_高
fullScreen: false,
// aspect_w: window.innerWidth, // 宽高比_宽
// aspect_h: window.innerHeight, // 宽高比_高
boxCopy: null
};
},
props: {
static: {
type: Boolean,
default: () => false,
},
xyz: {
type: Array,
default: () => [100, 100, 100],
},
imgs: {
type: Array,
// default: () => [
// require('@/assets/3d_demo/right.jpg'),
// require('@/assets/3d_demo/left.jpg'),
// require('@/assets/3d_demo/top.jpg'),
// require('@/assets/3d_demo/bottom.jpg'),
// require('@/assets/3d_demo/front.jpg'),
// require('@/assets/3d_demo/back.jpg'),
// ]
},
axialDirection: {
type: String,
default: ''
},
scaleType: {
type: String,
default: ''
},
aspect_w: {
type: Number,
default: 600
},
aspect_h: {
type: Number,
default: 450
},
scaleXYZ: {
type: Array,
default: () => {
return [1, 1, 1]
}
},
},
methods: {
takeScale() {
this.box.scale.x = this.scaleXYZ[0];
this.plane.scale.x = this.scaleXYZ[0];
this.box.scale.y = this.scaleXYZ[1];
this.plane.scale.y = this.scaleXYZ[1];
this.box.scale.z = this.scaleXYZ[2];
this.plane.scale.z = this.scaleXYZ[2];
// if (this.scaleType === 'big') {
// if (this.axialDirection === 'X') {
// console.log(this.scaleXYZ[0])
// this.box.scale.x += this.scaleXYZ[0];
// console.log(this.scaleXYZ[0])
// this.plane.scale.x += this.scaleXYZ[0];
// } else if (this.axialDirection === 'Y') {
// this.box.scale.y += this.scaleXYZ[1];
// this.plane.scale.y += this.scaleXYZ[1];
// } else if (this.axialDirection === 'Z') {
// this.box.scale.z += this.scaleXYZ[2];
// this.plane.scale.z += this.scaleXYZ[2];
// }else if(this.axialDirection === ''){
// console.log(this.scaleXYZ)
// this.box.scale.x += this.scaleXYZ[0];
// this.plane.scale.x += this.scaleXYZ[0];
// this.box.scale.y += this.scaleXYZ[1];
// this.plane.scale.y += this.scaleXYZ[1];
// this.box.scale.z += this.scaleXYZ[2];
// this.plane.scale.z += this.scaleXYZ[2];
// }
// } else if (this.scaleType === 'small') {
// if (this.axialDirection === 'X') {
// this.box.scale.x -= this.scaleXYZ[0];
// this.plane.scale.x -= this.scaleXYZ[0];
// } else if (this.axialDirection === 'Y') {
// this.box.scale.y -= this.scaleXYZ[1];
// this.plane.scale.y -= this.scaleXYZ[1];
// } else if (this.axialDirection === 'Z') {
// this.box.scale.z -= this.scaleXYZ[2];
// this.plane.scale.z -= this.scaleXYZ[2];
// }else if(this.axialDirection === ''){
// this.box.scale.x -= this.scaleXYZ[0];
// this.plane.scale.x -= this.scaleXYZ[0];
// this.box.scale.y -= this.scaleXYZ[1];
// this.plane.scale.y -= this.scaleXYZ[1];
// this.box.scale.z -= this.scaleXYZ[2];
// this.plane.scale.z -= this.scaleXYZ[2];
// }
// } else {
// this.box.scale.x = 1
// this.plane.scale.x = 1
// this.box.scale.y = 1
// this.plane.scale.y = 1
// this.box.scale.z = 1
// this.plane.scale.z = 1
// }
},
**// 模型旋转
**modelRotation(type){
if(this.rotationType == 'x'){
if(this.rotation == 'Math.PI/2'){
this.box.rotateX(Math.PI/-2);//上面
}else if(this.rotation == 'Math.PI/-2'){
this.box.rotateX(Math.PI/2)
}
}else if(this.rotationType == 'y'){
if(this.rotation == 'Math.PI/2'){
this.box.rotateY(Math.PI/-2)
}else if(this.rotation == 'Math.PI/-2'){
this.box.rotateY(Math.PI/2)
}else if(this.rotation == 'Math.PI/180'){
this.box.rotateY(Math.PI/-180)
}else if(this.rotation == 'Math.PI/1'){
this.box.rotateY(Math.PI/1)
}
}
if(type ==1){
this.box.rotateX(Math.PI/2);//上面
this.rotation = 'Math.PI/2'
this.rotationType = 'x'
}else if(type==2){
this.box.rotateX(Math.PI/-2);//下面
this.rotation = 'Math.PI/-2'
this.rotationType = 'x'
}else if(type==3){
this.box.rotateY(Math.PI/2);//左面
this.rotation = 'Math.PI/2'
this.rotationType = 'y'
}else if(type==4){
this.box.rotateY(Math.PI/-2);//右面
this.rotation = 'Math.PI/-2'
this.rotationType = 'y'
}else if(type ==5){
this.box.rotateY(Math.PI/180);//前面
this.rotation = 'Math.PI/180'
this.rotationType = 'y'
}else if(type ==6){
this.box.rotateY(Math.PI/1);//前面
this.rotation = 'Math.PI/1'
this.rotationType = 'y'
}
},****
initGui() { // 调试插件
new dat.GUI();
},
initScene() { // 场景
this.scene = new Scene();
this.scene.background = new Color(0x3c3838);
this.scene.fog = new Fog(0x050505, 2000, 3500);
},
initCamera() { // 相机
this.camera = new PerspectiveCamera(35, this.aspect_w / this.aspect_h, 0.1, 50000);
const max = Math.max(...this.xyz) + 20;
let num = 1000/max
this.camera.position.z = max+100;
this.camera.position.x = max-4000*num;
this.camera.position.y = max-300;
console.log(max)
},
initLight() { // 灯光
const light = new DirectionalLight(0xffffff, 5.0);
light.position.set(500, 500, 3500);
this.scene.add(light);
const light1 = new DirectionalLight(0xffffff, 1);
light.position.set(1400, -3500, 1400);
this.scene.add(light1);
const light2 = new DirectionalLight(0xffffff, 1);
light.position.set(-1400, -3500, 1400);
this.scene.add(light2);
const light3 = new DirectionalLight(0xffffff, 1);
light.position.set(-3500, -3500, -3500);
this.scene.add(light3);
},
initRenderer() { // 渲染器
this.renderer = new WebGLRenderer();
this.renderer.setPixelRatio(window.devicePixelRatio);
const h = this.container.clientWidth / this.camera.aspect;
const w = this.container.clientWidth;
if (w === 0) {
this.renderer.setSize(this.aspect_w, this.aspect_h);
} else {
this.renderer.setSize(w, h);
}
this.container.appendChild(this.renderer.domElement);
},
initStats() { // 性能插件
this.stats = new Stats();
this.container.appendChild(this.stats.dom);
},
initControls() { // 控制器
this.controls = new OrbitControls(this.camera, this.container);
},
initContent() {
if (!this.imgs || this.imgs.length < 1) return;
const box = new BoxGeometry(this.xyz[0], this.xyz[1], this.xyz[2]); // XYZ
// 加载六个面的纹理贴图
const textureLoader = new TextureLoader();
// textureLoader.setCrossOrigin('');
// 材质数组
const materialArr = this.imgs.map(img => new MeshBasicMaterial({map: textureLoader.load(img)}));
const mesh = new Mesh(box, materialArr);
this.mesh = mesh
// mesh.scale.set(2, 1.5, 2); 模型缩放
// 边缘棱线
const edges = new EdgesGeometry(box, 1);
const line = new LineSegments(edges, new LineBasicMaterial({color: 0xffffff}));
this.box = new Group();
this.box.add(mesh);
this.box.add(line);
this.boxCopy = JSON.parse(JSON.stringify(this.box))
this.scene.add(this.box);
},
update() {
// this.stats.update();
this.controls.update();
},
animate() {
requestAnimationFrame(this.animate);
this.renderer.render(this.scene, this.camera);
this.update();
},
init() {
if (this.fullScreen) {
this.aspect_w = window.innerWidth; // 宽高比_宽
this.aspect_h = window.innerHeight; // 宽高比_高
}
this.container = this.$refs.container;
this.initScene();
this.initCamera();
this.initRenderer();
this.initLight();
// this.initGui();
// this.initStats();
this.initContent();
this.initControls();
// 窗口变动触发的方法
const onWindowResize = () => {
// 重新设置相机的宽高比
this.camera.aspect = this.aspect_w / this.aspect_h;
// 更新相机投影矩阵
this.camera.updateProjectionMatrix();
// 更新渲染器大小
const h = this.container.clientWidth / this.camera.aspect;
const w = this.container.clientWidth;
this.renderer.setSize(w, h);
};
window.addEventListener('resize', onWindowResize, false);
},
capture() { // 截图
this.renderer.render(this.scene, this.camera); // 先渲染一帧,防止截图内容为空
const src = this.renderer.domElement.toDataURL("image/jpeg"); //转化为base64
this.$emit('capture', src);
return src;
},
// initThreePlanes({x = 0, y = 0, z = 0} = {}, {xImgUrl, yImgUrl, zImgUrl}) {
initThreePlanes(obj, {xImgUrl, yImgUrl, zImgUrl}) {
// 注意:后端的z对应前端的y,后端的x对应前端的z,后端的y对应前端的x
// let y = obj.y;
// let z = obj.z;
// let x = obj.x;
// let y = obj.z;
// let z = obj.x;
// let x = obj.y;
let x = obj.x;
let y = obj.y;
let z = obj.z;
x -= 1;
y -= 1;
z -= 1;
const textureLoader = new TextureLoader();
// const [z_length, x_length, y_length] = this.xyz;
const [x_length, y_length, z_length] = this.xyz;
// y = y_length - y; // 后端的这个方向相反
// z = y_length - z; // 后端的这个方向相反
if (this.plane) {
this.scene.remove(this.plane);
}
this.plane = new Group();
if (zImgUrl) {
// z方向的面
const geometry_z = new PlaneBufferGeometry();
const vertices_z = new Float32Array([
0, y_length, z, // 左上
x_length, y_length, z, // 右上
0, 0, z, // 左下
x_length, 0, z // 右下
// 0, y_length, 360, // 左上
// x_length, y_length, 360, // 右上
// 0, 0, 360, // 左下
// x_length, 0, 360 // 右下
]);
geometry_z.addAttribute("position", new BufferAttribute(vertices_z, 3));
const material_z = new MeshBasicMaterial({side: DoubleSide, map: textureLoader.load(zImgUrl)});
this.plane_z = new Mesh(geometry_z, material_z);
this.plane.add(this.plane_z);
}
if (yImgUrl) {
const geometry_y = new PlaneBufferGeometry();
const vertices_y = new Float32Array([
0, y, 0, // 左上
x_length, y, 0, // 右上
0, y, z_length, // 左下
x_length, y, z_length, // 右下
]);
geometry_y.addAttribute("position", new BufferAttribute(vertices_y, 3));
const material_y = new MeshBasicMaterial({side: DoubleSide, map: textureLoader.load(yImgUrl)});
this.plane_y = new Mesh(geometry_y, material_y);
this.plane.add(this.plane_y);
}
if (xImgUrl) {
const geometry_x = new PlaneBufferGeometry();
const vertices_x = new Float32Array([
x, y_length, 0, // 左上
x, y_length, z_length, // 右上
x, 0, 0, // 左下
x, 0, z_length // 右下
]);
geometry_x.addAttribute("position", new BufferAttribute(vertices_x, 3));
const material_x = new MeshBasicMaterial({side: DoubleSide, map: textureLoader.load(xImgUrl)});
this.plane_x = new Mesh(geometry_x, material_x);
this.plane.add(this.plane_x);
}
// 将模型中心移到坐标系原点
this.plane.translateX(-x_length / 2);
this.plane.translateY(-y_length / 2);
this.plane.translateZ(-z_length / 2);
this.scene.add(this.plane);
},
showThreePlane(xyz, imgs) {
this.initThreePlanes(xyz, imgs);
this.plane.visible = true;
this.box.visible = false;
const max = Math.max(...this.xyz) + 250;
this.camera.position.z = max;
this.camera.position.x = max;
this.camera.position.y = max;
},
showBox() {
this.box.visible = true;
this.plane.visible = false;
const max = Math.max(...this.xyz) + 50;
this.camera.position.z = max;
this.camera.position.x = max;
this.camera.position.y = max;
},
maximize() {
this.aspect_w = window.innerWidth; // 宽高比_宽
this.aspect_h = window.innerHeight; // 宽高比_高
this.camera.aspect = this.aspect_w / this.aspect_h;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.aspect_w, this.aspect_h);
this.fullScreen = true;
},
mini() {
this.aspect_w = this.aspect_w || 600; // 宽高比_宽
this.aspect_h = this.aspect_h || 450; // 宽高比_高
this.camera.aspect = this.aspect_w / this.aspect_h;
this.camera.updateProjectionMatrix();
this.renderer.setSize(this.aspect_w, this.aspect_h);
this.fullScreen = false;
},
},
mounted() {
this.init();
this.animate();
}
};
</script>
<style scoped lang="scss">
#container {
height: 100%;
width: 100%;
}
::v-deep canvas {
width: 100% !important;
height: 100% !important;
}
.fullModal {
position: fixed;
top: 0;
left: 0;
z-index: 2042;
width: 100vw;
height: 100vh;
background-color: #000;
&_close {
position: fixed;
cursor: pointer;
top: 5px;
right: 5px;
height: 25px;
width: 25px;
z-index: 2043;
color: #FFF;
}
}
</style>