C# - Joystick sensitivity formula - c#

How to calculate joystick sensitivity, taking into account deadzone and the circular nature of the stick?
I'm working on a class that represents a stick of a gamepad. I'm having trouble with the mathematics of it, specifically with the sensitivity part. Sensitivity should make the joystick's distance from center non-linear. I applied sensitivity on a X-Box trigger without problems, but because a joystick has two axis (X and Y), I'm having trouble with the math involved.
I want to apply circular sensitivity to the stick, but I don't really know how to do that, specially taking into account other calculations on the axes (like deadzone, distance from center, etc.). How sould I accomplish that?
Additional details about the problem
Right now, I already have my temporary fix which is not working very well. It seems to be working when the joystick direction is either horizontal or vertical, but when I move it to a diagonal direction, is seems buged. My Joystick class has a Distance property, which retrieves the stick's distance from center (a value from 0 to 1). My Distance property is working well, but when I apply the sensitivity, the retrieved distance is less than 1 on diagonal directions if I move my josytick around, when it should be exactly 1, no matter the direction.
Below, I'm including a simplified version of my Joystick class, where I removed most of the unrelevant code. The calculated X and Y positions of the axes are retrieved by ComputedX and ComputedY properties. Each of this properties should include its axis final position (from -1 to 1) taking into account all the modifiers (deadzone, saturation, sensitivity, etc.).
public class Joystick
{
// Properties
// Physical axis positions
public double X { get; set;}
public double Y { get; set; }
// Virtual axis positions, with all modifiers applied (like deadzone, sensitivity, etc.)
public double ComputedX { get => ComputeX(); }
public double ComputedY {get => ComputeY(); }
// Joystick modifiers, which influence the computed axis positions
public double DeadZone { get; set; }
public double Saturation { get; set; }
public double Sensitivity { get; set; }
public double Range { get; set; }
public bool InvertX { get; set; }
public bool InvertY { get; set; }
// Other properties
public double Distance
{
get => CoerceValue(Math.Sqrt((ComputedX * ComputedX) + (ComputedY * ComputedY)), 0d, 1d);
}
public double Direction { get => ComputeDirection(); }
// Methods
private static double CoerceValue(double value, double minValue, double maxValue)
{
return (value < minValue) ? minValue : ((value > maxValue) ? maxValue : value);
}
protected virtual double ComputeX()
{
double value = X;
value = CalculateDeadZoneAndSaturation(value, DeadZone, Saturation);
value = CalculateSensitivity(value, Sensitivity);
value = CalculateRange(value, Range);
if (InvertX) value = -value;
return CoerceValue(value, -1d, 1d);
}
protected virtual double ComputeY()
{
double value = Y;
value = CalculateDeadZoneAndSaturation(value, DeadZone, Saturation);
value = CalculateSensitivity(value, Sensitivity);
value = CalculateRange(value, Range);
if (InvertY) value = -value;
return CoerceValue(value, -1d, 1d);
}
/// <sumary>Gets the joystick's direction (from 0 to 1).</summary>
private double ComputeDirection()
{
double x = ComputedX;
double y = ComputedY;
if (x != 0d && y != 0d)
{
double angle = Math.Atan2(x, y) / (Math.PI * 2d);
if (angle < 0d) angle += 1d;
return CoerceValue(angle, 0d, 1d);
}
return 0d;
}
private double CalculateDeadZoneAndSaturation(double value, double deadZone, double saturation)
{
deadZone = CoerceValue(deadZone, 0.0d, 1.0d);
saturation = CoerceValue(saturation, 0.0d, 1.0d);
if ((deadZone > 0) | (saturation < 1))
{
double distance = CoerceValue(Math.Sqrt((X * X) + (Y * Y)), 0.0d, 1.0d);
double directionalDeadZone = Math.Abs(deadZone * (value / distance));
double directionalSaturation = 1 - Math.Abs((1 - saturation) * (value / distance));
double edgeSpace = (1 - directionalSaturation) + directionalDeadZone;
double multiplier = 1 / (1 - edgeSpace);
if (multiplier != 0)
{
if (value > 0)
{
value = (value - directionalDeadZone) * multiplier;
value = CoerceValue(value, 0, 1);
}
else
{
value = -((Math.Abs(value) - directionalDeadZone) * multiplier);
value = CoerceValue(value, -1, 0);
}
}
else
{
if (value > 0)
value = CoerceValue(value, directionalDeadZone, directionalSaturation);
else
value = CoerceValue(value, -directionalSaturation, -directionalDeadZone);
}
value = CoerceValue(value, -1, 1);
}
return value;
}
private double CalculateSensitivity(double value, double sensitivity)
{
value = CoerceValue(value, -1d, 1d);
if (sensitivity != 0)
{
double axisLevel = value;
axisLevel = axisLevel + ((axisLevel - Math.Sin(axisLevel * (Math.PI / 2))) * (sensitivity * 2));
if ((value < 0) & (axisLevel > 0))
axisLevel = 0;
if ((value > 0) & (axisLevel < 0))
axisLevel = 0;
value = CoerceValue(axisLevel, -1d, 1d);
}
return value;
}
private double CalculateRange(double value, double range)
{
value = CoerceValue(value, -1.0d, 1.0d);
range = CoerceValue(range, 0.0d, 1.0d);
if (range < 1)
{
double distance = CoerceValue(Math.Sqrt((X * X) + (Y * Y)), 0d, 1d);
double directionalRange = 1 - Math.Abs((1 - range) * (value / distance));
value *= CoerceValue(directionalRange, 0d, 1d);
}
return value;
}
}
I tried to make this question as short as possible, but it's hard for me to explain this specific problem without describing some details about it. I know I should keep it short, but I would like to write at least a few more words:
Thank you for having the time to read all this!

After searching a bit for geometry math on the Internet, I finally found out the solution to my problem. I'm really bad at math, but now I know that it is actually very simple.
Instead of applying deadzone and sensitivity for each axis independently, I should apply them to the joystick radius. So, to do that, I just need to convert my joystick's cartesian coordinates (X and Y) to polar coordinates (Radius and Angle). Then, I apply deadzone sensitivity and all modifiers I want on the radius coordinate and convert it back to cartesian coordianates.
I'm posting here the code I'm using now. This looks far simpler and cleaner than the code on my question above:
private void ComputeCoordinates()
{
// Convert to polar coordinates.
double r = CoerceValue(Math.Sqrt((X * X) + (Y * Y)), 0d, 1d); // Radius;
double a = Math.Atan2(Y, X); // Angle (in radians);
// Apply modifiers.
double value = ComputeModifiers(r);
// Convert to cartesian coordinates.
double x = value * Math.Cos(a);
double y = value * Math.Sin(a);
// Apply axis independent modifiers.
if (InvertX) x = -x;
if (InvertY) y = -y;
// Set calculated values to property values;
_computedX = x;
_computedY = y;
}
private double ComputeModifiers(double value)
{
// Apply dead-zone and saturation.
if (DeadZone > 0d || Saturation < 1d)
{
double edgeSpace = (1 - Saturation) + DeadZone;
if (edgeSpace < 1d)
{
double multiplier = 1 / (1 - edgeSpace);
value = (value - DeadZone) * multiplier;
value = CoerceValue(value, 0d, 1d);
}
else
{
value = Math.Round(value);
}
}
// Apply sensitivity.
if (Sensitivity != 0d)
{
value = value + ((value - Math.Sin(value * (Math.PI / 2))) * (Sensitivity * 2));
value = CoerceValue(value, 0d, 1d);
}
// Apply range.
if (Range < 1d)
{
value = value * Range;
}
// Return calculated value.
return CoerceValue(value, 0d, 1d);
}
Explanation of the code above
Convert the physical joystick's X and Y coordinates to polar coordinates;
Apply deadzone, saturation, sensitivity and range modifiers to the radius coordinate;
Convert back to cartesian coordiantes (X and Y) using the original angle and the modified radius;
Optional: apply axis independent modifiers to each of the new axis (in this case, I'm just inverting each axis if the user wants the axis to be inverted);
Done. Every modifier is now applied in a circular way, no matter the direction I move the joystick;
Well, this situation had cost me about a day of work, because I didn't found anything related to my problem on Internet and I didn't know very well how to search for the solution, but I hope other people getting to this question may find this useful.
Here are some references about cartesian and polar coordinate systems:
https://en.wikipedia.org/wiki/Cartesian_coordinate_system
https://en.wikipedia.org/wiki/Polar_coordinate_system
https://social.msdn.microsoft.com/Forums/vstudio/en-US/9f120a35-dcac-42ab-b763-c65f3c39afdc/conversion-between-cartesian-to-polar-coordinates-and-back?forum=vbgeneral

The below worked well for me. It takes a standard parabola (x^2) and makes sure the result is signed. You can probably adjust the curve to make it closer to what you need by using a graphing calculator.
As it is, f(-1) = -1, f(0) = 0, f(1) = 1 and the curve in between is not too sensitive.
Mathf.Pow(axes.x, 2) * (axes.x < 0 ? -1 : 1)

Related

Unit Test - sometimes works, sometimes not

I have a unit test below running against the code that follows. This test sometimes passes, sometimes fails. Not sure why and hesitate to change things radically since well, it is a formula and sometimes passes...I'm thinking it may have something to do with the precision of the type double? Not sure. Thoughts?
[TestMethod]
public void CircleFromCircumference()
{
var random = new Random();
var circumference = random.NextDouble();
var circle = new Circle("My circle", circumference, Circle.CircleDimensions.Circumference);
var var1 = circumference - circle.Circumference;
var var2 = circumference - 2 * Math.PI * circle.Radius;
var var3 = circumference - Math.PI * circle.Diameter;
var var4 = Math.Pow(circumference / (2 * Math.PI), 2) * Math.PI - circle.Area;
Assert.IsTrue(
circumference - circle.Circumference <= 0 //circumference
&& circumference - 2 * Math.PI * circle.Radius <= 0 //radius
&& circumference - Math.PI * circle.Diameter <= 0 //diameter
&& Math.Pow(circumference / (2 * Math.PI), 2) * Math.PI - circle.Area <= 0 //area
&& string.IsNullOrEmpty(circle.ShapeException));
}
using System;
using System.Runtime.Serialization;
namespace Shapes
{
[DataContract]
public class Circle : Shape
{
[DataMember] public double Radius { get; set; }
[DataMember] public double Diameter { get; set; }
[DataMember] public double Circumference { get; set; }
/// <summary>
/// The name of the value you are sending. Radius is the default
/// </summary>
public enum CircleDimensions
{
Circumference = 1,
Area = 2,
Diameter = 3
}
/// <summary>
///
/// </summary>
/// <param name="circleName">The name of your circle</param>
/// <param name="dimension">The value of the dimension you are providing</param>
/// <param name="circleDimensions">The name of the value you are providing. Radius is default</param>
public Circle(string circleName, double dimension = 0, CircleDimensions circleDimensions = 0)
{
this.ShapeName = circleName;
if (dimension <= 0)
{
this.ShapeException = "Parameters must be greater than zero";
return;
}
switch (circleDimensions)
{
case CircleDimensions.Circumference:
//radius from Circumference
this.Circumference = dimension;
this.Radius = this.RadiusFromCircumference(dimension);
this.Area = this.CalculateArea(this.Radius);
this.Diameter = this.CalculateDiameter(this.Radius);
break;
case CircleDimensions.Area:
//radius from Area
break;
case CircleDimensions.Diameter:
//radius from diameter
break;
default: //calculate from radius
this.Radius = dimension;
this.Diameter = this.CalculateDiameter(dimension);
this.Circumference = this.CalculateCircumference(dimension);
this.Area = this.CalculateArea(dimension);
break;
}
}
private double RadiusFromCircumference(double dimension) => dimension / (2 * Math.PI);
private double CalculateCircumference(double dimension) => 2 * Math.PI * dimension;
private double CalculateDiameter(double dimension) => 2 * dimension;
private double CalculateArea(double dimension) =>
Math.PI * (Math.Pow(dimension, 2));
}
}
The inconsistency has nothing to do with precision per se, it has more to do with how floating point representation works. For example, if you write this:
for (float f = 0.0f; f != 1.0f; f+=0.1f)
{
Console.WriteLine(f);
}
It will never exit. Because 0.1 does not have an exact representation in binary form. (https://www.exploringbinary.com/why-0-point-1-does-not-exist-in-floating-point/). I also recommend reading (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)
Back to the problem at hand, in your code, you are getting the Radius using this :
dimension / (2 * Math.PI); //passed in dimension is the Circumference, returns radius
And then in your test you are asserting that:
circumference - 2 * Math.PI * circle.Radius <= 0
Dividing and then multiplying by the same floating point number is not guaranteed to give you the original floating point number as a result.
Thus, it is a bad idea in general to assert this. The most common way to test "almost equality" is to test equality "within limits". In your case, all you have to do is define a small enough epsilon that you deem "acceptable", greater or equal to double.Epsilon in your tests.
var epsilon = double.Epsilon;
Assert.IsTrue(
Math.Abs(circumference - circle.Circumference) <= epsilon //circumference
&& Math.Abs(circumference - 2 * Math.PI * circle.Radius) <= epsilon //radius
&& Math.Abs(circumference - Math.PI * circle.Diameter) <= epsilon //diameter
&& Math.Abs(Math.Pow(circumference / (2 * Math.PI), 2) * Math.PI - circle.Area) <= epsilon //area
&& string.IsNullOrEmpty(circle.ShapeException));
If instead you have to guarantee exactness, one option is to switch to a non-floating point type like decimal, but expect a performance hit.

Draw arc between two lines. I need to calculate points

I can't find a way to drawing ARC between two lines. My constraint is : I have to calculate this Arc stroke points. Because i am using InkCanvas and i have to draw this arc point by point, i can't put any object to screen or canvas. So I know i can draw any arc with PATH object and use ArcSegment. With this method yes i can draw arc but it isn't stroke point on the Canvas. For this reason i cannot delete or save it.
Anyway i need calculate this arch point by point.
I have code for drawing circle on canvas like this :
Stroke GetCircleStroke(int centerX, int centerY, int radiusX, int radiusY,double angletoDraw=2.0)
{
StylusPointCollection strokePoints = new StylusPointCollection();
int numTotalSteps = 180;
for (int i = 0; i <= numTotalSteps; i++)
{
double angle = angletoDraw * Math.PI * (double)i / (double)numTotalSteps;
StylusPoint sp = new StylusPoint();
//compute x and y points
sp.X = centerX + Math.Cos(angle) * radiusX;
sp.Y = centerY - Math.Sin(angle) * radiusY;
//add to the collection
strokePoints.Add(sp);
}
Stroke newStroke = new Stroke(strokePoints);
return newStroke;
}
I can draw circle easly, but i couldn't find a way to draw an arc :(
We know center point X,Y and we know Line1 and Line2 coordinates. I just don't know what is that arc..
Could you please help me for calculate arc points like this way ?
You have a few concepts flying around like Line/Segment, Point, Circle, etc. Instead of making a mess of hard to understand code, let's try to breakdown the problem into smaller parts that are easier to digest.
You have a notion of Point, ok, lets implement one:
public struct Point2D //omitted equality logic
{
public double X { get; }
public double Y { get; }
public Point2D(double x, double y)
{
X = x;
Y = y;
}
public override string ToString() => $"{X:N3}; {Y:N3}";
}
Ok, we also have a notion of Segment or a delimitted Line:
public struct Segment2D
{
public Point2D Start { get; }
public Point2D End { get; }
public double Argument => Math.Atan2(End.Y - Start.Y , End.X - Start.X);
public Segment2D(Point2D start, Point2D end)
{
Start = start;
End = end;
}
}
And last, but not least, we have the notion of Circle:
public struct Circle2D
{
private const double FullCircleAngle = 2 * Math.PI;
public Point2D Center { get; }
public double Radius { get; }
public Circle2D(Point2D center, double radius)
{
if (radius <= 0)
throw new ArgumentOutOfRangeException(nameof(radius));
Center = center;
Radius = radius;
}
public IEnumerable<Point2D> GetPointsOfArch(int numberOfPoints, double startAngle, double endAngle)
{
double normalizedEndAngle;
if (startAngle < endAngle)
{
normalizedEndAngle = endAngle;
}
else
{
normalizedEndAngle = endAngle + FullCircleAngle;
}
var angleRange = normalizedEndAngle - startAngle;
angleRange = angleRange > FullCircleAngle ? FullCircleAngle : angleRange;
var step = angleRange / numberOfPoints;
var currentAngle = startAngle;
while (currentAngle <= normalizedEndAngle)
{
var x = Center.X + Radius * Math.Cos(currentAngle);
var y = Center.Y + Radius * Math.Sin(currentAngle);
yield return new Point2D(x, y);
currentAngle += step;
}
}
public IEnumerable<Point2D> GetPoints(int numberOfPoints)
=> GetPointsOfArch(numberOfPoints, 0, FullCircleAngle);
}
Study the implementation of GetPointsOfArch, it shouldn't be too hard to understand.
And now, to solve your problem, you would do:
var myCircle = new Circle2D(new Point2D(centerX, centerY), radius);
var line1 = ....
var line2 = ....
var archPoints = myCircle.GetPointsOfArch(number, line2.Argument, line1.Argument);
Isn't that much easier to read, follow and understand?

Place GPS coordinates on a map image without external API

I'm currently working on a "trip tracker". The goal is to place some GPS coordinates (logged by a GPS device) on a static image map downloaded from MapQuest (or OpenStreetMap).
In order to achieve this goal, I followed to follwing procedure:
Find my GPS coordinates set's center ((maxLat-minLat)/2, (maxLon-minLon)/2)
Download a 3840x3840 map (fixed zoom 15 for now) centered on my "coordinates set's center" from MapQuest
Using mercator projection (I tries both spherical & eliptical with EPSG:4326 or EPSG:3857), get the (X,Y) of the center in meters
For each point of my set
Get the point's (X,Y) using mercator projection
Substract the point(X,Y) to center(X,y)
Convert meters to pixel according to Zoom level and map (tile?) width (I tried both tile width (256) and map width (3840)
Unfortunatly, in one week of research & tries, I didn't succeed in placing those point.
Does anybody have a complete solution for this kind of problems ?
Thank you
Edit #1
(Removed: inconsistent)
Edit #2
Here is a clean project sample
https://dl.dropboxusercontent.com/u/429726/MapSample.zip
The path is rotate of 90° (tricked #MainWindow.xaml.cs:L130)
The path is flattened
Img:
https://dl.dropboxusercontent.com/u/429726/MapSample.jpg
Edit #3
Added multiple formulas
GeographicCoordinates > ToMercator() modification
public System.Windows.Point ToMercator(int test = 0)
{
System.Windows.Point mercator;
double x = this.Longitude.ToMercator(test);
double y = this.Latitude.ToMercator(test);
mercator = new System.Windows.Point(x, y);
return mercator;
}
GeographicCoordinate > ToMercator() modification
public double ToMercator(int test = 0)
{
double result = 0;
switch (this.Type)
{
case(GeographicCoordinateType.Longitude):
switch (test) {
case 0:
return this.DecimalDegrees.ToRadians() * Maps.EarthGreatRadius;
case 1:
//http://jackofalltradesdeveloper.blogspot.be/2012/03/how-to-project-point-from-geography-to.html
return this.DecimalDegrees * 0.017453292519943 * 6378137;
case 2:
//http://alastaira.wordpress.com/2011/01/23/the-google-maps-bing-maps-spherical-mercator-projection/
return this.DecimalDegrees * 20037508.34 / 180;
}
break;
case(GeographicCoordinateType.Latitude):
switch (test)
{
case 0:
double latitude = this.DecimalDegrees;
if (latitude > 89.5)
{
latitude = 89.5;
}
if (latitude < -89.5)
{
latitude = -89.5;
}
double temp = Maps.EarthGreatRadius / Maps.EarthGreatRadius;
double es = 1.0 - (temp * temp);
double eccent = Math.Sqrt(es);
double phi = latitude.ToRadians();
double sinphi = Math.Sin(phi);
double con = eccent * sinphi;
double com = 0.5 * eccent;
con = Math.Pow((1.0 - con) / (1.0 + con), com);
double ts = Math.Tan(0.5 * ((Math.PI * 0.5) - phi)) / con;
double y = 0 - Maps.EarthGreatRadius * Math.Log(ts);
return y;
case 1:
double FSin = Math.Sin(this.DecimalDegrees.ToRadians());
return 6378137 / 2.0 * Math.Log((1.0 + FSin) / (1.0 - FSin));
case 2:
y = Math.Log(Math.Tan((90 + this.DecimalDegrees) * Math.PI / 360)) / (Math.PI / 180);
return y * 20037508.34 / 180;
}
break;
default:
throw new Exception();
}
return result;
}
Edit #4
I've tried multiples formulas & Proj.Net library, I always end up with the same shape (-90° && "flatened")
The map coordinates need also translate to mercator. You need delta x and delta y of the map and the image properties:Convert lat/lon to pixel coordinate?.
I've used this in the past to build information on a map on a Windows Form client:
http://greatmaps.codeplex.com/
Here is the answer
GeographicCoordinates > ToMercator()
public System.Windows.Point ToMercator(int test = 0)
{
System.Windows.Point mercator;
double x = this.Longitude.ToMercator(test);
double y = this.Latitude.ToMercator(test);
mercator = new System.Windows.Point(x, y);
return mercator;
}
Should be
public System.Windows.Point ToMercator(int test = 0)
{
System.Windows.Point mercator;
double x = this.Latitude.ToMercator(test);
double y = this.Longitude.ToMercator(test);
mercator = new System.Windows.Point(x, y);
return mercator;
}
And
GeographicCoordinate > ToMercator()
should swap GeographicCoordinateType.Latitude/Longitude cases.
I also had to fix Y according to the hemisphere
& the job was done.

Math/Trig to get XYZ coordinates of dipping ellipse in C#

I need to get the coordinates of points along an ellipse that lies on a dipping plane. The ellipse is also rotated in the XY plane. I am able to get correct X and Y coordinates of points along a rotated horizontal ellipse, and the X and Y points of the horizontal projection of a dipping ellipse, but cannot figure out the maths/trig that I need to get the Z value of the points on the dipping ellipse. I am trying to do this by using the apparent dip of a vector from the center of the ellipse to each point along the dipping ellipse.
Here's the code I'm using:
class ellipsoid
{
public ellipsoid() { }
public double bearing { get; set; }
public double plunge { get; set; }
public double dip { get; set; }
public double x { get; set; }
public double y { get; set; }
public double z { get; set; }
}
class exampleCalc
{
public void createDippingEllipse(ellipsoid ellipsoid, double majorAxis, double semiMajorAxis)
// majorAxis is the longest axis, semiMajorAxis is the shorter axis
{
double semiMajorAxisApparent = 0;
Int32 strNum = 1;
point delta = new point();
point horz = new point();
point ep = new point();
point p = new point();
p.x = ellipsoid.x;
p.y = ellipsoid.y;
p.z = ellipsoid.z;
// az represents the angular interval along which points will be located
for (Int32 az = 0; az <= 360; az = az + 10)
{
double rakeAngle = 0;
if (az >= 0 && az <= 90)
{
rakeAngle = az;
}
if (az > 90 && az <= 180)
{
rakeAngle = 180 - az;
}
if (az > 180 && az <= 270)
{
rakeAngle = az - 180;
}
if (az > 270 && az <= 360)
{
rakeAngle = 360 - az;
}
if (ellipsoid.dip == 90)
{
semiMajorAxisApparent = semiMajorAxis;
}
else
{
semiMajorAxisApparent = semiMajorAxis * Math.Cos(ep.degreesToRadians(Math.Abs(ellipsoid.dip)));
}
double cosAz = Math.Cos(ep.degreesToRadians(az));
double sinAz = Math.Sin(ep.degreesToRadians(az));
// convert mathematical bearing to bearing where north is zero
double bearing0north = ellipsoid.bearing + 90;
if (bearing0north > 360) { bearing0north = 360 - bearing0north; }
double cosBearing = Math.Cos(ep.degreesToRadians(bearing0north));
double sinBearing = Math.Sin(ep.degreesToRadians(bearing0north));
// delta.x and delta.y are correct
delta.x = (majorAxis * cosBearing * cosAz) - (semiMajorAxisApparent * sinBearing * sinAz);
delta.y = (majorAxis * cosAz * sinBearing) + (semiMajorAxisApparent * sinAz * cosBearing);
double horzDist = Math.Sqrt(Math.Pow(delta.x, 2) + Math.Pow(delta.y, 2));
// uncorrected for apparent horz length
horz.x = (majorAxis * cosBearing * cosAz) - (semiMajorAxis * sinBearing * sinAz);
horz.y = (majorAxis * cosAz * sinBearing) + (semiMajorAxis * sinAz * cosBearing);
double apparentDip = Math.Atan(Math.Tan(ep.degreesToRadians(Math.Abs(ellipsoid.bearing))) * Math.Sin(ep.degreesToRadians(rakeAngle)));
// delta.z is not correct
delta.z = horzDist * Math.Atan(apparentDip);
ep.x = p.x + delta.x;
ep.y = p.y + delta.y;
ep.z = p.z + delta.z;
}
}
}
You are truly attempting the most complex possible solution to a relatively straightforward problem (except perhaps choosing a non-inertial frame).
Simply solve all the maths in an ellipse lying in the X-Y plane, about the origin, and calculate the affine matrix that translates and rotates the ellipse to the desired location and orientation. Then simply apply the matrix to each calculated point to get the correct oriented point.

Maximum clockwise angles from 3 nearest points

Help me, because I'm rly tired of this...
I need to count angles between current point and the closest 3 points (look at an image below) - I need to sort the angles in descending order (to get the point with the largest angle - if it doesn't fit expectations, I have to get another one).
I tried to do something, but it doesn't work...
private static Vertex[] SortByAngle(IEnumerable<Vertex> vs, Vertex current, Vertex previous)
{
if (current.CompareTo(previous) == 0)
{
previous.X = previous.X - 1.0; // this is a trick to handle the first point
}
var vertices = new Dictionary<Vertex, double>();
foreach (var v in vs)
{
double priorAngle = Angle(previous, current);
double nextAngle = Angle(current, v);
double angleInBetween = 180.0 - (priorAngle + nextAngle);
vertices.Add((Vertex) v.Clone(), angleInBetween);
}
// here the angles are incorrect, because I want to sort them in desc order, but it's a real mess when I do OrderByDescending - something is wrong with my code:S
vertices = vertices.OrderBy(v => v.Value).ToDictionary(k => k.Key, v => v.Value);
return vertices.Select(v => new Vertex(v.Key.X, v.Key.Y)).ToArray();
}
private static double Angle(Vertex v1, Vertex v2, double offsetInDegrees = 0.0)
{
return (RadianToDegree(Math.Atan2(-v2.Y + v1.Y, -v2.X + v1.X)) + offsetInDegrees);
}
public static double RadianToDegree(double radian)
{
var degree = radian * (180.0 / Math.PI);
if (degree < 0)
degree = 360 + degree;
return degree;
}
vs is my set of 3 nearest points
current and previous are obvious:)
i didn't tested it, but i restyled a little, avoiding dictionaries. I think your mistake is in: double angleInBetween = 180.0 - (priorAngle + nextAngle); should be: double angleInBetween = (180.0 - priorAngle) + nextAngle;
public struct Vertex
{
public double X { get; set; }
public double Y { get; set; }
}
private static double CalcDistance(Vertex v1, Vertex v2)
{
double dX = (v2.X - v1.X);
double dY = (v2.Y - v1.Y);
return Math.Sqrt((dX * dX) + (dY * dY));
}
private static Vertex[] SortByAngle(IEnumerable<Vertex> vs, Vertex current, Vertex previous)
{
var verticesOnDistance = from vertex in vs
where !vertex.Equals(current)
let distance = CalcDistance(current, vertex)
orderby distance
select vertex;
double priorAngle = Angle(previous, current);
var verticeAngles = from vertex in verticesOnDistance.Take(3)
let nextAngle = Angle(current, vertex)
let angleInBetween = (180.0 - priorAngle) + nextAngle
orderby angleInBetween descending
select vertex;
return verticeAngles.ToArray();
}
private static double Angle(Vertex v1, Vertex v2, double offsetInDegrees = 0.0)
{
return (RadianToDegree(Math.Atan2(-v2.Y + v1.Y, -v2.X + v1.X)) + offsetInDegrees);
}
public static double RadianToDegree(double radian)
{
var degree = radian * (180.0 / Math.PI);
if (degree < 0)
degree = 360 + degree;
return degree;
}
I'm running a little out of time here. I'll be on later... I'm not sure this is correct, but maybe shine another light on it...
Good luck

Categories

Resources