I've been looking for a solution to this for some time now and already have many elements to work with but not really how to piece them together.
Objective: Draw a trail for the player's ship.
So far: Since the ship's direction is unpredictable I have only the previous positions of the player's ship to work with. To draw the trail I could simply draw a pixel (or a texture) at the previous position of the player but this is memory expensive and it doesn't draw curves, it won't achieve a pleasing to the eye curved effect.
I've been looking into Beziers Paths and Cathmull Rom for solutions.
Now I can get the control points for a given point, then from 2 points and 2 control points calculate a curve, from here I make an array of VertexPositionColor with a distance between points to make a triangleStrip from the curve.
These are the main functions I have so far:
public Vector2[] GetControlPoints(Vector2 p0, Vector2 p1, Vector2 p2, float tension = 0.5f)
{
// get length of lines [p0-p1] and [p1-p2]
float d01 = Vector2.Distance(p0, p1);
float d12 = Vector2.Distance(p1, p2);
// calculate scaling factors as fractions of total
float sa = tension * d01 / (d01 + d12);
float sb = tension * d12 / (d01 + d12);
// left control point
float c1x = p1.X - sa * (p2.X - p0.X);
float c1y = p1.Y - sa * (p2.Y - p0.Y);
// right control point
float c2x = p1.X + sb * (p2.X - p0.X);
float c2y = p1.Y + sb * (p2.Y - p0.Y);
return new Vector2[] {new Vector2(c1x, c1y), new Vector2(c2x, c2y) };
}
// Given 2 points and 2 control points
public static VertexPositionColor[] bezierCurve(Vector2 start, Vector2 end, Vector2 c1, Vector2 c2)
{
VertexPositionColor[] points = new VertexPositionColor[SUBDIVISIONS + 2];
float fraction;
for (int i = 0; i < SUBDIVISIONS + 2; i++)
{
fraction = i * (1f / (float)SUBDIVISIONS);
points[i] = new VertexPositionColor(new Vector3((float)((start.X * Math.Pow((1 - fraction), 3))
+(c1.X * 3 * fraction * Math.Pow(1-fraction, 2))
+(c2.X * 3 * Math.Pow(fraction,2) * (1-fraction))
+(end.X * Math.Pow(fraction,3))),
(float)((start.Y * Math.Pow((1 - fraction), 3))
+ (c1.Y * 3 * fraction * Math.Pow(1 - fraction, 2))
+ (c2.Y * 3 * Math.Pow(fraction, 2) * (1 - fraction))
+ (end.Y * Math.Pow(fraction, 3))), 0), UNLIT);
}
return points;
}
/*
* This function treats the curve as a series of straight lines and calculates points on a line perpendicular to each point, resulting in two points THICKNESS appart.
* Requires THICKNESS to be set
*/
public static VertexPositionColor[] curveToStrip(VertexPositionColor[] curve)
{
VertexPositionColor[] strip = new VertexPositionColor[curve.Length * 2];
VertexPositionColor[] new1 = new VertexPositionColor[curve.Length];
VertexPositionColor[] new2 = new VertexPositionColor[curve.Length];
for (int i = 0; i < curve.Length; i++)
{
if (i < curve.Length-1)
{
Vector2 p1 = new Vector2(curve[i].Position.X, curve[i].Position.Y);
Vector2 p2 = new Vector2(curve[i + 1].Position.X, curve[i + 1].Position.Y);
Vector2 perpPoint = perpendicularPoint(p1, p2);
new1[i] = new VertexPositionColor(new Vector3(distanceToPoint(p1, perpPoint, THICKNESS / 2), 0), UNLIT);
new2[i] = new VertexPositionColor(new Vector3(distanceToPoint(p1, perpPoint, -1 * THICKNESS / 2), 0), UNLIT);
}
else
{
Vector2 p1 = new Vector2(curve[i].Position.X, curve[i].Position.Y);
Vector2 p2 = new Vector2(curve[i - 1].Position.X, curve[i - 1].Position.Y);
Vector2 perpPoint = perpendicularPoint(p1, p2);
new1[i] = new VertexPositionColor(new Vector3(distanceToPoint(p1, perpPoint, -1 * THICKNESS / 2), 0), UNLIT);
new2[i] = new VertexPositionColor(new Vector3(distanceToPoint(p1, perpPoint, THICKNESS / 2), 0), UNLIT);
}
}
I thought about calling the functions on the draw phase but this seems very expensive just to make a tiny curve and to draw a bigger Beziers path I imagine it worse. Since I would get a point at each frame, each function would be called to calculate the curve between points just to draw 1 curve of 3 pixels (or less).
How can I proceed? Any suggestions?
I am still a beginner on this kind of stuff!
All this I got from several sources:
CathmullRom
Beziers and Triangle strip
http://www.imagehosting.cz/images/trails.gif
I will just briefly explain how this works:
It is function that receives position, it is called each time you want add next segment of trail.
When function is called it adds two vertices on position, look at tangent vector from previous step, creates normal vector to current position and place vertices along that normal according to trail width.
Next it looks to at previous two vertexes and align them according to average of current and previous tangent, creating trapezoid.
I suggest to leave calculation of fading on GPU (effectively using approach of GPU particles).
If you know velocity vector of object when you are calling update of trail you can use it to optimize that algorithm. Use of dynamic vertex buffer is probably without saying (just use your own vertex format that will include current time at moment when you create those vertices so you can fade it on GPU).
One way could be that you create a list of trails/particles, and you init that on every frame or how much you like. i will try to explain in pseudo code below. i also rotate a bit every trail, and use different size and color of smoke texture, and added a bit of ofsset +- 5 pixels on init.
class Trail
position as vector2d
duration as single
velocity as vector2d
fade as integer = 1
active = true
end class
class Trails
inherits list of Trail
sub Init(position as vector2d, velocity as vector2d)
// add trail to list
end sub
sub Update()
for each trail in me.findAll(function(c) c.active))
position += velocity
fade -= .05 // or some value
end for
me.removeAll(function(c) not(c.active)) // remove from list when unused
end sub
sub Draw()
for each trail in me.findAll(function(c) c.active))
draw(smokeTexture, position, size, rotate, color * trail.fade)
end for
end sub
end class
by this i have achieved this effect, it's barely visible but it gives effect.
Related
For a while now I've been using the following function to rotate a series of Points around a pivot point in various programs of mine.
private Point RotatePoint(Point point, Point pivot, double radians)
{
var cosTheta = Math.Cos(radians);
var sinTheta = Math.Sin(radians);
var x = (cosTheta * (point.X - pivot.X) - sinTheta * (point.Y - pivot.Y) + pivot.X);
var y = (sinTheta * (point.X - pivot.X) + cosTheta * (point.Y - pivot.Y) + pivot.Y);
return new Point((int)x, (int)y);
}
This has always worked great, until I tried to rotate a shape repeatedly by small amounts. For example, this is what I get from calling it for 45° on a rectangular polygon made up of 4 points:
foreach (var point in points)
Rotate(point, center, Math.PI / 180f * 45);
But this is what I get by calling rotate 45 times for 1°:
for (var i = 0; i < 45; ++i)
foreach (var point in points)
Rotate(point, center, Math.PI / 180f * 1)
As long as I call it only once it's fine, and it also seems like it gets gradually worse the lower the rotation degree is. Is there some flaw in the function, or am I misunderstanding something fundamental about what this function does?
How could I rotate repeatedly by small amounts? I could save the base points and use them to update the current points whenever the rotation changes, but is that the only way?
Your Point position measure is off because of the integer rounding generated by the RotatePoint() method.
A simple correction in the method returned value, using float coordinates, will produce the correct measure:
To test it, create a Timer and register its Tick event as RotateTimerTick():
Added a rotation spin increment (see the rotationSpin Field) to emphasize the motion effect.
PointF pivotPoint = new PointF(100F, 100F);
PointF rotatingPoint = new PointF(50F, 100F);
double rotationSpin = 0D;
private PointF RotatePoint(PointF point, PointF pivot, double radians)
{
var cosTheta = Math.Cos(radians);
var sinTheta = Math.Sin(radians);
var x = (cosTheta * (point.X - pivot.X) - sinTheta * (point.Y - pivot.Y) + pivot.X);
var y = (sinTheta * (point.X - pivot.X) + cosTheta * (point.Y - pivot.Y) + pivot.Y);
return new PointF((float)x, (float)y);
}
private void RotateTimerTick(object sender, EventArgs e)
{
rotationSpin += .5;
if (rotationSpin > 90) rotationSpin = 0;
rotatingPoint = RotatePoint(rotatingPoint, pivotPoint, (Math.PI / 180f) * rotationSpin);
Panel1.Invalidate(new Rectangle(new Point(50,50), new Size(110, 110)));
}
private void Panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillEllipse(Brushes.White, new RectangleF(100, 100, 8, 8));
e.Graphics.FillEllipse(Brushes.Yellow, new RectangleF(rotatingPoint, new SizeF(8, 8)));
}
This is the result using float values:
And this is what happens using integer values:
If you want you can use the Media3D to only deal with matrix and simplify the coding. Something as simple as this will work.
public Point3D Rotate(Point3D point, Point3D rotationCenter, Vector3D rotation, double degree)
{
// create empty matrix
var matrix = new Matrix3D();
// translate matrix to rotation point
matrix.Translate(rotationCenter - new Point3D());
// rotate it the way we need
matrix.Rotate(new Quaternion(rotation, degree));
// apply the matrix to our point
point = matrix.Transform(point);
return point;
}
Then you simply call the method and specify the rotation. Lets say you work with 2D (like in your example) and lets assume we work with XY plane so the rotation is in Z. You can do something like :
var rotationPoint = new Point3D(0, 0, 0);
var currentPoint = new Point3D(10, 0, 0);
// rotate the current point around the rotation point in Z by 45 degree
var newPoint = Rotate(currentPoint, rotation, new Vector3D(0, 0, 1), 45d);
I was trying to map the 360 video pixel coordinate to sphere surface coordinate but I couldn't get right result... It just mapped to the wrong position I already know the points of the XY data for 360 video pixels.
how map 2d grid points (x,y) onto sphere as 3d points (x,y,z)
I checked this link and I copied method from this but what I'm getting is not mapped to the desired position.
How can I get radius from the pixels?
I am not sure if I'm passing right radius for imageRadius but I thought it will be circumference/PI to get radius and the video ratio is 4096x2048. I also tried to pass the number 1 because UV is 0-1 but it was not right...
Is Method wrong?
Maybe the method is wrong. I passed random numbers into the imageRadius but couldn't get the right position... If I make X to negative number the seems like little bit closer to result....?
Current Result
https://youtu.be/t0I7Hlb-tbk
It mapped to up right position with the method that I found online...
Project File
https://drive.google.com/a/swordfish-sf.com/file/d/0B45RYzVs0t0_VVdaaHdmNHRWTk0/view?usp=sharing
If somebody can check the Unity project file that will be great...
Current Code
public class mapScript : MonoBehaviour {
public int input = 4098;
float imageRadius = 4098f / Mathf.PI; //2098? 3072? 4098?
float radius;
public GameObject testSphere;
void Start () {
radius = this.transform.localScale.x;
}
void Update () {
imageRadius = input / Mathf.PI;
int currentFrame = (int)this.GetComponent<VideoPlayer>().frame;
testSphere.transform.position = MercatorProjection(mapVals[currentFrame,0],mapVals[currentFrame,1]);
}
Vector3 MercatorProjection(float xVal, float yVal)
{
float lon = (xVal / imageRadius);
float lat = (2 * Mathf.Atan(Mathf.Exp(yVal / imageRadius)) - Mathf.PI / 2);
float calcX = radius * Mathf.Cos(lat) * Mathf.Cos(lon);
float calcY = radius * Mathf.Cos(lat) * Mathf.Sin(lon);
float calcZ = radius * Mathf.Sin(lat);
Vector3 result = new Vector3(calcX,calcY,calcZ);
Debug.Log(result);
return result;
}
float[,] mapVals = new float[,] {
{1969.21f, 928.625f},
{1969.6f, 928.533f},
{1968.92f, 928.825f},
{1968.68f, 929f},
{1968.47f, 929.067f},
{1968.41f, 929.025f},
{1968.48f, 928.992f},
....
};
}
Thank you.
As a side note, the radius is arbitrary. The pixel coordinates only map to the directional coordinates (polar [θ] and azimuthal [ϕ] angles).
We can do this by mapping each pixel to equal θ and ϕ intervals. The diagram below illustrates a low-resolution setup:
Let us adopt the convention that, for an image of with W, ϕ = 0 corresponds to:
Even W: half way between X = floor((W - 1) / 2) and X = ceil((W - 1) / 2)
Odd W: in the middle of the pixel column at X = floor((W - 1) / 2)
The pixel row at Y maps to the equilatitudinal line at θ = (Y + 0.5) / H * π.
To map all pixels in their entirety, let X start at -0.5 instead of 0, and end at W - 0.5; likewise for Y. Since integer coordinates map to the centers of the pixel regions shown above, this allows the whole area of any particular pixel to be addressed. You may need this later on if you plan on doing multi-sampling filtering for e.g. anti-aliasing.
Code:
Vector3 Mercator(float x, float y, int w, int h)
{
// outside of valid pixel region
if (x < -0.5f || x >= w - 0.5f || y < -0.5f || y >= h - 0.5f)
return new Vector3();
float theta = (y + 0.5f) / h * Math.PI;
float phi = ((x + 0.5f) / w - 0.5f) * 2.0 * Math.PI;
float c_t = Math.Cos(theta);
return new Vector3(c_t * Math.Cos(phi), c_t * Math.Sin(phi), Math.Sin(theta));
}
... and multiply the resulting direction vector by any "radius" you like, since it has (basically) nothing to do with the mapping anyway.
The code snippet below generates a curve line and arrow at the end of the path, as seen in the image below in wpf using C#. All you have to do is supply the function a starting and end point. You'll see in the image below that the Red Arrow on the left is incorrectly aligned to the path. The Red Arrow on the right is aligned correctly, however I'm not sure how to calculate the correct orientation? Does anyone have advice or a solution on how i could do this? The green points in the image demonstrate the points being generated in the function.
Thank you.
// Return the shape's path and arrow geometry.
protected override Geometry DefiningGeometry
{
get
{
GeometryGroup group = new GeometryGroup();
// Used for curvey lines
GenerateCurvedLine(group);
//GeneratedCurvedArrow(group);
// Used for straight lines
//GeneratedStraightLine(group);
GenerateArrowHeadGeometry(group);
// Return cached geometry.
return group;
}
}
// Generate the geometry for a curved line connecting the start and end points.
private void GenerateCurvedLine(GeometryGroup geometryGroup)
{
//Calculate points between start and end plugs
Point secondPoint = new Point(this.Start.X, this.Start.Y + 50);
Point thirdPoint = new Point(this.End.X, this.End.Y - 50);
// Build geometry for the curvey line.
PathFigure curvedPath = new PathFigure();
curvedPath.IsClosed = false;
curvedPath.IsFilled = false;
curvedPath.StartPoint = this.Start;
curvedPath.Segments.Add(new BezierSegment(secondPoint, thirdPoint, this.End, true));
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(curvedPath);
geometryGroup.Children.Add(pathGeometry);
}
Generates the Arrow at the end of the path, while orienting it towards the starting point.
// Generate the geometry for the three optional arrow symbol at the end of the path.
private void GenerateArrowHeadGeometry(GeometryGroup geometryGroup)
{
EllipseGeometry ellipse = new EllipseGeometry(this.Start, DotSize, DotSize);
geometryGroup.Children.Add(ellipse);
Vector startDir = this.End - this.Start;
startDir.Normalize();
Point basePoint = this.End - (startDir * ArrowHeadLength);
Vector crossDir = new Vector(-startDir.Y, startDir.X);
Point[] arrowHeadPoints = new Point[3];
arrowHeadPoints[0] = this.End;
arrowHeadPoints[1] = basePoint - (crossDir * (ArrowHeadWidth / 2));
arrowHeadPoints[2] = basePoint + (crossDir * (ArrowHeadWidth / 2));
// Build geometry for the arrow head.
PathFigure arrowHeadFig = new PathFigure();
arrowHeadFig.IsClosed = true;
arrowHeadFig.IsFilled = true;
arrowHeadFig.StartPoint = arrowHeadPoints[1];
arrowHeadFig.Segments.Add(new LineSegment(arrowHeadPoints[0], true));
arrowHeadFig.Segments.Add(new LineSegment(arrowHeadPoints[2], true));
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(arrowHeadFig);
geometryGroup.Children.Add(pathGeometry);
}
DISCLAIMER: I'm NOT a math expert... so this could be all wrong.
You need to figure out the slope of the curve towards the end of the line. If you use the parametric equation for a Bezier curve at time t=0.95 (you may need to adjust this value) and then again at time t=1.0 (which is just this.End) and then subtract them, you will have what you need (the slope of the curve at the end).
To calculate it, see Quadratic Bezier Curve: Calculate Point - you'll want the cubic answer (2nd answer).
In GenerateCurvedLine, calculate the arrow direction like this:
var t = 0.95;
var x1 = (1 - t) * (1 - t) * (1 - t) * this.Start.X
+ 3 * (1 - t) * (1 - t) * t * secondPoint.X
+ 3 * (1 - t) * t * t * thirdPoint.X
+ t * t * t * this.End.X;
var y1 = (1 - t) * (1 - t) * (1 - t) * this.Start.Y
+ 3 * (1 - t) * (1 - t) * t * secondPoint.Y
+ 3 * (1 - t) * t * t * thirdPoint.Y
+ t * t * t * this.End.Y;
arrowDir = new Vector(this.End.X - x1, this.End.Y - y1);
In GenerateArrowHeadGeometry, use the arrowDir above instead of your startDir.
FYI - t (time) ranges from 0 to 1 for points on your curve. 0 will be this.Start and 1 will be this.End. So, t=0.95 would be towards the end of the curve.
I have 3 particles and one of them is the center particle. I want to rotate other two particle ( stored in particles list ) relative to the center particle with the formula q' = Θq + p where q' is the new position of the rotated particle, Θ is the orientation angle and p is the position of center particle. The initial position of other two particles is stored in initialParticlePosition list. THe problem is I think the angle I calculate is wrong because of the range. I thing I should take the range as [-pi, pi) or something like this. In some parts it calculates correct but sometimes it is wrong. Can someone help me with this code or give me another method of rotating.
{
angle = Math.Acos(Vector2.Dot(heading,new Vector2(0,-1) ));
for (int i = 0; i < 2; i++)
{
tempX = (double)initialParticlePositions[i].X * Math.Cos(angle) - (double)initialParticlePositions[i].Y * Math.Sin(angle) + centerParticle.position.x;
tempY = (double)initialParticlePositions[i].X * Math.Sin(angle) + (double)initialParticlePositions[i].Y * Math.Cos(angle) + centerParticle.position.y;
particles[i].position.x = tempX;
particles[i].position.y = tempY;
}
}
Some methods that might help (angles always in degrees, not rad):
public static double GetAngle(Vector v)
{
return Math.Atan2(v.X, -v.Y) * 180.0 / Math.PI;
}
public static Vector SetAngle(Vector v, double angle)
{
var angleInRads = angle * (Math.PI / 180.0);
var distance = v.Length;
v.X = (Math.Sin(angleInRads) * distance);
v.Y = -(Math.Cos(angleInRads) * distance);
return v;
}
static public Point RotatePointAroundCenter(Point point, Point center, double rotationChange)
{
Vector centerToPoint = point - center;
double angle = GetAngle(centerToPoint);
Vector centerToNewPoint = SetAngle(centerToPoint, angle + rotationChange);
return center + centerToNewPoint;
}
(You should start marking answers that help as answer, click the checkmark outline below the votes on the left, e.g. you could accept this answer)
Edit: Optimized the methods a bit.
The particle positions that are orbiting can be set with a single line of code each:
Assume p1, p2, & p3 are Vector2s and p2 & p3 are orbiting p1.
p2 = Vector2.Transform(p2 - p1, Matrix.CreateRotationZ(rotationChangeP2)) + p1;
p3 = Vector2.Transform(p3 - p1, Matrix.CreateRotationZ(rotationChangeP3)) + p1;
The Matrix.Create...() method will call the two trig functions for you.
edit. the Matrix & Vector2 structures & methods are XNA specific but included here because that's what the OP tagged his Q with.
angle = Math.Acos(Vector2.Dot(heading,new Vector2(0,-1)));
As you suspect, your combination of dot product and Acos will only give you angles in a 180
degree range.
Instead, use Atan2 on your unit vector to get a full range of angles from -pi to pi.
angle = (float)Math.Atan2((double)heading.Y, (double)heading.X);
You may need to negate the Y term if your Y axis is positive in the down direction.
I've been trying to do this for a while but haven't had much success. All I want to do is rotate the rectangle and then create a new rectangle which encompasses the rotated points.
Anyone have any ideas how it should be done properly?
The code I have doesn't work, but I'm not sure where it's going wrong exactly (the numbers make me think it actually works), for example if I have a rectangle with the following values:
{X:865 Y:76 Width:22 Height:164}
The result is:
{X:1863 Y:1740 Width:164 Height:22}
Where it is rotated -1.57094443
What I do is grab all four points of the original rectangle and rotate them with this function:
static public Vector2 RotateVectorAround(Vector2 anchor, Vector2 vector, float rotation)
{
Matrix mat = new Matrix();
mat.Translation = new Vector3(vector.X - anchor.X, vector.Y - anchor.Y, 0);
Matrix rot = new Matrix();
rot = Matrix.CreateRotationZ(rotation);
mat *= rot;
mat.Translation += new Vector3(anchor.X, anchor.Y, 0);
return new Vector2(mat.Translation.X, mat.Translation.Y);
}
Where 'anchor' is the pivot point (I'm not sure if this function is mathematically sound), then I determine the corners of the rotated rectangle with this:
Vector2 newTopLeft = new Vector2( Math.Min(Math.Min(topleft.X, bottomRight.X), Math.Min(bottomleft.X, topright.X)),
Math.Min(Math.Min(topleft.Y, bottomRight.Y), Math.Min(bottomleft.Y, topright.Y)));
Vector2 newBottomRight = new Vector2(
Math.Max(Math.Max(topleft.X, bottomRight.X), Math.Max(bottomleft.X, topright.X)),
Math.Max(Math.Max(topleft.Y, bottomRight.Y), Math.Max(bottomleft.Y, topright.Y) ));
You can multiply the points of the rectangle with a rotation matrix.
so given point P in a rotation will result in point R
where a is the rotation
a = degrees * (PI/180)
Rx = Px * cos(a) + Py * -sin(a)
Ry = Px * sin(a) + Py * cos(a)
to rotate about a point you can substract the pivot point before rotating it and add them after the rotation again (so the rotation is virtually around (0,0)
Px = Px - PivotX
Py = Py - PivotY
Rx = Px * cos(a) + Py * -sin(a)
Ry = Px * sin(a) + Py * cos(a)
Px = Rx + PivotX
Py = Ry + PivotY
I would not use the 3'th dimension here for a 2d rotation
in XNA that is something like (sorry no VStudio here):
point -= pivot
point = Vector2.Transform(point, Matrix.CreateRotationZ(angle));
point += pivot