func (this *Mesh) TriangleUvFromPoint(faceIndex int, f *vec3.T) verb.UV { tri := this.Faces[faceIndex] p0 := this.Points[tri[0]] p1 := this.Points[tri[1]] p2 := this.Points[tri[2]] uv0 := this.UVs[tri[0]] uv1 := this.UVs[tri[1]] uv2 := this.UVs[tri[2]] f0 := vec3.Sub(&p0, f) f1 := vec3.Sub(&p1, f) f2 := vec3.Sub(&p2, f) // calculate the areas and factors (order of parameters doesn't matter): p1.Sub(&p0) p2.Sub(&p0) aVec := vec3.Cross(&p1, &p2) a := aVec.Length() a0Vec := vec3.Cross(&f1, &f2) a1Vec := vec3.Cross(&f2, &f0) a2Vec := vec3.Cross(&f0, &f1) a0 := a0Vec.Length() / a a1 := a1Vec.Length() / a a2 := a2Vec.Length() / a // find the uv corresponding to point f (uv1/uv2/uv3 are associated to p1/p2/p3): return verb.UV{ a0*uv0[0] + a1*uv1[0] + a2*uv2[0], a0*uv0[1] + a1*uv1[1] + a2*uv2[1], } }
// // Sample a NURBS curve at 3 points, facilitating adaptive sampling // // **params** // + NurbsCurveData object // + start parameter for sampling // + end parameter for sampling // + whether to prefix the point with the parameter // // **returns** // + an array of dim + 1 length where the first element is the param where it was sampled and the remaining the pt // func (this *NurbsCurve) adaptiveSampleRange(start, end, tol float64, includeU bool) []CurvePoint { // sample curve at three pts p1, p3 := this.Point(start), this.Point(end) t := 0.5 + 0.2*rand.Float64() mid := start + (end-start)*t p2 := this.Point(mid) // if the two end control points are coincident, the three point test will always return 0, let's split the curve diff := vec3.Sub(&p1, &p3) diff2 := vec3.Sub(&p1, &p2) // the first condition checks if the curve makes up a loop, if so, we will need to continue evaluation if (vec3.Dot(&diff, &diff) < tol && vec3.Dot(&diff2, &diff2) > tol) || !threePointsAreCollinear(&p1, &p2, &p3, tol) { // get the exact middle exact_mid := start + (end-start)*0.5 // recurse on the two halves left_pts := this.adaptiveSampleRange(start, exact_mid, tol, includeU) right_pts := this.adaptiveSampleRange(exact_mid, end, tol, includeU) // concatenate the two leftEnd := len(left_pts) - 1 return append(left_pts[:leftEnd:leftEnd], right_pts...) } else { return []CurvePoint{{start, p1}, {end, p3}} } }
// Determine if three points form a straight line within a given tolerance for their 2 * squared area // // * p2 // / \ // / \ // / \ // / \ // * p1 ---- * p3 // // The area metric is 2 * the squared norm of the cross product of two edges, requiring no square roots and no divisions // // **params** // + p1 // + p2 // + p3 // + The tolerance // // **returns** // + Whether the triangle passes the test // func threePointsAreCollinear(p1, p2, p3 *vec3.T, tol float64) bool { // find the area of the triangle without using a square root p2mp1 := vec3.Sub(p2, p1) p3mp1 := vec3.Sub(p3, p1) norm := vec3.Cross(&p2mp1, &p3mp1) area := vec3.Dot(&norm, &norm) return area < tol }
func (this Line) SameSide(p0, p1 vec3.T) bool { lineVec := vec3.Sub(&this[1], &this[0]) p0Vec := vec3.Sub(&p0, &this[0]) p1Vec := vec3.Sub(&p1, &this[0]) cp0 := vec3.Cross(&p0Vec, &lineVec) cp1 := vec3.Cross(&p1Vec, &lineVec) return vec3.Dot(&cp0, &cp1) >= 0 }
// Find the closest point on a ray // // **params** // + point to project // + origin for ray // + direction of ray 1, assumed normalized // // **returns** // + pt func (this Ray) ClosestPoint(pt vec3.T) vec3.T { o2pt := vec3.Sub(&pt, &this.Origin) do2ptr := vec3.Dot(&o2pt, &this.Dir) dirScaled := this.Dir.Scaled(do2ptr) proj := vec3.Add(&this.Origin, &dirScaled) return proj }
func (this Plane) IntersectLine(line Line) *vec3.T { lineVec := vec3.Sub(&line[1], &line[0]) invLineVec := vec3.Sub(&line[0], &line[1]) denom := vec3.Dot(&this.Normal, &invLineVec) if math.Abs(denom) < epsilon { // Line is parallel to plane return nil } numer := vec3.Dot(&this.Normal, &line[0]) + this.Offset t := numer / denom if t < 0 || t > 1 { return nil } lineVec.Scale(t) intersectPt := vec3.Add(&lineVec, &line[0]) return &intersectPt }
func distToSegment(a, b, c *vec3.T) float64 { // check if ac is zero length acv := vec3.Sub(c, a) acl := acv.Length() // subtract b from a var bma = vec3.Sub(b, a) if acl < Tolerance { return bma.Length() } // normalize acv acv.Normalize() // project b - a to acv = p p := vec3.Dot(&bma, &acv) // multiply ac by d = acd acv.Scale(p).Add(a) return vec3.Distance(&acv, b) }
func (this *adaptiveRefinementNode) ShouldDivide(options *adaptiveRefinementOptions, currentDepth int) bool { if currentDepth < options.MinDepth { return true } if currentDepth >= options.MaxDepth { return false } if this.HasBadNormals() { this.FixNormals() // don't divide any further when encountering a degenerate normal return false } normDiff01 := vec3.Sub(this.corners[0].Normal, this.corners[1].Normal) normDiff23 := vec3.Sub(this.corners[2].Normal, this.corners[3].Normal) this.splitVert = normDiff01.LengthSqr() > options.NormTol || normDiff23.LengthSqr() > options.NormTol normDiff12 := vec3.Sub(this.corners[1].Normal, this.corners[2].Normal) normDiff30 := vec3.Sub(this.corners[3].Normal, this.corners[0].Normal) this.splitHoriz = normDiff12.LengthSqr() > options.NormTol || normDiff30.LengthSqr() > options.NormTol if this.splitVert || this.splitHoriz { return true } center := this.Center() for _, corner := range this.corners { diffVec := vec3.Sub(center.Normal, corner.Normal) if diffVec.LengthSqr() > options.NormTol { return true } } return false }
// Find the closest point on a segment // // **params** // + point to project // + first point of segment // + second point of segment // + first param of segment // + second param of segment // // **returns** // + *Object* with u and pt properties func segmentClosestPoint(pt, segpt0, segpt1 *vec3.T, u0, u1 float64) CurvePoint { dif := vec3.Sub(segpt1, segpt0) l := dif.Length() if l < Epsilon { return CurvePoint{u0, *segpt0} } o := segpt0 r := dif.Normalize() o2pt := vec3.Sub(pt, o) do2ptr := vec3.Dot(&o2pt, r) if do2ptr < 0 { return CurvePoint{u0, *segpt0} } else if do2ptr > l { return CurvePoint{u1, *segpt1} } return CurvePoint{ u0 + (u1-u0)*do2ptr/l, vec3.Add(o, r.Scale(do2ptr)), } }
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 a revolved surface // (Corresponds to Algorithm A7.1 from Piegl & Tiller) // // **params** // + center of the rotation axis // + axis of the rotation axis // + angle to revolve around axis // + degree of the generatrix // + control points of the generatrix // + weights of the generatrix // // **returns** // + an object with the following properties: controlPoints, weights, knots, degree func RevolvedSurface(profile *verb.NurbsCurve, center *vec3.T, axis *vec3.T, theta float64) *verb.NurbsSurface { prof_controlPoints := profile.ControlPoints() prof_weights := profile.Weights() var narcs int var knotsU []float64 switch { case theta <= math.Pi/2: { // less than 90 narcs = 1 knotsU = make([]float64, 6+2*(narcs-1)) } case theta <= math.Pi: { // between 90 and 180 narcs = 2 knotsU = make([]float64, 6+2*(narcs-1)) knotsU[3], knotsU[4] = 0.5, 0.5 } case theta <= 3*math.Pi/2: { // between 180 and 270 narcs = 3 knotsU = make([]float64, 6+2*(narcs-1)) knotsU[3], knotsU[4] = 1/3, 1/3 knotsU[5], knotsU[6] = 2/3, 2/3 } default: { // between 270 and 360 narcs = 4 knotsU = make([]float64, 6+2*(narcs-1)) knotsU[3], knotsU[4] = 1/4, 1/4 knotsU[5], knotsU[6] = 1/2, 1/2 knotsU[7], knotsU[8] = 3/4, 3/4 } } dtheta := theta / float64(narcs) // divide the interval into several points j := 3 + 2*(narcs-1) // initialize the start and end knots // keep in mind that we only return the knot vector for thes for i := 0; i < 3; i++ { knotsU[j+i] = 1 } // do some initialization wm := math.Cos(dtheta / 2) sines, cosines := make([]float64, narcs+1), make([]float64, narcs+1) controlPoints := make([][]vec3.T, 2*narcs+1) for i := range controlPoints { controlPoints[i] = make([]vec3.T, len(prof_controlPoints)) } weights := make([][]float64, 2*narcs+1) for i := range weights { weights[i] = make([]float64, len(prof_controlPoints)) } // initialize the sines and cosines var angle float64 for i := 1; i <= narcs; i++ { angle += dtheta cosines[i] = math.Cos(angle) sines[i] = math.Sin(angle) } // for each pt in the generatrix // i.e. for each row of the 2d knot vectors for j := range prof_controlPoints { // get the closest point of the generatrix point on the axis O := rayClosestPoint(prof_controlPoints[j], center, axis) // X is the vector from the axis to generatrix control pt X := vec3.Sub(&prof_controlPoints[j], &O) // radius at that height r := X.Length() // Y is perpendicular to X and axis, and complete the coordinate system Y := vec3.Cross(axis, &X) if r > internal.Epsilon { X.Scale(1 / r) Y.Scale(1 / r) } // the first row of controlPoints and weights is just the generatrix controlPoints[0][j] = prof_controlPoints[j] P0 := prof_controlPoints[j] weights[0][j] = prof_weights[j] // store T0 as the Y vector var T0 = Y var index int // proceed around the circle for i := 1; i <= narcs; i++ { // O + r * cos(theta) * X + r * sin(theta) * Y // rotated generatrix pt var P2 vec3.T if r == 0 { P2 = O } else { xCompon := X.Scaled(r * cosines[i]) yCompon := Y.Scaled(r * sines[i]) offset := xCompon.Add(&yCompon) P2 = vec3.Add(&O, offset) } controlPoints[index+2][j] = P2 weights[index+2][j] = prof_weights[j] // construct the vector tangent to the rotation temp0 := Y.Scaled(cosines[i]) temp1 := X.Scaled(sines[i]) T2 := temp0.Sub(&temp1) // construct the next control pt if r == 0 { controlPoints[index+1][j] = O } else { T0Norm := T0.Normalized() T2Norm := T2.Normalized() inters := intersect.Rays(&P0, &T0Norm, &P2, &T2Norm) T0Scaled := T0.Scaled(inters.U0) P1 := T0Scaled.Add(&P0) controlPoints[index+1][j] = *P1 } weights[index+1][j] = wm * prof_weights[j] index += 2 if i < narcs { P0 = P2 T0 = *T2 } } } return verb.NewNurbsSurfaceUnchecked(2, profile.Degree(), controlPoints, weights, knotsU, profile.Knots()) }