项目中有一个电子围栏的需求,之前已经通过点,线,面的博文中的代码实现了多变形电子围栏的绘制.圆形的还未实现,未了达到与之前样式统一,因此无法通过three.js的管道来实现.
ThreeBSP
three.js本身并没有提供用于几何体布尔运算的构造函数,需要借助一个库ThreeBSP.js实现。几何体的布尔运算可以借助数学中学习的差集、并集、交集概念去理解, 几何体之间的运算本质上就是两个顶点集合的运算,具体运算的算法可以查看计算几何学的理论内容,多数的三维软件基本都有布尔运算的相关命令, 尤其是机械类的三维建模软件,对于计算机辅助设计有兴趣的可以多研究。
绘制一个外圆内方的铜钱
绘制一个简易铜钱形状几何体很简单,绘制一个扁圆柱减去一个立方体就可以。
/**
* 创建网格模型
*/
//几何体对象
var cylinder = new THREE.CylinderGeometry(50,50,5,40);//圆柱
var box = new THREE.BoxGeometry(40,5,40);//立方体
//材质对象
var material=new THREE.MeshPhongMaterial({color:0x0000ff});
//网格模型对象
var cylinderMesh=new THREE.Mesh(cylinder,material);//圆柱
var boxMesh=new THREE.Mesh(box,material);//立方体
//包装成ThreeBSP对象
var cylinderBSP = new ThreeBSP(cylinderMesh);
var boxBSP = new ThreeBSP(boxMesh);
var result = cylinderBSP.subtract(boxBSP);
//ThreeBSP对象转化为网格模型对象
var mesh = result.toMesh();
scene.add(mesh);//网格模型添加到场景中
网格Mesh模型对象作为构造函数ThreeBSP()的参数,可以把three.js的普通Mesh模型包装为ThreeBSP对象,表示对应Mesh对象的ThreeBSP对象可以进行布尔运算 ,计算结果在执行toMesh()方法可以把ThreeBSP对象重新转化为Mesh对象。
布尔计算方法
方法 | 作用 |
---|---|
intersrct | 交集、重合的部分 |
union | 并集、组合、相加 |
subtract | 差集、相减 |
绘制圆形电子围栏
经过上面的一些内容,我们可以通过2个圆柱形,通过差集相减的方法来获取一个环形圆柱体,底部加入一个圆形底座,就完成了圆形电子围栏
/**
* 创建网格模型
*/
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load('./images/danger_wall.png');
texture.wrapS = THREE.RepeatWrapping; //水平方向如何包裹
texture.wrapT = THREE.RepeatWrapping; // 垂直方向如何包裹
// uv两个方向纹理重复数量、看板中重复数量
texture.repeat.set(15, 1);
var smallCylinderGeom = new THREE.CylinderGeometry(40, 35, 100, 100, 14); //- 中心小圆
var largeCylinderGeom = new THREE.CylinderGeometry(40, 40, 10, 100, 4); //- 外圆
var smallCylinderBSP = new ThreeBSP(smallCylinderGeom); //- 中心圆bsp对象
var largeCylinderBSP = new ThreeBSP(largeCylinderGeom); //- 外圈圆bsp对象
var intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP); //- 获取差集
var redMaterial = new THREE.MeshBasicMaterial({ //- 设置空心圆柱体的样式
map: texture,
transparent: true,
opacity: 1
})
var hollowCylinder = intersectionBSP.toMesh(redMaterial); //- 添加样式
hollowCylinder.position.y += 5; //- 由于高度为10,翻转后有5的值在基准坐标之下,所以做Y轴校准
scene.add(hollowCylinder); //- 添加到场景中
//- 添加底座
var geometry = new THREE.CircleGeometry(40, 100);
var material = new THREE.MeshBasicMaterial({
color: 0xFF0018,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.1
});
var circle = new THREE.Mesh(geometry, material);
circle.rotateX(Math.PI / 2)
scene.add(circle);
显示效果:
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>绘制空心圆柱体</title>
<style>
body {
margin: 0;
overflow: hidden;
/* 隐藏body窗口区域滚动条 */
}
</style>
<!--引入three.js三维引擎-->
<script src="./js-r119/three.js"></script>
<script src="./js-r119/controls/OrbitControls.js"></script>
<script src='./js-r119/libs/tween.min.js'></script>
<!-- <script src='./js-r119/libs/stats.min.js'></script> -->
<script src="https://johnson2heng.github.io/three.js-demo/lib/threebsp.js"></script>
</head>
<body>
<script>
/**
* 创建场景对象Scene
*/
var scene = new THREE.Scene();
/**
* 创建网格模型
*/
let textureLoader = new THREE.TextureLoader();
let texture = textureLoader.load('./images/danger_wall.png');
texture.wrapS = THREE.RepeatWrapping; //水平方向如何包裹
texture.wrapT = THREE.RepeatWrapping; // 垂直方向如何包裹
// uv两个方向纹理重复数量、看板中重复数量
texture.repeat.set(15, 1);
var smallCylinderGeom = new THREE.CylinderGeometry(40, 35, 100, 100, 14); //- 中心小圆
var largeCylinderGeom = new THREE.CylinderGeometry(40, 40, 10, 100, 4); //- 外圆
var smallCylinderBSP = new ThreeBSP(smallCylinderGeom); //- 中心圆bsp对象
var largeCylinderBSP = new ThreeBSP(largeCylinderGeom); //- 外圈圆bsp对象
var intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP); //- 获取差集
var redMaterial = new THREE.MeshBasicMaterial({ //- 设置空心圆柱体的样式
map: texture,
transparent: true,
opacity: 1
})
var hollowCylinder = intersectionBSP.toMesh(redMaterial); //- 添加样式
hollowCylinder.position.y += 5; //- 由于高度为10,翻转后有5的值在基准坐标之下,所以做Y轴校准
scene.add(hollowCylinder); //- 添加到场景中
//- 添加底座
var geometry = new THREE.CircleGeometry(40, 100);
var material = new THREE.MeshBasicMaterial({
color: 0xFF0018,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.1
});
var circle = new THREE.Mesh(geometry, material);
circle.rotateX(Math.PI / 2)
scene.add(circle);
/**
* 光源设置
*/
//点光源
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.25, 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对象
// 辅助坐标系 参数250表示坐标系大小,可以根据场景大小去设置
let axisHelper = new THREE.AxisHelper(250);
scene.add(axisHelper);
// 获取与射线相交的对象数组
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 到 1
mouse.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;
}
/**
* 点击模型对象把镜头拉近
*/
//- 添加鼠标对视觉相机的操作
var controls = new THREE.OrbitControls(camera, renderer.domElement);
//- 渲染函数
function render() {
//执行渲染操作 指定场景、相机作为参数
renderer.render(scene, camera);
requestAnimationFrame(render)
TWEEN.update();
controls.update();
}
render();
</script>
</body>
</html>