6种常见三维曲面的Three.js实现

news2024/11/23 12:28:58

这篇文章详细介绍了三次曲线、贝塞尔曲线和 B 样条曲线和曲面背后的数学原理,并提供了使用 Three.js 库实现的代码。

1、简介

你可以在这里访问上图应用程序。此应用程序名为 CurSur,是 Curves and Surfaces 的简称。原始代码可以在这里获取。

在这里插入图片描述

在几何设计中,有多种类型的曲线和曲面 - 2D 和 3D。其中,在几何建模的入门课程中,通常教授三种类型——三次曲线、贝塞尔曲线和B样条曲线和曲面。本文的目的是介绍如何使用Three.js 库在浏览器中绘制这些 3D 曲线和曲面的程序。

在本文中,我们将介绍如何使用 Three.js 在浏览器中以 3D 方式绘制直线和曲面,以及如何修改这些曲线和曲面的控制点和切线(导数)等几何参数。这里给出的代码的主要要求是:

  • 应该在屏幕上显示这六种 3D 曲线和曲面 - Parametric Cubic Curve、Coons Bicubic Surface、Bezier Curve、Bezier Surface、B-Spline(实际上是 NURBS)Curve 和 NURBS Surface。
  • 应允许用户修改控制点的 x、y、z 坐标,和/或相对于曲线或曲面的 x、y、z(切线)的导数,并查看曲线或曲面如何动态修改屏幕。
  • 还应使用户能够在线框模式下查看表面。
  • 应显示曲线或曲面的边界框,其尺寸为 2 个单位,以原点为中心。
  • 应允许用户修改摄像机角度,使摄像机绕垂直轴旋转,围绕正在查看的场景。
  • 应使用户能够修改参数值 - u 在曲线的情况下,u,w 在曲面的情况下,并看到相应的点在曲线和曲面上随着这些 u,w 值的变化而移动。
  • 单击按钮应显示一些标准曲线和曲面。
  • 应该使用 Three.js 库在屏幕上显示 3D 曲线或曲面。
  • 应该使用 Vanilla JS 而不是任何框架。
  • 不应有文本框类型的用户输入,所有用户交互都应仅通过滑块、复选框、组合框和按钮。

2、参数化曲线曲面简介

我们在高中和中级大学了解到直线有笛卡尔和极坐标形式的表示形式,例如直线、圆弧、圆锥曲线等。然而,出于几何设计的目的,笛卡尔和极坐标形式通常不是首选,原因有很多,其中两个是 (i) 用笛卡尔形式表示垂直或接近垂直的线并不容易,因为斜率(导数)趋向无限大,并且(ii)用这些笛卡尔和极坐标形式表示一般形状并不容易。因此,首选形式是参数化的。

在参数形式中,3D 曲线表示为:

  • x = x(u)
  • y = y(u)
  • z = z(u)

其中x,y,z是该曲线上任意一点的3D坐标,u是一个参数,通常在0≤u≤1范围内。其中x(u),y(u),z(u)是三个参数 u 的函数。使用这种表示法,可以很容易地表示任何形状的线,并且不会像笛卡尔表示法那样存在无限导数的缺点。特别是,我们看到了 x(u)、y(u)、z(u) 的三个这样的函数,它们定义了本文中的三种曲线 - 参数三次曲线、贝塞尔曲线和 B 样条曲线。

以类似的方式,也可以使用以下等式来表示表面:

  • x = x(u,w)
  • y = y(u,w)
  • z = z(u,w)

其中,x, y, z 通常是曲面上一点的三维坐标,u, w 是参数空间中相互垂直方向的两个参数,通常在 0 ≤ u ≤ 1, 0 ≤ w ≤ 1。在这里,三个函数 x(u,w)、y(u,w)、z(u,w) 采用三种不同的形式表示双三次、贝塞尔和 B 样条。

3、Three.js中的绘图

一条曲线被绘制为许多直线段,首尾相接以呈现平滑曲线的外观,因此,最基本的做法是在浏览器中绘制一条 3D 直线。

自 WebGL 出现以来,出现了抽象出实际 WebGL 库内部细节的 JavaScript 3D 库,其中两个流行的此类 JavaScript 库是 Three.js 和 Babylon.js。 在本文中,我使用了 Three.js 库,并将展示有关如何在屏幕上绘制线条和曲面的代码摘录。

在 Three.js 中绘制 3D 对象所需的三个最重要的实体是场景、相机和渲染器。 代码如下:

scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000 );
renderer = new THREE.WebGLRenderer({ antialias: true });

3.1 在 Three.js 中画一条直线

直线段由其在空间中的两个端点 (x1, y1, z1) 和 (x2, y2, z2) 确定。

绘制直线段,Three.js 中有一个Geometry 对象,叫做BufferGeometry。 早些时候还有一个 Geometry 对象,但在 Three.js 的修订版 125 中已弃用,将使用 BufferGeometry 对象取而代之。

此外,还要指定一种材料。 线和面有不同的材料。

使用以下代码绘制直线段:

const material = new THREE.LineBasicMaterial({ color: 0xff00ff });
const geometry = new THREE.BufferGeometry();
const vertices = [];
vertices.push(-0.75, -0.75, -0.75);
vertices.push(0.75, 0.75, 0.75);
geometry.setAttribute(
    "position",
    new THREE.Float32BufferAttribute(vertices, 3));
let line = new THREE.Line(geometry, material);
scene.add(line);
render();

在上面的代码中,在端点(-0.75,-0.75,-0.75)和(0.75,0.75,0.75)之间绘制了一条直线,如下图洋红色线所示:
在这里插入图片描述

3.2 在 Three.js 中绘制一个简单的表面

一个表面被定义为一组三角形。 因此,要绘制的基本几何实体是三角形。 3D 中的三角形由其三个顶点 (x1, y1, z1)、(x2, y2, z2) 和 (x3, y3, z3) 指定。 我们不能只绘制定义三角形的三条直线,因为那样会显示为线框图。

如果场景只包含一组线段,则不需要额外的光来照亮场景。 但是,如果场景中有表面或 3D 对象(如立方体、球体等),则必须指定一盏或多盏灯。 有不同类型的灯,这些在 Three.js 文档中定义。 向场景添加几个灯的代码如下所示:

scene.add(new THREE.HemisphereLight(0x606060, 0x404040));

// White directional light at 0.65 intensity shining from the top.
let directionalLight = new THREE.DirectionalLight(0xffffff, 0.65);
scene.add(directionalLight);

要将三角形指定为曲面,我们需要定义三角形的法线。 现在,三角形有两条法线,都指向相反的方向。 如果材质指定为双面,则三角形的两边都将显示,并定义了灯光。 但是,如果表面指定为单面,则光线未照射到的一侧不太可能被照亮。

在场景中绘制三角面的代码如下所示:

function computeCoonsBicubicSurface() {
  setupFourPoints();
  surfacePoints.length = 0;
  let uVal, wVal;

  for (let j = 0; j <= noDivisions; ++j) {
    wVal = j * step;

    for (let i = 0; i <= noDivisions; ++i) {
      uVal = i * step;
      let pt = computePointOnSurface(uVal, wVal);
      surfacePoints.push(pt.xVal, pt.yVal, pt.zVal);
    }
  }
  renderCoonsBicubicSurface();
  handleUWValue();
}

function renderCoonsBicubicSurface() {
  scene.remove(surfaceMesh);
  scene.remove(lineWire);

  let material = new THREE.MeshStandardMaterial({
    side: THREE.DoubleSide,
    color: 0x00ffff,
    emissive: 0x111111,
    dithering: true,
    flatShading: false,
    roughness: 1,
    metalness: 0.15,
    skinning: true,
  });

  let materialLine = new THREE.LineBasicMaterial({
    color: 0x00ffff,
  });

  let geometry = new THREE.BufferGeometry();
  const indices = [];
  indices.length = 0;

  for (let i = 0; i < noDivisions; i++) {
      for (let j = 0; j < noDivisions; j++) {
          const a = i * (noDivisions + 1) + (j + 1);
          const b = i * (noDivisions + 1) + j;
          const c = (i + 1) * (noDivisions + 1) + j;
          const d = (i + 1) * (noDivisions + 1) + (j + 1);

          // generate two faces (triangles) per iteration

          indices.push(a, b, d); // face one
          indices.push(b, c, d); // face two
      }
  }

  geometry.setIndex(indices);
  geometry.setAttribute(
     "position",
      new THREE.Float32BufferAttribute(surfacePoints, 3).onUpload(disposeArray)
  );
  geometry.computeVertexNormals();

  surfaceMesh = new THREE.Mesh(geometry, material);
  scene.add(surfaceMesh);

  let surfaceWire = new THREE.WireframeGeometry(geometry);
  lineWire = new THREE.LineSegments(surfaceWire, materialLine);
  scene.add(lineWire);
  render();
}

其中 surfacePoints 是上面定义的点数组。 使用上述代码绘制的表面如下所示。 随着三角形数量的增加,表面开始呈现出光滑的外观。 请查看文件 script2.js 以获取完整代码。
在这里插入图片描述

4、参数化三次曲线

参数三次曲线由以下三个方程定义:

x(u) = B0x + B1x u + B2x u2 + B3x u3
y(u) = B0y + B1y u + B2y u2 + B3y u3
z(u) = B0z + B1z u + B2z u2 + B3z u3

这里,12个常数B0x、B1x、B2x、B3x、B0y、B1y、B2y、B3y、B0z、B1z、B2z、B3z是根据曲线上的点确定的常数。

有两种方法可以确定这些常数:

  • 四点式:如果已知曲线上的四个点,则这四个点的坐标指定十二个方程来确定十二个常数。
  • Hermite 形式:如果两个端点的坐标已知,并且两个端切线(相对于端点处的 x、y、z 的导数)已知,则这些也指定十二个方程来确定十二个常数

这是本文随附的应用程序中介绍的两种方法。对应于这两种形式的方程的推导在 Mortenson 的几何建模一书中或 Rogers 和 Adams 的计算机图形学的数学元素一书中给出。对于Hermite Form,x,y,z方程关于x,y,z坐标的导数,这些在上面提到的书中也给出了。

4.1 四点式

对于这个四点形式,我们需要指定四个点的 u 值。 出于此代码的目的,我们将这些取为 0、1/3、2/3 和 1。对于参数 u 的这些值,将它们代入曲线方程,可以确定常数 Bij 的值。 当使用坐标值的滚动条改变四个点中任何一个的坐标值时,动态计算这些常数 Bij,计算曲线上所有点的新坐标,并在屏幕上刷新曲线。 代码是:

function computePointFourPointForm(uVal) {
  let u2, u3;
  let coeff1, coeff2, coeff3, coeff4;
  let xCurve, yCurve, zCurve;

  u2 = uVal * uVal;
  u3 = u2 * uVal;

  // This is the Four Point Formula from Mortenson's book on Geometric Modeling
  // For values of u being 0, 1/3, 2/3 and 1.
  coeff1 = -4.5 * u3 + 9 * u2 - 5.5 * uVal + 1;
  coeff2 = 13.5 * u3 - 22.5 * u2 + 9 * uVal;
  coeff3 = -13.5 * u3 + 18 * u2 - 4.5 * uVal;
  coeff4 = 4.5 * u3 - 4.5 * u2 + uVal;
  xCurve = p1x * coeff1 + p2x * coeff2 + p3x * coeff3 + p4x * coeff4;
  yCurve = p1y * coeff1 + p2y * coeff2 + p3y * coeff3 + p4y * coeff4;
  zCurve = p1z * coeff1 + p2z * coeff2 + p3z * coeff3 + p4z * coeff4;
  return {
    xVal: xCurve,
    yVal: yCurve,
    zVal: zCurve,
  };
}

4.2 Hermite形式

对于这种形式,指定了两个端点和在这些端点处的两组导数,这些用于计算常数 Bij。 这些也是来自摩顿森书中给出的公式。 代码是:

function computePointHermiteForm(uVal) {
  let u2, u3;
  let coeff1, coeff2, coeff3, coeff4;
  let xCurve, yCurve, zCurve;

  u2 = uVal * uVal;
  u3 = u2 * uVal;

  // This is the Hermite Formula from Mortenson's book on Geometric Modeling
  // u and du at the endpoints.
  coeff1 = 2 * u3 - 3 * u2 + 1;
  coeff2 = -2 * u3 + 3 * u2;
  coeff3 = u3 - 2 * u2 + uVal;
  coeff4 = u3 - u2;
  xCurve = p1xh * coeff1 + p2xh * coeff2 + p1dxh * coeff3 + p2dxh * coeff4;
  yCurve = p1yh * coeff1 + p2yh * coeff2 + p1dyh * coeff3 + p2dyh * coeff4;
  zCurve = p1zh * coeff1 + p2zh * coeff2 + p1dzh * coeff3 + p2dzh * coeff4;
  return {
    xVal: xCurve,
    yVal: yCurve,
    zVal: zCurve,
  };
}

当用户使用屏幕上的滚动条修改终点坐标和导数对应的滚动条时,动态计算常数B ij ,重新计算整条曲线。

4.3 参数三次曲线的代码

曲线呈现为曲线上的一组直线段。 将整个参数范围0≤u≤1分成若干部分,所有的直线段画成一个环。 其代码在文件 script1.js 中。 相应的 HTML 位于文件 page1.html 中。

let curvePoints = [];
curvePoints.length = 0;

for (let i = 0; i < noUPoints; ++i) {
    uVal = uStep * i; // uVal and uStep are defined earlier
    let pt = computePointFourPointForm(uVal);
    // let pt = computePointHermiteForm(uVal);
    curvePoints.push(pt.xVal, pt.yVal, pt.zVal);
}

4.4 验证

为了验证,使用了一组点坐标/导数的标准值,并且可以看到生成的曲线的形状。此外,通过改变参数 u 获得的曲线上的点显示在屏幕上,并用于验证。

这里要注意的一个有趣案例是非线性直线。通常情况下,对于参数 u 的相等增量,沿曲线经过的距离相等,这通常是不正确的。这将非线性元素引入参数化三次曲线。这是一条非线性直线。即使这些点都位于同一条直线上,但不同的参数增量会导致沿该直线的遍历距离不同。

在 Hermite 形式中,对于直线,可能会发生端点导数位于直线段之外的方向。在这种情况下,将参数 u 从 0 增加到 1 后,该点会超出直线段,然后反转方向,然后向第二个端点移动。这又是一条非线性直线。

一些这样的情况在屏幕上以指定有趣曲线的按钮的形式给出。

5、3D 贝塞尔曲线

上图的Parametric Cubic Curve,尤其是Four Point Form中,曲线通过了所有的四个点,属于曲线拟合的一种形式。 贝塞尔曲线由 P Bezier 引入,他根据控制点定义了曲线方程。 贝塞尔曲线由这个等式定义

在这里插入图片描述

在我们的应用程序中,我们考虑了具有 5 个控制点的贝塞尔曲线,因此多项式中 u 的最高次数为 4。

5.1 贝塞尔曲线代码

与参数三次曲线的情况一样,贝塞尔曲线也呈现为曲线上的一组直线段。 将整个参数范围0≤u≤1分成若干部分,所有的直线段画成一个环。 其代码在文件 script3.js 中,它对应于 HTML 文件 page3.html:

u2 = uVal * uVal;
u3 = u2 * uVal;
u4 = u3 * uVal;

// This is the Bezier Curve Formula from Rogers and Adam's Book - Mathematical Elements for
// Computer Graphics
coeff1 = u4 - 4 * u3 + 6 * u2 - 4 * uVal + 1;
coeff2 = -4 * u4 + 12 * u3 - 12 * u2 + 4 * uVal;
coeff3 = 6 * u4 - 12 * u3 + 6 * u2;
coeff4 = -4 * u4 + 4 * u3;
coeff5 = u4;
xCurve = p1x * coeff1 + p2x * coeff2 + p3x * coeff3 + p4x * coeff4 + p5x * coeff5;
yCurve = p1y * coeff1 + p2y * coeff2 + p3y * coeff3 + p4y * coeff4 + p5y * coeff5;
zCurve = p1z * coeff1 + p2z * coeff2 + p3z * coeff3 + p4z * coeff4 + p5z * coeff5;

5.2 验证

贝塞尔曲线有以下验证方面:

  • 在一般情况下,曲线应该通过端点,而不是通过其他控制点。
  • 但是,当所有的控制点都在一条直线上时,曲线也应该是通过这些点的直线。
  • 端点处曲线的切线应与连接该特定端点及其下一个控制点的线一致。 例如,在曲线的起点,曲线的切线应与连接第一和第二控制点的直线一致。 同样在结束控制点。

所有这些点都针对贝塞尔曲线进行了验证,并绘制了一组有趣的贝塞尔曲线,每条曲线在屏幕上都有自己的按钮。

6、3D 中的 NURBS 曲线

NURBS 代表非均匀有理 B 样条曲线。出现这种曲线的必要性是因为参数立方曲线和贝塞尔曲线都不允许对曲线进行局部控制。换句话说,对于参数三次曲线和贝塞尔曲线,修改一个控制点会修改整条曲线,这在许多应用程序中都是不可取的。另一方面,B 样条曲线允许对曲线进行局部控制。修改控制点的坐标会导致仅在该控制点附近修改曲线,而曲线的其余部分不受影响。

有理 B 样条曲线的一般方程如下:
在这里插入图片描述

NURBS 曲线的阶数不依赖于控制点的数量。这两个是 NURBS 曲线中的独立实体。

在我们的应用程序中,我们最初定义了六个控制点,并允许用户添加控制点(最多总共 20 个控制点),并修改每个控制点的坐标(x、y、z、h 值)点,并可视化生成的 NURBS 曲线。

6.1 Nurbs 曲线代码

由于 Three.js 已经有 NURBS 曲线的开源代码,我们不打算重新发明轮子。 因此,在这里,我们从那里提取了 NURBS 曲线代码的相关内容,并将其包含在名为 NurbsHelper.js 的文件中。

在我们的代码中,为了指定控制点,我们将它们生成为以原点为中心的 2 维边界框内的随机数。

尽管实际上 NURBS 曲线是我们应用程序中三个曲线中最复杂的一个,但它的代码 - script5.js 是最简单的,它只调用文件 NurbsHelper.js 中的相关函数。 对应的 HTML 文件是 page5.html。

6.2 验证

  • 如上所述,NURBS 曲线允许局部控制,这意味着通过修改一个点的坐标 (x, y, z, h),曲线仅在局部进行修改,而曲线的另一部分保持不变。 这是一个可以很容易地通过视觉验证的属性。
  • 还有一点需要验证的是,通过修改一个控制点的齐次坐标h的值。 这应该将曲线拉向该控制点。 这也从视觉上得到了验证。
  • 另一个验证点是修改曲线的度数。 对于等于 1 的度数,曲线类似于控制多边形本身。 随着曲线度数的增加,曲线远离控制点(端点除外),对于这些度数的较高值,曲线离其相应的控制点最远。

7、Coons双三次曲面

参数三次曲线的二维等价物是库恩斯双三次曲面。这种曲面的方程如下:
在这里插入图片描述

在参数空间中,有两个参数 u 和 w,它们在 [0, 1] 范围内变化。需要为 Coons 双三次曲面定义以下边界条件:

  • 矩形面片的四个端点。
  • 在这些端点处关于参数 u 的切向量。这些是关于 u 的偏导数。
  • 在这些端点处关于参数 w 的切向量。这些是关于 w 的偏导数。
  • 在这些端点处相对于参数 u、w 扭曲向量。这些是关于 u 和 w 的偏导数。

当上述任何一项发生变化时,表面都会发生变化。

所有这些如下图所示:

在这里插入图片描述

补丁的边界是这四个曲线:

  • u 增加且 w 为 0 的曲线。
  • w 递增且 u 为 0 的曲线。
  • u 增加且 w 为 1 的曲线。
  • w 递增且 u 为 1 的曲线。

7.1 Coons 双三次曲面代码

以下 JavaScript 函数在文件 script2.js 中计算曲面上的一个点。

function computePointOnSurface(uVal, wVal) {
  let u2, u3, w2, w3;
  let f1u, f2u, f3u, f4u, f1w, f2w, f3w, f4w;
  let valueX, valueY, valueZ;
  let valx1, valx2, valx3, valx4;
  let valy1, valy2, valy3, valy4;
  let valz1, valz2, valz3, valz4;

  w2 = wVal * wVal;
  w3 = w2 * wVal;
  f1w = 2.0 * w3 - 3 * w2 + 1.0;
  f2w = -2.0 * w3 + 3.0 * w2;
  f3w = w3 - 2.0 * w2 + wVal;
  f4w = w3 - w2;
  u2 = uVal * uVal;
  u3 = u2 * uVal;
  f1u = 2.0 * u3 - 3 * u2 + 1.0;
  f2u = -2.0 * u3 + 3.0 * u2;
  f3u = u3 - 2.0 * u2 + uVal;
  f4u = u3 - u2;

  valx1 = f1u * (p1x * f1w + p2x * f2w + p1wx * f3w + p2wx * f4w);
  valx2 = f2u * (p3x * f1w + p4x * f2w + p3wx * f3w + p4wx * f4w);
  valx3 = f3u * (p1ux * f1w + p2ux * f2w + p1uwx * f3w + p2uwx * f4w);
  valx4 = f4u * (p3ux * f1w + p4ux * f2w + p3uwx * f3w + p4uwx * f4w);
  valueX = valx1 + valx2 + valx3 + valx4;

  valy1 = f1u * (p1y * f1w + p2y * f2w + p1wy * f3w + p2wy * f4w);
  valy2 = f2u * (p3y * f1w + p4y * f2w + p3wy * f3w + p4wy * f4w);
  valy3 = f3u * (p1uy * f1w + p2uy * f2w + p1uwy * f3w + p2uwy * f4w);
  valy4 = f4u * (p3uy * f1w + p4uy * f2w + p3uwy * f3w + p4uwy * f4w);
  valueY = valy1 + valy2 + valy3 + valy4;

  valz1 = f1u * (p1z * f1w + p2z * f2w + p1wz * f3w + p2wz * f4w);
  valz2 = f2u * (p3z * f1w + p4z * f2w + p3wz * f3w + p4wz * f4w);
  valz3 = f3u * (p1uz * f1w + p2uz * f2w + p1uwz * f3w + p2uwz * f4w);
  valz4 = f4u * (p3uz * f1w + p4uz * f2w + p3uwz * f3w + p4uwz * f4w);
  valueZ = valz1 + valz2 + valz3 + valz4;

  return {
    xVal: valueX,
    yVal: valueY,
    zVal: valueZ,
  };
}

7.2 验证

以下是验证方面:

  • 当使用它们的滑块改变边界点的坐标时,相应的边界点应该在指定的 x、y 或 z 方向上变化,并且表面应该相应地改变。
  • 当使用滑块更改 u 或 v 方向的切线矢量时,也会发生同样的情况,尽管这种变化不像更改坐标值时那样明显。
  • 类似地,当扭曲向量发生变化时,曲面也应该发生变化。
  • 还显示了一些预定义的表面,这些表面在单击左窗格底部的按钮时出现。

8、贝塞尔曲面

贝塞尔曲线的二维(在参数空间中)等价物是贝塞尔曲面。 这种表面的方程式如下:
在这里插入图片描述

8.1 贝塞尔曲面的代码

以下 JavaScript 函数在文件 script4.js 中计算贝塞尔曲面上的一个点。 出于本文的目的,所提供的代码适用于 4 x 4 贝塞尔曲面,共有 16 个控制点。 对应的 HTML 文件是 page4.html。

function computeBezierSurfacePoint(uVal, wVal) {
  let u2, u3, w2, w3;
  u2 = uVal * uVal;
  u3 = uVal * u2;
  w2 = wVal * wVal;
  w3 = wVal * w2;

  // Need to note the following regarding THREE.js Matrix4.
  // When we set the matrix, we set it in row major order.
  // However, when we access the elements of this matrix, these are
  // returned in column major order.
  let matC = new THREE.Matrix4();
  matC.set(-1, 3, -3, 1, 3, -6, 3, 0, -3, 3, 0, 0, 1, 0, 0, 0);

  let matPx = new THREE.Matrix4();
  matPx.set(
    p00x, p10x, p20x, p30x, p01x, p11x, p21x, p31x,
    p02x, p12x, p22x, p32x, p03x, p13x, p23x, p33x
  );

  let matPy = new THREE.Matrix4();
  matPy.set(
    p00y, p10y, p20y, p30y, p01y, p11y, p21y, p31y,
    p02y, p12y, p22y, p32y, p03y, p13y, p23y, p33y
  );

  let matPz = new THREE.Matrix4();
  matPz.set(
    p00z, p10z, p20z, p30z, p01z, p11z, p21z, p31z,
    p02z, p12z, p22z, p32z, p03z, p13z, p23z, p33z
  );

  let mat1x = new THREE.Matrix4();
  mat1x.multiplyMatrices(matC, matPx);

  let mat1y = new THREE.Matrix4();
  mat1y.multiplyMatrices(matC, matPy);

  let mat1z = new THREE.Matrix4();
  mat1z.multiplyMatrices(matC, matPz);

  let mat2x = new THREE.Matrix4();
  mat2x.multiplyMatrices(mat1x, matC);

  let mat2y = new THREE.Matrix4();
  mat2y.multiplyMatrices(mat1y, matC);

  let mat2z = new THREE.Matrix4();
  mat2z.multiplyMatrices(mat1z, matC);

  // We access the matrix elements in column major order.
  let ex = mat2x.elements;
  let w0x = ex[0] * w3 + ex[4] * w2 + ex[8] * wVal + ex[12];
  let w1x = ex[1] * w3 + ex[5] * w2 + ex[9] * wVal + ex[13];
  let w2x = ex[2] * w3 + ex[6] * w2 + ex[10] * wVal + ex[14];
  let w3x = ex[3] * w3 + ex[7] * w2 + ex[11] * wVal + ex[15];

  let ey = mat2y.elements;
  let w0y = ey[0] * w3 + ey[4] * w2 + ey[8] * wVal + ey[12];
  let w1y = ey[1] * w3 + ey[5] * w2 + ey[9] * wVal + ey[13];
  let w2y = ey[2] * w3 + ey[6] * w2 + ey[10] * wVal + ey[14];
  let w3y = ey[3] * w3 + ey[7] * w2 + ey[11] * wVal + ey[15];

  let ez = mat2z.elements;
  let w0z = ez[0] * w3 + ez[4] * w2 + ez[8] * wVal + ez[12];
  let w1z = ez[1] * w3 + ez[5] * w2 + ez[9] * wVal + ez[13];
  let w2z = ez[2] * w3 + ez[6] * w2 + ez[10] * wVal + ez[14];
  let w3z = ez[3] * w3 + ez[7] * w2 + ez[11] * wVal + ez[15];

  let qx = u3 * w0x + u2 * w1x + uVal * w2x + w3x;
  let qy = u3 * w0y + u2 * w1y + uVal * w2y + w3y;
  let qz = u3 * w0z + u2 * w1z + uVal * w2z + w3z;

  return {
    xVal: qx,
    yVal: qy,
    zVal: qz,
  };
}

8.2 验证

  • 同样,当任何控制点(坐标)发生变化时,相应的表面也应发生变化。 这可以通过查看渲染的表面来验证。
  • 显示了许多有趣的表面,可通过单击它们的按钮获得。

9、NURBS曲面

NURBS 曲线的二维(在参数空间中)等价物是 NURBS 曲面。 NURBS 曲面相对于其他两种曲面的优势在于 NURBS 曲面提供了对曲面的局部控制。修改控制点的坐标只会影响该控制点附近的表面,而表面的其余部分不受影响。这是因为对应于 NURBS 曲面的基函数。

9.1 NURBS 曲面代码

Three.js 库的创建者再次提供了一个 JavaScript 文件来计算 NURBS 曲面上一个点的坐标,我们从那里提取了相关内容,并将其用于此应用程序。它们位于 NurbsHelper.js 文件中。与 NURBS 曲线的情况一样,定义了 u 和 w 方向的两个结向量。

在此应用程序中,我们定义了一个在 u 和 w 方向各有 7 个控制点的 NURBS 曲面。因此,共有 49 个控制点可供用户修改。对于这些控制点中的每一个,用户都可以在范围内更改 x、y、z、h 值。

NURBS Surface的计算代码在文件script6.js中,如下。 HTML 是 page6.html。

function computeNurbsSurface() {
  nurbsSurface = new NURBSSurface(
    degreeU,
    degreeW,
    knotVectorU,
    knotVectorW,
    points
  );

  surfacePoints.length = 0;
  let uVal, wVal;

  for (let j = 0; j <= noDivisions; ++j) {
    wVal = j * step;
    for (let i = 0; i <= noDivisions; ++i) {
      uVal = i * step;
      let pt = new Vector3();
      nurbsSurface.getPoint(uVal, wVal, pt);
      //let poi = new THREE.Vector3();
      surfacePoints.push(pt.x, pt.y, pt.z);
    }
  }
  renderNurbsSurface();
}

10、兴趣点

我的意图是将每种类型的曲线/曲面都放在自己的文件夹中,独立包含自己的 HTML 和 JS 文件。这是使它们各自的逻辑彼此分离的方式。它们都引用相同的 three.min.js 文件,即 WebGL 库文件。此外,它们都引用相同的 style.css 文件。
因此,安排是有六个不同的文件夹,p01CubicCurve、p02CubicSurface、p03BezierCurve、p04BezierSurface、p05NurbsCurve和p06NurbsSurface。每个文件夹都有其 HTML 和 JS 文件。

另外还有一个js文件夹,里面有三个文件,three.min.js、script.js和NurbsHelper.js。

通过上面的代码安排,可能会有一些代码重复。例如,绘制边界框的那部分代码在六个 JS 文件中被复制了六次,script1.js、script2.js、script3.js、script4.js、script5.js、script6.js。我保留了它是故意这样的,因为任何想要获取这些单独文件的学习者现在都可以从它们单独的文件夹中获取它们并直接在他们的应用程序中使用它们,而无需集成代码的麻烦。这些文件夹之外唯一需要获取的代码是 three.min.js 缩小库文件和 CSS 文件。

你可能会注意到,应用程序中没有文本框类型的输入。用户交互仅通过滑块、复选框、组合框和按钮发生。这是我对软件进行防错的方法。我希望你不会“崩溃”这个应用程序。如果您遇到此应用程序崩溃、屏幕变黑或出现其他形式的不当行为的情况,请随时通过下面的评论部分通知我。

使用最新版本的 Three.js(版本 125),对几何图形的处理方式进行了重大更改。他们删除了 THREE.Geometry 并引入了 THREE.BufferGeometry。由于我不希望对 Three.js 库进行任何进一步的修改以影响我的代码的行为,因此我在代码中包含了该库的缩小版本。这样,代码是独立的,可以在没有 Internet 连接的情况下使用本地服务器运行。

该应用程序本身有一个简单直观的用户界面,没有任何多余的装饰和花哨的东西。有一个菜单,可以在其中选择所需的曲线和曲面类型。选择菜单项后,菜单下方会出现相应的屏幕。左侧窗格中是滑块和其他控件,用于修改右侧 HTML Canvas 元素上的 3D 对象。用户不能直接与 Canvas 元素交互,只能通过左侧的控件进行交互。

这里描述的数学并不新鲜,已有四十多年的历史了。有许多应用此数学来创建几何模型的软件包。然而,这里展示的打包方式,以一种在完全客户端 JavaScript 代码中运行的自包含方式,似乎是新的。

下面给出了一些可以使用此应用程序设计的表面的图库:
在这里插入图片描述

11、结束语

在本文中,我们描述并演示了使用 Three.js 库在浏览器上以 3D 方式绘制这六个几何对象的代码 - 参数立方曲线、贝塞尔曲线、NURBS 曲线、库恩斯双三次曲面、贝塞尔曲面和 NURBS 曲面.该应用程序允许您修改各个控制点的坐标(在范围内),在某些情况下,还可以修改切线(导数)的方向。用户还可以在线框中查看曲面,并更改相机角度,从不同方向查看曲线或曲面。所有这些都是用纯 Vanilla JavaScript 编码的,我已经在 Chrome、Safari 和 Edge 浏览器上对其进行了测试。

我喜欢编写这段代码的每一点,尤其是第一次在 3D 屏幕上移动滑块时,当滑块移动时,我感到非常激动。我希望你喜欢使用该应用程序,并在使用滑块更改控制点时查看这些动态变化的曲线和曲面。

原文链接:Threejs三维曲面实现 — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法训练Day27 | LeetCode39. 组合总和 (元素可重复,数组长度不限);40. 组合总和III(去重);131.分割回文串

目录 LeetCode39. 组合总和 1. 思路 2. 代码实现 3. 剪枝优化 4. 复杂度分析 5. 思考与收获 LeetCode40. 组合总和III 1. 思路 2. 代码实现 3. 复杂度分析 4. 思考与收获 LeetCode131.分割回文串 1. 思路 2. 代码实现 3. 复杂度分析 4. 思考与收获 LeetCode39…

通过DewarpNet解决图片扭曲问题

一、论文 DewarpNet:使用堆叠的三维和二维回归网络进行单幅图像文件纠正 论文地址: https://paperswithcode.com/paper/dewarpnet-single-image-document-unwarping 代码地址: https://github.com/cvlab-stonybrook/DewarpNet 二、效果展示 2.1 论文图片效果展示 从上往下…

Android Material Design之SwitchMaterial(三)

老规矩先上图 引入 implementation com.google.android.material:material:1.4.0说明 该控件就是Switch控件的增强版本,属性基本一致 属性 属性描述android:text文本android:textOn滑块打开时显示的文本android:textOff滑块关闭时显示的文本android:thumb滑块图片app:trac…

这款比奥维地图还方便的地图软件!轻便好用,功能很强大

写方案或报告的人往往会遇到需要在地图上标注的问题。最简单的方法莫过于在百度上截图&#xff0c;然后在ppt里标注。现场勘察需要定位&#xff0c;最基本的方法是利用手机读取和记录经纬度坐标。但使用百度底图标注给客户的感觉太普通&#xff0c;而勘察现场记录的经纬度数据事…

深度分页、唯一索引的坑、分库分表、查询分离、连接池、bufferpool优化等

文章目录表过大深度分页count(*) 与 count(列名)唯一索引分库分表只分库不分表不分库只分表分库也分表查询分离使用方法查询分离的适用场景冷热分离适用场景实现方案数据库连接池优化主键无序buffer pool 太小MySQL频繁抖动的性能优化原因解决表过大 历史数据进行归档 深度分页…

需求开发到一半需要改别的分支的bug该怎么办呢?(git stash 和 git commit)

在实际开发中&#xff0c;经常我们会遇到需求开发到一半&#xff0c;别的分支有bug急需解决的情况&#xff0c;这个时候我们改怎么办呢&#xff1f; 有的人会说可以先提交当前分支的代码再切换到别的分支改bug&#xff0c;当然这样是没问题的&#xff0c;但是呢&#xff0c;在项…

【计算机毕业设计】1.房屋租赁系统

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘要 当今社会房屋租赁、出售买卖是必不可少的&#xff0c;人们不管走到哪里都需要有一个温馨的家&#xff0c;有一个落脚之地&#xff0c;所以房屋租赁、出售市场也是非常火爆&#xff01;不管是房屋租赁、出售、中…

基于conda的OpenCV库安装

基于conda的OpenCV库安装 OpenCV库的调用名是cv2 所以会看到这样的import语句 import cv2这句话就是对openCV库的调用 openCV库的下载安装 First 好像不需要像网上的教程那样, 首先去官网下载exe执行文件, 然后在VS中进行配置 直接在teminal中, 进入要指定的conda环境, …

编译原理实验--实验二 递归下降法判断算术表达式的正确性--Python实现

目录 一、实验目的和要求 二、实验内容 三、实验环境 四、实验步骤 1、语法分析所依据的文法&#xff1b; 2、给出消除左递归及提取左公因子的文法&#xff1b; 五、测试要求 六、实验步骤 1、语法分析所依据的文法 2、给出消除左递归及提取左公因子的文法&#xff1…

iNFTnews|国内数藏平台大撤退,寒冬之下海外市场是否有出路?

腾讯旗下继腾讯新闻、幻核之后&#xff0c;仅存的数藏平台也关停了。 11月16日&#xff0c;据界面新闻报道&#xff0c;腾讯TME旗下QQ音乐已经叫停“TME数字藏品”业务&#xff0c;原团队部分成员已内部活水。 一接近腾讯集团的知情人士称&#xff0c;腾讯方面曾对数字藏品业…

【Linux】生产者消费者模型

文章目录1.生产者消费者模型1.1生产者消费者模型的特点1.2生产者消费者模型的原则1.3生产者消费者模型的优点2.基于阻塞队列的生产者消费者模型2.1如何理解生产者消费者模型的并发&#xff1f;3.信号量3.1信号量接口3.2基于环形队列的生产者消费者模型3.3信号量和条件变量的区别…

Git_GitHub——基本操作、创建远程库、远程库操作、团队协作、SSH免密登录

网址:GitHub: Let’s build from here GitHub 目录 一、创建远程仓库 二、远程库操作 2.1 查看远程库别名 2.2 创建远程仓库别名 2.3 推送本地分支到远程仓库 2.4 拉取远程库到本地库 2.5 克隆远程库到本地 三、 跨团队协作 3.1 团队内协作 3.2 跨团队协作 四、SSH免密码登…

Go : golang发布三方包流程简介

文章目录一、创建项目仓库二、拉去仓库&#xff0c;编辑代码三、推送与发布代码四、使用发布的第三方包小结一、创建项目仓库 1.输入仓库的名字&#xff0c;我这里输入simpleExample&#xff0c;用来做演示 2.选择public&#xff0c;公开。要不并不好拉 3.选择需要添加的文件(…

用PyPy加速Python程序

用PyPy加速Python程序 在《Python性能优化指南–让你的Python代码快x3倍的秘诀》中有提到&#xff0c;我们可以用更好的Python运行环境或运行时优化来提升Python的速度&#xff0c;其中最成熟、使用最简单的当属PyPy。用PyPy&#xff0c;可以在不改变源代码的情况下&#xff…

二叉树相关OJ - C++

文章目录&#xff1a;根据二叉树创建字符串二叉树的层序遍历二叉树的最近公共祖先二叉搜索树与双向链表从前序与中序遍历序列构造二叉树从中序与后序遍历序列构造二叉树二叉树的前序遍历&#xff08;非递归&#xff09;二叉树的中序遍历&#xff08;非递归&#xff09;二叉树的…

【LeetCode与《代码随想录》】数组篇:做题笔记与总结-Java版

代码随想录地址 是学习过程中的笔记&#xff01;图来自代码随想录。 文章目录理论题目704. 二分查找35. 搜索插入位置34. 在排序数组中查找元素的第一个和最后一个位置69. x 的平方根367.有效的完全平方数理论 数组是存放在连续内存空间上的相同类型数据的集合。 数组下标都是…

[附源码]java毕业设计新能源汽车租赁管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

基于armv8的kvm实现分析(一)虚拟化介绍

本文基于以下软硬件假定&#xff1a; 架构&#xff1a;AARCH64 内核版本&#xff1a;5.14.0-rc5 1 什么是虚拟化 虚拟化就是把一台物理计算机虚拟成多台逻辑计算机&#xff0c;每台逻辑计算机里面可以运行不同操作系统&#xff0c;而相互之间不受影响&#xff0c;其典型架构…

面试了个 985 毕业的同学,回答“性能调优”题时表情令我毕生难忘

又逢“金九银十”&#xff0c;年轻的毕业生们满怀希望与忐忑&#xff0c;去寻找、竞争一个工作机会。已经在职的开发同学&#xff0c;也想通过社会招聘或者内推的时机争取到更好的待遇、更大的平台。 然而&#xff0c;面试人群众多&#xff0c;技术市场却相对冷淡&#xff0c;面…

JavaIO流:概述

在接触 IO 流前&#xff0c;无论是 变量的声明、数组的创建&#xff0c;又或者是复杂的并发设计还是 Jvm 的性能调优&#xff0c;我们更多的还是和内存打交道。但我们知道计算机组成包括运算器&#xff0c;控制器&#xff0c;存储器&#xff0c;输入设备&#xff0c;输出设备。…