I am making a polygon from lines.
x = options.e.pageX - offset.left;
y = options.e.pageY - offset.top;
On click, I capture mouse position. After, I add that point to array of points.
roofPoints.push(new Point(x, y));
function Point(x, y) {
this.x = x;
this.y = y;
}
When all points are set I do this:
var roof = new fabric.Polyline(roofPoints, {
fill : 'purple',
});
The problem with this is that while I set points I am drawing lines for the polygon like so:
var points = [ x, y, x, y ];
lines.push(new fabric.Line(points, {
strokeWidth : 1,
selectable : false,
stroke : 'red'
}).setOriginX(x).setOriginY(y));
So basically I'm making a counter of a polygon and when I'm done drawing the counter, I just create a polygon. But the problem is that when I create polygon it doesn't fit in the counter it just moves away from the counter. I have tried to find how to correctly offset it.
roof.set({
left : left,
top : top,
});
I was trying to get left upper point of a bounding rectangle for the polygon and to set it so it places correctly. But that din't work. This how it looks
And a fiddle How it looks
Ok so I checked coords of created polygon and saw that it has recalculated them somehow and that's why it was offset. For example:
PointBefore
58 | 193
PointAfter
-189 | -52
This is how it changes, so to make the correct offset all I did was get it before I create the polygon and set top and left after it has been created.
var left = findLeftPaddingForRoof(roofPoints);
var top = findTopPaddingForRoof(roofPoints);
var roof = new fabric.Polyline(roofPoints, {
fill: 'purple'
});
roof.set({
left: left,
top: top
});
return roof;
And a fiddle that works.
This script doesn't work on 1.4.13 and 1.5.0 fabricjs versions. Cannot confirm on previus versions.
EDIT: Fixed the problem, also in later versions:
Change this:
var points = [ x, y, x, y ];
lines.push(new fabric.Line(points, {
strokeWidth : 1,
selectable : false,
stroke : 'red'
}).setOriginX(x).setOriginY(y));
into this:
var points = [ x, y, x, y ];
lines.push(new fabric.Line(points, {
strokeWidth : 1,
selectable : false,
stroke : 'red'
}));
Related
After saw this question many times and replied with an old (an not usable) code I decide to redo everything and post about it.
Rectangles are defined by:
center : x and y for his position (remember that 0;0 is TOP Left, so Y go down)
size: x and y for his size
angle for his rotation (in deg, 0 deg is following axis OX and turn clockwise)
The goal is to know if 2 rectangles are colliding or not.
Will use Javascript in order to demo this (and also provide code) but I can be done on every language following the process.
Links
Final Demo on Codepen
GitHub repository
Concept
In order to achieve this we'll use corners projections on the other rectangle 2 axis (X and Y).
The 2 rectangles are only colliding when the 4 projections on one rectangles hit the others:
Rect Blue corners on Rect Orange X axis
Rect Blue corners on Rect Orange Y axis
Rect Orange corners on Rect Blue X axis
Rect Orange corners on Rect Blue Y axis
Process
1- Find the rects axis
Start by creating 2 vectors for axis 0;0 (center of rect) to X (OX) and Y (OY) then rotate both of them in order to get aligned to rectangles axis.
Wikipedia about rotate a 2D vector
const getAxis = (rect) => {
const OX = new Vector({x:1, y:0});
const OY = new Vector({x:0, y:1});
// Do not forget to transform degree to radian
const RX = OX.Rotate(rect.angle * Math.PI / 180);
const RY = OY.Rotate(rect.angle * Math.PI / 180);
return [
new Line({...rect.center, dx: RX.x, dy: RX.y}),
new Line({...rect.center, dx: RY.x, dy: RY.y}),
];
}
Where Vector is a simple x,y object
class Vector {
constructor({x=0,y=0}={}) {
this.x = x;
this.y = y;
}
Rotate(theta) {
return new Vector({
x: this.x * Math.cos(theta) - this.y * Math.sin(theta),
y: this.x * Math.sin(theta) + this.y * Math.cos(theta),
});
}
}
And Line represent a slop using 2 vectors:
origin: Vector for Start position
direction: Vector for unit direction
class Line {
constructor({x=0,y=0, dx=0, dy=0}) {
this.origin = new Vector({x,y});
this.direction = new Vector({x:dx,y:dy});
}
}
Step Result
2- Use Rect Axis to get corners
First want extend our axis (we are 1px unit size) in order to get the half of width (for X) and height (for Y) in order to be able by adding when (and inverse) to get all corners.
const getCorners = (rect) => {
const axis = getAxis(rect);
const RX = axis[0].direction.Multiply(rect.w/2);
const RY = axis[1].direction.Multiply(rect.h/2);
return [
rect.center.Add(RX).Add(RY),
rect.center.Add(RX).Add(RY.Multiply(-1)),
rect.center.Add(RX.Multiply(-1)).Add(RY.Multiply(-1)),
rect.center.Add(RX.Multiply(-1)).Add(RY),
]
}
Using this 2 news methods for Vector:
// Add(5)
// Add(Vector)
// Add({x, y})
Add(factor) {
const f = typeof factor === 'object'
? { x:0, y:0, ...factor}
: {x:factor, y:factor}
return new Vector({
x: this.x + f.x,
y: this.y + f.y,
})
}
// Multiply(5)
// Multiply(Vector)
// Multiply({x, y})
Multiply(factor) {
const f = typeof factor === 'object'
? { x:0, y:0, ...factor}
: {x:factor, y:factor}
return new Vector({
x: this.x * f.x,
y: this.y * f.y,
})
}
Step Result
3- Get corners projections
For every corners of a rectangle, get the projection coord on both axis of the other rectangle.
Simply by adding this function to Vector class:
Project(line) {
let dotvalue = line.direction.x * (this.x - line.origin.x)
+ line.direction.y * (this.y - line.origin.y);
return new Vector({
x: line.origin.x + line.direction.x * dotvalue,
y: line.origin.y + line.direction.y * dotvalue,
})
}
(Special thank to Mbo for the solution to get projection.)
Step Result
4- Select externals corners on projections
In order to sort (along the rect axis) all the projected point and take the min and max projected points we can:
Create a vector to represent: Rect Center to Projected corner
Get the distance using the Vector Magnitude function.
get magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
Use the dot product to know if the vector is facing the same direction of axis of inverse (where signed distance" is negative)
getSignedDistance = (rect, line, corner) => {
const projected = corner.Project(line);
const CP = projected.Minus(rect.center);
// Sign: Same directon of axis : true.
const sign = (CP.x * line.direction.x) + (CP.y * line.direction.y) > 0;
const signedDistance = CP.magnitude * (sign ? 1 : -1);
}
Then using a simple loop and test of min/max we can find the 2 externals corners. The segment between them is the projection of a Rect on the other one axis.
Step result
5- Final: Do all projections hit rect ?
Using simple 1D test along the axis we can know if they hit or not:
const isProjectionHit = (minSignedDistance < 0 && maxSignedDistance > 0
|| Math.abs(minSignedDistance) < rectHalfSize
|| Math.abs(maxSignedDistance) < rectHalfSize);
Done
Testing all 4 projections will give you the final result. =] !!
Hope this answer will help as many people as possible. Any comments are appreciated.
I have built a square and a rectangle. I use dojo.declare to overwrite the onMouseMove event, so that the square only is dnd inside the surface.
But i have problem to connect the square with a function.
The rectangle doesn't use the custom mover and is connected to a function. It prints out to the console every time it moves.
What am i doing wrong ?
http://jsfiddle.net/Baboly/jh6dnb49/
HTML:
<body>
<div id='drawing_surface'></div>
</body>
Javascript:
djConfig='parseOnLoad: true'>
dojo.require('dojox.gfx'); // omit this and the rectangle doesn't draw
dojo.require('dojox.gfx.move'); // omit this and the rectangle doesn't move
dojo.require('dojo.parser'); // parser and dojo.css aren't required
dojo.ready(function() {
surface = dojox.gfx.createSurface(dojo.byId('drawing_surface'),'640px', '550px');
// draw rectangle on the surface
// createRect({}) parameters
// x: horizontal offset from left side of surface
// y: vertical offset from top of surface
// height, width: dimensions of rectangle
rectangle = surface.createRect({ x: 20, y: 5, height: 100, width: 150})
.setFill([0,0,255,0.3])
.setStroke({ color: "blue", width: 2})
;
var symbol = surface.createGroup();
square = surface.createRect({ x: 20, y: 10, height: 100, width: 100})
.setFill([0,0,255,0.3])
.setStroke({ color: "blue", width: 2})
;
// define limits to motion - in this case, the entire drawing surface.
var limits = { xmin: 0, xmax: 640, ymin: 0, ymax: 550};
// Extend mover with a class that overwites onMouseMove.
// We will call the class net.aa3sd.surfaceMover to prevent naming
// conflicts with any existing dojo classes
dojo.declare("net.aa3sd.surfaceMover",dojox.gfx.Mover, {
// Mover has several methods, we only need to overwrite onMouseMove.
onMouseMove: function(event) {
// getTransform() will tell us how far the shape has been moved
// from its initial position
transform = this.shape.getTransform();
// If this.shape hasn't been transformed yet
// getTransform() will return null, so we
// need to handle the special case of the first mouse movement.
if (transform==null) {
// We aren't dealing with rotations here, so we will
// just define initial values for the translations dx and dy.
transform = { dx: 0, dy: 0 };
}
// event.layerX is the mouse position in the coordinate system of the
// layer that was clicked on, the rectangle in this case
var layerx = event.layerX;
var layery = event.layerY;
if (this.click_on_rectangle==null) {
// we need to find where in the rectangle the mouse pointer is
// otherwise the rectangle will drag untill the mouse pointer
// hits the limits, rather than when the rectangel hits
// the limits.
this.click_on_rectangle = {
x: layerx - this.shape.shape.x - transform.dx,
y: layery - this.shape.shape.y - transform.dy
} // x,y of initial mouse click in coordinate system of rectangle
}
var click_on_surface = {
x: layerx - this.click_on_rectangle.x,
y: layery - this.click_on_rectangle.y
}; // x,y of mouse click in coordinate system of drawing surface
var x = event.clientX;
var y = event.clientY;
// check to see if the edges of the rectangle are within the
// limits of movement, if so, allow the rectangle to be moved
if (click_on_surface.x > limits.xmin
& click_on_surface.y > limits.ymin
& click_on_surface.x < limits.xmax - square.shape.width
& click_on_surface.y < limits.ymax - square.shape.height
) {
// move the rectangle by applying a translation
this.shape.applyLeftTransform({
dx: x - this.lastX,
dy: y - this.lastY
});
// store the last position of the rectangle
this.lastX = x;
this.lastY = y;
}
dojo.stopEvent(event);
}
});
// now we simply make the rectangle movable using the custom mover.
var recHandler = new dojox.gfx.Moveable(rectangle);
console.log(recHandler);
dojo.connect(recHandler, "onMove", function(mover) {
console.log("recHandler start moving with mover:", mover);
});
var sqHandler = new dojox.gfx.Moveable(square, { mover: net.aa3sd.surfaceMover })
console.log(sqHandler);
dojo.connect(sqHandler, "onMove", function(mover) {
console.log("sqHandler start moving with mover:", mover);
});
});
I'm developing a collision detection system in Javascript, and I need to find from which side of the rectangle a ball collided.
Anyway, what I need right now is to find the angle from the center of a rectangle to its vertices. Like this:
As you can see in the image, I want to find that angle, but also the rest of the angles to the bottom left and top left vertices.
I know this is math, but I need to code the formula in Javascript anyway.
Let's say I have this:
var box = {
width : 200,
height : 100
};
var boxCenter = {x : box.width / 2, y : box.height / 2 };
var angleRight = // ... ;
var angleBottom = // ... ;
And so on
The angle (red) may be calculated with:
var angle = 2* Math.atan(height/width);
I have made canvas Shapes(squares) in kinetic js having 4 control points on each of their vertices.A user can click and drag these control points and distort the shape any way he/she pleases. I also tried adding control points in the mid-point of each line by adding additional anchors and plugging them into the Bezier Curve..The js fiddle is http://jsfiddle.net/Lucy1/opsy1pn9/4/
The corresponding JS code is
var room = new Kinetic.Shape({
x: 0,
y: 0,
width: w,
height: h,
stroke: "blue",
fill: 'red',
drawFunc: function (context) {
var x = this.x();
var y = this.y();
var w = this.width();
var h = this.height();
var tlX = this.anchorTL.x();
var tlY = this.anchorTL.y();
context.beginPath();
context.moveTo(tlX, tlY);
// top
context.bezierCurveTo(x + w / 3, y, this.anchorTM.x(), this.anchorTM.y(), this.anchorTR.x(), this.anchorTR.y());
// right
context.bezierCurveTo(x + w, y + h / 3, this.anchorRM.x(), this.anchorRM.y(), this.anchorBR.x(), this.anchorBR.y());
// bottom
context.bezierCurveTo(x + w * 2 / 3, y + h, this.anchorBM.x(), this.anchorBM.y(), this.anchorBL.x(), this.anchorBL.y());
// left
context.bezierCurveTo(x, y + h * 2 / 3, this.anchorLM.x(), this.anchorLM.y(), tlX, tlY);
context.closePath();
context.fillStrokeShape(this);
}
});
g.add(room);
room.anchorTR = makeAnchor(w, 0, g);
room.anchorBR = makeAnchor(w, h, g);
room.anchorBL = makeAnchor(0, h, g);
room.anchorTL = makeAnchor(0, 0, g);
room.anchorTM = makeAnchor(w/2,0,g);
room.anchorRM = makeAnchor(w,h/2,g);
room.anchorLM = makeAnchor(0,h/2,g);
room.anchorBM = makeAnchor(w/2,h,g);
layer.draw();
}
The problem i am facing is that the mid-point control points are not moving with the line like the control points situated at the vertex..Please Help.
In looking at the history of your posts, you have previously been using Cubic Bezier Curves.
Each Bezier curve has 4 control points so you need 4 anchors--not 3 as you show. The control points are: (1) starting point (a corner) (2) mid point influencing the starting point (3) mid point influencing the ending point (4) ending point (a corner).
But your fiddle uses just one control point on your curve between the corners. This indicates a Quadratic Curve instead of a Cubic Bezier Curve.
Each Quadratic curve has 3 control points so you need 3 anchors as in your fiddle. The control points are: (1) starting point (a corner) (2) middle control point influencing the curve (3) ending point (a corner).
So if instead you want the user to drag on a quadratic curve to manipulate that curve, you can approximate the resulting middle quadratic control point using this math:
var controlPointX = 2*mouseX -startpointX/2 -endpoinX/2;
var controlPointY = 2*mouseY -startpointY/2 -endpointY/2;
Here's a Demo having the user drag to adjust a Quadratic curve:
http://jsfiddle.net/m1erickson/f4ag0myj/
In the following fiddle, you can click and drag around the image, and it will not be able to exit the blue border. By clicking the red and green rectangles, you can rotate the image. However when you click and drag a rotated object, the image does not follow the mouse. I would like the image to follow the mouse even if it is rotated.
http://jsfiddle.net/n3Sn5/
I think the issue occurs within my move function
move = function (dx, dy)
{
nowX = Math.min(boundary.attr("x")+boundary.attr("width")-this.attr("width"), this.ox + dx);
nowY = Math.min(boundary.attr("y")+boundary.attr("height")-this.attr("height"), this.oy + dy);
nowX = Math.max(boundary.attr("x"), nowX);
nowY = Math.max(boundary.attr("y"), nowY);
this.attr({x: nowX, y: nowY });
}
One thing to notice is that when you click and drag a rotated object, after you release your mouse click, if you rotate the image, it snaps to where your mouse was when you released the mouse click, even obeying the boundary.
I was able to get the rotated image to drag with the mouse previously, but by adding the boundary rectangle, i had to use a more complex approach.
If anyone has an idea of what I need to change, I would be very grateful!
Thanks!
The required output can be achieved in a bit different way. Please check the fiddle at http://jsfiddle.net/6BbRL/. I have trimmed to code to keep the basic parts for demo.
var paper = Raphael(0, 0, 475, 475),
boxX = 100,
boxY = 100,
boxWidth = 300,
boxHeight = 200,
// EDITED
imgWidth = 50,
imgHeight = 50,
box = paper.rect(boxX, boxY, boxWidth, boxHeight).attr({fill:"#ffffff"}),
// EDITED
html5 = paper.image("http://www.w3.org/html/logo/downloads/HTML5_Badge_512.png",boxX+boxWidth-imgWidth,boxY+boxHeight-imgHeight,imgWidth,imgHeight)
.attr({cursor: "move"}),
elementCounterClockwise = paper.rect(180, 0, 50, 50).attr({fill:"#ff5555", cursor:"pointer"}),
elementClockwise = paper.rect(250, 0, 50, 50).attr({ fill: "#55ff55", cursor: "pointer" }),
boundary = paper.rect(50,50,400,300).attr({stroke: '#3333FF'}),
transform,
// EDITED
xBound = {min: 50 + imgWidth/2, max: 450 - imgWidth/2},
yBound = {min: 50 + imgHeight/2, max: 350 - imgHeight/2};
start = function (x, y) {
// Find min and max values of dx and dy for "html5" element and store them for validating dx and dy in move()
// This is required to impose a rectagular bound on drag movement of "html5" element.
transform = html5.transform();
}
move = function (dx, dy, x, y) {
// To restrict movement of the dragged element, Validate dx and dy before applying below.
// Here, dx and dy are shifts along x and y axes, with respect to drag start position.
// EDITED
var deltaX = x > xBound.max && xBound.max - x || x < xBound.min && xBound.min - x || 0;
deltaY = y > yBound.max && yBound.max - y || y < yBound.min && yBound.min - y || 0;
this.attr({transform: transform + 'T'+ [dx + deltaX, dy + deltaY]});
}
up = function () {
};
html5.drag(move, start, up);
elementClockwise.click(function() {
html5.animate({transform: '...r90'}, 100);
})
elementCounterClockwise.click(function() {
html5.animate({transform: '...r-90'}, 100);
})
Use of '...' to append a transformation to the pre-existing transformation state (Raphael API) is important for the rotational issue. While, for translating the element on drag requires absolute translation, which neglects the rotational state of the element while translating the element.
//EDIT NOTE
Drag bounding is worked on and updated. However, there remains an issue with incorporating the difference between mouse position and image center.
I can help you with your rotation and drag problem, you need to store the rotation and apply it after you have moved the object.
elementClockwise.node.onclick = function()
{
html5.animate({'transform': html5.transform() +'r90'}, 100, onAnimComplete);
}
elementCounterClockwise.node.onclick = function()
{
html5.animate({'transform': html5.transform() +'r-90'}, 100, onAnimComplete);
}
function onAnimComplete(){
default_transform = html5.transform();
}
At present I can't get the boundary to work, but will have a try later.
http://jsfiddle.net/n3Sn5/2/