The right way to draw an expanding circle

1,154 views
Skip to first unread message

Andrew C

unread,
Jul 7, 2011, 5:04:02 PM7/7/11
to Paper.js
I'm new to vector programming, and created a simple script that makes
an expanding circle when you drag the mouse. I've created the script,
but I'm not sure I've done it the "right" way. Is this the correct
way to find a circle center point and radius? Thanks!

----------------------------

var circle;
var center;

function onMouseDown(event) {
circle = new Path.Circle(event.point, 1);
circle.strokeColor = 'black';
circle.fillColor = 'white';
center = new Point(circle.segments[1].point.x,
circle.segments[0].point.y);
}

function onMouseDrag(event) {
var radius = (circle.segments[2].point.x -
circle.segments[0].point.x) / 2;
var new_radius = event.point - center;
circle.scale(new_radius.length/radius);
}

meetamit

unread,
Jul 7, 2011, 9:01:20 PM7/7/11
to Paper.js
Andrew raises an issue I've been meaning to ask about. It appears that
transformations can only be applied relative to the current transform
of the object -- which is why in Andrew's example, he has to un-scale
the circle when setting the new scale: circle.scale(new_radius.length/
radius);

In other words, it looks like there's no way to call
circle.setScale(2), which would make the circle 2x the scale it was
when constructed -- even if prior to that call you made a call like
circle.setScale(4). There's also no way to get the current scale of an
object (i.e. circle.getScale()).

This all suggests that transformations are "baked into the geometry",
rather than being applied to the geometry at render time. Is this
true?

- Amit

P.S.
PaperJS is loads of fun. Thank you guys for making this happen!

Jürg Lehni

unread,
Jul 8, 2011, 3:22:01 AM7/8/11
to pap...@googlegroups.com
Hi Andrew and Amit,

Yes, for items of the PathItem class such as Path and CompoundPath it is true that transformations are baked in rather than stored separately in a matrix.

This is how things were done in Scriptographer, mainly due to limitations defined by Illustrator. To use a separate matrix object at the moment, you can wrap your items in a Symbol and then place it in the project using Symbol#place(), resulting in a PlacedSymbol that has its own matrix (similar to instances of MovieClips in flash).

I am considering lifting this limitation and also giving a matrix to PathItem, with an added method for 'baking' the transformation into the item's segment list, and resetting it to the identity matrix.

This would make things much easier for such cases and one would not have to use Symbols all the time.

Jürg

Joseph Bergen

unread,
Jul 27, 2011, 3:06:50 AM7/27/11
to pap...@googlegroups.com
fwiw, I'd like to chime in because I'd love to be able to directly scale path items. Wrapping them as symbols is kind of a kludge and also scales lineWeight which is undesirable. 

thanks for the library!

Jürg Lehni

unread,
Jul 27, 2011, 4:21:10 AM7/27/11
to pap...@googlegroups.com
Yes, we're aware of this and it is planned. The current limitations are one of the not so great left-overs from porting over parts of the existing API in Scriptographer.

But once every object will have its own transformation matrix, things can become messy in different ways, e.g. when nesting in groups and layers, hit-testing, etc. So before adding this we're thinking long and hard about how to do it in a way that will not lead to unforeseen complexities.

I completely agree about the limitations of using symbols for this.

Jürg

Jonathan Puckey

unread,
Jul 27, 2011, 2:39:09 PM7/27/11
to pap...@googlegroups.com
In the meantime, you can take the last scale into account (as seen in examples/Rasters/Raster.html):

var lastScale = 1;
function onFrame(event) {
var scale = (Math.sin(event.time * 2) + 1) / 2;
raster.scale(scale / lastScale);
lastScale = scale;
}

In this example, we're remembering the scale in a local variable - but you could also save it as a property on the item.

But, I agree that it would be nicer to have a scale property that you could just change on the item..

Peter Rietzler

unread,
Aug 11, 2011, 2:56:39 AM8/11/11
to Paper.js
You may want to have a look at how things work in SVG.

We've got a lot of experience with SVG based graphics and one of the
really large benefits is, that you can create parts of your graphic in
whatever size you want and then let some outer container fit it to the
size you need. It is essential (e.g. for animations), that the sizes
of the elements stay untouched - so if you change or add something,
you can just use the sizes that you originally used to construct the
graphic!

As far as I can imagine, SVG just adds transformation matrices and
multiplies them when stepping down the hierarchy. Additionally, they
are just relative to the parent's transformation matrix but absolute
on their own. So you can again just think in your original size and
don't have to "unscale" as stated in one of the posts above.

However, it's sometimes awkward to get actual sizes and positions in
SVG. Since you always get the original size of the item (getBBox())
you have to take care of calculating the screen size and position on
your own. For this case you have to get the CTM (client transformation
matrix) and run your points through it. It's even worse with sizes/
lengths since you need to run multiple points through it and calculate
the length on your own. I guess that PaperJS can do this better!

Do you use Canvas transformations and Canvas save/restore for
rendering? I guess that this will lead to much higher performance due
to upcoming hardware acceleration (despite that fact that Canvas can
do the whole stuff in good old C performance).

Peter

On Jul 27, 8:39 pm, Jonathan Puckey <m...@jonathanpuckey.com> wrote:
> In the meantime, you can take the lastscaleinto account (as seen in examples/Rasters/Raster.html):
>
>                 var lastScale = 1;
>                 function onFrame(event) {
>                         varscale= (Math.sin(event.time * 2) + 1) / 2;
>                         raster.scale(scale/ lastScale);
>                         lastScale =scale;
>                 }
>
> In this example, we're remembering thescalein a local variable - but you could also save it as a property on the item.
>
> But, I agree that it would be nicer to have ascaleproperty that you could just change on the item..
>
> On Jul 27, 2011, at 10:21 AM, Jürg Lehni wrote:
>
>
>
>
>
>
>
> > Yes, we're aware of this and it is planned. The current limitations are one of the not so great left-overs from porting over parts of the existing API in Scriptographer.
>
> > But once every object will have its own transformation matrix, things can become messy in different ways, e.g. when nesting in groups and layers, hit-testing, etc. So before adding this we're thinking long and hard about how to do it in a way that will not lead to unforeseen complexities.
>
> > I completely agree about the limitations of using symbols for this.
>
> > Jürg
>
> > On 27 Jul 2011, at 08:06, Joseph Bergen wrote:
>
> >> fwiw, I'd like to chime in because I'd love to be able to directlyscalepath items. Wrapping them as symbols is kind of a kludge and also scales lineWeight which is undesirable.
>
> >> thanks for the library!

Stewart Smith

unread,
Aug 11, 2011, 4:27:37 AM8/11/11
to pap...@googlegroups.com

Peter, your emails are coming off as condescending. (Which is a bit laughable considering who you're lecturing on vector graphics!) Am I just misreading something here?

Cheers,
Stewart

Jürg Lehni

unread,
Aug 11, 2011, 5:54:42 AM8/11/11
to pap...@googlegroups.com
I didn't read it that way.

We are indeed planning on offering nested matrices / transformations the way you describe it for all items.

But this is quite a big change from the current architecture with lots of implications, so it's something we have to plan and think through carefully. Drawing is one part, and probably the easier, the other is handling segment points and bounding boxes, hit testing, etc. For example a Path nested inside a Group and a Layer, both with matrices applied. In what coordinate space do you write and read the coordinates of its segment points? The easier approach would be in local ones. But then there needs to be a set of functions on each item that allows conversion from local to global coordinates, and back, adding complexity. In some ways we quite like how currently it's all provided in global coordinates and any matrix is immediately applied to all nested items and paths. From a programmer's point of view, especially from someone who is about to learn to code, this is much simpler. Except for the issues outlined in the original email below. So we need to consider this change well, since the implications are huge. It's not a matter of not understanding the involved techniques, it's a hesitation regarding such a fundamental architectural change.

Another approach could be to allow the nesting of matrices but to make segment points seem global while internally they are stored in local coordinates. This would mean it would all look and fell the same as now, but it would allow the overriding of any previously set matrix. We already have read-only properties on Matrix for rotation and scaling, these could easily become writable. e.g.

path.matrix.rotation = 45; // Reset the path's rotation regardless of the previous state of its transformation matrix.

There could be a shortcut on Path too:

path.rotation = 45;

Lots to think about…

Jürg

Peter Rietzler

unread,
Aug 12, 2011, 2:51:09 AM8/12/11
to Paper.js
Dear Steward

I'm really sorry about this! It wasn't meant this way.

I just wanted to write down our experience with SVG and give some
hints what was important for us - solely from a user's point of view.
We are currently looking into Paper.js as an alternative or companion
to our SVG based graphics. Our graphics are currently based on a
composition of parts, which are developed separately (much like
widgets). Therefore it is very convenient to implement the graphics
(and especially animations) in a local coordinate system and don't
care about actual rendering sizes. On the other side, from the
perspective of the one who creates the composition, it's sometimes
nasty, since you have to convert everything to your global coordinate
system (e.g. if you want to display some kind of handle around a
potentially transformed part of your graphic).

I never meant to lecture on vector graphics - just wanted to offer our
user experience!

Peter

Peter Rietzler

unread,
Aug 12, 2011, 3:38:33 AM8/12/11
to Paper.js
Dear Jürg,

this is an interesting discussion.

Especially from the point of view, when creating a graphic from
multiple sub-parts (let's call it widget), it seems to be much easier
to think in local coordinates for creating, and especially for
changing the single widgets after they were created - at least at a
first glance...

In a typical scenario, a widget could install a callback, e.g. waiting
for data to display. In the meantime the widget is added to some kind
of dashboard which transforms the widget according to it's layout
algorithm. When the callback fires and the widget needs to add the
data points, it seems to be much easier to do all the calculations in
it's local coordinate system.

In the case of hit testing I'd like to choose between global or local
coordinate systems. At a first shot, it could be a convenient way to
let the user decide on which item to perform the hit test. E.g. to use
a global hit test (e.g. hit testing mouse positions), the user calls
hitTest on the view and uses global coordinates. To perform a local
hit test (e.g. for algorithmic work within a single widget), the user
calls hitTest on the widget group item and uses local coordinates.

Cheers,
Peter

Michal Migurski

unread,
Aug 12, 2011, 11:58:12 AM8/12/11
to pap...@googlegroups.com
For what it's worth, this way of doing things has been baked into Flash for a long time. The platform has its problems, but fast predictable rendering of vector graphics is not one of them. Nested matrices are definitely an important feature, so I look forward to seeing how you guys make progress on them!

-mike.

----------------------------------------------------------------
michal migurski- mi...@stamen.com
415.558.1610

Jürg Lehni

unread,
Aug 13, 2011, 4:22:04 AM8/13/11
to pap...@googlegroups.com
Mike,

I'm curious to hear how you expect the coordinates of a path to behave if for example it is transformed by multiple nested matrices. Would you expect segment manipulation to happen in local coordinates? What about hit-testing or editing points / handles them with the mouse?

The hard part is not implementing nested matrices but getting all these details right. At the moment it's all nicely straight forward, which is a big plus too.

Jürg

Amit S

unread,
Aug 14, 2011, 3:37:08 AM8/14/11
to pap...@googlegroups.com
Jürg,

I'd like to try to answer this question, and excuse me if any of this is stuff is simply obvious or comes off as too "explain-y".

Being a (former) heavy ActionScript developer, my idea of how a graphics API should be structured come mainly from the Flash world, which seems to do a good job at that. In Flash DisplayObjects (which are extended by Container, Sprite and MovieClip) handle the concatenation of transforms, but don't have any drawing methods. Instead, they each have a Graphics object for that. When you draw into the Graphics object (via lineTo, curveTo, etc) you do so in local coordinates. There's no way to transform Graphics directly; instead you transform the containing DisplayObject. The (0,0) of the Graphics object corresponds to the registration point (i.e. pivot point) of the containing DisplayObject.

Translating that hierarchy into PaperJS, the Path class could become just the graphics object that gets attached to a not-yet-exiting container class (at first I thought that's what Group was for, but turned out not to be) that has the transform matrix. Path wouldn't have any of the transformation methods it currently has (like scale, translate, etc). Also, like in Flash, the container class would have globalToLocal() and localToGlobal() methods for converting a Point from screen coordinates to Path coordinates. So shape-specific hit tests (as opposed to Bounds Box hit tests, I suppose) can be done in local coordinates; something like this: 

if( myPath.hitTest( myContainer.globalToLocal(mousePos) ) ) { // that's a hit }

Without knowing too much about the implementation of PaperJS, it seems like this separation between drawing and transforming duties would require relatively minimal modification to existing drawing code.

Jürg Lehni

unread,
Dec 28, 2011, 8:17:18 AM12/28/11
to pap...@googlegroups.com, Michal Migurski
Hi Mike,

Just a heads up to let you know that Paper.js now supports affine transformations on all objects other than Path, and these can be nested, just like we're used to from other platforms, e.g. Flash.

Paper.js now also supports item level mouse events.

We will write more about this once the new release is up.

Jürg

On 12 Aug 2011, at 17:58, Michal Migurski wrote:

Reply all
Reply to author
Forward
0 new messages