在 Three.js 中的两点之间绘制椭圆弧
Draw an ellipse arc between two points in Three.js
我一直在尝试在任意两个点之间绘制椭圆弧,但我的实现在某些情况下不起作用。
因为这一部分涉及到数学,所以我先问了this question。
基本上,给定两个点和中心,如果你允许旋转,你总能得到一个椭圆,除了点共线的情况。
针对该问题提出的解决方案是:
- 将中心平移到原点,将两个点平移到同一向量。
- 将两个点旋转角度
-alpha
,这是最大矢量与正 x 半轴的角度的对称度。
- 求解椭圆方程以求出其半径(具有两个未知数的两个方程组)。
- 定义椭圆
- 将椭圆回转角度
alpha
并平移回其中心。
但是,我在 Three.js 中实现它时遇到了问题。
EllipseCurve 的文档列出了预期的参数。我假设起始角度始终为零,然后将结束角度设置为两个向量之间的角度或其对称角。我还希望弧线始终是最小的(即,如果角度大于 180º,我将使用互补弧线)。我假设椭圆的中心是形状边界框中心之间的中点。
这是我的示例代码:
https://jsfiddle.net/at5dc7yk/1/
此示例尝试从原始形状中的顶点和修改后形状中的相同顶点创建弧。
关于椭圆弧的代码在 class EllipseArc
下,您可以在第 190 行弄乱应用于对象的变换。
它适用于某些情况:
但不是全部:
只是从零开始的想法,不是最终的解决方案。
当您克隆和平移对象时,要在两个相应点之间建立弧线,您需要它们在世界坐标系中的坐标,以及对象质心之间的中点坐标。
- 找到世界中点之间的中点 space(在开始和结束向量之间)。
- 找到它在平移向量上的投影(这是圆弧的中心)。
- 通过从每个向量中减去结果中心向量,找到向量之间的角度。
- 将一个角度除以分度数 - 您将得到步长值。
- 以结束向量为底,绕一个轴(三角形的法线,由开始、中心、结束向量构成)循环旋转,将步进角值乘以当前迭代。
代码示例:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 10000);
camera.position.set(0, 0, 150);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var shapeGeom = new THREE.ShapeBufferGeometry(new THREE.Shape(californiaPts));
shapeGeom.center();
shapeGeom.scale(0.1, 0.1, 0.1);
var shapeMat = new THREE.MeshBasicMaterial({
color: "orange"
});
var shape = new THREE.Mesh(shapeGeom, shapeMat);
shape.updateMatrixWorld();
scene.add(shape);
var shapeClone = shape.clone();
shapeClone.position.set(25, 25, 0);
shapeClone.updateMatrixWorld();
scene.add(shapeClone);
var center = new THREE.Vector3().lerpVectors(shapeClone.position, shape.position, 0.5);
var vecStart = new THREE.Vector3();
var vecEnd = new THREE.Vector3();
var pos = shapeGeom.getAttribute("position");
for (let i = 0; i < pos.count; i++) {
vecStart.fromBufferAttribute(pos, i);
shape.localToWorld(vecStart);
vecEnd.fromBufferAttribute(pos, i);
shapeClone.localToWorld(vecEnd);
makeArc(center, vecStart, vecEnd);
}
function makeArc(center, start, end) {
console.log(center, start, end);
let vM = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
let dir = new THREE.Vector3().subVectors(end, start).normalize();
let c = new THREE.Vector3().subVectors(vM, center);
let d = c.dot(dir);
c.copy(dir).multiplyScalar(d).add(center); // get a center of an arc
let vS = new THREE.Vector3().subVectors(start, c);
let vE = new THREE.Vector3().subVectors(end, c);
let a = vS.angleTo(vE); // andgle between start and end, relatively to the new center
let divisions = 100;
let aStep = a / divisions;
let pts = [];
let vecTemp = new THREE.Vector3();
let tri = new THREE.Triangle(start, c, end);
let axis = new THREE.Vector3();
tri.getNormal(axis); // get the axis to rotate around
for (let i = 0; i <= divisions; i++) {
vecTemp.copy(vE);
vecTemp.applyAxisAngle(axis, aStep * i);
pts.push(vecTemp.clone());
}
let g = new THREE.BufferGeometry().setFromPoints(pts);
let m = new THREE.LineDashedMaterial({
color: 0xff0000,
dashSize: 1,
gapSize: 1
});
let l = new THREE.Line(g, m);
l.computeLineDistances();
l.position.copy(c);
scene.add(l);
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script>
var californiaPts = [
new THREE.Vector2(610, 320),
new THREE.Vector2(450, 300),
new THREE.Vector2(392, 392),
new THREE.Vector2(266, 438),
new THREE.Vector2(190, 570),
new THREE.Vector2(190, 600),
new THREE.Vector2(160, 620),
new THREE.Vector2(160, 650),
new THREE.Vector2(180, 640),
new THREE.Vector2(165, 680),
new THREE.Vector2(150, 670),
new THREE.Vector2(90, 737),
new THREE.Vector2(80, 795),
new THREE.Vector2(50, 835),
new THREE.Vector2(64, 870),
new THREE.Vector2(60, 945),
new THREE.Vector2(300, 945),
new THREE.Vector2(300, 743),
new THREE.Vector2(600, 473),
new THREE.Vector2(626, 425),
new THREE.Vector2(600, 370),
new THREE.Vector2(610, 320)
];
</script>
如果你不平移,只是旋转一个物体,在这种情况下你不需要为每条弧计算一个新的中心,只需省略这一步,因为所有的中心都等于对象。
希望我解释的比较通俗易懂^^
我一直在尝试在任意两个点之间绘制椭圆弧,但我的实现在某些情况下不起作用。
因为这一部分涉及到数学,所以我先问了this question。 基本上,给定两个点和中心,如果你允许旋转,你总能得到一个椭圆,除了点共线的情况。
针对该问题提出的解决方案是:
- 将中心平移到原点,将两个点平移到同一向量。
- 将两个点旋转角度
-alpha
,这是最大矢量与正 x 半轴的角度的对称度。 - 求解椭圆方程以求出其半径(具有两个未知数的两个方程组)。
- 定义椭圆
- 将椭圆回转角度
alpha
并平移回其中心。
但是,我在 Three.js 中实现它时遇到了问题。 EllipseCurve 的文档列出了预期的参数。我假设起始角度始终为零,然后将结束角度设置为两个向量之间的角度或其对称角。我还希望弧线始终是最小的(即,如果角度大于 180º,我将使用互补弧线)。我假设椭圆的中心是形状边界框中心之间的中点。
这是我的示例代码:
https://jsfiddle.net/at5dc7yk/1/
此示例尝试从原始形状中的顶点和修改后形状中的相同顶点创建弧。
关于椭圆弧的代码在 class EllipseArc
下,您可以在第 190 行弄乱应用于对象的变换。
它适用于某些情况:
但不是全部:
只是从零开始的想法,不是最终的解决方案。
当您克隆和平移对象时,要在两个相应点之间建立弧线,您需要它们在世界坐标系中的坐标,以及对象质心之间的中点坐标。
- 找到世界中点之间的中点 space(在开始和结束向量之间)。
- 找到它在平移向量上的投影(这是圆弧的中心)。
- 通过从每个向量中减去结果中心向量,找到向量之间的角度。
- 将一个角度除以分度数 - 您将得到步长值。
- 以结束向量为底,绕一个轴(三角形的法线,由开始、中心、结束向量构成)循环旋转,将步进角值乘以当前迭代。
代码示例:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 10000);
camera.position.set(0, 0, 150);
var renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var shapeGeom = new THREE.ShapeBufferGeometry(new THREE.Shape(californiaPts));
shapeGeom.center();
shapeGeom.scale(0.1, 0.1, 0.1);
var shapeMat = new THREE.MeshBasicMaterial({
color: "orange"
});
var shape = new THREE.Mesh(shapeGeom, shapeMat);
shape.updateMatrixWorld();
scene.add(shape);
var shapeClone = shape.clone();
shapeClone.position.set(25, 25, 0);
shapeClone.updateMatrixWorld();
scene.add(shapeClone);
var center = new THREE.Vector3().lerpVectors(shapeClone.position, shape.position, 0.5);
var vecStart = new THREE.Vector3();
var vecEnd = new THREE.Vector3();
var pos = shapeGeom.getAttribute("position");
for (let i = 0; i < pos.count; i++) {
vecStart.fromBufferAttribute(pos, i);
shape.localToWorld(vecStart);
vecEnd.fromBufferAttribute(pos, i);
shapeClone.localToWorld(vecEnd);
makeArc(center, vecStart, vecEnd);
}
function makeArc(center, start, end) {
console.log(center, start, end);
let vM = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
let dir = new THREE.Vector3().subVectors(end, start).normalize();
let c = new THREE.Vector3().subVectors(vM, center);
let d = c.dot(dir);
c.copy(dir).multiplyScalar(d).add(center); // get a center of an arc
let vS = new THREE.Vector3().subVectors(start, c);
let vE = new THREE.Vector3().subVectors(end, c);
let a = vS.angleTo(vE); // andgle between start and end, relatively to the new center
let divisions = 100;
let aStep = a / divisions;
let pts = [];
let vecTemp = new THREE.Vector3();
let tri = new THREE.Triangle(start, c, end);
let axis = new THREE.Vector3();
tri.getNormal(axis); // get the axis to rotate around
for (let i = 0; i <= divisions; i++) {
vecTemp.copy(vE);
vecTemp.applyAxisAngle(axis, aStep * i);
pts.push(vecTemp.clone());
}
let g = new THREE.BufferGeometry().setFromPoints(pts);
let m = new THREE.LineDashedMaterial({
color: 0xff0000,
dashSize: 1,
gapSize: 1
});
let l = new THREE.Line(g, m);
l.computeLineDistances();
l.position.copy(c);
scene.add(l);
}
renderer.setAnimationLoop(() => {
renderer.render(scene, camera);
});
body {
overflow: hidden;
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script>
var californiaPts = [
new THREE.Vector2(610, 320),
new THREE.Vector2(450, 300),
new THREE.Vector2(392, 392),
new THREE.Vector2(266, 438),
new THREE.Vector2(190, 570),
new THREE.Vector2(190, 600),
new THREE.Vector2(160, 620),
new THREE.Vector2(160, 650),
new THREE.Vector2(180, 640),
new THREE.Vector2(165, 680),
new THREE.Vector2(150, 670),
new THREE.Vector2(90, 737),
new THREE.Vector2(80, 795),
new THREE.Vector2(50, 835),
new THREE.Vector2(64, 870),
new THREE.Vector2(60, 945),
new THREE.Vector2(300, 945),
new THREE.Vector2(300, 743),
new THREE.Vector2(600, 473),
new THREE.Vector2(626, 425),
new THREE.Vector2(600, 370),
new THREE.Vector2(610, 320)
];
</script>
如果你不平移,只是旋转一个物体,在这种情况下你不需要为每条弧计算一个新的中心,只需省略这一步,因为所有的中心都等于对象。
希望我解释的比较通俗易懂^^