Improve Item#transform() and implement #applyMatrix() to support nested matrices. Work in progress.

This commit is contained in:
Jürg Lehni 2011-12-18 16:54:21 +01:00
parent b0b49d027a
commit 9e5eb17264
5 changed files with 71 additions and 51 deletions

View file

@ -1665,6 +1665,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* @function
* @param {Number} scale the scale factor
* @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
*
* @example {@paperscript}
* // Scaling an item from its center point:
@ -1697,6 +1698,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* @param {Number} hor the horizontal scale factor
* @param {Number} ver the vertical scale factor
* @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
*
* @example {@paperscript}
* // Scaling an item horizontally by 300%:
@ -1709,24 +1711,26 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* // Scale the path horizontally by 300%
* circle.scale(3, 1);
*/
scale: function(hor, ver /* | scale */, center) {
scale: function(hor, ver /* | scale */, center, apply) {
// See Matrix#scale for explanation of this:
if (arguments.length < 2 || typeof ver === 'object') {
apply = center;
center = ver;
ver = hor;
}
return this.transform(new Matrix().scale(hor, ver,
center || this.getPosition()));
center || this.getPosition(true)), apply);
},
/**
* Translates (moves) the item by the given offset point.
*
* @param {Point} delta the offset to translate the item by
* @param {Boolean} apply
*/
translate: function(delta) {
translate: function(delta, apply) {
var mx = new Matrix();
return this.transform(mx.translate.apply(mx, arguments));
return this.transform(mx.translate.apply(mx, arguments), apply);
},
/**
@ -1736,6 +1740,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
*
* @param {Number} angle the rotation angle
* @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* @see Matrix#rotate
*
* @example {@paperscript}
@ -1770,9 +1775,9 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* path.rotate(3, view.center);
* }
*/
rotate: function(angle, center) {
rotate: function(angle, center, apply) {
return this.transform(new Matrix().rotate(angle,
center || this.getPosition()));
center || this.getPosition(true)), apply);
},
// TODO: Add test for item shearing, as it might be behaving oddly.
@ -1784,6 +1789,7 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* @function
* @param {Point} point
* @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* @see Matrix#shear
*/
/**
@ -1795,41 +1801,48 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* @param {Number} hor the horizontal shear factor.
* @param {Number} ver the vertical shear factor.
* @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* @see Matrix#shear
*/
shear: function(hor, ver, center) {
// PORT: Add support for center back to Scriptographer too!
shear: function(hor, ver, center, apply) {
// PORT: Add support for center and apply back to Scriptographer too!
// See Matrix#scale for explanation of this:
if (arguments.length < 2 || typeof ver === 'object') {
apply = center;
center = ver;
ver = hor;
}
return this.transform(new Matrix().shear(hor, ver,
center || this.getPosition()));
center || this.getPosition(true)), apply);
},
/**
* Transform the item.
*
* @param {Matrix} matrix
* @param {Matrix} matrix the matrix by which the item shall be transformed.
* @param {Boolean} apply controls wether the transformation should just be
* concatenated to {@link #matrix} ({@code false}) or if it should directly
* be applied to item's content and its children.
*/
// Remove this for now:
// @param {String[]} flags Array of any of the following: 'objects',
// 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns',
// 'lines'. Default: ['objects', 'children']
transform: function(matrix, flags) {
// TODO: Handle flags, add TransformFlag class and convert to bit mask
// for quicker checking.
// TODO: Call transform on chidren only if 'children' flag is provided.
transform: function(matrix, apply) {
// Calling _changed will clear _bounds and _position, but depending
// on matrix we can calculate and set them again.
var bounds = this._bounds,
position = this._position,
children = this._children;
bounds = position = null;
// Simply preconcatenate the internal matrix with the passed one:
this._matrix.preConcatenate(matrix);
if (this._transform)
this._transform(matrix, flags);
// We need to call _changed even if we don't have _transform, since
// we're caching bounds on such items too now, e.g. Group
this._transform(matrix);
if (apply)
this.applyMatrix(false);
// We always need to call _changed since we're caching bounds on all
// items, including Group.
this._changed(Change.GEOMETRY);
// Detect matrices that contain only translations and scaling
// and transform the cached _bounds and _position without having to
@ -1855,12 +1868,33 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
// changes, since it's a LinkedPoint and would cause recursion!
this._position = matrix._transformPoint(position, position, true);
}
for (var i = 0, l = children && children.length; i < l; i++)
children[i].transform(matrix, flags);
// PORT: Return 'this' in all chainable commands
return this;
},
applyMatrix: function(recursive) {
if (this._applyMatrix(this._matrix, recursive)) {
// Set _matrix to the identity
// TODO: Introduce Matrix#setIdentity() and use it from
// #initialize() too?
this._matrix.initialize();
// TODO: This needs a _changed notification, but the GEOMETRY
// actually sdoesnt change! What to do?
}
},
_applyMatrix: function(matrix, recursive) {
if (this._children) {
for (var i = 0, l = this._children.length; i < l; i++) {
var child = this._children[i];
child.transform(matrix);
if (recursive)
child.applyMatrix(true);
}
return true;
}
},
/**
* Transform the item so that its {@link #bounds} fit within the specified
* rectangle, without changing its aspect ratio.
@ -1931,13 +1965,6 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
this.setBounds(newBounds);
},
/*
_transform: function(matrix, flags) {
// The code that performs the actual transformation of content,
// if defined. Item itself does not define this.
},
*/
toString: function() {
return (this.constructor._name || 'Item') + (this._name
? " '" + this._name + "'"

View file

@ -25,12 +25,5 @@
*/
var PlacedItem = this.PlacedItem = Item.extend(/** @lends PlacedItem# */{
// PlacedItem uses strokeBounds for bounds
_boundsType: { bounds: 'strokeBounds' },
_transform: function(matrix, flags) {
// In order to set the right context transformation when drawing the
// raster, simply preconcatenate the internal matrix with the provided
// one.
this._matrix.preConcatenate(matrix);
}
_boundsType: { bounds: 'strokeBounds' }
});

View file

@ -64,6 +64,7 @@ var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend(/** @lends PlacedSymbol
initialize: function(symbol, matrixOrOffset) {
this.base();
this.setSymbol(symbol instanceof Symbol ? symbol : new Symbol(symbol));
// XXX: Define one way of creating matrices and passing them to ctors
this._matrix = matrixOrOffset !== undefined
? matrixOrOffset instanceof Matrix
? matrixOrOffset

View file

@ -206,21 +206,20 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{
// path, with the added benefit that b can be < a, and closed looping is
// taken into account.
_transform: function(matrix, flags) {
if (matrix && !matrix.isIdentity()) {
var coords = new Array(6);
for (var i = 0, l = this._segments.length; i < l; i++) {
this._segments[i]._transformCoordinates(matrix, coords, true);
}
// TODO: Can't we access _style._fillColor, as we do in strokeBounds?
var fillColor = this.getFillColor(),
strokeColor = this.getStrokeColor();
// Try calling transform on colors in case they are GradientColors.
if (fillColor && fillColor.transform)
fillColor.transform(matrix);
if (strokeColor && strokeColor.transform)
strokeColor.transform(matrix);
_applyMatrix: function(matrix) {
var coords = new Array(6);
for (var i = 0, l = this._segments.length; i < l; i++) {
this._segments[i]._transformCoordinates(matrix, coords, true);
}
// TODO: Can't we access _style._fillColor, as we do in strokeBounds?
var fillColor = this.getFillColor(),
strokeColor = this.getStrokeColor();
// Try calling transform on colors in case they are GradientColors.
if (fillColor && fillColor.transform)
fillColor.transform(matrix);
if (strokeColor && strokeColor.transform)
strokeColor.transform(matrix);
return true;
},
/**

View file

@ -38,6 +38,7 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{
initialize: function(point) {
this.base();
this._point = Point.read(arguments).clone();
// XXX: Define one way of creating matrices and passing them to ctors
this._matrix = new Matrix().translate(this._point);
},
@ -62,9 +63,8 @@ var PointText = this.PointText = TextItem.extend(/** @lends PointText# */{
this.translate(Point.read(arguments).subtract(this._point));
},
_transform: function(matrix, flags) {
this._matrix.preConcatenate(matrix);
// Also transform _point:
_transform: function(matrix) {
// Transform _point:
matrix._transformPoint(this._point, this._point);
},