/// <summary> /// Inspector - move a position by its velocity. /// </summary> /// <param name="position">The position.</param> /// <param name="velocity">A velocity vector perpendicular to the position. The magnitude is the radians to rotate (can be zero or greater).</param> /// <returns>The mext position after adjusting by velocity.</returns> public static Vector3 move(Vector3 position, Vector3 velocity) { float speed_in_radians = velocity.magnitude; Vector3 direction = velocity.normalized; return(PlanetariaMath.spherical_linear_interpolation(position, direction, speed_in_radians)); }
/// <summary> /// Mutator - Make sure the face index is in range [0,6); UV coordinate normalization handled in UVCoordinate /// </summary> private void normalize() { if (6 <= face_index_variable || face_index_variable < 0) { face_index_variable = (int)PlanetariaMath.modolo_using_euclidean_division(face_index_variable, 6); // HACK: CONSIDER: would anyone ever enter something like 2^31 in here? } }
// Methods (non-Public) /// <summary> /// Mutator - Wrap UV coordinates so that neither value is outside of [0,1]. /// </summary> private void normalize() { if (data_variable.x < 0 || data_variable.x > 1) { data_variable.x = PlanetariaMath.modolo_using_euclidean_division(data_variable.x, 1); // TODO: does this work? } else if (data_variable.x == 0) { data_variable.x = Precision.just_above_zero; } else if (data_variable.x == 1) { data_variable.x = Precision.just_below_one; } if (data_variable.y < 0 || data_variable.y > 1) { data_variable.y = PlanetariaMath.modolo_using_euclidean_division(data_variable.y, 1); } else if (data_variable.y == 0) { data_variable.y = Precision.just_above_zero; } else if (data_variable.y == 1) { data_variable.y = Precision.just_below_one; } }
private static float max_error_location(Arc arc, float begin_point_angle, float end_point_angle) { OctahedronUVCoordinates begin_point = new NormalizedCartesianCoordinates(arc.position(begin_point_angle)); OctahedronUVCoordinates end_point = new NormalizedCartesianCoordinates(arc.position(end_point_angle)); float begin = begin_point_angle; float end = end_point_angle; // 2) use binary / bisection search to find the point of maximal error while (end - begin > Precision.delta) { float midpoint = (begin + end) / 2; OctahedronUVCoordinates left_midpoint = new NormalizedCartesianCoordinates(arc.position(midpoint - Precision.delta)); OctahedronUVCoordinates right_midpoint = new NormalizedCartesianCoordinates(arc.position(midpoint + Precision.delta)); float error_left = PlanetariaMath.point_line_distance(begin_point.data, end_point.data, left_midpoint.data); float error_right = PlanetariaMath.point_line_distance(begin_point.data, end_point.data, right_midpoint.data); if (error_left < error_right) //error begin should be replaced since it has less error { begin = midpoint; } else //error end should be replaced since it has less error { end = midpoint; } } return((begin + end) / 2); // return location of max error }
/// <summary> /// Inspector - Get the normal at a particular angle. /// </summary> /// <param name="angle">The angle in radians along the arc.</param> /// <param name="extrusion">The radius to extrude.</param> /// <returns>A normal on the arc.</returns> public Vector3 normal(float angle, float extrusion = 0f) // TODO: delegate to position() [bug-prone when adding PI/2] { if (curvature == ArcType.ConcaveCorner) // Concave corners are "inside-out" { extrusion *= -1; } float actual_elevation = arc_latitude + extrusion; if (actual_elevation >= -Mathf.PI / 2) { Vector3 equator_position = PlanetariaMath.spherical_linear_interpolation(forward_axis, right_axis, angle); Vector3 result = PlanetariaMath.spherical_linear_interpolation(equator_position, center_axis, actual_elevation + Mathf.PI / 2); return(curvature == ArcType.ConcaveCorner ? -result : result); } else // if (actual_elevation < -Mathf.PI/2) // Primarily used for concave corners { actual_elevation += Mathf.PI / 2; actual_elevation /= Mathf.Cos(half_angle); actual_elevation -= Mathf.PI / 2; if (curvature == ArcType.ConcaveCorner || curvature == ArcType.ConvexCorner) // Concave corners are "inside-out" { angle *= -1; } Vector3 normal_position = PlanetariaMath.spherical_linear_interpolation(forward_axis, center_axis, actual_elevation - Mathf.PI / 2); Vector3 result = PlanetariaMath.spherical_linear_interpolation(normal_position, right_axis, angle); return(curvature == ArcType.ConvexCorner ? -result : result); } }
// Preview rendering of this function is non-trivial, since ephemeral edges only work for the most recent edge. public static PlanetariaShape create_equilateral(PlanetariaShape shape, Vector3 center, Vector3 vertex, int faces, PlanetariaShape.AppendMode permanence = PlanetariaShape.AppendMode.OverwriteWithPermanent) { if (faces > 0 && center != vertex) { if (faces >= 2) { Vector3 forward = center.normalized; Vector3 right = Vector3.ProjectOnPlane(vertex, forward).normalized; Vector3 up = Vector3.Cross(forward, right).normalized; float phi = Vector3.Angle(vertex, center) * Mathf.Deg2Rad; List <Vector3> vertices = new List <Vector3>(); for (float face_index = 0; face_index < faces; ++face_index) { Vector3 equatorial_position = PlanetariaMath.spherical_linear_interpolation(right, up, -(face_index / faces) * (Mathf.PI * 2)); Vector3 final_position = PlanetariaMath.spherical_linear_interpolation(forward, equatorial_position, phi); vertices.Add(final_position); } List <SerializedArc> polygon = new List <SerializedArc>(); for (int face_index = 0; face_index < faces; ++face_index) { Vector3 start_point = vertices[face_index]; Vector3 end_point = vertices[(face_index + 1) % faces]; polygon.Add(ArcFactory.line(start_point, end_point)); } shape.append(polygon, permanence); return(shape); } else // create a circle with given radius { // first_vertex is circle start Vector3 right = Vector3.Cross(center, vertex).normalized; Vector3 mirror = Vector3.Cross(center, right).normalized; Vector3 hidden_vertex = Vector3.Reflect(vertex, mirror).normalized; // opposite end of circle start Vector3 first_up = Vector3.Cross(vertex, right).normalized; Vector3 first_tangent = -Vector3.Cross(first_up, vertex).normalized; Vector3 second_up = -Vector3.Cross(hidden_vertex, right).normalized; Vector3 second_tangent = -Vector3.Cross(second_up, hidden_vertex).normalized; SerializedArc upper_circle = ArcFactory.curve(vertex, first_tangent, hidden_vertex); SerializedArc lower_circle = ArcFactory.curve(hidden_vertex, second_tangent, vertex); // TODO: this entire function can be replaced now (with the circle generator) shape.append(new List <SerializedArc>() { upper_circle, lower_circle }, permanence); return(shape); } } shape.append(new List <SerializedArc>(), permanence); return(shape); }
/// <summary> /// Inspector - Finds the point at "angle" extruded "extrusion" along "local_angle" direction. /// </summary> /// <param name="arc">The arc (used to determine positions and relative angles).</param> /// <param name="angle">The angle along the arc path. Range: [-arc.angle()/2, +arc.angle()/2]</param> /// <param name="local_angle">The secant angle relative to the arc at position("angle"). Range: [0, 2PI]</param> /// <param name="extrusion">The distance along "local_angle" to extrude.</param> /// <returns>The relative position after extruding the point at "angle" by "extrusion" along "local_angle".</returns> public static Vector3 relative_point(Arc arc, float angle, float local_angle, float extrusion) { Vector3 from = arc.position(angle); Vector3 local_direction = Bearing.bearing(arc.position(angle), arc.normal(angle), local_angle); Vector3 to = PlanetariaMath.spherical_linear_interpolation(from, local_direction, extrusion); return(to); }
private void aerial_move(float delta) { Vector3 next_position = PlanetariaMath.spherical_linear_interpolation(get_position(), velocity.normalized, delta); // Note: when velocity = Vector3.zero, it luckily still returns "position" intact. Vector3 next_velocity = PlanetariaMath.spherical_linear_interpolation(get_position(), velocity.normalized, delta + Mathf.PI / 2); transform.position = next_position; velocity = next_velocity.normalized * velocity.magnitude; // FIXME: I thought this was numerically stable, but it seems to create more energy. //velocity = Vector3.ProjectOnPlane(velocity, get_position()); // TODO: CONSIDER: ensure velocity and position are orthogonal - they seem to desynchronize //Debug.DrawRay(get_position(), velocity, Color.green); // draw new velocity (not old one) }
/// <summary> /// Mutator - Normalizes Cartesian vector using Manhattan distance /// </summary> private void normalize() { float length = PlanetariaMath.manhattan_distance(Vector3.zero, data_variable); float absolute_error = Mathf.Abs(length - 1); if (absolute_error > Precision.tolerance) { data_variable /= length; } }
protected override void initialize() { Camera camera = this.GetComponentInChildren <Camera>() as Camera; shutter_edges = new GameObject[edges]; screen_height = PlanetariaMath.cone_radius(PlanetariaCamera.near_clip_plane, camera.fieldOfView * Mathf.Deg2Rad) * 2; screen_width = screen_height * camera.aspect; for (int edge_index = 0; edge_index < edges; ++edge_index) { shutter_edges[edge_index] = (GameObject)Instantiate(Resources.Load("BlinkShutter"), new Vector3(0, screen_height * Mathf.Cos(edge_index * Mathf.PI), PlanetariaCamera.near_clip_plane + Precision.tolerance), Quaternion.Euler(0, 0, edge_index * 180f), camera.transform); shutter_edges[edge_index].transform.localScale = new Vector3(screen_width, screen_height, 1); } }
/*public static implicit operator OctahedronUVCoordinates(NormalizedSphericalCoordinates spherical) * { * return (NormalizedCartesianCoordinates) spherical; * }*/ /// <summary> /// Mutator - Wrap elevation and azimuth so they are within [0, PI] and [0, 2*PI) respectively. /// </summary> private void normalize() // FIXME: verify - it's been wrong until now at least { if (data_variable.x < -Mathf.PI / 2 || data_variable.x > +Mathf.PI / 2) { data_variable.x = PlanetariaMath.modolo_using_euclidean_division(data_variable.x + Mathf.PI / 2, 2 * Mathf.PI); // modulo is undefined behavior with negative numbers if (data_variable.x > Mathf.PI) // result must be positive (due to euclidean division) { data_variable.x = 2 * Mathf.PI - data_variable.x; data_variable.y += Mathf.PI; // going past the pole (to the opposite hemisphere) changes the azimuth } data_variable.x -= Mathf.PI / 2; } if (data_variable.y < 0 || data_variable.y >= 2 * Mathf.PI) { data_variable.y = PlanetariaMath.modolo_using_euclidean_division(data_variable.y, 2 * Mathf.PI); } }
public static void draw_grid() { UnityEditor.Handles.color = Color.white; for (float row = 1; row <= EditorGlobal.self.rows; ++row) // equator lines { float angle = Mathf.PI * row / (EditorGlobal.self.rows + 1); float radius = Mathf.Sin(angle); Vector3 center = Vector3.down * Mathf.Cos(angle); UnityEditor.Handles.DrawWireDisc(center, Vector3.up, radius); } for (float column = 0; column < EditorGlobal.self.columns; ++column) // time zone lines { float angle = Mathf.PI * column / EditorGlobal.self.columns; Vector3 normal = PlanetariaMath.spherical_linear_interpolation(Vector3.forward, Vector3.right, angle); UnityEditor.Handles.DrawWireDisc(Vector3.zero, normal, 1); } }
/// <summary> /// Inspector - Get the position at a particular angle. /// </summary> /// <param name="angle">The angle in radians along the arc.</param> /// <param name="extrusion">The radius to extrude.</param> /// <returns>A position on the arc.</returns> public Vector3 position(float angle, float extrusion = 0f) { if (curvature == ArcType.ConcaveCorner) // Concave corners are "inside-out" { extrusion *= -1; } float actual_elevation = arc_latitude + extrusion; if (actual_elevation >= -Mathf.PI / 2) { Vector3 equator_position = PlanetariaMath.spherical_linear_interpolation(forward_axis, right_axis, angle); return(PlanetariaMath.spherical_linear_interpolation(equator_position, center_axis, actual_elevation)); } else // if (actual_elevation < -Mathf.PI/2) // Primarily used for concave corners { actual_elevation += Mathf.PI / 2; actual_elevation /= Mathf.Cos(half_angle); actual_elevation -= Mathf.PI / 2; return(PlanetariaMath.spherical_linear_interpolation(forward_axis, center_axis, actual_elevation)); } }
private static void subdivide(Arc arc, float begin_point_angle, float end_point_angle) { float middle_point_angle = max_error_location(arc, begin_point_angle, end_point_angle); Vector3 begin = arc.position(begin_point_angle); Vector3 middle = arc.position(middle_point_angle); Vector3 end = arc.position(end_point_angle); OctahedronUVCoordinates begin_point = new NormalizedCartesianCoordinates(begin); OctahedronUVCoordinates middle_point = new NormalizedCartesianCoordinates(middle); OctahedronUVCoordinates end_point = new NormalizedCartesianCoordinates(end); if (PlanetariaMath.point_line_distance(begin_point.data, end_point.data, middle_point.data) > Precision.threshold) // if the max error is greater than a threshold, recursively add the left and right halves into the list of lines { subdivide(arc, begin_point_angle, middle_point_angle); subdivide(arc, middle_point_angle, end_point_angle); } else { QuadraticBezierCurve curve = new QuadraticBezierCurve(begin_point.data, middle_point.data, end_point.data); VectorGraphicsWriter.set_edge(curve); } }
protected override void initialize() { Camera camera = this.GetComponentInChildren <Camera>() as Camera; shutter_edges = new GameObject[edges]; for (int edge_index = 0; edge_index < edges; ++edge_index) { shutter_edges[edge_index] = (GameObject)Instantiate(Resources.Load("PrimaryEdge"), new Vector3(0, 0, 2 * PlanetariaCamera.near_clip_plane), Quaternion.Euler(0, 0, edge_index * 360f / edges), camera.transform); #if UNITY_EDITOR shutter_edges[edge_index].transform.localScale = Vector3.one * 4 * PlanetariaMath.cone_radius(2 * PlanetariaCamera.near_clip_plane, camera.fieldOfView * Mathf.Deg2Rad) * Mathf.Sqrt(1 + (camera.aspect * camera.aspect)); #else float x = PlanetariaMath.cone_radius(2 * PlanetariaCamera.near_clip_plane, camera.fieldOfView * Mathf.Deg2Rad); float y = x * camera.aspect; float z = 2 * PlanetariaCamera.near_clip_plane; StereoscopicProjectionCoordinates stereoscopic_projection = new NormalizedCartesianCoordinates(new Vector3(x, y, z)); shutter_edges[edge_index].transform.localScale = Vector3.one * stereoscopic_projection.data.magnitude; // FIXME: VR FOV #endif } }
public bool grounded(Vector3 velocity) { return(geometry_visitor.contains(PlanetariaMath.move(geometry_visitor.position(), velocity * Time.deltaTime))); }
/// <summary> /// Inspector (Cache Mutator) - Creates a spherical rectangle UV coordinate set from a point on a unit sphere and a rectangle representing the x/y angles. /// </summary> /// <param name="cartesian">The point on the surface of a unit sphere to be converted.</param> /// <param name="canvas">A Rect (measuring radians) representing the start and stop angles relative to Quaternion.identity. X/Y Range: (-2PI, +2PI).</param> /// <returns>UV Coordinates for a spherical rectangle. Valid X/Y Range: [0, 1], although one axis may be in the (-INF, +INF) range.</returns> public static SphericalRectangleUVCoordinates cartesian_to_spherical_rectangle(Vector3 cartesian, Rect canvas) { // cache the canvas and derivative arcs (so they are not recomputed every time the function is called) cache_spherical_rectangle(canvas); Vector3 equator_projection = PlanetariaMath.project_onto_equator(cartesian, cached_north_hemisphere); Vector3 prime_meridian_projection = PlanetariaMath.project_onto_equator(cartesian, cached_east_hemisphere); float u; // FIXME: keep it DRY (Do not Repeat Yourself) if (Vector3.Dot(cartesian, cached_east_hemisphere) >= 0) // right (East) { float angle; if (Vector3.Dot(cartesian, cached_right_positive_partition) >= 0) { angle = Vector3.Angle(cached_right_biangle_focus, equator_projection) * Mathf.Deg2Rad; } else { angle = Vector3.Angle(-cached_right_biangle_focus, equator_projection) * Mathf.Deg2Rad + Mathf.PI; } angle -= cached_right_start_angle; u = angle / (cached_right_end_angle - cached_right_start_angle); u = 0.5f + u / 2; } else // if (Vector3.Dot(cartesian, cached_east_hemisphere) < 0) // left (West) { float angle; if (Vector3.Dot(cartesian, cached_left_positive_partition) >= 0) { angle = Vector3.Angle(cached_left_biangle_focus, equator_projection) * Mathf.Deg2Rad; } else { angle = Vector3.Angle(-cached_left_biangle_focus, equator_projection) * Mathf.Deg2Rad + Mathf.PI; } angle -= cached_left_start_angle; u = angle / (cached_left_end_angle - cached_left_start_angle); u = 0.5f - u / 2; } float v; if (Vector3.Dot(cartesian, cached_north_hemisphere) >= 0) // upper (North) { float angle; if (Vector3.Dot(cartesian, cached_upper_positive_partition) >= 0) { angle = Vector3.Angle(cached_upper_biangle_focus, prime_meridian_projection) * Mathf.Deg2Rad; } else { angle = Vector3.Angle(-cached_upper_biangle_focus, prime_meridian_projection) * Mathf.Deg2Rad + Mathf.PI; } angle -= cached_upper_start_angle; v = angle / (cached_upper_end_angle - cached_upper_start_angle); v = 0.5f + v / 2; } else // if (Vector3.Dot(cartesian, cached_north_hemisphere) < 0) // lower (South) { float angle; if (Vector3.Dot(cartesian, cached_lower_positive_partition) >= 0) { angle = Vector3.Angle(cached_lower_biangle_focus, prime_meridian_projection) * Mathf.Deg2Rad; } else { angle = Vector3.Angle(-cached_lower_biangle_focus, prime_meridian_projection) * Mathf.Deg2Rad + Mathf.PI; } angle -= cached_lower_start_angle; v = angle / (cached_lower_end_angle - cached_lower_start_angle); v = 0.5f - v / 2; } return(new SphericalRectangleUVCoordinates(new Vector2(u, v), canvas)); }