项目太丑,扎眼了,刚写好,不立刻记下笔记就忘了,自己优化 npm run serve
链接:https://pan.baidu.com/s/1fVZLTfuALxi4Gmpc6hGNfg
提取码:ee0e
复制这段内容后打开百度网盘手机App,操作更方便哦--来自百度网盘超级会员V3的分享
1.vue引入three 不用在main中做配置
import * as THREE from "three";
import { OBJLoader, MTLLoader } from "three-obj-mtl-loader";
import { CSS2DRenderer, CSS2DObject } from "three-css2drender";
const OrbitControls = require("three-orbit-controls")(THREE);
或者直接在package.json中dependencies和devDependencies对象中加入下面的
然后 npm install
"dependencies": {
"three-css2drender": "^1.0.0",
"three-obj-mtl-loader": "^1.0.3"
},
"devDependencies": {
"three": "^0.123.0",
"three-obj-mtl-loader": "^1.0.3"
},
听说不能直接赋值,vue会监听,也没研究那么深,人家竟然这么说,也就没定义值
data() {
return {
axes: "",
group: "",
scene: "",
light: "",
camera: "",
controls: "",
renderer: "",
directionalLight: "",
ambient: "",
loader: "", //地板
glass_material: "",
publicPath: process.env.BASE_URL,
m1: "",
m2: "",
m3: "",
mouse: "",
objects: [],
raycaster: "",
getBoundingClientRect: {},
offsetWidth: "",
offsetHeight: "",
theta: 0, //相机旋转角度
cord1: {},
cord2: {},
cord3: {},
cord4: {},
selectObject: {}, //被点击的第一个元素
};
},
mounted() {
this.init();
this.loadObj();
this.animate();
},
这里是:因为我加载的obj模型是有四个子模块组成的打印的时候它的child有四个,才定义四个的
this.cord1 = new THREE.Object3D();
this.cord2 = new THREE.Object3D();
this.cord3 = new THREE.Object3D();
this.cord4 = new THREE.Object3D();
接下来:创建一个场景
this.scene = new THREE.Scene();
接下来:在three.js,可以利用THREE.Raycaster来达到点击与交互,即选择物体的操作。点击事件少不了的东西
this.raycaster = new THREE.Raycaster();
接下来:因为是小白,就给自己加载的模型放了个长500的坐标轴方便观看
this.scene.add(new THREE.AxesHelper(500));
接下来:得创建相机了,因为不想弄全屏的模型展示,做个800x800的,要是想做全屏的就用window.innerWidth/window.innnerHeight,因为800/800为1,担心不知道是哪个一,第二个参数
/**
* @param {
* 初始化相机
* }
*/
this.camera = new THREE.PerspectiveCamera(
1,
// window.innerWidth / window.innerHeight,
800/800,
1,
10000
);
接下来设置相机的位置还是什么东西,自己查查这个api就会有介绍,反正我不懂,瞎设置呗,然后就把相机add到scene(场景)中
this.camera.position.set(0, 1000, 1000);
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
// this.camera.lookAt(0, 0, 0);
this.scene.add(this.camera);
接下来:既然有点击获取当前的obj模型模块,当然得获取下鼠标的位置吧
//鼠标的位置
this.mouse = new THREE.Vector2();
接下来:渲染器,干什么的?依次的设置是,alpha: true, antialias: true,加了好像清楚一点,然后就是渲染大小当然我的是800x800的全屏就用window.innerWidth, window.innerHeight,渲染显示比例,渲染背景颜色,最后是追加到dom节点
/**
* @param {
* 渲染器
* }
*/
this.renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
});
// this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setSize(800, 800);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0x87ceeb, 1.0);
const container = document.getElementById("container");
container.appendChild(this.renderer.domElement);
接下来:控制器,不添加模型不能旋转,开始找了好多参数加里面,最后发现还是什么都不加效果控制的更舒坦一点
/**
* @param {
* 控制器
* }
*/
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
// this.controls.autoRotate=true;
// this.controls.target.set(0, 0, 0);
// this.controls.minDistance = 80;
// this.controls.maxDistance = 400;
// this.controls.maxPolarAngle = Math.PI / 3;
this.controls.update();
接下来:定义光线,就是有点光打在引入的3d模型上,让模型能正常显示颜色,要不然就是黑白的,参数乱设置的,也没搞懂
/**
* @param {
* 定义光线
* }
*/
this.directionalLight = new THREE.DirectionalLight(0xc1c1c1, 1);
// 光线照射的方向
this.directionalLight.position.set(0, 1000, 1000).normalize();
this.scene.add(this.directionalLight);
this.ambient = new THREE.AmbientLight(0xffffff, 1);
this.ambient.position.set(0, 0, 0);
this.scene.add(this.ambient);
${that.publicPath}models/zzs.mtl
,function(){})// var loader = THREE.Loader.Handlers.get( url );
var loader = manager.getHandler(url);
在methods中定义的loadObj(加载obj模型)函数代码
loadObj() {
let that = this;
let objLoader = new OBJLoader();
let mtlLoader = new MTLLoader();
let textureLoader = new THREE.TextureLoader();
// mtlLoader.load(`${that.publicPath}models/zzs.mtl`, function (materials) {
mtlLoader.load(
`${that.publicPath}models/zzs.mtl`,
// `${that.publicPath}models/object.mtl`,
function (materials) {
objLoader.setMaterials(materials);
objLoader.load(
// `${that.publicPath}models/object.obj`,
`${that.publicPath}models/zzs.obj`,
function (obj) {
obj.scale.multiplyScalar(1);
obj.traverse(function (child) {
console.log(child.name, "ddddd");
// 这里把匹配的模块单独提取出来,如果想让其中一个子模块动
// 到时候直接可以在函数外面methed中写一个方法设置,比方说
//就单独让m3这个子模块单独动了
// that.m3.position.x -= 0.1;
// that.m3.position.y -= 0.1;
// that.m3.position.z -= 0.1;
if (child instanceof THREE.Mesh) {
if (child.name == "Box001") {
that.m1 = child;
} else if (child.name == "Cylinder002") {
that.m2 = child;
} else if (child.name == "Cylinder003") {
//圆柱
that.m3 = child;
} else if (child.name == "Cylinder004") {
//圆柱
that.m4 = child;
}
}
});
// 这里是做关联,比方说我要移动m4模块,整个模块都会跟着动
// 我要移动m3模块,只有m3,m2,m1模块会动
that.cord4.add(that.m4);
that.cord3.add(that.m3, that.cord4);
that.cord2.add(that.m2, that.cord3);
that.cord1.add(that.m1, that.cord2);
that.scene.add(that.cord1);
that.render();
return obj;
/**
* @param {
* 将模型加入场景中
* }
*/
// that.scene.add(obj);
/**
* @param {
* 将纹理加入模型中
* }
*/
// that.renderer.render(that.scene, that.camera);
},
function (xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
function (error) {
console.log("An error happened");
}
);
}
);
},
接下来:添加窗口监听事件,固定宽高的就不用了了吧,800x800监听window窗口大小不还是800x800展示么,不过像是用window.innerWidth做长window.innerHeight做宽全屏展示的还是要监听的
window.addEventListener("resize", this.onWindowResize, false);
然后这是this.onWindowResize的方法,卸载methods中就行
/**
* @param {
* 窗口监听函数
* }
*/
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
},
接下来:点击总得有个点击事件,
一、这个是800x800固定宽高的点击函数写法,因为不需要监听点击全屏啊,监听当前的dom容器就行了
this.$refs.container.addEventListener("click", this.clickTest, false);
二、这个是设置长window.innerWidth宽window.innerHeight的写法
window.addEventListener("click", this.clickTest, false);
三、点击函数方法:在methods中定义clickTest方法
开始只知道全屏的点击可以点击到模型本身并不知道800x800这种自定义宽高的怎么点击到当前obj模型,总是匹配不到,全网都没找到这个方法,可能全网就我是最笨的,
window.innerWidth x window.innerHeight全屏大小的写法
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
800x800写法:因为监听的是当前的dom元素的点击区域,发现event对象中有layerX 和layerY 两个参数非常可疑,正是鼠标点击dom元素上在dom元素自身上的坐标,参考上面全屏写法所以就懂了。
this.raycaster.intersectObjects(this.objects);是判断当前点击的位置与照相机的位置相链接成一条直线,哪些模型会在这条线上,返回一个数组,这个数组如果有值,第[0]位就是最上面的,也就是所谓点击的这么模型中的子模块
this.mouse.x = (event.layerX / 800) * 2 - 1;
this.mouse.y = -(event.layerY / 800) * 2 + 1;
//点击事件
clickTest(event) {
console.log(event)
event.preventDefault();
this.objects = [];
// this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.x = (event.layerX / 800) * 2 - 1;
this.mouse.y = -(event.layerY / 800) * 2 + 1;
// this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
this.scene.children.forEach((child) => {
//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
if (
!(child instanceof THREE.GridHelper) &&
!(child instanceof THREE.DirectionalLight) &&
!(child instanceof THREE.AmbientLight)
) {
child.children.forEach((childv) => {
if (childv instanceof THREE.Mesh) {
//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
this.objects.push(childv);
} else if (childv instanceof THREE.Object3D) {
this.meshDg(childv.children);
}
});
}
});
console.log(this.objects, 6666666 + "obj");
var intersects = this.raycaster.intersectObjects(this.objects);
console.log(intersects, 8888 + "intersects");
if (
intersects.length != 0 &&
intersects[0].object instanceof THREE.Mesh
) {
this.selectObject = intersects[0].object;
this.changeMaterial(this.selectObject);
console.log(this.selectObject.name);
// $("#alert_center").css({
// display: "block",
// });
// $("#alert_font").html(
// "当前点击的模块名字是:" + this.selectObject.name
// );
this.render();
}
},
点击事件,点击后改变当前对象的方法:在methods中定义,点击了给它变个颜色看看,纯属个人无聊
/**
* @param {
* 改变当前对象属性
* }
*/
changeMaterial(object) {
var material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
});
object.material = material;
},
动画:写完后面,就忘了前面,写着不报错无关痛痒,在methods中定义
/**
* @param {
* 动画
* }
*/
animate() {
requestAnimationFrame(this.animate);
this.render();
},
渲染:忘了具体干嘛的,应该少不了:在methos中定义
/**
* @param {
* 渲染
* }
*/
render() {
this.renderer.render(this.scene, this.camera);
},
<template>
<div class="spring">
<!-- <div class="box"></div> -->
<div id="container" ref="container"></div>
</div>
</template>
<script>
import * as THREE from "three";
import { OBJLoader, MTLLoader } from "three-obj-mtl-loader";
import { CSS2DRenderer, CSS2DObject } from "three-css2drender";
const OrbitControls = require("three-orbit-controls")(THREE);
export default {
name: "",
components: {},
props: {},
data() {
return {
axes: "",
group: "",
scene: "",
light: "",
camera: "",
controls: "",
renderer: "",
directionalLight: "",
ambient: "",
loader: "", //地板
glass_material: "",
publicPath: process.env.BASE_URL,
m1: "",
m2: "",
m3: "",
mouse: "",
objects: [],
raycaster: "",
getBoundingClientRect: {},
offsetWidth: "",
offsetHeight: "",
theta: 0, //相机旋转角度
cord1: {},
cord2: {},
cord3: {},
cord4: {},
selectObject: {}, //被点击的第一个元素
};
},
created() {},
mounted() {
this.init();
this.loadObj();
this.animate();
},
methods: {
/**
* @param {
* 初始化three.js相关内容}
*/
init() {
/**
* @param {
* 场景
* }
*/
this.cord1 = new THREE.Object3D();
this.cord2 = new THREE.Object3D();
this.cord3 = new THREE.Object3D();
this.cord4 = new THREE.Object3D();
this.scene = new THREE.Scene();
this.raycaster = new THREE.Raycaster();
/**
* @param {
* 世界坐标系
* }
*/
this.scene.add(new THREE.AxesHelper(500));
// var helper = new THREE.GridHelper(3000, 50, 0xcd3700, 0x4a4a4a);
// this.scene.add(helper);
/**
* @param {
* 初始化相机
* }
*/
this.camera = new THREE.PerspectiveCamera(
1,
// window.innerWidth / window.innerHeight,
1,
1,
10000
);
this.camera.position.set(0, 1000, 1000);
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
// this.camera.lookAt(0, 0, 0);
this.scene.add(this.camera);
//鼠标的位置
this.mouse = new THREE.Vector2();
/**
* @param {
* 渲染器
* }
*/
this.renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
});
// this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setSize(800, 800);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0x87ceeb, 1.0);
const container = document.getElementById("container");
container.appendChild(this.renderer.domElement);
/**
* @param {
* 获取当前dom元素坐标
* }
*/
this.getBoundingClientRect = this.$refs.container.getBoundingClientRect();
this.offsetWidth = this.$refs.container.offsetWidth;
this.offsetHeight = this.$refs.container.offsetHeight;
/**
* @param {
* 控制器
* }
*/
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.update();
/**
* @param {
* 定义光线
* }
*/
this.directionalLight = new THREE.DirectionalLight(0xc1c1c1, 1);
// 光线照射的方向
this.directionalLight.position.set(0, 1000, 1000).normalize();
this.scene.add(this.directionalLight);
this.ambient = new THREE.AmbientLight(0xffffff, 1);
this.ambient.position.set(0, 0, 0);
this.scene.add(this.ambient);
/**
* @param {
* 添加窗口监听事件(resize-onresize即窗口或框架被重新调整大小)
* }
*/
this.$refs.container.addEventListener("click", this.clickTest, false);
// window.addEventListener("resize", this.onWindowResize, false);
},
/**
* @param {
* 外部模型加载函数
* }
*/
loadObj() {
let that = this;
let objLoader = new OBJLoader();
let mtlLoader = new MTLLoader();
let textureLoader = new THREE.TextureLoader();
// mtlLoader.load(`${that.publicPath}models/zzs.mtl`, function (materials) {
mtlLoader.load(
`${that.publicPath}models/zzs.mtl`,
// `${that.publicPath}models/object.mtl`,
function (materials) {
objLoader.setMaterials(materials);
objLoader.load(
// `${that.publicPath}models/object.obj`,
`${that.publicPath}models/zzs.obj`,
function (obj) {
obj.scale.multiplyScalar(1);
obj.traverse(function (child) {
console.log(child.name,'ddddd')
if (child instanceof THREE.Mesh) {
if (child.name == "Box001") {
that.m1 = child;
} else if (child.name == "Cylinder002") {
that.m2 = child;
} else if (child.name == "Cylinder003") {
that.m3 = child;
}else if (child.name == "Cylinder004") {
that.m4 = child;
}
}
});
that.cord4.add(that.m4);
that.cord3.add(that.m3, that.cord4);
// console.log(999);
// that.cord3.add(that.m3);
that.cord2.add(that.m2, that.cord3);
that.cord1.add(that.m1, that.cord2);
that.scene.add(that.cord1);
that.render();
return obj;
},
function (xhr) {
console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
},
function (error) {
console.log("An error happened");
}
);
}
);
},
//递归 收集 THREE.Mesh
meshDg(child) {
child.forEach((childv) => {
if (childv instanceof THREE.Mesh) {
//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
this.objects.push(childv);
} else if (childv instanceof THREE.Object3D) {
this.meshDg(childv.children);
}
});
},
//点击事件
clickTest(event) {
console.log(event)
event.preventDefault();
this.objects = [];
// this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.x = (event.layerX / 800) * 2 - 1;
this.mouse.y = -(event.layerY / 800) * 2 + 1;
// this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera(this.mouse, this.camera);
this.scene.children.forEach((child) => {
//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
if (
!(child instanceof THREE.GridHelper) &&
!(child instanceof THREE.DirectionalLight) &&
!(child instanceof THREE.AmbientLight)
) {
child.children.forEach((childv) => {
if (childv instanceof THREE.Mesh) {
//根据需求判断哪些加入objects,也可以在生成object的时候push进objects
this.objects.push(childv);
} else if (childv instanceof THREE.Object3D) {
this.meshDg(childv.children);
}
});
}
});
console.log(this.objects, 6666666 + "obj");
var intersects = this.raycaster.intersectObjects(this.objects);
console.log(intersects, 8888 + "intersects");
if (
intersects.length != 0 &&
intersects[0].object instanceof THREE.Mesh
) {
this.selectObject = intersects[0].object;
this.changeMaterial(this.selectObject);
console.log(this.selectObject.name);
//这里找的代码,用到了jq,用vue就更简单了
// $("#alert_center").css({
// display: "block",
// });
// $("#alert_font").html(
// "当前点击的模块名字是:" + this.selectObject.name
// );
this.render();
}
},
/**
* @param {
* 改变当前对象属性
* }
*/
changeMaterial(object) {
var material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
});
object.material = material;
},
/**
* @param {
* 动画
* }
*/
animate() {
requestAnimationFrame(this.animate);
this.render();
},
/**
* @param {
* 渲染
* }
*/
render() {
this.renderer.render(this.scene, this.camera);
},
/**
* @param {
* 窗口监听函数
* }
*/
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
// this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setSize(800, 800);
},
},
computed: {},
};
</script>
<style lang='scss' scoped>
.spring {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.box {
width: 100%;
height: 100px;
}
</style>