rotating a single line multiple times in canvas - rotation

the code below is almost fine but I want to make it short and simple by using a single makeline() function so as to avoid the repetition of the code lines.
I want the loading image to remain same only I want the code to be short. by using the above-mentioned function. As you can see below the code is too lengthy and when it is run the lines are not properly adjusted.
I want the shape to remain the same the position of the lines should be same only a little bit of adjustment
<!DOCTYPE html>
<html>
<head>
<style>
myCanvas {
border: 1px;
background: rgba( 240, 238, 238, 0.898);
}
</style>
</head>
<body>
<canvas id="canvas" width="1000" height="1000" style="border: ">
</canvas>
<script>
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.lineWidth = 5;
ctx.beginPath();
ctx.translate(470, 470)
ctx.rotate(15 * Math.PI / 180);
ctx.moveTo(25, 45);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(55 * Math.PI / 180);
ctx.moveTo(35, 35);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(45 * Math.PI / 180);
ctx.moveTo(35, 35);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(45 * Math.PI / 180);
ctx.moveTo(35, 35);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(50 * Math.PI / 180);
ctx.moveTo(25, 35);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(50 * Math.PI / 180);
ctx.moveTo(35, 35);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(40 * Math.PI / 180);
ctx.moveTo(35, 50);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(5 * Math.PI / 180);
ctx.moveTo(35, 50);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(15 * Math.PI / 180);
ctx.moveTo(35, 45);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(15 * Math.PI / 180);
ctx.moveTo(35, 45);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(25 * Math.PI / 180);
ctx.moveTo(35, 35);
ctx.lineTo(100, 150);
ctx.stroke();
ctx.beginPath();
ctx.rotate(45 * Math.PI / 180);
ctx.moveTo(35, 35);
ctx.lineTo(100, 150);
ctx.stroke();
</script>
</body>
</html>

Sweep A Line
If you are wanting to move the line along its normal (90deg from its direction) and rotate it while doing so the following will explain how to do this using absolute positioning (no ctx.rotate calls)
Some utility functions to get started.
// A Point also defines a vector.
const Point = (x,y) => ({x,y});
const Line = (p1, p2) => ({p1, p2});
const lengthOfVec = vec => (vec.x ** 2 + vec.y ** 2) ** 0.5;
const normalVec = line => { // normalize the line and return a vector
const vec = Point(line.p2.x - line.p1.x, line.p2.y - line.p1.y);
const length =lengthOfVec(vec);
vec.x /= length;
vec.y /= length;
return vec;
}
const rotateVec90 = vec => ([vec.y, vec.x] = [vec.x, - vec.y], vec);
We can start with a line defined by its end points p1 and p2 and the normal of the line is a vector that is at 90 deg to the left of the line.
We will create a function that moved the line along this normal, the function will also rotate the line as it does, and by the looks of your example the line needs to change length as well so we can add a scale argument as well.
The steps
The function will be sweepLine(line, distance, rotate, scale), whererotate is in radians (I will not use degrees), distance is in pixels, scale > 1 will grow the line and scale < 1 will shrink the line.
function sweepLine(line, dist, rot, scale){
We need the center of the line and the line normalised and normal as a vector
const center = Point((line.p1.x + line.p2.x) / 2, (line.p1.y + line.p2.y) / 2);
const lineNorm = normalVec(line);
const norm = rotateVec90(Point(lineNorm.x, lineNorm.y));
No rotate
We need the new position of the center, if we are rotating the new center will be at the end of an arc, if not rotating the new position will be at the end of a line. Just move the center along the normal
if(rot !== 0){
// need the dist of point from center
const ax = line.p2.x - center.x;
const ay = line.p2.y - center.y;
// move the point
center.x += norm.x * dist;
center.y += norm.y * dist;
Now we can just scale the line
line.p1.x = center.x - ax * scale
line.p1.y = center.y - ay * scale;
line.p2.x = center.x + ax * scale;
line.p2.y = center.y + ay * scale;
}
Rotate moves on an arc
For the rotated line we need to find the point on the arc, and to define an arc we need that arc's center. The length of an arc is the change in angle times the radius, we dont have the radius
else {
const arcRadius = dist / rot;
The arc center is arcRadius distance from the center (Note that rot can be negative an that will move the center to the correct position)
const arcCenter = Point(
center.x + lineNorm.x * arcRadius,
center.y + lineNorm.y * arcRadius
);
now we have the center we need the start angle of the arc, which is the direction of the line.
const startAngle = Math.atan2(lineNorm.y, lineNorm.x);
const endAngle = startAngle + rot;
We add the rotation to the startAngle and then move arcRadius distance from the arcCenter along that new angle to the new center.
center.x = arcCenter.x + Math.cos(endAngle) * arcRadius;
center.y = arcCenter.y + Math.sin(endAngle) * arcRadius;
With the new position of the center we can change the lines size and rotate it at the same time if we get the length of the line.
const len = lengthOfVec(Point(line.p1.x - line.p2.x, line.p1.y - line.p2.y));
line.p1.x = center.x - Math.cos(endAngle) * len * scale * 0.5;
line.p1.y = center.y - Math.sin(endAngle) * len * scale * 0.5;
line.p2.x = center.x + Math.cos(endAngle) * len * scale * 0.5;
line.p2.y = center.y + Math.sin(endAngle) * len * scale * 0.5;
And that is it. The function can return.
}
}
Example
To show usage example the snippet below does the same but has some optimizations along the way.
The example creates a random line and then moves it using the sweepLine function. It is animated to continuously draw lines.
requestAnimationFrame(update);
const ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function update(timer){
if(w !== innerWidth || h !== innerHeight){
w = canvas.width = innerWidth;
h = canvas.height = innerHeight;
}
jiggle();
sweepLine(line, moveBy,rotateBy, scaleBy);
drawLine(line);
requestAnimationFrame(update);
}
// A Point also defines a vector.
const Point = (x,y) => ({x,y});
const Line = (p1, p2) => ({p1, p2});
const lengthOfVec = vec => (vec.x ** 2 + vec.y ** 2) ** 0.5;
const normalVec = line => { // normalize the line and return a vector
const vec = Point(line.p2.x - line.p1.x, line.p2.y - line.p1.y);
const length = lengthOfVec(vec);
vec.x /= length;
vec.y /= length;
return vec;
}
const rotateVec90 = vec => {
const t = vec.x;
vec.x = - vec.y;
vec.y = t;
return vec;
}
function sweepLine(line, dist, rot, scale){
const center = Point((line.p1.x + line.p2.x) / 2, (line.p1.y + line.p2.y) / 2);
const len = lengthOfVec(Point(line.p1.x - line.p2.x, line.p1.y - line.p2.y));
const lineNorm = normalVec(line);
const norm = rotateVec90(Point(lineNorm.x, lineNorm.y));
if(rot === 0){
const ax = (line.p2.x - center.x) * scale;
const ay = (line.p2.y - center.y) * scale;
center.x += norm.x * dist;
center.y += norm.y * dist;
line.p1.x = center.x - ax
line.p1.y = center.y - ay;
line.p2.x = center.x + ax;
line.p2.y = center.y + ay;
} else {
const arcRadius = dist / rot;
const arcCenter = Point(
center.x - lineNorm.x * arcRadius, center.y - lineNorm.y * arcRadius
);
const endAngle = Math.atan2(lineNorm.y, lineNorm.x) + rot;
var ax = Math.cos(endAngle);
var ay = Math.sin(endAngle);
center.x = arcCenter.x + ax * arcRadius;
center.y = arcCenter.y + ay * arcRadius;
const len = lengthOfVec(Point(line.p1.x - line.p2.x, line.p1.y - line.p2.y));
ax *= len * scale * 0.5;
ay *= len * scale * 0.5;
line.p1.x = center.x - ax;
line.p1.y = center.y - ay;
line.p2.x = center.x + ax;
line.p2.y = center.y + ay;
}
}
function drawLine(line){
ctx.lineWidth = 8;
ctx.lineCap = "round";
ctx.strokeStyle = col;
ctx.beginPath();
ctx.lineTo(line.p1.x, line.p1.y);
ctx.lineTo(line.p2.x, line.p2.y);
ctx.stroke();
}
function createRandomLine(){
const x = Math.random() * w * 0.3 + w * 0.35;
const y = Math.random() * h * 0.3 + h * 0.35;
const len = Math.random() * 40 + 10;
const dir = Math.random() * Math.PI * 2;
return Line(
Point(x - Math.cos(dir) * len * 0.5, y - Math.sin(dir) * len * 0.5),
Point(x + Math.cos(dir) * len * 0.5, y + Math.sin(dir) * len * 0.5)
);
}
// sweep the line randomly needs some settings
var line, rotateBy, moveBy, scaleBy, col, l = 50,s = 70,hue = 0,moveFor = 0; //
function randomize(){
rotateBy = Math.random() * 0.5 - 0.25;
moveBy = Math.random() * 5 + 5;
scaleBy = 1;
moveFor = 200;
line = createRandomLine();
}
function jiggle(){
if(moveFor === 0 ){ randomize() }
rotateBy += (Math.random() - 0.5) * 0.2;
scaleBy = Math.random() < 0.2 ? 1/1.1 : Math.random() < 0.2 ? 1.1 : 1;
moveBy += (Math.random() - 0.5) * 4;
moveFor --;
hue = (hue + 1) % 360;
s = (s + 100 + Math.random() - 0.5) % 100;
l = (l + 100 + Math.random() - 0.5) % 100;
col = "hsl("+hue+","+s+"%,"+l+"%)";
}
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>

Looks like you want to draw some sort of sun with rays coming out.
Here is one option with much less code than yours, the key is to use a function to do the drawing
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var hw = c.width/2;
var hh = c.height/2
ctx.translate(hw, hh)
ctx.lineWidth = 5;
function drawLine(x, y) {
ctx.beginPath();
ctx.moveTo(x * hw/3, y * hh/3);
ctx.lineTo(x * hw, y * hh);
ctx.stroke();
}
var p200 = Math.PI * 200
for (i = 0; i < p200; i += p200 / 12)
drawLine(Math.sin(i/100), Math.cos(i/100));
<canvas id="canvas" width="170" height="170">
And with that same function and a few math tricks you can draw some more complex shapes like a sea shell looking shape like this:
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
const p200 = Math.PI * 200
function drawLine(x, y, m) {
ctx.beginPath();
ctx.moveTo(x * m/3, y * m/3);
ctx.lineTo(x * m, y * m);
ctx.stroke();
}
function shell(ini, end, mid) {
ctx.lineWidth = 5;
ctx.strokeStyle="black";
for (i = ini; i < end; i += p200 / 124)
drawLine(Math.cos(i/100), Math.sin(i/100), mid - i/10);
ctx.translate(-3, -3)
ctx.strokeStyle="cyan";
for (i = ini; i < end; i += p200 / 96)
drawLine(Math.cos(i/100), Math.sin(i/100), mid - i/10);
ctx.strokeStyle="blue";
for (i = ini; i < end; i += p200 / 48)
drawLine(Math.cos(i/100), Math.sin(i/100), mid - i/10);
ctx.lineWidth = 0.5;
ctx.strokeStyle="green";
for (i = ini; i < end; i += p200 / 48)
drawLine(Math.cos(i/100), Math.sin(i/100), mid - i/10);
}
ctx.translate(70, 60)
shell(0, p200, 95)
ctx.translate(200, 40)
shell(p200/1.8, p200+p200/1.8, 135)
<canvas id="canvas" width="340" height="170">

If you are happy with the result, you may write a function like this:
function drawLine(t,r, p1,p2){
//t: translation object
//r: rotation object
//p1: point object for the moveTo() method
//p2: point object for the lineTo() method
//ctx.save();
ctx.beginPath();
ctx.translate(t.x, t.y)
ctx.rotate(r * Math.PI / 180);
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
//ctx.restore();
}
I've commented out the save & restore methods since you don't use them, but those methods are very useful and help you to save a lot of calculations.

Related

pgraphics element won't center on mobile devices | drawingContext.drawImage causing mobile offset

I’m working on some of Tim Rodenbrökers code involving a copy() function (https://timrodenbroeker.de/processing-tutorial-kinetic-typography-1/), expanding it and making it ready for web.
This involves replacing the copy() function with drawingContext.drawImage() for performance increase (found here: https://discourse.processing.org/t/p5-js-copy-function-slow-performance-since-version-0-10-0/30007).
Doing this works great for desktop; on mobile, however, the pgraphics element (centered on the canvas, usually), moves position.
Using the regular copy() function centeres it correctly.
The positioning varies according to mobile screen size, I can’t seem to figure out the exact behavior to fix. It's not the font size, I've tried adapting the position to screen.size and document.documentElement.clientWidth, no luck.
let font;
let pg;
function setup() {
font = loadFont("./assets/FGrotesk-Regular.otf");
createCanvas(innerWidth, innerHeight);
pg = createGraphics(innerWidth, innerHeight, P2D);
frameRate(60);
pixelDensity(1);
}
function draw() {
background(0);
pg.background(0);
pg.fill(255);
pg.textFont(font);
pg.textSize(380);
pg.push();
pg.translate(innerWidth / 2, innerHeight / 2);
pg.textAlign(CENTER, CENTER);
pg.text("Enrico", 0, -50);
pg.text("Gisana", 0, 50);
pg.pop();
let tilesX = 400;
let tilesY = 20;
let tileW = int(width / tilesX);
let tileH = int(height / tilesY);
for (y = 0; y < tilesY; y++) {
for (x = 0; x < tilesX; x++) {
// WARP
let wave_x = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
let wave_y = int(sin(frameCount * 0.02 + (x * y) * 0.07) * 100) - (mouseY / 2);
if (mouseX - (width / 2) >= 0) {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
} else {
wave_x = int(sin(frameCount * 0.02 + ((x / 0.8) * (y/0.2)) * 0.04) * (-1 * (mouseX - (width / 2)) / 30));
}
if (mouseY - (height / 2) >= 0) {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
} else {
wave_y = int(sin(frameCount * 0.02 + ((x / 0.2) * (y/0.8)) * 0.04) * ((mouseY - (height / 2)) / 30));
}
// SOURCE
let sx = x * tileW + wave_x;
// + wave should be added here
let sy = y * tileH - wave_y;
let sw = tileW;
let sh = tileH;
// DESTINATION
let dx = x * tileW;
let dy = y * tileH;
let dw = tileW;
let dh = tileH;
drawingContext.drawImage(pg.elt, sx, sy, sw, sh, dx, dy, dw, dh);
}
}
}

Can you create numbers around the analog clock?

so, as an assaignment i want to make an analog clock, and i am pretty far. I only need the numbers around the clock, but i cant figure out how to make these. I have made some dots right now, but i want to replace theese with the numbers 1-12.. does any1 know an easy and fast way of doing this? my code is as following:
let cx, cy;
let secondsRadius;
let minutesRadius;
let hoursRadius;
let clockDiameter;
function setup() {
createCanvas(800, 800);
let radius= min(width,height)/2;
secondsRadius=radius*0.71;
minutesRadius = radius*0.6;
hoursRadius = radius*0.5;
clockDiameter = radius*1.7;
cx = width/2;
cy = height/2;
}
function draw() {
background("pink");
noStroke();
fill(244, 122, 158);
ellipse(cx, cy, clockDiameter + 25, clockDiameter + 25);
fill("green");
ellipse(cx,cy,clockDiameter, clockDiameter);
let s = map(second(), 0, 60, 0, TWO_PI) - HALF_PI;
let m = map(minute() + norm(second(), 0, 60), 0, 60, 0, TWO_PI) - HALF_PI;
let h = map(hour() + norm(minute(), 0, 60), 0, 24, 0, TWO_PI *2) - HALF_PI;
stroke(255);
strokeWeight(1);
line(cx, cy, cx + cos(s) * secondsRadius, cy + sin(s) * secondsRadius);
strokeWeight(2);
line(cx, cy, cx + cos(m) * minutesRadius, cy + sin(m) * minutesRadius);
strokeWeight(4);
line(cx, cy, cx + cos(h) * hoursRadius, cy + sin(h) * hoursRadius);
strokeWeight(2);
beginShape(POINTS);
for (let a = 0; a < 360; a += 6) {
let angle = radians(a);
let x = cx + cos(angle) * secondsRadius;
let y = cy + sin(angle) * secondsRadius;
vertex(x, y);
}
endShape();
}```
As a quick and dirty:) answer - just add something like that before the last brace:
textSize(36);
fill("white");
noStroke()
for (i = 0; i < 12; i++) {
v = p5.Vector.fromAngle((i + 1) / 12.0 * TAU - HALF_PI);
v.mult(310);
text(i + 1, 382 + v.x, 416 + v.y);
}
My live working demo (your code + this addition) is on ReplIt

How to draw curved text using MFC functions?

How to draw curved text using MFC functions? I want to achieve like this below.
DrawText() function draws text in straight line only, I do not know how to draw curved text at particular angle. Please help me.
Thanks.
You could use GDI+, There is a sample in code project, which is written in C#, I translate it into C++:
Graphics graphics(hWnd);
RECT rect = { 0 };
GetWindowRect(hWnd, &rect);
POINT center = { (rect.right - rect.left) / 2,(rect.bottom - rect.top) / 2 };
double radius = min(rect.right - rect.left, (rect.bottom - rect.top)) / 3;
TCHAR text[] = L"ABCDEFGHIJLKMNOPQRSTUVWXYZ";
REAL emSize = 24;
Font* font = new Font(FontFamily::GenericSansSerif(), emSize, FontStyleBold);
for (int i = 0; i < _tcslen(text); ++i)
{
RectF re, in;
Status result = graphics.MeasureString(&text[i], 1, font, in, &re);;
double charRadius = radius + re.Height;
double angle = (((float)i / _tcslen(text)) - 0.25) * 2 * M_PI;
double x = (int)(center.x + cos(angle) * charRadius);
double y = (int)(center.y + sin(angle) * charRadius);
result = graphics.TranslateTransform(x, y);
result = graphics.RotateTransform((float)(90 + 360 * angle / (2 * M_PI)));
PointF start(0, 0);
SolidBrush Red(Color(255, 255, 0, 0));
result = graphics.DrawString(&text[i], 1, font, start, &Red);
result = graphics.ResetTransform();
SolidBrush Green(Color(255, 0, 255, 0));
Pen* pen = new Pen(&Green, 2.0f);
result = graphics.DrawArc(pen, (REAL)(center.x - radius), (REAL)(center.y - radius), radius * 2, radius * 2, 0, 360);
}
Some header files:
#define _USE_MATH_DEFINES
#include <math.h>
#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib,"Gdiplus.lib")
Usage:
You must call GdiplusStartup before you create any GDI+ objects, and
you must delete all of your GDI+ objects (or have them go out of
scope) before you call GdiplusShutdown.
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
//To Do.
GdiplusShutdown(gdiplusToken);
Result:
UPDATE:
Graphics graphics(hWnd);
RECT rect = { 0 };
GetWindowRect(hWnd, &rect);
POINT center = { (rect.right - rect.left) / 2,(rect.bottom - rect.top) / 2 };
double radius = min(rect.right - rect.left, (rect.bottom - rect.top)) / 3;
TCHAR text[72][4] = { 0 };
for (int i = 0; i < 72; i++)
{
_itot((i/2)*10, text[i],10);
i++;
_tcscpy(text[i],L"");
}
REAL emSize = 8;
Font* font = new Font(FontFamily::GenericSansSerif(), emSize, FontStyleBold);
for (int i = 0; i < 72; ++i)
{
RectF re, in,rel;
Status result = graphics.MeasureString(text[i], _tcslen(text[i]), font, in, &re);
result = graphics.MeasureString(L"|", 1, font, in, &rel);
double charRadius = radius - re.Height;
double angle = (((float)i / 72) - 0.25) * 2 * M_PI;
double x = (center.x + cos(angle) * charRadius);
double y = (center.y + sin(angle) * charRadius);
result = graphics.TranslateTransform(x, y);
result = graphics.RotateTransform((float)(90 + 360 * angle / (2 * M_PI)));
PointF start(0- re.Width/2, 0);
SolidBrush Red(Color(255, 255, 0, 0));
result = graphics.DrawString(text[i], _tcslen(text[i]), font, start, &Red);
result = graphics.ResetTransform();
x = (int)(center.x + cos(angle) * radius);
y = (int)(center.y + sin(angle) * radius);
result = graphics.TranslateTransform(x, y);
result = graphics.RotateTransform((float)(90 + 360 * angle / (2 * M_PI)));
PointF start1(0 - rel.Width / 2, 0);
result = graphics.DrawString(L"|", 1, font, start1, &Red);
result = graphics.ResetTransform();
}
SolidBrush Green(Color(255, 0, 255, 0));
Pen* pen = new Pen(&Green, 2.0f);
Status result = graphics.DrawArc(pen, (REAL)(center.x - radius), (REAL)(center.y - radius), radius * 2, radius * 2, 0, 360);
Result:

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

Formula to translate xy movement with rotation

i have a canvas element that i rotate with context.rotate();
when i drag around the image in the canvas if i rotated lets say 90 degrees, and i move to the left, the image moves down,
is there a formula in which i can apply a movement of
for example
x+5 y+2 * degrees
and i get the real movement i need to do to move the rotated canvas in the direction i want? I would like to apply it to this function which works but with the undesired efect of moving left and the image moving down `
vm.canvasMouseMove = function (event) {
vm.delta = Date.now();
if (vm.mouseisdown && vm.delta - vm.now > (1000 / 60) && (event.clientX > 0 && event.clientY > 0)) {
vm.now = vm.delta
vm.snapshot.mouse.x -= event.clientX;
vm.snapshot.offsetSlider.value -= vm.snapshot.mouse.x;
if (vm.snapshot.offsetSlider.value < -160) {
vm.snapshot.offsetSlider.value = -160
}
else if (vm.snapshot.offsetSlider.value > 160) {
vm.snapshot.offsetSlider.value = 160
}
vm.snapshot.mouse.y -= event.clientY;
vm.snapshot.verticalOffsetSlider.value += vm.snapshot.mouse.y;
if (vm.snapshot.verticalOffsetSlider.value < -120) {
vm.snapshot.verticalOffsetSlider.value = -120
}
else if (vm.snapshot.verticalOffsetSlider.value > 120) {
vm.snapshot.verticalOffsetSlider.value = 120
}
vm.snapshot.mouse.x = event.clientX;
vm.snapshot.mouse.y = event.clientY;
}
};`
This draws
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.drawImage(image, vm.snapshot.offsetSlider.value - (canvas.width / 2), (vm.snapshot.verticalOffsetSlider.value * -1) - (canvas.height / 2), vm.scaledImageW * vm.snapshot.zoomSlider.value / 100, vm.scaledImageH * vm.snapshot.zoomSlider.value / 100);
ctx.rotate(-1 * vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
vm.donePicture = canvas.toDataURL();
This made the trick, passing the offset to the translate method, then draw the image only taking into account the canvas half translation
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.translate((canvas.width / 2) + (vm.snapshot.offsetSlider.value), (canvas.height / 2) - vm.snapshot.verticalOffsetSlider.value);
ctx.rotate(vm.snapshot.rotationSlider.value * Math.PI / 180);
ctx.drawImage(vm.canvasImage, 0 - (canvas.width/2), 0 - (canvas.height/2), vm.scaledImageW * vm.snapshot.zoomSlider.value / 100, vm.scaledImageH * vm.snapshot.zoomSlider.value / 100);
ctx.restore();

Resources