From a7276ce95d75a223416f012d2bf73762b12e0dee Mon Sep 17 00:00:00 2001 From: joostdecock Date: Thu, 6 Apr 2023 09:50:55 +0200 Subject: [PATCH] wip(core): Work on supported part-level transforms in layouting This is some initial work to support part-level (SVG) transforms when layouting the pattern. It updates the method that calculates the bounding box to calculate a bounding box with transforms applied. It also adds two new methods to utils to help with that, methods that could be useful for other people trying to do fancy transform stuff. --- packages/core/src/part.mjs | 9 +++ packages/core/src/utils.mjs | 132 ++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) diff --git a/packages/core/src/part.mjs b/packages/core/src/part.mjs index 6c1579c5985..96f79c9da8a 100644 --- a/packages/core/src/part.mjs +++ b/packages/core/src/part.mjs @@ -269,6 +269,15 @@ Part.prototype.__boundary = function () { if (bottomRight.x === -Infinity) bottomRight.x = 0 if (bottomRight.y === -Infinity) bottomRight.y = 0 + // Handle part transforms + const transforms = this.attributes.get('transform') + if (transforms) { + const combinedTransform = + typeof transforms === 'string' ? transforms : utils.combineTransforms(transforms) + topLeft = utils.applyTransformToPoint(combinedTransform, topLeft) + bottomRight = utils.applyTransformToPoint(combinedTransform, bottomRight) + } + this.topLeft = topLeft this.bottomRight = bottomRight this.width = this.bottomRight.x - this.topLeft.x diff --git a/packages/core/src/utils.mjs b/packages/core/src/utils.mjs index 84eb6031262..d2ead571933 100644 --- a/packages/core/src/utils.mjs +++ b/packages/core/src/utils.mjs @@ -665,3 +665,135 @@ export function __isCoord(value) { export function __macroName(name) { return `__macro_${name}` } + +/** + * Helper method to parse an (SVG) transform string + * + * @private + * @param {string} transform - The SVG transform string + * @return {object} result - An object with the parts, name, and values + */ +function __parseTransform(transform) { + const parts = transform.match(/(\w+)\(([^\)]+)\)/) + const name = parts[1] + const values = parts[2].split(/,\s*/).map(parseFloat) + + return { parts, name, values } +} + +/** + * Combines an array of (SVG) transforms into a single matrix transform + * + * @param {array} transorms - The list of transforms to combine + * @return {string} matrixTransform - The combined matrix transform + */ +export function combineTransforms(transforms = []) { + // Don't bother if there are no part transforms + if (transforms.length < 1) return '' + + // The starting matrix + let matrix = [1, 0, 0, 1, 0, 0] + + // Loop through the transforms + for (let i = 0; i < transforms.length; i++) { + // Parse the transform string + const { parts, name, values } = __parseTransform(transforms[i]) + + // Update matrix for transform + switch (name) { + case 'matrix': + matrix = [ + matrix[0] * values[0] + matrix[2] * values[1], + matrix[1] * values[0] + matrix[3] * values[1], + matrix[0] * values[2] + matrix[2] * values[3], + matrix[1] * values[2] + matrix[3] * values[3], + matrix[0] * values[4] + matrix[2] * values[5] + matrix[4], + matrix[1] * values[4] + matrix[3] * values[5] + matrix[5], + ] + break + case 'translate': + matrix[4] += matrix[0] * values[0] + matrix[2] * values[1] + matrix[5] += matrix[1] * values[0] + matrix[3] * values[1] + break + case 'scale': + matrix[0] *= values[0] + matrix[1] *= values[0] + matrix[2] *= values[1] + matrix[3] *= values[1] + break + case 'rotate': + const angle = (values[0] * Math.PI) / 180 + const cos = Math.cos(angle) + const sin = Math.sin(angle) + matrix = [ + matrix[0] * cos + matrix[2] * sin, + matrix[1] * cos + matrix[3] * sin, + matrix[0] * -sin + matrix[2] * cos, + matrix[1] * -sin + matrix[3] * cos, + matrix[4], + matrix[5], + ] + break + case 'skewX': + matrix[2] += matrix[0] * Math.tan((values[0] * Math.PI) / 180) + matrix[3] += matrix[1] * Math.tan((values[0] * Math.PI) / 180) + break + case 'skewY': + matrix[0] += matrix[2] * Math.tan((values[0] * Math.PI) / 180) + matrix[1] += matrix[3] * Math.tan((values[0] * Math.PI) / 180) + break + } + } + + // Return the combined matrix transform + return 'matrix(' + matrix.join(' ') + ')' +} + +/** + * Applies and (SVG) transform to a point's coordinates (x and y) + * + * @param {string} transorm - The transform to apply + * @param {Point} point - The point of which to update the coordinates + * @return {Point} point - The point with the transform applied to its coordinates + */ +export function applyTransformToPoint(transform, point) { + // Parse the transform string + const { parts, name, values } = __parseTransform(transform) + + // The starting matrix + let matrix = [1, 0, 0, 1, 0, 0] + + // Update matrix for transform + switch (name) { + case 'matrix': + matrix = values + break + case 'translate': + matrix[4] = values[0] + matrix[5] = values[1] + break + case 'scale': + matrix[0] = values[0] + matrix[3] = values[1] + break + case 'rotate': + const angle = (values[0] * Math.PI) / 180 + const cos = Math.cos(angle) + const sin = Math.sin(angle) + console.log('in rotate', { angle }) + matrix = [cos, sin, -sin, cos, 0, 0] + break + case 'skewX': + matrix[2] = Math.tan((values[0] * Math.PI) / 180) + break + case 'skewY': + matrix[1] = Math.tan((values[0] * Math.PI) / 180) + break + } + + // Apply the matrix transform to the coordinates + point.x = point.x * matrix[0] + point.y * matrix[2] + matrix[4] + point.y = point.x * matrix[1] + point.y * matrix[3] + matrix[5] + + return point +}