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 * @function
* @param {Number} scale the scale factor * @param {Number} scale the scale factor
* @param {Point} [center={@link Item#position}] * @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* *
* @example {@paperscript} * @example {@paperscript}
* // Scaling an item from its center point: * // 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} hor the horizontal scale factor
* @param {Number} ver the vertical scale factor * @param {Number} ver the vertical scale factor
* @param {Point} [center={@link Item#position}] * @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* *
* @example {@paperscript} * @example {@paperscript}
* // Scaling an item horizontally by 300%: * // 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% * // Scale the path horizontally by 300%
* circle.scale(3, 1); * circle.scale(3, 1);
*/ */
scale: function(hor, ver /* | scale */, center) { scale: function(hor, ver /* | scale */, center, apply) {
// See Matrix#scale for explanation of this: // See Matrix#scale for explanation of this:
if (arguments.length < 2 || typeof ver === 'object') { if (arguments.length < 2 || typeof ver === 'object') {
apply = center;
center = ver; center = ver;
ver = hor; ver = hor;
} }
return this.transform(new Matrix().scale(hor, ver, 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. * Translates (moves) the item by the given offset point.
* *
* @param {Point} delta the offset to translate the item by * @param {Point} delta the offset to translate the item by
* @param {Boolean} apply
*/ */
translate: function(delta) { translate: function(delta, apply) {
var mx = new Matrix(); 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 {Number} angle the rotation angle
* @param {Point} [center={@link Item#position}] * @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* @see Matrix#rotate * @see Matrix#rotate
* *
* @example {@paperscript} * @example {@paperscript}
@ -1770,9 +1775,9 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
* path.rotate(3, view.center); * path.rotate(3, view.center);
* } * }
*/ */
rotate: function(angle, center) { rotate: function(angle, center, apply) {
return this.transform(new Matrix().rotate(angle, 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. // 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 * @function
* @param {Point} point * @param {Point} point
* @param {Point} [center={@link Item#position}] * @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* @see Matrix#shear * @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} hor the horizontal shear factor.
* @param {Number} ver the vertical shear factor. * @param {Number} ver the vertical shear factor.
* @param {Point} [center={@link Item#position}] * @param {Point} [center={@link Item#position}]
* @param {Boolean} apply
* @see Matrix#shear * @see Matrix#shear
*/ */
shear: function(hor, ver, center) { shear: function(hor, ver, center, apply) {
// PORT: Add support for center back to Scriptographer too! // PORT: Add support for center and apply back to Scriptographer too!
// See Matrix#scale for explanation of this: // See Matrix#scale for explanation of this:
if (arguments.length < 2 || typeof ver === 'object') { if (arguments.length < 2 || typeof ver === 'object') {
apply = center;
center = ver; center = ver;
ver = hor; ver = hor;
} }
return this.transform(new Matrix().shear(hor, ver, return this.transform(new Matrix().shear(hor, ver,
center || this.getPosition())); center || this.getPosition(true)), apply);
}, },
/** /**
* Transform the item. * 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: // Remove this for now:
// @param {String[]} flags Array of any of the following: 'objects', // @param {String[]} flags Array of any of the following: 'objects',
// 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns', // 'children', 'fill-gradients', 'fill-patterns', 'stroke-patterns',
// 'lines'. Default: ['objects', 'children'] // 'lines'. Default: ['objects', 'children']
transform: function(matrix, flags) { transform: function(matrix, apply) {
// 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.
// Calling _changed will clear _bounds and _position, but depending // Calling _changed will clear _bounds and _position, but depending
// on matrix we can calculate and set them again. // on matrix we can calculate and set them again.
var bounds = this._bounds, var bounds = this._bounds,
position = this._position, position = this._position,
children = this._children; children = this._children;
bounds = position = null;
// Simply preconcatenate the internal matrix with the passed one:
this._matrix.preConcatenate(matrix);
if (this._transform) if (this._transform)
this._transform(matrix, flags); this._transform(matrix);
// We need to call _changed even if we don't have _transform, since if (apply)
// we're caching bounds on such items too now, e.g. Group this.applyMatrix(false);
// We always need to call _changed since we're caching bounds on all
// items, including Group.
this._changed(Change.GEOMETRY); this._changed(Change.GEOMETRY);
// Detect matrices that contain only translations and scaling // Detect matrices that contain only translations and scaling
// and transform the cached _bounds and _position without having to // 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! // changes, since it's a LinkedPoint and would cause recursion!
this._position = matrix._transformPoint(position, position, true); 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 // PORT: Return 'this' in all chainable commands
return this; 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 * Transform the item so that its {@link #bounds} fit within the specified
* rectangle, without changing its aspect ratio. * rectangle, without changing its aspect ratio.
@ -1931,13 +1965,6 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{
this.setBounds(newBounds); 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() { toString: function() {
return (this.constructor._name || 'Item') + (this._name return (this.constructor._name || 'Item') + (this._name
? " '" + this._name + "'" ? " '" + this._name + "'"

View file

@ -25,12 +25,5 @@
*/ */
var PlacedItem = this.PlacedItem = Item.extend(/** @lends PlacedItem# */{ var PlacedItem = this.PlacedItem = Item.extend(/** @lends PlacedItem# */{
// PlacedItem uses strokeBounds for bounds // PlacedItem uses strokeBounds for bounds
_boundsType: { bounds: 'strokeBounds' }, _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);
}
}); });

View file

@ -64,6 +64,7 @@ var PlacedSymbol = this.PlacedSymbol = PlacedItem.extend(/** @lends PlacedSymbol
initialize: function(symbol, matrixOrOffset) { initialize: function(symbol, matrixOrOffset) {
this.base(); this.base();
this.setSymbol(symbol instanceof Symbol ? symbol : new Symbol(symbol)); 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 this._matrix = matrixOrOffset !== undefined
? matrixOrOffset instanceof Matrix ? matrixOrOffset instanceof Matrix
? matrixOrOffset ? 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 // path, with the added benefit that b can be < a, and closed looping is
// taken into account. // taken into account.
_transform: function(matrix, flags) { _applyMatrix: function(matrix) {
if (matrix && !matrix.isIdentity()) { var coords = new Array(6);
var coords = new Array(6); for (var i = 0, l = this._segments.length; i < l; i++) {
for (var i = 0, l = this._segments.length; i < l; i++) { this._segments[i]._transformCoordinates(matrix, coords, true);
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);
} }
// 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) { initialize: function(point) {
this.base(); this.base();
this._point = Point.read(arguments).clone(); 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); 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)); this.translate(Point.read(arguments).subtract(this._point));
}, },
_transform: function(matrix, flags) { _transform: function(matrix) {
this._matrix.preConcatenate(matrix); // Transform _point:
// Also transform _point:
matrix._transformPoint(this._point, this._point); matrix._transformPoint(this._point, this._point);
}, },