Ortho Camera like Autocad with OpenGL - c#

I'm going to write a program to view dwg file. I'm using SharpGL library.
I'm wrote a mouse control camera for 3D.
This is a code:
private void Device_OpenGLDraw(object sender, SharpGL.SceneGraph.OpenGLEventArgs args)
{
OpenGL gl = args.OpenGL;
gl.ClearColor((float)s3.Value, (float)s2.Value, (float)s1.Value, 0f);
gl.Clear(OpenGL.GL_COLOR_BUFFER_BIT | OpenGL.GL_DEPTH_BUFFER_BIT);
gl.LoadIdentity();
gl.MatrixMode(OpenGL.GL_PROJECTION);
gl.LoadIdentity();
gl.Ortho(-wheelx, wheelx, -wheely, wheely, 0.1f, 1000000000.0f);
gl.MatrixMode(OpenGL.GL_MODELVIEW);
gl.Rotate(xrot, 1.0, 0.0, 0.0);
gl.Rotate(yrot, 0.0, 1.0, 0.0);
gl.Translate(-xpos, -ypos, -zpos);
DrawScene();
gl.Flush();
}
private void Device_MouseMove(object sender, MouseEventArgs e)
{
System.Windows.Point position = Mouse.GetPosition(Device);
x = (int)Math.Ceiling(position.X);
y = -(int)Math.Ceiling(position.Y);
if (e.MiddleButton == MouseButtonState.Pressed)
{
Cursor = Cursors.Hand;
diffx = x - lastx; //check the difference between the
diffy = y - lasty; //check the difference between the
xrot += (float)diffy * 0.05f * (float)Math.Min(wheelx, wheely); ; //set the xrot to xrot with the addition
yrot += (float)diffx * 0.05f * (float)Math.Min(wheelx, wheely); ; //set the xrot to yrot with the addition
if (xrot < 0)
{
xrot = 360;
}
if (xrot > 360)
{
xrot = 0;
}
if (yrot < 0)
{
yrot = 360;
}
if (yrot > 360)
{
yrot = 0;
}
}
if (e.LeftButton == MouseButtonState.Pressed)
{
diffx = x - lastx; //check the difference between the
diffy = y - lasty; //check the difference between the
float xrotrad, yrotrad;
yrotrad = (yrot / 180 * PI);
xrotrad = (xrot / 180 * PI);
float xs = (float)(Math.Tan(xrotrad));
float xc = (float)(Math.Cos(xrotrad));
float yc = (float)(Math.Cos(yrotrad));
float ys = (float)(Math.Sin(yrotrad));
ypos -= (xc * (float)diffy)* 0.003f * (float)Math.Min(wheelx, wheely);
zpos -= (-yc * (float)diffy + ys * (float)diffx) * 0.003f * (float)Math.Min(wheelx, wheely);
xpos += (-ys * (float)diffy - yc * (float)diffx) * 0.003f * (float)Math.Min(wheelx, wheely);
l5.Content = xc;
}
lastx = x; //set lastx to the current x position
lasty = y; //set lasty to the current y position
}
For 2D camera works a good. On 3D a have problem:
When X angle is between 0-180 degree, vertical moving works a good, but X angle is between 180-360 (ABOUT 315 degree) , vertical moving not working properly.
What am I doing wrong?

Related

Determining if a Line intersects with a rotated rectangle on a canvas

I have managed to find code that determines if a Line intersects with a rectangle. My problem is when the Rectangle is rotated. I have looked hi and low to find code that will give me the coordinates of the corners of the rectangle after the transform is performed with no luck. My rectangle when it is rotated is rotated around CenterX and CenterY of 0,0.
I'd appreciate any code that you may have that does this.
Thanks!
More information:
I am working on a program that I want to be able to select one or more rectangles on a canvas by drawing a line. The rectangles can be rotated.
I have tried the following code. The second function works properly for non rotated rectangles but not for rotated rectangles.:
public bool AdjustedIntersects(FrameworkElement elem, Line line)
{
double x = Canvas.GetLeft(elem);
double y = Canvas.GetTop(elem);
double X = x * Math.Cos(((RotateTransform)elem.RenderTransform).Angle)
- y * Math.Sin(((RotateTransform)elem.RenderTransform).Angle);
double Y = x * Math.Sin(((RotateTransform)elem.RenderTransform).Angle)
+ y * Math.Cos(((RotateTransform)elem.RenderTransform).Angle);
x = Canvas.GetLeft(elem) + elem.Width;
y = Canvas.GetTop(elem) + elem.Height;
double X2 = x * Math.Cos(((RotateTransform)elem.RenderTransform).Angle)
- y * Math.Sin(((RotateTransform)elem.RenderTransform).Angle);
double Y2 = x * Math.Sin(((RotateTransform)elem.RenderTransform).Angle)
+ y * Math.Cos(((RotateTransform)elem.RenderTransform).Angle);
return SegmentIntersectRectangle(X, Y,X2,Y2,
line.X1, line.Y1, line.X2, line.Y2);
}
public bool SegmentIntersectRectangle(
double rectangleMinX,
double rectangleMinY,
double rectangleMaxX,
double rectangleMaxY,
double p1X,
double p1Y,
double p2X,
double p2Y)
{
// Find min and max X for the segment
double minX = p1X;
double maxX = p2X;
if (p1X > p2X)
{
minX = p2X;
maxX = p1X;
}
// Find the intersection of the segment's and rectangle's x-projections
if (maxX > rectangleMaxX)
{
maxX = rectangleMaxX;
}
if (minX < rectangleMinX)
{
minX = rectangleMinX;
}
if (minX > maxX) // If their projections do not intersect return false
{
return false;
}
// Find corresponding min and max Y for min and max X we found before
double minY = p1Y;
double maxY = p2Y;
double dx = p2X - p1X;
if (Math.Abs(dx) > 0.0000001)
{
double a = (p2Y - p1Y) / dx;
double b = p1Y - a * p1X;
minY = a * minX + b;
maxY = a * maxX + b;
}
if (minY > maxY)
{
double tmp = maxY;
maxY = minY;
minY = tmp;
}
// Find the intersection of the segment's and rectangle's y-projections
if (maxY > rectangleMaxY)
{
maxY = rectangleMaxY;
}
if (minY < rectangleMinY)
{
minY = rectangleMinY;
}
if (minY > maxY) // If Y-projections do not intersect return false
{
return false;
}
return true;
}
A polygon intersects with a line if one of lines of the poligon overlap with the line. Then try this:
Find four conners of the rectangle by the above way.
Check if one of lines make from sequence of conners overlap with the line. I have a ideal for it:
private bool IsStraightLineOverlap(System.Windows.Shapes.Line line1, Line line2)
{
var line1Min_X = Math.Min(line1.X1, line1.X2);
var line1Max_X = Math.Max(line1.X1, line1.X2);
var line1Min_Y = Math.Min(line1.Y1, line1.Y2);
var line1Max_Y = Math.Max(line1.Y1, line1.Y2);
var line2Min_X = Math.Min(line2.X1, line2.X2);
var line2Max_X = Math.Max(line2.X1, line2.X2);
var line2Min_Y = Math.Min(line2.Y1, line2.Y2);
var line2Max_Y = Math.Max(line2.Y1, line2.Y2);
var isOverlap_X = (line1Min_X <= line2Max_X && line1Max_X >= line2Min_X);
var isOverlap_Y = (line1Min_Y <= line2Max_Y && line1Max_Y >= line2Min_Y);
return isOverlap_X && isOverlap_Y;
}
In our game suite I have to find rotated points for various purposes.
Here's an extension method:
public static class PointExtension
{
public static Point GetRotatedPoint(this Point point, Point centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180.0);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
(cosTheta * (point.X - centerPoint.X) -
sinTheta * (point.Y - centerPoint.Y) + centerPoint.X),
Y =
(int)
(sinTheta * (point.X - centerPoint.X) +
cosTheta * (point.Y - centerPoint.Y) + centerPoint.Y)
};
}
}
I also have to work out the cells on a grid that a line passes through.
To do this I use a bresenham line algorithm.
You can google this and find various implementations.
Here's mine:
public static IEnumerable<Point> GetOrderedPointsOnLine(int x0, int y0, int x1, int y1)
{
bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
if (steep)
{
Swap<int>(ref x0, ref y0);
Swap<int>(ref x1, ref y1);
}
int dx = Math.Abs(x1 - x0);
int dy = Math.Abs(y1 - y0);
int error = (dx / 2);
int ystep = (y0 < y1 ? 1 : -1);
int xstep = (x0 < x1 ? 1 : -1);
int y = y0;
for (int x = x0; x != (x1 + xstep); x += xstep)
{
yield return new Point((steep ? y : x), (steep ? x : y));
error = error - dy;
if (error < 0)
{
y += ystep;
error += dx;
}
}
yield break;
}
The outline of a rectangle is of course 4 lines.
There is an edge case where both an edge and the line can be a variation of 4 degrees and intersection exactly picks cells don't match.
To obviate that you could use a bresenham variation which picks both cells the line partially passes through.
Or you can build two geometries and see if you get anything overlaps when you use the wpf library to detect intersection.
I'm not sure what happens if you apply a transform to a geometry and then use geometry.fillcontainswithdetail
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.geometry.fillcontainswithdetail?redirectedfrom=MSDN&view=net-5.0#overloads
There's also combinedgeometry to consider.
Pick the right options and it'll only give you what overlaps between 2 geometries.
I use neither because in game has to be very fast.
For pre game calculations I render offscreen and examine the bytes of the image I get. This is the fastest simplest way I've found to see which cells (px) an irregular shape occupies.
And another way you could approach this.
Solution 1: break the problem down into something that is easier to solve:
Pick any edge on the rectangle, represented by points P1 and P2.
Translate both the points in your rectangle and the points that define your line by -P1, so that the P1 point is now at the origin.
Calculate the angle of your edge i.e. Math.Atan2(deltaY, deltaX).
Rotate the points for both the rectangle and line in the opposite direction, so that you now have an axis-aligned rectangle.
Do your line/rectangle hit test between these new primitives, using the algorithm you already have for axis-aligned rectangles.
If you need the actual point of intersection in proper world space coordinates then rotate it forward by the angle and translate by +P1.
Solution 2: test the line against each line that forms the rectangle, your problem is now 4 line-to-line intersection tests.
Disclaimer: this is not c#. I only know javascript but the math should be the same.
The way I attack this is by creating an array for my shape that I use to store each vertex. I then use those points to determine where the objects boundaries are. I am assuming that translate and rotate work the same in c# and the objects coordinate are always axis-aligned.
When I draw my rectangle I draw it with the x (left) as -width/2 and y (top) as -height/2. This is because I am going to use translate to position it where I want it and also allow it to rotate from the center.
ctx.save();
ctx.translate(this.x, this.y)
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width/2, -this.height/2, this.width, this.height)
ctx.restore();
I don't know if c# uses save and restore but it would be the same as just translating and rotating it back after i.e.
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width/2, -this.height/2, this.width, this.height);
ctx.rotate(-this.rotation);
ctx.translate(-this.x, -this.y)
Also you'll notice in the snippet code that I created an array to store my x and y coordinates of each vertex
this.vertices = [];
for (let i = 0; i < 4; i++) {
//initially set to (0, 0) and updated in updateVertices()
this.vertices.push({ x: 0, y: 0 });
}
This is part of my rectangle object by the array could be global also.
The part that really matters is the math associated to updating the position of the vertices. In this snippet I use a function called updateVertices(). In this function I need to first calculate the sin and cos based on the current rotation.
let cos = Math.cos(this.rotation); //passing in radians in JS
let sin = Math.sin(this.rotation); //passing in radians in JS
Once I have that I just update the array of vertices that I created
//update Top Left Corner
this.vertices[0].x =
(this.x - this.centerX) * cos -
(this.y - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[0].y =
(this.x - this.centerX) * sin +
(this.y - this.centerY) * cos +
(this.centerY - this.height / 2);
Do that with all four corners. The math is slightly different for each vertex.
That's it. You have an array with all 4 vertices and can use them how you want. In this example I iterate over them passing two (adjacent) at time to my intersectLines() function to see if my vector lines intersects. Since I am testing 4 edges I use a loop to test all four sides against my vector, I do this in my passToIntersectFunction() function.
In this snippet you can use the mouse to move the vector around and see how the intersect points move.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let ptX, ptY;
let intersectPoints = [];
let mouse = {
x: null,
y: null
};
canvas.addEventListener("mousemove", (e) => {
mouse.x = e.x - canvas.getBoundingClientRect().x;
mouse.y = e.y - canvas.getBoundingClientRect().y;
});
class Square {
constructor() {
this.x = 100;
this.y = 100;
this.width = 50;
this.height = 50;
this.centerX = this.x + this.width / 2;
this.centerY = this.y + this.height / 2;
this.color = "red";
this.angle = 0;
this.rotation = (this.angle * Math.PI) / 180; //convert to rads
//used to store all four vertices
this.vertices = [];
for (let i = 0; i < 4; i++) {
//initially set to (0, 0) and updated in updateVertices()
this.vertices.push({ x: 0, y: 0 });
}
}
draw() {
this.angle += 0.5;
this.rotation = (this.angle * Math.PI) / 180;
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.fillStyle = this.color;
ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
ctx.rotate(-this.rotation);
ctx.translate(-this.x, -this.y);
}
drawIntersectPoint() {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(ptX, ptY, 3, 0, Math.PI * 2);
ctx.fill();
}
drawVertices() {
ctx.fillStyle = "blue";
ctx.beginPath();
for (let i = 0; i < this.vertices.length; i++) {
ctx.arc(this.vertices[i].x, this.vertices[i].y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
updateVertices() {
let cos = Math.cos(this.rotation);
let sin = Math.sin(this.rotation);
//update Top Left Corner
this.vertices[0].x =
(this.x - this.centerX) * cos -
(this.y - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[0].y =
(this.x - this.centerX) * sin +
(this.y - this.centerY) * cos +
(this.centerY - this.height / 2);
//updates Top Right Corner
this.vertices[1].x =
(this.x + this.width - this.centerX) * cos -
(this.y - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[1].y =
(this.x + this.width - this.centerX) * sin +
(this.y - this.centerY) * cos +
(this.centerY - this.height / 2);
//updates Bottom Right Corner
this.vertices[2].x =
(this.x + this.width - this.centerX) * cos -
(this.y + this.height - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[2].y =
(this.x + this.width - this.centerX) * sin +
(this.y + this.height - this.centerY) * cos +
(this.centerY - this.height / 2);
//updates Bottom Left Corner
this.vertices[3].x =
(this.x - this.centerX) * cos -
(this.y + this.height - this.centerY) * sin +
(this.centerX - this.width / 2);
this.vertices[3].y =
(this.x - this.centerX) * sin +
(this.y + this.height - this.centerY) * cos +
(this.centerY - this.height / 2);
}
}
let square = new Square();
class Vector {
constructor() {
this.x1 = 200;
this.y1 = 100;
this.x2 = mouse.x;
this.y2 = mouse.y;
}
draw() {
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
ctx.lineTo(this.x2, this.y2);
ctx.stroke();
}
updateVector() {
this.x2 = mouse.x;
this.y2 = mouse.y;
this.draw();
}
}
let vector = new Vector();
function intersectLines(coord1, coord2, vector) {
//this if statement just keeps the array from constantly growing
if (intersectPoints.length > 2) {
intersectPoints.shift();
}
let x1 = coord1.x;
let x2 = coord2.x;
let y1 = coord1.y;
let y2 = coord2.y;
let x3 = vector.x1;
let x4 = vector.x2;
let y3 = vector.y1;
let y4 = vector.y2;
let d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (d == 0) {
return;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / d;
let u = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d;
if (t > 0 && t < 1 && u > 0) {
intersectPoints.push({ x: x1 + t * (x2 - x1), y: y1 + t * (y2 - y1) });
}
return;
}
function passToIntersectFunction() {
for (let i = 0; i < square.vertices.length; i++) {
intersectLines(
square.vertices[i],
square.vertices[i + 1] ?? square.vertices[0],
vector
);
}
}
function drawIntersectPoints() {
for (let i = 0; i < intersectPoints.length; i++) {
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(intersectPoints[i].x, intersectPoints[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
square.draw();
square.updateVertices();
square.drawIntersectPoint();
square.drawVertices();
vector.updateVector();
drawIntersectPoints();
passToIntersectFunction();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
Sorry its not c# but maybe it can help.

Creating quaternion from direction and up vectors for rotation matrix

How to calculate rotation matrix based on cone direction? I have a spotlight defined like this:
I'm creating vertices array for a cone to draw spot light shape and then I'm creating a buffer (line loop mode):
private static float[] createConeVerticesArray(float radius,
float height,
int segments)
{
Array<Float> verticesArray = new Array<>();
float angle = 2 * MathUtils.PI / segments;
float cos = MathUtils.cos(angle);
float sin = MathUtils.sin(angle);
float cx = radius, cy = 0;
for(int i = 0; i < segments; i++)
{
verticesArray.add(cx);
verticesArray.add(cy);
verticesArray.add(height);
verticesArray.add(0f);
verticesArray.add(0f);
verticesArray.add(0f);
verticesArray.add(cx);
verticesArray.add(cy);
verticesArray.add(height);
float temp = cx;
cx = cos * cx - sin * cy;
cy = sin * temp + cos * cy;
verticesArray.add(cx);
verticesArray.add(cy);
verticesArray.add(height);
}
verticesArray.add(cx);
verticesArray.add(cy);
verticesArray.add(height);
cx = radius;
cy = 0;
verticesArray.add(cx);
verticesArray.add(cy);
verticesArray.add(height);
float[] result = new float[verticesArray.size];
for(int i = 0; i < verticesArray.size; i++)
{
result[i] = verticesArray.get(i);
}
return result;
}
Next step I'm creating three spot lights like this which represents 3 rotations around each axis:
this.spotLights.add(new CSpotLight(
new Color(1f, 0f, 0f, 1f), //Color
new Vector3(0f, 0f, 5000f), //Position
1f, //Intensity
new Vector3(0f, 0f, -1f), //Direction
15f, //Inner angle
30f, //Outer angle
4000f, //Radius
1f)); //Attenuation
this.spotLights.add(new CSpotLight(
new Color(1f, 0f, 0f, 1f),
new Vector3(0f, 5000f, 0f),
1f,
new Vector3(0f, -1f, 0f),
15f,
30f,
4000f,
1f));
this.spotLights.add(new CSpotLight(
new Color(1f, 0f, 0f, 1f),
new Vector3(5000f, 0f, 0f),
1f,
new Vector3(-1f, 0f, 0f),
15f,
30f,
4000f,
1f));
And then I'm calculating quaternion for each spot light to create rotation matrix for model matrix (just for vertex shader purpose):
private static Quaternion quaternionLookRotation(Vector3 direction, Vector3 up)
{
Vector3 vector = Pools.obtain(Vector3.class).set(direction).nor();
Vector3 vector2 = Pools.obtain(Vector3.class).set(up).crs(vector).nor();
Vector3 vector3 = Pools.obtain(Vector3.class).set(vector).crs(vector2);
float m00 = vector2.x;
float m01 = vector2.y;
float m02 = vector2.z;
float m10 = vector3.x;
float m11 = vector3.y;
float m12 = vector3.z;
float m20 = vector.x;
float m21 = vector.y;
float m22 = vector.z;
Pools.free(vector);
Pools.free(vector2);
Pools.free(vector3);
float num8 = (m00 + m11) + m22;
Quaternion quaternion = Pools.obtain(Quaternion.class);
if (num8 > 0f)
{
float num = (float) Math.sqrt(num8 + 1f);
quaternion.w = num * 0.5f;
num = 0.5f / num;
quaternion.x = (m12 - m21) * num;
quaternion.y = (m20 - m02) * num;
quaternion.z = (m01 - m10) * num;
return quaternion;
}
if ((m00 >= m11) && (m00 >= m22))
{
float num7 = (float) Math.sqrt(((1f + m00) - m11) - m22);
float num4 = 0.5f / num7;
quaternion.x = 0.5f * num7;
quaternion.y = (m01 + m10) * num4;
quaternion.z = (m02 + m20) * num4;
quaternion.w = (m12 - m21) * num4;
return quaternion;
}
if (m11 > m22)
{
float num6 = (float) Math.sqrt(((1f + m11) - m00) - m22);
float num3 = 0.5f / num6;
quaternion.x = (m10+ m01) * num3;
quaternion.y = 0.5f * num6;
quaternion.z = (m21 + m12) * num3;
quaternion.w = (m20 - m02) * num3;
return quaternion;
}
float num5 = (float) Math.sqrt(((1f + m22) - m00) - m11);
float num2 = 0.5f / num5;
quaternion.x = (m20 + m02) * num2;
quaternion.y = (m21 + m12) * num2;
quaternion.z = 0.5f * num5;
quaternion.w = (m01 - m10) * num2;
return quaternion;
}
this.rotMatrix.set(quaternionLookRotation(
spotLight.getDirection(),
new Vector3(0, 1, 0)));
Two rotations from three works perfectly. The problem appears only in the third rotation axis which should faced point (0, 0, 0):
Here is my final code which works fine in any cases:
private static Quaternion rotationBetweenTwoVectors(Vector3 vec1, Vector3 vec2)
{
Quaternion result = Pools.obtain(Quaternion.class);
Vector3 u = Pools.obtain(Vector3.class).set(vec1).nor();
Vector3 v = Pools.obtain(Vector3.class).set(vec2).nor();
float dot = u.dot(v);
if(dot < -0.999999)
{
Vector3 tmp1 = Pools.obtain(Vector3.class).set(Vector3.X).crs(u);
if(tmp1.len() < 0.000001)
{
tmp1.set(Vector3.Y).crs(u);
}
tmp1.nor();
result.setFromAxisRad(tmp1, MathUtils.PI).nor();
Pools.free(tmp1);
}
else if(dot > 0.999999)
{
result.idt();
}
else
{
result.setFromCross(u, v);
}
Pools.free(u);
Pools.free(v);
return result;
}
Where setFromCross calculates axis from cross product of two vectors and angle like this:
final float dot = MathUtils.clamp(Vector3.dot(x1, y1, z1, x2, y2, z2), -1f, 1f);
final float angle = (float)Math.acos(dot);
Hope this will helps somebody :)

how to enable scrolling when zoom in and zoom out on a content page in xamarin forms

i am trying to zoom in and zoom out on a content page using xamarin.forms.
I am able zoom in and zoom out but the problem is scrolling is not working.
i want zoom an image. with this code zooming is working perfectly. But while zooming i am not able to see full image. i must scroll to view the rest of the image. for that i need to scroll. but scrolling is not working.
XAML
xmlns:helper="clr-namespace:KPGTC.Deals.Mobile.Helpers"
<helper:PinchToZoomContainer>
<helper:PinchToZoomContainer.Content>
<Image x:Name="img_Popup"/>
</helper:PinchToZoomContainer.Content>
</helper:PinchToZoomContainer>
Code:
public class PinchToZoomContainer : ContentView
{
double MIN_SCALE = 1;
double MAX_SCALE = 4;
double startScale = 1;
double currentScale = 1;
double xOffset = 0;
double yOffset = 0;
bool _isActive = false;
public PinchToZoomContainer()
{
DependencyService.Get<IHelpers>().ShowAlert("Double-tap to zoom");
//var _pinchGesture = new PinchGestureRecognizer();
//_pinchGesture.PinchUpdated += OnPinchUpdated;
//GestureRecognizers.Add(_pinchGesture);
var _tapGesture = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
_tapGesture.Tapped += On_Tapped;
GestureRecognizers.Add(_tapGesture);
var _panGesture = new PanGestureRecognizer();
_panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(_panGesture);
TranslationX = 0;
TranslationY = 0;
_isActive = false;
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (_isActive)
{
if (e.TotalX > 0)
{
if (e.TotalX > 2)
{
TranslationX += 15;
}
}
else
{
if (e.TotalX < -2)
{
TranslationX -= 15;
}
}
}
}
private void On_Tapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
_isActive = false;
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
_isActive = true;
AnchorX = AnchorY = 0.5;
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Alright, this was quite a tough one and admittedly I don't understand fully how I made it, but I made it.
Some thoughts:
You mixed the translation of the container and the content, which is quite tricky to handle - if this is possible at all
When panning, you added 15 every time the pan event was raised, but there is a better way: Just store the initial offset of the content and then add the TotalX and TotalY respectively to the TranslationX and the TranslationY of the content (this was the easy part)
Panning while zooming was quite hard to get right and I had to find it out by trial and error
Basically you have to store the origin of the pinch gesture when the gesture starts and calculate the diff between the original origin and the current origin
Then you have to add the diff (multiplied by the with and height respectively of the control) to the target translation
Here is the code for the panning:
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (e.StatusType == GestureStatus.Started)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
if (e.StatusType != GestureStatus.Completed
&& e.StatusType != GestureStatus.Canceled)
{
this.Content.TranslationX = this.xOffset + e.TotalX;
this.Content.TranslationY = this.yOffset + e.TotalY;
}
if (e.StatusType == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
And here for the pinching
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
this.startScale = this.Content.Scale;
this.Content.AnchorX = 0;
this.Content.AnchorY = 0;
this.startScaleOrigin = e.ScaleOrigin;
}
if (e.Status == GestureStatus.Running)
{
var originDiff = PinchToZoomContainer.CalculateDiff(e.ScaleOrigin, this.startScaleOrigin);
this.currentScale += (e.Scale - 1) * this.startScale;
this.currentScale = Math.Max(1, this.currentScale);
double renderedX = this.Content.X + this.xOffset;
double deltaX = renderedX / this.Width;
double deltaWidth = this.Width / (this.Content.Width * this.startScale);
double originX = (this.startScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = this.Content.Y + this.yOffset;
double deltaY = renderedY / this.Height;
double deltaHeight = this.Height / (this.Content.Height * this.startScale);
double originY = (startScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = this.xOffset - ((originX) * this.Content.Width) * (this.currentScale - this.startScale) - originDiff.X * this.Content.Width;
double targetY = this.yOffset - ((originY) * this.Content.Height) * (this.currentScale - this.startScale) - originDiff.Y * this.Content.Height;
this.Content.TranslationX = targetX.Clamp(-this.Content.Width * (this.currentScale - 1), 0);
this.Content.TranslationY = targetY.Clamp(-this.Content.Height * (this.currentScale - 1), 0);
this.Content.Scale = this.currentScale;
}
if (e.Status == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
(Of course you have to add Point startScaleOrigin to your class).
Finally you need the method to calculate the distance between two points
private static Point CalculateDiff(Point first, Point second)
{
return second.Offset(-first.X, -first.Y);
}
Unfortunately I did not manage to get the tapping right, but I think you should be able to figure it out from here.

Calculating a reflected arc

I am using the following code to calculate points for a circle arc drawn with a Line Renderer.
for (int i = 0; i <= pts; i++)
{
float x = center.x + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
float y = center.y + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
arcLine.positionCount = i + 1;
arcLine.SetPosition(i, new Vector2(x, y));
ang += (float)totalAngle / pts;
}
How can I change the angle ang to create a reflected arc along the line P1P2 as in the image below?
Please note that totalAngle represents the portion of the circle that is to be drawn between 0 and 360.
I'm not entirely sure that's possible, but I've come up with another way that works like this:
First, a helper function:
Vector2 GetPosition (float radius, float angle)
{
angle *= Mathf.Deg2Rad;
return new Vector2
{
x = radius * Mathf.Cos(angle),
y = radius * Mathf.Sin(angle)
};
}
Then, compute positions p1 and p2:
var p1 = GetPosition(radius, ang);
var p2 = GetPosition(radius, totalAngle);
To derive the mid-point p3:
var p3 = (p1 + p2) * 0.5f;
And finally rotate the original point about p3 to obtain the reflected point:
var pos = p3 * 2f - GetPosition(radius, ang);
And that's it! Your code should look something like this:
void Draw ()
{
var p1 = GetPosition(radius, ang);
var p2 = GetPosition(radius, totalAngle);
var p3 = (p1 + p2) * 0.5f;
for (int i = 0; i <= pts; i++)
{
var pos = p3 * 2f - GetPosition(radius, ang);
arcLine.positionCount = i + 1;
arcLine.SetPosition(i, center + pos);
ang += totalAngle / pts;
}
}
Vector2 GetPosition (float radius, float angle)
{
angle *= Mathf.Deg2Rad;
return new Vector2
{
x = radius * Mathf.Cos(angle),
y = radius * Mathf.Sin(angle)
};
}
Here's it in action:

How do i draw anywhere in a graphics object after it is transformed

I'm trying to create a canvas for drawing different objects. I've created zoom and pan functions using graphics.scaleTransform and graphics.translateTransform, but i wan't the canvas background (a grid) to allways fill out the entire window, however it does not, using the following code:
EDIT: I've tried using the coordinates in the transformed graphics object, but it seems it won't accept negative numbers?!?
EDIT: This picture explains my problem:
public partial class Form1 : Form
{
PointF mouseDown;
float newX;
float newY;
float zoomFactor = 1F;
Region _rgn;
Graphics _dc;
PointF zoomPoint = new PointF(150, 150);
public Form1()
{
InitializeComponent();
mouseDown = new PointF(0F, 0F);
this.panel1.Paint += new PaintEventHandler(panel1_Paint);
this.panel1.MouseDown += new System.Windows.Forms.MouseEventHandler(panel1_MouseDown);
this.panel1.MouseMove += new System.Windows.Forms.MouseEventHandler(panel1_MouseMove);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
//Graphics bg =
Graphics dc = e.Graphics;
_dc = dc;
dc.SmoothingMode = SmoothingMode.AntiAlias;
Color gridColor = Color.FromArgb(230, 230, 230);
Pen gridPen = new Pen(gridColor, 1);
for (float i = 0; i < this.Height * (zoomFactor); i = i + 30*zoomFactor)
{
dc.DrawLine(gridPen, 0, i, this.Width * (zoomFactor), i);
}
for (float i = 0; i < this.Width * (zoomFactor); i = i + 30*zoomFactor)
{
dc.DrawLine(gridPen, i, 0, i, this.Height * (zoomFactor));
}
dc.TranslateTransform(newX, newY);
dc.ScaleTransform(zoomFactor, zoomFactor, MatrixOrder.Prepend);
float XPosition = 10;
float YPosition = 10;
float CornerRadius = 5;
float Width = 50;
float Height = 50;
Color BoxColor = Color.FromArgb(0, 0, 0);
Pen BoxPen = new Pen(BoxColor, 2);
GraphicsPath Path = new GraphicsPath();
Path.AddLine(XPosition + CornerRadius, YPosition, XPosition + Width - (CornerRadius * 2), YPosition);
Path.AddArc(XPosition + Width - (CornerRadius * 2), YPosition, CornerRadius * 2, CornerRadius * 2, 270, 90);
Path.AddLine(XPosition + Width, YPosition + CornerRadius, XPosition + Width, YPosition + Height - (CornerRadius * 2));
Path.AddArc(XPosition + Width - (CornerRadius * 2), YPosition + Height - (CornerRadius * 2), CornerRadius * 2, CornerRadius * 2, 0, 90);
Path.AddLine(XPosition + Width - (CornerRadius * 2), YPosition + Height, XPosition + CornerRadius, YPosition + Height);
Path.AddArc(XPosition, YPosition + Height - (CornerRadius * 2), CornerRadius * 2, CornerRadius * 2, 90, 90);
Path.AddLine(XPosition, YPosition + Height - (CornerRadius * 2), XPosition, YPosition + CornerRadius);
Path.AddArc(XPosition, YPosition, CornerRadius * 2, CornerRadius * 2, 180, 90);
Path.CloseFigure();
LinearGradientBrush lgb = new LinearGradientBrush(new PointF(XPosition+(Width/2),YPosition), new PointF(XPosition+(Width/2),YPosition + Height), Color.RosyBrown, Color.Red);
dc.FillPath(lgb, Path);
dc.DrawPath(BoxPen, Path);
Matrix transformMatrix = new Matrix();
transformMatrix.Translate(newX, newY);
transformMatrix.Scale(zoomFactor, zoomFactor);
_rgn = new Region(Path);
_rgn.Transform(transformMatrix);
}
private void panel1_MouseDown(object sender, EventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
if (mouse.Button == MouseButtons.Right)
{
mouseDown = mouse.Location;
mouseDown.X = mouseDown.X - newX;
mouseDown.Y = mouseDown.Y - newY;
}
else if (mouse.Button == MouseButtons.Left)
{
if (_rgn.IsVisible(mouse.Location, _dc))
{
MessageBox.Show("tada");
}
}
}
private void panel1_MouseMove(object sender, EventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
if (mouse.Button == MouseButtons.Right)
{
PointF mousePosNow = mouse.Location;
float deltaX = mousePosNow.X - mouseDown.X;
float deltaY = mousePosNow.Y - mouseDown.Y;
newX = deltaX;
newY = deltaY;
panel1.Invalidate();
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
MouseEventArgs mouse = e as MouseEventArgs;
PointF mP = mouse.Location;
if (e.Delta > 0)
{
if (zoomFactor >= 1 && zoomFactor <= 10)
{
zoomFactor += 1F;
newX = newX - ((mP.X - newX) / (zoomFactor - 1));
newY = newY - ((mP.Y - newY) / (zoomFactor - 1));
}
else if (zoomFactor == 0.5)
{
zoomFactor = zoomFactor * 2;
newX = 2 * newX - mP.X ;
newY = 2 * newY - mP.Y ;
}
else if (zoomFactor < 0.5)
{
zoomFactor = zoomFactor * 2;
newX = 2 * newX - mP.X;
newY = 2 * newY - mP.Y;
}
}
else if (e.Delta < 0)
{
if (zoomFactor >2)
{
zoomFactor -= 1F;
newX = newX + (((mP.X - newX)) / (zoomFactor+1 ));
newY = newY + (((mP.Y - newY)) / (zoomFactor+1));
}
else if (zoomFactor == 2) {
zoomFactor -= 1F;
newX = newX + ((mP.X - newX)/2);
newY = newY + ((mP.Y - newY)/2);
}else if(zoomFactor <= 1 && zoomFactor > 0.2)
{
zoomFactor = zoomFactor / 2;
newX = newX + ((mP.X - newX) / 2);
newY = newY + ((mP.Y - newY) / 2);
}
}
panel1.Invalidate();
}
}
Draw your GRID BEFORE transforming the coordinate system.
If you can't, use GetTransform() and ResetTransform(), then draw grid, then SetTransform back (the one got in the first step).
IMHO: It would be much better if you scale you image 'manually' instead of using coordinate transformation. You'll have much greater control this way, and won't hit the wall with anything.
EDIT:
Also: you have a bug in your grid drawing routine:
for (float i = 0; i < this.Height * (zoomFactor); i = i + 30*zoomFactor)
try replacing with
for (float i = 0; i < this.Height; i = i + 30*zoomFactor)

Categories

Resources