I have Unity 2021 so it uses C# version > 7 I believe.
Somehow I can not use static objects in Switch/Case statement.
private Position getStartingPosition(Direction direction) {
switch (direction) {
case Direction Direction.EAST:
return new Position(-1, height / 2);
case Direction Direction.NORTH:
return new Position(width / 2, height);
case Direction Direction.WEST:
return new Position(width, height / 2);
case Direction Direction.SOUTH:
return new Position(width / 2, -1);
default:
throw new System.Exception("Impossible");
}
}
and the Direction class:
public class Direction
{
static public readonly Direction EAST = new Direction(1, 0);
static public readonly Direction NORTH = new Direction(0, -1);
static public readonly Direction WEST = new Direction(-1, 0);
static public readonly Direction SOUTH = new Direction(0, 1);
...
The error I am getting is:
Grid.cs(38,31): error CS1003: Syntax error, ':' expected
Grid.cs(38,31): error CS1513: } expected
Grid.cs(38,36): error CS1003: Syntax error, ':' expected
Grid.cs(38,36): error CS1513: } expected
Grid.cs(40,31): error CS1003: Syntax error, ':' expected
Grid.cs(40,31): error CS1513: } expected
What am I doing wrong?
The case statment should be var _ when direction.Equals(Class.VALUE) without the declaration. But also Direction is an object so one option can be use this statment:
switch (direction) {
case var _ when direction.Equals(Direction.EAST):
return new Position(-1, height / 2);
case var _ when direction.Equals(Direction.NORTH):
return new Position(width / 2, height);
case var _ when direction.Equals(Direction.WEST):
return new Position(width, height / 2);
case var _ when direction.Equals(Direction.SOUTH):
return new Position(width / 2, -1);
default:
throw new System.Exception("Impossible");
}
And implement the interface IEquatable<Direction> with a method similar to this:
public bool Equals(Direction otherDirection)
{
return (this.x == otherDirection.x && this.y == otherDirection.y);
}
Where x and y are values into your class used to know if two objects are equals.
You can only switch on constants and patterns (where a constant is considered as a constant pattern). Unity uses C# 9.0 providing powerful pattern matching expressions.
Since C# 9.0 we have the switch expression with a simplified syntax compared to the switch statement. I therefore suggest using switch expressions in conjunction with pattern matching.
We can use a positional pattern to do the test. To use it we must add a deconstructor to the class
public class Direction
{
public Direction(int east, int south)
{
East = east;
South = south;
}
// You have not shown your whole class.
// I assume that it has two properties for the main directions.
public int East { get; }
public int South { get; }
public void Deconstruct(out int east, out int south)
{
east = East;
south = South;
}
}
Then we can switch like this:
// Positional pattern with deconstructor
return direction switch {
( 1, 0) => new Position(-1, height / 2),
( 0, -1) => new Position(width / 2, height),
(-1, 0) => new Position(width, height / 2),
( 0, 1) => new Position(width / 2, -1),
_ => throw new ArgumentException("Impossible", nameof(direction)),
};
Another possible pattern is the tuple pattern not requiring a deconstructor:
// Tuple pattern
return (direction.East, direction.South) switch {
( 1, 0) => new Position(-1, height / 2),
( 0, -1) => new Position(width / 2, height),
(-1, 0) => new Position(width, height / 2),
( 0, 1) => new Position(width / 2, -1),
_ => throw new ArgumentException("Impossible", nameof(direction)),
};
Yet another possibility is to switch on enum constants using a constant pattern:
public enum DirectionKind
{
None,
East,
North,
West,
South
}
We then add a property like the following one to the Direction class
public DirectionKind DirectionKind { get; }
I leave it up to you to initialize it. Then we switch like this:
// Constant pattern on enum constants
return direction.DirectionKind switch {
DirectionKind.East => new Position(-1, height / 2),
DirectionKind.North => new Position(width / 2, height),
DirectionKind.West => new Position(width, height / 2),
DirectionKind.South => new Position(width / 2, -1),
_ => throw new ArgumentException("Impossible", nameof(direction)),
};
The property pattern does not require a sepcial infrastructure:
// Property pattern
return direction switch {
{ East: 1, South: 0 } => new Position(-1, height / 2),
{ East: 0, South: -1 } => new Position(width / 2, height),
{ East: -1, South: 0 } => new Position(width, height / 2),
{ East: 0, South: 1 } => new Position(width / 2, -1),
_ => throw new ArgumentException("Impossible", nameof(direction)),
};
There is also a type pattern. It can be used if we declare the directions as a class hierarchy.
public abstract class Direction
{
public abstract int East { get; }
public abstract int South { get; }
}
public class EastDirection : Direction
{
private EastDirection() { } // Hide constructor to implement a singleton.
public static readonly Direction Instance = new EastDirection();
public override int East => 1;
public override int South => 0;
}
public class NorthDirection : Direction
{
private NorthDirection() { }
public static readonly Direction Instance = new NorthDirection();
public override int East => 0;
public override int South => -1;
}
public class WestDirection : Direction
{
private WestDirection() { }
public static readonly Direction Instance = new WestDirection();
public override int East => -1;
public override int South => 0;
}
public class SouthDirection : Direction
{
private SouthDirection() { }
public static readonly Direction Instance = new SouthDirection();
public override int East => 0;
public override int South => 1;
}
// Type pattern
return direction switch {
EastDirection => new Position(-1, height / 2),
NorthDirection => new Position(width / 2, height),
WestDirection => new Position(width, height / 2),
SouthDirection => new Position(width / 2, -1),
_ => throw new ArgumentException("Impossible", nameof(direction)),
};
Note: I used the singleton pattern here as a suggestion. I is not required for the switch expression.
This class hierarchy gives us even a way to eliminate the switch expression altogether by adding an abstract GetPosition method to Direction.
public abstract Position GetPosition(int width, int height);
As an example WestDirection would implement it like this:
public override Position GetPosition(int width, int height)
{
return new Position(width, height / 2);
}
Given a direction you can get a position like this
Direction direction = ...;
Position position = direction.GetPosition(width, height);
This is the true OOP way to solve the problem.
With target typed new we can write (with the positional pattern as an example):
return direction switch {
( 1, 0) => new (-1, height / 2),
( 0, -1) => new (width / 2, height ),
(-1, 0) => new (width, height / 2),
( 0, 1) => new (width / 2, -1 ),
_ => throw new ArgumentException("Impossible", nameof(direction)),
};
Was already answered here:
Switch statement with static fields
I changed it to
switch (direction) {
case var _ when direction == Direction.EAST:
return new Position(-1, height / 2);
case var _ when direction == Direction.NORTH:
return new Position(width / 2, height);
case var _ when direction == Direction.WEST:
return new Position(width, height / 2);
case var _ when direction == Direction.SOUTH:
return new Position(width / 2, -1);
default:
throw new System.Exception("Impossible");
}
Related
What I am doing is trying to place views on a sheet in Revit, either center page or center of one of four quadrants. But I have no experience building classes and I am struggling.
I want to set all my properties upon initialization and they should not change once the pickbox outline has been drawn.
I can draw the pick box with ViewPortOutline(UIDocument UIDoc) and if I taskDialog a pickBox.Min.X will return its values
but when I get to the 'setXYZParameter' this locks me in the pickbox and will not move to the dialogbox
if I comment out the 'setXYZParameter' the code moves forward to the dialog box but the ReturnOutline() pickBox.Min.X is null because the 'setXYZParameter' is commented out.
public class ViewPortOutline
{
public static XYZ VP_CenterPoint { get; set; }
public static XYZ VP_CenterPoint_Q1 { get; set; }
public static XYZ VP_CenterPoint_Q2 { get; set; }
public static XYZ VP_CenterPoint_Q3 { get; set; }
public static XYZ VP_CenterPoint_Q4 { get; set; }
private static UIDocument _UIDoc { get; set; }
public static PickedBox pickBox { get; set; }
public ViewPortOutline(UIDocument UIDoc)
{
_UIDoc = UIDoc;
PickBox();
}
public static void PickBox()
{
Selection selection = _UIDoc.Selection;
PickedBox pickBox = selection.PickBox(PickBoxStyle.Directional);//<<< Problem Here
setXYZParameters();
}
public static Outline ReturnOutline()
{
double minX = pickBox.Min.X;//<<< Null
double minY = pickBox.Min.Y;
double maxX = pickBox.Max.X;
double maxY = pickBox.Max.Y;
double sminX = Math.Min(minX, maxX);
double sminY = Math.Min(minY, maxY);
double smaxX = Math.Max(minX, maxX);
double smaxY = Math.Max(minY, maxY);
Outline outline = new Outline(new XYZ(sminX, sminY, 0), new XYZ(smaxX, smaxY, 0));
return outline;
}
public static void ReturnCenterPoints(UIDocument uidoc, Outline outline,
out XYZ midpoint, out XYZ q1c, out XYZ q2c, out XYZ q3c, out XYZ q4c)
{
double outlineDiagonal = outline.GetDiagonalLength();
double outlineCenter = outline.GetDiagonalLength() / 2;
XYZ bottomleft = (outline.MinimumPoint);
XYZ topleft = new XYZ(outline.MinimumPoint[0], outline.MaximumPoint[1], 0);
XYZ topright = (outline.MaximumPoint);
XYZ bottomright = new XYZ(outline.MaximumPoint[0], outline.MinimumPoint[1], 0);
midpoint = new XYZ((outline.MaximumPoint[0]) / 2, (outline.MaximumPoint[1]) / 2, 0);
XYZ midleft = new XYZ(outline.MinimumPoint[0], outline.MaximumPoint[1] / 2, 0);
XYZ midtop = new XYZ(outline.MaximumPoint[0] / 2, outline.MaximumPoint[1], 0);
XYZ midright = new XYZ(outline.MaximumPoint[0], outline.MaximumPoint[1] / 2, 0);
XYZ midbottom = new XYZ(outline.MaximumPoint[0] / 2, outline.MinimumPoint[1], 0);
Outline q1 = new Outline(midpoint, topright);
q1c = new XYZ(
q1.MaximumPoint[0] / 2,
q1.MaximumPoint[1] / 2, 0);
Outline q2 = new Outline(midleft, midtop);
q2c = new XYZ(
q2.MaximumPoint[0] / 2,
q2.MaximumPoint[1] / 2, 0);
Outline q3 = new Outline(bottomleft, midpoint);
q3c = new XYZ(
q3.MaximumPoint[0] / 2,
q3.MaximumPoint[1] / 2, 0);
Outline q4 = new Outline(midbottom, midright);
q4c = new XYZ(
q4.MaximumPoint[0] / 2,
q4.MaximumPoint[1] / 2, 0);
}
public static void setXYZParameters()
{
XYZ mp; XYZ q1c; XYZ q2c; XYZ q3c; XYZ q4c;
ReturnCenterPoints(_UIDoc, ReturnOutline(),
out mp, out q1c, out q2c, out q3c, out q4c);
VP_CenterPoint = mp;
VP_CenterPoint_Q1 = q1c;
VP_CenterPoint_Q2 = q2c;
VP_CenterPoint_Q3 = q3c;
VP_CenterPoint_Q4 = q4c;
}
}
ViewPortOutline outline = new ViewPortOutline(uidoc);
UnitSetup_Form form = new UnitSetup_Form(commandData);
form.ShowDialog();
I didn't pass my arguments...
setXYZParameters(pickBox);
public static Outline ReturnOutline(PickedBox pickBox)
public static void setXYZParameters(PickedBox pickBox)
ReturnCenterPoints(_UIDoc, ReturnOutline(pickBox),
out midpoint, out q1c, out q2c, out q3c, out q4c);
Programming is stressful, I spend more time debugging then creating...
So, I'm trying to find a way to find the first Rectangle (From a list of rectangles, all 2D) that my Point will hit, going in a specific direction (In C#), however I cannot seem to correctly describe the terminology (If there is such a thing for this, the closest thing I found is raycasting).
My goal is to start from a specific "Point" (In this case the asteterik- * seen from the example below), and from there choose a specific direction (Left, Right, Down, Up) (No Angles). So let's say we choose Down, then the first rect that it would hit is "I'M A RECT 2", and therefore should return this.
I know all the positions and sizes of the Rectangles already so I got that information.
How do I go about doing this?
* [I'M A RECT 1]
[I'M A RECT 2]
[I'M A RECT 3]
[I'M A RECT 4]
You can check if the rectangle can intersect with the ray originating from the point, and then compute the distance from the point:
var point = new PointF(1.2f, 2.5f);
var rectangles = new RectangleF[]
{
new RectangleF(1, 1, 1, 1),
new RectangleF(3, 1, 1, 1),
new RectangleF(5, 2, 1, 1),
};
var hit = rectangles
.Select(x =>
{
if (IsBetween(point.X, x.Left, x.Left + x.Width))
return new { Rectangle = x, Distance = GetClosestDistance(point.Y, x.Top - x.Height, x.Top) as float? };
else if (IsBetween(point.X, x.Top - x.Height, x.Top))
return new { Rectangle = x, Distance = GetClosestDistance(point.Y, x.Left, x.Left + x.Width) as float? };
else return new { Rectangle = x, Distance = default(float?) };
})
.Where(x => x.Distance != null)
.OrderBy(x => x.Distance)
.FirstOrDefault()?.Rectangle;
bool IsBetween(float value, float lBound, float uBound) => lBound <= value && value <= uBound;
float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));
Edit:
var hit = RayTest(point, rectangles, RayDirections.Right | RayDirections.Down) // or, try just `Down`
.Where(x => x.Success)
.OrderBy(x => x.Distance)
.FirstOrDefault()?.Target;
[Flags]
public enum RayDirections { None = 0, Left = 1 << 0, Up = 1 << 1, Right = 1 << 2, Down = 1 << 3, All = (1 << 4) - 1 }
public class RayHit<T>
{
public T Target { get; }
public float? Distance { get; }
public bool Success => Distance.HasValue;
public RayHit(T target, float? distance)
{
this.Target = target;
this.Distance = distance;
}
}
public IEnumerable<RayHit<RectangleF>> RayTest(PointF point, IEnumerable<RectangleF> rectangles, RayDirections directions = RayDirections.All)
{
if (directions == RayDirections.None)
return Enumerable.Empty<RayHit<RectangleF>>();
var ray = new
{
Horizontal = new
{
LBound = directions.HasFlag(RayDirections.Left) ? float.MinValue : point.X,
UBound = directions.HasFlag(RayDirections.Right) ? float.MaxValue : point.X,
},
Vertical = new
{
LBound = directions.HasFlag(RayDirections.Down) ? float.MinValue : point.Y,
UBound = directions.HasFlag(RayDirections.Up) ? float.MaxValue : point.Y,
},
};
return rectangles
.Select(x =>
{
float left = x.Left, right = x.Left + x.Width;
float top = x.Top, bottom = x.Top - x.Height;
if (IsBetween(point.X, left, right) && (IsBetween(top, ray.Vertical.LBound, ray.Vertical.UBound) || IsBetween(bottom, ray.Vertical.LBound, ray.Vertical.UBound)))
return new RayHit<RectangleF>(x, GetClosestDistance(point.Y, bottom, top) as float?);
else if (IsBetween(point.X, bottom, top) && (IsBetween(left, ray.Horizontal.LBound, ray.Horizontal.UBound) || IsBetween(right, ray.Horizontal.LBound, ray.Horizontal.UBound)))
return new RayHit<RectangleF>(x, GetClosestDistance(point.Y, left, right) as float?);
else return new RayHit<RectangleF>(x, default);
});
bool IsBetween(float value, float lBound, float uBound) => lBound <= value && value <= uBound;
float GetClosestDistance(float value, float p0, float p1) => Math.Min(Math.Abs(p0 - value), Math.Abs(p1 - value));
}
note: In both version, there is a bug when the point is inside the rectangle. The distance computed will be the distance to the closest edge, instead of 0 or negative value.
I am writing an Xamarin.iOS app, and found some Swift code for a loading spinner that I would like to use. So I am trying to convert the Swift code to C#. I hope this question is ok because I've been trying to convert this code for hours. This is the Swift code:
public class CircularProgressView: UIView {
public dynamic var progress: CGFloat = 0 {
didSet {
progressLayer.progress = progress
}
}
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
override public class var layerClass: AnyClass {
return CircularProgressLayer.self
}
override public func action(for layer: CALayer, forKey event: String) -> CAAction? {
if event == #keyPath(CircularProgressLayer.progress),
let action = action(for: layer, forKey: #keyPath(backgroundColor)) as? CAAnimation {
let animation = CABasicAnimation()
animation.keyPath = #keyPath(CircularProgressLayer.progress)
animation.fromValue = progressLayer.progress
animation.toValue = progress
animation.beginTime = action.beginTime
animation.duration = action.duration
animation.speed = action.speed
animation.timeOffset = action.timeOffset
animation.repeatCount = action.repeatCount
animation.repeatDuration = action.repeatDuration
animation.autoreverses = action.autoreverses
animation.fillMode = action.fillMode
animation.timingFunction = action.timingFunction
animation.delegate = action.delegate
self.layer.add(animation, forKey: #keyPath(CircularProgressLayer.progress))
}
return super.action(for: layer, forKey: event)
}
}
fileprivate class CircularProgressLayer: CALayer {
#NSManaged var progress: CGFloat
let startAngle: CGFloat = 1.5 * .pi
let twoPi: CGFloat = 2 * .pi
let halfPi: CGFloat = .pi / 2
override class func needsDisplay(forKey key: String) -> Bool {
if key == #keyPath(progress) {
return true
}
return super.needsDisplay(forKey: key)
}
override func draw(in ctx: CGContext) {
super.draw(in: ctx)
UIGraphicsPushContext(ctx)
//Light Grey
UIColor.lightGray.setStroke()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let strokeWidth: CGFloat = 4
let radius = (bounds.size.width / 2) - strokeWidth
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true)
path.lineWidth = strokeWidth
path.stroke()
//Red
UIColor.red.setStroke()
let endAngle = (twoPi * progress) - halfPi
let pathProgress = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle , clockwise: true)
pathProgress.lineWidth = strokeWidth
pathProgress.lineCapStyle = .round
pathProgress.stroke()
UIGraphicsPopContext()
}
}
let circularProgress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
UIView.animate(withDuration: 2, delay: 0, options: .curveEaseInOut, animations: {
circularProgress.progress = 0.76
}, completion: nil)
This is what I have so far:
public class CircularProgressView : UIView
{
// I doubt I made these properties correctly?
public float Progress
{
get
{
return ProgressLayer.Progress;
}
set
{
ProgressLayer.Progress = value;
}
}
public CircularProgressLayer ProgressLayer { get; set; }
public override Foundation.NSObject ActionForLayer(CALayer layer, string eventKey)
{
// I don't understand this part
return base.ActionForLayer(layer, eventKey);
}
}
public class CircularProgressLayer : CALayer
{
public float Progress { get; set; }
private nfloat startAngle = (nfloat)(1.5f * Math.PI);
private nfloat twoPi = (nfloat)(2 * Math.PI);
private double halfPi = Math.PI / 2;
public override bool NeedsDisplay
{
get
{
return base.NeedsDisplay;
}
}
public override void DrawInContext(CoreGraphics.CGContext ctx)
{
base.DrawInContext(ctx);
UIGraphics.PushContext(ctx);
UIColor.LightGray.SetStroke();
var center = new CGPoint(x: Bounds.GetMidX(), y: Bounds.GetMidY());
float strokeWidth = 4;
nfloat radius = (Bounds.Size.Width / 2) - strokeWidth;
var path = UIBezierPath.FromArc(center, radius: radius, startAngle: 0, endAngle: twoPi, clockwise: true);
path.LineWidth = strokeWidth;
path.Stroke();
UIColor.Red.SetStroke();
nfloat endAngle = (nfloat)((twoPi * Progress) - halfPi);
var pathProgress = UIBezierPath.FromArc(center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true);
pathProgress.LineWidth = strokeWidth;
pathProgress.LineCapStyle = CGLineCap.Round;
pathProgress.Stroke();
UIGraphics.PopContext();
}
}
There's a few parts that I do not understand, like this one:
if event == #keyPath(CircularProgressLayer.progress)
and this one:
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
I'm very good at C# but am new to Swift. I hope this question is ok because I've been trying to convert this code for hours.
Update:
I needed to create #dynamic / NSManaged in a Xamarin.iOS prototype and ended up being able to do this by creating them at runtime, see my comment here:
https://bugzilla.xamarin.com/show_bug.cgi?id=38823#c5
And related gist:
https://gist.github.com/sushihangover/327ba6282b2d41d8e10001b7e461bb21
So while Xamarin.iOS does not support them out-of-the-box, you can create them at runtime if needed.
Original:
Currently you can not generate dynamic properties (#dynamic / NSManaged) using Xamarin.iOS so the code you posted will not work even if converted.
Issue: https://bugzilla.xamarin.com/show_bug.cgi?id=38823
If manually constructing CAAction/CAAnimation objects is a valid option for your needs, you can review the Xamarin.iOS CustomPropertyAnimation example app:
https://github.com/xamarin/ios-samples/tree/master/CustomPropertyAnimation
This is the compiler friendly way for expressing key paths in Swift. Keypath is a series of properties in the consecutive objects (eg. company.manager.fullName). If you want to learn more, read about the KVC. This expression produces a String, in your case, it will be "progress". I think you can replace it with a simple comparison.
#keyPath(CircularProgressLayer.progress)
This is the read-only property which exposes the layer property as an instance of the CircularProgressLayer. The x as! y means: cast x to y or crash if not possible.
fileprivate var progressLayer: CircularProgressLayer {
return layer as! CircularProgressLayer
}
I'm writing a small graph writing program as a personal project.
Each point is supposed to get drawn on a wpf canvas but I'm having trouble translating the points from the graph's coordinate system, example: x = -8 to 4 y=-4 to 4, to the canvas' coordinate system example: x = 600 to 0 y = 400 to 0.
I'm using the method outlined here to precompute the transformation equations.
However I'm having trouble with the result of the multiplication v=M^-1*u.
My expected result is:
[0.2 ]
[0.0 ]
[-8.0]
[4.0 ]
But the result I'm getting is:
[4.0 ]
[-4.0 ]
[0.01 ]
[-0.006]
I've verified that my transformation matrix is correct and when I do the calculation by hand I'm getting the expected result.
The method to calculate the transformation equations:
private void CalculateTransformationFunctions()
{
// Define the transformation matrix
var transformationMatrix = new Matrix4x4
{
M11 = _destArea.XMin,
M12 = _destArea.YMin,
M13 = 1,
M14 = 0,
M21 = -_destArea.YMin,
M22 = _destArea.XMin,
M23 = 0,
M24 = 1,
M31 = _destArea.XMax,
M32 = _destArea.YMax,
M33 = 1,
M34 = 0,
M41 = -_destArea.YMax,
M42 = _destArea.XMax,
M43 = 0,
M44 = 1
};
// Define the source vector
var srcVector = new Vector4
{
X = _srcArea.XMin,
Y = _srcArea.YMax,
Z = _srcArea.XMax,
W = _srcArea.YMin
};
// Invert the transformationmatrix before the multiplication
Matrix4x4 invertedTransformationMatrix;
if(!Matrix4x4.Invert(transformationMatrix,out invertedTransformationMatrix))
throw new Exception();
// Returns the wrong value
var transformResult = Vector4.Transform(srcVector, invertedTransformationMatrix);
float a = transformResult.X,
b = transformResult.Y,
c = transformResult.Z,
d = transformResult.W;
_xTransformationFunction = (x, y) => (a*x + b*y - b*d - a*c)/(a*a + b*b);
_yTransformationFunction = (x, y) => (b*x - a*y - b*c + a*d)/(a*a + b*b);
}
Which is called in the constructor of its parent class.
My question:
Am I misunderstanding what Vector4.Transform() does here? Or am I completely blind and missing something very obvious?
Full source of the class:
using System;
using System.Numerics;
using System.Windows;
using System.Windows.Media;
using Grapher.Control.Grapher;
namespace Grapher.GraphingMath
{
public class Translator
{
private GraphingArea _srcArea;
private GraphingArea _destArea;
public GraphingArea SourceArea
{
get
{
return _srcArea;
}
set
{
_srcArea = value;
CalculateTransformationFunctions();
}
}
public GraphingArea DestinationArea
{
get { return _destArea; }
set
{
_destArea = value;
CalculateTransformationFunctions();
}
}
private Func<double, double, double> _xTransformationFunction;
private Func<double, double, double> _yTransformationFunction;
public Translator(GraphingArea sourceArea, GraphingArea destArea)
{
_destArea = destArea;
_srcArea = sourceArea;
CalculateTransformationFunctions();
}
public Point TranslatePoint(Point point)
{
var x = point.X;
var y = point.Y;
return new Point
{
X = _xTransformationFunction(x, y),
Y = _yTransformationFunction(x, y)
};
}
/*
x1 y1 1 0
-y1 x1 0 1
M= x2 y2 1 0
-y2 x2 0 1
x1,y1 = dest_min
x2,y2 = dest_max
*/
private void CalculateTransformationFunctions()
{
// Define the transformation matrix
var transformationMatrix = new Matrix4x4
{
M11 = _destArea.XMin,
M12 = _destArea.YMin,
M13 = 1,
M14 = 0,
M21 = -_destArea.YMin,
M22 = _destArea.XMin,
M23 = 0,
M24 = 1,
M31 = _destArea.XMax,
M32 = _destArea.YMax,
M33 = 1,
M34 = 0,
M41 = -_destArea.YMax,
M42 = _destArea.XMax,
M43 = 0,
M44 = 1
};
// Define the source vector
var srcVector = new Vector4
{
X = _srcArea.XMin,
Y = _srcArea.YMax,
Z = _srcArea.XMax,
W = _srcArea.YMin
};
// Invert the transformationmatrix before the multiplication
Matrix4x4 invertedTransformationMatrix;
if(!Matrix4x4.Invert(transformationMatrix,out invertedTransformationMatrix))
throw new Exception();
// Returns the wrong value
var transformResult = Vector4.Transform(srcVector, invertedTransformationMatrix);
float a = transformResult.X,
b = transformResult.Y,
c = transformResult.Z,
d = transformResult.W;
_xTransformationFunction = (x, y) => (a*x + b*y - b*d - a*c)/(a*a + b*b);
_yTransformationFunction = (x, y) => (b*x - a*y - b*c + a*d)/(a*a + b*b);
}
}
}
And for the graphing area struct:
using System;
namespace Grapher.Control.Grapher
{
public struct GraphingArea
{
public float XMin { get; set; }
public float YMin { get; set; }
public float XMax { get; set; }
public float YMax { get; set; }
public float Width => Math.Abs(XMax - XMin);
public float Height => Math.Abs(YMax - YMin);
}
}
In my main method I call the Translator class like this:
Point testPoint = new Point {X = 0, Y = 0};
var srcArea = new GraphingArea
{
XMax = 4,
XMin = -8,
YMax = 4,
YMin = -4
};
var destArea = new GraphingArea
{
XMax = 600,
XMin = 0,
YMax = 400,
YMin = 0
};
var translator = new Translator(srcArea, destArea);
var translatedPoint = translator.TranslatePoint(testPoint);
Edit
Ended up just writing my own matrix multiplication method. I must be misunderstanding what Vector4.Transform() does...
Code here, for anyone interested:
using System.Numerics;
namespace Grapher.GraphingMath.MatrixAndVectorMath
{
public static class Matrix4x4Multiply
{
public static Vector4 Vector4Multiply(Matrix4x4 matrix, Vector4 vector)
{
var mat = new float[4, 4]
{
{matrix.M11, matrix.M12, matrix.M13, matrix.M14},
{matrix.M21, matrix.M22, matrix.M23, matrix.M24},
{matrix.M31, matrix.M32, matrix.M33, matrix.M34},
{matrix.M41, matrix.M42, matrix.M43, matrix.M44}
}; // We'll just wrap the matrix in a float so we can index it.
var vec = new float[4] {vector.X, vector.Y, vector.Z, vector.W}; // And the same with the vector
var result = new float[4] {0, 0, 0, 0};
for (var row = 0; row < mat.GetLength(0); row++)
{
for (var col = 0; col < mat.GetLength(1); col++)
{
result[row] += mat[row, col]*vec[col];
}
}
return new Vector4
{
X = result[0],
Y = result[1],
Z = result[2],
W = result[3]
};
}
}
}
I don't know if this will help but I do something along those lines in one of my projects. I don't use matrices though so it may not be what you are looking for. I simply store the extents of coordinates for the graph and the container (canvas) width and height. I then provide two extensions functions:
public static System.Windows.Point ConvertToScreen(this System.Windows.Point point, CartesianExtents2D extents, double containerWidth, double containerHeight)
{
var x = (point.X - extents.XMinimum) * containerWidth / (extents.XMaximum - extents.XMinimum);
var y = (extents.YMaximum - point.Y) * containerHeight / (extents.YMaximum - extents.YMinimum);
return new System.Windows.Point(x, y);
}
public static System.Windows.Point ConvertToReal(this System.Windows.Point point, CartesianExtents2D extents, double containerWidth, double containerHeight, )
{
var x = extents.XMinimum + (point.X * (extents.XMaximum - extents.XMinimum)) / containerWidth;
var y = extents.YMaximum - (point.Y * (extents.YMaximum - extents.YMinimum)) / containerHeight;
return new System.Windows.Point(x, y);
}
Call thus:
Point p = new Point();
p.ConvertToReal(...);
I'm hoping the contents of CartesianExtents2D is obvious - just min and max for x and y
I saved group of points on my panel to List<MyVector> savedPoints, then I calculated the the point with lowest coordinate y :
public void searchLowest()
{
MyVector temp;
double ylon = savedPoints[0].getY();
for (int i = 0; i < savedPoints.Count; i++)
{
if (savedPoints[i].getY() > ylon)
{
ylon = savedPoints[i].getY();
lowest = i;
}
}
temp = savedPoints[lowest];
}
after this I made a method to calculate polar angles :
public static double angle(MyVector vec1, MyVector vec2)
{
double angle = Math.Atan2(vec1.getY() - vec2.getY(), vec1.getX() - vec2.getX());
return angle;
}
now don't know how to use Gift wrapping algorithm in my case. The pseudocode on WikiPedia link is not really understandable for me, so I'm asking for help here.
I'm using C# and win forms (net.framework 4.0)
Thanks for any help.
Using this as a reference, here is teh code:
namespace GiftWrapping
{
using System.Drawing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
class Program
{
static void Main(string[] args)
{
List<Point> test = new List<Point>(
new Point[]
{
new Point(200,200), new Point(300,100), new Point(200,50), new Point(100,100),
new Point(200, 100), new Point(300, 200), new Point(250, 100),
});
foreach (Point point in ConvexHull(test))
{
Console.WriteLine(point);
}
Console.ReadKey();
}
public static List<Point> ConvexHull(List<Point> points)
{
if (points.Count < 3)
{
throw new ArgumentException("At least 3 points reqired", "points");
}
List<Point> hull = new List<Point>();
// get leftmost point
Point vPointOnHull = points.Where(p => p.X == points.Min(min => min.X)).First();
Point vEndpoint;
do
{
hull.Add(vPointOnHull);
vEndpoint = points[0];
for (int i = 1; i < points.Count; i++)
{
if ((vPointOnHull == vEndpoint)
|| (Orientation(vPointOnHull, vEndpoint, points[i]) == -1))
{
vEndpoint = points[i];
}
}
vPointOnHull = vEndpoint;
}
while (vEndpoint != hull[0]);
return hull;
}
private static int Orientation(Point p1, Point p2, Point p)
{
// Determinant
int Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
}
}
adaptation to your private classes, would be your homework.
Here is an implementation using the System.Windows.Point class in WindowsBase:
public struct PolarVector {
public double Radius { get; set; }
public double Angle { get; set; }
public override string ToString() {
return "{" + Radius + "," + Angle + "}";
}
}
private static void Main(string[] args) {
var points = new[] {
new Point {X = 0, Y = 0},
//new Point {X = 2, Y = 0},
new Point {X = 0, Y = 2},
new Point {X = 1.5, Y = 0.5},
new Point {X = 2, Y = 2},
};
foreach(var point in ConvexHull(points)) {
Console.WriteLine(point);
}
Console.WriteLine();
if(Debugger.IsAttached) {
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
public static IList<Point> ConvexHull(IList<Point> points) {
var pointOnHull = LeftMost(points);
var pointsOnHull = new List<Point>();
Point currentPoint;
do {
pointsOnHull.Add(pointOnHull);
currentPoint = points[0];
foreach(var nextPoint in points.Skip(1)) {
if (currentPoint == pointOnHull || IsLeft(nextPoint, pointOnHull, currentPoint)) {
currentPoint = nextPoint;
}
}
pointOnHull = currentPoint;
}
while (currentPoint != pointsOnHull[0]);
return pointsOnHull;
}
private static Point LeftMost(IEnumerable<Point> points) {
return points.Aggregate((v1, v2) => v2.X < v1.X ? v2 : v1);
}
private static bool IsLeft(Point nextPoint, Point lastPoint, Point currentPoint) {
var nextVector = ToPolar(nextPoint, lastPoint);
var currentVector = ToPolar(currentPoint, lastPoint);
return nextVector.Radius != 0 && Normalize(nextVector.Angle - currentVector.Angle) > 0;
}
private static PolarVector ToPolar(Point target, Point start) {
var vector = target - start;
return new PolarVector { Radius = Math.Sqrt((vector.Y * vector.Y) + (vector.X * vector.X)), Angle = Math.Atan2(vector.Y, vector.X)};
}
private static double Normalize(double radians) {
while(radians > Math.PI) {
radians -= 2*Math.PI;
}
while (radians < -Math.PI) {
radians += 2*Math.PI;
}
return radians;
}
For Gift Wrapping algorithm implementation, it is advisable that one uses Left test technique
// Left test implementation given by Petr
private static int Orientation(Point p1, Point p2, Point p)
{
// Determinant
int Orin = (p2.X - p1.X) * (p.Y - p1.Y) - (p.X - p1.X) * (p2.Y - p1.Y);
if (Orin > 0)
return -1; // (* Orientation is to the left-hand side *)
if (Orin < 0)
return 1; // (* Orientation is to the right-hand side *)
return 0; // (* Orientation is neutral aka collinear *)
}
Using this(Left test) comparison technique helps us in wrapping the gift faster, so to speak.
Never ever use arc tangent calculations, it will impact the run times in a big way.
Reference: Left test technique mentioned here - https://stackoverflow.com/a/1560510/1019673