how to draw a line with arrow with varying stroke width values - xamarin

I have drawn arrow when i try to increase arrow stroke width , it looks wierd.Two lines are overlapping and thickness applies from center. Is there any other way to apply strokewidth outwards.
I have referred below link to draw an arrow,
How do I draw an arrowhead (in Android)?
public class Arrow: View
{
float x0 = 300, y0 = 1000, x1 = 600, y1 = 200;
internal static int DENSITY = -1;
public Arrow(Context con):base(con)
{
DENSITY = (int)con.Resources.DisplayMetrics.Density;
}
protected override void OnDraw(Canvas canvas)
{
Paint paint = new Paint();
paint.StrokeWidth = 10 * Arrow.DENSITY;
float angle, anglerad, radius, lineangle;
radius = 45;
angle = 45;
//calculate line angles
anglerad = (float)(Math.Pi * angle / 180.0f);
lineangle = (float)(Math.Atan2(y1 - y0, x1 - x0));
Path mArrow = new Android.Graphics.Path();
mArrow.MoveTo(x1, y1);
var a1 = (float)(x1 - radius * Math.Cos(lineangle - (anglerad / 2.0)));
var a2 = (float)(y1 - radius * Math.Sin(lineangle - (anglerad / 2.0)));
mArrow.LineTo(a1, a2);
mArrow.MoveTo(a1, a2);
mArrow.MoveTo(x1, y1);
var a3 = (float)(x1 - radius * Math.Cos(lineangle + (anglerad / 2.0)));
var a4 = (float)(y1 - radius * Math.Sin(lineangle + (anglerad / 2.0)));
mArrow.LineTo(a3, a4);
paint.AntiAlias = true;
paint.SetStyle(Android.Graphics.Paint.Style.Stroke);
canvas.DrawPath(mArrow, paint);
canvas.DrawLine(x0, y0, x1, y1, paint);
base.OnDraw(canvas);
}
}

You can use QuadTo to replace the LineTo here, and since this api add a quadratic bezier from the last point, approaching control point (x1,y1), and ending at (x2,y2). Be careful with the start point and the last point of the lines.
So you can replace your code:
mArrow.MoveTo(x1, y1);
var a1 = (float)(x1 - radius * Math.Cos(lineangle - (anglerad / 2.0)));
var a2 = (float)(y1 - radius * Math.Sin(lineangle - (anglerad / 2.0)));
mArrow.LineTo(a1, a2);
mArrow.MoveTo(a1, a2);
mArrow.MoveTo(x1, y1);
var a3 = (float)(x1 - radius * Math.Cos(lineangle + (anglerad / 2.0)));
var a4 = (float)(y1 - radius * Math.Sin(lineangle + (anglerad / 2.0)));
mArrow.LineTo(a3, a4);
To:
var a1 = (float)(x1 - radius * Java.Lang.Math.Cos(lineangle - (anglerad / 2.0)));
var a2 = (float)(y1 - radius * Java.Lang.Math.Sin(lineangle - (anglerad / 2.0)));
mArrow.MoveTo(a1, a2);
mArrow.QuadTo(a1, a2, x1, y1);
var a3 = (float)(x1 - radius * Java.Lang.Math.Cos(lineangle + (anglerad / 2.0)));
var a4 = (float)(y1 - radius * Java.Lang.Math.Sin(lineangle + (anglerad / 2.0)));
mArrow.QuadTo(x1, y1, a3, a4);
I changed the paint.Color to make it clear by my side:

Related

Rotation of a point around another point in pitch axis in C

I use the following code (from stackoverflow) to rotate point around another point.
It work great for the yaw rotation axis.
How do I modifiy this code for a pitch axis rotation?
double x1 = point.x - center.x;
double y1 = point.y - center.y;
double x2 = x1 * Math.cos(angle) - y1 * Math.sin(angle));
double y2 = x1 * Math.sin(angle) + y1 * Math.cos(angle));
point.x = x2 + center.x;
point.y = y2 + center.y;

Calculating points around circle

I have a Big Circle and several small circles around it as seen in picture
First I'm drawing middle small circle like this:
cxSmallMiddle = cxBig + radiusBig + hDist + radiusSmall;
sySmallMiddle = radiusBig;
cxBig is center of Big circle. hDist is the distance I want every small circle to be from big circle.
So this way now middle small circle's middle point is parallel to big circle's.
Now I want to draw next small circle with hDist from big circle and vDist (vertical distance) from middle small circle.
So this way hDist and vDist will control the distance small circles are separated from big circle and gap between small circles accordingly.
how can I find cx and cy for other buttons?
This is a hand drawn finished version
Edit: added a code suggested by #Gene
#Override
public void onDraw(Canvas canvas) {
float radiusBig = 110f * singleDp;
float cxBig = screenWidth / 2f;
//float cyBig = screenHeight / 2f;
float cyBig = radiusBig + strokeWidth + (20*singleDp);
canvas.drawCircle(cxBig, cyBig, radiusBig, paint);
float radiusSmall = 20 * singleDp;
float vDist = 0 * singleDp;
float hDist = 0 * singleDp;
float acPoint = radiusBig;
float bcPoint = radiusSmall + vDist;
float theta = (float) Math.acos(bcPoint / acPoint);
int i = 0;
double x_i = acPoint * Math.cos(i * theta) + cxBig;
double y_i = acPoint * Math.sin(i * theta) + cyBig;
canvas.drawCircle((float) x_i, (float) y_i, radiusSmall, paint);
i = 1;
x_i = acPoint * Math.cos(i * theta) + cxBig;
y_i = acPoint * Math.sin(i * theta) + cyBig;
canvas.drawCircle((float) x_i, (float) y_i, radiusSmall, paint);
}
I experimented a lot with this code and this is what I got. When I draw i=0 is almost 45 degree distance from i=0. While experimenting, I discovered if I specify vDist = 80; then it looks okay. The bigger the vDist the closer it gets to i=0.
This is high school trigonometry. There's a right triangle formed by the big circle center (A), the small circle center (C), and the point (B) on the horizontal radius directly below the small circle center.
The length of edge BC is vDist + 2 * radiusSmall. The length of AC is radiusBig
Let \theta be the angle BAC. Then
sin(\theta) = BC / AC = (vDist + 2 * radiusSmall) / radiusBig.
So you can determine \theta:
\theta = arcsin((vDist + radiusSmall) / radiusBig)
Once you have \theta, the locations of the circles wrt the origin are
x_i = radiusBig * cos(i * \theta)
y_i = radiusBig * sin(i * \theta)
For i = 0, +1, -1, +2, -2, ...
Edit
Okay here is a quick hack in Java Swing. Sorry in the original post I said arccos when I meant arcsin.
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Circles extends JPanel {
public static void main(String[] a) {
JFrame f = new JFrame();
f.setSize(800, 800);
f.add(new Circles());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
#Override
public void paint(Graphics g) {
int cx = 400, cy = 400, rBig = 200, rSmall = 40, hDist = 20, vDist = 10;
drawCircle(g, cx, cy, rBig); // Big circle.
int rSmallCircleCenters = rBig + hDist + rSmall;
double theta = Math.asin(((double) vDist + 2 * rSmall) / rSmallCircleCenters);
int nPairs = 3;
for (int i = 1 - nPairs; i < nPairs; ++i) {
int dx = (int) (rSmallCircleCenters * Math.cos(i * theta));
int dy = (int) (rSmallCircleCenters * Math.sin(i * theta));
drawCircle(g, cx + dx, cy + dy, rSmall);
drawCircle(g, cx - dx, cy - dy, rSmall);
}
}
private void drawCircle(Graphics g, int cx, int cy, int r) {
g.drawOval(cx - r, cy - r, 2 * r, 2 * r);
}
}
Here's what it draws:

How to make a crescent moon shape in HTML canvas

I need to make the following shape in HTML5 canvas. I have tried using cubic bezier arcs and also clipping two circles.
How can I make this shape?
Here's my work in progress, just cant get it right
https://codepen.io/matt3224/pen/oeXbdg?editors=1010
var canvas = document.getElementById("canvas1");
var ctx1 = canvas.getContext("2d");
ctx1.lineWidth = 2;
ctx1.beginPath();
ctx1.bezierCurveTo(4, 42, 0, 0, 42, 4);
ctx1.moveTo(4, 42);
ctx1.bezierCurveTo(4, 42, 0, 84, 42, 84);
ctx1.stroke();
var canvas = document.getElementById("canvas2");
var ctx2 = canvas.getContext("2d");
ctx2.lineWidth = 2;
ctx2.beginPath();
ctx2.arc(55, 75, 50, 0, Math.PI * 2, true);
ctx2.moveTo(165, 75);
ctx2.arc(75, 75, 50, 0, Math.PI * 2, true);
ctx2.fill();
Circle circle boolean operation.
Incase anyone is interested in a programmatic solution the example below finds the intercept points of the two circles and uses those points to calculate the start and end angles for the outer and inner circle.
This is a little more flexible than a masking solution as it give you a path.
Snippet shows circle, move mouse over circle to see crescent solution. Not the stroke that would not be available if using a masking solution.
const PI2 = Math.PI * 2;
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = 400;
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
const m = mouse;
const bounds = canvas.getBoundingClientRect();
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : m.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
// generic circle circle intercept function. Returns undefined if
// no intercept.
// Circle 1 is center x1,y1 and radius r1
// Circle 2 is center x2,y2 and radius r2
// If points found returns {x1,y1,x2,y2} as two points.
function circleCircleIntercept(x1,y1,r1,x2,y2,r2){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
if(dist > r1 + r2 || dist < Math.abs(r1-r2)){
return; // no intercept return undefined
}
var a = (dist * dist - r1 * r1 + r2 *r2) / ( 2 * dist);
var b = Math.sqrt(r2 * r2 - a * a);
a /= dist;
x *= a;
y *= a;
var mx = x2 - x;
var my = y2 - y;
dist = b / Math.sqrt(x * x + y * y);
x *= dist;
y *= dist;
return {
x1 : mx-y,
y1 : my+x,
x2 : mx+y,
y2 : my-x,
};
}
// draws a crescent from two circles if possible
// If not then just draws the first circle
function drawCrescent(x1,y1,r1,x2,y2,r2){
// The circle circle intercept finds points
// but finding the angle of the points does not consider
// the rotation direction and you end up having to do a lot of
// checking (if statments) to determin the correct way to draw each circle
// the following normalises the direction the circle are from each other
// thus making the logic a lot easier
var dist = Math.hypot(x2-x1,y2-y1);
var ang = Math.atan2(y2-y1,x2-x1);
var intercepts = circleCircleIntercept(x1,y1,r1,x1 + dist,y1,r2);
if(intercepts === undefined){
ctx.beginPath();
ctx.arc(x1, y1, r1, 0, PI2);
if(dist < r1){
ctx.moveTo(x2 + r2, y2);
ctx.arc(x2, y2, r2, 0, PI2, true);
}
ctx.fill();
ctx.stroke();
return;
}
// get the start end angles for outer then inner circles
const p = intercepts;
var startA1 = Math.atan2(p.y1 - y1, p.x1 - x1) + ang;
var endA1 = Math.atan2(p.y2 - y1, p.x2 - x1) + ang;
var startA2 = Math.atan2(p.y1 - y1, p.x1 - (x1 + dist)) + ang;
var endA2 = Math.atan2(p.y2 - y1, p.x2 - (x1 + dist)) + ang;
ctx.beginPath();
if(endA1 < startA1){
ctx.arc(x1, y1, r1, startA1, endA1);
ctx.arc(x2, y2, r2, endA2, startA2, true);
}else{
ctx.arc(x2, y2, r2, endA2, startA2);
ctx.arc(x1, y1, r1, startA1, endA1,true);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
const outerRadius = 100;
const innerRadius = 80;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.lineJoin = "round";
ctx.lineWidth = 8;
ctx.strokeStyle = "#999";
// main update function
function mainLoop(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black";
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "white";
ctx.fillText("Move mouse over circle",cw,40);
drawCrescent(cw, ch-40, outerRadius, mouse.x, mouse.y, innerRadius);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
Solved it using globalCompositeOperation
https://codepen.io/matt3224/pen/oeXbdg?editors=1010

calculate 3D coordinates from UV coordinates

I'm trying to convert the position of an object in THREE.js from a flat 2D coordinate (an annotation placed on a 2D image) to a 3D coordinate, when the image is wrapped as a texture around a 3D shape. The idea would be to place a small 3D object at equivalent 3D coordinates to represent the 2D annotation.
I can do the reverse by getting the uv.x and uv.y properties of the object that is intersected by a raycaster which is really neat.
Is the above possible in THREE? I would need to be able to account for different shape geometries.
Figured it out:
Given the UV coords of the point that you want to locate:
First you need to traverse through each triangle in the model recursively, passing in the UV coords from each, and compare against the face vertex UV array using barycentric technique to see if the point exists inside the triangle according to the UVs.
Once the correct triangle is located, you can find the related vertices for the triangle and apply the barycentric coordinates to it to get the coordinate in local space.
Then convert to world space.
Code as follows, a bit rough and ready but you get the idea:
// Recursively traverse through the model.
var traversePolygonsForGeometries = function (node, uvx, uvy) {
if (node.geometry) {
// Return a list of triangles that have the point within them.
// The returned objects will have the x,y,z barycentric coordinates of the point inside the respective triangle
var baryData = annotationTest(uvx, uvy, node.geometry.faceVertexUvs);
if (baryData.length) {
for (var j = 0; j < baryData.length; j++) {
// In my case I only want to return materials with certain names.
if (node.geometry.faces[baryData[j][0]].daeMaterial ===
"frontMaterial" || node.geometry.faces[baryData[j][0]].daeMaterial ===
"print_area1_0"
) {
// Find the vertices corresponding to the triangle in the model
var vertexa = node.geometry.vertices[node.geometry.faces[baryData[j][0]].a];
var vertexb = node.geometry.vertices[node.geometry.faces[baryData[j][0]].b];
var vertexc = node.geometry.vertices[node.geometry.faces[baryData[j][0]].c];
// Sum the barycentric coordinates and apply to the vertices to get the coordinate in local space
var worldX = vertexa.x * baryData[j][1] + vertexb.x * baryData[j][2] + vertexc.x * baryData[j][3];
var worldY = vertexa.y * baryData[j][1] + vertexb.y * baryData[j][2] + vertexc.y * baryData[j][3];
var worldZ = vertexa.z * baryData[j][1] + vertexb.z * baryData[j][2] + vertexc.z * baryData[j][3];
var vector = new THREE.Vector3(worldX, worldY, worldZ);
// Translate to world space
var worldVector = vector.applyMatrix4(node.matrixWorld);
return worldVector;
}
}
}
}
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
var worldVectorPoint = traversePolygonsForGeometries(node.children[i], uvx, uvy);
if (worldVectorPoint) return worldVectorPoint;
}
}
};
// Loops through each face vertex UV item and tests if it is within the triangle.
function annotationTest(uvX, uvY, faceVertexUvArray) {
var point = {};
point.x = uvX;
point.y = uvY;
var results = [];
for (i = 0; i < faceVertexUvArray[0].length; i++) {
var result = ptInTriangle(point, faceVertexUvArray[0][i][0], faceVertexUvArray[0][i][1], faceVertexUvArray[0][i][2]);
if (result.length) {
results.push([i, result[0], result[1], result[2]]);
}
}
return results;
};
// This is a standard barycentric coordinate function.
function ptInTriangle(p, p0, p1, p2) {
var x0 = p.x;
var y0 = p.y;
var x1 = p0.x;
var y1 = p0.y;
var x2 = p1.x;
var y2 = p1.y;
var x3 = p2.x;
var y3 = p2.y;
var b0 = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)
var b1 = ((x2 - x0) * (y3 - y0) - (x3 - x0) * (y2 - y0)) / b0
var b2 = ((x3 - x0) * (y1 - y0) - (x1 - x0) * (y3 - y0)) / b0
var b3 = ((x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)) / b0
if (b1 > 0 && b2 > 0 && b3 > 0) {
return [b1, b2, b3];
} else {
return [];
}
};

scale rotating boundingbox

I recently started to develop a Windows Phone game with XNA. I have problem as you might have guessed collision detection. After looking up tutorials about all the types that can be achieved I decided I will go for the basic rectangular collision detection. I have a rotating sprite and a method that calculates the bounding box every time in the Update() method so I know where it's bounding box is then I simply check for intersection between all the lines of the box with all the lines of the other sprite's boxes. But since my box is appearing square shaped and my texture of that rotating sprite is Rectangular I wanna scale the bounding box so it will be closer to the texture's size. Here is what I have for calculating the corners of the rotating bounding box:
double baseAngle = Math.Atan(this.Height / this.Width);
double len = Math.Sqrt(this.Height * this.Height / 4 + this.Width * this.Width / 4);
Vector2 tr = new Vector2((float)(Math.Sin(baseAngle + this.Rotation) * len) + this.Position.X, (float)(Math.Cos(baseAngle + this.Rotation) * len) + this.Position.Y);
Vector2 tl = new Vector2((float)(Math.Sin(Math.PI - baseAngle + this.Rotation) * len) + this.Position.X, (float)(Math.Cos(Math.PI - baseAngle + this.Rotation) * len) + this.Position.Y);
Vector2 bl = new Vector2((float)(Math.Sin(Math.PI + baseAngle + this.Rotation) * len) + this.Position.X, (float)(Math.Cos(Math.PI + baseAngle + this.Rotation) * len) + this.Position.Y);
Vector2 br = new Vector2((float)(Math.Sin(2 * Math.PI - baseAngle + this.Rotation) * len) + this.Position.X, (float)(Math.Cos(2 * Math.PI - baseAngle + this.Rotation) * len) + this.Position.Y);`
any help would be appreciated. Thanks
when you scale, it only appears bigger widht and height are same. so bounding box is same as for original. try multypllying height and width with scale number where you calculate bounding box.
and you cannot rotate bounding box, you will have to use matrix.class but you can allways use circle collision.
circle collision
int circlesColliding(int x1, int y1, int radius1, int x2, int y2, int radius2) {
//compare the distance to combined radii
int dx = x2 - x1;
int dy = y2 - y1;
int radii = radius1 + radius2;
if ((dx * dx) + (dy * dy) < radii * radii) {
return true;
} else {
return false;
}
}

Resources