/// <summary> /// Constructor - envelops a SphericalCap region using focal point of cone/SphericalCap (perfect fit) /// </summary> /// <param name="transformation">An optional Transform that will be used to shift the center (if moved).</param> /// <param name="cap">The SphericalCap that will be encapsulated by a Sphere (collider).</param> /// <returns>A Sphere that can be used as a collider.</returns> /// <details> /// This collider has the special property that all sphere colliders will only collide if the planetaria sphere is intersecting. /// This is because the sphere is formed at the focal point of a cone from the planetaria sphere's tangent lines. /// </details> public static PlanetariaSphereCollider ideal_collider(SphericalCap cap) { float distance = extrude_distance(cap, Precision.collider_extrusion); float tangent = Mathf.Tan(Mathf.Acos(distance)); float secant = 1 / distance; return(new PlanetariaSphereCollider(cap.normal * secant, tangent)); }
public static Vector3[] arc_path_intersections(Arc arc, Vector3 begin, Vector3 end, float extrusion) { SphericalCap arc_circle = arc.floor(extrusion).collider(); SphericalCap path_circle = SphericalCap.cap(Vector3.Cross(begin, end).normalized, 0); Vector3[] intersections = circle_circle_intersections(arc_circle, path_circle); return(valid_arc_intersections(arc, intersections, Quaternion.identity)); }
internal static PlanetariaSphereCollider[] elevation_collider(SphericalCap cap) { if (cap.offset > 1f - Precision.threshold) { return(new PlanetariaSphereCollider[2] { ideal_collider(cap), PlanetariaSphereCollider.always }); } PlanetariaSphereCollider[] colliders = new PlanetariaSphereCollider[2]; colliders[0] = uniform_collider(cap); colliders[1] = uniform_collider(cap.complement()); return(colliders); }
/// <summary> /// Constructor - envelops a SphericalCap region with a size-2 Sphere. /// </summary> /// <param name="transformation">An optional Transform that will be used to shift the center (if moved).</param> /// <param name="cap">The SphericalCap that will be encapsulated by a Sphere (collider).</param> /// <returns>A Sphere that can be used as a collider.</returns> public static PlanetariaSphereCollider uniform_collider(SphericalCap cap) // TODO: FIXME: documentation { // Background 1: // imagine two spheres: // "left" sphere is of radius 1 and placed at the origin (0,0,0). // "right" sphere is of radius 2 and is placed at (3,0,0). // These spheres intersect at a point (1,0,0). // If slid closer together they will intersect at a circle of radius (0,1]. // The goal is to find the distance between spheres to create a collider along a given circle. // Derivation 1: // I created four variables: // left_height = sin(left_angle) // left_distance = 1 - cos(left_angle) // right_height = 2sin(right_angle) // right_distance = 2 - 2cos(right_angle) // While we are trying to find 3 - sum(distances), we know that left_height must equal right_height for the intersection to be valid // Therefore sin(left_angle) = left_height = right_height = 2sin(right_angle) // Since left_angle is the planetaria_radius (because we are dealing with a unit sphere) we can solve for right_angle: // right_angle = arcsin(sin(left_angle)/2) // Now that we know right_angle and left_angle we can solve all formulas: // left_distance = 1 - cos(left_angle) // right_distance = 2 - 2cos(arcsin(sin(left_angle)/2)) // Next we just subtract these differences from 3 (the radii sum): // axial_distance = 3 - [1 - cos(left_angle)] - [2 - 2cos(arcsin(sin(left_angle)/2))] // axial_distance = 3 - 1 + cos(left_angle) - 2 + 2cos(arcsin(sin(left_angle)/2)) // axial_distance = cos(left_angle) + 2cos(arcsin(sin(left_angle)/2)) // which, according to Wolfram Alpha is: cos(left_angle) + sqrt(cos^2(left_angle) + 3) // Check negative values: // Background: // imagine two spheres: // "left" sphere is of radius 1 and placed at the origin (0,0,0). // "right" sphere is of radius 2 and is placed at (1,0,0). // These spheres intersect at a point (-1,0,0). // If slid further away they will intersect at a circle of radius (0,1]. // Testing: // An input of x=-1 should result in axial_distance=1 (the center of the size-2 sphere): // Plugging this into our equation we get x + sqrt(x*x + 3) = -1 + sqrt(-1*-1 + 3) = -1 + sqrt(4) = -1 + 2 = 1 // which is indeed equal to 1 float x = extrude_distance(cap, Precision.collider_extrusion); // collisions can be missed for small objects due to floating point error float axial_distance = x + Mathf.Sqrt(x * x + 3); return(new PlanetariaSphereCollider(cap.normal * axial_distance, 2)); }
public static Vector3[] raycast_intersection(Arc raycast_arc, Arc geometry_arc, float raycast_angle, Quaternion orientation) { SphericalCap relative_geometry_circle = geometry_arc.floor().collider(); if (orientation != Quaternion.identity) { Quaternion arc_to_world = orientation; Debug.DrawRay(Vector3.zero, relative_geometry_circle.normal, Color.green, 1f); relative_geometry_circle = SphericalCap.cap(arc_to_world * relative_geometry_circle.normal, relative_geometry_circle.offset); Debug.DrawRay(Vector3.zero, relative_geometry_circle.normal, Color.cyan, 1f); } Vector3[] intersections = circle_circle_intersections(raycast_arc.floor().collider(), relative_geometry_circle); intersections = valid_arc_intersections(raycast_arc, intersections, Quaternion.identity, raycast_angle); intersections = valid_arc_intersections(geometry_arc, intersections, orientation); return(intersections); }
/// <summary> /// Inspector - Returns intersection information between two circles on a sphere. /// </summary> /// <param name="coordinate_a">A point on a unit sphere and its radius along the surface.</param> /// <param name="coordinate_b">A point on a unit sphere and its radius along the surface.</param> /// <returns> /// If there are zero or infinite solutions, returns an empty array; /// If there are one or two solutions, returns an array with two Cartesian coordinates. /// </returns> public static Vector3[] circle_circle_intersections(SphericalCap a, SphericalCap b) // https://gis.stackexchange.com/questions/48937/calculating-intersection-of-two-circles { float similarity = Vector3.Dot(a.normal, b.normal); if (Mathf.Abs(similarity) > 1f - Precision.tolerance) // ignore points that are 1) equal or 2) opposite (within an error margin) because they will have infinite solutions { return(new Vector3[0]); } float a_radius = Mathf.Abs(Mathf.Acos(a.offset)); // TODO: simplify, elegance, efficiency (this will be one of many bottlenecks) float b_radius = Mathf.Abs(Mathf.Acos(b.offset)); float arc_distance = Mathf.Acos(similarity); float radii_sum = a_radius + b_radius; float radii_difference = Mathf.Abs(a_radius - b_radius); bool adjacent = arc_distance < radii_sum - Precision.tolerance; bool does_not_engulf = radii_difference + Precision.tolerance < arc_distance; bool intersects = does_not_engulf && adjacent; if (!intersects) // solution set will be null { return(new Vector3[0]); } // Instead of thinking of a circle on a globe, it's easier to think of the circle as the intersection of a sphere against the surrounding globe float distance_from_origin_a = Mathf.Cos(a_radius); float distance_from_origin_b = Mathf.Cos(b_radius); // The center should be distance_from_origin away iff similarity = 0, otherwise you have to push the center closer or further float center_fraction_a = (distance_from_origin_a - distance_from_origin_b * similarity) / (1 - similarity * similarity); float center_fraction_b = (distance_from_origin_b - distance_from_origin_a * similarity) / (1 - similarity * similarity); Vector3 intersection_center = center_fraction_a * a.normal + center_fraction_b * b.normal; Vector3 binormal = Vector3.Cross(a.normal, b.normal); float midpoint_distance = Mathf.Sqrt((1 - intersection_center.sqrMagnitude) / binormal.sqrMagnitude); //CONSIDER: rename? // Note: tangential circles (i.e. midpoint_distance = 0) return two intersections (i.e. a secant line) Vector3[] intersections = new Vector3[2]; intersections[0] = intersection_center + midpoint_distance * binormal; intersections[1] = intersection_center - midpoint_distance * binormal; return(intersections); }
public ArcPlanetarium(Arc arc, float radius) { this.arc = arc; this.radius = radius; // cache for early return center = arc.position(0); Vector3 vertex = arc.position(arc.angle() / 2); Vector3 furthest_point = Vector3.RotateTowards(vertex, -center, radius, 0.0f); dot_product_threshold = Vector3.Dot(center, furthest_point); SphericalCap lower = arc.floor(-radius); SphericalCap upper = arc.floor(+radius); center_offset = lower.offset + upper.offset; center_range = Mathf.Abs(upper.offset - lower.offset) / 2; pixel_centroids = new NormalizedCartesianCoordinates[0]; }
internal static PlanetariaSphereCollider boundary_collider(Vector3 center_axis, Vector3 point) { PlanetariaSphereCollider result; SphericalCap cap = SphericalCap.cap(center_axis, point); // create a SphericalCap centered at "center" that captures both corners (2nd implicitly) float real_angle = Vector3.Angle(center_axis, point) * Mathf.Deg2Rad; if (real_angle < Precision.max_sphere_radius) // use a r<=1 sphere { result = PlanetariaArcColliderUtility.ideal_collider(cap); } else // use r=2 sphere { result = PlanetariaArcColliderUtility.uniform_collider(cap); } if (result.radius < Precision.threshold) { return(new PlanetariaSphereCollider(center_axis, 0)); } return(result); }
internal static float extrude_distance(SphericalCap cap, float extrusion) { return(Mathf.Cos(Mathf.Acos(Mathf.Clamp(cap.offset, -1, +1)) - extrusion)); }
/// <summary> /// Inspector - Creates a SphericalCap that represents the floor (at given elevation). /// </summary> /// <param name="extrusion">The radius of the collider touching the floor.</param> /// <returns>A SphericalCap representing the floor. Normal goes "down" - towards floor.</returns> // FIXME: (?) unintuitive normal public SphericalCap floor(float extrusion = 0) { return(SphericalCap.cap(center_axis, Mathf.Sin(arc_latitude + extrusion)).complement()); }