I'm using the verlet.js plugin in order create a cloth simulation on canvas with a texture image.
The only thing (and the most important BTW) part that I haven't arrived is that I need skew the drawImage in order to make it fit the correct position.
jsfiddle with the progress
//Drawing the rectangle
ctx.save();
ctx.beginPath();
ctx.moveTo(cloth.particles[i1].pos.x, cloth.particles[i1].pos.y);
ctx.lineTo(cloth.particles[i1+1].pos.x, cloth.particles[i1+1].pos.y);
ctx.lineTo(cloth.particles[i2].pos.x, cloth.particles[i2].pos.y);
ctx.lineTo(cloth.particles[i2-1].pos.x, cloth.particles[i2-1].pos.y);
ctx.lineTo(cloth.particles[i1].pos.x, cloth.particles[i1].pos.y);
ctx.strokeStyle = "#fff";
ctx.stroke();
ctx.restore();
//Wrapping the image
ctx.save();
var off = cloth.particles[i2].pos.x - cloth.particles[i1].pos.x;
//THIS IS WHAT I TRY TO SOLVE TO FIT TO THE RECTANGLES
//ctx.transform(1,0.5,0,1,0,0);
ctx.drawImage(img, cloth.particles[i1].pos.x,cloth.particles[i1].pos.y, off, off, cloth.particles[i1].pos.x,cloth.particles[i1].pos.y, off ,off);
ctx.restore();
}
I have tried to adapt other cloth simulations but without success. Any clue where I could find some info to accomplish that?
Using skew (or rather shear) to fill tiles only works if the cell is a parallelogram, as 2D affine transforms only support this shape.
Here is one approach:
Calculate angle of upper line
Calculate angle of left line
Calculate width and height of cell
In a parallelogram bottom line will equal upper line, and of course right line equals left line.
Then set these angles as skew arguments for the transform coupled with translate to the upper left corner.
Then just repeat for each cell.
Example
var img = new Image;
img.onload = function() {
var ctx = document.querySelector("canvas").getContext("2d"),
tile1 = [
{x: 10, y: 10}, // upper left corner
{x: 210, y: 50}, // upper right
{x: 230, y: 150}, // bottom right
{x: 30, y: 110} // bottom left
],
tile2 = [
{x: 210, y: 50},
{x: 410, y: 5},
{x: 430, y: 105},
{x: 230, y: 150}
];
renderTile(this, tile1);
renderTile(this, tile2);
function renderTile(img, tile) {
var dx, dy, a1, a2, w, h, i = 1;
// reference shape (remove this section):
ctx.setTransform(1,0,0,1,0,0);
ctx.moveTo(tile[0].x, tile[0].y);
while(i < 4) ctx.lineTo(tile[i].x, tile[i++].y);
ctx.closePath();
ctx.strokeStyle = "#0c0";
ctx.lineWidth = 2;
ctx.stroke();
// calc horizontal angle
dx = tile[1].x - tile[0].x; // horizontal diff.
dy = tile[1].y - tile[0].y; // vertical diff.
a1 = Math.atan2(dy, dx); // angle, note dy,dx order here
w = dx|0; // width based on diff for x
// calc vertical angle
dx = tile[3].x - tile[0].x;
dy = tile[3].y - tile[0].y;
a2 = Math.atan2(dx, dy); // note dx,dy order here
h = dy|0;
// draw image to fit parallelogram
ctx.setTransform(1, a1, a2, 1, tile[0].x, tile[0].y);
ctx.drawImage(img, 0, 0, w, h);
}
};
img.src = "http://i.imgur.com/rUeQDjE.png";
<canvas width=500 height=160/>
Note: if your cloth simulation produces other shapes than parallelograms (ie. quadrilaterals), which is very likely since this is a physics simulation, this approach won't work well. In that case you need different techniques which are more compute heavy. For this reason WebGL is a better fit. Just my two cents..
Related
How can I fill a triangle with gradients starting at its vertices given a color for each vertex?
I'm trying to reproduce something like this:
I'm making use of the built in fill function from the HTML5 canvas Context2D. I'm trying to avoid having to deal with pixel-by-pixel interpolations based on their distance to the vertices. I fear it wont be as performatic as the built-in fill function (?). Also I can't deal with WebGL right now.
I've done a trick using radial gradients, but, there are a few problems with my naive approach:
The colors don't seem to blend well
The last applied gradient overwrites the others
The value used in the radius variable is arbitrary
OBS: I don't know if it's relevant but, I'm building a triangle strip (indexed geometry actually).
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var v1 = { x: 100, y: 0 };
var v2 = { x: 0, y: 180 };
var v3 = { x: 200, y: 180 };
var radius = 175;
var grd1 = ctx.createRadialGradient(v1.x, v1.y, 0, v1.x, v1.y, radius);
grd1.addColorStop(0, "#FF0000FF");
grd1.addColorStop(1, "#FF000000");
var grd2 = ctx.createRadialGradient(v2.x, v2.y, 0, v2.x, v2.y, radius);
grd2.addColorStop(0, "#00FF00FF");
grd2.addColorStop(1, "#00FF0000");
var grd3 = ctx.createRadialGradient(v3.x, v3.y, 0, v3.x, v3.y, radius);
grd3.addColorStop(0, "#0000FFFF");
grd3.addColorStop(1, "#0000FF00");
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.closePath();
ctx.fillStyle = "#FFFFFFFF"; // fill with white and apply the gradients on top of it
ctx.fill();
ctx.fillStyle = grd1;
ctx.fill();
ctx.fillStyle = grd2;
ctx.fill();
ctx.fillStyle = grd3;
ctx.fill();
<canvas width="200" height="180"></canvas>
The colors don't seem to blend well
For this you can use the globalCompositeOperation property of your 2D context to one of its blend modes, even though in your case the compositing mode "lighter" with a black background seems to produce the closest result to your model.
The last applied gradient overwrites the others
Thanks to the previous bullet point, it's not the case anymore.
The value used in the radius variable is arbitrary
Doesn't look like so to me, it does correspond to the distance between every points of your equilateral triangle and its center, which makes perfect sense.
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
// reordered to make the same as OP's image
var v1 = { x: 0, y: 180 };
var v2 = { x: 200, y: 180 };
var v3 = { x: 100, y: 0 };
var radius = 180;
var grd1 = ctx.createRadialGradient(v1.x, v1.y, 0, v1.x, v1.y, radius);
grd1.addColorStop(0, "#FF0000FF");
grd1.addColorStop(1, "#FF000000");
var grd2 = ctx.createRadialGradient(v2.x, v2.y, 0, v2.x, v2.y, radius);
grd2.addColorStop(0, "#00FF00FF");
grd2.addColorStop(1, "#00FF0000");
var grd3 = ctx.createRadialGradient(v3.x, v3.y, 0, v3.x, v3.y, radius);
grd3.addColorStop(0, "#0000FFFF");
grd3.addColorStop(1, "#0000FF00");
ctx.beginPath();
ctx.moveTo(v1.x, v1.y);
ctx.lineTo(v2.x, v2.y);
ctx.lineTo(v3.x, v3.y);
ctx.closePath();
// fill with black
ctx.fill();
// set blend mode
ctx.globalCompositeOperation = "lighter";
ctx.fillStyle = grd1;
ctx.fill();
ctx.fillStyle = grd2;
ctx.fill();
ctx.fillStyle = grd3;
ctx.fill();
// if you need to draw something else, don't forget to reset the gCO
ctx.globalCompositeOperation = "source-over";
<canvas width="200" height="180"></canvas>
is there any way that you can stretch an image between few given points on the canvas (similar to Transform). When I use Transform , I can definitely stretch it, however it is not giving all the options that I need for what I have in mind. For example, if I am to do a game that should generate objects , such as boxes, I want to make sure that depending on where the player is located, to turn the image on the side of the boxes and transform them as if you are moving around them. In transform, I can do that, however it will always keep the same height on both sides of the image (left and right) while I need to have more control to decrease the height on the right side of the image only (per say). Much appreciated if you can give a hint on how to do that without using any library.
Here is an example on what I have so far (though again the second image on the right still keeps the same height on the it's far right side same as the one on it's left side):
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Reshape images</title>
</head>
<body style="margin:10px;">
<canvas id="canvas" style="background:#000;"></canvas>
<script type="text/javascript">
// INITIAL SETUP
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
var innerWidth = 1000,
innerHeight = 650;
canvas.width = innerWidth;
canvas.height = innerHeight;
var img = new Image;
img.onload = function() {
var tile1 = [
{x: 10, y: 10}, // upper left corner
{x: 210, y: 50}, // upper right
{x: 230, y: 150}, // bottom right
{x: 30, y: 110} // bottom left
],
tile2 = [
{x: 210, y: 50},
{x: 410, y: 5},
{x: 430, y: 105},
{x: 230, y: 150}
];
renderTile(this, tile1);
renderTile(this, tile2);
function renderTile(img, tile) {
var dx, dy, a1, a2, w, h, i = 1;
// calc horizontal angle
dx = tile[1].x - tile[0].x; // horizontal diff.
dy = tile[1].y - tile[0].y; // vertical diff.
a1 = Math.atan2(dy, dx); // angle, note dy,dx order here
w = dx|0; // width based on diff for x
// calc vertical angle
dx = tile[3].x - tile[0].x;
dy = tile[3].y - tile[0].y;
a2 = Math.atan2(dx, dy); // note dx,dy order here
h = dy|0;
// draw image to fit parallelogram
// ctx.setTransform(1, a1, a2, 1, tile[0].x, tile[0].y);
ctx.setTransform(1, a1, a2, 1, tile[0].x, tile[0].y);
ctx.drawImage(img, 0, 0, w, h);
}
};
img.src = "http://i.imgur.com/rUeQDjE.png";
</script>
<script src="src.js"></script>
</body>
</html>
I have a bubble field with elliptical bubbles. I'd like the bubbles to stretch -- elongate -- as they move to the edges of the canvas. This works on the sides, but top & bottom bubbles are getting flattened. I was trying to use atan2 to identify the angle and point that bubble's nose in that direction.
But when I add the rotation, all bubbles rotate. Is there a way to trap each bubble, calculate it's angle and rotate only that bubble instead of the whole context? ctx.rotation rotates the whole canvas. Makes sense. But this.rotation (currently commented out on line 149 of my codepen) has all bubbles spinning the same.
bubble.prototype.draw = function(){
ctx.beginPath();
ctx.save();
var p1 = { x: 0, y: 0 };
var p2 = { x: this.location.x, y: this.location.y };
this.rotation = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
ctx.fillStyle = this.colour;
ctx.ellipse(this.location.x, this.location.y,
this.radius, this.radiusY,
this.rotation,
0,360, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
}
Here's the whole codepen
Or maybe there's another way to radiate out shapes according to their radiant?
I moved the rotation calculation out of thebubble.prototype.draw and into the bubble function. Then, I took the velocity instead of the location and everything lined up!
var p2 = { x: this.velocity.x, y: this.velocity.y };
Codepen updated!
I`m trying to draw a long (actually very long) bezier lines on my graph. When hover over line it should become thicker. And i encountered an issue. Sometimes lines has different curvature depending on lineWidth in Chrome.
Code exapmle
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, 1, -3700, -50)
const gap = 32;
const line = [
{x: 3830, y: 95},
{x: 3830 + gap, y: 95},
{x: 12600 - gap, y: 25895},
{x: 12600, y: 25895}
];
// Draw bezier out of box
function drawLine(p1, a1, a2, p2, width, color) {
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.bezierCurveTo(a1.x, a1.y, a2.x, a2.y, p2.x, p2.y);
ctx.stroke();
}
drawLine.apply(null, line.concat([3, '#f00']));
drawLine.apply(null, line.concat([1, '#00f']));
JSFiddle
In Safari it looks fine, but Chrome and FF draw the same lines with the same points different.
Looks like curvature depending on lineWidth.
May be someone knows how to solve this problem?
I want to use the arcTo method to draw several arcs of different colours along the circumference of a large circle.
See my codepen here
$(document).ready(() => {
let canvas = $('#canvas')[0];
let ctx = canvas.getContext('2d');
//draw a point on the canvas, optional radius and colour
const drawPoint = (point, radius, colour) => {
let col = colour || 'black';
let r = radius || 2;
ctx.beginPath();
ctx.arc(point.x, point.y, r, 0, Math.PI * 2, true);
ctx.fillStyle = col;
ctx.fill();
}
//draw an arc on the canvas using arcTo, optional colour
const drawArc = (p1, p2, p3, r,colour) => {
let col = colour || 'black';
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arcTo(p3.x, p3.y, p2.x, p2.y, r);
ctx.strokeStyle = col;
ctx.stroke();
}
ctx.translate(window.innerWidth/2, window.innerHeight/3);
//circle radius and path have been calculated by another function
let c = {x: 1052.5390625, y: 506.8760978034711};
let radius = 1096.702150363923;
ctx.beginPath();
ctx.arc(c.x,c.y,radius, 0, Math.PI*2)
ctx.strokeStyle = 'black';
ctx.stroke();
//now I want to draw several arcs of different lengths and colours on this circle
//Heres the first arc
//(again points on the circle are calculatedby a different function, I've entered them manually here)
let p1 = {x: 288.88884710654133, y: -280.26680862609};
let p2 = {x: -39.00216443306411 , y: 400.6058925796476};
let p3 = {x: 0, y: 0};
drawPoint(p1, 4, 'green');
drawPoint(p2, 4, 'green');
drawPoint(p3, 4, 'green');
drawArc(p1, p2, p3, radius, 'green');
let q1 = {x: 49.879184148698684, y: 62.546518597442386}
let q2 = {x: 80, y: 0}
let q3 = {x: 63.94929512052109, y: 30.796357420674724}
drawPoint(q1, 4, 'red');
drawPoint(q2, 4, 'red');
drawPoint(q3, 4, 'red');
drawArc(q1, q2, q3, radius, 'red');
});
There is a large black circle, then a large green arc with three green control points, and a smaller red arc with red control points.
In Firefox it works fine, the arcs all line up Chrome (well, Chromium) at large screen sizes they do not.
Chromium Version 47.0.2526.106 Ubuntu 15.10 (64-bit)