Determine original coordinates of corner before rotation - rotation

I'm having a brain-freeze trying to calculate the original coordinates of the top, left corner of a rectangle before it was rotated.
Given that I know rectangle was rotated n degrees/radians, and I know the new x and y coordinates, how would I calculate the x and y (? and ?) coordinates using trigonometry in JavaScript? I also know the width and height of the rectangle. The axis of the rotation is central to the rectangle.
The rotated rectangle is in blue, the original is in green.

you have to counter-rotate it around the pivot. (center of the rect)
//utils
function nr(v){ return +v || 0 }
class Point{
constructor(x,y){
this.x = nr(x);
this.y = nr(y);
}
add(pt){
var a = this, b = Point.from(pt);
return new Point(a.x + b.x, a.y + b.y);
}
subtract(pt){
var a = this, b = Point.from(pt);
return new Point(a.x - b.x, a.y - b.y);
}
rotate(radians, pivot){
if(pivot){
return this.subtract(pivot).rotate(radians).add(pivot);
}
var r = nr(radians), c = Math.cos(r), s = Math.sin(r);
return new Point(
this.x * c - this.y * s,
this.x * s + this.x * c
);
//return delta.matrixTransform( c, s, -s, c );
}
matrixTransform(a, b, c, d, tx, ty){
return new Point(
this.x * nr(a) + this.y * nr(c) + nr(tx),
this.x * nr(b) + this.y * nr(d) + nr(ty)
)
}
static from(pt){
if(pt instanceof Point) return pt;
return new Point(pt && pt.x, pt && pt.y);
}
}
and the computation:
var RAD = Math.PI/180, DEG = 180/Math.PI;
var knownPoint = new Point(100, 100);
var angle = 30*RAD;
var unrotatedRect = { width: 150, height: 100 };
//the pivot to (counter-)rotate your known point
//if you know this point, you don't have to compute it
var pivot = new Point(unrotatedRect.width/2, unrotatedRect.height/2)
.rotate(angle)
.add(knownPoint);
console.log(knownPoint.rotate(-angle, pivot));

The original X is exactly as far from the axis as the transposed ("rotated") Y is, and vice versa, the original Y is as far as the transposed X is from the axis.
So:
origX = axisX - (axisY - transY)
origY = axisY - (axisX - transX)

Related

Find "curve + straight + curve" path

I'm trying to calculate a path from Start point (T-like black shape) with Start direction (green) to Finish point with finish direction in 2D space. The whole path (light blue) is a bunch of points. I need to find positions of two red points. The problem is that in most cases circle sections (I and III purple) has not equal amount of points i.e. different length. Start and finish directions can be any from 0 to 359 degrees.
To make this a programming question, here is an implementation for getting the two tangent points (the red points).
This implementation defines Vector and Circle classes, each with methods to create new results from them. The Circle class has a tangentWith method, which takes another circle as argument and returns an array with two Vectors, i.e. the coordinates of the two red points.
The snippet below is interactive. It starts with the initial two circles, as depicted in your question, but allows you to draw alternative circles using the mouse (click to determine center of circle, and drag to set its radius):
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
// Derive polar coordinates:
this.size = (x ** 2 + y ** 2) ** 0.5;
this.angle = Math.atan2(this.y, this.x);
}
subtract(v) {
return new Vector(this.x - v.x, this.y - v.y);
}
add(v) {
return new Vector(this.x + v.x, this.y + v.y);
}
multiplyBy(scalar) {
return new Vector(this.x * scalar, this.y * scalar);
}
resize(size) {
return this.multiplyBy(size / this.size);
}
rotate(angle) {
angle += this.angle;
return new Vector(this.size * Math.cos(angle), this.size * Math.sin(angle));
}
}
class Circle extends Vector {
constructor(x=0, y=0, radius=0) {
super(x, y);
this.radius = radius;
}
touch(v) { // Set radius so that v is on the circle
return Circle.fromVector(this, this.subtract(v).size);
}
tangentWith(other) { // Main algorithm
let v = this.subtract(other);
const sinus = (this.radius - other.radius) / v.size;
if (Math.abs(sinus) > 1) return []; // One circle includes the other: no tangent
v = v.rotate(Math.asin(sinus) + Math.PI / 2);
return [v.resize(this.radius).add(this), v.resize(other.radius).add(other)];
}
static fromVector(v, radius=0) {
return new Circle(v.x, v.y, radius);
}
}
// The circles as depicted in the question
const circles = [new Circle(50, 50, 25), new Circle(80, 120, 25)];
// I/O management, allowing to draw different circles
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
function mouseVector(e) {
return new Vector(e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop);
}
canvas.addEventListener("mousedown", e => {
circles.reverse()[0] = Circle.fromVector(mouseVector(e));
});
canvas.addEventListener("mousemove", e => {
if (!e.buttons) return;
circles[0] = circles[0].touch(mouseVector(e));
draw();
});
function drawCircle(c) {
ctx.beginPath();
ctx.arc(c.x, c.y, c.radius, 0, 2*Math.PI);
ctx.stroke();
}
function drawSegment(start, end) {
if (!start) return;
ctx.beginPath();
ctx.moveTo(start.x, start.y);
ctx.lineTo(end.x, end.y);
ctx.stroke();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
circles.map(drawCircle);
drawSegment(...circles[0].tangentWith(circles[1]));
}
draw();
canvas { border: 1px solid }
First draw C1, then C2 (use mouse drag)<br>
<canvas width="600" height="150"></canvas>
I drawn a simple figure:
As you see, red line directional component of the vector (C1 - C2) or (C2 - C1) equals difference of radius.
(Roughly) writing this relation as a equation,
Inner-Product( C1 -C2, U ) = dr
dr = | r1 - r2 |
where U is unit vector has direction along the red line, and 2 scalars {r1, r2} are circle radius.
This becomes to:
| C1 - C2 | * | U | * cos(theta) = | C1 - C2 | * 1 * cos(theta) = dr
where theta is the angle between (C1-C2) and U.
Now you can calculate the cos(theta) value as:
cos(theta) = dr / | C1-C2 |

Apply gradient to BufferGeometry vertices

I have an animation that uses a BufferGeometry to create a grid of particles which are then animated using Perlin noise. That all works perfectly but the final thing to do is to apply a gradient across the grid. I have tried everything I have found and nothing is working. I feel like using a ShaderMaterial is the best/easiest solution but the code I've found for gradients just isn't working so I'm asking what the best way to do this is and ideally an example of how to do it.
Here is a link to the codepen so you can see all of the code and the example working.
https://codepen.io/JJGerrish/pen/oNxyJXX?editors=0010
And here is an example of the what I want the grid to look like.
I've left my attempt at creating a gradient shader in so you are welcome to play around with that or come up with a better solution.
Your problem is that you are using uVu.y , but you don't have any uv coordinates so the value will always be 0.
Are you sure you don't want to be using the position x value?
gl_FragColor = vec4(mix(color1, color2, smoothstep(-10.0, 10.0, pos.x)), 1.0);
(demo in code below with a smoothstep, note sending the pos variable from the vertex to fragment shader).
Also, why not do the noise in the shader too rather than in the JS?
//noise library
/*
* A speed-improved perlin and simplex noise algorithms for 2D.
*
* Based on example code by Stefan Gustavson (stegu#itn.liu.se).
* Optimisations by Peter Eastman (peastman#drizzle.stanford.edu).
* Better rank ordering method by Stefan Gustavson in 2012.
* Converted to Javascript by Joseph Gentle.
*
* Version 2012-03-09
*
* This code was placed in the public domain by its original author,
* Stefan Gustavson. You may use it as you see fit, but
* attribution is appreciated.
*
*/
(function(global){
var module = global.noise = {};
function Grad(x, y, z) {
this.x = x; this.y = y; this.z = z;
}
Grad.prototype.dot2 = function(x, y) {
return this.x*x + this.y*y;
};
Grad.prototype.dot3 = function(x, y, z) {
return this.x*x + this.y*y + this.z*z;
};
var grad3 = [new Grad(1,1,0),new Grad(-1,1,0),new Grad(1,-1,0),new Grad(-1,-1,0),
new Grad(1,0,1),new Grad(-1,0,1),new Grad(1,0,-1),new Grad(-1,0,-1),
new Grad(0,1,1),new Grad(0,-1,1),new Grad(0,1,-1),new Grad(0,-1,-1)];
var p = [151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
// To remove the need for index wrapping, double the permutation table length
var perm = new Array(512);
var gradP = new Array(512);
// This isn't a very good seeding function, but it works ok. It supports 2^16
// different seed values. Write something better if you need more seeds.
module.seed = function(seed) {
if(seed > 0 && seed < 1) {
// Scale the seed out
seed *= 65536;
}
seed = Math.floor(seed);
if(seed < 256) {
seed |= seed << 8;
}
for(var i = 0; i < 256; i++) {
var v;
if (i & 1) {
v = p[i] ^ (seed & 255);
} else {
v = p[i] ^ ((seed>>8) & 255);
}
perm[i] = perm[i + 256] = v;
gradP[i] = gradP[i + 256] = grad3[v % 12];
}
};
module.seed(0);
/*
for(var i=0; i<256; i++) {
perm[i] = perm[i + 256] = p[i];
gradP[i] = gradP[i + 256] = grad3[perm[i] % 12];
}*/
// Skewing and unskewing factors for 2, 3, and 4 dimensions
var F2 = 0.5*(Math.sqrt(3)-1);
var G2 = (3-Math.sqrt(3))/6;
var F3 = 1/3;
var G3 = 1/6;
// 2D simplex noise
module.simplex2 = function(xin, yin) {
var n0, n1, n2; // Noise contributions from the three corners
// Skew the input space to determine which simplex cell we're in
var s = (xin+yin)*F2; // Hairy factor for 2D
var i = Math.floor(xin+s);
var j = Math.floor(yin+s);
var t = (i+j)*G2;
var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed.
var y0 = yin-j+t;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
if(x0>y0) { // lower triangle, XY order: (0,0)->(1,0)->(1,1)
i1=1; j1=0;
} else { // upper triangle, YX order: (0,0)->(0,1)->(1,1)
i1=0; j1=1;
}
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
var y1 = y0 - j1 + G2;
var x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
var y2 = y0 - 1 + 2 * G2;
// Work out the hashed gradient indices of the three simplex corners
i &= 255;
j &= 255;
var gi0 = gradP[i+perm[j]];
var gi1 = gradP[i+i1+perm[j+j1]];
var gi2 = gradP[i+1+perm[j+1]];
// Calculate the contribution from the three corners
var t0 = 0.5 - x0*x0-y0*y0;
if(t0<0) {
n0 = 0;
} else {
t0 *= t0;
n0 = t0 * t0 * gi0.dot2(x0, y0); // (x,y) of grad3 used for 2D gradient
}
var t1 = 0.5 - x1*x1-y1*y1;
if(t1<0) {
n1 = 0;
} else {
t1 *= t1;
n1 = t1 * t1 * gi1.dot2(x1, y1);
}
var t2 = 0.5 - x2*x2-y2*y2;
if(t2<0) {
n2 = 0;
} else {
t2 *= t2;
n2 = t2 * t2 * gi2.dot2(x2, y2);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 70 * (n0 + n1 + n2);
};
// 3D simplex noise
module.simplex3 = function(xin, yin, zin) {
var n0, n1, n2, n3; // Noise contributions from the four corners
// Skew the input space to determine which simplex cell we're in
var s = (xin+yin+zin)*F3; // Hairy factor for 2D
var i = Math.floor(xin+s);
var j = Math.floor(yin+s);
var k = Math.floor(zin+s);
var t = (i+j+k)*G3;
var x0 = xin-i+t; // The x,y distances from the cell origin, unskewed.
var y0 = yin-j+t;
var z0 = zin-k+t;
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
if(x0 >= y0) {
if(y0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; }
else if(x0 >= z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; }
else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; }
} else {
if(y0 < z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; }
else if(x0 < z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; }
else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; }
}
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
// c = 1/6.
var x1 = x0 - i1 + G3; // Offsets for second corner
var y1 = y0 - j1 + G3;
var z1 = z0 - k1 + G3;
var x2 = x0 - i2 + 2 * G3; // Offsets for third corner
var y2 = y0 - j2 + 2 * G3;
var z2 = z0 - k2 + 2 * G3;
var x3 = x0 - 1 + 3 * G3; // Offsets for fourth corner
var y3 = y0 - 1 + 3 * G3;
var z3 = z0 - 1 + 3 * G3;
// Work out the hashed gradient indices of the four simplex corners
i &= 255;
j &= 255;
k &= 255;
var gi0 = gradP[i+ perm[j+ perm[k ]]];
var gi1 = gradP[i+i1+perm[j+j1+perm[k+k1]]];
var gi2 = gradP[i+i2+perm[j+j2+perm[k+k2]]];
var gi3 = gradP[i+ 1+perm[j+ 1+perm[k+ 1]]];
// Calculate the contribution from the four corners
var t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
if(t0<0) {
n0 = 0;
} else {
t0 *= t0;
n0 = t0 * t0 * gi0.dot3(x0, y0, z0); // (x,y) of grad3 used for 2D gradient
}
var t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
if(t1<0) {
n1 = 0;
} else {
t1 *= t1;
n1 = t1 * t1 * gi1.dot3(x1, y1, z1);
}
var t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
if(t2<0) {
n2 = 0;
} else {
t2 *= t2;
n2 = t2 * t2 * gi2.dot3(x2, y2, z2);
}
var t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
if(t3<0) {
n3 = 0;
} else {
t3 *= t3;
n3 = t3 * t3 * gi3.dot3(x3, y3, z3);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 32 * (n0 + n1 + n2 + n3);
};
// ##### Perlin noise stuff
function fade(t) {
return t*t*t*(t*(t*6-15)+10);
}
function lerp(a, b, t) {
return (1-t)*a + t*b;
}
// 2D Perlin Noise
module.perlin2 = function(x, y) {
// Find unit grid cell containing point
var X = Math.floor(x), Y = Math.floor(y);
// Get relative xy coordinates of point within that cell
x = x - X; y = y - Y;
// Wrap the integer cells at 255 (smaller integer period can be introduced here)
X = X & 255; Y = Y & 255;
// Calculate noise contributions from each of the four corners
var n00 = gradP[X+perm[Y]].dot2(x, y);
var n01 = gradP[X+perm[Y+1]].dot2(x, y-1);
var n10 = gradP[X+1+perm[Y]].dot2(x-1, y);
var n11 = gradP[X+1+perm[Y+1]].dot2(x-1, y-1);
// Compute the fade curve value for x
var u = fade(x);
// Interpolate the four results
return lerp(
lerp(n00, n10, u),
lerp(n01, n11, u),
fade(y));
};
// 3D Perlin Noise
module.perlin3 = function(x, y, z) {
// Find unit grid cell containing point
var X = Math.floor(x), Y = Math.floor(y), Z = Math.floor(z);
// Get relative xyz coordinates of point within that cell
x = x - X; y = y - Y; z = z - Z;
// Wrap the integer cells at 255 (smaller integer period can be introduced here)
X = X & 255; Y = Y & 255; Z = Z & 255;
// Calculate noise contributions from each of the eight corners
var n000 = gradP[X+ perm[Y+ perm[Z ]]].dot3(x, y, z);
var n001 = gradP[X+ perm[Y+ perm[Z+1]]].dot3(x, y, z-1);
var n010 = gradP[X+ perm[Y+1+perm[Z ]]].dot3(x, y-1, z);
var n011 = gradP[X+ perm[Y+1+perm[Z+1]]].dot3(x, y-1, z-1);
var n100 = gradP[X+1+perm[Y+ perm[Z ]]].dot3(x-1, y, z);
var n101 = gradP[X+1+perm[Y+ perm[Z+1]]].dot3(x-1, y, z-1);
var n110 = gradP[X+1+perm[Y+1+perm[Z ]]].dot3(x-1, y-1, z);
var n111 = gradP[X+1+perm[Y+1+perm[Z+1]]].dot3(x-1, y-1, z-1);
// Compute the fade curve value for x, y, z
var u = fade(x);
var v = fade(y);
var w = fade(z);
// Interpolate
return lerp(
lerp(
lerp(n000, n100, u),
lerp(n001, n101, u), w),
lerp(
lerp(n010, n110, u),
lerp(n011, n111, u), w),
v);
};
})(this);
//effective animation code
var wWidth = window.innerWidth;
var wHeight = window.innerHeight;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, wWidth / wHeight, 0.01, 1000);
camera.position.x = 0;
camera.position.y = 0; // 0
camera.position.z = 50; // 40
camera.lookAt(new THREE.Vector3(0, 0, 0));
var renderer = new THREE.WebGLRenderer({
alpha: true
});
renderer.setClearColor(0x000000, 0);
document.getElementById('sec-graphical-intro').appendChild(renderer.domElement);
//Animation parameters
var rows = 50;
var cols = 100;
var separation = 1;
var perlinScale = 0.025;
var waveSpeed = 0.1;
var waveHeight = 8;
var FPS = 45;
var startTime = new Date().getTime();
var particles = 0;
var count = 0;
noise.seed(Math.random());
function createGeometry() {
var numParticles = cols * rows;
var positions = new Float32Array( numParticles * 3 );
var i = 0
var j = 0;
for ( var ix = 0; ix < cols; ix ++ ) {
for ( var iy = 0; iy < rows; iy ++ ) {
positions[i] = ix * separation - ( ( cols * separation ) / 2 ); // x
positions[i + 1] = 0; // y
positions[i + 2] = iy * separation - ( ( rows * separation ) / 2 ); // z
i += 3;
j ++;
}
}
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
// geometry.dynamic = true;
// geometry.translate(-100, 0, -25);
return geometry;
}
var geo = createGeometry();
var material = new THREE.ShaderMaterial( {
uniforms: {
"color1": {
type : "c",
value: new THREE.Color(0x2753c9)
},
"color2": {
type : "c",
value: new THREE.Color(0x1dcdc0)
}
},
vertexShader: `
varying vec2 vUv;
varying vec4 pos;
void main() {
vUv = uv;
gl_PointSize = 4.0;
pos = projectionMatrix * modelViewMatrix * vec4(position,1.0);
gl_Position = pos;
}
`,
fragmentShader: `
uniform vec3 color1;
uniform vec3 color2;
varying vec2 vUv;
varying vec4 pos;
void main() {
if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;
gl_FragColor = vec4(mix(color1, color2, smoothstep(-10.0, 10.0, pos.x)), 1.0);
}
`
});
particles = new THREE.Points(geo, material);
scene.add(particles);
function perlinAnimate() {
var curTime = new Date().getTime();
var positions = particles.geometry.attributes.position.array;
var i = 0
var j = 0;
for ( var ix = 0; ix < cols; ix ++ ) {
for ( var iy = 0; iy < rows; iy ++ ) {
pX = (ix * perlinScale) + ((curTime - startTime) / 1000) * waveSpeed;
pZ = (iy * perlinScale) + ((curTime - startTime) / 1000) * waveSpeed;
positions[ i + 1 ] = (noise.simplex2(pX, pZ)) * waveHeight;
i += 3;
}
}
particles.geometry.attributes.position.needsUpdate = true;
count += 0.1;
}
function render() {
renderer.render(scene, camera);
}
function animate() {
perlinAnimate();
render();
window.setTimeout(function() {
requestAnimationFrame(animate);
}, 1000 / FPS);
}
function refreshCanvasState() {
wWidth = window.innerWidth;
wHeight = window.innerHeight;
camera.aspect = wWidth / wHeight;
camera.updateProjectionMatrix();
renderer.setSize(wWidth, wHeight);
}
//EVENTS && INTERACTIONS
window.addEventListener('resize', refreshCanvasState, false);
animate();
refreshCanvasState();
addEvent(document, "keypress", function(e) {
e = e || window.event;
// use e.keyCode
console.log(e.keyCode);
});
function addEvent(element, eventName, callback) {
if (element.addEventListener) {
element.addEventListener(eventName, callback, false);
} else if (element.attachEvent) {
element.attachEvent("on" + eventName, callback);
} else {
element["on" + eventName] = callback;
}
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/84/three.min.js"></script>
</head>
<body>
<section id="sec-graphical-intro"></section>

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

XNA/Monogame Creating rectangle around texture regardless of rotation?

I know Rectangle is axis aligned, that's fine, I just can't figure out how to create a rectangle so it is always encompassing the entire sprite, regardless of rotation. I have been looking everywhere for an answer but I can't get a straight one anywhere.
For example:
Assuming the origin point is the middle of the texture, how can I go about this?
EDIT
Fiddling around with it a little, I've gotten this far:
public Rectangle BoundingBox
{
get
{
var cos = Math.Cos(SpriteAngle);
var sin = Math.Cos(SpriteAngle);
var t1_opp = Width * cos;
var t1_adj = Math.Sqrt(Math.Pow(Width, 2) - Math.Pow(t1_opp, 2));
var t2_opp = Height * sin;
var t2_adj = Math.Sqrt(Math.Pow(Height, 2) - Math.Pow(t2_opp, 2));
int w = Math.Abs((int)(t1_opp + t2_opp));
int h = Math.Abs((int)(t1_adj + t2_adj));
int x = Math.Abs((int)(Position.X) - (w / 2));
int y = Math.Abs((int)(Position.Y) - (h / 2));
return new Rectangle(x, y, w, h);
}
}
(doing this off the top of my head.. but the principle should work)
Create a matrix to rotate around the center of the rectangle - that is a translate of -(x+width/2), -(y+height/2)
followed by a rotation of angle
followed by a translate of (x+width/2), (y+height/2)
Use Vector2.Transform to transform each corner of the original rectangle
Then make a new rectangle with
x = min(p1.x, p2.x, p3.x, p4.x)
width = max(p1.x, p2.x, p3.x, p4.x) - x
similar for y
Sorry this is coming so late, but I figured this out a while ago and forgot to post an answer.
public virtual Rectangle BoundingBox
{
get
{
int x, y, w, h;
if (Angle != 0)
{
var cos = Math.Abs(Math.Cos(Angle));
var sin = Math.Abs(Math.Sin(Angle));
var t1_opp = Width * cos;
var t1_adj = Math.Sqrt(Math.Pow(Width, 2) - Math.Pow(t1_opp, 2));
var t2_opp = Height * sin;
var t2_adj = Math.Sqrt(Math.Pow(Height, 2) - Math.Pow(t2_opp, 2));
w = (int)(t1_opp + t2_opp);
h = (int)(t1_adj + t2_adj);
x = (int)(Position.X - (w / 2));
y = (int)(Position.Y - (h / 2));
}
else
{
x = (int)Position.X;
y = (int)Position.Y;
w = Width;
h = Height;
}
return new Rectangle(x, y, w, h);
}
}
This is it here. In my work in the edit, I accidentally had Math.Cos in the sin variable, which didn't help.
So it's just basic trigonometry. If the textures angle is something other than zero, calculate the sides of the two triangles formed by the width and the height, and use the sides as the values for the width and the height, then center the rectangle around the texture. If that makes sense.
Here's a picture to help explain:
Here's a gif of the final result:

circle rotated rectangle collision detection

I am trying to implement the collision detection between rotated rectangle and circle by following this http://www.migapro.com/circle-and-rotated-rectangle-collision-detection/
I have added the code in jsfiddle here http://jsfiddle.net/Z6KSX/2/.
What am i missing here ?
function check_coll ( circle_x,circle_y, rect_x, rect_y, rect_width, rect_height, rect_angle)
{
// Rotate circle's center point back
var rect_centerX = rect_x /2 ;
var rect_centerY = rect_y /2 ;
var cx = (Math.cos(rect_angle) * (circle_x - rect_centerX)) - (Math.sin(rect_angle) * (circle_y - rect_centerY)) + rect_centerX;
var cy = (Math.sin(rect_angle) * (circle_x - rect_centerX)) + (Math.cos(rect_angle) * (circle_y - rect_centerY)) + rect_centerY;
// Closest point
var x, y;
// Find the unrotated closest x point from center of unrotated circle
if (cx < rect_x) {
x = rect_x;
}
else if (cx > rect_x + rect_width){
x = rect_x + rect_width;
}
else{
x = cx;
}
// Find the unrotated closest y point from center of unrotated circle
if (cy < rect_y){
y = rect_y;
}
else if (cy > rect_y + rect_height) {
y = rect_y + rect_height;
}
else {
y = cy;
}
// Determine collision
var collision = false;
var c_radius = 5;
var distance = findDistance(cx, cy, x, y);
if (distance < c_radius) {
collision = true; // Collision
}
else {
collision = false;
}
return collision;
}
function findDistance (x1, y1, x2, y2) {
var a = Math.abs(x1 - x2);
var b = Math.abs(y1 - y2);
var c = Math.sqrt((a * a) + (b * b));
return c;
}
Hehe, I find this amusing as I somewhat recently solved this for myself after spending a large amount of time going down the wrong path.
Eventually I figured out a way:
1.) Simply rotate the point of the center of the circle by the Negative amount the rectangle has been rotated by. Now the point is 'aligned' with the rectangle (in the rectangles relative coordinate space).
2.) Solve for circle vs. AABB. The way I solved it gave me a point on the rectangle that is closest to the circle's center.
3.) Rotate the resulting point from by the Positive amount the rectangle has been rotated by. Continue solving as usual (checking if the distance between that point and the circle center is within the circle's radius)
From a very quick glance at your code, it seems like maybe you are doing the same thing, but missing the last step? I suggest drawing out your point on the rectangle from step 2 to see exactly where it is to help debug.
I was able to figure this out . The issue in the code was, I was using the wrong radius and had missed the center of rect_x and rect_y
var rect_centerX = rect_x + (rect_width / 2);
var rect_centerY = rect_y + (rect_height /2);
When dealing with rotation on the canvas we will need to add the translate values to the corresponding x and y values used in createrect.
I also use this code for my project and it's working. The only thing you need to do is use -angle instead of the angle.
Here is my code link
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const rectX = 100;
const rectY = 100;
const rectWidth = 200;
const rectHeight = 100;
const circleRadius = 2;
const rectMidPointX = rectX + rectWidth / 2;
const rectMidPointY = rectY + rectHeight / 2;
const angle = Math.PI / 4;
let circleX;
let circleY;
canvas.addEventListener('mousemove', (e) => {
circleX = e.clientX;
circleY = e.clientY;
ctx.save();
ctx.beginPath();
ctx.fillStyle = '#fff';
ctx.arc(circleX, circleY, circleRadius, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
ctx.restore();
calculateIntersection();
})
ctx.save();
//ctx.fillRect(100, 100, 100, 100);
ctx.strokeStyle = 'black';
ctx.translate(rectMidPointX, rectMidPointY);
ctx.rotate(angle);
ctx.translate(-rectMidPointX, -rectMidPointY);
ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);
ctx.restore();
// Determine collision
let collision = false;
const findDistance = (fromX, fromY, toX, toY) => {
const a = Math.abs(fromX - toX);
const b = Math.abs(fromY - toY);
return Math.sqrt((a * a) + (b * b));
};
function calculateIntersection() {
// Rotate circle's center point back
const unrotatedCircleX = Math.cos(-angle) * (circleX - rectMidPointX) -
Math.sin(-angle) * (circleY - rectMidPointY) + rectMidPointX;
const unrotatedCircleY = Math.sin(-angle) * (circleX - rectMidPointX) +
Math.cos(-angle) * (circleY - rectMidPointY) + rectMidPointY;
// Closest point in the rectangle to the center of circle rotated backwards(unrotated)
let closestX, closestY;
// Find the unrotated closest x point from center of unrotated circle
if (unrotatedCircleX < rectX)
closestX = rectX;
else if (unrotatedCircleX > rectX + rectWidth)
closestX = rectX + rectWidth;
else
closestX = unrotatedCircleX;
// Find the unrotated closest y point from center of unrotated circle
if (unrotatedCircleY < rectY)
closestY = rectY;
else if (unrotatedCircleY > rectY + rectHeight)
closestY = rectY + rectHeight;
else
closestY = unrotatedCircleY;
const distance = findDistance(unrotatedCircleX, unrotatedCircleY, closestX, closestY);
if (distance < circleRadius)
collision = true; // Collision
else
collision = false;
console.log('collision', collision);
}
<canvas id="canvas" width="400px" height="400px" />

Resources