/* * jgeom: Geometry Library fo Java * * Copyright (C) 2005 Samuel Gerber * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package toxi.geom.nurbs; import toxi.geom.Axis3D; import toxi.geom.GMatrix; import toxi.geom.GVector; import toxi.geom.Origin3D; import toxi.geom.ReadonlyVec3D; import toxi.geom.SingularMatrixException; import toxi.geom.Vec3D; import toxi.geom.Vec4D; import toxi.math.MathUtils; /** * This class offers some static methods to create NurbsSurfaces and NurbsCurves * from diffrent inputs. * * @author sg * @version 1.0 */ public final class NurbsCreator { private static KnotVector averaging(final float[] uk, final int p) { int m = uk.length + p; int n = uk.length - 1; float ip = 1f / p; float[] u = new float[m + 1]; for (int i = 0; i <= p; i++) { u[m - i] = 1; } for (int j = 1; j <= n - p; j++) { float sum = 0; for (int i = j; i <= j + p - 1; i++) { sum += uk[i]; } u[j + p] = sum * ip; } return new KnotVector(u, p); } private static float[] centripetal(Vec3D[] points) { int n = points.length - 1; float d = 0; float[] uk = new float[n + 1]; uk[n] = 1; double[] tmp = new double[n]; for (int k = 1; k <= n; k++) { tmp[k - 1] = Math.sqrt(points[k].distanceTo(points[k - 1])); d += tmp[k - 1]; } d = 1f / d; for (int i = 1; i < n; i++) { uk[i] = uk[i - 1] + (float) (tmp[i - 1] * d); } return uk; } /** * Create an Arc. * * @param o * Origin to creat arc around * @param r * Radius of the arc. * @param thetaStart * Start angle of the arc in radians * @param thetaEnd * End angle of the arc in radians. If end angle is smaller than * start angle, the end angle is increased by 2*PI. * @return A NurbsCurve for the Arc. */ public static NurbsCurve createArc(Origin3D o, float r, float thetaStart, float thetaEnd) { Vec3D tmp = new Vec3D(); if (thetaEnd < thetaStart) { thetaEnd += MathUtils.TWO_PI; } double theta = thetaEnd - thetaStart; int narcs = 4; if (theta <= MathUtils.HALF_PI) { narcs = 1; } else if (theta <= MathUtils.PI) { narcs = 2; } else if (theta <= MathUtils.THREE_HALVES_PI) { narcs = 3; } double dtheta = theta / narcs; int n = 2 * narcs; double w1 = Math.cos(dtheta / 2); final float sinStart = (float) Math.sin(thetaStart); final float cosStart = (float) Math.cos(thetaStart); tmp.set(o.xAxis).scaleSelf(r * cosStart); Vec3D p0 = new Vec3D(o.origin).addSelf(tmp); tmp.set(o.yAxis).scaleSelf(r * sinStart); p0.addSelf(tmp); tmp.set(o.yAxis).scaleSelf(cosStart); Vec3D t0 = new Vec3D(o.xAxis).scaleSelf(-sinStart).addSelf(tmp); Vec4D[] cps = new Vec4D[n + 1]; cps[0] = new Vec4D(p0, 1); int index = 0; double angle = thetaStart; Vec3D p1 = new Vec3D(); Vec3D p2 = new Vec3D(); Vec3D t2 = new Vec3D(); for (int i = 1; i <= narcs; i++) { angle += dtheta; final double sin = Math.sin(angle); final double cos = Math.cos(angle); tmp.set(o.xAxis).scaleSelf((float) (r * cos)); p2.set(o.origin).addSelf(tmp); tmp.set(o.yAxis).scaleSelf((float) (r * sin)); p2.addSelf(tmp); cps[index + 2] = new Vec4D(p2, 1); t2.set(o.xAxis).scaleSelf((float) -sin); tmp.set(o.yAxis).scaleSelf((float) cos); t2.addSelf(tmp); lineIntersect3D(p0, t0, p2, t2, p1, p1); cps[index + 1] = new Vec4D(p1, (float) w1); index += 2; if (i < narcs) { p0.set(p2); t0.set(t2); } } int j = n + 1; float[] uKnot = new float[j + 3]; for (int i = 0; i < 3; i++) { uKnot[i + j] = 1; } switch (narcs) { case 2: uKnot[3] = 0.5f; uKnot[4] = 0.5f; break; case 3: uKnot[3] = uKnot[4] = MathUtils.THIRD; uKnot[5] = uKnot[6] = 2 * MathUtils.THIRD; break; case 4: uKnot[3] = 0.25f; uKnot[4] = 0.25f; uKnot[5] = 0.5f; uKnot[6] = 0.5f; uKnot[7] = 0.75f; uKnot[8] = 0.75f; break; } return new BasicNurbsCurve(cps, uKnot, 2); } /** * Create a full-circle NurbsCurve around the given Origin with radius r. * The NurbsCurve has controlpolygon which has 7 controlpoints and the shape * of quadrat. * * @param o * Origin to create the full-circle around * @param r * Radius of the full-circle * @return A NurbsCurve for a full-circle */ public static NurbsCurve createFullCircleQuad7(Origin3D o, float r) { Vec4D[] cp = new Vec4D[7]; cp[0] = new Vec4D(o.xAxis.scale(r), 1); cp[3] = cp[0].getInvertedXYZ(); cp[6] = cp[0].copy(); cp[1] = new Vec4D(o.yAxis.add(o.xAxis).scaleSelf(r), 0.5f); cp[4] = cp[1].getInvertedXYZ(); cp[2] = new Vec4D(o.xAxis.getInverted().addSelf(o.yAxis).scaleSelf(r), 0.5f); cp[5] = cp[2].getInvertedXYZ(); for (int i = 0; i < 7; i++) { cp[i].addXYZSelf(o.origin); } float[] u = { 0, 0, 0, 0.25f, 0.5f, 0.5f, 0.75f, 1, 1, 1 }; return new BasicNurbsCurve(cp, u, 2); } /** * Create a full-circle NurbsCurve around the given Origin with radius r. * The NurbsCurve has controlpolygon which has 9 controlpoints and the shape * of quadrat. * * @param o * Origin to create the full-circle around * @param r * Radius of the full-circle * @return A NurbsCurve for a full-circle */ public static NurbsCurve createFullCircleQuad9(Origin3D o, float r) { final float w = MathUtils.SQRT2 / 2; Vec4D[] cp = new Vec4D[9]; cp[0] = new Vec4D(o.xAxis.scale(r), 1); cp[4] = cp[0].getInvertedXYZ(); cp[8] = cp[0].copy(); cp[1] = new Vec4D(o.xAxis.add(o.yAxis).scaleSelf(r), w); cp[5] = cp[1].getInvertedXYZ(); cp[2] = new Vec4D(o.yAxis.scale(r), 1); cp[6] = cp[2].getInvertedXYZ(); cp[3] = new Vec4D(o.xAxis.getInverted().addSelf(o.yAxis).scaleSelf(r), w); cp[7] = cp[3].getInvertedXYZ(); for (int i = 0; i < 9; i++) { cp[i].addXYZSelf(o.origin); } float[] u = { 0, 0, 0, 0.25f, 0.25f, 0.5f, 0.5f, 0.75f, 0.75f, 1, 1, 1 }; return new BasicNurbsCurve(cp, u, 2); } /** * Create a revolved NurbsSurface from the given NurbsCurve around the given * axis whith the angle theta. * * @param a * Axis to revolve around. * @param curve * NurbsCurve to revolve * @param theta * Angle to revolve * @return The revolved NurbsSurface */ // TODO:call createRevolvedSurface(Axis3D a, NurbsCurve curve, double // thetaStart, double thetaEnd) as as it is tested public static NurbsSurface createRevolvedSurface(Axis3D a, NurbsCurve curve, double theta) { int narcs = 4; if (theta <= MathUtils.HALF_PI) { narcs = 1; } else if (theta <= MathUtils.PI) { narcs = 2; } else if (theta <= MathUtils.THREE_HALVES_PI) { narcs = 3; } int j = 3 + 2 * (narcs - 1); final double dtheta = theta / narcs; final float[] uKnot = new float[j + 3]; for (int i = 0; i < 3; i++) { uKnot[j + i] = 1; } switch (narcs) { case 2: uKnot[3] = 0.5f; uKnot[4] = 0.5f; break; case 3: uKnot[3] = uKnot[4] = MathUtils.THIRD; uKnot[5] = uKnot[6] = 2 * MathUtils.THIRD; break; case 4: uKnot[3] = 0.25f; uKnot[4] = 0.25f; uKnot[5] = 0.5f; uKnot[6] = 0.5f; uKnot[7] = 0.75f; uKnot[8] = 0.75f; break; } double angle = 0; final double[] cos = new double[narcs + 1]; final double[] sin = new double[narcs + 1]; for (int i = 0; i <= narcs; i++) { cos[i] = Math.cos(angle); sin[i] = Math.sin(angle); angle += dtheta; } Vec4D[] pj = curve.getControlPoints(); Vec3D P0; // = new Vec3D(); final Vec3D P2 = new Vec3D(); final Vec3D O = new Vec3D(); final Vec3D T2 = new Vec3D(); final Vec3D T0 = new Vec3D(); final Vec3D tmp = new Vec3D(); final Vec3D X = new Vec3D(); final Vec3D Y = new Vec3D(); final Vec4D[][] pij = new Vec4D[2 * narcs + 1][pj.length]; final double wm = Math.cos(dtheta / 2); for (j = 0; j < pj.length; j++) { pointToLine3D(a.origin, a.dir, pj[j].to3D(), O); X.set(pj[j].to3D().subSelf(O)); final double r = X.magnitude(); if (r == 0) { X.set(O); } X.normalize(); a.dir.crossInto(X, Y); pij[0][j] = new Vec4D(pj[j]); P0 = pj[j].to3D(); T0.set(Y); int index = 0; for (int i = 1; i <= narcs; i++) { tmp.set(X).scaleSelf((float) (r * cos[i])); P2.set(O).addSelf(tmp); tmp.set(Y).scaleSelf((float) (r * sin[i])); P2.addSelf(tmp); pij[index + 2][j] = new Vec4D(P2, pj[j].w); tmp.set(Y).scaleSelf((float) cos[i]); T2.set(X).scaleSelf((float) -sin[i]).addSelf(tmp); lineIntersect3D(P0, T0, P2, T2, tmp, tmp); pij[index + 1][j] = new Vec4D(tmp, (float) (wm * pj[j].w)); index += 2; if (i < narcs) { P0.set(P2); T0.set(T2); } } } ControlNet cnet = new ControlNet(pij); return new BasicNurbsSurface(cnet, uKnot, curve.getKnots(), 2, curve.getDegree()); } /** * Create a revolved NurbsSurface from the given NurbsCurve around the given * axis whith the angle theta. * * @param a * Axis to revolve around. * @param curve * NurbsCurve to revolve * @param thetaStart * Angle to start revolution * @param thetaEnd * Angle to end revolution * @return The revolved NurbsSurface */ public static NurbsSurface createRevolvedSurface(Axis3D a, NurbsCurve curve, double thetaStart, double thetaEnd) { int narcs = 4; if (thetaStart > thetaEnd) { double tmp = thetaEnd; thetaEnd = thetaStart; thetaStart = tmp; } double theta = thetaEnd - thetaStart; if (theta <= MathUtils.HALF_PI) { narcs = 1; } else if (theta <= MathUtils.PI) { narcs = 2; } else if (theta <= MathUtils.THREE_HALVES_PI) { narcs = 3; } int j = 3 + 2 * (narcs - 1); final double dtheta = theta / narcs; final float[] uKnot = new float[j + 3]; for (int i = 0; i < 3; i++) { uKnot[i] = 0; uKnot[j + i] = 1; } switch (narcs) { case 2: uKnot[3] = 0.5f; uKnot[4] = 0.5f; break; case 3: uKnot[3] = uKnot[4] = MathUtils.THIRD; uKnot[5] = uKnot[6] = 2 * MathUtils.THIRD; break; case 4: uKnot[3] = 0.25f; uKnot[4] = 0.25f; uKnot[5] = 0.5f; uKnot[6] = 0.5f; uKnot[7] = 0.75f; uKnot[8] = 0.75f; break; } double angle = thetaStart; final double[] cos = new double[narcs + 1]; final double[] sin = new double[narcs + 1]; for (int i = 0; i <= narcs; i++) { cos[i] = Math.cos(angle); sin[i] = Math.sin(angle); angle += dtheta; } final Vec4D[] pj = curve.getControlPoints(); Vec3D P0; // = new Vec3D(); final Vec3D O = new Vec3D(); final Vec3D P2 = new Vec3D(); final Vec3D T2 = new Vec3D(); final Vec3D T0 = new Vec3D(); final Vec3D tmp = new Vec3D(); final Vec3D X = new Vec3D(); final Vec3D Y = new Vec3D(); final Vec4D[][] pij = new Vec4D[2 * narcs + 1][pj.length]; final double wm = Math.cos(dtheta / 2); for (j = 0; j < pj.length; j++) { pointToLine3D(a.origin, a.dir, pj[j].to3D(), O); X.set(pj[j].to3D().subSelf(O)); final double r = X.magnitude(); if (r == 0) { X.set(O); } X.normalize(); a.dir.crossInto(X, Y); pij[0][j] = new Vec4D(pj[j]); P0 = pj[j].to3D(); T0.set(Y); int index = 0; for (int i = 1; i <= narcs; i++) { tmp.set(X).scaleSelf((float) (r * cos[i])); P2.set(O).addSelf(tmp); tmp.set(Y).scaleSelf((float) (r * sin[i])); P2.addSelf(tmp); pij[index + 2][j] = new Vec4D(P2, pj[j].w); tmp.set(Y).scaleSelf((float) cos[i]); T2.set(X).scaleSelf((float) -sin[i]).addSelf(tmp); lineIntersect3D(P0, T0, P2, T2, tmp, tmp); pij[index + 1][j] = new Vec4D(tmp, (float) (wm * pj[j].w)); index += 2; if (i < narcs) { P0.set(P2); T0.set(T2); } } } ControlNet cnet = new ControlNet(pij); return new BasicNurbsSurface(cnet, uKnot, curve.getKnots(), 2, curve.getDegree()); } /** * Create a semi-circle NurbsCurve around the given Origin with radius r. * * @param o * Origin to create semi-circle around. * @param r * Radius of the semi-circle * @return A NurbsCurve for a semi-circle */ public static NurbsCurve createSemiCircle(Origin3D o, float r) { Vec4D[] cp = new Vec4D[4]; cp[0] = new Vec4D(o.xAxis.scale(r), 1); cp[3] = cp[0].getInvertedXYZ(); cp[0].addXYZSelf(o.origin); cp[3].addXYZSelf(o.origin); cp[1] = new Vec4D(o.xAxis.add(o.yAxis).scaleSelf(r).addSelf(o.origin), 0.5f); cp[2] = new Vec4D(o.xAxis.getInverted().addSelf(o.yAxis).scaleSelf(r) .addSelf(o.origin), 0.5f); float[] u = { 0, 0, 0, 0.5f, 1, 1, 1 }; return new BasicNurbsCurve(cp, u, 2); } /** * Creates a {@link NurbsSurface} by swinging a profile {@link NurbsCurve} * in the XZ plane around a trajectory curve in the XY plane. Both curves * MUST be offset from the major axes (i.e. their control points should have * non-zero coordinates for the Y coordinates of the profile curve and the Z * coordinates of the trajectory). * * @param proj * profile curve in XZ * @param traj * trajectory curve in XY * @param alpha * scale factor * @return 3D NURBS surface */ public static NurbsSurface createSwungSurface(NurbsCurve proj, NurbsCurve traj, float alpha) { Vec4D[] cpProj = proj.getControlPoints(); Vec4D[] cpTraj = traj.getControlPoints(); // The NURBS Book, Piegl, p.455,456 // http://books.google.co.uk/books?id=7dqY5dyAwWkC&pg=PA455&lpg=PA455 // fixed Z handling (was wrong in original jgeom version) Vec4D[][] cps = new Vec4D[cpProj.length][cpTraj.length]; for (int i = 0; i < cpProj.length; i++) { for (int j = 0; j < cpTraj.length; j++) { Vec4D cp = new Vec4D(); cp.x = cpProj[i].x * cpTraj[j].x * alpha; cp.y = cpProj[i].y * cpTraj[j].y * alpha; cp.z = (cpProj[i].z + cpTraj[j].z) * alpha; cp.w = cpProj[i].w * cpTraj[j].w; cps[i][j] = cp; } } return new BasicNurbsSurface(new ControlNet(cps), proj.getKnots(), traj.getKnots(), proj.getDegree(), traj.getDegree()); } /** * Perform a linear extrusion of the given {@link NurbsCurve} along the * supplied vector to produce a new {@link NurbsSurface}. The extrusion * length is the length of the vector given. * * @param curve * NURBS curve instance * @param extrude * a extrusion vector * @return a NurbsSurface. */ public static NurbsSurface extrudeCurve(NurbsCurve curve, Vec3D extrude) { // Curve and Surface Construction using Rational B-splines // Piegl and Tiller CAD Vol 19 #9 November 1987 pp 485-498 KnotVector vKnot = new KnotVector(new float[] { 0f, 0f, 1f, 1f }, 1); Vec4D[][] cpoints = new Vec4D[curve.getControlPoints().length][2]; Vec4D[] curvePoints = curve.getControlPoints(); for (int i = 0; i < cpoints.length; i++) { for (int j = 0; j < 2; j++) { /* * Change added 11/02/90 Steve Larkin : Have multiplied the term * wcoord to the extrusion vector before adding to the curve * coordinates. Not really sure this is the correct fix, but it * works ! */ Vec4D cp = new Vec4D(); cp.x = curvePoints[i].x + j * extrude.x; cp.y = curvePoints[i].y + j * extrude.y; cp.z = curvePoints[i].z + j * extrude.z; cp.w = curvePoints[i].w; cpoints[i][j] = cp; } } ControlNet cnet = new ControlNet(cpoints); return new BasicNurbsSurface(cnet, curve.getKnots(), vKnot.getArray(), curve.getDegree(), vKnot.getDegree()); } /** * Interpolates a NurbCurve form the given Points using a global * interpolation technique. * * @param points * Points to interpolate * @param degree * degree of the interpolated NurbsCurve * @return A NurbsCurve interpolating the given Points * @throws InterpolationException * thrown if interpolation failed or is not possible. */ public static NurbsCurve globalCurveInterpolation(Vec3D[] points, int degree) throws InterpolationException { try { final int n = points.length; final double[] A = new double[n * n]; final float[] uk = centripetal(points); KnotVector uKnots = averaging(uk, degree); for (int i = 0; i < n; i++) { int span = uKnots.findSpan(uk[i]); double[] tmp = uKnots.basisFunctions(span, uk[i]); System.arraycopy(tmp, 0, A, i * n + span - degree, tmp.length); } final GMatrix a = new GMatrix(n, n, A); final GVector perm = new GVector(n); final GMatrix lu = new GMatrix(n, n); a.computeLUD(lu, perm); final Vec4D[] cps = new Vec4D[n]; for (int i = 0; i < cps.length; i++) { cps[i] = new Vec4D(0, 0, 0, 1); } // x-ccordinate final GVector b = new GVector(n); for (int j = 0; j < n; j++) { b.setElement(j, points[j].x); } final GVector sol = new GVector(n); sol.backSolveLUD(lu, b, perm); for (int j = 0; j < n; j++) { cps[j].x = (float) sol.get(j); } // y-ccordinate for (int j = 0; j < n; j++) { b.setElement(j, points[j].y); } sol.zero(); sol.backSolveLUD(lu, b, perm); for (int j = 0; j < n; j++) { cps[j].y = (float) sol.get(j); } // z-ccordinate for (int j = 0; j < n; j++) { b.setElement(j, points[j].z); } sol.zero(); sol.backSolveLUD(lu, b, perm); for (int j = 0; j < n; j++) { cps[j].z = (float) sol.get(j); } return new BasicNurbsCurve(cps, uKnots); } catch (SingularMatrixException ex) { throw new InterpolationException(ex); } } /** * Interpolates a NurbsSurface from the given points using a gloabl * interpolation technique. * * @param points * Points arranged in a net (matrix) to interpolate * @param uDegrees * degree in u direction * @param vDegrees * degree in v direction * @return A NurbsSurface interpolating the given points. * @throws InterpolationException * thrown if interpolation failed or is not possible. */ public static NurbsSurface globalSurfaceInterpolation(Vec3D[][] points, int uDegrees, int vDegrees) throws InterpolationException { final int n = points.length; final int m = points[0].length; float[][] uv = surfaceMeshParameters(points, n - 1, m - 1); KnotVector u = averaging(uv[0], uDegrees); KnotVector v = averaging(uv[1], vDegrees); Vec4D[][] r = new Vec4D[m][n]; Vec3D[] tmp = new Vec3D[n]; for (int l = 0; l < m; l++) { for (int i = 0; i < n; i++) { tmp[i] = points[i][l]; } try { NurbsCurve curve = globalCurveInterpolation(tmp, uDegrees); r[l] = curve.getControlPoints(); } catch (InterpolationException ex) { for (int i = 0; i < tmp.length; i++) { r[l][i] = new Vec4D(tmp[i], 1); } } } Vec4D[][] cp = new Vec4D[n][m]; tmp = new Vec3D[m]; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { tmp[j] = r[j][i].to3D(); } try { NurbsCurve curve = globalCurveInterpolation(tmp, vDegrees); cp[i] = curve.getControlPoints(); } catch (InterpolationException ex) { for (int j = 0; j < tmp.length; j++) { cp[i][j] = new Vec4D(tmp[j], 1); } } } return new BasicNurbsSurface(new ControlNet(cp), u, v); } private static void lineIntersect3D(Vec3D p0, Vec3D t0, Vec3D p2, Vec3D t2, Vec3D out0, Vec3D out2) { Vec3D v02 = p0.sub(p2); double a = t0.dot(t0); double b = t0.dot(t2); double c = t2.dot(t2); double d = t0.dot(v02); double e = t2.dot(v02); double denom = a * c - b * b; double mu0, mu2; if (denom < MathUtils.EPS) { mu0 = 0; mu2 = b > c ? d / b : e / c; } else { mu0 = (b * e - c * d) / denom; mu2 = (a * e - b * d) / denom; } out0.set(t0.scale((float) mu0).addSelf(p0)); out2.set(t2.scale((float) mu2).addSelf(p2)); } private static void pointToLine3D(ReadonlyVec3D p, ReadonlyVec3D t, Vec3D top, Vec3D out) { Vec3D dir = top.sub(p); float hyp = dir.magnitude(); out.set(p.add(t.scale(t.dot(dir.normalize()) * hyp))); } private static float[][] surfaceMeshParameters(Vec3D points[][], int n, int m) { final float[][] res = new float[2][]; int num = m + 1; final float[] cds = new float[(n + 1) * (m + 1)]; final float[] uk = new float[n + 1]; uk[n] = 1; for (int l = 0; l <= m; l++) { float total = 0; for (int k = 1; k <= n; k++) { cds[k] = points[k][l].distanceTo(points[k - 1][l]); total += cds[k]; } if (total == 0) { num = num - 1; } else { float d = 0; total = 1f / total; for (int k = 1; k <= n; k++) { d += cds[k]; uk[k] += d * total; } } } if (num == 0) { return null; } float inum = 1f / num; for (int k = 1; k < n; k++) { uk[k] *= inum; } num = n + 1; final float[] vk = new float[m + 1]; vk[m] = 1; for (int l = 0; l <= n; l++) { float total = 0; Vec3D[] pl = points[l]; for (int k = 1; k <= m; k++) { cds[k] = pl[k].distanceTo(pl[k - 1]); total += cds[k]; } if (total == 0) { num = num - 1; } else { float d = 0; total = 1f / total; for (int k = 1; k <= m; k++) { d += cds[k]; vk[k] += d * total; } } } if (num == 0) { return null; } inum = 1f / num; for (int k = 1; k < m; k++) { vk[k] *= inum; } res[0] = uk; res[1] = vk; return res; } private NurbsCreator() { } }