//Calculate the angle between two vectors //This angle should be measured in 360 degrees (Vector3.Angle is measured in 180 degrees) //Should maybe be moved to _Geometry?? //In 3d space [radians] //https://stackoverflow.com/questions/5188561/signed-angle-between-two-3d-vectors-with-same-origin-within-the-same-plane //https://math.stackexchange.com/questions/2906314/how-to-calculate-angle-between-two-vectors-in-3d-with-clockwise-or-counter-clock public static float AngleFromToCCW(MyVector3 from, MyVector3 to, MyVector3 upRef) { //This is only working in 2d space //float angleDegrees = Quaternion.FromToRotation(to.ToVector3(), from.ToVector3()).eulerAngles.y; from = MyVector3.Normalize(from); to = MyVector3.Normalize(to); upRef = MyVector3.Normalize(upRef); float angleRad = AngleBetween(from, to, shouldNormalize: false); //To get 0-2pi (360 degrees) we can use the determinant [a, b, u] = (a x b) dot u //Where u is a reference up vector //Remember that the cross product is not alwayspointing up - it can change to down depending on how the vectors are aligned //Which is why we need a fixed reference up MyVector3 cross = MyVector3.Cross(from, to); float determinant = MyVector3.Dot(MyVector3.Cross(from, to), upRef); //Debug.Log(determinant); if (determinant >= 0f) { return(angleRad); } else { return((Mathf.PI * 2f) - angleRad); } }
//If we have a forward and an up reference vector //So this is not going to work if we have loops //tangent is same as forward public InterpolationTransform(MyVector3 position, MyVector3 tangent, MyVector3 up) { this.position = position; MyVector3 biNormal = MyVector3.Normalize(MyVector3.Cross(up, tangent)); MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(tangent, biNormal)); this.orientation = Quaternion.LookRotation(tangent.ToVector3(), normal.ToVector3()); }
// // Calculate the normal of a clock-wise oriented triangle in 3d space // public static MyVector3 CalculateTriangleNormal(MyVector3 p1, MyVector3 p2, MyVector3 p3, bool shouldNormalize = true) { MyVector3 normal = MyVector3.Cross(p3 - p2, p1 - p2); if (shouldNormalize) { normal = MyVector3.Normalize(normal); } return(normal); }
// // Get transforms (position and orientation) at point t // //The position and the tangent are easy to find //what's difficult to find is the normal because a line doesn't have a single normal //To get the normal in 2d, we can just flip two coordinates in the forward vector and set one to negative //MyVector3 normal = new MyVector3(-forwardDir.z, 0f, forwardDir.x); //In 3d there are multiple alternatives: //You can read about these methods here: //https://pomax.github.io/bezierinfo/#pointvectors3d //Game Programming Gems 2: The Parallel Transport Frame (p. 215) //Unite 2015 - A coder's guide to spline-based procedural geometry https://www.youtube.com/watch?v=o9RK6O2kOKo // // Alternative 1. Fixed up // //Use ref vector to know which direction is up //Is not going to work if we have loops, but should work if you make "2d" roads like in cities skylines so no roller coasters public static MyQuaternion GetOrientation_UpRef(MyVector3 tangent, MyVector3 upRef) { tangent = MyVector3.Normalize(tangent); MyVector3 biNormal = MyVector3.Normalize(MyVector3.Cross(upRef, tangent)); MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(tangent, biNormal)); MyQuaternion orientation = new MyQuaternion(tangent, normal); return(orientation); }
// // Add a triangle to this mesh // //We dont have a normal so we have to calculate it, so make sure v1-v2-v3 is clock-wise public HalfEdgeFace3 AddTriangle(MyVector3 p1, MyVector3 p2, MyVector3 p3, bool findOppositeEdge = false) { MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(p3 - p2, p1 - p2)); MyMeshVertex v1 = new MyMeshVertex(p1, normal); MyMeshVertex v2 = new MyMeshVertex(p2, normal); MyMeshVertex v3 = new MyMeshVertex(p3, normal); HalfEdgeFace3 f = AddTriangle(v1, v2, v3); return(f); }
// // Alternative 3. Rotation Minimising Frame (also known as "Parallel Transport Frame" or "Bishop Frame") // //Gets its stability by incrementally rotating a coordinate system (= frame) as it is translate along the curve //Has to be computed for the entire curve because we need the previous frame (previousTransform) belonging to a point before this point //Is initalized by using "Fixed Up" or "Frenet Normal" public static MyQuaternion GetOrientation_RotationFrame(MyVector3 position, MyVector3 tangent, InterpolationTransform previousTransform) { /* * //This version is from https://pomax.github.io/bezierinfo/#pointvectors3d * //Reflect the known frame onto the next point, by treating the plane through the curve at the point exactly between the next and previous points as a "mirror" * MyVector3 v1 = position - previousTransform.position; * * float c1 = MyVector3.Dot(v1, v1); * * MyVector3 riL = previousTransform.Right - v1 * (2f / c1) * MyVector3.Dot(v1, previousTransform.Right); * * MyVector3 tiL = previousTransform.Forward - v1 * (2f / c1) * MyVector3.Dot(v1, previousTransform.Forward); * * //This gives the next point a tangent vector that's essentially pointing in the opposite direction of what it should be, and a normal that's slightly off-kilter * //reflect the vectors of our "mirrored frame" a second time, but this time using the plane through the "next point" itself as "mirror". * MyVector3 v2 = tangent - tiL; * * float c2 = MyVector3.Dot(v2, v2); * * //Now we can calculate the normal and right vector belonging to this orientation * MyVector3 right = riL - v2 * (2f / c2) * MyVector3.Dot(v2, riL); * * //The source has right x tangent, but then every second normal is flipped * MyVector3 normal = MyVector3.Cross(tangent, right); * * MyQuaternion orientation = new MyQuaternion(tangent, normal); */ //This version is from Game Programming Gems 2: The Parallel Transport Frame //They generate the same result and this one is easier to understand //The two tangents MyVector3 T1 = previousTransform.Forward; MyVector3 T2 = tangent; //You move T1 to the new position, so A is a vector going from the new position MyVector3 A = MyVector3.Cross(T1, T2); //This is the angle between T1 and T2 float alpha = Mathf.Acos(MyVector3.Dot(T1, T2) / (MyVector3.Magnitude(T1) * MyVector3.Magnitude(T2))); //Now rotate the previous frame around axis A with angle alpha MyQuaternion F1 = previousTransform.orientation; MyQuaternion F2 = MyQuaternion.RotateQuaternion(F1, alpha * Mathf.Rad2Deg, A); MyQuaternion orientation = F2; return(orientation); }
// // Alternative 2. Frenet normal (also known as Frenet Frame) // //Use the tagent we have and a tangent next to it //Works in many cases (but sometimes the frame may flip because of changes in the second derivative) public static MyQuaternion GetOrientation_FrenetNormal(MyVector3 tangent, MyVector3 secondDerivativeVec) { MyVector3 a = MyVector3.Normalize(tangent); //What a next point's tangent would be if the curve stopped changing at our point and just had the same derivative and second derivative from that point on MyVector3 b = MyVector3.Normalize(a + secondDerivativeVec); //A vector that we use as the "axis of rotation" for turning the tangent a quarter circle to get the normal MyVector3 r = MyVector3.Normalize(MyVector3.Cross(a, b)); //The normal vector should be perpendicular to the plane that the tangent and the axis of rotation lie in MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(r, a)); MyQuaternion orientation = new MyQuaternion(tangent, normal); return(orientation); }
// // Calculate the center of circle in 3d space given three coordinates // //From https://gamedev.stackexchange.com/questions/60630/how-do-i-find-the-circumcenter-of-a-triangle-in-3d public static MyVector3 CalculateCircleCenter(MyVector3 a, MyVector3 b, MyVector3 c) { MyVector3 ac = c - a; MyVector3 ab = b - a; MyVector3 abXac = MyVector3.Cross(ab, ac); //This is the vector from a to the circumsphere center MyVector3 toCircumsphereCenter = MyVector3.Cross(abXac, ab) * Mathf.Pow(MyVector3.Magnitude(ac), 2f); toCircumsphereCenter += MyVector3.Cross(ac, abXac) * Mathf.Pow(MyVector3.Magnitude(ab), 2f); toCircumsphereCenter *= (1f / (2f * Mathf.Pow(MyVector3.Magnitude(abXac), 2f))); float circumsphereRadius = MyVector3.Magnitude(toCircumsphereCenter); //The circumsphere center becomes MyVector3 ccs = a + toCircumsphereCenter; return(ccs); }
//Remove flat tetrahedrons (a vertex in a triangle) private static bool RemoveFlatTetrahedrons(HalfEdgeData3 meshData, Normalizer3 normalizer = null) { HashSet <HalfEdgeVertex3> vertices = meshData.verts; bool foundFlatTetrahedron = false; foreach (HalfEdgeVertex3 vertex in vertices) { HashSet <HalfEdge3> edgesGoingToVertex = vertex.GetEdgesPointingToVertex(meshData); if (edgesGoingToVertex.Count == 3) { //Find the vertices of the triangle covering this vertex clock-wise HalfEdgeVertex3 v1 = vertex.edge.v; HalfEdgeVertex3 v2 = vertex.edge.prevEdge.oppositeEdge.v; HalfEdgeVertex3 v3 = vertex.edge.oppositeEdge.nextEdge.v; //Build a plane MyVector3 normal = MyVector3.Normalize(MyVector3.Cross(v3.position - v2.position, v1.position - v2.position)); Plane3 plane = new Plane3(v1.position, normal); //Find the distance from the vertex to the plane float distance = _Geometry.GetSignedDistanceFromPointToPlane(vertex.position, plane); distance = Mathf.Abs(distance); if (distance < FLAT_TETRAHEDRON_DISTANCE) { //Debug.Log("Found flat tetrahedron"); Vector3 p1 = normalizer.UnNormalize(v1.position).ToVector3(); Vector3 p2 = normalizer.UnNormalize(v2.position).ToVector3(); Vector3 p3 = normalizer.UnNormalize(v3.position).ToVector3(); TestAlgorithmsHelpMethods.DebugDrawTriangle(p1, p2, p3, normal.ToVector3(), Color.blue, Color.red); foundFlatTetrahedron = true; //Save the opposite edges HashSet <HalfEdge3> oppositeEdges = new HashSet <HalfEdge3>(); oppositeEdges.Add(v1.edge.oppositeEdge); oppositeEdges.Add(v2.edge.oppositeEdge); oppositeEdges.Add(v3.edge.oppositeEdge); //Remove the three triangles foreach (HalfEdge3 e in edgesGoingToVertex) { meshData.DeleteFace(e.face); } //Add the new triangle (could maybe connect it ourselves) HalfEdgeFace3 newTriangle = meshData.AddTriangle(v1.position, v2.position, v3.position, findOppositeEdge: false); meshData.TryFindOppositeEdge(newTriangle.edge, oppositeEdges); meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge, oppositeEdges); meshData.TryFindOppositeEdge(newTriangle.edge.nextEdge.nextEdge, oppositeEdges); break; } } } return(foundFlatTetrahedron); }