コード例 #1
0
        /// <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));
        }
コード例 #2
0
        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));
        }
コード例 #3
0
 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);
 }
コード例 #4
0
        /// <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));
        }
コード例 #5
0
        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);
        }
コード例 #6
0
        /// <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);
        }
コード例 #7
0
        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];
        }
コード例 #8
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);
        }
コード例 #9
0
 internal static float extrude_distance(SphericalCap cap, float extrusion)
 {
     return(Mathf.Cos(Mathf.Acos(Mathf.Clamp(cap.offset, -1, +1)) - extrusion));
 }
コード例 #10
0
 /// <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());
 }