I've implemented the scaling of a paper and it works great.
I linked it to the scrolling of the mousewheel, but I still encounter one problem: In the API the scale function is defined as scale paper.scale(sx, sy, [ox, oy]) and I figured that ox and oy can center the zooming to a specific position. In my case this position should be the pointer. But although I hand over the coordinates (offsetX and offsetY of the mouse event), it has absolutly no effect.
Can someone give me an example of how to use ox and oy?
Don't forget to transform the mouse coordinates into the viewport coordinate system first.
function offsetToLocalPoint(offsetX, offsetY, paper) {
var svgPoint = paper.svg.createSVGPoint();
svgPoint.x = offsetX;
svgPoint.y = offsetY;
var offsetTransformed = svgPoint.matrixTransform(paper.viewport.getCTM().inverse());
return offsetTransformed
}
And reset the previous viewport translate transformation before you call paper.scale() with the origin [ox, oy] of scale transformation specified. You can see this in the mousewheel event handler example below.
function onMouseWheel(e) {
e.preventDefault();
e = e.originalEvent;
var delta = Math.max(-1, Math.min(1, e.wheelDelta)) / SPEED;
var newScale = V(paper.viewport).scale().sx + delta;
if (newScale > MIN_SCALE && newScale < MAX_SCALE) {
paper.setOrigin(0, 0); // reset the previous 'translate'
var p = offsetToLocalPoint(e.offsetX, e.offsetY);
paper.scale(newScale, newScale, p.x, p.y);
}
}
A cross-browser version fiddle here.
Related
I have created a class where I define Shape objects for my program. Each of these Shapes has a transparent ellipse drawn around it (I defined that in my constructor) and if any other Shape moves into that circular ellipse area, I want that Shape to change it's direction so that it moves in a circular path.
Each Shape object has a defined radius attribute (because of the ellipse I draw around each object) and I want to use that value to determine how big of a circular pattern the Shape has to move in when it collides.
Please help! Anything is greatly appreciated!
EDIT:
As I said above, I want the shape to move into a circular path. HOWEVER, I want it only to move in a circular path once (meaning it moves around a circle once) and then I want it to continue on the original path it was programmed with.
The short answer is that you'll have to use basic trig to figure out the angle between the points, and then more basic trig to determine subsequent points on the circular path.
Check out the trigonometry section of the Processing reference for more info.
But basically, if you have two points, you can use the atan2() function to calculate the angle between them. You'd use this to find the starting angle from the center of your circle to the shape.
Once you have that angle, you can simply increment it, and then use cos() and sin() to figure out the x and y coordinates at that new angle.
Here is a basic sketch that does all of the above:
PVector center;
float angle;
float radius;
void setup() {
size(500, 500);
center = new PVector(width/2, height/2);
//get the initial point
//for you, this would be the initial location of the object
PVector point = new PVector(random(width), random(height));
//find the angle between the points
float deltaX = center.x - point.x;
float deltaY = center.y - point.y;
angle = atan2(deltaX, deltaY);
//find the radius of the circle
radius = dist(center.x, center.y, point.x, point.y);
ellipseMode(RADIUS);
}
void draw() {
background(0);
//draw the center point
ellipse(center.x, center.y, 10, 10);
//find the point based on the angle
float x = center.x + cos(angle)*radius;
float y = center.y + sin(angle)*radius;
//draw the traveling point
ellipse(x, y, 10, 10);
//increment the angle to move the point
angle += PI/120;
}
Well, before I saw Kevin's post, I did one also. Not using objects, just a simple procedural example. Posting anyway :)
PVector pos, speed, stored;
float diam = 40;
boolean wonder = false;
float angle = 0;
void setup() {
size(300, 300);
// arbitrary positioning and speeding
pos = new PVector(-20, height/2);
speed = new PVector(1, 0);
noStroke();
}
void draw() {
background(5);
// normally increment speed
if (!wonder) {
pos.add(speed);
} else {
// if is to wonder...
if (angle <= 360) {
//get circle path by trig
pos.x = stored.x + cos(radians(angle))*diam;
pos.y = stored.y + sin(radians(angle))*diam;
} else {
// if the circle is complete
// reset angle and stop wondering
wonder = false;
angle = 0;
}
// increment angle
angle++;
}
// draw
ellipse(pos.x, pos.y, diam, diam);
}
void mouseClicked() {
if (isOverCircle() ) {
// store position where it has being clicked
stored = pos.get();
// off set the diam
stored.x -= diam;
// trig wondering
wonder = true;
angle = 0;
}
}
boolean isOverCircle() {
float disX = pos.x - mouseX;
float disY = pos.y - mouseY;
return sqrt(sq(disX) + sq(disY)) < diam/2;
}
For a project I'm trying to draw a moving line in Phaser. I initially drew it using game.debug.geom(line), but that is not really the right way to do it, since it doesn't allow for styling, and because the debugger takes a toll in performance.
Reading the docs, it seems to me that the way to do it would be with a Phaser.Graphics object, but I haven't been able to get it to work. As an example, I tried making a line move as the hand of a clock, with one end fixed and the other moving around it.
I thought it would be fine to create the Graphics object in the middle and then in update use reset to clear it and bring it back to the center, and then lineTo to make the rest of the line. But instead what I get is a line coming outwards from the centre, and then a ring.
Picture for sadness:
I made a pen with my attempts. The code is repeated below. What I would like to have is a line (lines?) coming from the center of the circle to the points in the circumference.
Is a Graphics object the best way to do that? How do I do it?
Demo.prototype = {
create: function() {
this.graphics = game.add.graphics(
game.world.centerX,
game.world.centerY
);
this.graphics.lineStyle(2, 0xffd900);
this.counter = 0;
this.step = Math.PI * 2 / 360;
this.radius = 80;
},
update: function() {
this.graphics.reset(
this.game.world.centerX,
this.game.world.centerY
);
var y = this.radius * Math.sin(this.counter);
var x = this.radius * Math.cos(this.counter);
this.graphics.lineTo(x, y);
this.counter += this.step;
}
};
You may want to check out this Phaser game called Cut It (not my game btw, found it here).
It also draws a variable length dotted line by cleverly using the Phaser.TileSprite, and then changing its width.
TileSprite draws a repeating pattern, and you can use this to draw a line by drawing one bitmap of a linepart segment, use that as background of the TileSprite and make the height of the TileSprite the same as the height of the bitmap.
You can take a look at the game's source code, it's compressed and minified but still somewhat readable. You can look for the variable called cut_line.
I finally understood that the coordinates taken by the Phaser.Graphics object are local, respective to the object's internal coordinate system. Using moveTo(0, 0) has the desired result of moving the object's drawing pointer back to its origin (and not, as I initially thought, to the origin of the game world). Using reset(0, 0), on the other hand, would have the effect of moving the object's origin to the world's origin.
As for deleting the previous lines, the only method I've found is to manually clear the object's graphicsData Array (short of calling destroy() and creating an entirely new object, which is probably not a very good idea).
Replacing the code in the original question with this does the trick:
Demo.prototype = {
create: function() {
this.graphics = game.add.graphics(
game.world.centerX,
game.world.centerY
);
this.graphics.lineStyle(2, 0xffd900);
this.counter = 0;
this.step = Math.PI * 2 / 360;
this.radius = 80;
},
update: function(){
// Erases the previous lines
this.graphics.graphicsData = [];
// Move back to the object's origin
// Coordinates are local!
this.graphics.moveTo( 0, 0 );
var y = this.radius * Math.sin(this.counter);
var x = this.radius * Math.cos(this.counter);
this.graphics.lineTo(x, y);
this.counter += this.step;
}
};
What is the best practice for resizing say an Ellipse or Rectangle on another SVG element?
If I check for the onMouseMove event on the ellipse when I am out of it, the resizing stops. I was thinking to implement the listener on the svg element as well and pass the information to the ellipse I started the resizing on. This means having a generic Resize() method on the svg element that passes to the Resize() of the selected ellipse.
Isn't there an easier way?
I'm currently doing it with Dart, but it should be the same with javascript.
An example is an <svg> element with a <g> containing <rect> and an <ellipse>. If I start to resize the rectangle on a rectangle.onMouseMove, the moment I'm outside it, it stops resizing. To avoid this, I have to use the svg.onMouseMove, and use a resize method that resizes the rectangle. To know that it's the rectangle to be resized, I check for onMouseDown and again check the target (MouseEvent.target on Dart. Not sure how to detect what I'm touching without doing a cumbersome check on id maybe). Note that what I am trying to accomplish is to use the rectangle to resize the ellipse. I'm showing the rectangle only when resizing.
The following code will create a circle and resize when you start dragging.
It also works when the mouse gets outside the circle.
Maybe you can modify this to your liking.
Oh, and my Dart-code might not be the best, as I just recently started learning Dart.
Any improvements are welcome.
import 'dart:html';
import 'dart:svg';
import 'dart:math' as math;
void main() {
// add new svg
var svg = new SvgSvgElement();
svg.style.width = '400px';
svg.style.height = '400px';
svg.style.border = '1px solid black';
var body = document.querySelector('body');
body.append(svg);
// add group
var g = new SvgElement.tag('g');
svg.append(g);
// center of circle
var center = new math.Point(200, 200);
var startR = 70;
var newR = 70;
// add circle
var circle = new CircleElement();
circle.setAttribute('cx', center.x.toString());
circle.setAttribute('cy', center.y.toString());
circle.setAttribute('r', startR.toString());
circle.setAttribute('fill', 'green');
g.append(circle);
circle.onMouseDown.listen((MouseEvent E) {
var startOffset = E.offset;
var startDistance = startOffset.distanceTo(center);
math.Point offset = E.offset;
// now start listening for document movements so we don't need to stay on the circle
var move = document.onMouseMove.listen((MouseEvent E) {
// calculate new position
var movement = E.movement;
offset = new math.Point(
// multiply with 0.5 to make the mouse move faster than the circle grows
// that way we show that the mouse movement also works outside the circle element
offset.x + movement.x * 0.5,
offset.y + movement.y * 0.5
);
// calculate new distance from center
var distance = offset.distanceTo(center);
// calculate new radius for circle
newR = distance / startDistance * startR;
// resize circle
circle.setAttribute('r', newR.toString());
});
// and stop all listening on mouseup
var up = document.onMouseUp.listen(null);
up.onData((MouseEvent E) {
move.cancel();
up.cancel();
startR = newR;
});
});
}
Hope this helps,
Kind regards,
Hendrik Jan
I've been trying to teach myself D3.js, but I can't seem to get semantic zoom (zooming positions but not shapes) to work for me.
I've read the d3 zoom docs here, and attempted to functionally copy the svg semantic zoom example code
This is my code:
var X, Y, circle, circles, h, i, j, svg, transform, w, zoom, _i, _j;
w = 1200;
h = 600;
circles = [];
for (j = _i = 0; _i <= 6; j = ++_i) {
for (i = _j = 0; _j <= 12; i = ++_j) {
circles.push({r: 25, cx: i * 50, cy: j * 50});
}
}
X = d3.scale.linear()
.domain([0, 1])
.range([0, 1]);
Y = d3.scale.linear()
.domain([0, 1])
.range([0, 1]);
zoom = d3.behavior.zoom()
.x(X)
.y(Y)
.on("zoom", function() {
return circle.attr("transform", transform);
});
transform = function(d) {
return "translate(" + (X(d.cx)) + ", " + (Y(d.cy)) + ")";
};
svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.call(zoom)
.append("g");
circle = svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("r", function(d) {
return d.r;
}).attr("cx", function(d) {
return d.cx;
}).attr("cy", function(d) {
return d.cy;
}).attr("transform", transform);
Live version at jsfiddle.
This should be pretty simple. I'm creating grid of circles that should exactly touch when no zoom is applied (distance is 50 px, diameter is 50 px). When I zoom in, I expect the circles to spread apart, with the point under the mouse remaining stationary. I expect the zoom to be smooth and linear with applied mouse wheeling. The circles should remain the same size, though, so that they stop touching when I zoom in; they should overlap when I zoom out.
Instead, initially, the circles are spread out exactly twice as far as they should be. When I zoom in and out, the center point is not under the mouse (and moves around depending on how I pan). Zoom is highly nonlinear, asymptotically approaching a scale of 1 (circles touching) as I zoom out, and rapidly accelerating as I zoom in.
This seems really odd, and I can't spot significant differences between my code and the semantic zoom example, which works as expected. I conclude that I don't actually understand how D3 zoom is supposed to work. Can someone sort me out?
Your code is very close to being correct: Working demo.
Use scale to map the location of objects
Instead of saving the exact location of objects in them and then using scales with range and domain set to [0, 1], use the scales to do the mapping for you:
for (j = _i = 0; _i <= 6; j = ++_i) {
for (i = _j = 0; _j <= 12; i = ++_j) {
circles.push({
r: 25,
cx: i,
cy: j,
color: "#000"
});
}
}
X = d3.scale.linear()
.domain([0, 6])
.range([0, w]);
Y = d3.scale.linear()
.domain([0, 12])
.range([0, h]);
The change here is that now D3 knows about the aspect ratio of your viewport and in what proportions it should transform the scales so as to keep the point under the svg static under the mouse. Otherwise, it was trying to zoom in and out of a square, resulting in a jarring experience.
The problem was the initial position of the circles stacking up on the translation.
Live code with the problem pointed out and fixed, and a few other modifications:
var size = 600
var scale = 100
circles = []
for (var j = 0; j<6; j++) {
for (var i = 0; i<6; i++) {
circles.push({x: i*scale, y: j*scale })
}
}
var X = d3.scale.linear()
.domain([0,6*scale])
.range([0,size])
var Y = d3.scale.linear()
.domain([0,6*scale])
.range([0,size])
function transform(d) {
return "translate("+X(d.x)+", "+Y(d.y)+")"
}
var circle /*fwd declaration*/
var zoom = d3.behavior.zoom()
.x(X).y(Y)
.on("zoom", function () {
circle.attr("transform", transform)
})
var svg = d3.select("body").append("svg")
.attr("width", size).attr("height", size)
.call(zoom)
.append("g")
circle = svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("r", 20)
/*the problem was this initial offset interfering with the
translation we were applying, resulting in very strange behavior*/
/* .attr("cx", function (d) {return d.x})
.attr("cy", function (d) {return d.y})*/
.attr("transform", transform)
The "scale" parameter should do nothing, but if you add in those commented lines, it affects the initial position and causes the non-intuitive effects.
The original problems were:
Initial scale appeared to be more zoomed than it should have been.
Zooming out very var produced a noticeable nonlinear asymptotic effect.
Zooming out then panning around, then zooming back in did not work at all like expected, with the diagram sliding under the mouse instead of staying pinned.
All of these are straightforward consequences of the initial position:
The initial distances appeared bigger because we applied their original positions plus the zoom translation.
The nonlinear asymptotic effect was the zoom translation distances going to zero asymptotically (as expected), but the initially applied distances not going to zero, giving the appearance of a nonzero zoom asymptote.
While zoomed out, D3 thinks it's zoomed out more than the user does (because of the extra distances between circles), which means when a pan is applied, the center of the image as D3 tracks it is moving differently than what the user expects, which causes the effect of the zoom center not being under the mouse.
You can play with these effects to understand them by uncommenting the initial position lines and applying the same zoom actions with different scale parameters. Commenting them causes the circles to initially be all at screen-space 0,0, so that only the zoom distance translation is applied, which is what we want.
Props to musically_ut's answer for suggesting the smaller world-space coordinate scale, which shouldn't have made any difference, but did, which helped me identify the problem.
At the moment I'm using the dot product of the mouse position and (0, 1) to generate radians to rotate an object, in three.js
Code below, works ok but the object 'jumps' because the radian angle skips from positive to negative when the clientX value goes between window.innerWidth / 2
onDocumentMouseMove : function(event) {
// rotate circle relative to current mouse pos
var oldPos = new THREE.Vector2(0, 1);
Template.Main.mouseCurrPos = new THREE.Vector2((event.clientX / window.innerWidth ) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
Template.Main.mouseCurrPos.normalize();
//Template.Main.projector.unprojectVector(Template.Main.mouseCurrPos, Template.Main.scene);
var angle = oldPos.dot(Template.Main.mouseCurrPos);
Template.Main.mousePrevPos.x = event.clientX;
Template.Main.mousePrevPos.y = event.clientY;
if (event.clientX < window.innerWidth / 2) {
Template.Main.circle.rotation.z = -angle;
}
else {
Template.Main.circle.rotation.z = angle;
}
console.log(Template.Main.circle.rotation.z);
}
However if I add this to assign the value to oldPos:
if (event.clientX < window.innerWidth / 2) {
oldPos = new THREE.Vector2(0, -1);
}
else {
oldPos = new THREE.Vector2(0, 1);
}
Then the "jumping" goes but the effect of rotation is inverted when the mouse is on the left of the window.
I.e. mouse going up rotates anti-clockwise and vice-versa which is not desired.
It's frustrating.
Also if I keep the oldPos conditional assignment and leave out the conditional negation of the angle instead, the jumping comes back.
You can see a demo here: http://theworldmoves.me/rotation-demo/
Many thanks for any tips.
Why are you using the result of the dot product as the angle (radians)? The dot product gives you the cosine of the angle (times the magnitude of the vectors, but these are a unit vector and a normalized vector, so that doesn't matter).
You could change your angle computation to
var angle = Math.acos(oldPos.dot(Template.Main.mouseCurrPos));
However, you may get the wrong quadrant, since there can be two values of theta that satisfy cos(theta) = n. The usual way to get the angle of a vector (origin to mouse position) in the right quadrant is to use atan2():
var angle = Math.atan2(Template.Main.mouseCurrPos.y,
Template.Main.mouseCurrPos.x);
This should give the angle of the mouse position vector, going counterclockwise from (1, 0). A little experimentation can determine for sure where the zero angle is, and which direction is positive rotation.