Ball to Ball Collision resolution - graphics

I was going through some collision detection tutorials on youtube, In one of the tutorial, the guy used the following code to resolve a collision between two balls:
/**
* Rotates coordinate system for velocities
*
* Takes velocities and alters them as if the coordinate system they're on was rotated
*
* #param Object | velocity | The velocity of an individual particle
* #param Float | angle | The angle of collision between two objects in radians
* #return Object | The altered x and y velocities after the coordinate system has been rotated
*/
function rotate(velocity, angle) {
const rotatedVelocities = {
x: velocity.x * Math.cos(angle) - velocity.y * Math.sin(angle),
y: velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
};
return rotatedVelocities;
}
/**
* Swaps out two colliding particles' x and y velocities after running through
* an elastic collision reaction equation
*
* #param Object | particle | A particle object with x and y coordinates, plus velocity
* #param Object | otherParticle | A particle object with x and y coordinates, plus velocity
* #return Null | Does not return a value
*/
function resolveCollision(particle, otherParticle) {
const xVelocityDiff = particle.velocity.x - otherParticle.velocity.x;
const yVelocityDiff = particle.velocity.y - otherParticle.velocity.y;
const xDist = otherParticle.x - particle.x;
const yDist = otherParticle.y - particle.y;
// Prevent accidental overlap of particles
if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {
// Grab angle between the two colliding particles
const angle = -Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x);
// Store mass in var for better readability in collision equation
const m1 = particle.mass;
const m2 = otherParticle.mass;
// Velocity before equation
const u1 = rotate(particle.velocity, angle);
const u2 = rotate(otherParticle.velocity, angle);
// Velocity after 1d collision equation
const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y };
const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y };
// Final velocity after rotating axis back to original location
const vFinal1 = rotate(v1, -angle);
const vFinal2 = rotate(v2, -angle);
// Swap particle velocities for realistic bounce effect
particle.velocity.x = vFinal1.x;
particle.velocity.y = vFinal1.y;
otherParticle.velocity.x = vFinal2.x;
otherParticle.velocity.y = vFinal2.y;
}
}
I've mostly understood this code. However, I'm unable to understand how this if condition is working to find out whether the balls have overlapped or not.
if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0)
Can somebody please explain?

By taking the differences of positions and velocities, you view everything in the frame of otherParticle. In that frame, otherParticle is standing still at the origin and particle is moving with velocityDiff. Here is how it looks like:
The term xVelocityDiff * xDist + yVelocityDiff * yDist is the dot product of the two vectors. This dot product is negative if velocityDiff points somewhat in the opposite direction of dist, i.e. if the particle is getting closer like in the above image. If the dot product is positive, the particle is moving away from otherParticle and you don't need to do anything.

Related

hi all . i am trying to run the bym2 model . at this stage, i have a problem.are you help me?

library(“rstan”)
library(“rstudioapi”)
library(“parallel”)
library(“brms”)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectgores())
library(pkgbuild) # load packAge
find_rtools() # should be TRUE, assuming you have Rtools 3.5
#fit model icar.stan to NYC census tracts neighborhood map
install.packages(‘tidyverse’, dependencies = TRUE)
install.packages(‘rstanarm’, dependencies = TRUE)
library(rstan);
library(tidyverse)
library(rstanarm)
"data {
int<lower=0> N;
int<lower=0> N_edges;
int<lower=1, upper=N> node1[N_edges]; // node1[i] adjacent to node2[i]
int<lower=1, upper=N> node2[N_edges]; // and node1[i] < node2[i]
int<lower=0> y[N]; // count outcomes
vector<lower=0>[N] E; // exposure
int<lower=1> K; // num covariates
matrix[N, K] x; // design matrix
real<lower=0> scaling_factor; // scales the variance of the spatial effects
}
transformed data {
vector[N] log_E = log(E);
}
parameters {
real beta0; // intercept
vector[K] betas; // covariates
real<lower=0> sigma; // overall standard deviation
real<lower=0, upper=1> rho; // proportion unstructured vs. spatially structured variance
vector[N] theta; // heterogeneous effects
vector[N] phi; // spatial effects
}
transformed parameters {
vector[N] convolved_re;
// variance of each component should be approximately equal to 1
convolved_re = sqrt(1 - rho) * theta + sqrt(rho / scaling_factor) * phi;
}
model {
y ~ poisson_log(log_E + beta0 + x * betas + convolved_re * sigma); // co-variates
// This is the prior for phi! (up to proportionality)
target += -0.5 * dot_self(phi[node1] - phi[node2]);
beta0 ~ normal(0.0, 1.0);
betas ~ normal(0.0, 1.0);
theta ~ normal(0.0, 1.0);
sigma ~ normal(0, 1.0);
rho ~ beta(0.5, 0.5);
// soft sum-to-zero constraint on phi)
sum(phi) ~ normal(0, 0.001 * N); // equivalent to mean(phi) ~ normal(0,0.001)
}
generated quantities {
real logit_rho = log(rho / (1.0 - rho));
vector[N] eta = log_E + beta0 + x * betas + convolved_re * sigma; // co-variates
vector[N] mu = exp(eta);
}"
options(mc.cores = parallel::detectCores())
library(INLA)
source(“mungecardata4stan.R”)
source(“iran_data.R”)
y = data$y;
E = data$E;
K = 1;
x = 0.1 * data$x;
nbs = mungeCARdata4stan(data$adj, data$num);
N = nbs$N;
node1 = nbs$node1;
node2 = nbs$node2;
N_edges = nbs$N_edges;
adj.matrix = sparseMatrix(i=nbs$node1,j=nbs$node2,x=1,symmetric=TRUE)
Q= Diagonal(nbs$N, rowSums(adj.matrix)) - adj.matrix
Q_pert = Q + Diagonal(nbs$N) * max(diag(Q)) * sqrt(.Machine$double.eps)
Q_inv = inla.qinv(Q_pert, constr=list(A = matrix(1,1,nbs$N),e=0))
scaling_factor = exp(mean(log(diag(Q_inv))))
scot_stanfit = stan(“bym2_predictor_plus_offset.stan”, data=list(N,N_edges,node1,node2,y,x,E,scaling_factor), warmup=5000, iter=6000);
Error in new_CppObject_xp(fields$.module, fields$.pointer, …) : **
** Exception: variable does not exist; processing stage=data initialization; variable name=N; base type=int (in ‘string’, line 3, column 2 to column 17)
In addition: Warning message:
In readLines(file, warn = TRUE) :
** incomplete final line found on ‘C:\Users\Uaer\Downloads\bym2_predictor_plus_offset.stan’
failed to create the sampler;** sampling not done
in my opinion , in source(“mungecardata4stan.R”) you should type the address of mungecardata4stan.R that is placed in your pc. and also for source(“iran_data.R”). like this: source("C:/Users/me/Desktop/iran_data.R").

Raytracer renders objects too large

I am following this course to learn computer graphics and write my first ray tracer.
I already have some visible results, but they seem to be too large.
The overall algorithm the course outlines is this:
Image Raytrace (Camera cam, Scene scene, int width, int height)
{
Image image = new Image (width, height) ;
for (int i = 0 ; i < height ; i++)
for (int j = 0 ; j < width ; j++) {
Ray ray = RayThruPixel (cam, i, j) ;
Intersection hit = Intersect (ray, scene) ;
image[i][j] = FindColor (hit) ;
}
return image ;
}
I perform all calculations in camera space (where the camera is at (0, 0, 0)). Thus RayThruPixel returns me a ray in camera coordinates, Intersect returns an intersection point also in camera coordinates, and the image pixel array is a direct mapping from the intersectionr results.
The below image is the rendering of a sphere at (0, 0, -40000) world coordinates and radius 0.15, and camera at (0, 0, 2) world coordinates looking towards (0, 0, 0) world coordinates. I would normally expect the sphere to be a lot smaller given its small radius and far away Z coordinate.
The same thing happens with rendering triangles too. In the below image I have 2 triangles that form a square, but it's way too zoomed in. The triangles have coordinates between -1 and 1, and the camera is looking from world coordinates (0, 0, 4).
This is what the square is expected to look like:
Here is the code snippet I use to determine the collision with the sphere. I'm not sure if I should divide the radius by the z coordinate here - without it, the circle is even larger:
Sphere* sphere = dynamic_cast<Sphere*>(object);
float t;
vec3 p0 = ray->origin;
vec3 p1 = ray->direction;
float a = glm::dot(p1, p1);
vec3 center2 = vec3(modelview * object->transform * glm::vec4(sphere->center, 1.0f)); // camera coords
float b = 2 * glm::dot(p1, (p0 - center2));
float radius = sphere->radius / center2.z;
float c = glm::dot((p0 - center2), (p0 - center2)) - radius * radius;
float D = b * b - 4 * a * c;
if (D > 0) {
// two roots
float sqrtD = glm::sqrt(D);
float root1 = (-b + sqrtD) / (2 * a);
float root2 = (-b - sqrtD) / (2 * a);
if (root1 > 0 && root2 > 0) {
t = glm::min(root1, root2);
found = true;
}
else if (root2 < 0 && root1 >= 0) {
t = root1;
found = true;
}
else {
// should not happen, implies sthat both roots are negative
}
}
else if (D == 0) {
// one root
float root = -b / (2 * a);
t = root;
found = true;
}
else if (D < 0) {
// no roots
// continue;
}
if (found) {
hitVector = p0 + p1 * t;
hitNormal = glm::normalize(result->hitVector - center2);
}
Here I generate the ray going through the relevant pixel:
Ray* RayThruPixel(Camera* camera, int x, int y) {
const vec3 a = eye - center;
const vec3 b = up;
const vec3 w = glm::normalize(a);
const vec3 u = glm::normalize(glm::cross(b, w));
const vec3 v = glm::cross(w, u);
const float aspect = ((float)width) / height;
float fovyrad = glm::radians(camera->fovy);
const float fovx = 2 * atan(tan(fovyrad * 0.5) * aspect);
const float alpha = tan(fovx * 0.5) * (x - (width * 0.5)) / (width * 0.5);
const float beta = tan(fovyrad * 0.5) * ((height * 0.5) - y) / (height * 0.5);
return new Ray(/* origin= */ vec3(modelview * vec4(eye, 1.0f)), /* direction= */ glm::normalize(vec3( modelview * glm::normalize(vec4(alpha * u + beta * v - w, 1.0f)))));
}
And intersection with a triangle:
Triangle* triangle = dynamic_cast<Triangle*>(object);
// vertices in camera coords
vec3 vertex1 = vec3(modelview * object->transform * vec4(*vertices[triangle->index1], 1.0f));
vec3 vertex2 = vec3(modelview * object->transform * vec4(*vertices[triangle->index2], 1.0f));
vec3 vertex3 = vec3(modelview * object->transform * vec4(*vertices[triangle->index3], 1.0f));
vec3 N = glm::normalize(glm::cross(vertex2 - vertex1, vertex3 - vertex1));
float D = -glm::dot(N, vertex1);
float m = glm::dot(N, ray->direction);
if (m == 0) {
// no intersection because ray parallel to plane
}
else {
float t = -(glm::dot(N, ray->origin) + D) / m;
if (t < 0) {
// no intersection because ray goes away from triange plane
}
vec3 Phit = ray->origin + t * ray->direction;
vec3 edge1 = vertex2 - vertex1;
vec3 edge2 = vertex3 - vertex2;
vec3 edge3 = vertex1 - vertex3;
vec3 c1 = Phit - vertex1;
vec3 c2 = Phit - vertex2;
vec3 c3 = Phit - vertex3;
if (glm::dot(N, glm::cross(edge1, c1)) > 0
&& glm::dot(N, glm::cross(edge2, c2)) > 0
&& glm::dot(N, glm::cross(edge3, c3)) > 0) {
found = true;
hitVector = Phit;
hitNormal = N;
}
}
Given that the output image is a circle, and that the same problem happens with triangles as well, my guess is the problem isn't from the intersection logic itself, but rather something wrong with the coordinate spaces or transformations. Could calculating everything in camera space be causing this?
I eventually figured it out by myself. I first noticed the problem was here:
return new Ray(/* origin= */ vec3(modelview * vec4(eye, 1.0f)),
/* direction= */ glm::normalize(vec3( modelview *
glm::normalize(vec4(alpha * u + beta * v - w, 1.0f)))));
When I removed the direction vector transformation (leaving it at just glm::normalize(alpha * u + beta * v - w)) I noticed the problem disappeared - the square was rendered correctly. I was prepared to accept it as an answer, although I wasn't completely sure why.
Then I noticed that after doing transformations on the object, the camera wasn't positioned properly, which makes sense - we're not pointing the rays in the correct direction.
I realized that my entire approach of doing the calculations in camera space was wrong. If I still wanted to use this approach, the rays would have to be transformed, but in a different way that would involve some complex math I wasn't ready to deal with.
I instead changed my approach to do transformations and intersections in world space and only use camera space at the lighting stage. We have to use camera space at some point, since we want to actually look in the direction of the object we are rendering.

Optimize quadratic curve tracing using numeric methods

I am trying to trace quadratic bezier curves, placing "markers" at a given step length distance. Tried to do it a naive way:
const p = toPoint(map, points[section + 1]);
const p2 = toPoint(map, points[section]);
const {x: cx, y: cy} = toPoint(map, cp);
const ll1 = toLatLng(map, p),
ll2 = toLatLng(map, p2),
llc = toLatLng(map, { x: cx, y: cy });
const lineLength = quadraticBezierLength(
ll1.lat,
ll1.lng,
llc.lat,
llc.lng,
ll2.lat,
ll2.lng
);
for (let index = 0; index < Math.floor(lineLength / distance); index++) {
const t = distance / lineLength;
const markerPoint = getQuadraticPoint(
t * index,
p.x,
p.y,
cx,
cy,
p2.x,
p2.y
);
const markerLatLng = toLatLng(map, markerPoint);
markers.push(markerLatLng);
}
This approach does not work since the correlation of a quadratic curve between t and L is not linear. I could not find a formula, that would give me a good approximation, so looking at solving this problem using numeric methods [Newton]. One simple option that I am considering is to split the curve into x [for instance 10] times more pieces than needed. After that, using the same quadraticBezierLength() function calculate the distance to each of those points. After this, chose the point so that the length is closest to the distance * index.
This however would be a huge overkill in terms of algorithm complexity. I could probably start comparing points for index + 1 from the subset after/without the point I selected already, thus skipping the beginning of the set. This would lower the complexity some, yet still very inefficient.
Any ideas and/or suggestions?
Ideally, I want a function that would take d - distance along the curve, p0, cp, p1 - three points defining a quadratic bezier curve and return an array of coordinates, implemented with the least complexity possible.
OK I found analytic formula for 2D quadratic bezier curve in here:
Calculate the length of a segment of a quadratic bezier
So the idea is simply binary search the parameter t until analytically obtained arclength matches wanted length...
C++ code:
//---------------------------------------------------------------------------
float x0,x1,x2,y0,y1,y2; // control points
float ax[3],ay[3]; // coefficients
//---------------------------------------------------------------------------
void get_xy(float &x,float &y,float t) // get point on curve from parameter t=<0,1>
{
float tt=t*t;
x=ax[0]+(ax[1]*t)+(ax[2]*tt);
y=ay[0]+(ay[1]*t)+(ay[2]*tt);
}
//---------------------------------------------------------------------------
float get_l_naive(float t) // get arclength from parameter t=<0,1>
{
// naive iteration
float x0,x1,y0,y1,dx,dy,l=0.0,dt=0.001;
get_xy(x1,y1,t);
for (int e=1;e;)
{
t-=dt; if (t<0.0){ e=0; t=0.0; }
x0=x1; y0=y1; get_xy(x1,y1,t);
dx=x1-x0; dy=y1-y0;
l+=sqrt((dx*dx)+(dy*dy));
}
return l;
}
//---------------------------------------------------------------------------
float get_l(float t) // get arclength from parameter t=<0,1>
{
// analytic fomula from: https://stackoverflow.com/a/11857788/2521214
float ax,ay,bx,by,A,B,C,b,c,u,k,cu,cb;
ax=x0-x1-x1+x2;
ay=y0-y1-y1+y2;
bx=x1+x1-x0-x0;
by=y1+y1-y0-y0;
A=4.0*((ax*ax)+(ay*ay));
B=4.0*((ax*bx)+(ay*by));
C= (bx*bx)+(by*by);
b=B/(2.0*A);
c=C/A;
u=t+b;
k=c-(b*b);
cu=sqrt((u*u)+k);
cb=sqrt((b*b)+k);
return 0.5*sqrt(A)*((u*cu)-(b*cb)+(k*log(fabs((u+cu))/(b+cb))));
}
//---------------------------------------------------------------------------
float get_t(float l0) // get parameter t=<0,1> from arclength
{
float t0,t,dt,l;
for (t=0.0,dt=0.5;dt>1e-10;dt*=0.5)
{
t0=t; t+=dt;
l=get_l(t);
if (l>l0) t=t0;
}
return t;
}
//---------------------------------------------------------------------------
void set_coef() // compute coefficients from control points
{
ax[0]= ( x0);
ax[1]= +(2.0*x1)-(2.0*x0);
ax[2]=( x2)-(2.0*x1)+( x0);
ay[0]= ( y0);
ay[1]= +(2.0*y1)-(2.0*y0);
ay[2]=( y2)-(2.0*y1)+( y0);
}
//---------------------------------------------------------------------------
Usage:
set control points x0,y0,...
then you can use t=get_t(wanted_arclength) freely
In case you want to use get_t_naive and or get_xy you have to call set_coef first
In case you want to tweak speed/accuracy you can play with the target accuracy of binsearch currently set to1e-10
Here optimized (merged get_l,get_t functions) version:
//---------------------------------------------------------------------------
float get_t(float l0) // get parameter t=<0,1> from arclength
{
float t0,t,dt,l;
float ax,ay,bx,by,A,B,C,b,c,u,k,cu,cb,cA;
// precompute get_l(t) constants
ax=x0-x1-x1+x2;
ay=y0-y1-y1+y2;
bx=x1+x1-x0-x0;
by=y1+y1-y0-y0;
A=4.0*((ax*ax)+(ay*ay));
B=4.0*((ax*bx)+(ay*by));
C= (bx*bx)+(by*by);
b=B/(2.0*A);
c=C/A;
k=c-(b*b);
cb=sqrt((b*b)+k);
cA=0.5*sqrt(A);
// bin search t so get_l == l0
for (t=0.0,dt=0.5;dt>1e-10;dt*=0.5)
{
t0=t; t+=dt;
// l=get_l(t);
u=t+b; cu=sqrt((u*u)+k);
l=cA*((u*cu)-(b*cb)+(k*log(fabs((u+cu))/(b+cb))));
if (l>l0) t=t0;
}
return t;
}
//---------------------------------------------------------------------------
For now, I came up with the below:
for (let index = 0; index < Math.floor(numFloat * times); index++) {
const t = distance / lineLength / times;
const l1 = toLatLng(map, p), lcp = toLatLng(map, new L.Point(cx, cy));
const lutPoint = getQuadraticPoint(
t * index,
p.x,
p.y,
cx,
cy,
p2.x,
p2.y
);
const lutLatLng = toLatLng(map, lutPoint);
const length = quadraticBezierLength(l1.lat, l1.lng, lcp.lat, lcp.lng, lutLatLng.lat, lutLatLng.lng);
lut.push({t: t * index, length});
}
const lut1 = lut.filter(({length}) => !isNaN(length));
console.log('lookup table:', lut1);
for (let index = 0; index < Math.floor(numFloat); index++) {
const t = distance / lineLength;
// find t closest to distance * index
const markerT = lut1.reduce((a, b) => {
return a.t && Math.abs(b.length - distance * index) < Math.abs(a.length - distance * index) ? b.t : a.t || 0;
});
const markerPoint = getQuadraticPoint(
markerT,
p.x,
p.y,
cx,
cy,
p2.x,
p2.y
);
const markerLatLng = toLatLng(map, markerPoint);
}
I think only that my Bezier curve length is not working as I expected.
function quadraticBezierLength(x1, y1, x2, y2, x3, y3) {
let a, b, c, d, e, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = a = 4 * (d * d + d1 * d1);
c1 += b = 4 * (d * e + d1 * e1);
c1 += c = e * e + e1 * e1;
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (
(a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) /
(4 * a1)
);
}
I believe that the full curve length is correct, but the partial length that is being calculated for the lookup table is wrong.
If I am right, you want points at equally spaced points in terms of curvilinear abscissa (rather than in terms of constant Euclidean distance, which would be a very different problem).
Computing the curvilinear abscissa s as a function of the curve parameter t is indeed an option, but that leads you to the resolution of the equation s(t) = Sk/n for integer k, where S is the total length (or s(t) = kd if a step is imposed). This is not convenient because s(t) is not available as a simple function and is transcendental.
A better method is to solve the differential equation
dt/ds = 1/(ds/dt) = 1/√(dx/dt)²+(dy/dt)²
using your preferred ODE solver (RK4). This lets you impose your fixed step on s and is computationally efficient.

How can I use Google Maps Circle, Rectangle and Polygon in Node JS?

Anyone knows if I am able to user Google Maps Circle, Rectangle and Polygon classes in Node JS? In the frontend is easy with Google Maps Javascript SDK, but I can't figure out how to get a hold of this library within Node JS.
I need to be able to check if points are with bounds, something in the lines of:
const location = google.maps.LatLng(lat, lng);
const circle = new google.maps.Circle({
center: area.center,
radius: area.radius,
});
const doesContain = circle.getBounds().contains(location);
Thanks ahead!
Alright boys, after giving some thought I realized it's easier to create my own code for checking if a geometry contains a point than depend on Google Maps library to do so.
Although this does not offer and the functionality Google Maps SDK offers, it does solve the geometry problem.
For anyone else looking for other Google Maps SDK functionalities, checkout this Node.js Client for Google Maps Services. Though it does not include the geometry functions I was looking for.
Solution
Without further ado here is my code:
class Circle {
/**
* Circle constructor
* #param {array} center Center coordinate [lat, lng]
* #param {number} radius Radius of the circle in meters
*/
constructor(center, radius) {
this.name = "Circle";
this.center = center;
this.radius = radius;
}
/**
* Checks if a point is within the circle
* #param {array} point Coordinates of a point [lat,lng]
* #returns true if point is within, false otherwhise
*/
contains(point) {
const { center, radius } = this;
const distance = this.distance(center, point);
if (distance > radius) return false;
return true;
}
/**
* Calculate the distance between two points (in meters)
* #param {array} p1 [lat,lng] point 1
* #param {array} p2 p1 [lat,lng] point 2
* #returns Distance between the points in meters
*/
distance(p1, p2) {
var R = 6378.137; // Radius of earth in KM
var dLat = (p2[0] * Math.PI) / 180 - (p1[0] * Math.PI) / 180;
var dLon = (p2[1] * Math.PI) / 180 - (p1[1] * Math.PI) / 180;
var a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((p1[0] * Math.PI) / 180) *
Math.cos((p2[0] * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d * 1000; // meters
}
}
class Rectangle {
/**
* Rectangle constructor
* #param {arrar} sw South-west coorodinate of the rectangle [lat,lng]
* #param {array} ne North-east coordinate of the rectangle [lat, lng]
*/
constructor(sw, ne) {
this.name = "Rectangle";
this.sw = sw;
this.ne = ne;
}
/**
* Checks if a point is within the reactangle
* #param {array} point Coordinates of a point [lat,lng]
* #returns true if point is within, false otherwhise
*/
contains(point) {
const { sw, ne } = this;
const x = point[0];
const y = point[1];
if (x < sw[0] || x > ne[0] || y < sw[1] || y > ne[1]) return false;
return true;
}
}
class Polygon {
/**
* Polygon constructor
* #param {array} points Array of vertices/points of the polygon [lat,lng]
*/
constructor(points) {
this.name = "Polygon";
this.points = points;
}
/**
*
* #returns {obj} Returns the coordinate of the min/max bounds that surounds the polygon
* (south-west coordinate, north-east coordinage as in [lat,lng] format)
*/
getBounds() {
const { points } = this;
let arrX = [];
let arrY = [];
for (let i in points) {
arrX.push(points[i][0]);
arrY.push(points[i][1]);
}
return {
sw: [Math.min.apply(null, arrX), Math.min.apply(null, arrY)],
ne: [Math.max.apply(null, arrX), Math.max.apply(null, arrY)],
};
}
/**
* Checks if a point is within the polygon
* #param {array} point Coordinates of a point [lat,lng]
* #returns true if point is within, false otherwhise
*/
contains(point) {
const x = point[0];
const y = point[1];
const bounds = this.getBounds();
// Check if point P lies within the min/max boundary of our polygon
if (x < bounds.sw[0] || x > bounds.ne[0] || y < bounds.sw[1] || y > bounds.ne[1])
return false;
let intersect = 0;
const { points } = this;
// Geofencing method (aka Even–odd rule)
// See more at: https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
// Now for each path of our polygon we'll count how many times our imaginary
// line crosses our paths, if it crosses even number of times, our point P is
// outside of our polygon, odd number our point is within our polygon
for (let i = 0; i < points.length; i++) {
// Check if pont P lies on a vertices of our polygon
if (x === points[i][0] && y === points[i][1]) return true;
let j = i !== points.length - 1 ? i + 1 : 0;
// Check if Py (y-component of our point P) is with the y-boundary of our path
if (
(points[i][1] < points[j][1] && y >= points[i][1] && y <= points[j][1]) ||
(points[i][1] > points[j][1] && y >= points[j][1] && y <= points[i][1])
) {
// Check if Px (x-componet of our point P) crosses our path
let sx =
points[i][0] +
((points[j][0] - points[i][0]) * (y - points[i][1])) /
(points[j][1] - points[i][1]);
if (sx >= x) intersect += 1;
}
}
return intersect % 2 === 0 ? false : true;
}
}
module.exports = { Circle, Rectangle, Polygon };
Explanation
The Circle and Rectangle class is pretty straight forward, it's trivial to determine if a point lies within a boundary. The Polygon class is a bit more complicated because of obvious reasons.
The method used here to determine if a point P is within a polygon is called Geofencing (aka Even–odd rule), a common method in geospacial analysis.
Step 1
First we check if the point P falls within the max/min boundaries of the polygon (image 1), if it doesn't, we return false, problem solved.
Image 1 -- Polygon boundaries, P1 is within the polygon boundaries, P2 is not.
Step 2
Then we check if the point lies on a vertices (points) of the polygon, if it does, we return true, problem solved. (Image 2)
Image 2 -- Polygon boundaries, point P is on a vertices, return true.
Step 3
This next step is the most gratifying one, by now we know the point is with the polygon boundaries (from step 1) but we don't know if it's within it or not. The way to solve this we cast an imaginary line departing from the point to any direction, if it crosses the path of polygon even number of times, the point is outside of the polygon, if it crosses an odd number of times, the point is within the polygon. Like so:
Image 3 -- An imaginary line from P1 crosses the polygon paths an odd number of times (3 times), it's within the polygon boundaries. A imaginary line from P2 crosses an even number of times (4 times), it lies outside of the polygon.
Since we can pick any direction we want to cast the imaginary line from, we'll pick along the x-axis to simplify things, like so:
Image 4 -- Casting the imaginary line from point P parallel to the x-axis t0 simplify determining how many times it intersects our polygon.
To determine how many times the imaginary line intersects our polygon, we have to check each path of the polygon at a time. To do this, we break it down into two steps (see image 5 for references):
For each segment/path of the polygon we check if our point Py (y-component of our point P) is within the the boundaries of the path in question (Y1 and Y2). If it is not, we know our point is does not intersects that specific path and we can move on to the next one. If it is within the path's y-boundaries, then we have to check if it crosses our path in the x-direction (next step).
Assuming the step before is true, to check intersection in the x-direction we have calculate the equation for the path (using line equation: y2 - y1 = m(x2 - x1)) and plug in our Py component to solve for our intersection (in my code I call this Sx). Then we check if Sx is greater than Px, if so, then our imaginary line intersects the path in the x positive direction.
It's important to note that the imaginary line starts at our point P and we only count intersections in that direction we originally picked, in this case x-axis+. This is why Sx has to be grater than or equal to Px, otherwise the test fails.
Image 5 -- We break down each path of the polygon to determine the number of intersections.
Once this path is done we move to the next one and so on. In this case the line crosses 3 times our paths, and therefore we know it's within our polygon.
This is a very clever and simple way if you think about it, it works for any shape, it's truly amazing.
Read more
https://en.wikipedia.org/wiki/Even%E2%80%93odd_rule
Examples
Example 1 - Simple shapes
const p = new Polygon([
[-3, 3],
[-4, 1],
[-3, 0],
[-2, -1],
[0, 0],
[3, 2],
[0, 1],
[-1, 4],
]);
console.log("Contains: ", p.contains([-1, 1])); // returns true
JSFiddle 1
Example 2 - Complex shapes (overlapping areas)
This method works for more complex shapes, when the polygon coordinates creates overlappping areas and they cancel each other out.
const p = new Polygon([
[-2, 0],
[2, 0],
[2, 4],
[-2, 4],
[-2, 0],
[0, 2],
[2, 0],
[0, -2],
[-2, 0],
]);
console.log("Contains: ", p.contains([0, 1])); // returns false
JSFiddle 2
Side note
If you need to quickly plot points just to get a view of a shape/grid, this plotting tool helped a lot to get a visual of what's going on. Very often I thought my code had a bug when in fact my coordinates was skewed and code was correct.
https://www.desmos.com/calculator
I only wish it let you draw lines between points. Either way I found it helpful.

Problems drawing an SVG arc path in a PDF using itextsharp

I'm trying to draw an SVG path in a PDF using itextsharp v5.
The approach I am following is roughly this:
Reading the SVG path from the SVG file (Svg.SvgPath)
Getting the list of segments from the path ({Svg.Pathing.SvgPathSegmentList})
Creating an iTextSharp PdfAnnotation and associate a PdfAppearance to it
Drawing each segment in the SvgPathSegmentList using the corresponding PdfContentByte method ( for SvgLineSegment I use PdfContentByte.LineTo, for SvgCubicCurveSegment I use PdfContentByte.CurveTo )
For most of the SvgPathSegments types, there is a clear mapping between values in the SvgPathSegments and the arguments in the PdfContentByte method. A few examples:
SvgMoveToSegment has the attribute End which is the target point (X, Y) and the PdfContentByte.MoveTo takes two parameters: X, Y
SvgLineSegment, very similar to the Move. It has the Target End and the PdfContentByte.LineTo takes two parameters X and Y and draws a line from the current position to the target point.
app.MoveTo(segment.Start.X, segment.Start.Y);
SvgCubicCurveSegment has all you need to create a Bezier curve (The Start point, the End point, and the first and second control point). With this I use PdfContentByte.CurveTo and get a curve in the PDF that looks exactly as it looks in the SVG editor.
var cubicCurve = (Svg.Pathing.SvgCubicCurveSegment)segment;
app.CurveTo(
cubicCurve.FirstControlPoint.X, cubicCurve.FirstControlPoint.Y,
cubicCurve.SecondControlPoint.X, cubicCurve.SecondControlPoint.Y,
cubicCurve.End.X, cubicCurve.End.Y);
The problem I have is with the ARC ("A" command in the SVG, SvgArcSegment)
The SvgArcSegment has the following values:
Angle
Start (X, Y)
End (X, Y)
RadiusX
RadiusY
Start
Sweep
On the other hand, PdfContentByte.Arc method expect:
X1, X2, Y1, Y2
StartAngle,
Extent
As per the itextsharp documentation, Arc draws a partial ellipse inscribed within the rectangle x1,y1,x2,y2 starting (counter-clockwise) at StartAngle degrees and covering extent degrees. I.e. startAng=0 and extent=180 yield an openside-down semi-circle inscribed in the rectangle.
My question is: How to "map" the values in the SvgArcSegment created from the SVG A command into the arguments that PdfContentByte.Arc method expects.
I know that the Start and End values are indeed the origin and target of the curve I want, but no clue what RadiusX and RadiusY mean.
As #RobertLongson pointed in his comment, what I needed was to convert from Center to Endpoint Parametrization.
I'm posting my own C# implementation of the algorithm documented in the SVG documentation, just in case someone else needs it.
public static SvgCenterParameters EndPointToCenterParametrization(Svg.Pathing.SvgArcSegment arc)
{
//// Conversion from endpoint to center parameterization as in SVG Implementation Notes:
//// https://www.w3.org/TR/SVG11/implnote.html#ArcConversionEndpointToCenter
var sinA = Math.Sin(arc.Angle);
var cosA = Math.Cos(arc.Angle);
//// Large arc flag
var fA = arc.Size == Svg.Pathing.SvgArcSize.Large ? 1 : 0;
//// Sweep flag
var fS = arc.Sweep == Svg.Pathing.SvgArcSweep.Positive ? 1 : 0;
var radiusX = arc.RadiusX;
var radiusY = arc.RadiusY;
var x1 = arc.Start.X;
var y1 = arc.Start.Y;
var x2 = arc.End.X;
var y2 = arc.End.Y;
/*
*
* Step 1: Compute (x1′, y1′)
*
*/
//// Median between Start and End
var midPointX = (x1 - x2) / 2;
var midPointY = (y1 - y2) / 2;
var x1p = (cosA * midPointX) + (sinA * midPointY);
var y1p = (cosA * midPointY) - (sinA * midPointX);
/*
*
* Step 2: Compute (cx′, cy′)
*
*/
var rxry_2 = Math.Pow(radiusX, 2) * Math.Pow(radiusY, 2);
var rxy1p_2 = Math.Pow(radiusX, 2) * Math.Pow(y1p, 2);
var ryx1p_2 = Math.Pow(radiusY, 2) * Math.Pow(x1p, 2);
var sqrt = Math.Sqrt(Math.Abs(rxry_2 - rxy1p_2 - ryx1p_2) / (rxy1p_2 + ryx1p_2));
if (fA == fS)
{
sqrt = -sqrt;
}
var cXP = sqrt * (radiusX * y1p / radiusY);
var cYP = sqrt * -(radiusY * x1p / radiusX);
/*
*
* Step 3: Compute (cx, cy) from (cx′, cy′)
*
*/
var cX = (cosA * cXP) - (sinA * cYP) + ((x1 + x2) / 2);
var cY = (sinA * cXP) + (cosA * cYP) + ((y1 + y2) / 2);
/*
*
* Step 4: Compute θ1 and Δθ
*
*/
var x1pcxp_rx = (float)(x1p - cXP) / radiusX;
var y1pcyp_ry = (float)(y1p - cYP) / radiusY;
Vector2 vector1 = new Vector2(1f, 0f);
Vector2 vector2 = new Vector2(x1pcxp_rx, y1pcyp_ry);
var angle = Math.Acos(((vector1.x * vector2.x) + (vector1.y * vector2.y)) / (Math.Sqrt((vector1.x * vector1.x) + (vector1.y * vector1.y)) * Math.Sqrt((vector2.x * vector2.x) + (vector2.y * vector2.y)))) * (180 / Math.PI);
if (((vector1.x * vector2.y) - (vector1.y * vector2.x)) < 0)
{
angle = angle * -1;
}
var vector3 = new Vector2(x1pcxp_rx, y1pcyp_ry);
var vector4 = new Vector2((float)(-x1p - cXP) / radiusX, (float)(-y1p - cYP) / radiusY);
var extent = (Math.Acos(((vector3.x * vector4.x) + (vector3.y * vector4.y)) / Math.Sqrt((vector3.x * vector3.x) + (vector3.y * vector3.y)) * Math.Sqrt((vector4.x * vector4.x) + (vector4.y * vector4.y))) * (180 / Math.PI)) % 360;
if (((vector3.x * vector4.y) - (vector3.y * vector4.x)) < 0)
{
extent = extent * -1;
}
if (fS == 1 && extent < 0)
{
extent = extent + 360;
}
if (fS == 0 && extent > 0)
{
extent = extent - 360;
}
var rectLL_X = cX - radiusX;
var rectLL_Y = cY - radiusY;
var rectUR_X = cX + radiusX;
var rectUR_Y = cY + radiusY;
return new SvgCenterParameters
{
LlX = (float)rectLL_X,
LlY = (float)rectLL_Y,
UrX = (float)rectUR_X,
UrY = (float)rectUR_Y,
Angle = (float)angle,
Extent = (float)extent
};
}

Resources