Fix position for tranlate() while rotate() only in p5js? - javascript

Have a class with methods that draws rectangular shapes with random lengths.
However, is unable to only do rotate() on the shapes without translating ( translate() ), which translate will make the shapes draw off the canvas.
So are there anyways to make it so no translation occurs while rotating?
The code:
class rect {
constructor(range) {
this.boundary = 100;
this.x = random(this.boundary, width - this.boundary);
this.y = random(this.boundary, height - this.boundary);
this.xu = this.x + random(50, 200);
this.yu = this.y + random(50, 200);
this.range = range;
this.limit = random(-range, range);
this.rand_color1 = random(255);
this.rand_color2 = random(255);
this.rand_color3 = random(255);
}
custom_shapes() {
// how to make no translations occur while only perform rotation on shapes?
translate(this.x-this.margin,this.y-this.margin);
rotate(30);
fill(this.rand_color1, this.rand_color2, this.rand_color3)
quad(this.x, this.y, this.xu + this.limit, this.y, this.xu, this.yu, this.x, this.yu + this.limit);
}
}

If you mean that your rectangular is going of the screen when rotating, it's rotating around x = 0, y= 0 point, so i guess you could do something like:
push() //push and pop acts as a way to "seperate" any style and translate and so on...
rectMode(CENTER) // basically the middle of the rect = x , y
translate(this.x,this.y) // **OR** translate(this.x - this.rectSizeX / 2, this.y - this.rectSizeY / 2)
//quad() // if you're not using the rectMode()
pop() // also you'll have to fill() and so on in here i believe, not too sure
also if you know it's allways going to be a long or tall square, you can just use rect(x,y,xSize,ySize) // if think it's the size anyways
If you just want to separate translate() in general, just put push() and pop() around it...
Oh yeah and translate() basically just makes whatever x and y you give it into 0,0... Dunno if i said that already i'm just editing this the next day.

Related

How to make a triangle that always faces at the same point?

In a p5/processing project i have been working on, i need to create a line that has a triangle in the middle which always faces one of the connection points of the line.
It is pretty easy to create one that stands still, but my endpoints move around and rotate.
I need to find a way to also rotate the little triangle when the line shifts to this "|" from this "---".
My current code goes like this:
let middleX = (fromX + toX)/2;
let middleY = (fromY + toY)/2;
triangle(middleX,middleY+5,middleX+5,middleY,middleX,middleY-5);
line(fromX , fromY, toX, toY);
As you can anticipate, this doesn't work with rotations.
I need help :).
Thanks for your attention.
You can:
use atan2() to calculate the rotation between the two points,
use push() to isolate the coordinate space (rotate locally without affecting the rest of the sketch (e.g. the line)
simply call rotate(): it takes in an angle in radians which is what atan2() returns
Here's an example based on your snippet:
let fromX = 200;
let fromY = 200;
let toX = 300;
let toY = 100;
let triangleSize = 5;
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
// test: change to position
toX = mouseX;
toY = mouseY;
let middleX = (fromX + toX) / 2;
let middleY = (fromY + toY) / 2;
// calculate the angle between from -> to points
let angle = atan2(toY - fromY, toX - fromX);
// isolate coordinate system (indenting is purely visual, not required)
push();
// move to central position
translate(middleX, middleY);
// rotate from translated position
rotate(angle);
// render triangle
triangle(0, triangleSize, triangleSize, 0, 0, -triangleSize);
pop();
line(fromX, fromY, toX, toY);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
Note that the order of transformations (translation, rotation, scale) is important.
(e.g. if rotate, then translate the triangle will land in a different location)
Also you draw the triangle as pointing to the right by default which aligns nicely with 0 radians rotation.

Set allowed drawing area in JavaScript canvas API

I'm using the JavaScript canvas API for free drawing. I'm stuck at masking the area that is allowed to be drawn on - in my example it should only be the speechbubble area.
I'm using this Vue component: https://github.com/sametaylak/vue-draw/blob/master/src/components/CanvasDraw.vue
draw(event) {
this.drawCursor(event);
if (!this.isDrawing) return;
if (this.tools[this.selectedToolIdx].name === 'Eraser') {
this.canvasContext.globalCompositeOperation = 'destination-out';
} else {
this.canvasContext.globalCompositeOperation = 'source-over';
this.canvasContext.strokeStyle = this.tools[this.selectedToolIdx].color;
}
this.canvasContext.beginPath();
this.canvasContext.moveTo(this.lastX, this.lastY);
this.canvasContext.lineTo(event.offsetX, event.offsetY);
this.canvasContext.stroke();
[this.lastX, this.lastY] = [event.offsetX, event.offsetY];
},
drawCursor(event) {
this.cursorContext.beginPath();
this.cursorContext.ellipse(
event.offsetX, event.offsetY,
this.brushSize, this.brushSize,
Math.PI / 4, 0, 2 * Math.PI
);
this.cursorContext.stroke();
setTimeout(() => {
this.cursorContext.clearRect(0, 0, this.width, this.height);
}, 100);
},
There is a built-in clip() method which sets a path as the clipping region.
var ctx=document.getElementById("cnv").getContext("2d");
ctx.lineWidth=2;
ctx.strokeStyle="red";
ctx.moveTo(0,0);
ctx.lineTo(100,100);
ctx.stroke(); // 1.
ctx.strokeStyle="black";
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(100,10);
ctx.lineTo(100,60);
ctx.lineTo(30,60);
ctx.lineTo(10,80);
ctx.closePath();
ctx.stroke(); // 2.
ctx.clip(); // 3.
ctx.strokeStyle="green";
ctx.beginPath();
ctx.moveTo(0,100);
ctx.lineTo(100,0);
ctx.stroke(); // 4.
<canvas id="cnv"></canvas>
red line is drawn between 0,0 and 100,100, without clipping
bubble is drawn in black
bubble is set as clipping region
green line is drawn between 0,100 and 100,0, and correctly clipped into the bubble.
In practice you may want to have the clipping region one pixel inside the bubble, so a separate path (which is not stroke()-d, just clip()-ped), so drawing can not modify the bubble itself. If you zoom in now as it is, you will see that the green line actually overdraws the inner pixels of the bubble (linewidth is 2 pixels, and the outer one is "unharmed").
Establishing if a given point belongs to polygon's area is a quite tricky and solved problem in Computer Science.
In this concrete scenario, where you have canvas with above image set as background and 500x300 dimension you don't really need to use ray casting algorithm.
You can for example divide speech bubble area to a rectangle and a triangle, and then using event.offsetX, event.offsetY check if any given point lies inside of any of these two figures.
Code example:
isPointInArea(event) {
const x = event.offsetX;
const y = event.offsetY;
// For rectangle it is straightforward
if (x >= 60 && x <= 325 && y >= 60 && y <= 215) {
return true;
}
/* Since two sides of this triangle are parallel to canvas
It is enough to check y coordinate with one linear function of a third one
in form of y = ax + b */
if(x >= 60 && x <= 120 && y >= 215) {
const boundaryY = -0.81818181818 * x + 313.181818182;
if (y <= boundaryY) {
return true;
}
}
return false;
}
In draw function of CanvasDraw.vue
draw(event) {
if(!this.isPointInArea(event)) {
return;
}
this.drawCursor(event);
if (!this.isDrawing) return;
...
Working example on codesandbox
Result:
Edit:
As pointed out by #tevemadar you can also use simply clip() method of Canvas API. If there is nothing else you want to render (And since it is not a game, so probably that is the case), then you can just execute clip() once and you are all set. Otherwise remember to use save() method (and then of course restore(), so that you can render stuff also outside of speech bubble clipping region.

Can I optimize this canvas draw call that takes up 30% of my memory profiling?

My game has many Laser objects. mx & my represent velocity. I use the following code to draw a line from behind the Laser 2 pixels to ahead of the Laser in the direction it's going 2 pixels.
Removing the first line of the function adjusted the % of the Profiling by ~1% but I don't like the way it looks. I think I could optimize the drawing by sorting by Linewidth but that doesn't appear to get me much.
How else could I optimize this?
Laser.prototype.draw = function(client, context) {
context.lineWidth = Laser.lineWidth;
context.beginPath();
context.moveTo(this.x - this.mx * 2, this.y - this.my * 2);
context.lineTo(this.x + this.mx * 2, this.y + this.my * 2);
context.strokeStyle = this.teamColor;
context.closePath();
context.stroke();
}
Instead of multiplying things by two, why not add them?
E.g.
context.moveTo(this.x - this.mx - this.mx, this.y - this.my - this.my);
context.lineTo(this.x + this.mx + this.mx, this.y + this.my - this.my);
Testing shows that addition is an order of magnitude faster on an imac over multiplication
https://jsfiddle.net/1c85r2pq/
Dont use moveTo or lineTo as they do not use the hardware to render and are very slow. Also your code is drawing the line twice
ctx.beginPath(); // starts a new path
ctx.moveTo(x,y); // sets the start point of a line
ctx.lineTo(xx,yy); // add a line from x,y to xx,yy
// Not needed
ctx.closePath(); // This is not like beginPath
// it is like lineTo and tells the context
// to add a line from the last point xx,yy
// back to the last moveTo which is x,y
This would half the already slow render time.
A quick way to draw lines using bitmaps.
First at the start create an image to hold the bitmap used to draw the line
function createLineSprite(col,width){
var lineSprite = document.createElement("canvas");
var lineSprite.width = 2;
var lineSprite.height = width;
lineSprite.ctx = lineSprite.getContext("2d");
lineSprite.ctx.fillStyle = col;
lineSprite.ctx.fillRect(0,0,2,width);
return lineSprite;
}
var line = createLineSprite("red",4); // create a 4 pixel wide red line sprite
Or you can use an image that you load.
To draw a line you just need to create a transform that points in the direction of the line, and draw that sprite the length of the line.
// draw a line with sprite from x,y,xx,yy
var drawLineSprite = function(sprite,x,y,xx,yy){
var nx = xx-x; // get the vector between the points
var ny = yy-y;
if(nx === 0 && ny === 0){ // nothing to draw
return;
}
var d = Math.hypot(nx,ny); // get the distance. Note IE does not have hypot Edge does
// normalise the vector
nx /= d;
ny /= d;
ctx.setTransform(nx,ny,-ny,nx,x,y); // create the transform with x axis
// along the line and origin at line start x,y
ctx.drawImage(sprite, 0, 0, sprite.width, sprite.height, 0, -sprite.height / 2, d, sprite.height);
}
To draw the line
drawSpriteLine(line,0,0,100,100);
When you are done drawing all the lines you can get the default transform back with
ctx.setTransform(1,0,0,1,0,0);
The sprite can be anything, this allows for very detailed lines and great for game lasers and the like.
If you have many different colours to draw then create one sprite (image) that has many colour on it, then in the line draw function simply draw only the part of the sprite that has the colour you want. You can stretch out a single pixel to any size so you can get many colours on a small bitmap.

Zoom in in a canvas at a certain point

I try to let the user zoom in the canvas with a pinch gesture, it's a Javascript Canvas Game (using Intel XDK)
I got the point coordinates (relativley to the window document, saved in an array) and the scale "strength".
var scale = 1;
function scaleCanvas(sc, point) { //point["x"] == 200
//sc has value like 0.5, 1, 1.5 and so on
x = sc/scale;
scale = sc;
ctx.scale(x, x);
}
I know that I have to translate the canvas to the point coordinates, and then retranslate it again. My problem is, that the canvas is already translated. The translation values are saved in the vars dragOffX and dragOffY. Furthermore, the initial translation may be easy, but when the canvas is already scaled, every coordinate is changed.
This is the translation of the canvas when dragging/shifting the content:
var dragOffX = 0;
var dragOffY = 0;
function dragCanvas(x,y) {
dragOffX = dragOffX + x;
dragOffY = dragOffY + y;
x = x* 1/scale;
y = y* 1/scale;
ctx.translate(x,y);
}
So when the player is dragging the content for e.g. 100px to the right, dragOffX gets the value 100.
How do I translate my canvas to the correct coordinates?
It will probably be easier if you store the transformation matrix and use setTransform each time you change it - that resets the canvas transformation matrix first before applying the new transformation, so that you have easier control over the way that the different transformations accumulate.
var transform = {x: 0, y: 0, scale: 1}
function scaleCanvas(scale, point) {
var oldScale = transform.scale;
transform.scale = scale / transform.scale;
// Re-centre the canvas around the zoom point
// (This may need some adjustment to re-centre correctly)
transform.x += point.x / transform.scale - point.x / oldScale
transform.y += point.y / transform.scale - point.y / oldScale;
setTransform();
}
function dragCanvas(x,y) {
transform.x += x / transform.scale;
transform.y += y / transform.scale;
setTransform();
}
function setTransform() {
ctx.setTransform(transform.scale, 0, 0, transform.scale, transform.x, transform.y);
}
JSFiddle
Simply Use this to scale canvas on pivot point
function scaleCanvasOnPivotPoint(s, p_x , p_y) {
ctx.translate(p_x, p_y);
ctx.scale(s);
ctx.translate( -p_x, -p_y);
}

Get coordinates of two different places on HTML canvas

Aim of my code:
Draw a small rectangle on a HTML canvas whenever a user clicks the canvas. The rectangle should have a small number representing the number of rectangles made by the user.
The user should be able to connect any two rectangles using a straight line. (Preferably by just pressing left mouse button, and taking the mouse from first rectangle to second rectangle)
Approach and my attempt
As you can see in this jsFiddle , I have been able to achieve the first part of above very well. On clicking on the canvas, a rectangle with a number inside of it is made. But I am really clueless about the second part.
How do I make the user connect any two made rectangles? I want the connection to be made only if a rectangle is there ( So I would need to store coordinates of every rectangle that has been made, that's okay as I can use an array for that ).
Basically, I just want to check if the mousedown was at one place and mouseup at the other.
How do I get these two different coordinates ( one of mousedown and other of mouseup ) , and draw a line between them?
I have given the Fiddle above but still here's my jquery:
$(function () {
var x, y;
var globalCounter = 0;
$('#mycanvas').on("click", function (event) {
x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
x -= mycanvas.offsetLeft;
y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
y -= mycanvas.offsetLeft;
// alert("x:"+x+"y: "+y);
drawRectangle(x, y);
});
function drawRectangle(x, y) {
var acanvas = document.getElementById("mycanvas");
var context = acanvas.getContext("2d");
context.strokeRect(x, y, 25, 25);
globalCounter++;
writeNo(x, y, globalCounter);
}
function writeNo(x, y, n) {
var acanvas = document.getElementById("mycanvas");
var context = acanvas.getContext("2d");
context.font = "bold 14px sans-serif";
context.fillText(n, x + 8, y + 12.5);
}
});
The main question is therefore: connecting the two made rectangles by mousedrag
How do I achieve this?
Thank You.
How about this: http://jsfiddle.net/4jqptynt/4/
Ok, first I did a little refactoring for your code to make things easier. Just stuff like putting the code that gets the canvas coordinates into it's own function, and caching some variables (like the canvas context) in the outer function's scope. Oh, and defining your rectangle dimensions as constants because we'll be using the same numbers in a couple of different places.
As you said, the first thing we need is to keep track of the existing rectangles using an array rects (easy enough to do within drawRectangle). Then we need a function to check if a particular pair of coordinates are within some rectangle:
function inRectangle(x, y) {
for (var i = 0, l = rects.length; i < l; i++) {
if ((x - rects[i].x) <= RECT_X && (y - rects[i].y) <= RECT_Y &&
(x - rects[i].x) >= 0 && (y - rects[i].y) >= 0) {
return i;
}
}
}
where RECT_X & RECT_Y define the sides of the rectangle. If the coordinates do exist within some rectangle then this will return the index of that rectangle within the rects array.
Then it's a case of checking whether or not a mousedown occurred within a rectangle, noting that inRectangle will only return a number if the mousedown event was within a rectangle:
$acanvas.on("mousedown", function (event) {
var coords = getCoords(event),
rect = inRectangle(coords.x, coords.y);
if (typeof rect === "number") {
dragStart = rect + 1;
} else {
drawRectangle(coords.x, coords.y);
}
});
if so, make a note of which rectangle using dragStart, if not draw a rectangle as before.
Then to complete the drag, we need to attach a handler to mouseup:
$acanvas.on("mouseup", function (event) {
if (!dragStart) { return; }
var coords = getCoords(event),
rect = inRectangle(coords.x, coords.y);
if (typeof rect === "number") {
drawConnection(dragStart - 1, rect);
}
dragStart = 0;
});
If no drag was started, then it does nothing. If it's coordinates aren't within a rectangle, then it does nothing but reset dragStart. If however, it is within a rectangle, then it draws a connecting line:
function drawConnection(rect1, rect2) {
context.strokeStyle = "black";
context.lineWidth = 1;
context.beginPath();
context.moveTo(rects[rect1].x + RECT_X/2, rects[rect1].y + RECT_Y/2);
context.lineTo(rects[rect2].x + RECT_X/2, rects[rect2].y + RECT_Y/2);
context.stroke();
context.closePath();
}

Categories

Resources