How to color line in canvas? - javascript

EDIT: I'm trying to get a single line colored, not the whole grid
So, what I'm trying to do is that when the canvas gets clicked, the lines selected move and get colored.
I already have the lines moving but I cannot get them colored. I know I have to use the strokeStyle property, but how do I get the specific line colored?
I tried putting the strokeStyle under the draw_each method, but that doesn't work, and I don't know why. Here's my code:
//draw each line in array
function draw_each(p1, p2, p3, p4) {
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
}
https://codepen.io/diazabdulm/pen/qJyrZP?editors=0010

It seems to me that what you want is actually to generate a color per segment based on the force that's been applied to it, so that the wave effect keeps its effect.
What you can do, is to save the median of each node's velocity in both axis as a mean to represent the force they're currently experiencing, and in your drawing part to get the median of all nodes that will compose each segment of your grid and compose a color from it.
But this comes with a drawback: your code was well written enough to compose the whole grid a single sub-path, thus limiting the number of drawings necessary. But now, we have to make each segment its own sub-path (since they'll got their own color), so we'll loose a lot in terms of performances...
var gnum = 90; //num grids / frame
var _x = 2265; //x width (canvas width)
var _y = 1465; //y height (canvas height)
var w = _x / gnum; //grid sq width
var h = _y / gnum; //grid sq height
var $; //context
var parts; //particles
var frm = 0; //value from
var P1 = 0.0005; //point one
var P2 = 0.01; //point two
var n = 0.98; //n value for later
var n_vel = 0.03; //velocity
var ŭ = 0; //color update
var msX = 0; //mouse x
var msY = 0; //mouse y
var msdn = false; //mouse down flag
var Part = function() {
this.x = 0; //x pos
this.y = 0; //y pos
this.vx = 0; //velocity x
this.vy = 0; //velocity y
this.ind_x = 0; //index x
this.ind_y = 0; //index y
};
Part.prototype.frame = function() {
if (this.ind_x == 0 || this.ind_x == gnum - 1 || this.ind_y == 0 || this.ind_y == gnum - 1) {
return;
}
var ax = 0; //angle x
var ay = 0; //angle y
//off_dx, off_dy = offset distance x, y
var off_dx = this.ind_x * w - this.x;
var off_dy = this.ind_y * h - this.y;
ax = P1 * off_dx;
ay = P1 * off_dy;
ax -= P2 * (this.x - parts[this.ind_x - 1][this.ind_y].x);
ay -= P2 * (this.y - parts[this.ind_x - 1][this.ind_y].y);
ax -= P2 * (this.x - parts[this.ind_x + 1][this.ind_y].x);
ay -= P2 * (this.y - parts[this.ind_x + 1][this.ind_y].y);
ax -= P2 * (this.x - parts[this.ind_x][this.ind_y - 1].x);
ay -= P2 * (this.y - parts[this.ind_x][this.ind_y - 1].y);
ax -= P2 * (this.x - parts[this.ind_x][this.ind_y + 1].x);
ay -= P2 * (this.y - parts[this.ind_x][this.ind_y + 1].y);
this.vx += (ax - this.vx * n_vel);
this.vy += (ay - this.vy * n_vel);
//EDIT\\
// store the current velocity (here base on 100 since it will be used with hsl())
this.color = (Math.abs(this.vx)+Math.abs(this.vy)) * 50;
this.x += this.vx * n;
this.y += this.vy * n;
if (msdn) {
var dx = this.x - msX;
var dy = this.y - msY;
var ɋ = Math.sqrt(dx * dx + dy * dy);
if (ɋ > 50) {
ɋ = ɋ < 10 ? 10 : ɋ;
this.x -= dx / ɋ * 5;
this.y -= dy / ɋ * 5;
}
}
};
function go() {
parts = []; //particle array
for (var i = 0; i < gnum; i++) {
parts.push([]);
for (var j = 0; j < gnum; j++) {
var p = new Part();
p.ind_x = i;
p.ind_y = j;
p.x = i * w;
p.y = j * h;
parts[i][j] = p;
}
}
}
//move particles function
function mv_part() {
for (var i = 0; i < gnum; i++) {
for (var j = 0; j < gnum; j++) {
var p = parts[i][j];
p.frame();
}
}
}
//draw grid function
function draw() {
//EDIT
// we unfortunately have to break the drawing part
// since each segment has its own color, we can't have a single sub-path anymore...
ŭ -= .5;
for (var i = 0; i < gnum - 1; i += 1) {
for (var j = 0; j < gnum - 1; j += 1) {
var p1 = parts[i][j];
var p2 = parts[i][j + 1];
var p3 = parts[i + 1][j + 1];
var p4 = parts[i + 1][j];
draw_each(p1, p2, p3, p4);
}
}
}
//draw each in array
function draw_each(p1, p2, p3, p4) {
// for each segment we set the color
$.strokeStyle = `hsl(0deg, ${(p1.color+p2.color+p3.color+p4.color) / 4}%, 50%)`;
// begin a new sub-path
$.beginPath();
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
// and stroke it
$.stroke();
}
//call functions to run
function calls() {
$.fillStyle = "hsla(0, 0%, 7%, 1)";
$.fillRect(0, 0, _x, _y);
mv_part();
draw();
frm++;
}
var c = document.getElementById('canv');
var $ = c.getContext('2d');
$.fillStyle = "hsla(0, 0%, 7%, 1)";
$.fillRect(0, 0, _x, _y);
function resize() {
if (c.width < window.innerWidth) {
c.width = window.innerWidth;
}
if (c.height < window.innerHeight) {
c.height = window.innerHeight;
}
}
requestAnimationFrame(go);
document.addEventListener('click', MSMV, false);
document.addEventListener('click', MSDN, false);
function MSDN(e) {
msdn = true;
window.setTimeout(function() {
msdn = false;
}, 100);
}
function MSUP(e) {
msdn = false;
}
function MSMV(e) {
var rect = e.target.getBoundingClientRect();
msX = e.clientX - rect.left;
msY = e.clientY - rect.top;
}
window.onload = function() {
run();
function run() {
requestAnimationFrame(calls);
requestAnimationFrame(run);
}
resize();
};
onresize = resize;
body {
width: 100%;
overflow: hidden;
cursor:move;
}
<canvas id="canv" width="150" height="150"></canvas>
Now, a more performant way, but less good looking, would be to draw a radial-gradient where the click happened, with a bit of compositing, you'd be able to get something cheap that might work, but it would be quite a lot of coding actually to get multiple such gradients at the same time...

That works for me:
//draw each line in array
function draw_each(p1, p2, p3, p4) {
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
$.strokeStyle = '#458920';
}

Related

What's causing this tunnel effect to render incorrectly?

I'm currently attempting to create a sort of 3D tunnel effect in Javascript using the canvas tag.
It works, in the sense that it actually renders something, but it looks like this:
Not only is that not a tunnel, but it also takes up only a quarter of the canvas for some reason.
I wish I knew how to solve this, but I'm not getting any errors from the console, and the script itself seems to be running just fine, so I don't know where to start.
var texWidth = 256;
var texHeight = 256;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var texture = new Array(texHeight);
var distanceTable = new Array(h);
var angleTable = new Array(h);
var imageData = ctx.createImageData(canvas.width, canvas.height);
var w = imageData.width;
var h = imageData.height;
for (i = 0; i < texHeight; i++) {
texture[i] = new Array(texWidth);
}
for (j = 0; j < h; j++) {
distanceTable[j] = new Array(w);
angleTable[j] = new Array(w);
}
for (y = 0; y < texHeight; y++) {
for (x = 0; x < texHeight; x++) {
texture[y][x] = x ^ y;
}
}
for (y2 = 0; y2 < h; y2++) {
for (x2 = 0; x2 < w; x2++) {
let angle;
let distance;
let ratio = 32.0;
distance = ~~(ratio * texHeight / Math.sqrt((x2 - w / 2.0) * (x2 - w / 2.0) + (y2 - h / 2.0) * (y2 - h / 2.0))) % texHeight;
angle = Math.abs(~~(0.5 * texWidth * Math.atan2(y2 - h / 2.0, x2 - w / 2.0) / Math.PI));
distanceTable[y2][x2] = distance;
angleTable[y2][x2] = angle;
}
}
var animation = 0;
function draw() {
animation = animation + 0.01;
let shiftX = texWidth * 1.0 * animation;
let shiftY = texHeight * 0.25 * animation;
for (y3 = 0; y3 < h; y3++) {
for (x3 = 0; x3 < w; x3++) {
let id = y3 * w + x3 * 4;
let c = texture[~~(distanceTable[y3][x3] + shiftX) % texWidth][~~(angleTable[y3][x3] + shiftY) % texHeight];
imageData.data[id] = 0;
imageData.data[id+1] = 0;
imageData.data[id+2] = c;
imageData.data[id+3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
Since I have very little else to work with, I tried modifying some of the variables. That produces some interesting strobe-like effects, but not much else.

Circle - circle collision response (balls stuck overlapping)

I'm trying to build something like this in JavaScript using p5.js:
I'm able to detect collision between two circles and I'm trying to calculate the new speeds using these (two-dimensional elastic collision) formulas from Wikipedia:
With these angles for theta and phi:
The problem seems to be that when I have calculated the new speeds, the balls are already collided / overlapping and they are getting stuck. What I think I should do is calculate a new circle position based on the distance that the circles are overlapping. Unfortunately I'm not sure how to do this. I also think that the way I'm calculating is overly complex and inefficient.
This is what my collision handling code looks like:
// takes ball object as input, sets speedvector to new x,y speed
collision(other) {
var newSpeed = createVector();
var phi = Math.atan((this.pos.y - other.pos.y) / this.pos.x - other.pos.x);
var theta1 = this.speed.heading();
var theta2 = other.speed.heading();
newSpeed.x = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.cos(phi) - this.speed.mag() * Math.sin(theta1 - phi) * Math.sin(phi);
newSpeed.y = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.sin(phi) + this.speed.mag() * Math.sin(theta1 - phi) * Math.cos(phi);
this.speed.x = newSpeed.x;
this.speed.y = newSpeed.y;
}
The complete code example:
var balls = [];
var numOfBalls = 5;
var maxSpeed = 2;
function setup() {
createCanvas(500, 500);
for (var i = 0; i < numOfBalls; i++) {
var ball = new Ball(30);
balls.push(ball);
}
}
function draw() {
background(0);
for (var i = 0; i < balls.length; i++) {
balls[i].move();
}
for (var i = 0; i < balls.length; i++) {
balls[i].show();
}
}
class Ball {
constructor(radius) {
this.radius = radius;
this.pos = this.pickLocation();
this.speed = createVector(random(-maxSpeed, maxSpeed), random(-maxSpeed, maxSpeed));
this.mass = 1;
}
pickLocation() {
//spawn within canvas
var xOption = random(this.radius, width - this.radius);
var yOption = random(this.radius, height - this.radius);
// check whether spawning on this location doesn't overlap other circles
for(var i = 0; i < balls.length; i++) {
// don't check for current circle
if(balls[i] != this) {
// get distance to other circle
var d = dist(xOption, yOption, balls[i].pos.x, balls[i].pos.y);
// check whether overlapping
if (d <= this.radius + balls[i].radius) {
// generate new location and rerun check
console.log("overlapping another circle, trying new location");
var xOption = random(this.radius, width - this.radius);
var yOption = random(this.radius, height - this.radius);
i = -1;
}
}
}
return(createVector(xOption, yOption));
}
move() {
for (var i = 0; i < balls.length; i++) {
if(balls[i] != this) {
var d = dist(this.pos.x, this.pos.y, balls[i].pos.x, balls[i].pos.y);
if(d < this.radius + balls[i].radius) {
this.collision(balls[i]);
}
}
}
if(this.pos.x - this.radius < 0 || this.pos.x + this.radius > width) {
this.speed.x *= -1;
}
if(this.pos.y - this.radius < 0 || this.pos.y + this.radius > height) {
this.speed.y *= -1;
}
this.pos.x += this.speed.x;
this.pos.y += this.speed.y;
}
// takes ball object as input, sets speedvector to new x,y speed
collision(other) {
var newSpeed = createVector();
var phi = Math.atan((this.pos.y - other.pos.y) / this.pos.x - other.pos.x);
var theta1 = this.speed.heading();
var theta2 = other.speed.heading();
newSpeed.x = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.cos(phi) - this.speed.mag() * Math.sin(theta1 - phi) * Math.sin(phi);
newSpeed.y = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.sin(phi) + this.speed.mag() * Math.sin(theta1 - phi) * Math.cos(phi);
this.speed.x = newSpeed.x;
this.speed.y = newSpeed.y;
}
show() {
fill(200, 100);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>
I made this code. I completely changed the collision function but it's working great!
also, constrained the position so it won't get stuck on the walls.
I made the exact thing a few months ago so you can look at it
here
I also divided the canvas into grids so it is a lot more efficient than looping through every ball.
mine can do about 4000 balls before it lags
they way the collision function works is like the newton's cradle. the two objects change speeds(ball A which is moving hits stationary ball B and now ball A doesn't move and ball B moves at the same speed ball A was)
var balls = [];
var numOfBalls = 5;
var maxSpeed = 2;
function setup() {
createCanvas(500, 500);
for (var i = 0; i < numOfBalls; i++) {
var ball = new Ball(30);
balls.push(ball);
}
}
function draw() {
background(0);
for (var i = 0; i < balls.length; i++) {
balls[i].move();
}
for (var i = 0; i < balls.length; i++) {
balls[i].show();
}
}
class Ball {
constructor(radius) {
this.radius = radius;
this.pos = this.pickLocation();
this.speed = createVector(random(-maxSpeed, maxSpeed), random(-maxSpeed, maxSpeed));
this.mass = 1;
}
pickLocation() {
//spawn within canvas
var xOption = random(this.radius, width - this.radius);
var yOption = random(this.radius, height - this.radius);
// check whether spawning on this location doesn't overlap other circles
for(var i = 0; i < balls.length; i++) {
// don't check for current circle
if(balls[i] != this) {
// get distance to other circle
var d = dist(xOption, yOption, balls[i].pos.x, balls[i].pos.y);
// check whether overlapping
if (d <= this.radius + balls[i].radius) {
// generate new location and rerun check
console.log("overlapping another circle, trying new location");
xOption = random(this.radius, width - this.radius);
yOption = random(this.radius, height - this.radius);
i = -1;
}
}
}
return(createVector(xOption, yOption));
}
move() {
for (var i = 0; i < balls.length; i++) {
if(balls[i] != this) {
var d = dist(this.pos.x, this.pos.y, balls[i].pos.x, balls[i].pos.y);
if(d < this.radius + balls[i].radius) {
this.collision(balls[i]);
}
}
}
if(this.pos.x - this.radius < 0 || this.pos.x + this.radius > width) {
this.speed.x *= -1;
}
if(this.pos.y - this.radius < 0 || this.pos.y + this.radius > height) {
this.speed.y *= -1;
}
this.pos.x += this.speed.x;
this.pos.y += this.speed.y;
this.pos.x = constrain(this.pos.x,0,500)
this.pos.y = constrain(this.pos.y,0,500)
}
// takes ball object as input, sets speedvector to new x,y speed
collision(other) {
this.v1 = 0;
this.v2 = 0;
this.direction = p5.Vector.sub(other.pos, this.pos)
this.dist = this.direction.mag()
this.direction.normalize();
//this is 60 because that is the radius you give them times two
this.correction = 60-this.dist;
this.pos.sub(p5.Vector.mult(this.direction,this.correction/2))
other.pos.add(p5.Vector.mult(this.direction,this.correction/2))
this.v1 = this.direction.dot(this.speed)
this.v2 = this.direction.dot(other.speed)
this.direction.mult(this.v1-this.v2)
this.speed.sub(p5.Vector.mult(this.direction,1));
other.speed.add(p5.Vector.mult(this.direction,1))
}
show() {
fill(200, 100);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

how to use html canvas in between 2 <div>s

I tried to use canvas in middle of my html page.but it does't work fine. I want to use ribbon effect in div. it works fine when the there is no div and when there is div or other element it doesn't work. I want use canvas in between two div. I used ribbon pen in codepen and will post the code below. I want to how to use it in my html page.
var TWO_PI = Math.PI * 2;
var HALF_PI = Math.PI * 0.5;
var THICKNESS = 12;
var LENGTH = 10;
var STEP = 0.1;
var FPS = 1000 / 60;
function Particle(x, y, mass) {
this.x = x || 0;
this.y = y || 0;
this.ox = this.x;
this.oy = this.y;
this.mass = mass || 1.0;
this.massInv = 1.0 / this.mass;
this.fixed = false;
this.update = function (dt) {
if (!this.fixed) {
var fx = 0.0000;
var fy = 0.0000;
var tx = this.x,
ty = this.y;
this.x += (this.x - this.ox) + fx * this.massInv * dt * dt;
this.y += (this.y - this.oy) + fy * this.massInv * dt * dt;
this.ox = tx;
this.oy = ty;
}
};
};
function Spring(p1, p2, restLength, strength) {
this.p1 = p1;
this.p2 = p2;
this.restLength = restLength || 10;
this.strength = strength || 1.0;
this.update = function (dt) {
// Compute desired force
var dx = p2.x - p1.x,
dy = p2.y - p1.y,
dd = Math.sqrt(dx * dx + dy * dy) + 0.0001,
tf = (dd - this.restLength) / (dd * (p1.massInv + p2.massInv)) * this.strength,
f;
// Apply forces
if (!p1.fixed) {
f = tf * p1.massInv;
p1.x += dx * f;
p1.y += dy * f;
}
if (!p2.fixed) {
f = -tf * p2.massInv;
p2.x += dx * f;
p2.y += dy * f;
}
}
};
function Sim() {
this.particles = [];
this.springs = [];
this.tick = function (dt) {
var i, n;
for (i = 0, n = this.springs.length; i < n; ++i) {
this.springs[i].update(dt);
}
for (i = 0, n = this.particles.length; i < n; ++i) {
this.particles[i].update(dt);
}
}
};
// Create a new system
var sim = new Sim(),
old = new Date().getTime(),
canvas = document.getElementById('world'),
context = canvas.getContext('2d');
function init() {
var np,
op,
mouse,
anchor,
step = STEP,
length = LENGTH,
count = length / step;
var sx = canvas.width * 0.5;
var sy = canvas.height * 0.5;
for (var i = 0; i < count; ++i) {
//np = new Particle(i*8,i*8,0.1+Math.random()*0.01);
np = new Particle(sx + (Math.random() - 0.5) * 200, sy + (Math.random() - 0.5) * 200, 0.1 + Math.random() * 0.01);
sim.particles.push(np);
if (i > 0) {
s = new Spring(np, op, step, 0.95);
sim.springs.push(s);
}
op = np;
}
// Fix the first particle
anchor = sim.particles[0];
//anchor.fixed = true;
anchor.x = 50;
anchor.y = 50;
// Move last particle with mouse
mouse = sim.particles[count - 1];
mouse.fixed = true;
canvas.addEventListener('mousemove', function (event) {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
};
function step() {
var now = new Date().getTime(),
delta = now - old;
sim.tick(delta);
// Clear canvas
canvas.width = canvas.width;
var points = []; // Midpoints
var angles = []; // Delta angles
var i, n, p1, p2, dx, dy, mx, my, sin, cos, theta;
// Compute midpoints and angles
for (i = 0, n = sim.particles.length - 1; i < n; ++i) {
p1 = sim.particles[i];
p2 = sim.particles[i + 1];
dx = p2.x - p1.x;
dy = p2.y - p1.y;
mx = p1.x + dx * 0.5;
my = p1.y + dy * 0.5;
points[i] = {
x: mx,
y: my
};
angles[i] = Math.atan2(dy, dx);
}
// Render
context.beginPath();
for (i = 0, n = points.length; i < n; ++i) {
p1 = sim.particles[i];
p2 = points[i];
theta = angles[i];
r = Math.sin((i / n) * Math.PI) * THICKNESS;
sin = Math.sin(theta - HALF_PI) * r;
cos = Math.cos(theta - HALF_PI) * r;
context.quadraticCurveTo(
p1.x + cos,
p1.y + sin,
p2.x + cos,
p2.y + sin);
}
for (i = points.length - 1; i >= 0; --i) {
p1 = sim.particles[i + 1];
p2 = points[i];
theta = angles[i];
r = Math.sin((i / n) * Math.PI) * THICKNESS;
sin = Math.sin(theta + HALF_PI) * r;
cos = Math.cos(theta + HALF_PI) * r;
context.quadraticCurveTo(
p1.x + cos,
p1.y + sin,
p2.x + cos,
p2.y + sin);
}
context.strokeStyle = 'rgba(255,255,255,0.1)';
context.lineWidth = 8;
context.stroke();
context.strokeStyle = 'rgba(0,0,0,0.8)';
context.lineWidth = 0.5;
context.stroke();
context.fillStyle = 'rgba(255,255,255,0.9)';
context.fill();
old = now;
setTimeout(step, FPS);
};
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener("resize", resize);
resize();
init();
step();
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,600,700' rel='stylesheet' type='text/css'>
<canvas id='world' width='500' height='500'></canvas>
<header><h1>Wiggle your mouse...</h1></header>
Here is one way:
HTML:
<div class="top">
top
</div>
<div class="middle">
<canvas id='world' width='500' height='500'></canvas>
<header><h1>Wiggle your mouse...</h1></header>
</div>
<div class="bottom">
bottom
</div>
CSS:
div {
border: 1px solid black
}
.top, .bottom {
height: 200px
}
The js remains the same. The CSS gives the top and bottom divs some height. The canvas is in the middle div. Here is a jsfiddle: https://jsfiddle.net/av902pcs/

HTML5 Canvas Scallop Shape

I really need help badly on how to create a scallop shape using Canvas
I tried playing around the cloud sample but it was really difficult for me to create what I've wanted.
I simply wanted to know the code for the scallop shape for rectangle and circle.
This is the image that What I've wanted.
It design doesn't have to exactly the same but as possible it does look like this.
THANK YOU SO MUCH IN ADVANCEEE..
You can draw such a shape by using dotted line dash, like this(a bit tricky).
JavaScript:
const canvas = document.querySelector("#canvas");
canvas.width = canvas.height = 300;
const ctx = canvas.getContext("2d");
const rect = [50, 50, 200, 200];
//draw dotted line dash.
ctx.lineCap = "round";
ctx.setLineDash([0, 40]);
ctx.lineDashOffset = 20;
ctx.lineWidth = 42;
ctx.strokeStyle = "purple";
ctx.strokeRect(...rect);
//remove disuse range.
ctx.globalCompositeOperation = "destination-out";
ctx.lineWidth = 38;
ctx.strokeRect(...rect);
ctx.fillRect(...rect);
Demo:
http://jsdo.it/defghi1977/iFR7
From an older answer but the question was very vague and has a lot of extra baggage. Here is a snippet from that answer. It has some extra code in it that may be helpful but not directly related.
The function display (about halfway down) does most of the work, adding the arcs to the object box.
See running demo for instruction
const pointSize = 4;
const pointCol = "#4AF";
var arcDepth = -0.5; // depth of arc as a factor of line seg length
// Note to have arc go the other (positive) way you have
// to change the ctx.arc draw call by adding anticlockwise flag
// see drawArc for more
const arcCol = "#F92";
const arcWidth = 8;
// Find a circle that fits 3 points.
function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) {
var vx,
vy,
c,
c1,
u;
c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2
c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3
// This will not happen in this example
if (c === c1) { // if slope is the same they must be on the same line
return null; // points are in a line
}
// locate the center
if (p1y === p2y) { // special case with p1 and p2 have same y
vx = (p1x + p2x) / 2;
vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2));
} else
if (p2y === p3y) { // special case with p2 and p3 have same y
vx = (p2x + p3x) / 2;
vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2));
} else {
vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1);
vy = c * vx + u;
}
arc.x = vx;
arc.y = vy;
vx = p1x - vx;
vy = p1y - vy;
arc.rad = Math.sqrt(vx * vx + vy * vy);
return arc;
}
var points = [];
var arcs = [];
function addArc(p1, p2, depth) {
var arc = {
p1 : p1,
p2 : p2,
depth : depth,
rad : null, // radius
a1 : null, // angle from
a2 : null, // angle to
x : null,
y : null,
}
arcs.push(arc);
return arc;
}
function calcArc(arc, depth) {
var p = points[arc.p1]; // get points
var pp = points[arc.p2];
// change depth if needed
depth = arc.depth = depth !== undefined ? depth : arc.depth;
var vx = pp[0] - p[0]; // vector from p to pp
var vy = pp[1] - p[1];
var cx = (pp[0] + p[0]) / 2; // center point
var cy = (pp[1] + p[1]) / 2; // center point
var len = Math.sqrt(vx * vx + vy * vy); // get length
cx -= vy * depth; // find 3 point at 90 deg to line and dist depth
cy += vx * depth;
// To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above.
//var nx = vx / len; // normalise vector
//var ny = vy / len;
//cx -= ny * depth; // find 3 point at 90 deg to line and dist depth
//cy += nx * depth;
fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits
arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point
arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point
}
function addPoint(x, y) {
points.push([x, y]);
}
function drawPoint(x, y, size, col) {
ctx.fillStyle = col;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
function drawArcStart(width,col){
ctx.lineCap = "round";
ctx.strokeStyle = col;
ctx.lineJoin = "round";
ctx.lineWidth = width;
ctx.beginPath();
}
function drawArc(arc){
ctx.arc(arc.x,arc.y,arc.rad,arc.a1,arc.a2);
}
function drawArcDone(){
ctx.closePath();
ctx.stroke();
}
function findClosestPoint(x, y, dist) {
var index = -1;
for (var i = 0; i < points.length; i++) {
var p = points[i];
var vx = x - p[0];
var vy = y - p[1];
var d = Math.sqrt(vx * vx + vy * vy);
if (d < dist) {
dist = d;
index = i;
}
}
return index;
}
var dragging = false;
var drag = -1;
var dragX, dragY;
var recalcArcs = false;
var box;
//========================================================================
// New box code from here down
// creates the box when canvas is ready
var onResize = function(){
box = {
x : canvas.width * (1/8),
y : canvas.height * (1/8),
w : canvas.width * (6/8),
h : canvas.height * (6/8),
recalculate : true,
arcCount : 20, // number of arcs to try and fit. Does not mean that it will happen
}
}
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
if(mouse.w !== 0){
if(mouse.buttonRaw & 4){ // change arc depth
if(mouse.w < 0){
arcDepth *= 1/1.05;
}else{
arcDepth *= 1.05;
}
recalcArcs = true;
}else{ // change arc count
box.arcCount += Math.sign(mouse.w);
box.arcCount = Math.max(4,box.arcCount);
box.recalculate = true;
}
mouse.w = 0;
}
// drag out box;
if(mouse.buttonRaw & 1){
if(!dragging){
box.x = mouse.x;
box.y = mouse.y;
dragging = true;
}
box.w = mouse.x - box.x;
box.h = mouse.y - box.y;
box.recalculate = true;
if(box.w <0){
box.x = box.x + box.w;
box.w = - box.w;
}
if(box.h <0){
box.y = box.y + box.h;
box.h = - box.h;
}
}else{
dragging = false;
}
// stop error
if(box.w === 0 || box.h === 0){
box.recalculate = false;
}
// calculate box arcs
if(box.recalculate){
// reset arrays
points.length = 0;
arcs.length = 0;
// get perimeter length
var perimLen = (box.w + box.h)* 2;
// get estimated step size
var step = perimLen / box.arcCount;
// get inset size for width and hight
var wInStep = (box.w - (Math.floor(box.w/step)-1)*step) / 2;
var hInStep = (box.h - (Math.floor(box.h/step)-1)*step) / 2;
// fix if box to narrow
if(box.w < step){
wInStep = 0;
hInStep = 0;
step = box.h / (Math.floor(box.h/step));
}else if(box.h < step){
wInStep = 0;
hInStep = 0;
step = box.w / (Math.floor(box.w/step));
}
// Add points clock wise
var x = box.x + wInStep;
while(x < box.x + box.w){ // across top
addPoint(x,box.y);
x += step;
}
var y = box.y + hInStep;
while(y < box.y + box.h){ // down right side
addPoint(box.x + box.w,y);
y += step;
}
x = box.x + box.w - wInStep;
while(x > box.x){ // left along bottom
addPoint(x,box.y + box.h);
x -= step;
}
var y = box.y + box.h - hInStep;
while(y > box.y){ // up along left side
addPoint(box.x,y);
y -= step;
}
// calculate arcs.
for(var i =0; i <points.length; i++){
calcArc(addArc(i,(i + 1) % points.length,arcDepth));
}
box.recalculate = false;
}
// recalculate arcs if needed
for(var i = 0; i < arcs.length; i ++){
if(recalcArcs){
calcArc(arcs[i],arcDepth);
}
}
// draw arcs
drawArcStart(arcWidth,arcCol)
for(var i = 0; i < arcs.length; i ++){
drawArc(arcs[i]);
}
drawArcDone();
recalcArcs = false;
}
//===========================================================================================
// END OF ANSWER
// Boiler plate code from here down. Does mouse,canvas,resize and what not
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ;
(function () {
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas,
resizeCanvas,
setGlobals,
resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if (firstRun) {
onResize();
firstRun = false;
} else {
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
e.preventDefault();
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
function update(timer) { // Main update loop
if (ctx === undefined) {
return;
}
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function () {
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
}, 0);
})();
Left click drag to create a box<br>Mouse wheel to change arc count<br>Hold right button down and wheel to change arc depth.<br>
Use https://www.w3schools.com/tags/canvas_beziercurveto.asp "Bezier Curve Method" to make complicated shapes.
I suggest going on desmos and messing around with the bezier curve in order to understand the complications. I hope this helped :)
Edit: Bezier curves work like this:
ctx.bezierCurveTo(Control point x, control point y, 2nd control point x, 2nd control point y, finishing x, finishing y);

Drawing scalloped polygon between multiple points

I am trying to draw a scalloped path using SVG between multiple points like it is drawn for rectangle here but between multiple points. Expecting two or more two or more selected points to be connected by scalloped line.
But the problems I am facing are,
Scallops are not symmetric or random in sizes. - I solved this
After clicking multiple points scallops directions and up down. Like in below image.
I am completely ok even if the answer is given in html5 canvas context. I will make adjustments. I am missing some extra calculation but could not figure out what.
Please click multiple times in result page to see the scallops drawn currently
var strokeWidth = 3;
function distance(x1, y1, x2, y2) {
return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
function findNewPoint(x, y, angle, distance) {
var result = {};
result.x = Math.round(Math.cos(angle) * distance + x);
result.y = Math.round(Math.sin(angle) * distance + y);
return result;
}
function getAngle(x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
}
function scapolledLine(points, strokeWidth) {
var that = this;
var scallopSize = strokeWidth * 8;
var path = [],
newP = null;
path.push("M", points[0].x, points[0].y);
points.forEach(function(s, i) {
var stepW = scallopSize,
lsw = 0;
var e = points[i + 1];
if (!e) {
path.push('A', stepW / 2, stepW / 2, "0 0 1", s.x, s.y);
return;
}
var args = [s.x, s.y, e.x, e.y];
var dist = that.distance.apply(that, args);
if (dist === 0) return;
var angle = that.getAngle.apply(that, args);
newP = s;
// Number of possible scallops between current points
var n = dist / stepW,
crumb;
if (dist < (stepW * 2)) {
stepW = (dist - stepW) > (stepW * 0.38) ? (dist / 2) : dist;
} else {
n = (n - (n % 1));
crumb = dist - (n * stepW);
/*if((stepW - crumb) > (stepW * 0.7)) {
lsw = crumb;
} else {
stepW += (crumb / n);
}*/
stepW += (crumb / n);
}
// Recalculate possible scallops.
n = dist / stepW;
var aw = stepW / 2;
for (var i = 0; i < n; i++) {
newP = that.findNewPoint(newP.x, newP.y, angle, stepW);
if (i === (n - 1)) {
aw = (lsw > 0 ? lsw : stepW) / 2;
}
path.push('A', aw, aw, "0 0 1", newP.x, newP.y);
}
// scallopSize = stepW;
});
return path.join(' ');
// return path.join(' ') + (points.length > 3 ? 'z' : '');
}
var points = [];
var mouse = null;
var dblclick = null,
doneEnding = false;
window.test.setAttribute('stroke-width', strokeWidth);
function feed() {
if (dblclick && doneEnding) return;
if (!dblclick && (points.length > 0 && mouse)) {
var arr = points.slice(0);
arr.push(mouse);
var str = scapolledLine(arr, strokeWidth);
window.test.setAttribute('d', str);
} else if (dblclick) {
points.push(points[0]);
doneEnding = true;
var str = scapolledLine(points, strokeWidth);
window.test.setAttribute('d', str);
}
}
document.addEventListener('mousedown', function(event) {
points.push({
x: event.clientX,
y: event.clientY
});
feed();
});
document.addEventListener('dblclick', function(event) {
dblclick = true;
feed();
});
document.addEventListener('mousemove', function(event) {
if (points.length > 0) {
mouse = {
x: event.clientX,
y: event.clientY
}
feed();
}
});
body,
html {
height: 100%;
width: 100%;
margin: 0;
padding: 0
}
svg {
height: 100%;
width: 100%
}
<svg id="svgP">
<path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>
Finding circle to fit 3Points
This method uses a function that finds a circle that fits 3 points. Two of the points are the set of points you have. The 3rd point is taken perpendicular to the line between the points and moved out by a factor of the line seg length.
When the circle is found then the start and end angle from the circle center point is found to make the arc segment and that is all done. Just draw the arcs with ctx.arc(
I am not sure exactly what you want. I have it so the arcs all bend up, But it is easy to make the go around.
If you want them all the same size you have to separate the points be equal distance, which is very simple, but means its hard to fit a given area.
Demo
The demo lets you add and drag point. Mouse wheel changes arc depth.
The const at the top arcDepth determines how deep each arc is compared to line segment length. It is a fraction.
You can make it a constant in pixels see, calcArc for how to change.
Each arc has an independent depth so if you don't like the overlapping arcs reduce the depth for that arc (in the code of course).
Hope that helps.
const pointSize = 4;
const pointCol = "#4AF";
var arcDepth = -0.5; // depth of arc as a factor of line seg length
// Note to have arc go the other (positive) way you have
// to change the ctx.arc draw call by adding anticlockwise flag
// see drawArc for more
const arcCol = "#4FA";
const arcWidth = 3;
// Find a circle that fits 3 points.
function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) {
var vx,
vy,
c,
c1,
u;
c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2
c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3
// This will not happen in this example
if (c === c1) { // if slope is the same they must be on the same line
return null; // points are in a line
}
// locate the center
if (p1y === p2y) { // special case with p1 and p2 have same y
vx = (p1x + p2x) / 2;
vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2));
} else
if (p2y === p3y) { // special case with p2 and p3 have same y
vx = (p2x + p3x) / 2;
vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2));
} else {
vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1);
vy = c * vx + u;
}
arc.x = vx;
arc.y = vy;
vx = p1x - vx;
vy = p1y - vy;
arc.rad = Math.sqrt(vx * vx + vy * vy);
return arc;
}
var points = [];
var arcs = [];
function addArc(p1, p2, depth) {
// remove next 5 line if you dont want all arcs to face the same way.
if(points[p1][0] > points[p2][0]){
var temp = p1;
p1 = p2;
p2 = temp;
}
var arc = {
p1 : p1,
p2 : p2,
depth : depth,
rad : null, // radius
a1 : null, // angle from
a2 : null, // angle to
x : null,
y : null,
}
arcs.push(arc);
return arc;
}
function calcArc(arc, depth) {
var p = points[arc.p1]; // get points
var pp = points[arc.p2];
// change depth if needed
depth = arc.depth = depth !== undefined ? depth : arc.depth;
var vx = pp[0] - p[0]; // vector from p to pp
var vy = pp[1] - p[1];
var cx = (pp[0] + p[0]) / 2; // center point
var cy = (pp[1] + p[1]) / 2; // center point
var len = Math.sqrt(vx * vx + vy * vy); // get length
cx -= vy * depth; // find 3 point at 90 deg to line and dist depth
cy += vx * depth;
// To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above.
//var nx = vx / len; // normalise vector
//var ny = vy / len;
//cx -= ny * depth; // find 3 point at 90 deg to line and dist depth
//cy += nx * depth;
fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits
arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point
arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point
}
function addPoint(x, y) {
points.push([x, y]);
}
function drawPoint(x, y, size, col) {
ctx.fillStyle = col;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
function drawArc(arc, width, col) {
ctx.lineCap = "round";
ctx.strokeStyle = col;
ctx.lineWidth = width;
ctx.beginPath();
ctx.arc(arc.x, arc.y, arc.rad, arc.a1, arc.a2,false); // true for anti clock wise
ctx.stroke();
}
function findClosestPoint(x, y, dist) {
var index = -1;
for (var i = 0; i < points.length; i++) {
var p = points[i];
var vx = x - p[0];
var vy = y - p[1];
var d = Math.sqrt(vx * vx + vy * vy);
if (d < dist) {
dist = d;
index = i;
}
}
return index;
}
var dragging = false;
var drag = -1;
var dragX, dragY;
var recalcArcs = false;
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
if(mouse.w > 0){
arcDepth *= 1.05;
mouse.w = 0;
recalcArcs = true;
}
if(mouse.w < 0){
arcDepth *= 1/1.05;
mouse.w = 0;
recalcArcs = true;
}
if (mouse.buttonRaw & 1) {
if (!dragging) {
var i = findClosestPoint(mouse.x, mouse.y, pointSize * 3);
if (i > -1) {
drag = i;
dragging = true;
dragX = mouse.x - points[drag][0];
dragY = mouse.y - points[drag][1];
}
}
if (dragging) {
points[drag][0] = mouse.x - dragX
points[drag][1] = mouse.y - dragY
recalcArcs = true;
} else {
addPoint(mouse.x, mouse.y);
if (points.length > 1) {
calcArc(addArc(points.length - 2, points.length - 1, arcDepth));
}
mouse.buttonRaw = 0;
}
} else {
if (dragging) {
dragging = false;
drag = -1;
recalcArcs = true;
}
var i = findClosestPoint(mouse.x, mouse.y, pointSize * 3);
if (i > -1) {
canvas.style.cursor = "move";
} else {
canvas.style.cursor = "default";
}
}
for (var i = 0; i < arcs.length; i++) {
if (recalcArcs) {
calcArc(arcs[i],arcDepth);
}
drawArc(arcs[i], arcWidth, arcCol);
}
recalcArcs = false;
for (var i = 0; i < points.length; i++) {
var p = points[i];
drawPoint(p[0], p[1], pointSize, pointCol);
}
}
//===========================================================================================
// END OF ANSWER
// Boiler plate code from here down. Does mouse,canvas,resize and what not
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ;
(function () {
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas,
resizeCanvas,
setGlobals,
resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if (firstRun) {
onResize();
firstRun = false;
} else {
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
e.preventDefault();
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
function update(timer) { // Main update loop
if (ctx === undefined) {
return;
}
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function () {
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
}, 0);
})();
Left click to add point. Left click drag to move points.<br>
Mouse wheel changes arc depth.
Take two...
Maybe this is what you are after.. Sorry its a bit of a mess as I am short on time at the moment.
Same code as before just add the points to the outside of the box, making sure that the width and height steps are equally spaced from the edge.
const pointSize = 4;
const pointCol = "#4AF";
var arcDepth = -0.5; // depth of arc as a factor of line seg length
// Note to have arc go the other (positive) way you have
// to change the ctx.arc draw call by adding anticlockwise flag
// see drawArc for more
const arcCol = "#F92";
const arcWidth = 8;
// Find a circle that fits 3 points.
function fitCircleTo3P(p1x, p1y, p2x, p2y, p3x, p3y, arc) {
var vx,
vy,
c,
c1,
u;
c = (p2x - p1x) / (p1y - p2y); // slope of vector from vec 1 to vec 2
c1 = (p3x - p2x) / (p2y - p3y); // slope of vector from vec 2 to vec 3
// This will not happen in this example
if (c === c1) { // if slope is the same they must be on the same line
return null; // points are in a line
}
// locate the center
if (p1y === p2y) { // special case with p1 and p2 have same y
vx = (p1x + p2x) / 2;
vy = c1 * vx + (((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2));
} else
if (p2y === p3y) { // special case with p2 and p3 have same y
vx = (p2x + p3x) / 2;
vy = c * vx + (((p1y + p2y) / 2) - c * ((p1x + p2x) / 2));
} else {
vx = ((((p2y + p3y) / 2) - c1 * ((p2x + p3x) / 2)) - (u = ((p1y + p2y) / 2) - c * ((p1x + p2x) / 2))) / (c - c1);
vy = c * vx + u;
}
arc.x = vx;
arc.y = vy;
vx = p1x - vx;
vy = p1y - vy;
arc.rad = Math.sqrt(vx * vx + vy * vy);
return arc;
}
var points = [];
var arcs = [];
function addArc(p1, p2, depth) {
var arc = {
p1 : p1,
p2 : p2,
depth : depth,
rad : null, // radius
a1 : null, // angle from
a2 : null, // angle to
x : null,
y : null,
}
arcs.push(arc);
return arc;
}
function calcArc(arc, depth) {
var p = points[arc.p1]; // get points
var pp = points[arc.p2];
// change depth if needed
depth = arc.depth = depth !== undefined ? depth : arc.depth;
var vx = pp[0] - p[0]; // vector from p to pp
var vy = pp[1] - p[1];
var cx = (pp[0] + p[0]) / 2; // center point
var cy = (pp[1] + p[1]) / 2; // center point
var len = Math.sqrt(vx * vx + vy * vy); // get length
cx -= vy * depth; // find 3 point at 90 deg to line and dist depth
cy += vx * depth;
// To have depth as a fixed length uncomment 4 lines below and comment out 2 lines above.
//var nx = vx / len; // normalise vector
//var ny = vy / len;
//cx -= ny * depth; // find 3 point at 90 deg to line and dist depth
//cy += nx * depth;
fitCircleTo3P(p[0], p[1], cx, cy, pp[0], pp[1], arc); // get the circle that fits
arc.a1 = Math.atan2(p[1] - arc.y, p[0] - arc.x); // get angle from circle center to first point
arc.a2 = Math.atan2(pp[1] - arc.y, pp[0] - arc.x); // get angle from circle center to second point
}
function addPoint(x, y) {
points.push([x, y]);
}
function drawPoint(x, y, size, col) {
ctx.fillStyle = col;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
}
function drawArcStart(width,col){
ctx.lineCap = "round";
ctx.strokeStyle = col;
ctx.lineJoin = "round";
ctx.lineWidth = width;
ctx.beginPath();
}
function drawArc(arc){
ctx.arc(arc.x,arc.y,arc.rad,arc.a1,arc.a2);
}
function drawArcDone(){
ctx.closePath();
ctx.stroke();
}
function findClosestPoint(x, y, dist) {
var index = -1;
for (var i = 0; i < points.length; i++) {
var p = points[i];
var vx = x - p[0];
var vy = y - p[1];
var d = Math.sqrt(vx * vx + vy * vy);
if (d < dist) {
dist = d;
index = i;
}
}
return index;
}
var dragging = false;
var drag = -1;
var dragX, dragY;
var recalcArcs = false;
var box;
//========================================================================
// New box code from her down
// creates the box when canvas is ready
var onResize = function(){
box = {
x : canvas.width * (1/8),
y : canvas.height * (1/8),
w : canvas.width * (6/8),
h : canvas.height * (6/8),
recalculate : true,
arcCount : 20, // number of arcs to try and fit. Does not mean that it will happen
}
}
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
if(mouse.w !== 0){
if(mouse.buttonRaw & 4){ // change arc depth
if(mouse.w < 0){
arcDepth *= 1/1.05;
}else{
arcDepth *= 1.05;
}
recalcArcs = true;
}else{ // change arc count
box.arcCount += Math.sign(mouse.w);
box.arcCount = Math.max(4,box.arcCount);
box.recalculate = true;
}
mouse.w = 0;
}
// drag out box;
if(mouse.buttonRaw & 1){
if(!dragging){
box.x = mouse.x;
box.y = mouse.y;
dragging = true;
}
box.w = mouse.x - box.x;
box.h = mouse.y - box.y;
box.recalculate = true;
if(box.w <0){
box.x = box.x + box.w;
box.w = - box.w;
}
if(box.h <0){
box.y = box.y + box.h;
box.h = - box.h;
}
}else{
dragging = false;
}
// stop error
if(box.w === 0 || box.h === 0){
box.recaculate = false;
}
// caculate box arcs
if(box.recalculate){
// reset arrays
points.length = 0;
arcs.length = 0;
// get perimiter length
var perimLen = (box.w + box.h)* 2;
// get estimated step size
var step = perimLen / box.arcCount;
// get inset size for width and hight
var wInStep = (box.w - (Math.floor(box.w/step)-1)*step) / 2;
var hInStep = (box.h - (Math.floor(box.h/step)-1)*step) / 2;
// fix if box to narrow
if(box.w < step){
wInStep = 0;
hInStep = 0;
step = box.h / (Math.floor(box.h/step));
}else if(box.h < step){
wInStep = 0;
hInStep = 0;
step = box.w / (Math.floor(box.w/step));
}
// Add points clock wise
var x = box.x + wInStep;
while(x < box.x + box.w){ // across top
addPoint(x,box.y);
x += step;
}
var y = box.y + hInStep;
while(y < box.y + box.h){ // down right side
addPoint(box.x + box.w,y);
y += step;
}
x = box.x + box.w - wInStep;
while(x > box.x){ // left along bottom
addPoint(x,box.y + box.h);
x -= step;
}
var y = box.y + box.h - hInStep;
while(y > box.y){ // up along left side
addPoint(box.x,y);
y -= step;
}
// caculate arcs.
for(var i =0; i <points.length; i++){
calcArc(addArc(i,(i + 1) % points.length,arcDepth));
}
box.recalculate = false;
}
// recaculate arcs if needed
for(var i = 0; i < arcs.length; i ++){
if(recalcArcs){
calcArc(arcs[i],arcDepth);
}
}
// draw arcs
drawArcStart(arcWidth,arcCol)
for(var i = 0; i < arcs.length; i ++){
drawArc(arcs[i]);
}
drawArcDone();
recalcArcs = false;
}
//===========================================================================================
// END OF ANSWER
// Boiler plate code from here down. Does mouse,canvas,resize and what not
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true; ;
(function () {
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas,
resizeCanvas,
setGlobals,
resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if (firstRun) {
onResize();
firstRun = false;
} else {
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
e.preventDefault();
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
function update(timer) { // Main update loop
if (ctx === undefined) {
return;
}
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function () {
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
}, 0);
})();
Left click drag to create a box<br>Mouse wheel to change arc count<br>Hold right button down and wheel to change arc depth.<br>
The following code snippet determines the direction (CW, CCW) of each segment by analyzing the adjacent segments. For segment A, if both adjacent segments are on the same side of A (or if A has only one adjacent segment), there is no ambiguity and the scallops of segment A are on the outside of the convex shape formed by these segments. However, if the adjacent segments are on opposite sides of A, in a zigzag pattern, the adjacent segment that extends the farthest from segment A is chosen to set the direction of segment A.
function distance(x1, y1, x2, y2) {
return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
function findNewPoint(x, y, angle, distance) {
var result = {};
result.x = Math.round(Math.cos(angle) * distance + x);
result.y = Math.round(Math.sin(angle) * distance + y);
return result;
}
function getAngle(x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
}
function getSeparationFromLine(lineOrigin, lineAngle, pt) {
x = pt.x - lineOrigin.x;
y = pt.y - lineOrigin.y;
return -x * Math.sin(lineAngle) + y * Math.cos(lineAngle);
}
function getDirection(pts, index, closed) {
var last = pts.length - 1;
var start = index;
var end = (closed && start == last) ? 0 : index + 1;
var prev = (closed && start == 0) ? last : start - 1;
var next = (closed && end == last) ? 0 : end + 1;
var isValidSegment = 0 <= start && start <= last && 0 <= end && end <= last && end !== start;
if (!isValidSegment) {
return 1;
}
var pt1 = pts[start];
var pt2 = pts[end];
var pt, x, y;
var ccw = 0.0;
var theta = Math.atan2(pt2.y - pt1.y, pt2.x - pt1.x);
if (0 <= prev && prev <= last) {
ccw += getSeparationFromLine(pt1, theta, pts[prev]);
}
if (0 <= next && next <= last) {
ccw += getSeparationFromLine(pt1, theta, pts[next]);
}
return ccw > 0 ? "1" : "0";
}
function scapolledLine(pts, closed, strokeWidth) {
var that = this;
var scallopSize = strokeWidth * 8;
var lastIndex = pts.length - 1;
var path = [], newP = null;
path.push("M", pts[0].x, pts[0].y);
pts.forEach(function (s, currentIndex) {
var stepW = scallopSize, lsw = 0;
var isClosingSegment = closed && currentIndex == lastIndex;
var nextIndex = isClosingSegment ? 0 : currentIndex + 1;
var e = pts[nextIndex];
if (!e) {
return;
}
var direction = getDirection(pts, currentIndex, closed);
var args = [s.x, s.y, e.x, e.y];
var dist = that.distance.apply(that, args);
if (dist === 0) {
return;
}
var angle = that.getAngle.apply(that, args);
newP = s;
// Number of possible scallops between current pts
var n = dist / stepW, crumb;
if (dist < (stepW * 2)) {
stepW = (dist - stepW) > (stepW * 0.38) ? (dist / 2) : dist;
} else {
n = (n - (n % 1));
crumb = dist - (n * stepW);
stepW += (crumb / n);
}
// Recalculate possible scallops.
n = dist / stepW;
var aw = stepW / 2;
for (var i = 0; i < n; i++) {
newP = that.findNewPoint(newP.x, newP.y, angle, stepW);
if (i === (n - 1)) {
aw = (lsw > 0 ? lsw : stepW) / 2;
}
path.push('A', aw, aw, "0 0 " + direction, newP.x, newP.y);
}
if (isClosingSegment) {
path.push('A', stepW / 2, stepW / 2, "0 0 " + direction, e.x, e.y);
}
});
return path.join(' ');
}
var strokeWidth = 3;
var points = [];
var mouse = null;
var isClosed = false;
window.test.setAttribute('stroke-width', strokeWidth);
function feed(isDoubleClick) {
if (isClosed) {
return;
}
if (!isDoubleClick && (points.length > 0 && mouse)) {
var arr = points.slice(0);
arr.push(mouse);
var str = scapolledLine(arr, isClosed, strokeWidth);
window.test.setAttribute('d', str);
} else if (isDoubleClick) {
isClosed = true;
points.pop();
var str = scapolledLine(points, isClosed, strokeWidth);
window.test.setAttribute('d', str);
}
}
document.addEventListener('mousedown', function (event) {
points.push({
x: event.clientX,
y: event.clientY
});
feed(false);
});
document.addEventListener('dblclick', function (event) {
feed(true);
});
document.addEventListener('mousemove', function (event) {
if (points.length > 0) {
mouse = {
x: event.clientX,
y: event.clientY
}
feed(false);
}
});
body,
html {
height: 100%;
width: 100%;
margin: 0;
padding: 0
}
svg {
height: 100%;
width: 100%
}
<svg id="svgP">
<path id="test" style="stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>

Categories

Resources