// // Generate the control points, weights, and knots of a cone // // **params** // + normalized axis of cone // + position of base of cone // + height from base to tip // + radius at the base of the cone // // **returns** // + an object with the following properties: controlPoints, weights, knots, degree // func ConicalSurface(axis, xaxis *vec3.T, base *vec3.T, height, radius float64) *verb.NurbsSurface { angle := 2 * math.Pi profDegree := 1 heightCompon := axis.Scaled(height) radiusCompon := xaxis.Scaled(radius) profCtrlPts := []vec3.T{vec3.Add(base, &heightCompon), vec3.Add(base, &radiusCompon)} profKnots := []float64{0, 0, 1, 1} profWeights := []float64{1, 1} prof := verb.NewNurbsCurveUnchecked(profDegree, profCtrlPts, profWeights, profKnots) return RevolvedSurface(prof, base, axis, angle) }
// Compute the derivatives at a point on a NURBS surface // // **params** // + NurbsSurfaceData object representing the surface // + number of derivatives to evaluate // + u parameter at which to evaluate the derivatives // + v parameter at which to evaluate the derivatives // // **returns** // + a point represented by an array of length (dim) func (this *NurbsSurface) Derivatives(uv UV, numDerivs int) [][]vec3.T { ders := this.nonRationalDerivatives(uv, numDerivs) wders := Weight2d(ders) skl := make([][]vec3.T, numDerivs+1) for k := 0; k <= numDerivs; k++ { for l := 0; l <= numDerivs-k; l++ { v := ders[k][l].Vec3 for j := 1; j <= l; j++ { scaled := skl[k][l-j].Scaled(binomial(l, j) * wders[0][j]) v.Sub(&scaled) } for i := 1; i <= k; i++ { scaled := skl[k-i][l].Scaled(binomial(k, i) * wders[i][0]) v.Sub(&scaled) var v2 vec3.T for j := 1; j <= l; j++ { scaled := skl[k-i][l-j].Scaled(binomial(l, j) * wders[i][j]) v2.Add(&scaled) } scaled = v2.Scaled(binomial(k, i)) v.Sub(&scaled) } v.Scale(1 / wders[0][0]) skl[k][l] = v } } return skl }
// // Compute a point in a non-uniform, non-rational B spline volume // // **params** // + VolumeData // + u parameter at which to evaluate the volume point // + v parameter at which to evaluate the volume point // + w parameter at which to evaluate the volume point // // **returns** // + a point represented by an array of length (dim) func (this *volume) PointGivenNML(n, m, l int, uvw UVW) vec3.T { if !areValidRelations(this.DegreeU, len(this.ControlPoints), len(this.KnotsU)) || !areValidRelations(this.DegreeV, len(this.ControlPoints[0]), len(this.KnotsV)) || !areValidRelations(this.DegreeW, len(this.ControlPoints[0][0]), len(this.KnotsW)) { panic("Invalid relations between control points and knot vector") } controlPoints := this.ControlPoints degreeU, degreeV, degreeW := this.DegreeU, this.DegreeV, this.DegreeW knotsU, knotsV, knotsW := this.KnotsU, this.KnotsV, this.KnotsW knotSpanIndexU := knotsU.SpanGivenN(n, degreeU, uvw[0]) knotSpanIndexV := knotsV.SpanGivenN(m, degreeV, uvw[1]) knotSpanIndexW := knotsW.SpanGivenN(l, degreeW, uvw[2]) uBasisVals := BasisFunctionsGivenKnotSpanIndex(knotSpanIndexU, uvw[0], degreeU, knotsU) vBasisVals := BasisFunctionsGivenKnotSpanIndex(knotSpanIndexV, uvw[0], degreeV, knotsV) wBasisVals := BasisFunctionsGivenKnotSpanIndex(knotSpanIndexV, uvw[0], degreeW, knotsW) uind := knotSpanIndexU - degreeU var position, temp, temp2 vec3.T for i := 0; i <= degreeW; i++ { temp2 = vec3.Zero wind := knotSpanIndexW - degreeW + i for j := 0; j <= degreeV; j++ { temp = vec3.Zero vind := knotSpanIndexV - degreeV + j for k := 0; k <= degreeU; k++ { scaled := controlPoints[uind+k][vind][wind].Scaled(uBasisVals[k]) temp.Add(&scaled) } // add weighted contribution of u isoline scaled := temp.Scaled(vBasisVals[j]) temp2.Add(&scaled) } // add weighted contribution from uv isosurfaces scaled := temp2.Scaled(wBasisVals[i]) position.Add(&scaled) } return position }
func Homogenized(pt vec3.T, w float64) HomoPoint { return HomoPoint{pt.Scaled(w), w} }
func (this *NurbsCurve) ClosestParam(p vec3.T) float64 { // We want to solve: // // C'(u) * ( C(u) - P ) = 0 = f(u) // // C(u) is the curve, p is the point, * is a dot product // // We'll use newton's method: // // u* = u - f / f' // // We use the product rule in order to form the derivative, f': // // f' = C"(u) * ( C(u) - p ) + C'(u) * C'(u) // // What is the conversion criteria? (Piegl & Tiller suggest) // // |C(u) - p| < e1 // // |C'(u)*(C(u) - P)| // ------------------ < e2 // |C'(u)| |C(u) - P| // // 1) first check 2 & 3 // 2) if at least one of these is not, compute new value, otherwise halt // 3) ensure the parameter stays within range // * if not closed, don't allow outside of range a-b // * if closed (e.g. circle), allow to move back to beginning // 4) if |(u* - u)C'(u)| < e1, halt // min := math.MaxFloat64 var u float64 pts := this.regularSample(len(this.controlPoints) * this.degree) for i := 0; i < len(pts)-1; i++ { u0, u1 := pts[i].U, pts[i+1].U p0 := pts[i].Pt p1 := pts[i+1].Pt proj := segmentClosestPoint(&p, &p0, &p1, u0, u1) dv := vec3.Sub(&p, &proj.Pt) d := dv.Length() if d < min { min = d u = proj.U } } maxits := 5 var i int var e []vec3.T eps1, eps2 := 0.0001, 0.0005 var dif vec3.T minu, maxu := this.knots[0], this.knots[len(this.knots)-1] firstCtrlPt := this.controlPoints[0].Dehomogenized() lastCtrlPt := this.controlPoints[len(this.controlPoints)-1].Dehomogenized() closed := vec3.SquareDistance(&firstCtrlPt, &lastCtrlPt) < Epsilon cu := u f := func(u float64) []vec3.T { return this.Derivatives(u, 2) } n := func(u float64, e []vec3.T, d vec3.T) float64 { // C'(u) * ( C(u) - P ) = 0 = f(u) f := vec3.Dot(&e[1], &d) // f' = C"(u) * ( C(u) - p ) + C'(u) * C'(u) s0 := vec3.Dot(&e[2], &d) s1 := vec3.Dot(&e[1], &e[1]) df := s0 + s1 return u - f/df } for i < maxits { e = f(cu) dif = vec3.Sub(&e[0], &p) // |C(u) - p| < e1 c1v := dif.Length() // C'(u) * (C(u) - P) // ------------------ < e2 // |C'(u)| |C(u) - P| c2n := vec3.Dot(&e[1], &dif) c2d := e[1].Length() * c1v c2v := c2n / c2d c1 := c1v < eps1 c2 := math.Abs(c2v) < eps2 // if both tolerances are met if c1 && c2 { return cu } ct := n(cu, e, dif) // are we outside of the bounds of the curve? if ct < minu { if closed { ct = maxu - (ct - minu) } else { ct = minu } } else if ct > maxu { if closed { ct = minu + (ct - maxu) } else { ct = maxu } } // will our next step force us out of the curve? c3vv := e[1].Scaled(ct - cu) c3v := c3vv.Length() if c3v < eps1 { return cu } cu = ct i++ } return cu }
// Generate the control points, weights, and knots of an elliptical arc // // **params** // + the center // + the scaled x axis // + the scaled y axis // + start angle of the ellipse arc, between 0 and 2pi, where 0 points at the xaxis // + end angle of the arc, between 0 and 2pi, greater than the start angle // // **returns** // + a NurbsCurveData object representing a NURBS curve func EllipseArc(center *vec3.T, xaxis, yaxis *vec3.T, startAngle, endAngle float64) *verb.NurbsCurve { xradius, yradius := xaxis.Length(), yaxis.Length() xaxisNorm, yaxisNorm := xaxis.Normalized(), yaxis.Normalized() // if the end angle is less than the start angle, do a circle if endAngle < startAngle { endAngle = 2.0*math.Pi + startAngle } theta := endAngle - startAngle // how many arcs? var numArcs int if theta <= math.Pi/2 { numArcs = 1 } else { if theta <= math.Pi { numArcs = 2 } else if theta <= 3*math.Pi/2 { numArcs = 3 } else { numArcs = 4 } } dtheta := theta / float64(numArcs) w1 := math.Cos(dtheta / 2) xCompon := xaxisNorm.Scaled(xradius * math.Cos(startAngle)) yCompon := yaxisNorm.Scaled(yradius * math.Sin(startAngle)) P0 := vec3.Add(&xCompon, &yCompon) temp0 := yaxisNorm.Scaled(math.Cos(startAngle)) temp1 := xaxisNorm.Scaled(math.Sin(startAngle)) T0 := vec3.Sub(&temp0, &temp1) controlPoints := make([]vec3.T, 2*numArcs+1) knots := make([]float64, 2*numArcs+3) index := 0 angle := startAngle weights := make([]float64, numArcs*2) controlPoints[0] = P0 weights[0] = 1.0 for i := 1; i <= numArcs; i++ { angle += dtheta xCompon = xaxisNorm.Scaled(xradius * math.Cos(angle)) yCompon = yaxisNorm.Scaled(yradius * math.Sin(angle)) offset := vec3.Add(&xCompon, &yCompon) P2 := vec3.Add(center, &offset) weights[index+2] = 1 controlPoints[index+2] = P2 temp0 := yaxisNorm.Scaled(math.Cos(angle)) temp1 := xaxisNorm.Scaled(math.Sin(angle)) T2 := vec3.Sub(&temp0, &temp1) T0Norm := T0.Normalized() T2Norm := T2.Normalized() inters := intersect.Rays(&P0, &T0Norm, &P2, &T2Norm) T0Scaled := T0.Scaled(inters.U0) P1 := vec3.Add(&P0, &T0Scaled) weights[index+1] = w1 controlPoints[index+1] = P1 index += 2 if i < numArcs { P0 = P2 T0 = T2 } } j := 2*numArcs + 1 for i := 0; i < 3; i++ { knots[i] = 0.0 knots[i+j] = 1.0 } switch numArcs { case 2: knots[3] = 0.5 knots[4] = 0.5 case 3: knots[3] = 1 / 3 knots[4] = 1 / 3 knots[5] = 2 / 3 knots[6] = 2 / 3 case 4: knots[3] = 0.25 knots[4] = 0.25 knots[5] = 0.5 knots[6] = 0.5 knots[7] = 0.75 knots[8] = 0.75 } return verb.NewNurbsCurveUnchecked(2, controlPoints, weights, knots) }
// Generate the control points, weights, and knots of an arbitrary arc // (Corresponds to Algorithm A7.1 from Piegl & Tiller) // // **params** // + the center of the arc // + the xaxis of the arc // + orthogonal yaxis of the arc // + radius of the arc // + start angle of the arc, between 0 and 2pi // + end angle of the arc, between 0 and 2pi, greater than the start angle // // **returns** // + a NurbsCurveData object representing a NURBS curve func Arc(center *vec3.T, xaxis, yaxis *vec3.T, radius float64, startAngle, endAngle float64) *verb.NurbsCurve { xaxisScaled, yaxisScaled := xaxis.Scaled(radius), yaxis.Scaled(radius) return EllipseArc(center, &xaxisScaled, &yaxisScaled, startAngle, endAngle) }
// // Generate the control points, weights, and knots of a sphere // // **params** // + the center of the sphere // + normalized axis of sphere // + vector perpendicular to axis of sphere, starting the rotation of the sphere // + radius of the sphere // // **returns** // + an object with the following properties: controlPoints, weights, knotsU, knotsV, degreeU, degreeV // func SphericalSurface(center *vec3.T, axis, xaxis *vec3.T, radius float64) *verb.NurbsSurface { invAxis := axis.Inverted() arc := Arc(center, &invAxis, xaxis, radius, 0, math.Pi) return RevolvedSurface(arc, center, axis, 2*math.Pi) }