点 ponint
首先,我们如果需要在环境中画一个点.我们希望这个点是圆形的,那么我们就创建一个球体网格模型对象,设置我们希望的位置(position),下面的代码中,把y轴+1.是因为在业务场景中把圆点浮出面板,可以看到一个完整的球体点的位置.
//- 添加点function addPoint(point) {if (scene) {scene.children.forEach(item => {if (item.type === "LineLoop") {scene.remove(item);tempData = []}})}let geometry233 = new THREE.SphereGeometry(1, 40, 40); //创建一个立方体几何对象Geometry// 点渲染模式let material233 = new THREE.PointsMaterial({color: 0xff0000,// size: 5.0 //点对象像素尺寸}); //材质对象let points233 = new THREE.Points(geometry233, material233); //点模型对象points233.position.set(point.x, point.y +1, point.z);scene.add(points233)}
核心代码是通过实例化three.js的Points对象,通过外部点击事件来获取需要放置点的位置,然后添加点对象到场景中
线 Line
有了点的位置,只需要添加线把点连接起来,就行了,下面的代码中包含了部分业务逻辑成分.
tempData变量为模型中的点的个数.通过循环,把实例化THREE.Vector3对象进行数组保存在tempData2变量中.
geometry.vertices需要一个数组,所以我们就直接把前面保存好的tempData2数组push进去即可.
vertices: 用来保存模型中所有顶点位置的数组。
要更新该数组,Geometry.verticesNeedUpdate 需要被设置为true。
let geometry = new THREE.Geometry(); //声明一个空几何体对象tempData.forEach(item => {tempData2.push(new THREE.Vector3(item.x, item.y + 1, item.z))})geometry.vertices.push(...tempData2)let material = new THREE.MeshLambertMaterial({color: 0xfac15f,//三角面颜色side: THREE.DoubleSide//两面可见});//材质对象let line = new THREE.LineLoop(geometry, material);//线条模型对象scene.add(line);
通过添加点和线了,我们在点击后即可看到连线效果了.
由于这里用的是LineLoop对象,当点击第三个点的时候,会自动连线闭环,如果不希望自动闭环可以使用Line对象进行实例化
var line = new THREE.Line( geometry, material );
在有线的部分绘制立方体
首先我们明白,绘制立方体和我们不同在(0,0)位置放置一个立方体并无不同,只是我们需要精确到线条的长度决定于墙体的长度,线条的角度决定了墙体的角度即可.
这里提供一个墙体绘制算法函数,通过传入4个点的方式计算长度和角度.
//画墙function drawWall(objs) {//长度let lens = Math.sqrt(Math.pow((Number(objs.eZ) - Number(objs.sZ)), 2) + Math.pow((Number(objs.eX) - Number(objs.sX)), 2));//位置let posx = (objs.eX + objs.sX) / 2;let posz = (objs.eZ + objs.sZ) / 2;//- 纹理材质var texture = new THREE.TextureLoader().load("images/pink.png");texture.wrapS = THREE.RepeatWrapping;texture.wrapT = THREE.RepeatWrapping;texture.repeat.set(4, 4);//旋转角度let rotate = -Math.atan2((objs.eZ - objs.sZ), (objs.eX - objs.sX));console.log(Math.atan2((objs.eZ - objs.sZ), (objs.eX - objs.sX)))// return;let box = new THREE.CubeGeometry(lens, 15, 2); //- 墙体参数var material = new THREE.MeshBasicMaterial({// color: 0xe04747,transparent: true,opacity: 0.4,map: texture});var mesh = new THREE.Mesh(box, material);mesh.position.set(posx, 15 / 2, posz);mesh.rotation.y = rotate;this.scene.add(mesh);render();}
调用绘制墙体函数的时,就需要手动组装一下每个点的参数.
document.getElementById('show3d').addEventListener('click', function (e) {if (scene) {scene.children.forEach(item => {//- 判断当前子对象是否是环形线if (item.type === 'LineLoop') {console.log(item.geometry.vertices)for (let i = 0; i < item.geometry.vertices.length; i++) {if (i !== item.geometry.vertices.length - 1) {let params = {eZ: item.geometry.vertices[i + 1].z,sZ: item.geometry.vertices[i].z,eX: item.geometry.vertices[i + 1].x,sX: item.geometry.vertices[i].x}drawWall(params)}}//- 如果不是一条线,至少是一个三角形的情况最后一条边if (item.geometry.vertices.length > 2) {let index = item.geometry.vertices.length - 1;let params = {eZ: item.geometry.vertices[0].z,sZ: item.geometry.vertices[index].z,eX: item.geometry.vertices[0].x,sX: item.geometry.vertices[index].x}drawWall(params)}// drawWall(item.geometry)}})}})
完整代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>第一个three.js文件_WebGL三维场景</title><style>body {margin: 0;overflow: hidden;/* 隐藏body窗口区域滚动条 */}.clickFn {position: fixed;top: 10px;right: 10px;padding: 4px 15px;background-color: #999;border-radius: 4px;}</style><!--引入three.js三维引擎--><script src="./js-r100/three.js"></script><script src="./js-r100/controls/OrbitControls.js"></script></head><body><div class='clickFn' id="show3d">显示3D墙体</div><script>/*** 创建场景对象Scene*/var scene = new THREE.Scene();/*** 创建网格模型*/// var geometry = new THREE.SphereGeometry(60, 40, 40); //创建一个球体几何对象var geometry = new THREE.BoxGeometry(100, 2, 100); //创建一个立方体几何对象Geometryvar material = new THREE.MeshLambertMaterial({color: 0xffffff,name: 'belinebox'}); //材质对象Materialvar mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshscene.add(mesh); //网格模型添加到场景中/*** 光源设置*///点光源var point = new THREE.PointLight(0xffffff);point.position.set(400, 200, 300); //点光源位置scene.add(point); //点光源添加到场景中//环境光var ambient = new THREE.AmbientLight(0x444444);scene.add(ambient);/*** 相机设置*/var width = window.innerWidth; //窗口宽度var height = window.innerHeight; //窗口高度var k = width / height; //窗口宽高比var s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大//创建相机对象var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);camera.position.set(200, 300, 200); //设置相机位置camera.lookAt(scene.position); //设置相机方向(指向的场景对象)/*** 创建渲染器对象*/var renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);//设置渲染区域尺寸renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色document.body.appendChild(renderer.domElement); //body元素中插入canvas对象//- 渲染函数function render() {//执行渲染操作 指定场景、相机作为参数renderer.render(scene, camera);}render();//- 添加鼠标对视觉相机的操作let controls = new THREE.OrbitControls(camera);//监听鼠标事件,触发渲染函数,更新canvas画布渲染效果controls.addEventListener('change', render, { passive: true });// 获取与射线相交的对象数组function getIntersects(event) {event.preventDefault();// console.log("event.clientX:" + event.clientX)// console.log("event.clientY:" + event.clientY)// 声明 raycaster 和 mouse 变量var raycaster = new THREE.Raycaster();var mouse = new THREE.Vector2();// 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置raycaster.setFromCamera(mouse, camera);// 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越近的越靠前var intersects = raycaster.intersectObjects(scene.children);//返回选中的对象数组return intersects;}//- 随机生成16进制颜色function getColor() {var colorElements = "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f";var colorArray = colorElements.split(",");var color = "#";for (var i = 0; i < 6; i++) {color += colorArray[Math.floor(Math.random() * 16)];}return color;}//画墙function drawWall(objs) {//长度let lens = Math.sqrt(Math.pow((Number(objs.eZ) - Number(objs.sZ)), 2) + Math.pow((Number(objs.eX) - Number(objs.sX)), 2));//位置let posx = (objs.eX + objs.sX) / 2;let posz = (objs.eZ + objs.sZ) / 2;//- 纹理材质var texture = new THREE.TextureLoader().load("images/pink.png");texture.wrapS = THREE.RepeatWrapping;texture.wrapT = THREE.RepeatWrapping;texture.repeat.set(4, 4);//旋转角度let rotate = -Math.atan2((objs.eZ - objs.sZ), (objs.eX - objs.sX));console.log(Math.atan2((objs.eZ - objs.sZ), (objs.eX - objs.sX)))// return;let box = new THREE.CubeGeometry(lens, 15, 2); //- 墙体参数var material = new THREE.MeshBasicMaterial({// color: 0xe04747,transparent: true,opacity: 0.4,map: texture});var mesh = new THREE.Mesh(box, material);mesh.position.set(posx, 15 / 2, posz);mesh.rotation.y = rotate;this.scene.add(mesh);render();}document.getElementById('show3d').addEventListener('click', function (e) {if (scene) {scene.children.forEach(item => {//- 判断当前子对象是否是环形线if (item.type === 'LineLoop') {console.log(item.geometry.vertices)for (let i = 0; i < item.geometry.vertices.length; i++) {if (i !== item.geometry.vertices.length - 1) {let params = {eZ: item.geometry.vertices[i + 1].z,sZ: item.geometry.vertices[i].z,eX: item.geometry.vertices[i + 1].x,sX: item.geometry.vertices[i].x}drawWall(params)}}//- 如果不是一条线,至少是一个三角形的情况最后一条边if (item.geometry.vertices.length > 2) {let index = item.geometry.vertices.length - 1;let params = {eZ: item.geometry.vertices[0].z,sZ: item.geometry.vertices[index].z,eX: item.geometry.vertices[0].x,sX: item.geometry.vertices[index].x}drawWall(params)}// drawWall(item.geometry)}})}})let isdown = false, ismove = false;window.addEventListener('mousemove', function () {if (isdown) {ismove = true;//鼠标拖动事件执行函数}})window.addEventListener('mousedown', function () {isdown = trueismove = false})//- 点击事件window.addEventListener('mouseup', function (event) {if (!isdown && ismove) {return 0;}if (ismove) {return 0;}let intsersects = getIntersects(event)if (intsersects.length > 0) {if (intsersects[0].object.material.name === 'belinebox') {let mathColor = getColor();console.log(intsersects[0])// intsersects[0].object.material.color.set(mathColor)//- 添加点到点击的面板上addPoint(intsersects[0].point);render()}}})var tempData = []; //- 存放当前模型中的点的个数var tempData2 = [];//- 添加点function addPoint(point) {if (scene) {scene.children.forEach(item => {if (item.type === "LineLoop") {scene.remove(item);tempData = []}})}tempData.push(point)let geometry233 = new THREE.SphereGeometry(1, 40, 40); //创建一个立方体几何对象Geometry// 点渲染模式let material233 = new THREE.PointsMaterial({color: 0xff0000,// size: 5.0 //点对象像素尺寸}); //材质对象let points233 = new THREE.Points(geometry233, material233); //点模型对象points233.position.set(point.x, point.y +1, point.z);scene.add(points233)let geometry = new THREE.Geometry(); //声明一个空几何体对象tempData.forEach(item => {tempData2.push(new THREE.Vector3(item.x, item.y + 1, item.z))})console.log(tempData2, 999)geometry.vertices.push(...tempData2)let material = new THREE.MeshLambertMaterial({color: 0xfac15f,//三角面颜色side: THREE.DoubleSide//两面可见});//材质对象let line = new THREE.LineLoop(geometry, material);//线条模型对象scene.add(line);}</script></body></html>
绘制底部区域
绘制的区域底部是个多边形,我们可以通过前面画点的形式添加多个三角形来填充多边形,经常尝试发现,如果所有点在当前角度下都是最外侧,则显示正常,如果出现”凹”字类型的有部分挖空区域,则需要通过算法来实现三角形的绘制方法了,目前该代码存在算法bug
//- 画不规则图形let geometry = new THREE.Geometry(); //声明一个几何体对象Geometryconsole.log(pointVector3Arr, 222);//顶点坐标添加到geometry对象geometry.vertices.push(...pointVector3Arr)if (pointVector3Arr.length > 2) {pointVector3Arr.forEach((item, index) => {if (index < pointVector3Arr.length - 2) {face3Arr.push(new THREE.Face3(0, index + 1, index + 2))face3Arr[index].color = new THREE.Color(0xff0000);}})}geometry.faces.push(...face3Arr);console.log(geometry);let material = new THREE.MeshBasicMaterial({color: 0xe04747,side: THREE.DoubleSide, // -两面可见});let mesh = new THREE.Mesh(geometry, material);scene.add(mesh);render();
呈现效果1: 当不存在凹面的情况
呈现效果2: 存在凹面的情况
这样肯定是不行的,项目中电子围栏的区域底部虽然绘制三角形的方式进行了填充,当遇到有向内部缺口时,底部区域范围就出现问题了.为了解决这个问题,我们需要换一个思路来填充底部区域
shape 绘制多边形底部区域
这里写了一个方法来实现底部区域绘制,只要通过shape对象,添加各点坐标,通过ShapeBufferGeometry创建一个多边形的几何体,放置到具体的位置上.
这里需要注意的是,shape.moveTo以及lineTo函数接收的都是一个平面坐标系.当直接通过ShapeBufferGeometry创建几何体添加到场景后,是竖直向下的,这里我们需要在生成Mesh对象后,对Mesh对象进行旋转.就可以正常显示了
//- 画不规则多边形底座 -- 用于作业面底部区域颜色绘制function generatePloygon() {let pointArr = [];if (scene) {scene.children.forEach(item => {//- 判断当前子对象是否是环形线if (item.type === 'LineLoop') {item.geometry.vertices.forEach(item2 => {pointArr.push({ x: item2.x, y: item2.y, z: item2.z })})}})}//- 根据绘制的点来定义一个二维形状平面let shape = new THREE.Shape();console.log(pointArr, 111);shape.moveTo(pointArr[0].x, pointArr[0].z);for (let i = 1; i < pointArr.length; i++) {const element = pointArr[i];shape.lineTo(pointArr[i].x, pointArr[i].z);}shape.autoClose = true;//- 从一个或多个路径形状中创建一个单面多边形几何体let shapeGeometry = new THREE.ShapeBufferGeometry(shape, 25);let shapeMaterial = new THREE.MeshBasicMaterial({color: 0xe04747,side: THREE.DoubleSide,transparent: true,opacity: 0.4})let shapeMesh = new THREE.Mesh(shapeGeometry, shapeMaterial);shapeMesh.rotateX(Math.PI / 2); //- 按照X轴进行旋转90度shapeMesh.position.y = 2 //- 设置高度2,放置于地面重合看不到scene.add(shapeMesh);render();}
完整代码:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>第一个three.js文件_WebGL三维场景</title><style>body {margin: 0;overflow: hidden;/* 隐藏body窗口区域滚动条 */}.clickFn {position: fixed;top: 10px;right: 10px;padding: 4px 15px;background-color: #999;border-radius: 4px;}</style><!--引入three.js三维引擎--><script src="./js-r100/three.js"></script><script src="./js-r100/controls/OrbitControls.js"></script></head><body><div class='clickFn' id="show3d">显示3D墙体</div><script>/*** 创建场景对象Scene*/var scene = new THREE.Scene();/*** 创建网格模型*/// var geometry = new THREE.SphereGeometry(60, 40, 40); //创建一个球体几何对象var geometry = new THREE.BoxGeometry(100, 2, 100); //创建一个立方体几何对象Geometryvar material = new THREE.MeshLambertMaterial({color: 0xffffff,name: 'belinebox',transparent: true,opacity: 0.4}); //材质对象Materialvar mesh = new THREE.Mesh(geometry, material); //网格模型对象Meshscene.add(mesh); //网格模型添加到场景中/*** 光源设置*///点光源var point = new THREE.PointLight(0xffffff);point.position.set(400, 200, 300); //点光源位置scene.add(point); //点光源添加到场景中//环境光var ambient = new THREE.AmbientLight(0x444444);scene.add(ambient);/*** 相机设置*/var width = window.innerWidth; //窗口宽度var height = window.innerHeight; //窗口高度var k = width / height; //窗口宽高比var s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大//创建相机对象var camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);camera.position.set(200, 300, 200); //设置相机位置camera.lookAt(scene.position); //设置相机方向(指向的场景对象)/*** 创建渲染器对象*/var renderer = new THREE.WebGLRenderer();renderer.setSize(width, height);//设置渲染区域尺寸renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色document.body.appendChild(renderer.domElement); //body元素中插入canvas对象//- 渲染函数function render() {//执行渲染操作 指定场景、相机作为参数renderer.render(scene, camera);}render();//- 添加鼠标对视觉相机的操作let controls = new THREE.OrbitControls(camera);//监听鼠标事件,触发渲染函数,更新canvas画布渲染效果controls.addEventListener('change', render, { passive: true });// 获取与射线相交的对象数组function getIntersects(event) {event.preventDefault();// console.log("event.clientX:" + event.clientX)// console.log("event.clientY:" + event.clientY)// 声明 raycaster 和 mouse 变量var raycaster = new THREE.Raycaster();var mouse = new THREE.Vector2();// 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置raycaster.setFromCamera(mouse, camera);// 获取与raycaster射线相交的数组集合,其中的元素按照距离排序,越近的越靠前var intersects = raycaster.intersectObjects(scene.children);//返回选中的对象数组return intersects;}//- 随机生成16进制颜色function getColor() {var colorElements = "0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f";var colorArray = colorElements.split(",");var color = "#";for (var i = 0; i < 6; i++) {color += colorArray[Math.floor(Math.random() * 16)];}return color;}// 辅助坐标系 参数250表示坐标系大小,可以根据场景大小去设置var axisHelper = new THREE.AxisHelper(250);scene.add(axisHelper);//画墙function drawWall(objs) {//长度let lens = Math.sqrt(Math.pow((Number(objs.eZ) - Number(objs.sZ)), 2) + Math.pow((Number(objs.eX) - Number(objs.sX)), 2));//位置let posx = (objs.eX + objs.sX) / 2;let posz = (objs.eZ + objs.sZ) / 2;//- 纹理材质// var texture = new THREE.TextureLoader().load("images/pink.png");// texture.wrapS = THREE.RepeatWrapping;// texture.wrapT = THREE.RepeatWrapping;// texture.repeat.set(4, 4);//旋转角度let rotate = -Math.atan2((objs.eZ - objs.sZ), (objs.eX - objs.sX));// return;let box = new THREE.CubeGeometry(lens, 15, 2); //- 墙体参数let material = new THREE.MeshBasicMaterial({// color: 0xe04747,transparent: true,opacity: 0.4,});let meshWall = new THREE.Mesh(box, material);meshWall.position.set(posx, 15 / 2, posz);meshWall.rotation.y = rotate;scene.add(meshWall);render();}document.getElementById('show3d').addEventListener('click', function (e) {let pointVector3Arr = [] //- 存放Vector3数组对象let pointVector2Arr = [] //- 存放Vector2数组对象let face3Arr = [] //- 存放三角形面对象if (scene) {scene.children.forEach(item => {//- 判断当前子对象是否是环形线if (item.type === 'LineLoop') {for (let i = 0; i < item.geometry.vertices.length; i++) {if (i !== item.geometry.vertices.length - 1) {let params = {eZ: item.geometry.vertices[i + 1].z,sZ: item.geometry.vertices[i].z,eX: item.geometry.vertices[i + 1].x,sX: item.geometry.vertices[i].x}drawWall(params)}//- 把每个点存在点对象中pointVector3Arr.push(new THREE.Vector3(item.geometry.vertices[i].x, item.geometry.vertices[i].y, item.geometry.vertices[i].z));pointVector2Arr.push(new THREE.Vector2(item.geometry.vertices[i].x, item.geometry.vertices[i].y));}//- 如果不是一条线,至少是一个三角形的情况最后一条边if (item.geometry.vertices.length > 2) {let index = item.geometry.vertices.length - 1;let params = {eZ: item.geometry.vertices[0].z,sZ: item.geometry.vertices[index].z,eX: item.geometry.vertices[0].x,sX: item.geometry.vertices[index].x}drawWall(params)}}})}//- 绘制多边形底座generatePloygon();})//- 画不规则多边形底座 -- 用于作业面底部区域颜色绘制function generatePloygon() {let pointArr = [];if (scene) {scene.children.forEach(item => {//- 判断当前子对象是否是环形线if (item.type === 'LineLoop') {item.geometry.vertices.forEach(item2 => {pointArr.push({ x: item2.x, y: item2.y, z: item2.z })})}})}//- 根据绘制的点来定义一个二维形状平面let shape = new THREE.Shape();console.log(pointArr, 111);shape.moveTo(pointArr[0].x, pointArr[0].z);for (let i = 1; i < pointArr.length; i++) {const element = pointArr[i];shape.lineTo(pointArr[i].x, pointArr[i].z);}shape.autoClose = true;//- 从一个或多个路径形状中创建一个单面多边形几何体let shapeGeometry = new THREE.ShapeBufferGeometry(shape, 25);let shapeMaterial = new THREE.MeshBasicMaterial({color: 0xe04747,side: THREE.DoubleSide,transparent: true,opacity: 0.4})let shapeMesh = new THREE.Mesh(shapeGeometry, shapeMaterial);shapeMesh.rotateX(Math.PI / 2)// shapeMesh.rotateY(Math.PI / 2)// shapeMesh.rotateZ(Math.PI / 2)shapeMesh.position.y = 2scene.add(shapeMesh);render();}let isdown = false, ismove = false;window.addEventListener('mousemove', function () {if (isdown) {ismove = true;//鼠标拖动事件执行函数}})window.addEventListener('mousedown', function () {isdown = trueismove = false})//- 点击事件window.addEventListener('mouseup', function (event) {if (!isdown && ismove) {return 0;}if (ismove) {return 0;}let intsersects = getIntersects(event)if (intsersects.length > 0) {if (intsersects[0].object.material.name === 'belinebox') {let mathColor = getColor();// intsersects[0].object.material.color.set(mathColor)//- 添加点到点击的面板上addPoint(intsersects[0].point);render()}}})var tempData = []; //- 存放当前模型中的点的个数var tempData2 = [];//- 添加点function addPoint(point) {if (scene) {scene.children.forEach(item => {if (item.type === "LineLoop") {scene.remove(item);tempData = []}})}tempData.push(point)let geometry233 = new THREE.SphereGeometry(1, 40, 40); //创建一个立方体几何对象Geometry// 点渲染模式let material233 = new THREE.PointsMaterial({color: 0xff0000,// size: 5.0 //点对象像素尺寸}); //材质对象let points233 = new THREE.Points(geometry233, material233); //点模型对象points233.position.set(point.x, point.y + 1, point.z);scene.add(points233)let geometry = new THREE.Geometry(); //声明一个空几何体对象tempData.forEach(item => {tempData2.push(new THREE.Vector3(item.x, item.y + 1, item.z))})geometry.vertices.push(...tempData2)let material = new THREE.MeshLambertMaterial({color: 0xfac15f,//三角面颜色side: THREE.DoubleSide//两面可见});//材质对象let line = new THREE.LineLoop(geometry, material);//线条模型对象scene.add(line);}</script></body></html>
