Resize rectangle in Paper.js - javascript

I have a very ordinary rectangle created in Paper.js and I'd like to resize it, but I can't find any obvious ways to do it.
var rect = new Rectangle([0, 0],[width,height]);
rect.center = mousePoint;
var path = new Path.Rectangle(rect, 4);
path.fillColor = fillColor;
path.meta = fillColor;
There's a scale transformation method, but it's not really for mouse interaction and my goal is to create a handle that can resize a component.

Note that PaperJS has three different kinds of Rectangles:
Rectangle — This is the basic type (data structure) that defines a rectangle. Basically, top-left point, width, and height. (Nothing is displayed on the screen.) This kind of rectangle can be resized by setting its size property, for instance:
let rect;
const originalSize = [50, 50];
const newSize = [100, 100];
rect = new Rectangle([10, 50], originalSize);
rect.size = newSize;
Path.Rectangle — This is a method for generating a list of Segments that make up a rectangular-shaped Path. This does get displayed, but a Path lacks methods associated with a rectangle. For instance, a Path.Rectangle has no size property (so trying to modify it has no effect). To resize a Path you can use the scale() method as another answer proposes, or modify its Segments:
rect = new Path.Rectangle([210, 50], originalSize);
rect.strokeColor = "red";
rect.strokeWidth = 3;
rect.segments[0].point = rect.segments[0].point.add([-25, 25]); // lower left point
rect.segments[1].point = rect.segments[1].point.add([-25, -25]); // upper left point
rect.segments[2].point = rect.segments[2].point.add([25, -25]); // upper right point
rect.segments[3].point = rect.segments[3].point.add([25, 25]); // lower right point
Shape.Rectangle — This kind of rectangle gets displayed and exposes properties about its shape, such as size. To resize a Shape.Rectangle you can modify its size property directly:
rect = new Shape.Rectangle([410, 50], originalSize)
rect.strokeColor = "blue"
rect.strokeWidth = 3
rect.size = newSize
Most likely, if you want to draw a rectangle and modify its properties after the fact, the rectangle you are looking for is Shape.Rectangle.
Here is a Sketch that lets you play around with the different kinds of rectangles.

You can calculate the scaling by dividing the intended width/height of your rectangle with the current width/height of your rectangle.
Then you can use that scaling 'coefficient' to apply the scaling.
Based on your code above, you can get the current width/height of your rectangle by using: rect.bounds.width and rect.bounds.height
Here's a function you can use
var rectangle = new Shape.Rectangle({
from: [0, 0],
to: [100, 50],
fillColor: 'red'
});
function resizeDimensions(elem,width,height){
//calc scale coefficients and store current position
var scaleX = width/elem.bounds.width;
var scaleY = height/elem.bounds.height;
var prevPos = new Point(elem.bounds.x,elem.bounds.y);
//apply calc scaling
elem.scale(scaleX,scaleY);
//reposition the elem to previous pos(scaling moves the elem so we reset it's position);
var newPos = prevPos + new Point(elem.bounds.width/2,elem.bounds.height/2);
elem.position = newPos;
}
resizeDimensions(rectangle,300,200)
And here's the Sketch for it.
Be aware that the above function will also reposition the element at it's previous position but it will use top-left positioning. Paper.js uses the element's center to position them so I'm clarifying this so it doesn't cause confusion

Related

EaselJS: Stretching Shape to a Specific Side

By default all shapes stretch symmetrically from the center of the shape. Is it possible to stretch a shape to specific side?
Things "stretch" from their origin point. If you draw from the center, and then scale, then it will appear to scale from the center.
Draw rectangle from center
var r1 = new createjs.Shape();
r1.graphics.beginStroke("red").drawRect(-100,-100,200,200);
Draw rectangle from left
var r2 = new createjs.Shape();
r2.graphics.beginStroke("red").drawRect(0,0,200,200);
Here is a fiddle
https://jsfiddle.net/owx26481/
Alternately you can change the registration point, which basically offsets where the object is drawn from, and has the same effect:
var r1 = new createjs.Shape();
r1.graphics.beginStroke("red").drawRect(0,0,200,200);
var r2 = new createjs.Shape();
r2.graphics.beginStroke("red").drawRect(0,0,200,200); // SAME
r2.regX = r2.regY = 100; // Change registration point to the center (50%)
Here is an updated fiddle: https://jsfiddle.net/owx26481/2
I hope that makes sense!

Using easelJS to free transform shapes on pressmove

I want to replicate the basic functionality of a free transform tool (no rotation), by dragging on the border of a easeljs Shape and adjusting the container to match it. I'm currently using the scaleX and scaleY properties and it sort of works but is not quite right.
If you do one scaling transformation it works pretty well. However if you release, then do another scaling transformation, it jumps very glitchily, and can occasionally break sending the x/y coordinates all the way to stage 0. Any help on this issue would be great!
http://jsfiddle.net/frozensoviet/dsczvrpw/13/
//circle
var circle = new createjs.Shape(new createjs.Graphics()
.beginFill("#b2ffb2")
.drawCircle(0, 0, 50));
circle.setBounds(0, 0, 50, 50);
//create the border as a seperate object
var cBorder = new createjs.Shape(new createjs.Graphics().setStrokeStyle(10)
.beginStroke("#000").drawCircle(0, 0, 50));
cBorder.setBounds(0, 0, 50, 50);
//add both to the container
circleContainer.addChild(circle);
circleContainer.addChild(cBorder);
var cWidth = circleContainer.getBounds().width;
var cHeight = circleContainer.getBounds().height;
//find initial mouse position relative to circle center
cBorder.on("mousedown", function (evt) {
//initial mouse pos
this.initial = {
x: Math.abs(-(circleContainer.x - evt.stageX)),
y: Math.abs(circleContainer.y - evt.stageY)
};
});
//set the relevant circle axis scale to ratio of mouse pos/initial mouse pos
cBorder.on("pressmove", function (evt) {
//current moouse pos
this.offset = {
x: Math.abs(-(circleContainer.x - evt.stageX)),
y: Math.abs(circleContainer.y - evt.stageY)
};
if (this.initial.x > this.initial.y) {
//sides
circleContainer.scaleX = this.offset.x / this.initial.x;
} else if (this.initial.x < this.initial.y) {
//top/bottom
circleContainer.scaleY = this.offset.y / this.initial.y;
} else {
//diagonals
circleContainer.scaleX = this.offset.x / this.initial.x;
circleContainer.scaleY = this.offset.y / this.initial.y;
}
stage.update();
});
The issue is your initial calculations don't account for the change in the scale of the circle. You would have to transform the coordinates using localToGlobal. Fortunately, there is an even easier way:
this.initial = {
x: Math.abs(evt.localX),
y: Math.abs(evt.localY)
};
You can also turn on ignoreScale on the border, which makes it not stretch:
createjs.Graphics().setStrokeStyle(10,null,null,null,true) // The 5th argument
Lastly, your bounds setting might work for your demo, but it is not correct. Your circle draws from the center, so it should be:
cBorder.setBounds(-25, -25, 50, 50);
Here is an updated fiddle: http://jsfiddle.net/tfy1sjnj/3/

Creating a scratch card in EaselJS

I've been trying to develop a scratch card in EaselJS.
So far, I've managed to get a Shape instance above a Bitmap one and enabled erasing it with click and drag events, so the image below becomes visible.
I've used the updateCache() with the compositeOperation approach and it was easy enough, but here is my issue:
How can I find out how much the user has already erased from the Shape instance, so I can setup a callback function when, say, 90% of the image below is visible?
Here is a functioning example of what I'm pursuing: http://codecanyon.net/item/html5-scratch-card/full_screen_preview/8721110?ref=jqueryrain&ref=jqueryrain&clickthrough_id=471288428&redirect_back=true
This is my code so far:
function Lottery(stageId) {
this.Stage_constructor(stageId);
var self = this;
var isDrawing = false;
var x, y;
this.autoClear = true;
this.enableMouseOver();
self.on("stagemousedown", startDrawing);
self.on("stagemouseup", stopDrawing);
self.on("stagemousemove", draw);
var rectWidth = self.canvas.width;
var rectHeight = self.canvas.height;
// Image
var background = new createjs.Bitmap("http://www.taxjusticeblog.org/lottery.jpg");
self.addChild(background);
// Layer above image
var overlay = new createjs.Shape();
overlay.graphics
.f("#55BB55")
.r(0, 0, rectWidth, rectHeight);
self.addChild(overlay);
overlay.cache(0, 0, self.canvas.width, self.canvas.height);
// Cursor
self.brush = new createjs.Shape();
self.brush.graphics
.f("#DD1111")
.dc(0, 0, 5);
self.brush.cache(-10, -10, 25, 25);
self.cursor = "none";
self.addChild(self.brush);
function startDrawing(evt) {
x = evt.stageX-0.001;
y = evt.stageY-0.001;
isDrawing = true;
draw(evt);
};
function stopDrawing() {
isDrawing = false;
};
function draw(evt) {
self.brush.x = self.mouseX;
self.brush.y = self.mouseY;
if (!isDrawing) {
self.update();
return;
}
overlay.graphics.clear();
// Eraser line
overlay.graphics
.ss(15, 1)
.s("rgba(30,30,30,1)")
.mt(x, y)
.lt(evt.stageX, evt.stageY);
overlay.updateCache("destination-out");
x = evt.stageX;
y = evt.stageY;
self.update();
$rootScope.$broadcast("LotteryChangeEvent");
};
}
Any ideas?
That's a tricky one, regardless of the language. The naive solution would simply be to track the length of the paths the user "draws" within the active area, and then reveal when they scratch long enough. That's obviously not very accurate, but is fairly simple and might be good enough.
The more accurate approach would be to get the pixel data of the cacheCanvas, then check the alpha value of each pixel to get an idea of how many pixels are transparent (have low alpha). You could optimize this significantly by only checking every N pixel (ex. every 5th pixel in every 5th row would run 25X faster).

How to create overlapping - not stacking - shapes with canvas?

I'm trying to create an array of shapes that overlap. But I'm having difficulty preventing those shapes stacking on top of one-another.
I guess I want them to mesh together, if that makes sense?
Here's the code:
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x,y);
for (j=0;j<rectQTY;j++){ // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
// Degrees to rotate for next position
overlap_context.rotate((Math.PI/180)*360/rectQTY);
}
And here's my jsFiddle:
http://jsfiddle.net/Q8yjP/
And here's what I'm trying to achieve:
Any help or guidance would be greatly appreciated!
You cannot specify this behavior but you can implement an algorithmic-ish approach that uses composite modes.
As shown in this demo the result will be like this:
Define line width and the rectangles you want to draw (you can fill this array with the loop you already got to calculate the positions/angles - for simplicity I just use hard-coded ones here):
var lw = 4,
rects = [
[20, 15, 200, 75],
[150, 20, 75, 200],
[20, 150, 200, 75],
[15, 20, 75, 200]
], ...
I'll explain the line width below.
/// set line-width to half the size
ctx.lineWidth = lw * 0.5;
In the loop you add one criteria for the first draw which is also where you change composite mode. We also clear the canvas with the last rectangle:
/// loop through the array with rectangles
for(;r = rects[i]; i++) {
ctx.beginPath();
ctx.rect(r[0], r[1], r[2], r[3]);
ctx.fill();
ctx.stroke();
/// if first we do a clear with last rectangle and
/// then change composite mode and line width
if (i === 0) {
r = rects[rects.length - 1];
ctx.clearRect(r[0] - lw * 0.5, r[1] - lw * 0.5, r[2] + lw, r[3] + lw);
ctx.lineWidth = lw;
ctx.globalCompositeOperation = 'destination-over';
}
}
This will draw the rectangles and you have the flexibility to change the sizes without needing to recalculate clipping.
The line-width is set separately as stroke strokes the line from the middle. Therefor, since we later use destination-over mode it means half of the line won't be visible as we first fill which becomes part of destination so that the stroke will only be able to fill outside the stroked area (you could reverse the order of stroke and fill but will always run into an adjustment for the first rectangle).
We also need it to calculate the clipping which must include (half) the line on the outside.
This is also why we initially set it to half as the whole line will be drawn the first time - otherwise the first rectangle will have double as thick borders.
The only way to do it to cut your rectangles and compute which sub rectangle goes over which one. But I think you will have to draw your borders and inner rectangles separately because separating rectangles will add additional borders.
Hope it helped
Sadly, the feature you want of setting z-indexes on part of an element using canvas is not available currently. If you just need it for the four rectangle object you could do something like this which hides part of the rectangle to fake the effect you want, however this is hard coded to only 4 rectangles.
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x, y);
for (j = 0; j < rectQTY; j++) { // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
if (j === 3) {
overlap_context.beginPath();
overlap_context.rect(24, -86, 72, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20, -89.5);
overlap_context.lineTo(100, -89.5);
overlap_context.stroke();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20.5, -93.1);
overlap_context.lineTo(20.5, 23);
overlap_context.stroke();
overlap_context.closePath();
}
// Degrees to rotate for next position
overlap_context.rotate((Math.PI / 180) * 360 / rectQTY);
}
Demo here
If you have to make it dynamic, you could cut the shapes like Dark Duck suggested or you could try to create a function that detects when an object is overlapped and redraw it one time per rectangle (hard to do and not sure if it'd work). Perhaps you could come up with some equation for positioning the elements in relation to how I have them hard coded now to always work depending on the rotation angle, this would be your best bet IMO, but I don't know how to make that happen exactly
Overall you can't really do what you're looking for at this point in time
Using pure JavaScript ...
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas id="mycanvas" width="400px" height="400px"></canvas>
<script>
window.onload = function(){
var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
//cheat - use a hidden canvas
var hidden = document.createElement('canvas');
hidden.width = 400;
hidden.height = 400;
var hiddenCtx = hidden.getContext('2d');
hiddenCtx.strokeStyle = 'blue';
hiddenCtx.fillStyle = 'yellow';
hiddenCtx.lineWidth = 5;
//translate origin to centre of hidden canvas, and draw 3/4 of the image
hiddenCtx.translate(200,200);
for(var i=0; i<3; i++){
hiddenCtx.fillRect(-170, -150, 300, 120);
hiddenCtx.strokeRect(-170, -150, 300, 120);
hiddenCtx.rotate(90*(Math.PI/180));
}
//reset the hidden canvas to original status
hiddenCtx.rotate(90*(Math.PI/180));
hiddenCtx.translate(-200,-200);
//translate to middle of visible canvas
ctx.translate(200, 200);
//repeat trick, this time copying from hidden to visible canvas
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
ctx.rotate(180*(Math.PI/180));
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
};
</script>
</body>
</html>
Demo on jsFiddle

WebGL: how to prevent camera bounce when changing center of rotation

I've hit a mental block of sorts, and was looking for some advice or suggestions. My problem is this:
I have a WebGL scene (I'm not using a 3rd party library, except gl-matrix), in which the user can rotate the camera up/down and left/right (rotate around X/Y axis). They can also rotate the model as well (yaw/pitch).
To see the problem, imagine the model has two blocks, A and B in the scene, with A at the center and B to the right (in the viewport), and the rotation center in the center of A. If the user rotates the model, it rotates about the center of block A. But if the user clicks on object B, I need to be able to change the center of rotation to B's center, but still maintain the current camera orientation. Currently, when the center of rotation switches to B, block B moves to the center of the screen, and block A moves to the left. Basically, the code always centers on the current center or rotation.
I use the following code for the modelview matrix update:
var mvMatrix = this.mvMatrix;
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, mvMatrix, this.orbit);
mat4.rotateY(mvMatrix, mvMatrix, this.orbitYaw);
mat4.rotateX(mvMatrix, mvMatrix, this.orbitPitch);
mat4.translate(mvMatrix, mvMatrix, this.eye);
mat4.rotateY(mvMatrix, mvMatrix, this.eyeYaw);
mat4.rotateX(mvMatrix, mvMatrix, this.eyePitch);
I'm trying to figure out what the right yaw and pitch values for orbit and eye I should use in order to move back the current location and to achieve the present camera/eye orientation to avoid the "bounce" from one object to another as the rotation center moves.
I've searched a lot and can't seem to find how best to do this (my current attempt(s) have issues). Any sample code, or just good descriptions would be appreciated.
Edit
I followed gman's advice and tried the following code, but switching orbits just jumped around. My model is composed of multiple objects, and the orbit center can change, but after changing orbits, the orientation of the camera needs to remain steady, which is why I have to calculate the correction to the orbit yaw/pitch and eye yaw/pitch to put the eye back in the same spot and pointing in the same direction after changing orbits. BTW, I only have one orbit yaw and pitch, based on where the current orbit is, so that's a little different from gman's sample:
Camera.prototype.changeOrbit = function (newOrbit) {
var matA = mat4.create();
var matB = mat4.create();
mat4.translate(matA, matA, this.orbit);
mat4.rotateY(matA, matA, this.orbitYaw);
mat4.rotateX(matA, matA, this.orbitPitch);
mat4.translate(matB, matB, newOrbit);
mat4.rotateY(matB, matB, this.orbitYaw);
mat4.rotateX(matB, matB, this.orbitPitch);
var matInverseNewOrbit = mat4.create();
var matNewOrbitToCamera = mat4.create();
mat4.invert(matInverseNewOrbit, matB);
mat4.multiply(matNewOrbitToCamera, matInverseNewOrbit, matA);
var m = matNewOrbitToCamera;
this.eye[0] = m[12];
this.eye[1] = m[13];
this.eye[2] = m[14];
this.eyePitch = ExtractPitch(m);
this.eyeYaw = ExtractYaw(m);
this.update();
};
ExtractPitch and ExtractYaw work as gman had specified, but I do rotate around different axes since pitch is normally defined around the Y axis, and so on. Thanks for the suggestions, though.
I'm not sure I can explain this but basically:
When switching from A to B, at switch time,
Compute the matrix for the camera going around A (the code you have above). (camera)
Compute the matrix for B (matB)
Compute the inverse of the matrix for B. (inverseMatB)
Multiply camera by inverseMatB. (matBtoCamera)
You now have a matrix that goes from B to the camera.
Decompose this matrix (matBToCamera) back into translation and rotation.
Unfortunately I don't know of a good decompose matrix function to point you at. I haven't needed one in a long time. Translation is basically elements 12, 13, 14 of your matrix. (Assuming you are using 16 element matrices which I think is what glMatrix uses).
var translation = [m[12], m[13], m[14]];
For rotation the upper/left 3x3 part of the matrix represents rotation. As long as there is no scaling or skewing involved, according to this page (http://nghiaho.com/?page_id=846) it's
var rotXInRadians = Math.atan2(m[9], m[10]);
var rotYInRadians = Math.atan2(-m[8], Math.sqrt(m[9] * m[9] + m[10] * m[10]));
var rotZInRadians = Math.atan2(m[4], m[0]);
Here's an example
http://jsfiddle.net/greggman/q7Bsy/
I'll paste the code here specific to glMatrix
// first let's make 3 nodes, 'a', 'b', and 'camera
var degToRad = function(v) {
return v * Math.PI / 180;
}
var a = {
name: "a",
translation: [0, -50, -75],
pitch: 0,
yaw: degToRad(30),
};
var b = {
name: "b",
translation: [0, 100, 50],
pitch: 0,
yaw: degToRad(-75),
}
var camera = {
name: "cam",
translation: [0, 15, 10],
pitch: 0,
yaw: degToRad(16),
parent: a,
};
Here's the code that computes the matrix of each
var matA = mat4.create();
mat4.identity(matA);
mat4.translate(matA, matA, a.translation);
mat4.rotateY(matA, matA, a.pitch);
mat4.rotateX(matA, matA, a.yaw);
a.mat = matA;
var matB = mat4.create();
mat4.identity(matB);
mat4.translate(matB, matB, b.translation);
mat4.rotateY(matB, matB, b.pitch);
mat4.rotateX(matB, matB, b.yaw);
b.mat = matB;
var matCamera = mat4.create();
mat4.identity(matCamera);
var parent = camera.parent;
mat4.translate(matCamera, matCamera, parent.translation);
mat4.rotateY(matCamera, matCamera, parent.pitch);
mat4.rotateX(matCamera, matCamera, parent.yaw);
mat4.translate(matCamera, matCamera, camera.translation);
mat4.rotateY(matCamera, matCamera, camera.pitch);
mat4.rotateX(matCamera, matCamera, camera.yaw);
camera.mat = matCamera;
and here's the code that swaps cameras
// Note: Assumes matrices on objects are updated.
var reparentObject = function(obj, newParent) {
var matInverseNewParent = mat4.create();
var matNewParentToObject = mat4.create();
mat4.invert(matInverseNewParent, newParent.mat);
mat4.multiply(matNewParentToObject, matInverseNewParent, obj.mat);
var m = matNewParentToObject;
obj.translation[0] = m[12];
obj.translation[1] = m[13];
obj.translation[2] = m[14];
var rotXInRadians = Math.atan2(m[9], m[10]);
var rotYInRadians = Math.atan2(-m[8], Math.sqrt(m[9] * m[9] + m[10] * m[10]));
var rotZInRadians = Math.atan2(m[4], m[0]);
obj.pitch = rotYInRadians;
obj.yaw = rotXInRadians;
obj.parent = newParent;
};
var newParent = camera.parent == a ? b : a;
reparentObject(camera, newParent);

Categories