I need a little help with maths for drawing lines between 2 points on a sphere. I have a 3d globe and some markers on it. I need to draw curved line from point 1 to point 2. I managed to draw lines from point to point with LineRenderer, but they are drawn with the wrong angle and I can't figure out, how to implement lines that go at the right angle. The code by far:
public static void DrawLine(Transform From, Transform To){
float count = 12f;
LineRenderer linerenderer;
GameObject line = new GameObject("Line");
linerenderer = line.AddComponent<LineRenderer>();
var points = new List<Vector3>();
Vector3 center = new Vector3(
(From.transform.position.x + To.transform.position.x) / 2f,
(From.transform.position.y + To.transform.position.y) ,
(From.transform.position.z + To.transform.position.z) / 2f
);
for (float ratio = 0; ratio <= 1; ratio += 1 / count)
{
var tangent1 = Vector3.Lerp(From.position, center, ratio);
var tangent2 = Vector3.Lerp(center, To.position, ratio);
var curve = Vector3.Lerp(tangent1, tangent2, ratio);
points.Add(curve);
}
linerenderer.positionCount = points.Count;
linerenderer.SetPositions(points.ToArray());
}
So what I have now is creepy lines rising above along y axis:
What should I take into account to let lines go along the sphere?
I suggest you to find the normal vector of your two points with a cross product (if your sphere is centered at the origin) and then normalize it to use it as a rotation axis for a rotation using quaternions. To make the interpolations, you can simply rotate the first point around this vector with an angle of k * a where k is a parameter from 0 to 1 and a is the angle between your first two vectors which you can find with the acos() of the dot product of your two normalized points
EDIT : I thought about a much easier solution (again, if the sphere is centered) : you can do a lerp between your two vectors and then normalize the result and multiply it by the radius of the sphere. However, the spacings between the resulting points wont be constant, especially if they are far from each other.
EDIT 2 : you can fix the problem of the second solution by using a function instead of a linear parameter for the lerp : f(t) = sin(t*a)/sin((PI+a*(1-2*t))/2)/dist(point1, point2) where a is the angle between the two points.
Related
Working in Unity
I have a working solution using raycasts and a sphere collider, but would like to understand how to accomplish the same result using maths alone.
Scenario is as such:
a) I have a thoretical sphere
b) I project a line from any point within the sphere along any path/direction (both of which are expressed as a Vector3s)
c) I would like to determine the point (also a Vector3) at which the path intersects with the surface of the sphere, as if detected by a raycast returning point data.
I am familiar with the use of COS and SIN plotting points on a 2D plane, but not in 3 dimensions.
Hopefully my description is clear enough.
Any help would be most appreciated.
Let sphere is described by equation (where cx, cy, cz is sphere center, R is radius)
(x-cx)^2+(y-cy)^2+(z-cz)^2 = R^2
Point P0, direction vector is dir, so parameteric ray equation is
R = P0 + t * dir
or in coordinates
x = p0.x + t * dir.x
y = p0.y + t * dir.y
z = p0.z + t * dir.z
Substitute these expressions into sphere equation, solve resulting quadratic equation for unknown parameter t.
(p0x+t*dirx-cx)^2+.... = R^2
p0x^2+t^2*dirx^2+cx^2+2*p0x*t*dirx-2*p0x*cx-2*t*dirx*cx +... = R^2
t^2*(dirx^2+diry^2+dirz^2) +
t*(2*p0x*dirx-2*dirx*cx+2*p0y*diry-2*diry*cy+2*p0z*dirz-2*dirz*cz)
+ (p0x^2+p0y^2+p0z^2+cx^2+cy^2+cz^2-R^2) = 0
This is quadratic equation for unknown t
You might get 0, 1, or 2 solution for cases: ray does not intersect sphere, ray touches sphere, ray (line) intersects sphere in two points. For point P0 inside sphere one root will be negative, ignore it.
After that put t value into coordinate equations and get intersection point.
I can't figure out a way to do this. I have a list of vector2 points and I need all the points which are inside that polygon with a x distance.
So I have a List of Green points and looking for a List of Red points that have a x distance from respective green points.
I am thinking of getting 2 imaginary points, 1 unity towards the previous and next point.
Then moving towards the center of that 2 points by x distance. But then if the inter angle is not 90 then it will move outside of the polygon.
Vector2 me = point; // point
Vector2 next = n_point; // on left
Vector2 previous = p_point; // on right
//Debug.DrawLine(me, (me - next), Color.green);
// 2 points ep1 & ep2
Vector2 center = Vector2.Lerp(ep1,ep2, 0.5f);
Vector2 finalpoint = Vector2.Lerp(me,center,0.1f); //move towards center
I think I am overthinking this. Is there a super-easy way to do this?
Assuming that all the edges are either horizontal or vertical I would simply consider each possible case separately.
Get the direction vectors.
Vector2 from = me - previous;
Vector2 to = next - me;
I also assume that there is always a turn. I.e., if from is horizontal, then to is vertical and vice versa. Either x or y is 0.0f and the other coordinate is not zero.
I also assume that the x-axis points to the right and the y-axis upwards.
Assuming points are listed clock-wise.
float x, y;
if (from.x > 0.0f) { // from points to the right
y = me.y - distance;
if (to.y > 0.0f) x = me.x + distance else x = me.x - distance;
} else if (from.x < 0.0f) { // from points to the left
y = me.y + distance;
if (to.y > 0.0f) x = me.x + distance else x = me.x - distance;
} else if (from.y > 0.0f) { // from points upwards
x = me.x + distance;
if (to.x > 0.0f) y = me.y - distance else y = me.y + distance;
} else { // from.y < 0.0f, points downwards
x = me.x - distance;
if (to.x > 0.0f) y = me.y - distance else y = me.y + distance;
}
Vector2 me_inner = new Vector2(x, y);
I hope I got all the signs right.
There are two methods that spring to mind
Option1:
For each line define a normal, i.e. a perpendicular line pointing outward
Define a normal for each vertex as the average of the normals of the lines the vertex is part of.
Move the vertex X units along the normal.
This is fairly easy to implement, but may have problems with self-intersection for some kinds of geometry.
Option2:
For each line define a normal, i.e. a perpendicular line pointing outward
Move each line-segment X Units along the normal.
for each sequential pair of line segments determine if:
the two line segments intersect, if so, use the intersection point as the vertex. i.e. add the intersection point into your point-list.
If they do not intersect, insert a new line segment between the start and end point of the lines. i.e. Insert both start and end vertex to your point-list.
This should handle self-intersection better, but there might still be problem-cases. And it a bit more cumbersome to implement. It somewhat depend on how exact you need the new line positioned, and well it should handle different kinds of geometry.
Hi,
I found a large number of references but without being able to adapt them to my needs.
As per attached figures I have my character in a given position. Below the character's feet is a new plane (). With the mouse wheel I move the character up along the Y axis and the plane moves with it. Then I drag the character to any position and I join the three vector3s with Gizmos lines. Now I need to know the slope in degrees between the starting point (the red point) and the new position of the character. I tried to use Vector3.Angle or Atan2 and many examples found around but all return different values when you rotate the character despite the slope is always the same. For example charAngle = Vector3.Angle (initialCharPos - character.transform.position, Vector3.left) returns the correct value only in that certain direction and I can get the 4 points left, right, forward, back. But for directions other than these? I was wondering if for each of the 360 points it is necessary to make checks based on the direction or if there is a faster way to get this value.
You can use Vector3.Angle, you just need to take it between the down direction & the direction from the new feet position to the start feet position, and subtract the result from 90:
Vector3 newFeetPosition;
Vector3 startFeetPosition;
// direction of "down", could be different in a zero g situation for instance
Vector3 downDirection = Vector3.down:
float slopeDegrees = 90f - Vector3.Angle(newFeetPosition - startFeetPosition, downDirection);
If you need the rise/run for other reasons, you can get them in the process of calculating the angle yourself using vector math:
Vector3 newFeetPosition;
Vector3 startFeetPosition;
// direction of "up", could be different in a zero g situation for instance
Vector3 upDirection = Vector3.up:
Vector3 feetDiff = newFeetPosition - startFeetPosition:
float riseMagnitude = Vector3.Dot(feetDiff, upDirection);
Vector3 riseVector = riseMagnitude * upDirection;
float runMagnitude = (feetDiff - riseVector).magnitude;
float slopeDegrees = Mathf.Rad2Deg * Mathf.Atan2(riseMagnitude, runMagnitude);
I'm having an issue where I can't figure out the algorithm to find out the destination point based on objects rotation and the amount to move. I have to move to the direction of my rotation a certain amount, but I don't know how to calculate the destination point I end up being at. Example:
Object location = (0, 0)
Object rotation = 45
Amount to move = 4
with these variables the destination point would be (2.5, 2.5)
Example 2:
Object location = (0, 0)
Object rotation = 0
Amount to move = 4
and with these it would be (0, 4)
The problem is, I don't know how to calculate the destination point when I know those variables. I need an algorithm that will calculate the destination point, can somebody help with this? :)
Regards, Tuukka.
If this is a strictly algorithmic question where you want to calculate the destination point (i.e. no game object to move around, but abstract data), you can do this:
Consider the two-dimensional plane in cartesian coordinates, (i.e. the standard x/y system). Let O be an object at point (0,0). From your "destination point" (2.5, 2.5) I can assume that you want the following thing:
So 45° is the angle and 4 (amount to move) is the length of the line segment you want to move along. Starting from (0,0), this end point can be calculated using sine and cosine by using the formula for the polar representation of a point:
But actually, that image is wrong, which we'll see in the following computation. If the movement is along the line with a slope angle of 45°, you'd land a little bit elsewhere.
Anyways, for this example, alpha would be 45° which is pi/4 in radians (you get this by dividing by 180 and multiplying with pi), and the radius r would be 4 (the amount we want to move), so we'd have calculated the destination point as:
If the point is located anywhere in the room (not at (0,0) but at (x_0, y_0)), then you can still add it as an offset:
So in code you'd write:
public static Vector2 ComputeDestination(Vector2 origin, float amountToMove, float angle)
{
//convert degrees to radians
var rad = angle * Mathf.Deg2Rad;
//calculate end point
var end_point = origin + amountToMove * new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));
return end_point;
}
float homMuchToMove = 4f;
float angle = 45f;
float pointX = Mathf.Cos (ConvertToRadians (angle)) * homMuchToMove;
float pointY = Mathf.Sin (ConvertToRadians (angle)) * homMuchToMove;
public float ConvertToRadians(float angle)
{
return (Mathf.PI / 180f) * angle;
}
For these values you will get both points at 2.828427f
Basically what I'm trying to do is shade a 2D heightmap using a very very basic raycasting system that basically just checks if the ray is intercepted before it should be to shade it. However it's not working correctly and I've been banging my head for several hours now on this so I figured it couldn't hurt to turn it over to you guys, because I think it's probably something either so blindingly obvious that I won't see it or so complex that I'll never wrap my head around it.
I have a map like this:
And the raycasting is giving me this (keep in mind it's just debug colors; red is ray interception, but before intended position (so shading), blue would be ray interception in the correct place (so highlights or just as-is), and yellow means that point had no ray interaction at all before the while loop cut-out).
The result should be with red on backfacing slopes and areas behind large mountains (shadows) and blue on sun-facing slopes (highlights). There should not be any yellow. So this image indicates that either all of the rays are hitting the wrong place, or the rays are being intersected ALWAYS somewhere else before they reach their target, which is impossible.
At this point I highly suspect the problem is with my trig.
Here's the Ray class:
class Ray
{
public Vector2 Position;
public Vector2 Direction; // Think in XZ coordinates for these (they are on a perpendicular plane to the heightmap)
// Angle is angle from horizon (I think), and height is height above zero (arbitrary)
public float Angle, Height;
private TerrainUnit[,] Terrainmap;
private float U, V;
public Ray(ref TerrainUnit[,] Terrainmap, float height, float angle)
{
this.Terrainmap = Terrainmap;
this.Angle = angle;
this.Height = this.V = height;
// Create new straight vector
this.Direction = new Vector2(0, 1);
// Rotate it to the values determined by the angle
this.Direction = Vector2.Transform(Direction, Matrix.CreateRotationX(Angle));
//this.Direction = new Vector2((float)Math.Sin(angle), -(float)Math.Cos(angle));
// Find the horizontal distance of the origin-destination triangle
this.U = V / (float)Math.Tan(Angle);
// Bleh just initialize the vector to something
this.Position = new Vector2(U, V);
}
public void CastTo(int x, int y)
{
// Get the height of the target terrain unit
float H = (float)Terrainmap[x, y].Height;
// Find where the ray would have to be to intersect that terrain unit based on its angle and height
Position = new Vector2(x - U, H + V);
float Z = 1000 * (float)Terrainmap[0, y].Height;
// As long as the ray is not below the terrain and not past the destination point
while (Position.Y > Z && Position.X <= x)
{
// If the ray has passed into terrain bounds update Z every step
if (Position.X > 0) Z = 1000 * (float)Terrainmap[(int)Position.X, y].Height;
Position.X += Direction.X;
Position.Y += Direction.Y;
}
Terrainmap[x, y].TypeColor = Color.Yellow;
if ((int)Position.X == x) Terrainmap[x, y].TypeColor = Color.Blue;
else Terrainmap[x, y].TypeColor = Color.Red;
}
}
Also just as a formality, the function that is casting each ray and how I am calling that:
if (lighting) CastSunRays(1f, MathHelper.PiOver4);
private void CastSunRays(float height, float angle)
{
Ray ray = new Ray(ref Terrainmap, height, angle);
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
ray.CastTo(x, y);
}
I ended up using a much simpler approach with Bresenham's Line Algorithm to find the intercept point; I imagine it's much faster and more efficient than the way I was trying to do it would have been.
My guess is that when your Direction vector is applied to Position, it oversteps the lower limit (Position.Y > -1) before it has a chance to hit the surface (Position.Y <= Terrainmap[(int)Position.X, y].Height).
You could try to decrease the lower limit, or re-order your if/while tests.
Another problem might be that the Direction Vector is too large in comparison to your height-range. The distance between two neighboring pixels is 1, while the whole range of height differences is contained in the range (-1,1). This gives a very flat surface from the ray-casters point of view. When the Direction vector is applied to the Position vector is takes a relatively small step over the length, and a relatively large step over the height.
#Maltor: I actually wanted to comment your own answer, but due to my reputation am not currently able to.
I also used the bresenham's line approach and decreased calculation time to 1/10!
A running example of that can be viewed at my github project TextureGenerator-Online.
The terrain tool uses this approach.
See function setTerrainShadow() at tex_terrain.js