How do I link and drag 2 Circle shapes in Raphael JS? - raphael

For some reason i can get this working with rectangle variables but not with circles.
At the moment, this code allows both circles to be dragged independently but not together
Anybody know how to fix this or an alternative method?
addIntermediateSymbol = function()
{
var Intermediate = raphaelReference.set();
Intermediate.push(
raphaelReference.circle(74, 79, 20).attr({fill: "#ff7f00",stroke: "#000000",'stroke-width': 3}),
raphaelReference.circle(74, 79, 10).attr({fill: "#ff7f00",stroke: "#000000",'stroke-width': 4})
);
var start = function () {
// storing original coordinates
this.ox = this.attr("cx");
this.oy = this.attr("cy");
},
move = function (dx, dy) {
// move will be called with dx and dy
this.attr({cx: this.ox + dx, cy: this.oy + dy});
},
up = function () {
;
};
Intermediate.drag(move, start, up);
}

You have to use Intermediate again in the drag functions (start, move, up), but with translate function (which make everybody in the set move in the same way):
var start = function () {
Intermediate.oBB = Intermediate.getBBox();
},
move = function (dx, dy) {
var bb = Intermediate.getBBox();
Intermediate.translate(Intermediate.oBB.x - bb.x + dx, Intermediate.oBB.y - bb.y + dy);
},
See http://irunmywebsite.com/raphael/additionalhelp.php?v=1&q=anglebannersoncurves#PAGETOP (click on "Draggable Set" down of the right hand side list of examples)
It seems Intermediate.func() is just mapping the property func() to the elements inside of the set (applies to drag() and translate()), like:
for (var shape in Intermediate) {shape.func();}
About monkee answer:
As monkee points it out, in the dragging methods this references the clicked SVG object
Raphael sets don't have "cx" as such, so Intermediate.attr({cx:this.ox ... is working only if all the elements of the set are circles and have the same geometrical center ...

In the move function, "this" references to the clicked Raphäel object.
Instead of:
move = function (dx, dy) {
this.attr({cx: this.ox + dx, cy: this.oy + dy});
}
Do this:
move = function (dx, dy) {
Intermediate.attr({cx: this.ox + dx, cy: this.oy + dy});
}
Bad formatted working example here: http://jsbin.com/uxege4/7/edit

Here is a helpful js Fiddle solution that does exactly what you want, adapted from http://www.irunmywebsite.com/raphael/additionalhelp.php?v=2#pagetop
http://jsfiddle.net/q4vUx/102/
var paper = Raphael('stage', 300, 300);
var r = paper.rect(50,100,30,50).attr({fill:"#FFF"}),
c = paper.circle(90,150,10).attr({fill:"#FFF"}),
t = paper.text(50, 140, "Hello");
var rr = paper.rect(200,100,30,50).attr({fill:"#FFF"}),
cc = paper.circle(240,150,10).attr({fill:"#FFF"}),
tt = paper.text(200, 140, "Hello");
var pp = paper.set(rr, cc, tt);
var p = paper.set(r, c, t);
r.set = p, c.set = p, t.set = p;
rr.set = pp, cc.set = pp, tt.set = pp;
p.newTX=0,p.newTY=0,p.fDx=0,p.fDy=0,p.tAddX,p.tAddY,p.reInitialize=false,
pp.newTX=0,pp.newTY=0,pp.fDx=0,pp.fDy=0,pp.tAddX,pp.tAddY,pp.reInitialize=false,
start = function () {
},
move = function (dx, dy) {
var a = this.set;
a.tAddX=dx-a.fDx,a.tAddY=dy-a.fDy,a.fDx=dx,a.fDy=dy;
if(a.reInitialize)
{
a.tAddX=0,a.fDx=0,a.tAddY=0;a.fDy=0,a.reInitialize=false;
}
else
{
a.newTX+=a.tAddX,a.newTY+=a.tAddY;
a.attr({transform: "t"+a.newTX+","+a.newTY});
}
},
up = function () {
this.set.reInitialize=true;
};
p.drag(move, start, up);
pp.drag(move, start, up);

I was running into all sort of problems too regards dragging sets around.
It does:
- extends Raphael to make dragging sets possible
- creates new sets with a mouse click
- keeps the dragged set within the canvas boundaries.
The code in short:
CANVAS_WIDTH = 250;
CANVAS_HEIGHT = 250;
var newSet = document.getElementById("newSet");
paper = Raphael('canvas', CANVAS_WIDTH, CANVAS_HEIGHT);
var backGround = paper.rect(0,0,CANVAS_HEIGHT, CANVAS_WIDTH);
backGround.attr({fill: "lightgrey", "fill-opacity": 0.5, "stroke-width": 0});
newSet.onclick = function() {
createNewSet();
}
createNewSet = function() {
var mySet = paper.set();
var rect = paper.rect(0, 0, 50, 50);
rect.attr({fill: "red", "fill-opacity": 0.5, "stroke-width": 0});
var bBox = rect.getBBox();
var text = paper.text(10, 10, "Hello");
text.attr({fill: 'black', 'text-anchor': 'start'});
mySet.push(rect, text);
mySet.draggable();
//mySet = reposText(mySet);
mySet.max_x = CANVAS_WIDTH - bBox.width;
mySet.min_x = 0;
mySet.max_y = CANVAS_HEIGHT - bBox.height;
mySet.min_y = 0;
};
Raphael.st.draggable = function() {
var me = this,
lx = 0,
ly = 0,
ox = 0,
oy = 0,
moveFnc = function(dx, dy) {
lx = dx + ox;
ly = dy + oy;
if (lx < me.min_x ) {
lx = me.min_x;
}
else if ( lx > me.max_x) {
lx = me.max_x;
}
if ( ly < me.min_y ) {
ly = me.min_y;
}
else if ( ly > me.max_y) {
ly = me.max_y;
}
me.transform('t' + lx + ',' + ly);
},
startFnc = function() {},
endFnc = function() {
ox = lx;
oy = ly;
};
this.drag(moveFnc, startFnc, endFnc);
};
See this code in action here:
http://jsfiddle.net/Alexis2000/mG2EL/
Good Luck!

Related

How to rotate camera around object without centering to it

I would like to make a camera rotate around object, but without shifting pivot to it's center. A good example I made with blender:
Link to gif (In this example camera rotates around cursor, but it works as an example)
So what I want is when I click a certain object, I want to rotate around it, but without centering camera pivot to objects position, basically retaining objects position on screen. I found many examples on rotating around objects center, but I can seem to find anything for my problem.
Currently I have working camera rotation and movement, but I don't know how to approach this. I am working in OpenGL with Cinder framework.
I would be grateful for a simple explanation on how would I be able to do it :)
My current code:
void HandleUICameraRotate() {
//selectedObj <- object...has position etc..
float deltaX = (mMousePos.x - mInitialMousePos.x) / -100.0f;
float deltaY = (mMousePos.y - mInitialMousePos.y) / 100.0f;
// Camera direction vector
glm::vec3 mW = glm::normalize(mInitialCam.getViewDirection());
bool invertMotion = (mInitialCam.getOrientation() * mInitialCam.getWorldUp()).y < 0.0f;
// Right axis vector
vec3 mU = normalize(cross(mInitialCam.getWorldUp(), mW));
if (invertMotion) {
deltaX = -deltaX;
deltaY = -deltaY;
}
glm::vec3 rotatedVec = glm::angleAxis(deltaY, mU) * (-mInitialCam.getViewDirection() * mInitialPivotDistance);
rotatedVec = glm::angleAxis(deltaX, mInitialCam.getWorldUp()) * rotatedVec;
mCamera.setEyePoint(mInitialCam.getEyePoint() + mInitialCam.getViewDirection() * mInitialPivotDistance + rotatedVec);
mCamera.setOrientation(glm::angleAxis(deltaX, mInitialCam.getWorldUp()) * glm::angleAxis(deltaY, mU) * mInitialCam.getOrientation());
}
This is how you can do this rotation (look at the function orbit(...) in the code below).
The basic idea is to rotate the position and the lookAt direction of the camera about the target position. When you run the code demo, use the mouse right button to select the target, and move the mouse to rotate the camera around the target.
Hit me up if you need any clarifications.
let renderer;
let canvas;
let camera;
let scene;
const objects = [];
const highlightGroup = new THREE.Group();
const xaxis = new THREE.Vector3(1, 0, 0);
const yaxis = new THREE.Vector3(0, 1, 0);
const zaxis = new THREE.Vector3(0, 0, 1);
const radius = 10;
const fov = 40;
const tanfov = Math.tan(fov * Math.PI / 360.0);
function initCamera() {
const aspect = 2; // the canvas default
const near = 0.1;
const far = 2000;
camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 0, 500);
}
function initLights() {
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.PointLight(color, intensity);
light.position.set(0,0,200)
scene.add(light);
const light1 = new THREE.PointLight(color, intensity);
light1.position.set(100,200,-200)
scene.add(light1);
}
function initObjects() {
const geometry = new THREE.SphereBufferGeometry( radius, 13, 13 );
const yellowMat = new THREE.MeshPhongMaterial( {color: 0xffff00} );
const redMat = new THREE.MeshPhongMaterial( {color: 0xff0000} );
const greenMat = new THREE.MeshPhongMaterial( {color: 0x00ff00} );
const blueMat = new THREE.MeshPhongMaterial( {color: 0x0000ff} );
const magentaMat = new THREE.MeshPhongMaterial( {color: 0xff00ff} );
const cyanMat = new THREE.MeshPhongMaterial( {color: 0x00ffff} );
const lblueMat = new THREE.MeshPhongMaterial( {color: 0x6060ff} );
let sphere
sphere = new THREE.Mesh( geometry, yellowMat );
sphere.position.set(0, 0, 0);
objects.push(sphere);
scene.add(sphere)
sphere = new THREE.Mesh( geometry, redMat );
sphere.position.set(50, 0, 0);
objects.push(sphere);
scene.add(sphere)
sphere = new THREE.Mesh( geometry, blueMat );
sphere.position.set(0, 0, 50);
objects.push(sphere);
scene.add(sphere)
sphere = new THREE.Mesh( geometry, greenMat );
sphere.position.set(0, 50, 0);
objects.push(sphere);
scene.add(sphere)
sphere = new THREE.Mesh( geometry, magentaMat );
sphere.position.set(0, -50, 0);
objects.push(sphere);
scene.add(sphere)
sphere = new THREE.Mesh( geometry, cyanMat );
sphere.position.set(-50, 0, 0);
objects.push(sphere);
scene.add(sphere);
sphere = new THREE.Mesh( geometry, lblueMat );
sphere.position.set(0, 0, -50);
objects.push(sphere);
scene.add(sphere);
scene.add( highlightGroup );
}
function createRenderLoop() {
function render(time) {
time *= 0.001;
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
function initEventHandlers() {
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
window.addEventListener( 'resize', onWindowResize, false );
onWindowResize()
canvas.addEventListener('contextmenu', event => event.preventDefault());
}
function initOrbitCam() {
const diffToAngle = 0.01;
const hscale = 1.05;
const highlightMat = new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.2,
});
let isMouseButtonDown = -1;
let mouseDownPos;
let rightDownDragging = false;
let savedCamPos;
let savedCamLookAt = new THREE.Vector3();
let orbitTarget;
function absScrDist(pos1, pos2) {
return Math.abs(pos1[0] - pos2[0]) + Math.abs(pos1[1] - pos2[1]);
}
function addHighlight(obj) {
const objCopy = obj.clone();
objCopy.material = highlightMat;
objCopy.scale.set(hscale, hscale, hscale);
highlightGroup.add(objCopy);
}
function emptyHighlightGroup() {
highlightGroup.children.slice(0).forEach(child => {
highlightGroup.remove(child);
})
}
function getTarget(camera, event) {
const [x, y] = [event.offsetX, event.offsetY];
const [cw, ch] = [canvas.width, canvas.height];
const mouse3D = new THREE.Vector3( ( x / cw ) * 2 - 1,
-( y / ch ) * 2 + 1,
0.5 );
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera( mouse3D, camera );
const intersects = raycaster.intersectObjects( objects );
console.log(intersects)
if ( intersects.length > 0 ) {
addHighlight(intersects[0].object);
return intersects[0].object.position.clone();
}
const nv = new THREE.Vector3();
camera.getWorldDirection(nv);
return camera.position.clone().add(nv.clone().multiplyScalar(500));
}
function onCanvasMouseDown(event) {
isMouseButtonDown = event.button;
mouseDownPos = [event.offsetX, event.offsetY];
orbitTarget = getTarget(camera, event);
event.preventDefault();
event.stopPropagation();
}
canvas.addEventListener("mousedown", onCanvasMouseDown, false);
function onCanvasMouseUp(event) {
isMouseButtonDown = -1;
rightDownDragging = false;
emptyHighlightGroup();
event.preventDefault();
event.stopPropagation();
}
canvas.addEventListener("mouseup", onCanvasMouseUp, false);
function onCanvasMouseMove(event) {
if (rightDownDragging === false) {
if (isMouseButtonDown === 2) {
const currPos = [event.clientX, event.clientY];
const dragDist = absScrDist(mouseDownPos, currPos);
if (dragDist >= 5) {
rightDownDragging = true;
savedCamPos = camera.position.clone();
camera.getWorldDirection( savedCamLookAt );
}
}
} else {
const xdiff = event.clientX - mouseDownPos[0];
const ydiff = event.clientY - mouseDownPos[1];
const yAngle = xdiff * diffToAngle;
const xAngle = ydiff * diffToAngle;
orbit(-xAngle, -yAngle, savedCamPos.clone(), savedCamLookAt.clone(), orbitTarget)
}
}
canvas.addEventListener("mousemove", onCanvasMouseMove, false);
function orbit(xRot, yRot, camPos, camLookAt, target) {
const newXAxis = camLookAt.clone();
const lx = camLookAt.x;
const lz = camLookAt.z;
newXAxis.x = -lz;
newXAxis.z = lx;
newXAxis.y = 0;
const newCamPos = camPos
.sub(target)
.applyAxisAngle( newXAxis, xRot )
.applyAxisAngle( yaxis, yRot )
.add(target);
camera.position.set(...newCamPos.toArray());
const relLookAt = camLookAt
.applyAxisAngle( newXAxis, xRot )
.applyAxisAngle( yaxis, yRot )
.add(newCamPos);
camera.lookAt(...relLookAt.toArray());
camera.updateProjectionMatrix();
}
}
function setup() {
canvas = document.querySelector('#c');
renderer = new THREE.WebGLRenderer({canvas});
scene = new THREE.Scene();
initCamera();
initLights();
initObjects();
initEventHandlers();
initOrbitCam();
createRenderLoop();
}
setup();
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script src="https://unpkg.com/three#0.85.0/examples/js/libs/stats.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://unpkg.com/three#0.85.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.5/gsap.min.js"></script>
I don't exactly understand what you want to do... But maybe this helps...
Transformations in 3d space happen through matrcices, there are different kind of transformation matrices (i.e. translation, scale, rotation, ...) if you want to rotate an object around an axis which is not its own, you will have to move the object to this axis, rotate it this position and than move it back. What will happen is you multply the coordinates of whatever object you want to rotate around something, by the translation matrix, then mutltiply with a rotation matrix and than again multiple with a translation matrix. Luckily according to the rules of linear algebra, we can simply multiply all of these matrices in order, than multply it with the coordinates...
instead of this:
translationMatrix * somePosition;
rotationMatrix * somePosition;
anotherTranslationMatrix * somePosition;
this:
translationMatrix * rotationMatrix * anotherTranslationMatrix * somePosition;
It is a bit vague to explain this like that, but the idea is there. This might seem a like a lot of work, but GPUs are highly optimised to perform matrix multiplications, so if you succeed in lettling the GPU perform these, it will not be an issue performance wise...
If you already knew this: welp...
If you did not know this, research some linear algebra, specifically: coordinate spaces, matrix multiplication and transformation matrices.
cheers!

Multiple convex shape corner connection

I have an immutable array structure holding convex shapes as in the image above (they may vary in size and count, however they are always convex and never overlapping). What I want to do is connect the corners between them that can be connected without overlaping any edge, as in the image bellow where the blue lines represent the connections.
The data I have available are data structures holding the corner positions in the convex shapes, represented as a Vector structure similar to the following:
class Vector2
{
public:
float x, y;
}
The convex shape structure looks something like this:
class ConvexShape
{
public:
std::vector<Vector2> edges;
}
What I want to return from the function is an std::vector of a structure similar to the following:
class LinkedVector2 : public Vector2
{
public:
std::vector<LinkedVector2*> links;
}
So each linked vector is supposed to have a pointer to each other linked vector it is connected to.
The final function will thereby have this format:
std::vector<LinkedVector>* generateLinks(const std::vector<ConvexShape>& shapes)
{
std::vector<LinkedVector> links{ new std::vector<LinkedVector>{} };
// Create a linked vector for each shape's corner.
// Calculate links.
return links;
}
All of these links I then want to save for use in a later function which connects two points to the already linked shapes, along the lines of this:
The function should not alter the already existing connections and should look something like this:
// Argument 'links' will contain the previously generated links.
std::vector<LinkedVector>* connectPoints(const Vector2& a, const Vector2& b, const std::vector<LinkedVector>& links)
{
std::vector<LinkedVector>* connections{ new std::vector<LinkedVector>{} };
// Add old links to 'connections'.
// Connect the new links to the old.
// Add the new links to 'connections'.
return connections;
}
Could someone help me with how this could be done?
This is a description for an algorithm with example implementation to get you going.
Step 1
Preprocess every edge of the two shapes (s0 and s1) and extract the following information:
Distances from every edge in one shape to the vertices in the other
An ordered set of the vertices in one shape facing towards the other
Finding the distances is an exhaustive task (O(|V(s0)| * |V(s1)|)), it is also very cheap (line-point distance) and embarrassingly parallelisable. The facing vertices are found using the distances from above:
Start with the first vertex on the first shape, where the other shape is completely outside of its two adjacent edges (i.e. for any adjacent edge there exist outside values in its distances).
Since the facing set is a unique sequential set of vertices for convex polygons, continue adding vertices...
...until you reach a vertex where all vertices from the other shape lie inside of its adjacent edges
Doing this for both sides results in two sequences of facing vertices in each shape (the green dots per shape):
Step 2
To connect the two facing sets a scanline approach can be used:
In the ordered set of facing vertices the first vertex from one shape is always in line of sight of the last vertex from the other shape (first and last in case the shapes are oriented the same). From there we'll search sequentially, using the angle criteria from above for both the query from the first and the candidate vertex from the other shape, in the facing set to initialise our loop.
Looping sequentially over the facing vertices of the first shape, remove vertices that have broken line of sight (red line) and add vertices that came within line of sight (green line).
Step 3
Connecting the two outside points with the shapes is equivalent to finding the facing set of one shape in step 1 but instead of to another shape now there are only those individual outside points.
I've implemented step 1 and 2 in the following little browser demo as a prove of concept:
Click on the canvas and drag to move the camera
Click inside a shape and drag to move the shape
(function(canvas) {
function v2(x, y) { return { x: x, y: y }; }
function v2mul(lhs, rhs) { lhs.x *= rhs.x; lhs.y *= rhs.y; }
function v2subed(lhs, rhs) { return v2(lhs.x - rhs.x, lhs.y - rhs.y); }
function v2dot(lhs, rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; }
function v2normalized(v) { var len = Math.sqrt(v2dot(v, v)); if(len < 1e-7) len = 1; return v2(v.x / len, v.y / len); }
function v2perped(v) { return v2(-v.y, v.x); }
// Line from origin o : v2 and direction d : v2
function Line(o, d) {
this.o = o;
this.d = d;
}
// Signed distance to a point v : v2, in units of direction this.d
Line.prototype.distance = function(v) {
var o = v2subed(v, this.o);
var d = v2perped(this.d);
return v2dot(o, d);
};
// A polygon is made up of a sequence of points (arguments[i] : v2)
function Polygon() {
this.positions = [].slice.call(arguments);
}
// Transform polygon to new base [bx, by] and translation t
Polygon.prototype.transform = function(bx, by, t) {
this.positions.forEach(function(v) {
var x = bx.x * v.x + by.x * v.y + t.x;
var y = bx.y * v.x + by.y * v.y + t.y;
v.x = x;
v.y = y;
});
};
// Naive point inside polygon test for polygon picking
Polygon.prototype.isInside = function(v) {
if(this.positions.length < 3)
return false;
var o0 = this.positions[this.positions.length - 1];
for(var i = 0, imax = this.positions.length; i < imax; ++i) {
var o1 = this.positions[i];
var line = new Line(o0, v2normalized(v2subed(o1, o0)));
if(line.distance(v) <= 0)
return false;
o0 = o1;
}
return true;
};
// A camera positioned at eye : v2
function Camera(eye) {
this.eye = eye;
}
// Prepare temporaries for screen conversions
Camera.prototype.prepare = function(w, h) {
this.screen = {
off: v2(w / 2, h / 2),
};
};
Camera.prototype.toScreenX = function(x) { return x + this.screen.off.x - this.eye.x; }
Camera.prototype.toScreenY = function(y) { return this.screen.off.y - y + this.eye.y; }
Camera.prototype.fromScreenX = function(x) { return x - this.screen.off.x + this.eye.x; }
Camera.prototype.fromScreenY = function(y) { return this.screen.off.y - y + this.eye.y; }
Camera.prototype.toScreen = function(v) { return v2(this.toScreenX(v.x), this.toScreenY(v.y)); };
Camera.prototype.fromScreen = function(v) { return v2(this.fromScreenX(v.x), this.fromScreenY(v.y)); }
// Compute the distances of the line through e0 in p0 to each vertex in p1
// #post e0.distances.length === p1.positions.length
function computeEdge(e0, p0, p1) {
var line = new Line(p0.positions[e0.start], v2normalized(v2subed(p0.positions[e0.end], p0.positions[e0.start])));
var distances = [];
p1.positions.forEach(function(v) { distances.push(line.distance(v)); });
e0.line = line;
e0.distances = distances;
return e0;
}
// Find vertices in a convex polygon p0 that face p1
// #pre edges.length === p0.positions.length
function computeFacing(edges, p0, p1) {
var facing = [];
var count0 = p0.positions.length;
var count1 = p1.positions.length;
function isFacingVertex(i0) {
var e0 = edges[(i0 + count0 - 1) % count0];
var e1 = edges[i0];
for(var i1 = 0; i1 < count1; ++i1)
if(e0.distances[i1] < 0 || e1.distances[i1] < 0)
return true;
return false;
}
// Find the first vertex in the facing set of two non-intersecting, convex polygons
for(var i0 = 0; i0 < count0; ++i0) {
// For the first chance facing vertex
if(isFacingVertex(i0)) {
if(i0 === 0) {
// Search backwards here, s.t. we can complete the loop in one sitting
var iStart = count0;
for(; iStart > 1 && isFacingVertex(iStart - 1); --iStart);
while(iStart < count0)
facing.push(iStart++);
}
facing.push(i0++);
// In a convex polygon the (single) set of facing vertices is sequential
while(i0 < count0 && isFacingVertex(i0))
facing.push(i0++);
break;
}
}
return facing;
}
// Preprocesses the convex polygon p0 building the edges and facing lists
function preprocessPolygon(p0, p1) {
var result = {
edges: [],
facing: null,
};
for(var i = 0, imax = p0.positions.length; i < imax; ++i)
result.edges.push(computeEdge({ start: i, end: (i + 1) % imax }, p0, p1));
result.facing = computeFacing(result.edges, p0, p1);
return result;
}
// Scanline-approach to find all line of sight connections between the facing vertices of two preprocessed convex polygons p0 : Polygon and p1 : Polygon
// Output is prep.connections where prep.connections[i] : { v0, v1 } describes an unobstructed line of sight edge between vertex index v0 in p0 and v1 in p1
function computeConnections(prep, p0, p1) {
var connections = [];
var facing1count = prep.p1.facing.length;
// For oriented polygons the first facing vertex in p0 must surely face the last facing vertex in p1
var facing1begin = facing1count - 1, facing1end = facing1count;
prep.p0.facing.forEach(function(v0) {
function isConnectingVertex(v1) {
// Is v1 outside of adjacent edge-lines from v0?
var count0 = prep.p0.edges.length;
var ep = prep.p0.edges[(v0 + count0 - 1) % count0];
var en = prep.p0.edges[v0];
if(!(ep.distances[v1] < 0 || en.distances[v1] < 0)) return false;
// Is v0 outside of adjacent edge-lines from v1?
var count1 = prep.p1.edges.length;
ep = prep.p1.edges[(v1 + count1 - 1) % count1];
en = prep.p1.edges[v1];
return ep.distances[v0] < 0 || en.distances[v0] < 0;
}
// Throw away vertices that are no longer facing the current vertex
for(; facing1end > 0 && !isConnectingVertex(prep.p1.facing[facing1end - 1]); --facing1end);
// Add newly facing vertices
for(; facing1begin > 0 && isConnectingVertex(prep.p1.facing[facing1begin - 1]); --facing1begin);
// Generate the connections in facing range
for(var facing1 = facing1begin; facing1 < facing1end; ++facing1)
connections.push({ v0: v0, v1: prep.p1.facing[facing1] });
});
prep.connections = connections;
}
function process(prep, p0, p1) {
delete prep.p0;
delete prep.p1;
delete prep.connections;
prep.p0 = preprocessPolygon(p0, p1);
prep.p1 = preprocessPolygon(p1, p0);
computeConnections(prep, p0, p1);
}
var polygons = null;
var prep = null;
var camera = null;
var ui = null;
function reset() {
polygons = [
new Polygon(v2(25, -75), v2(50, -175), v2(140, -225), v2(255, -200), v2(195, -65), v2(140, -40)),
new Polygon(v2(400, -100), v2(295, -70), v2(260, -80), v2(310, -220), v2(425, -230)),
];
// Scale to a fitting size and move to center
var bx = v2(0.5, 0), by = v2(0, 0.5), off = v2(-120, 70);
polygons[0].transform(bx, by, off);
polygons[1].transform(bx, by, off);
prep = {};
camera = new Camera(v2(0, 0));
ui = { pickedPolygon: -1 };
update();
draw();
}
function update() {
// Reprocess polygons
process(prep, polygons[0], polygons[1]);
}
function draw() {
var g = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
camera.prepare(w, h);
g.fillStyle = "linen";
g.fillRect(0, 0, w, h);
var iPick = 0;
polygons.forEach(function(polygon) {
var highlight = iPick++ === ui.pickedPolygon;
var positions = polygon.positions;
if(positions.length > 2) {
g.beginPath();
g.lineWidth = highlight ? 2 : 1;
g.strokeStyle = "black";
var pLast = camera.toScreen(positions[positions.length - 1]);
g.moveTo(pLast.x, pLast.y);
positions.forEach(function(pos) {
var pScreen = camera.toScreen(pos);
g.lineTo(pScreen.x, pScreen.y);
});
g.stroke();
}
});
prep.connections.forEach(function(connection) {
var v0 = camera.toScreen(polygons[0].positions[connection.v0]);
var v1 = camera.toScreen(polygons[1].positions[connection.v1]);
g.beginPath();
g.lineWidth = 2;
g.strokeStyle = "cyan";
g.moveTo(v0.x, v0.y);
g.lineTo(v1.x, v1.y);
g.stroke();
});
}
(function(c) {
reset();
var dragStartPos = null, dragLastPos = null;
var pickedPolygon = null;
var cameraStartPos = v2(0, 0);
function toScreen(client) {
var rect = c.getBoundingClientRect();
return v2(client.x - rect.left, client.y - rect.top);
}
function startDragging(x, y) {
dragStartPos = v2(x, y);
dragLastPos = v2(x, y);
if(pickedPolygon !== null) {
// Nothing to prepare
} else {
cameraStartPos.x = camera.eye.x;
cameraStartPos.y = camera.eye.y;
}
}
function continueDragging(x, y) {
if(pickedPolygon !== null) {
var dx = x - dragLastPos.x, dy = -(y - dragLastPos.y);
pickedPolygon.transform(v2(1, 0), v2(0, 1), v2(dx, dy));
update();
} else {
var dx = -(x - dragStartPos.x), dy = y - dragStartPos.y;
camera.eye.x = cameraStartPos.x + dx;
camera.eye.y = cameraStartPos.y + dy;
}
dragLastPos.x = x;
dragLastPos.y = y;
}
function stopDragging() {
dragStartPos = null;
dragLastPos = null;
if(pickedPolygon !== null) {
// Nothing to do here...
} else {
cameraStartPos.x = 0;
cameraStartPos.y = 0;
}
}
c.onmousemove = function(e) {
if(dragStartPos !== null)
continueDragging(e.clientX, e.clientY);
else {
pickedPolygon = null;
var iPick = 0;
var cursorPos = camera.fromScreen(toScreen(v2(e.clientX, e.clientY)));
for(var imax = polygons.length; iPick < imax; ++iPick) {
if(polygons[iPick].isInside(cursorPos)) {
pickedPolygon = polygons[iPick];
break;
}
}
ui.pickedPolygon = pickedPolygon !== null ? iPick : -1;
}
draw();
};
c.onmouseleave = function(e) {
if(dragStartPos !== null)
stopDragging();
pickedPolygon = null;
ui.pickedPolygon = -1;
draw();
};
c.onmousedown = function(e) {
if(e.button === 0)
startDragging(e.clientX, e.clientY);
draw();
};
c.onmouseup = function(e) {
if(e.button === 0 && dragStartPos !== null)
stopDragging();
draw();
};
})(canvas);
})(document.getElementById("screen"));
<canvas id="screen" width="300" height="300"></canvas>

Why double click sets my Raphael SET coordinates wrong?

I am using Raphael library to draw and work with objects in my extjs app. Basically I was able to find a way to DRAG the SET the following way:
var set = paper.set();
set.push(
paper.circle(200,200,65).attr({fill: "orange", stroke: "black"}),
paper.text(200, 200, buttonText).attr({"font-size": 24, "font-weight": "bold", fill: "black"})
);
set.data("myset", set);
set.data("position", [0,0]);
var current_position = [0,0];
set.drag(
function(dx, dy) {
this.data('myset').transform("T" + (this.data("position")[0] + dx) + "," + (this.data("position")[1] + dy));
current_position = [dx,dy];
},
function() {
this.data('myset').data("position",
[this.data("position")[0] += current_position[0],
this.data("position")[1] += current_position[1]
]);
}
);
I found this little code in jsFiddle and modified it a little to do what I need. You can check out the demo here. When you are dragging the item by clicking on it once, then everything seams to work. But the problem is that when you doublclick on it couple of times and play with it/drag, it starts to jump around. The coordinates get messed up.
Could you help me to find where my coordinates get messed up. Thanks
I fixed it by looking at this example: here
var paper = Raphael(0, 0, 500, 500);
var set = paper.set();
set.push(paper.circle(100,100,35), paper.circle(150,150,15));
set.attr("fill", "orange");
set.data("myset", set);
set.drag(
function(dx, dy, x, y, e) {
this.data('myset').transform("T" + dx + "," + dy);
},
function(x, y, e) {},
function(e) {}
);

How do I get an event in Raphael's paper coordinates

I would like to get the coordinates of a mouse event in Raphael's paper coordinates. I would like those to be accurate even when I have used setViewBox.
Please see http://jsfiddle.net/CEnBN/
The following creates a 10x10 green box and then zooms way in - with the center of that box at the view's origin.
var paper = Raphael(10, 50, 320, 200);
var rect = paper.rect(0, 0, 10, 10);
rect.attr('fill', 'green');
rect.mousedown(function (event, a, b) {
$('#here').text([a, b]);
console.log(event);
});
paper.setViewBox(5, 5, 10, 10);
I would like to receive click coordinates that reflect their position in the box. ie. they should range from ([5-10], [5-10]).
Note: much later, and I have migrated to D3.js - which has generally made me a lot happier.
Edited: simplified by using clientX/Y of the mouse event - remove need to get element offset
Here is what I came up with. Basically, correct the mouse position to be relative to the paper by using the client rect of the paper and clientX/Y of the mouse event. Then compare the corrected positions to the client rect's width/height, then factor the results by original paper dimensions:
var paper = Raphael(10, 50, 320, 200);
var rect = paper.rect(0, 0, 10, 10);
rect.attr('fill', 'green');
rect.mousedown(function (event, a, b) {
// get bounding rect of the paper
var bnds = event.target.getBoundingClientRect();
// adjust mouse x/y
var mx = event.clientX - bnds.left;
var my = event.clientY - bnds.top;
// divide x/y by the bounding w/h to get location %s and apply factor by actual paper w/h
var fx = mx/bnds.width * rect.attrs.width
var fy = my/bnds.height * rect.attrs.height
// cleanup output
fx = Number(fx).toPrecision(3);
fy = Number(fy).toPrecision(3);
$('#here').text('x: ' + fx + ', y: ' + fy);
});
paper.setViewBox(5, 5, 10, 10);
An updated fiddle link is here:
http://jsfiddle.net/CEnBN/3/
more compact version of mouse down func:
rect.mousedown(function (event, a, b) {
var bnds = event.target.getBoundingClientRect();
var fx = (event.clientX - bnds.left)/bnds.width * rect.attrs.width
var fy = (event.clientY - bnds.top)/bnds.height * rect.attrs.height
$('#here').text('x: ' + fx + ', y: ' + fy);
});
You need to offset the result, something like this:
var posx, posy;
var paper = Raphael("canvas", 320, 200);
var rect = paper.rect(0, 0, 10, 10);
rect.attr('fill', 'green');
rect.mousedown(function (e, a, b) {
posx = e.pageX - $(document).scrollLeft() - $('#canvas').offset().left;
posy = e.pageY - $(document).scrollTop() - $('#canvas').offset().top;
$('#here').text([posx, posy]);
console.log(e);
});
paper.setViewBox(5, 5, 10, 10);
I added an element for Raphaeljs to target, have a look at this update to your jsfiddle
The answer by gthmb is very good, but missing a detail - the position of the rectangle on the paper. This version is only working, if the rectangle is at position (0,0). To support also the situation where it is translated, add the position of the rectangle to the result:
function mouseEvent_handler(e) {
var bnds = event.target.getBoundingClientRect();
var bbox = this.getBBox();
var fx = (event.clientX - bnds.left)/bnds.width * rect.attrs.width + bbox.x;
var fy = (event.clientY - bnds.top)/bnds.height * rect.attrs.height + bbox.y;
$('#here').text('x: ' + fx + ', y: ' + fy);
}
Here the modified version of the fiddle: http://jsfiddle.net/zJu8b/1/

Why does Raphael's framerate slow down on this code?

So I'm just doing a basic orbit simulator using Raphael JS, where I draw one circle as the "star" and another circle as the "planet". It seems to be working just fine, with the one snag that as the simulation continues, its framerate progressively slows down until the orbital motion no longer appears fluid. Here's the code (note: uses jQuery only to initialize the page):
$(function() {
var paper = Raphael(document.getElementById('canvas'), 640, 480);
var star = paper.circle(320, 240, 10);
var planet = paper.circle(320, 150, 5);
var starVelocity = [0,0];
var planetVelocity = [20.42,0];
var starMass = 3.08e22;
var planetMass = 3.303e26;
var gravConstant = 1.034e-18;
function calculateOrbit() {
var accx = 0;
var accy = 0;
accx = (gravConstant * starMass * ((star.attr('cx') - planet.attr('cx')))) / (Math.pow(circleDistance(), 3));
accy = (gravConstant * starMass * ((star.attr('cy') - planet.attr('cy')))) / (Math.pow(circleDistance(), 3));
planetVelocity[0] += accx;
planetVelocity[1] += accy;
planet.animate({cx: planet.attr('cx') + planetVelocity[0], cy: planet.attr('cy') + planetVelocity[1]}, 150, calculateOrbit);
paper.circle(planet.attr('cx'), planet.attr('cy'), 1); // added to 'trace' orbit
}
function circleDistance() {
return (Math.sqrt(Math.pow(star.attr('cx') - planet.attr('cx'), 2) + Math.pow(star.attr('cy') - planet.attr('cy'), 2)));
}
calculateOrbit();
});
It doesn't appear, to me anyway, that any part of that code would cause the animation to gradually slow down to a crawl, so any help solving the problem will be appreciated!
The problem is with the call back to calculateOrbit in your planet.animate() call. Its not being handled by raphael correctly and is causing a memory leak or an execution slow down. if you remove it and replace the line
calculateOrbit()
with
setInterval(calculateOrbit, 150);
it should run smoothly.
full code:
$(function() {
var paper = Raphael(document.getElementById('canvas'), 640, 480);
var star = paper.circle(320, 240, 10);
var planet = paper.circle(320, 150, 5);
var starVelocity = [0,0];
var planetVelocity = [20.42,0];
var starMass = 3.08e22;
var planetMass = 3.303e26;
var gravConstant = 1.034e-18;
function calculateOrbit() {
var accx = 0;
var accy = 0;
accx = (gravConstant * starMass * ((star.attr('cx') - planet.attr('cx')))) / (Math.pow(circleDistance(), 3));
accy = (gravConstant * starMass * ((star.attr('cy') - planet.attr('cy')))) / (Math.pow(circleDistance(), 3));
planetVelocity[0] += accx;
planetVelocity[1] += accy;
planet.animate({cx: planet.attr('cx') + planetVelocity[0], cy: planet.attr('cy') + planetVelocity[1]}, 150);
paper.circle(planet.attr('cx'), planet.attr('cy'), 1); // added to 'trace' orbit
}
function circleDistance() {
return (Math.sqrt(Math.pow(star.attr('cx') - planet.attr('cx'), 2) + Math.pow(star.attr('cy') - planet.attr('cy'), 2)));
}
setInterval(calculateOrbit, 150);
});