Best way for a Fox Rabbit Chase Simulation in QBasic - algorithm

I need some help with a homework.
Problem Definition
There is a rabbit 100 meters away from its hole and a fox 100 meters away from the rabbit in perpendicular direction to the hole.
(rabbit at (100,0), hole at (100,100), fox at (0,0)
The rabbit starts running straight to the hole with a given V1 speed and Fox chases the rabbit with a given V2 speed. Simulate the chase using QBasic and print if the rabbit gets caught or escapes.
I wrote some code, but it isn't working as it is supposed to. Even if the fox catches the rabbit, it prints that the rabbit escapes
My code so far:
CLS
SCREEN 12
WINDOW (-20, 120)-(120, -20)
LINE (0, 0)-(100, 0)
LINE (0, 100)-(0, 0)
CIRCLE (100, 100), 1
INPUT "Input speed of rabbit, v1=", v1
INPUT "input speed of fox, v2=", v2
dlt=0.01
x1 = 0: x2 = 0
y1 = 100: y2 = 0
drw: PSET (x1, y1), 1: PSET (x2, y2), 2
x1 = x1 + dlt * v1
x2 = x2 + dlt * v2 * (x1 - x2) / SQR((x1 - x2) ^ 2 + (y1 - y2) ^ 2)
y2 = y2 + dlt * v2 * (y1 - y2) / SQR((x1 - x2) ^ 2 + (y1 - y2) ^ 2)
IF SQR((x1 - x2) ^ 2 + (y1 - y2) ^ 2) < 0.01 GOTO caught
IF x1 > 100 GOTO escaped
GOTO drw
caught: PRINT "rabbit got caught": GOTO finish
escaped: PRINT "rabbit escaped"
finish: END
If you don't know about QBasic, you could also help me with an algorithm in any language. I just need a better algorithm to solve it.

First of all: although your assignment speaks of the rabbit moving along the Y-axis, you seem to have swapped X/Y coordinates throughout your code, so that actually the rabbit moves along the X axis in your implementation. This is a bit confusing, but it is not the cause of your problem.
The issue may be resulting from the margin of 0.01 you have in this test:
IF SQR((x1 - x2) ^ 2 + (y1 - y2) ^ 2) < 0.01 GOTO caught
If the speed of the fox is high enough, it may be that it "overshoots" the rabbit each time its new position is calculated, and end up at the other (alternating) side of the rabbit with a distance that is each time greater than 0.01. And so the above condition will never be true.
Instead test for the Y coordinate only (in your interpretation of X and Y), with:
IF y2 >= 100 GOTO caught
I also made an implementation in JavaScript (but with X and Y as given in the question), both with your "0.01" condition, and with the proposed fix. If you enter speeds V1=5 and V2=9, the fox can catch the rabbit, but you'll see the different outcome in both snippets:
Wrong:
async function animate() {
cls();
line(0, 0, 100, 0, "black");
line(0, 100, 0, 0, "black");
circle(100, 100, 3, "black");
let v1 = input("rabbit");
let v2 = input("fox");
let dlt = 0.1;
let x1 = 100, y1 = 0; // rabbit
let x2 = 0, y2 = 0; // fox
while (true) {
let y1to = y1 + dlt * v1;
let dist = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
let x2to = x2 + dlt * v2 * (x1 - x2) / dist;
let y2to = y2 + dlt * v2 * (y1 - y2) / dist;
line(x1, y1, x1, y1to, "green");
line(x2, y2, x2to, y2to, "red");
y1 = y1to;
x2 = x2to;
y2 = y2to;
// Problematic condition:
if (dist < 0.01) return output("rabbit got caught");
if (y1 >= 100) return output("rabbit escaped");
await delay(5);
}
}
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
// Some helper functions for JavaScript:
const input = (id) => +document.getElementById(id).value;
function line(x1, y1, x2, y2, color) {
ctx.beginPath();
ctx.moveTo(x1+0.5, y1+0.5);
ctx.lineTo(x2+0.5, y2+0.5);
ctx.strokeStyle = color;
ctx.stroke();
}
function circle(x, y, r, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.arc(x+0.5, y+0.5, r, 0, 2 * Math.PI);
ctx.stroke();
}
const output = (msg) => document.querySelector("#result").textContent = msg;
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function cls() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
output("");
}
// Bind a click handler for the button
document.querySelector("button").addEventListener("click", animate);
input { width: 4em }
.left { float: left; height: 180px; margin-right: 10px }
<div class="left">
<h3>Wrong implementation</h3>
Input speed of rabbit, v1= <input id="rabbit" type="number" value="5"><br>
Input speed of fox, v2= <input id="fox" type="number" value="9"><br>
<button>Go!</button><br>
</div>
<div>
<canvas width="105" height="105"></canvas>
<div id="result"></div>
</div>
Corrected:
async function animate() {
cls();
line(0, 0, 100, 0, "black");
line(0, 100, 0, 0, "black");
circle(100, 100, 3, "black");
let v1 = input("rabbit");
let v2 = input("fox");
let dlt = 0.1;
let x1 = 100, y1 = 0; // rabbit
let x2 = 0, y2 = 0; // fox
while (true) {
let y1to = y1 + dlt * v1;
let dist = Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2);
let x2to = x2 + dlt * v2 * (x1 - x2) / dist;
let y2to = y2 + dlt * v2 * (y1 - y2) / dist;
line(x1, y1, x1, y1to, "green");
line(x2, y2, x2to, y2to, "red");
y1 = y1to;
x2 = x2to;
y2 = y2to;
// Corrected condition:
if (x2 >= 100) return output("rabbit got caught");
if (y1 >= 100) return output("rabbit escaped");
await delay(5);
}
}
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
// Some helper functions for JavaScript:
const input = (id) => +document.getElementById(id).value;
function line(x1, y1, x2, y2, color) {
ctx.beginPath();
ctx.moveTo(x1+0.5, y1+0.5);
ctx.lineTo(x2+0.5, y2+0.5);
ctx.strokeStyle = color;
ctx.stroke();
}
function circle(x, y, r, color) {
ctx.beginPath();
ctx.strokeStyle = color;
ctx.arc(x+0.5, y+0.5, r, 0, 2 * Math.PI);
ctx.stroke();
}
const output = (msg) => document.querySelector("#result").textContent = msg;
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function cls() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
output("");
}
// Bind a click handler for the button
document.querySelector("button").addEventListener("click", animate);
input { width: 4em }
.left { float: left; height: 180px; margin-right: 10px }
<div class="left">
<h3>Corrected implementation</h3>
Input speed of rabbit, v1= <input id="rabbit" type="number" value="5"><br>
Input speed of fox, v2= <input id="fox" type="number" value="9"><br>
<button>Go!</button><br>
</div>
<div>
<canvas width="105" height="105"></canvas>
<div id="result"></div>
</div>
A final remark on your code: the use of GOTO is frowned upon. You should instead use a DO WHILE loop for looping here, potentially with an EXIT statement if really needed.

Related

What's wrong in my converting .PDE to .JS?

Hello coding community I need your help.
I took a sketch from openprocessing and I need to convert it into p5.js. The first script is the source code, then below will by my translation. I'm not sure about the syntax.
float x, y, x2, y2, rad, rad2, dist, dist2;
float deg, incr, yIn, rotateBy, ang;
void setup() {
size(600, 600);
background(#02021A);
incr = 1; // numVerts = 360/incr
rad = -20;
rad2 = -160;
dist = 500;
dist2 = 550;
}
void draw() {
noStroke();
fill(#02021A, 10);
rect(0, 0, width, height);
fill(random(0, 255), 255, 255);
rotateBy += .003;
pushMatrix();
translate(width/2, height/2);
rotate(rotateBy);
deg = 0;
while (deg <= 360) {
deg += incr;
ang = radians(deg);
x = cos(ang) * (rad + (dist * noise(y/100, yIn)));
y = sin(ang) * (rad + (dist * noise(x/80, yIn)));
ellipse(x, y, 1.5, 1.5);
x2 = sin(ang) * (rad2 + (dist2 * noise(y2/20, yIn)));
y2 = cos(ang) * (rad2 + (dist2 * noise(y2/20, yIn)));
ellipse(x2, y2, 1, 1);
}
yIn += .005;
popMatrix();
}
This what I've done.
p5.js:
let x, y, x2, y2, rad, rad2, dist, dist2;
let deg, incr, yIn, rotateBy, ang;
function setup() {
createCanvas(600, 600);
background('#02021A');
incr = 1; // numVerts = 360/incr
rad = -20;
rad2 = -160;
dist = 500;
dist2 = 550;
}
function draw() {
noStroke();
fill('#02021A');
rect(0, 0, width, height);
fill(random(0, 255), 255, 255);
rotateBy += '.003';
push();
translate(width/2, height/2);
rotate(rotateBy);
deg = 0;
while (deg <= 360) {
deg += incr;
ang = radians(deg);
x = cos(ang) * (rad + (dist * noise(y/100, yIn)));
y = sin(ang) * (rad + (dist * noise(x/80, yIn)));
ellipse(x, y, 1.5, 1.5);
x2 = sin(ang) * (rad2 + (dist2 * noise(y2/20, yIn)));
y2 = cos(ang) * (rad2 + (dist2 * noise(y2/20, yIn)));
ellipse(x2, y2, 1, 1);
}
yIn += '.005';
pop();
}
But it still doesn't work. Could you help me understand if the syntax is the same in these two languages.
You're almost there, but there are a couple of gotchas:
you declare variables at the top (e.g. float x, y, x2, y2, rad, rad2, dist, dist2;, etc.), however you don't initialize them with values. Because JavaScript is untyped (unlike Java), the interpreter can't guess what type you meant and the variables will be initialised to undefined (while in Java, because they're float type they'll default to 0). Doing math operations on undefined results in NaN (not a number).
you're accidentally incrementing some values by strings instead of floats: rotateBy += '.003'; yIn += '.005';
optional: fill(#02021A, 10); won't work in p5.js, however you can use fill(r,g,b,a) and pass your values in hex notation: fill(0x02, 0x02, 0x1A, 10);
This is your code with these two fixes applied:
let x = 0, y = 0, x2 = 0, y2 = 0, rad = 0, rad2 = 0, dist = 0, dist2 = 0;
let deg = 0, incr = 0, yIn = 0, rotateBy = 0, ang = 0;
function setup() {
createCanvas(600, 600);
background('#02021A');
incr = 1; // numVerts = 360/incr
rad = -20;
rad2 = -160;
dist = 500;
dist2 = 550;
}
function draw() {
noStroke();
fill(0x02, 0x02, 0x1A, 10);
rect(0, 0, width, height);
fill(random(0, 255), 255, 255);
rotateBy += 0.003;
push();
translate(width/2, height/2);
rotate(rotateBy);
deg = 0;
while (deg <= 360) {
deg += incr;
ang = radians(deg);
x = cos(ang) * (rad + (dist * noise(y/100, yIn)));
y = sin(ang) * (rad + (dist * noise(x/80, yIn)));
ellipse(x, y, 1.5, 1.5);
x2 = sin(ang) * (rad2 + (dist2 * noise(y2/20, yIn)));
y2 = cos(ang) * (rad2 + (dist2 * noise(y2/20, yIn)));
ellipse(x2, y2, 1, 1);
}
yIn += 0.005;
pop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
That looks pretty cool! Have fun !

How to draw regular geometrical shapes with a series of horizontal or vertical lines? [duplicate]

I'm trying to make this one https://massmoca.org/event/walldrawing340/
in Javascript code, using p5.js, but I have no clue how to fill these shapes with lines. Is there any other possibility, like making canvas that is circle or something like that, or I just have to make each shape seperately?
For now I was doing shape by shape, but making triangle and trapezoid is rough...
var sketch = function (p) {
with(p) {
let h,
w,
space;
p.setup = function() {
createCanvas(900, 400);
h = height / 2;
w = width / 3;
space = 10;
noLoop();
};
p.draw = function() {
drawBackground('red', 'blue', 0, 0);
shape('Circle', 'red', 'blue', 0, 0);
drawBackground('yellow', 'red', w, 0);
shape('Square', 'yellow', 'red', w, 0);
drawBackground('blue', 'yellow', 2 * w, 0);
shape('Triangle', 'blue', 'red', 2 * w, 0)
drawBackground('red', 'yellow', 0, h);
shape('Rectangle', 'red', 'blue', 0, h)
drawBackground('yellow', 'blue', w, h);
shape('Trapezoid', 'yellow', 'red', w, h);
drawBackground('blue', 'red', 2 * w, h);
};
function drawBackground(bColor, lColor, x, y) {
fill(bColor)
noStroke();
rect(x, y, w, h)
stroke(lColor);
strokeWeight(1);
for (let i = 0; i < h / space; i++) {
line(0 + x, i * space + y + 10, w + x, i * space + y + 10);
}
}
function shape(shape, bColor, lColor, x, y) {
fill(bColor)
noStroke();
let w1;
switch (shape) {
case 'Circle':
circle(x + w / 2, y + h / 2, h - space * 6);
stroke(lColor);
strokeWeight(1);
for (let i = 0; i < w / space; i++) {
for (let j = 0; j < h; j++) {
pX = i * space + x;
pY = 0 + y + j;
if (pow(x + w / 2 - pX, 2)
+ pow(pY - (y + h / 2), 2) <= pow(h - space * 6 * 2 - 10, 2)) {
point(pX, pY);
}
}
}
break;
case 'Square':
w1 = w - (h - space * 6);
rect(x + w1 / 2, y + space * 3, h - space * 6, h - space * 6);
stroke(lColor);
strokeWeight(1);
for (let i = 0; i < 15; i++) {
for (let j = 0; j < h - space * 6; j++) {
point(x + w1 / 2 + i * space, y + space * 3 + j)
}
}
break;
case 'Triangle':
w1 = w - (h - space * 6);
triangle(x + w1 / 2, h - space * 3 + y, x + w / 2, y + space * 3, x + w1 / 2 + h - space * 6, h - space * 3 + y)
for (let i = 0; i < w / space; i++) {
for (let j = 0; j < h; j++) {
pX = i * space + x;
pY = 0 + y + j;
if (pow(x + w / 2 - pX, 2)
+ pow(pY - (y + h / 2), 2) <= pow(h - space * 6 * 2 - 10, 2)) {
point(pX, pY);
}
}
}
break;
case 'Rectangle':
w1 = w - (h - space * 6) / 2;
rect(x + w1 / 2, y + space * 3, (h - space * 6) / 2, h - space * 6)
break;
case 'Trapezoid':
w1 = w - (h - space * 6);
quad(x + w1 / 2, h - space * 3 + y, x + w1 / 2 + (h - space * 6) / 4, y + space * 3, x + w1 / 4 + h - space * 6, y + space * 3, x + w1 / 2 + h - space * 6, h - space * 3 + y)
break;
case 'Parallelogram':
w1 = w - (h - space * 6);
quad(x + w1 / 4, h - space * 3 + y, x + w1 / 2, y + space * 3, x + w1 / 2 + h - space * 6, y + space * 3, x + w1 / 4 + h - space * 6, h - space * 3 + y)
break;
break;
}
}
}
};
let node = document.createElement('div');
window.document.getElementById('p5-container').appendChild(node);
new p5(sketch, node);
body {
background-color:#efefef;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<div id="p5-container"></div>
No messages, everything is working, I just want to know if I have to do so much arduous job...
If you don't need actual line coordinates (for plotting for example), I'd just make most out of createGraphics() to easily render shapes and lines into (taking advantage of the fact that get() returns a p5.Image) and p5.Image's mask() function.
Here's a basic example:
function setup() {
createCanvas(600, 300);
let w = 300;
let h = 150;
let spacing = 12;
let strokeWidth = 1;
const BLUE = color('#005398');
const YELLOW = color('#f9db44');
const RED = color('#dc1215');
bg = getLinesRect(w, h, RED, BLUE, spacing, strokeWidth, true);
fg = getLinesRect(w, h, RED, YELLOW, spacing, strokeWidth, false);
mask = getCircleMask(w, h, w * 0.5, h * 0.5, 100, 0);
image(bg, 0, 0);
image(fg, w, 0);
// render opaque mask (for visualisation only), mask() requires alpha channel
image(getCircleMask(w, h, w * 0.5, h * 0.5, 100, 255),0, h);
// apply mask
fg.mask(mask);
// render bg + masked fg
image(bg, w, h);
image(fg, w, h);
// text labels
noStroke();
fill(255);
text("bg layer", 9, 12);
text("fg layer", w + 9, 12);
text("mask", 9, h + 12);
text("bg + masked fg", w + 9, h + 12);
}
function getLinesRect(w, h, bg, fg, spacing, strokeWidth, isHorizontal){
let rect = createGraphics(w, h);
rect.background(bg);
rect.stroke(fg);
rect.strokeWeight(strokeWidth);
if(isHorizontal){
for(let y = 0 ; y < h; y += spacing){
rect.line(0, y + strokeWidth, w, y + strokeWidth);
}
}else{
for(let x = 0 ; x < w; x += spacing){
rect.line(x + strokeWidth, 0, x + strokeWidth, h);
}
}
// convert from p5.Graphics to p5.Image
return rect.get();
}
function getCircleMask(w, h, cx, cy, cs, opacity){
let mask = createGraphics(w, h);
// make background transparent (alpha is used for masking)
mask.background(0, opacity);
mask.noStroke();
mask.fill(255);
mask.circle(cx, cy, cs);
// convert p5.Graphics to p5.Image
return mask.get();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
You can apply the same logic for the rest of the shapes:
function setup() {
createCanvas(1620, 590);
let compWidth = 500;
let compHeight = 250;
let compSpacing= 30;
let lineWeight = 1.5;
let lineSpacing = 12;
const BLUE = color('#005398');
const YELLOW = color('#f9db44');
const RED = color('#dc1215');
// yellow square
circleMask = getCircleMask(compWidth, compHeight, compWidth * 0.5, compHeight * 0.5, 210);
redCircle = getComposition(compWidth, compHeight, RED,
BLUE,
YELLOW,
lineSpacing, lineWeight, circleMask);
// red box
boxMask = getRectMask(compWidth, compHeight, (compWidth - 100) * 0.5, 20, 100, 210);
redBox = getComposition(compWidth, compHeight, RED,
YELLOW,
BLUE,
lineSpacing, lineWeight, boxMask);
// yellow square
squareMask = getRectMask(compWidth, compHeight, 144, 20, 210, 210);
yellowSquare = getComposition(compWidth, compHeight, YELLOW,
RED,
BLUE,
lineSpacing, lineWeight, squareMask);
// yellow trapeze
trapezeMask = getQuadMask(compWidth, compHeight, 200, 25, 200 + 115, 25,
150 + 220, 220, 150, 220);
yellowTrapeze = getComposition(compWidth, compHeight, YELLOW,
BLUE,
RED,
lineSpacing, lineWeight, trapezeMask);
// blue triangle
triangleMask = getTriangleMask(compWidth, compHeight, compWidth * 0.5, 25,
150 + 220, 220, 150, 220);
blueTriangle = getComposition(compWidth, compHeight, BLUE,
YELLOW,
RED,
lineSpacing, lineWeight, triangleMask);
// blue parallelogram
parallelogramMask = getQuadMask(compWidth, compHeight, 200, 25, 200 + 145, 25,
150 + 145, 220, 150, 220);
blueParallelogram = getComposition(compWidth, compHeight, BLUE,
RED,
YELLOW,
lineSpacing, lineWeight, parallelogramMask);
// render compositions
image(redCircle, compSpacing, compSpacing);
image(redBox, compSpacing, compSpacing + (compHeight + compSpacing));
image(yellowSquare, compSpacing + (compWidth + compSpacing), compSpacing);
image(yellowTrapeze, compSpacing + (compWidth + compSpacing), compSpacing + (compHeight + compSpacing));
image(blueTriangle, compSpacing + (compWidth + compSpacing) * 2, compSpacing);
image(blueParallelogram, compSpacing + (compWidth + compSpacing) * 2, compSpacing + (compHeight + compSpacing));
}
function getComposition(w, h, bgFill, bgStroke, fgStroke, spacing, strokeWidth, mask){
let comp = createGraphics(w, h);
bg = getLinesRect(w, h, bgFill, bgStroke, spacing, strokeWidth, true);
fg = getLinesRect(w, h, bgFill, fgStroke, spacing, strokeWidth, false);
// apply mask
fg.mask(mask);
// render to final output
comp.image(bg, 0, 0);
comp.image(fg, 0, 0);
return comp;
}
function getRectMask(w, h, rx, ry, rw, rh){
let mask = createGraphics(w, h);
// make background transparent (alpha is used for masking)
mask.background(0,0);
mask.noStroke();
mask.fill(255);
mask.rect(rx, ry, rw, rh);
// convert p5.Graphics to p5.Image
return mask.get();
}
function getCircleMask(w, h, cx, cy, cs){
let mask = createGraphics(w, h);
// make background transparent (alpha is used for masking)
mask.background(0,0);
mask.noStroke();
mask.fill(255);
mask.circle(cx, cy, cs);
// convert p5.Graphics to p5.Image
return mask.get();
}
function getQuadMask(w, h, x1, y1, x2, y2, x3, y3, x4, y4){
let mask = createGraphics(w, h);
// make background transparent (alpha is used for masking)
mask.background(0,0);
mask.noStroke();
mask.fill(255);
mask.quad(x1, y1, x2, y2, x3, y3, x4, y4);
// convert p5.Graphics to p5.Image
return mask.get();
}
function getTriangleMask(w, h, x1, y1, x2, y2, x3, y3){
let mask = createGraphics(w, h);
// make background transparent (alpha is used for masking)
mask.background(0,0);
mask.noStroke();
mask.fill(255);
mask.triangle(x1, y1, x2, y2, x3, y3);
// convert p5.Graphics to p5.Image
return mask.get();
}
function getLinesRect(w, h, bg, fg, spacing, strokeWidth, isHorizontal){
let rect = createGraphics(w, h);
rect.background(bg);
rect.stroke(fg);
rect.strokeWeight(strokeWidth);
if(isHorizontal){
for(let y = 0 ; y < h; y += spacing){
rect.line(0, y + strokeWidth, w, y + strokeWidth);
}
}else{
for(let x = 0 ; x < w; x += spacing){
rect.line(x + strokeWidth, 0, x + strokeWidth, h);
}
}
// convert from p5.Graphics to p5.Image
return rect.get();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
Probably both rectangles and the triangle could've been drawn using getQuadMask() making good use of coordinates.
Note that I've just eye balled the shapes a bit so they're not going to be perfect, but it should be easy to tweak. Bare in mind the placement of the mask will have an effect of on how the vertical lines will align.
There are probably other ways to get the same visual effect.
For example, using texture() and textureWrap(REPEAT) with beginShape()/endShape(), using pixels for each line and checking intersections before changing direction and colours, etc.
In terms of generating lines for plotting I would start with horizontal lines, doing line to convex polygon intersection to determine where to stop the horizontal lines and start vertical lines. #AgniusVasiliauskas's answer(+1) is good for that approach.
Freya Holmér has a pretty nice visual explanation for the test.
You need linear algebra stuff, basically noticing how vertical line starting/ending Y coordinate changes in relation to line's X coordinate. And of course a lot of experimenting until you get something usable. Something like :
var w = 600
h = 600
sp = 15
var slides = [fcircle, fsquare, ftriangle, ftrapezoid, fparallelogram];
var active = 0;
var ms;
function blines(){
stroke(0);
for (var i=0; i < h; i+=sp) {
line(0,i,w,i);
}
}
function vertlines(calcline) {
for (var x=w/2-w/4+sp; x < w/2+w/4; x+=sp) {
var pnts = calcline(x);
line(pnts[0],pnts[1],pnts[2],pnts[3]);
}
}
function fcircle() {
// cut background
noStroke();
circle(w/2, h/2, w/2);
stroke('red');
// draw figure lines
let calc = function (x){
var sx = x-w/2;
var sy = h/2;
var ey = h/2;
sy += 137*sin(2.5+x/135);
ey -= 137*sin(2.5+x/135);
return [x,sy,x,ey];
}
vertlines(calc);
}
function fsquare() {
// cut background
noStroke();
quad(w/2-w/4, h/2-h/4, w/2+w/4, h/2-h/4,
w/2+w/4, h/2+h/4, w/2-w/4, h/2+h/4);
stroke('red');
// draw figure lines
let calc = function (x){
return [x,h/2-h/4,x,h/2+h/4];
}
vertlines(calc);
}
function ftriangle() {
// cut background
noStroke();
quad(w/2, h/2-h/4, w/2+w/4, h/2+h/4,
w/2-w/4, h/2+h/4, w/2, h/2-h/4);
stroke('red');
// draw figure lines
let calc = function (x){
var inpx = x > w/2 ? w-x : x;
var ys = h/2+h/4;
ys += -(0.3*inpx*log(inpx)-220);
return [x,ys,x,h/2+h/4];
}
vertlines(calc);
}
function ftrapezoid() {
// cut background
noStroke();
quad(w/2-w/10, h/2-h/4, w/2+w/10, h/2-h/4,
w/2+w/4, h/2+h/4, w/2-w/4, h/2+h/4);
stroke('red');
// draw figure lines
let calc = function (x){
var inpx = x > w/2 ? w-x : x;
var ys = h/2+h/4;
ys += -(0.55*inpx*log(inpx)-420);
if (x >= w/2-w/10 && x <= w/2+w/10) {
ys=h/2-h/4;
}
return [x,ys,x,h/2+h/4];
}
vertlines(calc);
}
function fparallelogram() {
// cut background
noStroke();
quad(w/2-w/10, h/2-h/4, w/2+w/7, h/2-h/4,
w/2, h/2+h/4, w/2-w/4, h/2+h/4);
stroke('red');
// draw figure lines
let calc = function (x){
// guard condition
if (x > w/2+w/7)
return [0,0,0,0];
var inpx = x > w/2 ? w-x : x;
var ys = h/2+h/4;
ys += -(0.55*inpx*log(inpx)-420);
var ye=h/2+h/4
if (x >= w/2-w/10) {
ys=h/2-h/4;
}
if (x > w/2) {
ye = h/2+h/4;
ye += 0.50*inpx*log(inpx)-870;
}
return [x,ys,x,ye];
}
vertlines(calc);
}
function setup() {
ms = millis();
createCanvas(w, h);
}
function draw() {
if (millis() - ms > 2000) {
ms = millis();
active++;
if (active > slides.length-1)
active = 0;
}
background('#D6EAF8');
fill('#D6EAF8');
blines();
slides[active]();
}
Slideshow DEMO
I have a way to do some of the shapes, but I am not sure about others. One way you could do it is if you know where every point on the outline of the shape is, you could just use a for loop and connect every other point from the top and bottom using the line or rect function. This would be relatively easy with shapes like squares and parallelograms, but I am not sure what functions could be used to get this for the points of a circle or trapezoid.
See more here: https://www.openprocessing.org/sketch/745383

Cartesian coordinates algorithmb question

On the 2D grid, there is a cartesian coordinate C(Cx,Cy) which is a center of square and it has 'b' of radius.
And there are two points P1(x1,y1), P2(x2,y2). When I connect P1 with P2 directly by line, there should be a straight line.
I wanna make the pseudo code that checking whether the straight line between P1 and P2 is on the square area or not.
The argument would be center point and two different points and radius.
Square center: (Cx,Cy)
Two points: P1(x1,y1), P2(x2,y2)
Radius: 'b'
function Straight ((x1,y1),(x2,y2),(Cx,Cy),b)
If the straight line is not on the square area, it should return true, if it is on the square area, it should return false.
You can convert this problem (by projecting the coordinates) to a square at (0, 0) with "radius" 1.
For determining whether the line segment (p1, p2) crosses the top side of the square, you can first test these conditions:
(y2 - 1) * (y2 - 1) > 0. If this is true, it means that the line segment is completely above the top of the square, or completely below it.
y1 = y2. If this is true, the line segment is parallel with the square.
In all other cases, the x-coordinate of the intersection point is:
x = x1 - y1 * (x2 - x1) / (y2 - y1)
If this x is not in the range [-1, 1], the line segment does not intersect the top of the square.
A similar operation can be done for the three other sides.
If any of them gives an intersection coordinate in the range [-1, 1], the function straight should return true, and false otherwise.
Here is an interactive JavaScript implementation with which you can draw a line segment (by "dragging" the mouse) near a square. The square will highlight when the call to straight returns true:
function intersectionWithXaxis(x1, y1, x2, y2) {
if (y1 * y2 > 0 || y1 === y2) return Infinity; // No intersection
return x1 - y1 * (x2 - x1) / (y2 - y1); // x-coordinate of intersection
}
function straight(x1, y1, x2, y2, cx, cy, b) {
// Project the coordinates so the square is at (0, 0) with "radius" 1
x1 = (x1-cx)/b;
y1 = (y1-cy)/b;
x2 = (x2-cx)/b;
y2 = (y2-cy)/b;
let z;
// Get intersections with top, bottom, left and right side of box:
z = intersectionWithXaxis(x1, y1-1, x2, y2-1);
if (Math.abs(z) <= 1) return true;
z = intersectionWithXaxis(x1, y1+1, x2, y2+1);
if (Math.abs(z) <= 1) return true;
// We can use the same function for vertical line intersections by swapping x and y
z = intersectionWithXaxis(y1, x1-1, y2, x2-1);
if (Math.abs(z) <= 1) return true;
z = intersectionWithXaxis(y1, x1+1, y2, x2+1);
if (Math.abs(z) <= 1) return true;
return false;
}
let cx = 100;
let cy = 60;
let b = 30;
let x1, y1, x2, y2;
// I/O handling
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "yellow";
let isMouseDown = false;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.rect(cx-b, cy-b, 2*b, 2*b);
ctx.stroke();
if (!isMouseDown) return;
// Call the main function. If true, highlight the square
if (straight(x1, y1, x2, y2, cx, cy, b)) ctx.fill();
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
canvas.addEventListener("mousedown", function(e) {
x1 = e.clientX - this.offsetLeft;
y1 = e.clientY - this.offsetTop;
isMouseDown = true;
});
canvas.addEventListener("mousemove", function(e) {
if (!isMouseDown) return;
x2 = e.clientX - this.offsetLeft;
y2 = e.clientY - this.offsetTop;
draw();
});
canvas.addEventListener("mouseup", function(e) {
isMouseDown = false;
});
draw();
<canvas width="400" height="180"></canvas>

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

how to calculate the dist() from mouseX, mouseY to a rectangle in Processing

If it was the dist to a point it would be
dist(mouseX, mouseY, x, y)
for
point(x,y)
but how can I calculate dist() from the mouse's current position to
rectMode(CORNERS);
rect(x1,y2,x2,y2);
Thanks
Something like this should do it:
float distrect(float x, float y, float x1, float y1, float x2, float y2){
float dx1 = x - x1;
float dx2 = x - x2;
float dy1 = y - y1;
float dy2 = y - y2;
if (dx1*dx2 < 0) { // x is between x1 and x2
if (dy1*dy2 < 0) { // (x,y) is inside the rectangle
return min(min(abs(dx1), abs(dx2)),min(abs(dy1),abs(dy2)));
}
return min(abs(dy1),abs(dy2));
}
if (dy1*dy2 < 0) { // y is between y1 and y2
// we don't have to test for being inside the rectangle, it's already tested.
return min(abs(dx1),abs(dx2));
}
return min(min(dist(x,y,x1,y1),dist(x,y,x2,y2)),min(dist(x,y,x1,y2),dist(x,y,x2,y1)));
}
Basically, you need to figure out if the closes point is on one of the sides, or in the corner. This picture may help, it shows the distance of a point from a rectangle for different positions of the point:
Here's a somewhat interactive program which accomplishes what you're looking for. You can drop it into Processing and run it if you would like.
EDIT: Here's a screenshot:
// Declare vars.
int x_click = -20; // Initializes circle and point off-screen (drawn when draw executes)
int y_click = -20;
float temp = 0.0;
float min_dist = 0.0;
int x1, x2, x3, x4, y1, y2, y3, y4;
// Setup loop.
void setup() {
size(400, 400);
// Calculate the points of a 40x40 centered rectangle
x1 = width/2 - 20;
y1 = height/2 - 20;
x2 = width/2 + 20;
y2 = y1;
x3 = x1;
y3 = height/2 + 20;
x4 = x2;
y4 = y3;
}
// Draw loop.
void draw(){
background(255);
// Draws a purple rectangle in the center of the screen.
rectMode(CENTER);
fill(154, 102, 200);
rect(width/2, height/2, 40, 40);
// Draws an orange circle where the user last clicked.
ellipseMode(CENTER);
fill(204, 102, 0);
ellipse(x_click, y_click, 10, 10);
// Draws black point where the user last clicked.
fill(0);
point(x_click, y_click);
// Draws min dist onscreen.
textAlign(CENTER);
fill(0);
text("min dist = " + min_dist, width/2, height/2 + 150);
}
void mousePressed(){
x_click = mouseX;
y_click = mouseY;
// If the click isn't perpendicular to any side of the rectangle, the min dist is a corner.
if ( ((x_click <= x1) || (x_click >= x2)) && ((y_click <= y1) || (y_click >= y3)) ) {
min_dist = min(min(dist(x1,y1,x_click,y_click),dist(x2,y2,x_click,y_click)), min(dist(x3,y3,x_click,y_click),dist(x4,y4,x_click,y_click)));
} else if( (x_click > x1) && (x_click < x2) && ((y_click < y1) || (y_click > y3)) ) {
// outside of box, closer to top or bottom
min_dist = min(abs(y_click - y1), abs(y_click - y3));
} else if( (y_click > y1) && (y_click < y3) && ((x_click < x1) || (x_click > x2)) ) {
// outside of box, closer to right left
min_dist = min(abs(x_click - x1), abs(x_click - x2));
} else {
// inside of box, check against all boundaries
min_dist = min(min(abs(y_click - y1), abs(y_click - y3)),min(abs(x_click - x1), abs(x_click - x2)));
}
// Print to console for debugging.
//println("minimum distance = " + min_dist);
}
This is what I use. If you are only interested in the relative distance there is probably no need to take the square root which should make it slightly quicker.
- (NSInteger) distanceFromRect: (CGPoint) aPoint rect: (CGRect) aRect
{
NSInteger posX = aPoint.x;
NSInteger posY = aPoint.y;
NSInteger leftEdge = aRect.origin.x;
NSInteger rightEdge = aRect.origin.x + aRect.size.width;
NSInteger topEdge = aRect.origin.y;
NSInteger bottomEdge = aRect.origin.y + aRect.size.height;
NSInteger deltaX = 0;
NSInteger deltaY = 0;
if (posX < leftEdge) deltaX = leftEdge - posX;
else if (posX > rightEdge) deltaX = posX - rightEdge;
if (posY < topEdge) deltaY = topEdge - posY;
else if (posY > bottomEdge) deltaY = posY - bottomEdge;
NSInteger distance = sqrt(deltaX * deltaX + deltaY * deltaY);
return distance;
}

Resources