绘制围栏,实际上可以理解为一个空心圆柱体加底部,类似一个碗状的图形。我是通过three.js的圆柱体几何体CylinderGeometry
+ 三维模型运算ThreeBSP
的函数subtract
、圆形几何体CircleGeometry
结合实现。
在Three.js中,绘制一个空心圆柱体可以通过创建一大一小两个CylinderGeometry
,使用ThreeBSP
的函数subtract
求两个圆柱体的差集得到。圆柱体的底部则通过绘制一个CircleGeometry
得到。最后合理设定两个模型的位置和旋转角度等实现效果。
圆柱几何体API
CylinderGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength)
参数说明:
- radiusTop:圆柱的顶部半径,默认值是1。
- radiusBottom:圆柱的底部半径,默认值是1。
- height:圆柱的高度,默认值是1。
- radialSegments:圆柱侧面周围的分段数,默认为8。
- heightSegments:圆柱侧面沿着其高度的分段数,默认值为1。
- openEnded:一个Boolean值,指明该圆锥的底面是开放的还是封顶的。默认值为false,即其底面默认是封顶的。
- thetaStart:第一个分段的起始角度,默认为0。(three o'clock position)
- thetaLength:圆柱底面圆扇区的中心角,通常被称为"θ"(西塔)。默认值是2*Pi,这使其成为一个完整的圆柱。
圆形几何体API
CircleGeometry(radius, segments, thetaStart, thetaLength)
参数说明:
- radius:圆形的半径,默认值为1。
- segments:分段(三角面)的数量,最小值为3,默认值为8。。
- thetaStart:第一个分段的起始角度,默认为0。(three o'clock position)。
- thetaLength:圆形扇区的中心角,通常被称为"θ"(西塔)。默认值是2*Pi,这使其成为一个完整的圆。。
ThreeBSP三维模型运算简述
ThreeBSP 是一个用于处理三维模型布尔运算(交集、并集、差集)的库,它基于Three.js开发,允许开发者对三维模型进行复杂的几何处理。ThreeBSP提供了intersect
、union
和subtract
等函数,这些函数可以用于处理两个或多个三维模型的布尔运算,从而创建出复杂的三维场景和模型。此外,由于ThreeBSP是基于Three.js的扩展,因此它支持Three.js的所有功能和特性。
- Intersect函数:交集,用于计算两个模型的交集部分,即计算两个模型重叠的部分。这在需要精确控制模型之间的交互时非常有用,例如,当需要创建一个模型穿过另一个模型时。
- Union函数:并集,将两个模型合并成一个模型。这在需要创建一个由多个部分组成的大型模型时非常有用,例如,创建一个由多个小部件组成的复杂机械或建筑结构。
- Subtract函数:差集,从一个模型中减去另一个模型的部分或全部。这在需要从一个大模型中去除一个小模型时非常有用,例如,创建一个有洞的物体或在一个物体上切割出一个形状。
绘制圆柱体
创建一大一小两个圆柱体。
arduino
const radius = 5; // 圆柱体半径
const height = 3; // 圆柱体高度
// 绘制两个圆柱体
const smallCylinderGeom = new THREE.CylinderGeometry(radius - 0.1, radius - 0.1, height, 100, 10, false);
const largeCylinderGeom = new THREE.CylinderGeometry(radius, radius, height, 100, 10, false);
获得空心圆柱体
通过ThreeBSP模型运算求两个圆柱体的差集获得一个新的模型,这个模型就是空心圆柱体。
arduino
// ThreeBSP模型运算,将两个或者多个立方体通过模型交集(intersect)、差集(subtract)、并集(union)运算生成新的运算后立方体
const smallCylinderBSP = new ThreeBSP(smallCylinderGeom); //- 中心圆bsp对象
const largeCylinderBSP = new ThreeBSP(largeCylinderGeom); //- 外圈圆bsp对象
// 计算两个圆柱体的差集得到空心圆柱体
const intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP);
绘制圆形
绘制一个圆心几何体设置位置和旋转方向实现围栏底部。
arduino
const geometry = new THREE.CircleGeometry(radius, 100);
const material = new THREE.MeshBasicMaterial({
color: 0xFF0018,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.1
});
const circle = new THREE.Mesh(geometry, material);
circle.position.set(0, -height / 2, 0);
circle.rotateX(Math.PI / 2);
完整代码
注意点:
- 圆柱体设置位置后中心点在圆柱体中心位置,而非底部位置,所以设置位置时需要注意,如果设置圆心几何体的位置,例如高度为5,就需要设置高度为-2.5。也可通过设置圆柱体的位置来确定。
- 需要引入
three-bsp
包。
ini
import { useRef, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
const ThreeBSP = require('jthreebsp')(THREE);
const wall = require('@/static/image/wall.png');
let renderer, controls, scene, camera;
// three.js绘制圆形围栏,实际是空心圆柱体,通过圆柱体几何体CylinderGeometry + 三维模型运算ThreeBSP + 圆形几何体CircleGeometry实现
const Draw3DHollowCylinder = () => {
const box = useRef(); // canvas盒子
// 渲染动画
function renderFn() {
requestAnimationFrame(renderFn);
// 用相机渲染一个场景
renderer.render(scene, camera);
}
useEffect(() => {
if (scene) {
// 绘制空心圆柱体
const radius = 5; // 圆柱体半径
const height = 3; // 圆柱体高度
// 绘制一大一小两个圆柱体
const smallCylinderGeom = new THREE.CylinderGeometry(radius - 0.1, radius - 0.1, height, 100, 10, false);
const largeCylinderGeom = new THREE.CylinderGeometry(radius, radius, height, 100, 10, false);
// ThreeBSP模型运算,将两个或者多个立方体通过模型交集(intersect)、差集(subtract)、并集(union)运算生成新的运算后立方体
const smallCylinderBSP = new ThreeBSP(smallCylinderGeom); //- 中心圆bsp对象
const largeCylinderBSP = new ThreeBSP(largeCylinderGeom); //- 外圈圆bsp对象
// 获取两个圆柱体的差集
const intersectionBSP = largeCylinderBSP.subtract(smallCylinderBSP);
// 创建网格模型
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(wall);
texture.wrapS = THREE.RepeatWrapping; // 水平方向如何包裹
texture.wrapT = THREE.RepeatWrapping; // 垂直方向如何包裹
// uv两个方向纹理重复数量、看板中重复数量
texture.repeat.set(15, 1);
const wireframeMaterial = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
opacity: 1,
});
// 添加样式
let hollowCylinder = intersectionBSP.toMesh(wireframeMaterial);
// 添加圆形底座
const geometry = new THREE.CircleGeometry(radius, 100);
const material = new THREE.MeshBasicMaterial({
color: 0xFF0018,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.1
});
const circle = new THREE.Mesh(geometry, material);
// 设置位置
circle.position.set(0, -height / 2, 0);
// 设置旋转方向
circle.rotateX(Math.PI / 2);
// 添加组
const group = new THREE.Group();
group.name = 'fenceGroup';
group.add(hollowCylinder);
group.add(circle);
// 添加到场景
scene.add(group);
}
}, [scene]);
// 初始化环境、灯光、相机、渲染器
useEffect(() => {
scene = new THREE.Scene();
// 添加光源
const ambitlight = new THREE.AmbientLight(0x404040);
scene.add(ambitlight)
const sunlight = new THREE.DirectionalLight(0xffffff);
sunlight.position.set(-20, 1, 1);
scene.add(sunlight);
// let axisHelper = new THREE.AxesHelper();
// scene.add(axisHelper);//坐标辅助线加入到场景中
// 获取宽高设置相机和渲染区域大小
const width = box.current.offsetWidth;
let height = box.current.offsetHeight;
let k = width / height;
// 投影相机
camera = new THREE.PerspectiveCamera(75, k, 0.1, 1000);
camera.position.set(1, 0, 25);
camera.lookAt(scene.position);
// 创建一个webGL对象
renderer = new THREE.WebGLRenderer({
//增加下面两个属性,可以抗锯齿
antialias: true,
alpha: true
});
renderer.setSize(width, height); // 设置渲染区域尺寸
renderer.setClearColor(0x000000, 1); // 设置颜色透明度
// 首先渲染器开启阴影
renderer.shadowMap.enabled = true;
box.current.appendChild(renderer.domElement);
// 监听鼠标事件
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;//设置为true则启用阻尼(惯性),默认false
controls.dampingFactor = 0.05;//值越小阻尼效果越强
// 渲染
renderFn();
}, []);
return <div className='ui_container_box'>
three.js绘制3D空心圆柱体。
<div style={{ width: '100%', height: '100%' }} ref={box}></div>
</div>;
}
export default Draw3DHollowCylinder;