I have this triangle that I have rounded corners on but I'm using the arcTo:
context.moveTo(140, 0);
context.arcTo(180, 100, 100, 100, 4);
context.arcTo(100, 100, 140, 0, 4);
context.arcTo(140, 0, 180, 100, 4);
As you will see the top angle looks a bit messed up. Any ideas how to fix it? Seems like there needs to be some calculations for the initial moveTo(x, y) but 140, 0 is where it should start.
I just got rid of the moveTo and arced them to eachother. Started the first part at 174, 176 (180-4 radius) works but 174 had no overlap at all.
Live Demo
var canvas = document.getElementsByTagName("canvas")[0],
ctx = canvas.getContext("2d");
canvas.width = canvas.height = 400;
ctx.beginPath();
ctx.arcTo(174, 100, 100, 100, 4);
ctx.arcTo(100, 100, 140, 0, 4);
ctx.arcTo(140, 0, 180, 100, 4);
ctx.arcTo(180, 100, 100, 100, 4);
ctx.stroke();
Here is what I came up with:
var r = 8;
var offset = (6 * r / 4) - 1;
context.arcTo((180 - offset), 100, 100, 100, r);
context.arcTo(100, 100, 140, 0, r);
context.arcTo(140, 0, 180, 100, r);
context.arcTo(180, 100, 100, 100, r);
Using part of what Loktar provided, I've modified it slightly to use a ratio of what I know works for a given diameter, and used this as the offset. With a diameter of 4, I know 6 works.
Seems like there should be a better way but I'm happy with this.
Related
I'am trying to clip the FabricJS rect shape to the polygon shape. The clipping works okay until the polygon shape which need to be clipped is now scaled. After this there is some weird offset that is caused by the polygon clipping.
Can anyone help me how can i fix the function to prevent the polygon offset issue when clip object is scaled.
This is how it looks before scalling. The clipping works fine
Image => https://i.imgur.com/Eop2YJh.png
And then there is the problem when the polygon is scaled.
2: Image => https://i.imgur.com/ICkP8SG.png
Here is the code on fiddle with the clipping function
https://jsfiddle.net/0xpvc9uq/
So if there is anyone who knows whats the point and how can I fix it I would appriciate it.
Thx
var canvas = new fabric.Canvas('c');
var rect1 = new fabric.Rect({
left: 0, top: 0,
width: 900, height: 900,
fill: 'blue',
selectable: false,
clipTo: clipRegion,
scaleX: 1.5,
scaleY: 1.5
});
var clipPoly = new fabric.Polygon([
{ x: 180, y: 10 },
{ x: 300, y: 50 },
{ x: 300, y: 180 },
{ x: 180, y: 220 }
], {
originX: 'left',
originY: 'top',
left: 180,
top: 10,
fill: 'transparent', /* use transparent for no fill */
strokeWidth: 0,
selectable: false,
strokeWidth: 1,
stroke: "red",
scaleX: 1.3,
scaleY: 1.3
});
canvas.add(rect1, clipPoly);
function clipRegion (ctx) {
rect1.setCoords();
const clipObj = clipPoly;
const scaleXTo1 = (1 / rect1.scaleX);
const scaleYTo1 = (1 / rect1.scaleY);
ctx.save();
const ctxLeft = -( rect1.width / 2 ) - clipObj.strokeWidth - rect1.strokeWidth;
const ctxTop = -( rect1.height / 2 ) - clipObj.strokeWidth - rect1.strokeWidth;
ctx.translate( ctxLeft, ctxTop );
ctx.scale(scaleXTo1, scaleYTo1);
ctx.rotate((rect1.angle * -1) * (Math.PI / 180));
ctx.beginPath();
const matrix = clipPoly.calcTransformMatrix();
let points = [];
clipObj.points.forEach( (point) => {
points.push({
x: ((point.x * matrix[0]) + (clipObj.strokeWidth * clipObj.scaleX)) - rect1.oCoords.tl.x,
y: ((point.y * matrix[3]) + (clipObj.strokeWidth * clipObj.scaleY)) - rect1.oCoords.tl.y
});
});
ctx.moveTo(points[0].x, points[0].y);
points.forEach((point) => {
ctx.lineTo(point.x, point.y);
});
ctx.lineTo(points[0].x, points[0].y);
ctx.closePath();
ctx.restore();
}
I discovered that there is also another approach that can be used and that solves all the problem.
The clipRegion function now looks like:
function clipRegion (ctx) {
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
clipPoly.render(ctx);
ctx.restore();
}
Which makes the rendering okay. If still anyone have other way to fix the above problem, I would like to see the answer
I am trying to use canvas's globalCompositeOperation='destination-in' setting to draw a series of dots that are masked by a radial gradient. My desired outcome is shown in the screenshot below:
Instead, my canvas is showing the solid gradient with none of the dots visible. Here's my JS:
var canvas = document.getElementById('canvas')
, ctx = canvas.getContext('2d');
var coordMatrix = [
[50, 100, 150, 50, 100, 150],
[50, 50, 50, 100, 100, 100]
];
var gradient = ctx.createRadialGradient(100, 100, 0, 100, 100, 100);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 200, 200);
ctx.globalCompositeOperation = 'destination-in';
coordMatrix[0].forEach(function(xCoord, i) {
var yCoord = coordMatrix[1][i];
ctx.moveTo(xCoord, yCoord);
ctx.arc(xCoord, yCoord, 10, 0, Math.PI * 2, false);
});
And here's a fiddle:
https://jsfiddle.net/73d9jawn/2/
Am I missing something?
You forgot to call ctx.fill() after setting the coordinates for the arcs. Also, you need to call ctx.fill() after the forEach has completed all iterations, otherwise globalCompositeOperation only applies to the first circle drawn. Here is an updated fiddle.
I was wondering what the best method was for changing the position of a shape within the canvas.
Here's what I have:
'use strict';
(function() {
const canvas = document.getElementsByClassName('canvas')[0],
c = canvas.getContext('2d');
//
c.beginPath();
c.moveTo(0, 50);
c.quadraticCurveTo(0, 0, 50, 0);
c.quadraticCurveTo(100, 0, 100, 50);
c.quadraticCurveTo(100, 100, 50, 100);
c.quadraticCurveTo(0, 100, 0, 50);
c.stroke();
})();
<canvas class="canvas" width="500" height="500"></canvas>
So right now that shape is sitting at 0,0. If I wanted to move it in the X direction I could theoretically do something like this:
c.beginPath();
c.moveTo(x+0,50);
c.quadraticCurveTo(x+0,0,x+50,0);
c.quadraticCurveTo(x+100,0,x+100,50);
c.quadraticCurveTo(x+100,100,x+50,100);
c.quadraticCurveTo(x+0,100,x+0,50);
c.stroke();
What's the correct way of achieving this?
As you ask "I was wondering what the best method was for changing the position of a shape within the canvas."
The answer is
By using the transformation matrix
Using direct manipulation of the coordinates is very inefficient.
The 2D API provides a full set of methods for transforming (moving) anything that is rendered.
These methods are;
ctx.setTransform(axisX_X, axisX_Y, axisY_X, axisY_Y, originX,
originY)
ctx.transform(axisX_X, axisX_Y, axisY_X, axisY_Y, originX, originY)
ctx.scale(scaleX, scaleY)
ctx.rotate(radians)
ctx.translate(originX, originY)
(links to MDN for each)
These will move anything that you render to the canvas apart from ctx.putPixelData
thus to move your object by x and y you can use
c.translate(x,y); // move the following coordinates by x, and y;
c.beginPath();
c.moveTo(0, 50);
c.quadraticCurveTo(0, 0, 50, 0);
c.quadraticCurveTo(100, 0, 100, 50);
c.quadraticCurveTo(100, 100, 50, 100);
c.quadraticCurveTo(0, 100, 0, 50);
c.stroke();
Or directly set the transform with
c.setTransform(1, 0, 0, 1, x, y);
c.beginPath();
c.moveTo(0, 50);
c.quadraticCurveTo(0, 0, 50, 0);
c.quadraticCurveTo(100, 0, 100, 50);
c.quadraticCurveTo(100, 100, 50, 100);
c.quadraticCurveTo(0, 100, 0, 50);
c.stroke();
These can be tricky to understand if you are not familiar with matrix manipulation as methods 2,3,4,5 from the above list are multiplications of the existing matrix. While method 1 simply replaces the existing matrix and is not effected by previous transformations.
Here is a very simple function that will move an object and includes scale and rotation.
// rotation is in radians and rotates the x and y axis
// scaleX scales along the new xAxis
// scaleY scales along the new yAxis
// x and y translate to the desired screen coordinates
function position(ctx, x, y, scaleX, scaleY, rotation){
var scaleRatio = scaleY / scaleX;
var rx = Math.cos(rotation) * scaleX;
var ry = Math.sin(rotation) * scaleX;
ctx.setTransform(rx, ry, -ry * scaleRatio, rx * scaleRatio, x, y);
}
to call
position(ctx,100,100,2,0.5, Math.PI/4)
moves anything drawn after that call 100,100 pixels, makes it twice as wide and half as high and rotates it 45 deg clockwise
to restore to the default do either
position(ctx, 0, 0, 1, 1, 0);
or quicker with
ctx.setTransform(1, 0, 0, 1, 0, 0);
For a more detailed explanation
of how to use setTransform and why it is prefered over the other methods see this answer
You've got the right idea. Just make sure that you're clearing the canvas between draws to make sure you're not drawing over what's currently on the canvas. Then you can just have some x value that you update and you can draw it anywhere. You could extend this to using some y value to allow you to move it vertically as well.
'use strict';
(function() {
const canvas = document.getElementsByClassName('canvas')[0],
c = canvas.getContext('2d');
var x = 0;
function render() {
c.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
c.beginPath();
// Adjust path based on x position
c.moveTo(x, 50);
c.quadraticCurveTo(x, 0, x + 50, 0);
c.quadraticCurveTo(x + 100, 0, x + 100, 50);
c.quadraticCurveTo(x + 100, 100, x + 50, 100);
c.quadraticCurveTo(x, 100, x, 50);
c.stroke();
// Doing this for animation
x += 5;
if (x > canvas.width) {
x = -100;
}
requestAnimationFrame(render);
}
render();
})();
<canvas class="canvas" width="500" height="500"></canvas>
So I have made basic drawings with html5 canvas and the basic shapes you can create have parameters to position the whole shape, below I center a circle centerX and centerY by taking the window size and dividing by 2.
context.beginPath();
context.globalCompositeOperation = 'destination-out';
context.arc(centerX, centerY, radius, Math.PI*2, false);
context.fill();
context.closePath();
The above drawing is nice and centered but now that I am playing with the bezier curve I can't find anything on the web that suggests how to center it.
// some arbitrary example
context.beginPath();
context.moveTo(170, 80);
context.bezierCurveTo(130, 100, 130, 150, 230, 150);
context.bezierCurveTo(250, 180, 320, 180, 340, 150);
context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.fill();
context.globalCompositeOperation = 'destination-out';
context.closePath();
I wrote up a fiddle so there is something to work with JSFIDDLE. Below is the code pasted directly from my fiddle.
var canvas = document.getElementById("c");
var context = canvas.getContext("2d");
canvas.width = $(window).width();
canvas.height = $(window).height();
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.fillStyle = '#333';
context.fillRect(0, 0, canvas.width, canvas.height);
context.closePath();
// custom shape (weird shape lol)
context.beginPath();
context.globalCompositeOperation = 'destination-out';
context.moveTo(170, 80);
context.bezierCurveTo(130, 100, 130, 150, 230, 150);
context.bezierCurveTo(250, 180, 320, 180, 340, 150);
context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.fill();
context.closePath();
context.globalCompositeOperation = 'source-over';
}
draw();
Here's one method to accurately center your group of cubic Bezier curves
A Demo: http://jsfiddle.net/m1erickson/6GZmp/
Step#1. Use De Casteljau's algorithm to plot points along each curve in your group of curves.
// De Casteljau's algorithm which calculates points along a cubic Bezier curve
// plot a point at interval T along a bezier curve
// T==0.00 at beginning of curve. T==1.00 at ending of curve
// Calculating 100 T's between 0-1 will usually define the curve sufficiently
function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
return({x:x,y:y});
}
// cubic helper formula at T distance
function CubicN(T, a,b,c,d) {
var t2 = T * T;
var t3 = t2 * T;
return a + (-a * 3 + T * (3 * a - a * T)) * T
+ (3 * b + T * (-6 * b + b * 3 * T)) * T
+ (c * 3 - c * 3 * T) * t2
+ d * t3;
}
Step#2. Determine the bounding box of the curve-group by getting the minX,maxX,minY,maxY of the points you plotted in #1. And use max-min to determine the width and height of the curves group.
var curvesWidth = maxX - minX;
var curvesHeight = maxY - minY;
Step#3. Calculate the offset needed in order to center your curves-group.
var offsetX=(canvas.width/2-curvesWidth/2)-curvesLeft;
var offsetY=(canvas.height/2-curvesHeight/2)-curvesTop;
Step#4. Knowing the offsets, you can use context.translate to draw your centered curves.
context.save();
context.translate(offsetX,offsetY);
context.beginPath();
context.moveTo(170, 80);
context.bezierCurveTo(130, 100, 130, 150, 230, 150);
context.bezierCurveTo(250, 180, 320, 180, 340, 150);
context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.fill();
context.restore();
I don't know if there's a quick way of doing it. My attempt works like this:
you check each point on the x axis and compare it to the other points, if it is the most left or the most right store their position in a variable, otherwise do nothing. Once you have those points you know the width of the whole path and you can calculate an offset value to place it inside the center (because you know the canvas width). Then just add that offset value to the points coordinates and you're good:
http://jsfiddle.net/jonigiuro/8jsw9/4/
var canvas = document.getElementById("c"); var context = canvas.getContext("2d");
canvas.width = $(window).width(); canvas.height = $(window).height();
var centerX = canvas.width / 2; var centerY = canvas.height / 2;
var bezierSteps = [
[130, 100, 130, 150, 230, 150],
[250, 180, 320, 180, 340, 150],
[420, 150, 420, 120, 390, 100]
];
var mostLeft = 2000; var mostRight = 0;
findCenter();
function findCenter() {
for (var i = 0; i < bezierSteps.length; i++) {
for (var p = 0; p < bezierSteps.length; p+=2) {
mostLeft = bezierSteps[i][p] < mostLeft ? bezierSteps[i][p] : mostLeft;
mostRight = bezierSteps[i][p] > mostRight ? bezierSteps[i][p] : mostRight;
}
}
console.log(mostLeft, mostRight) } var offset = (canvas.width - mostLeft - mostRight) / 2;
console.log(offset)
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.fillStyle = '#333';
context.fillRect(0, 0, canvas.width, canvas.height);
context.closePath();
// custom shape (weird shape lol)
context.beginPath();
context.globalCompositeOperation = 'destination-out';
context.moveTo(170 + offset, 80);
for (var i = 0, l = bezierSteps.length ; i < l ; i++) {
context.bezierCurveTo(bezierSteps[i][0] + offset,bezierSteps[i][1],bezierSteps[i][2] + offset,bezierSteps[i][3],bezierSteps[i][4] + offset,bezierSteps[i][5])
}
//context.bezierCurveTo(130, 100, 130, 150, 230, 150);
//context.bezierCurveTo(250, 180, 320, 180, 340, 150);
//context.bezierCurveTo(420, 150, 420, 120, 390, 100);
context.fill();
context.closePath();
context.globalCompositeOperation = 'source-over';
}
draw();
sorry for the dirty code..
Hi I've found some code that animates 3d shapes and even gives an example of making and animating an icosahedron I'm trying to turn it in to a decahedron though and my geometry is pretty bad. The code I have for the icosahedron is:
// draw a icosahedron
var tau = 1.6180,
phi = 20.90515745, // (180-138.1896851)/2
rt3 = Math.sqrt(3),
d = sideLen/2,
foldTbl = [ 60, -60, 60, -60,
-60, -60, 60, 60,
60, -60, 60, -60,
-60, -60, 60, 60,
60, -60, 60, -60],
moveTbl = [ 0, 2*d, 0, 2*d,
2*d, 2*d, 0, 0,
0, 2*d, 0, 2*d,
2*d, 2*d, 0, 0,
0, 2*d, 0, 2*d],
triangle = ['M',0,0,0, 'L', d*rt3,d,0, 0,2*d,0, 'z'],
tri,
faces = g.createGroup3D(),
bend = -2*phi,
i;
for (i=0; i<20; i++)
{
// create the next face
tri = g.compileShape3D(triangle, "red", null, 1); // backColor irrelevant
faces.addObj(tri);
faces.translate(0, -moveTbl[i], 0);
faces.rotate(0, 0, 1, foldTbl[i]);
faces.rotate(0, 1, 0, bend);
faces.translate(0, moveTbl[i], 0);
}
return faces;
i'm sure there must be an easy way to make this a decahedron but if anyone has any advice that'd be amazing - thanks!
If you have coordinates for an icosahedron but want to draw a dodecahedron, you can make use of the duality between those two. Take the icosahedron, and put a new vertex in the middle of every one of its triangular faces. Connect two new vertices with an edge if the corresponding faces of the icosahedron had an edge in common. You will obtain a dodecahedron, with one vertex for every face of the icosahedron, and one face for every vertex.