draw arc between 2 lines & draw a curve 2 points - javascript

Framework: fabricjs
My first problem is to draw a angle betweens 2 Lines. My code is working but i'm not happy with the result.
My second problem is to draw a curve between 2 Points.
My Code for the first problem.
I have 3 Points:
A , B , C
2 Lines:
AB , BC
With this information i calculate distance points with distance 10.
let angle = this.calcAngle(A,B,C);
let distanceAB = this.calcCornerPoint(A, B, 10);
let distanceBC = this.calcCornerPoint(C, B, 10);
Calc Angle:
calcAngle(A, B, C, final, finalAddon = "°") {
var nx1 = A.x - B.x;
var ny1 = A.y - B.y;
var nx2 = C.x - B.x;
var ny2 = C.y - B.y;
this.lineAngle1 = Math.atan2(ny1, nx1);
this.lineAngle2 = Math.atan2(ny2, nx2);
if (nx1 * ny2 - ny1 * nx2 < 0) {
const t = lineAngle2;
this.lineAngle2 = this.lineAngle1;
this.lineAngle1 = t;
}
// if angle 1 is behind then move ahead
if (lineAngle1 < lineAngle2) {
this.lineAngle1 += Math.PI * 2;
}
}
Than draw a Path with:
this.drawAngleTrapez(distanceAB, distanceBC, B);
drawAngleTrapez(AB, BC, B) {
let points = [AB, BC, B];
let path = "";
if (this.trapezObjs[this.iterator]) {
this.canvas.remove(this.trapezObjs[this.iterator]);
}
path += "M " + Math.round(points[0].x) + " " + Math.round(points[0].y) + "";
for (let i = 1; i < points.length; i++) {
path += " L " + Math.round(points[i].x) + " " + Math.round(points[i].y) + "";
}
this.currentTrapez = this.trapezObjs[this.iterator] = new fabric.Path(path, {
selectable: false,
hasControls: false,
hasBorders: false,
hoverCursor: 'default',
fill: '#ccc',
strokeWidth: this.strokeWidth,
});
this.canvas.add(this.trapezObjs[this.iterator]);
}
And than i draw a Circle:
drawAnglePoint(B,d = 10) {
this.currentCorner = new fabric.Circle({
left: B.x,
top: B.y,
startAngle: this.lineAngle1,
endAngle: this.lineAngle2,
radius: 10,
fill: '#ccc',
selectable: false,
hasControls: false,
hasBorders: false,
hoverCursor: 'default',
});
this.canvas.add(this.currentCorner);
}
But the result ist not beautiful:
And the blue point is not on the end of the line, mabye here also a little fix.
this.startPoint.set({ left: C.x, top: C.y });
Second Problem solved: was an error in my calculation.
The problem is , its not a beautiful curve:

Rather than draw the central 'wedge' as 2 shapes - a triangle and a portion of a circle, you should instead draw it as the 2-sided figure that it is.
Circles are drawn by supplying the origin. Therefore, to draw the blue point on the end of the line, you should specify the same coordinates for the end-point as you do for the circle-centre.
The below code will re-create your first image with the exception of the text.
While I've drawn the swept-angle indicator transparently, ontop of the lines, you'd probably want to change the draw order, colour and opacity.
(I used 0x88/0xFF = 136/255 = 53.3%),
"use strict";
function newEl(tag){return document.createElement(tag)}
function byId(id){return document.getElementById(id)}
class vec2
{
constructor(x,y){this.x = x;this.y = y;}
get x(){ return this._x; }
set x(newVal){ this._x = newVal; }
get y(){ return this._y; }
set y(newVal){ this._y = newVal; }
get length(){return Math.hypot(this.x,this.y);}
set length(len){var invLen = len/this.length; this.timesEquals(invLen);}
add(other){return new vec2(this.x+other.x, this.y+other.y);}
sub(other){return new vec2(this.x-other.x, this.y-other.y);}
plusEquals(other){this.x+=other.x;this.y+=other.y;return this;}
minusEquals(other){this.x-=other.x;this.y-=other.y;return this;}
timesEquals(scalar){this.x*=scalar;this.y*=scalar;return this;}
divByEquals(scalar){this.x/=scalar;this.y/=scalar;return this;}
setTo(other){this.x=other.x;this.y=other.y;}
toString(){return `vec2 {x: ${this.x}, y: ${this.y}}` }
toStringN(n){ return `vec2 {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}` }
dotProd(other){return this.x*other.x + this.y*other.y;}
timesEquals(scalar){this.x *= scalar;this.y *= scalar;return this;}
normalize(){let len = this.length;this.x /= len;this.y /= len;return this;}
static clone(other){let result = new vec2(other.x, other.y);return result;}
clone(){return vec2.clone(this);}
};
window.addEventListener('load', onWindowLoaded, false);
function onWindowLoaded(evt)
{
var can = byId('output');
let A = new vec2(172,602), B = new vec2(734,602), C = new vec2(847,194);
myTest(can, [A,B,C]);
}
function circle(canvas, x, y, radius)
{
let ctx = canvas.getContext('2d');
ctx.moveTo(x,y);
ctx.beginPath();
ctx.ellipse(x,y, radius,radius, 0, 0, 2*Math.PI);
ctx.closePath();
ctx.fill();
}
function getAngle(origin, pt)
{
let delta = pt.sub(origin);
let angle = Math.atan2( delta.y, delta.x );
return angle;
}
function myTest(canvas, points)
{
let ctx = canvas.getContext('2d');
// background colour
//
ctx.fillStyle = '#ebedf0';
ctx.fillRect(0,0,canvas.width,canvas.height);
// white square grid
//
//60,33 = intersection of first
//115 = square-size
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 12;
for (let x=60; x<canvas.width; x+=112)
{
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
ctx.closePath();
}
for (let y=33; y<canvas.height; y+=112)
{
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
ctx.closePath();
}
// wedge indicating swept angle
let angle1 = getAngle(points[1], points[2]);
let angle2 = getAngle(points[1], points[0]);
ctx.beginPath();
ctx.moveTo(points[1].x,points[1].y);
ctx.arc(points[1].x,points[1].y, 70, angle1,angle2, true);
ctx.fillStyle = '#cccccc88';
ctx.fill();
console.log(angle1, angle2);
// lines
//
ctx.lineWidth = 9;
ctx.strokeStyle = '#c3874c';
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
ctx.lineTo(points[2].x, points[2].y);
ctx.stroke();
ctx.closePath();
// points
//
ctx.fillStyle = '#3b89c9';
ctx.beginPath();
points.forEach( function(pt){circle(canvas, pt.x,pt.y, 10);} );
ctx.closePath();
}
canvas
{
zoom: 67%;
}
<canvas id='output' width='996' height='730'></canvas>

Related

How do I draw lines in a grid?

I need advice on how to make an event and draw mouse lines in the grid. Somehow I can't do it so I can draw straight lines in a grid. But I still have a grid, I don't know how to fix a drawing error. I am adding my Javascript code below. My mouse drawing event doesn't work, it doesn't draw me at all.
JS code for grid:
;(function () {
let canvas, ctx, mouse
class Mouse {
constructor (ctx, x = 0, y = 0) {
this.x = x
this.y = y
this.ctx = ctx
}
set pos (evt) {
const canvasDimensions = canvas.getBoundingClientRect()
this.x = Math.floor(evt.clientX - canvasDimensions.left)
this.y = Math.floor(evt.clientY - canvasDimensions.top)
const { x, y, ctx } = this
const txt = `X: ${x}, Y: ${y}`
ctx.font = '16px Poppins'
const offsetX = x < canvas.width / 2 ? 15 : -ctx.measureText(txt).width - 15
const offsetY = y < canvas.height / 2 ? 20 : -13
ctx.fillText(txt, this.x + offsetX, this.y + offsetY)
}
}
class Line {
constructor (color, lineWidth, startX, startY, endX, endY) {
this.color = color
this.lineWidth = lineWidth
this.startX = startX
this.startY = startY
this.endX = endX
this.endY = endY
}
draw (ctx) {
const { color, lineWidth, startX, startY, endX, endY } = this
ctx.beginPath()
ctx.strokeStyle = color
ctx.lineWidth = lineWidth
ctx.moveTo(startX, startY)
ctx.lineTo(endX, endY)
ctx.stroke()
}
}
class Grid {
constructor (
color = 'black', lineWidth = 0.25, step = 25,
boldNth = 5, boldColor = 'black', boldWidth = 0.25
) {
this.color = color
this.lineWidth = lineWidth
this.step = step
this.boldNth = boldNth
this.boldColor = boldColor
this.boldWidth = boldWidth
this.lines = null
}
createLines () {
const {
color, lineWidth, step,
boldNth, boldColor, boldWidth
} = this
const lines = []
const div = boldNth * step
for (let x = 0; x < canvas.width; x += step) {
const isNth = x % div === 0
lines.push(
isNth
? new Line(boldColor, boldWidth, x, 0, x, canvas.height)
: new Line(color, lineWidth, x, 0, x, canvas.height)
)
}
for (let y = 0; y < canvas.height; y += step) {
const isNth = y % div === 0
lines.push(
isNth
? new Line(boldColor, boldWidth, 0, y, canvas.width, y)
: new Line(color, lineWidth, 0, y, canvas.width, y)
)
}
this.lines = lines
}
drawText (ctx) {
const { step, boldNth, boldColor } = this
ctx.font = '16px Poppins'
ctx.fillStyle = boldColor
ctx.fillText('0', 1, 15)
for (let x = step * boldNth; x < canvas.width; x += step * boldNth) {
ctx.fillText(x, x, 15)
}
for (let y = step * boldNth; y < canvas.height; y += step * boldNth) {
ctx.fillText(y, 0, y + 15)
}
}
draw (ctx) {
if (!this.lines) this.createLines()
this.lines.forEach(line => line.draw(ctx))
this.drawText(ctx)
}
}
function init () {
canvas = document.getElementById('Canvas')
ctx = canvas.getContext('2d')
mouse = new Mouse(ctx)
const grid = new Grid('black', 0.25, 50, 1)
grid.draw(ctx)
canvas.addEventListener('mousemove', (evt) => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
grid.draw(ctx)
mouse.pos = evt
})
canvas.addEventListener('mouse:down', function(o){
isDown = true;
var pointer = canvas.getPointer(o.e);
var points = [ pointer.x, pointer.y, pointer.x, pointer.y ];
line = new fabric.Line(points, {
strokeWidth: 5,
fill: 'red',
stroke: 'red',
originX: 'center',
originY: 'center'
});
});
canvas.addEventListener('mouse:move', function(o){
if (!isDown) return;
var pointer = canvas.getPointer(o.e);
line.set({ x2: pointer.x, y2: pointer.y });
canvas.renderAll();
grid.draw(ctx)
});
canvas.on('mouse:up', function(o){
isDown = false;
grid.draw(ctx)
});
}
document.addEventListener('DOMContentLoaded', init)
})()
you can use this references codereview.stackexchange.com/questions/114702/… my it can help you – Tommy Ferdian Hadimarta 54 secs ago

Determine if a Selection Marquee is over a Rotated Rectangle

I have a Rectangle class for drawing to HTML Canvas. It has a rotation property that gets applied in its draw method. If the user drags within the canvas, a selection marquee is being drawn. How can I set the Rectangle's active attribute to true when the Rectangle is within the selection marquee using math? This is a problem I'm having in another language & context so I do not have all of Canvas' methods available to me there (e.g. isPointInPath).
I found a StackOverflow post about finding Mouse position within rotated rectangle in HTML5 Canvas, which I am implementing in the Rectangle method checkHit. It doesn't account for the selection marquee, however. It's just looking at the mouse X & Y, which is still off. The light blue dot is the origin around which the rectangle is being rotated. Please let me know if anything is unclear. Thank you.
class Rectangle
{
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.xOffset = this.x + this.width/2;
this.yOffset = this.y + ((this.y+this.height)/2);
this.rotation = rotation;
this.active = false;
}
checkHit()
{
// translate mouse point values to origin
let originX = this.xOffset;
let originY = this.yOffset;
let dx = marquee[2] - originX;
let dy = marquee[3] - originY;
// distance between the point and the center of the rectangle
let h1 = Math.sqrt(dx*dx + dy*dy);
let currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
let newA = currA - this.rotation;
// New position of mouse point when rotated
let x2 = Math.cos(newA) * h1;
let y2 = Math.sin(newA) * h1;
// Check relative to center of rectangle
if (x2 > -0.5 * this.width && x2 < 0.5 * this.width && y2 > -0.5 * this.height && y2 < 0.5 * this.height){
this.active = true;
} else {
this.active = false;
}
}
draw()
{
ctx.save();
ctx.translate(this.xOffset, this.yOffset);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation * Math.PI / 180);
ctx.translate(-this.xOffset, -this.yOffset);
if (this.active)
{
ctx.fillStyle = 'rgba(255,0,0,0.5)';
} else {
ctx.fillStyle = 'rgba(0,0,255,0.5)';
}
ctx.beginPath();
ctx.fillRect(this.x, this.y, this.width, this.y+this.height);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var rect = new Rectangle(50,50,90,30,45);
var marquee = [-3,-3,-3,-3];
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var start_x,start_y;
let draw = () => {
ctx.clearRect(0,0, canvas.width, canvas.height);
//rect.rotation+=1;
rect.draw();
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.fillRect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.strokeStyle = "white"
ctx.lineWidth = 1;
ctx.rect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.stroke()
raf = window.requestAnimationFrame(draw);
}
let dragStart = (e) =>
{
start_x = parseInt(e.clientX-offsetX);
start_y = parseInt(e.clientY-offsetY);
marquee = [start_x,start_y,0,0];
canvas.addEventListener("mousemove", drag);
}
let drag = (e) =>
{
let mouseX = parseInt(e.clientX-offsetX);
let mouseY = parseInt(e.clientY-offsetY);
marquee[2] = mouseX - start_x;
marquee[3] = mouseY - start_y;
rect.checkHit();
}
let dragEnd = (e) =>
{
marquee = [-10,-10,-10,-10];
canvas.removeEventListener("mousemove", drag);
}
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
raf = window.requestAnimationFrame(draw);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
Do convex polygons overlap
Rectangles are convex polygons.
Rectangle and marquee each have 4 points (corners) that define 4 edges (lines segments) connecting the points.
This solution works for all convex irregular polygons with 3 or more sides.
Points and edges must be sequential either Clockwise CW of Count Clockwise CCW
Test points
If any point of one poly is inside the other polygon then they must overlap. See example function isInside
To check if point is inside a polygon, get cross product of, edge start to the point as vector, and the edge as a vector.
If all cross products are >= 0 (to the left of) then there is overlap (for CW polygon). If polygon is CCW then if all cross products are <= 0 (to the right of) there is overlap.
It is possible to overlap without any points inside the other poly.
Test Edges
If any of the edges from one poly crosses any of the edges from the other then there must be overlap. The function doLinesIntercept returns true if two line segments intercept.
Complete test
Function isPolyOver(poly1, poly2) will return true if there is overlap of the two polys.
A polygon is defined by a set of Point's and Lines's connecting the points.
The polygon can be irregular, meaning that each edge can be any length > 0
Do not pass polygons with an edge of length === 0 or will not work.
Added
I added the function Rectangle.toPoints that transforms the rectangle and returning a set of 4 points (corners).
Example
Example is a copy of your code working using the above methods.
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
requestAnimationFrame(draw);
const Point = (x = 0, y = 0) => ({x, y, set(x,y){ this.x = x; this.y = y }});
const Line = (p1, p2) => ({p1, p2});
const selector = { points: [Point(), Point(), Point(), Point()] }
selector.lines = [
Line(selector.points[0], selector.points[1]),
Line(selector.points[1], selector.points[2]),
Line(selector.points[2], selector.points[3]),
Line(selector.points[3], selector.points[0])
];
const rectangle = { points: [Point(), Point(), Point(), Point()] }
rectangle.lines = [
Line(rectangle.points[0], rectangle.points[1]),
Line(rectangle.points[1], rectangle.points[2]),
Line(rectangle.points[2], rectangle.points[3]),
Line(rectangle.points[3], rectangle.points[0])
];
function isInside(point, points) {
var i = 0, p1 = points[points.length - 1];
while (i < points.length) {
const p2 = points[i++];
if ((p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x) < 0) { return false }
p1 = p2;
}
return true;
}
function doLinesIntercept(l1, l2) {
const v1x = l1.p2.x - l1.p1.x;
const v1y = l1.p2.y - l1.p1.y;
const v2x = l2.p2.x - l2.p1.x;
const v2y = l2.p2.y - l2.p1.y;
const c = v1x * v2y - v1y * v2x;
if(c !== 0){
const u = (v2x * (l1.p1.y - l2.p1.y) - v2y * (l1.p1.x - l2.p1.x)) / c;
if(u >= 0 && u <= 1){
const u = (v1x * (l1.p1.y - l2.p1.y) - v1y * (l1.p1.x - l2.p1.x)) / c;
return u >= 0 && u <= 1;
}
}
return false;
}
function isPolyOver(p1, p2) { // is poly p2 under any part of poly p1
if (p2.points.some(p => isInside(p, p1.points))) { return true };
if (p1.points.some(p => isInside(p, p2.points))) { return true };
return p1.lines.some(l1 => p2.lines.some(l2 => doLinesIntercept(l1, l2)));
}
const ctx = canvas.getContext("2d");
var dragging = false;
const marquee = [0,0,0,0];
const rotate = 0.01;
var startX, startY, hasSize = false;
const BB = canvas.getBoundingClientRect();
const offsetX = BB.left;
const offsetY = BB.top;
class Rectangle {
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.rotation = rotation;
this.active = false;
}
toPoints(points = [Point(), Point(), Point(), Point()]) {
const xAx = Math.cos(this.rotation) / 2;
const xAy = Math.sin(this.rotation) / 2;
const x = this.x, y = this.y;
const w = this.width, h = this.height;
points[0].set(-w * xAx + h * xAy + x, -w * xAy - h * xAx + y);
points[1].set( w * xAx + h * xAy + x, w * xAy - h * xAx + y);
points[2].set( w * xAx - h * xAy + x, w * xAy + h * xAx + y);
points[3].set(-w * xAx - h * xAy + x, -w * xAy + h * xAx + y);
}
draw() {
ctx.setTransform(1, 0, 0, 1, this.x, this.y);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.strokeStyle = this.active ? 'rgba(255,0,0,1)' : 'rgba(0,0,255,1)';
ctx.lineWidth = this.active ? 3 : 1;
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.rect(-this.width / 2, - this.height / 2, this.width, this.height);
ctx.stroke();
}
}
function draw(){
rect.rotation += rotate;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.draw();
drawSelector();
requestAnimationFrame(draw);
}
function drawSelector() {
if (dragging && hasSize) {
rect.toPoints(rectangle.points);
rect.active = isPolyOver(selector, rectangle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(...marquee);
ctx.fill();
ctx.stroke();
} else {
rect.active = false;
}
}
function dragStart(e) {
startX = e.clientX - offsetX;
startY = e.clientY - offsetY;
drag(e);
canvas.addEventListener("mousemove", drag);
}
function drag(e) {
dragging = true;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
const left = Math.min(startX, x);
const top = Math.min(startY, y);
const w = Math.max(startX, x) - left;
const h = Math.max(startY, y) - top;
marquee[0] = left;
marquee[1] = top;
marquee[2] = w;
marquee[3] = h;
if (w > 0 || h > 0) {
hasSize = true;
selector.points[0].set(left, top);
selector.points[1].set(left + w, top);
selector.points[2].set(left + w, top + h);
selector.points[3].set(left , top + h);
} else {
hasSize = false;
}
}
function dragEnd(e) {
dragging = false;
rect.active = false;
canvas.removeEventListener("mousemove", drag);
}
const rect = new Rectangle(canvas.width / 2, canvas.height / 2, 90, 90, Math.PI / 4);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>

Raycasting with maximum distance

I'm writing a simple raycast in htm5l and the main issue with the raycast is that the line goes off in a direction of probably infinite length.. I would like to limit that length to a specific radius but I'm not having any luck. If someone could guide me that'd be great.
window.addEventListener('DOMContentLoaded', (event) => {
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let coord = {
x: 0,
y: 0
}
function line(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Boundery {
constructor(x1, y1, x2, y2) {
this.a = new Vector(x1, y1);
this.b = new Vector(x2, y2);
}
show() {
ctx.strokeStyle = '#000000'
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}
class Ray {
constructor(x, y) {
this.pos = new Vector(x, y);
this.dir = new Vector(Math.cos(1), Math.sin(0));
}
show() {
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.ellipse(this.pos.x, this.pos.y, 5, 5, 0, 0, Math.PI * 2);
ctx.stroke();
}
cast(wall) {
let x1 = wall.a.x;
let y1 = wall.a.y;
let x2 = wall.b.x;
let y2 = wall.b.y;
let x3 = this.pos.x;
let y3 = this.pos.y;
let x4 = this.pos.x + this.dir.x;
let y4 = this.pos.y + this.dir.y;
let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (den == 0) {
return;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
if (t > 0 && t < 1 && u > 0) {
let point = new Vector(x1 + t * (x2 - x1), y1 + t * (y2 - y1));
return point;
} else {
return;
}
}
}
let wall = new Boundery(300, 100, 300, 300);
let ray = new Ray(100, 200);
function tick(timestamp) {
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
wall.show();
ray.show();
let r = ray.cast(wall);
if (r) {
ctx.fillStyle = 'red';
ctx.ellipse(r.x, r.y, 10, 10, 0, 0, 2 * Math.PI);
ctx.fill();
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
<canvas id="canvas" width="2000" height="1000"></canvas>
So the ray currently fires to the right (1,0) of the small red circle but it's distance just goes on forever so I'm trying to limit that distance. In the example the position the ray hits the wall is the red circle that's drawn on the wall
Ray length and direction
Modify the ray to have the start position, a direction, and a length. as follows
class Ray {
constructor(pos, direction, length) {
this.pos = pos;
this.dir = direction;
this.length = length;
}
You can get the end point with
get end() {
return new Vector(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
}
When you cast the ray you convert the ray to a line segment and then check against any wall segments for the intercept. Only points withing the length of the ray will be found.
Example.
The example uses a ray to check against many walls. It finds the closest intercept to the to the start of the ray and within the rays length.
Note (FOR example only) the walls are random so if a wall gets to close to the ray, click the canvas to randomize the walls.
I have re-organised it somewhat with Vector as a point, Line (2 vectors) as a line segment with an intercept function (also represents a wall), And Ray as a vector, direction and length. The Ray.cast finds the intercept of an array of line, returning undefined if no intercept found.
const ctx = canvas.getContext("2d");
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
var myRay;
const WALL_COUNT = 30;
const WALL_STYLE = {radius: 0, lineWidth: 1, strokeStyle: "#000"};
const RAY_STYLE_A = {radius: 2, lineWidth: 1, strokeStyle: "#0FF", fillStyle: "#F00"};
const RAY_STYLE_B = {radius: 5, lineWidth: 3, strokeStyle: "#00F", fillStyle: "#F00"};
const RAY_INTERCEPT_STYLE = {radius: 5, lineWidth: 1, strokeStyle: "#000", fillStyle: "#FF0"};
const ROTATE_RAY = 10; // seconds per rotation
const walls = [];
setTimeout(init, 0);
canvas.addEventListener("click",init);
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.arc(this.x, this.y, radius, 0, Math.TAU);
ctx.fill();
ctx.stroke();
}
}
class Line {
constructor(start, end) {
this.start = start;
this.end = end;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
if (radius > 0) {
this.start.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
this.end.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
}
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(this.start.x, this.start.y);
ctx.lineTo(this.end.x, this.end.y);
ctx.stroke();
}
intercept(line) {
var x1, y1, x2, y2, x3, y3, c, u;
x1 = line.end.x - line.start.x;
y1 = line.end.y - line.start.y;
x2 = this.end.x - this.start.x;
y2 = this.end.y - this.start.y;
c = x1 * y2 - y1 * x2;
if (c) {
x3 = line.start.x - this.start.x;
y3 = line.start.y - this.start.y;
u = (x1 * y3 - y1 * x3) / c;
if (u >= 0 && u <= 1) {
u = (x2 * y3 - y2 *x3) / c;
if (u >= 0 && u <= 1) { return [u, line.start.x + x1 * u, line.start.y + y1 * u] }
}
}
}
}
class Ray {
constructor(pos, direction, length) {
this.pos = pos;
this.dir = direction;
this.length = length;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
this.pos.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(this.pos.x, this.pos.y);
ctx.lineTo(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
ctx.stroke();
}
get end() {
return new Vector(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
}
get line() {
return new Line(this.pos, this.end);
}
cast(lines) {
const tLine = this.line;
var minDist = 1, point;
for (const line of lines) {
const result = line.intercept(tLine);
if (result) {
const [u, x, y] = result;
if (u <= minDist) {
minDist = u;
if (!point) { point = new Vector(x, y) }
else {
point.x = x;
point.y = y;
}
point.u = u;
}
}
}
return point;
}
}
function init() {
walls.length = 0;
for (let i = 0; i < WALL_COUNT / 2; i++) {
walls.push(new Ray(
new Vector(Math.rand(0, canvas.width * 0.4), Math.rand(0, canvas.height)),
(Math.rand(0, 8) | 0) / 4 * Math.PI, 100
).line);
walls.push(new Ray(
new Vector(Math.rand(canvas.width * 0.6, canvas.width), Math.rand(0, canvas.height)),
(Math.rand(0, 8) | 0) / 4 * Math.PI, 100
).line);
}
if(!myRay) {
myRay = new Ray(new Vector(canvas.width / 2, canvas.height / 2), 0, Math.max(canvas.width, canvas.height) * 0.485);
requestAnimationFrame(mainLoop);
}
}
function mainLoop(time) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
myRay.dir = (time / (ROTATE_RAY * 1000)) * Math.TAU;
const point = myRay.cast(walls)
myRay.draw(ctx, RAY_STYLE_A);
for(const w of walls) { w.draw(ctx, WALL_STYLE) }
if (point) {
const len = myRay.length;
myRay.length = point.u * len;
myRay.draw(ctx, RAY_STYLE_B);
myRay.length = len;
point.draw(ctx, RAY_INTERCEPT_STYLE);
}
requestAnimationFrame(mainLoop);
}
#canvas {
border: 2px solid black;
}
<canvas id="canvas" width="500" height="500"> </canvas>

Javascript Canvas apply Radial Gradient to Segment?

I am trying to create a shadow system for my 2D Game in a HTML5 Canvas. Right now, I am rendering my shadows like so:
function drawShadows(x, y, width) {
if (shadowSprite == null) {
shadowSprite = document.createElement('canvas');
var tmpCtx = shadowSprite.getContext('2d');
var shadowBlur = 20;
shadowSprite.width = shadowResolution;
shadowSprite.height = shadowResolution;
var grd = tmpCtx.createLinearGradient(-(shadowResolution / 4), 0,
shadowResolution, 0);
grd.addColorStop(0, "rgba(0, 0, 0, 0.1)");
grd.addColorStop(1, "rgba(0, 0, 0, 0)");
tmpCtx.fillStyle = grd;
tmpCtx.shadowBlur = shadowBlur;
tmpCtx.shadowColor = "#000";
tmpCtx.fillRect(0, 0, shadowResolution, shadowResolution);
}
graph.save();
graph.rotate(sun.getDir(x, y));
graph.drawImage(shadowSprite, 0, -(width / 2), sun.getDist(x, y), width);
graph.restore();
}
This renders a cube with a linear gradient that fades from black to alpha 0.
This however does not produce a realistic result, since it will always be a rectangle. Here is an illustration to describe the problem:
Sorry i'm not very artistic. It would not be an issue to draw the trapezoid shape. (Seen in blue). The issue is that I still there to be a gradient. Is it possible to draw a shape like that with a gradient?
The canvas is very flexible. Almost anything is possible. This example draws the light being cast. But it can just as easily be the reverse. Draw the shadows as a gradient.
If you are after realism then instead of rendering a gradient for the lighting (or shadows) use the shape created to set a clipping area and then render a accurate lighting and shadow solution.
With lineTo and gradients you can create any shape and gradient you my wish. Also to get the best results use globalCompositeOperation as they have a large variety of filters.
The demo just shows how to mix a gradient and a shadow map. (Very basic no recursion implemented, and shadows are just approximations.)
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var mouse = {
x:0,
y:0,
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
}
// add mouse controls
canvas.addEventListener('mousemove',mouseMove);
var boundSize = 10000; // a number....
var createImage = function(w,h){ // create an image
var image;
image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
var directionC = function(x,y,xx,yy){ // this should be inLine but the angles were messing with my head
var a; // so moved it out here
a = Math.atan2(yy - y, xx - x); // for clarity and the health of my sanity
return (a + Math.PI * 2) % (Math.PI * 2); // Dont like negative angles.
}
// Create background image
var back = createImage(20, 20);
back.ctx.fillStyle = "#333";
back.ctx.fillRect(0, 0, 20, 20);
// Create background image
var backLight = createImage(20, 20);
backLight .ctx.fillStyle = "#ACD";
backLight .ctx.fillRect(0, 0, 20, 20);
// create circle image
var circle = createImage(64, 64);
circle.ctx.fillStyle = "red";
circle.ctx.beginPath();
circle.ctx.arc(32, 32, 30, 0, Math.PI * 2);
circle.ctx.fill();
// create some circles semi random
var circles = [];
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
// shadows on for each circle;
var shadows = [{},{},{},{}];
var update = function(){
var c, dir, dist, x, y, x1, y1, x2, y2, dir1, dir2, aAdd, i, j, s, s1 ,nextDir, rev, revId;
rev = false; // if inside a circle reverse the rendering.
// set up the gradient at the mouse pos
var g = ctx.createRadialGradient(mouse.x, mouse.y, canvas.width * 1.6, mouse.x, mouse.y, 2);
// do each circle and work out the two shadow lines coming from it.
for(var i = 0; i < circles.length; i++){
c = circles[i];
dir = directionC(mouse.x, mouse.y, c.x, c.y);
dist = Math.hypot(mouse.x - c.x, mouse.y - c.y);
// cludge factor. Could not be bother with the math as the light sourse nears an object
if(dist < 30* c.scale){
rev = true;
revId = i;
}
aAdd = (Math.PI / 2) * (0.5 / (dist - 30 * c.scale));
x1 = Math.cos(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
y1 = Math.sin(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
x2 = Math.cos(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
y2 = Math.sin(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
// direction of both shadow lines
dir1 = directionC(mouse.x, mouse.y, c.x + x1, c.y + y1);
dir2 = directionC(mouse.x, mouse.y, c.x + x2, c.y + y2);
// create the shadow object to hold details
shadows[i].dir = dir;
shadows[i].d1 = dir1;
if (dir2 < dir1) { // make sure second line is always greater
dir2 += Math.PI * 2;
}
shadows[i].d2 = dir2;
shadows[i].x1 = (c.x + x1); // set the shadow start pos
shadows[i].y1 = (c.y + y1);
shadows[i].x2 = (c.x + x2); // for both lines
shadows[i].y2 = (c.y + y2);
shadows[i].circle = c; // ref the circle
shadows[i].dist = dist; // set dist from light
shadows[i].branch1 = undefined; //.A very basic tree for shadows that interspet other object
shadows[i].branch2 = undefined; //
shadows[i].branch1Dist = undefined;
shadows[i].branch2Dist = undefined;
shadows[i].active = true; // false if the shadow is in a shadow
shadows[i].id = i;
}
shadows.sort(function(a,b){ // sort by distance from light
return a.dist - b.dist;
});
// cull shdows with in shadows and connect circles with joined shadows
for(i = 0; i < shadows.length; i++){
s = shadows[i];
for(j = i + 1; j < shadows.length; j++){
s1 = shadows[j];
if(s1.d1 > s.d1 && s1.d2 < s.d2){ // if shadow in side another
s1.active = false; // cull it
}else
if(s.d1 > s1.d1 && s.d1 < s1.d2){ // if shodow intercepts going twards light
s1.branch1 = s;
s.branch1Dist = s1.dist - s.dist;
s.active = false;
}else
if(s.d2 > s1.d1 && s.d2 < s1.d2){ // away from light
s.branch2 = s1;
s.branch2Dist = s1.dist - s.dist;
s1.active = false;
}
}
}
// keep it quick so not using filter
// filter culled shadows
var shadowsShort = [];
for (i = 0; i < shadows.length; i++) {
if ((shadows[i].active && !rev) || (rev && shadows[i].id === revId)) { // to much hard work makeng shadow from inside the circles. Was a good idea at the time. But this i just an example after all;
shadowsShort.push(shadows[i])
}
}
// sort shadows in clock wise render order
if(rev){
g.addColorStop(0.3, "rgba(210,210,210,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(0,0,0,0.9)");
shadowsShort.sort(function(a,b){
return b.dir - a.dir;
});
// clear by drawing background image.
ctx.drawImage(backLight, 0, 0, canvas.width, canvas.height);
}else{
g.addColorStop(0.3, "rgba(0,0,0,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(215,215,215,0.9)");
shadowsShort.sort(function(a,b){
return a.dir - b.dir;
});
// clear by drawing background image.
ctx.drawImage(back, 0, 0, canvas.width, canvas.height);
}
// begin drawin the light area
ctx.fillStyle = g; // set the gradient as the light
ctx.beginPath();
for(i = 0; i < shadowsShort.length; i++){ // for each shadow move in to the light across the circle and then back out away from the light
s = shadowsShort[i];
x = s.x1 + Math.cos(s.d1) * boundSize;
y = s.y1 + Math.sin(s.d1) * boundSize;
if (i === 0) { // if the start move to..
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
ctx.lineTo(s.x1, s.y1);
if (s.branch1 !== undefined) { // if braching. (NOTE this is not recursive. the correct solution would to math this a function and use recursion to climb in an out)
s = s.branch1;
x = s.x1 + Math.cos(s.d1) * s.branch1Dist;
y = s.y1 + Math.sin(s.d1) * s.branch1Dist;
ctx.lineTo(x, y);
ctx.lineTo(s.x1, s.y1);
}
ctx.lineTo(s.x2, s.y2);
if (s.branch2 !== undefined) {
x = s.x2 + Math.cos(s.d2) * s.branch2Dist;
y = s.y2 + Math.sin(s.d2) * s.branch2Dist;
ctx.lineTo(x, y);
s = s.branch2;
ctx.lineTo(s.x2, s.y2);
}
x = s.x2 + Math.cos(s.d2) * boundSize;
y = s.y2 + Math.sin(s.d2) * boundSize;
ctx.lineTo(x, y);
// now fill in the light between shadows
s1 = shadowsShort[(i + 1) % shadowsShort.length];
nextDir = s1.d1;
if(rev){
if (nextDir > s.d2) {
nextDir -= Math.PI * 2
}
}else{
if (nextDir < s.d2) {
nextDir += Math.PI * 2
}
}
x = Math.cos((nextDir+s.d2)/2) * boundSize + canvas.width / 2;
y = Math.sin((nextDir+s.d2)/2) * boundSize + canvas.height / 2;
ctx.lineTo(x, y);
}
// close the path.
ctx.closePath();
// set the comp to lighten or multiply
if(rev){
ctx.globalCompositeOperation ="multiply";
}else{
ctx.globalCompositeOperation ="lighter";
}
// draw the gradient
ctx.fill()
ctx.globalCompositeOperation ="source-over";
// draw the circles
for (i = 0; i < circles.length; i++) {
c = circles[i];
ctx.drawImage(circle, c.x - 32 * c.scale, c.y - 32 * c.scale, 64 * c.scale, 64 * c.scale);
}
// feed the herbervors.
window.requestAnimationFrame(update);
}
update();
.canC { width:400px; height:400px;}
<canvas class="canC" id="canV" width=400 height=400></canvas>

Continuous gradient along a HTML5 canvas path

I am trying to draw a continous gradient along a path of points, where each point has a it's own color, using the HTML5 canvas API.
See http://bl.ocks.org/rveciana/10743959 for inspiration, where that effect is achieved with D3.
There doesn't seem to be a way to add multiple linear gradients for a single canvas path, so I resorted to something like this: http://jsfiddle.net/51toapv2/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var pts = [[100, 100, "red"], [150, 150, "green"], [200, 100, "yellow"]];
ctx.lineWidth = 20;
ctx.lineJoin = "round";
ctx.lineCap = "round";
for (var i = 0; i < pts.length - 1; i++) {
var begin = pts[i];
var end = pts[i + 1];
ctx.beginPath();
var grad = ctx.createLinearGradient(begin[0], begin[1], end[0], end[1]);
grad.addColorStop(0, begin[2]);
grad.addColorStop(1, end[2]);
ctx.strokeStyle = grad;
ctx.moveTo(begin[0], begin[1]);
ctx.lineTo(end[0], end[1]);
ctx.stroke();
}
As you can see it produces a subpar effect as the paths aren't merged and the "line joins" are clearly visible.
Is it possible to achieve the effect I'm looking for with the canvas API?
Here's a slight modification of your original idea that makes the joins blend nicely.
Original: Draw a gradient line from the start to end of a line segment.
This causes the line joins to overlap and produces a noticeable & undesired transition.
Modification: Draw a gradient line that doesn't extend to the start / endpoints.
With this modification, the line joins will always be solid colors rather than be partially gradiented. As a result, the line joins will transition nicely between line segments.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var lines = [
{x:100, y:050,color:'red'},
{x:150, y:100,color:'green'},
{x:200, y:050,color:'gold'},
{x:275, y:150,color:'blue'}
];
var linewidth=20;
ctx.lineCap='round';
ctx.lineJoint='round';
for(var i=1;i<lines.length;i++){
// calculate the smaller part of the line segment over
// which the gradient will run
var p0=lines[i-1];
var p1=lines[i];
var dx=p1.x-p0.x;
var dy=p1.y-p0.y;
var angle=Math.atan2(dy,dx);
var p0x=p0.x+linewidth*Math.cos(angle);
var p0y=p0.y+linewidth*Math.sin(angle);
var p1x=p1.x+linewidth*Math.cos(angle+Math.PI);
var p1y=p1.y+linewidth*Math.sin(angle+Math.PI);
// determine where the gradient starts and ends
if(i==1){
var g=ctx.createLinearGradient(p0.x,p0.y,p1x,p1y);
}else if(i==lines.length-1){
var g=ctx.createLinearGradient(p0x,p0y,p1.x,p1.y);
}else{
var g=ctx.createLinearGradient(p0x,p0y,p1x,p1y);
}
// add the gradient color stops
// and draw the gradient line from p0 to p1
g.addColorStop(0,p0.color);
g.addColorStop(1,p1.color);
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p1.x,p1.y);
ctx.strokeStyle=g;
ctx.lineWidth=linewidth;
ctx.stroke();
}
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=350 height=200></canvas>
You can do a simple approach interpolating two colors along a line. If you need smooth/shared gradients where two lines joins at steeper angles, you would need to calculate and basically implement a line drawing algorithm from (almost) scratch. This would be out of scope for SO, so here is a simpler approach.
That being said - the example in the link is not actually a line but several plots of squares of different colors. The issues it would have too is "hidden" by its subtle variations.
Example
This approach requires two main functions:
Line interpolate function which draws each segment in a line from previous mouse position to current position
Color interpolate function which takes an array of colors and interpolate between two current colors depending on length, position and segment size.
Tweak parameters such as segment size, number of colors in the array etc. to get the optimal result.
Line interpolate function
function plotLine(ctx, x1, y1, x2, y2) {
var diffX = Math.abs(x2 - x1), // get line length
diffY = Math.abs(y2 - y1),
dist = Math.sqrt(diffX * diffX + diffY * diffY),
step = dist / 10, // define some resolution
i = 0, t, b, x, y;
while (i <= dist) { // render circles along the line
t = Math.min(1, i / dist);
x = x1 + (x2 - x1) * t;
y = y1 + (y2 - y1) * t;
ctx.fillStyle = getColor(); // get current color
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI*2);
ctx.fill();
i += step;
}
Color interpolate function
function getColor() {
var r, g, b, t, c1, c2;
c1 = colors[cIndex]; // get current color from array
c2 = colors[(cIndex + 1) % maxColors]; // get next color
t = Math.min(1, total / segment); // calculate t
if (++total > segment) { // rotate segment
total = 0;
if (++cIndex >= maxColors) cIndex = 0; // rotate color array
}
r = c1.r + (c2.r - c1.r) * t; // interpolate color
g = c1.g + (c2.g - c1.g) * t;
b = c1.b + (c2.b - c1.b) * t;
return "rgb(" + (r|0) + "," + (g|0) + "," + (b|0) + ")";
}
Demo
Putting it all together will allow you to draw gradient lines. If you don't want to draw them manually simply call the plotLine() function whenever needed.
// Some setup code
var c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
colors = [
{r: 255, g: 0, b: 0},
{r: 255, g: 255, b: 0},
{r: 0, g: 255, b: 0},
{r: 0, g: 255, b: 255},
{r: 0, g: 0, b: 255},
{r: 255, g: 0, b: 255},
{r: 0, g: 255, b: 255},
{r: 0, g: 255, b: 0},
{r: 255, g: 255, b: 0},
],
cIndex = 0, maxColors = colors.length,
total = 0, segment = 500,
isDown = false, px, py;
setSize();
c.onmousedown = c.ontouchstart = function(e) {
isDown = true;
var pos = getPos(e);
px = pos.x;
py = pos.y;
};
window.onmousemove = window.ontouchmove = function(e) {if (isDown) plot(e)};
window.onmouseup = window.ontouchend = function(e) {
e.preventDefault();
isDown = false
};
function getPos(e) {
e.preventDefault();
if (e.touches) e = e.touches[0];
var r = c.getBoundingClientRect();
return {
x: e.clientX - r.left,
y: e.clientY - r.top
}
}
function plot(e) {
var pos = getPos(e);
plotLine(ctx, px, py, pos.x, pos.y);
px = pos.x;
py = pos.y;
}
function plotLine(ctx, x1, y1, x2, y2) {
var diffX = Math.abs(x2 - x1),
diffY = Math.abs(y2 - y1),
dist = Math.sqrt(diffX * diffX + diffY * diffY),
step = dist / 50,
i = 0,
t, b, x, y;
while (i <= dist) {
t = Math.min(1, i / dist);
x = x1 + (x2 - x1) * t;
y = y1 + (y2 - y1) * t;
ctx.fillStyle = getColor();
ctx.beginPath();
ctx.arc(x, y, 10, 0, Math.PI*2);
ctx.fill();
i += step;
}
function getColor() {
var r, g, b, t, c1, c2;
c1 = colors[cIndex];
c2 = colors[(cIndex + 1) % maxColors];
t = Math.min(1, total / segment);
if (++total > segment) {
total = 0;
if (++cIndex >= maxColors) cIndex = 0;
}
r = c1.r + (c2.r - c1.r) * t;
g = c1.g + (c2.g - c1.g) * t;
b = c1.b + (c2.b - c1.b) * t;
return "rgb(" + (r|0) + "," + (g|0) + "," + (b|0) + ")";
}
}
window.onresize = setSize;
function setSize() {
c.width = window.innerWidth;
c.height = window.innerHeight;
}
document.querySelector("button").onclick = function() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
};
html, body {background:#777; margin:0; overflow:hidden}
canvas {position:fixed;left:0;top:0;background: #333}
button {position:fixed;left:10px;top:10px}
<canvas></canvas>
<button>Clear</button>
TIPS:
The gradient values can be pre-populated / cached beforehand
The step for position in gradient can be bound to length to get even spread independent of draw speed
You can easily replace the brush with other path/figures/shapes, even combine image based brushes which is composited with current color

Categories

Resources