Skip to content

Commit

Permalink
Merge pull request #521 from jscad/v2-fix-transforms
Browse files Browse the repository at this point in the history
V2 : fix transforms
  • Loading branch information
z3dev authored Mar 18, 2020
2 parents e64677f + 2e60c19 commit 2e9b6bc
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 47 deletions.
33 changes: 24 additions & 9 deletions packages/modeling/src/operations/transforms/mirror.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { mat4, plane } = require('../../math')
const { geom2, geom3, path2 } = require('../../geometry')

/**
* Mirror the given object(s) using the given options (if any)
* Mirror the given object(s) using the given options (if any).
* Note: The normal should be given as 90 degrees from the plane origin.
* @param {Object} options - options for mirror
* @param {Array} [options.origin=[0,0,0]] - the origin of the plane
Expand All @@ -27,26 +27,41 @@ const mirror = (options, ...objects) => {
if (objects.length === 0) throw new Error('wrong number of arguments')

const planeOfMirror = plane.fromNormalAndPoint(normal, origin)
const matrix = mat4.mirrorByPlane(planeOfMirror)
// verify the plane, i.e. check that the given normal was valid
if (Number.isNaN(planeOfMirror[0])) {
throw new Error('the given origin and normal do not define a proper plane')
}

// special check to verify the plane, i.e. check that the given normal was valid
const validPlane = !Number.isNaN(planeOfMirror[0])
const matrix = mat4.mirrorByPlane(planeOfMirror)

const results = objects.map((object) => {
if (validPlane) {
if (path2.isA(object)) return path2.transform(matrix, object)
if (geom2.isA(object)) return geom2.transform(matrix, object)
if (geom3.isA(object)) return geom3.transform(matrix, object)
}
if (path2.isA(object)) return path2.transform(matrix, object)
if (geom2.isA(object)) return geom2.transform(matrix, object)
if (geom3.isA(object)) return geom3.transform(matrix, object)
return object
})
return results.length === 1 ? results[0] : results
}

/**
* Mirror the given object(s) about the X axis.
* @param {Object|Array} objects - the objects(s) to mirror
* @return {Object|Array} the mirrored object(s)
*/
const mirrorX = (...objects) => mirror({ normal: [1, 0, 0] }, objects)

/**
* Mirror the given object(s) about the Y axis.
* @param {Object|Array} objects - the objects(s) to mirror
* @return {Object|Array} the mirrored object(s)
*/
const mirrorY = (...objects) => mirror({ normal: [0, 1, 0] }, objects)

/**
* Mirror the given object(s) about the Z axis.
* @param {Object|Array} objects - the objects(s) to mirror
* @return {Object|Array} the mirrored object(s)
*/
const mirrorZ = (...objects) => mirror({ normal: [0, 0, 1] }, objects)

module.exports = {
Expand Down
11 changes: 0 additions & 11 deletions packages/modeling/src/operations/transforms/mirror.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,3 @@ test('mirror: mirroring of multiple objects produces an array of mirrored object
]
t.deepEqual(obs, exp)
})

test('mirror: mirroring about NO axis should return original objects', t => {
let junk = 'hello'
let geometry1 = path2.fromPoints({}, [[-5, 5], [5, 5], [-5, -5], [10, -5]])
let geometry2 = geom2.fromPoints([[-5, -5], [0, 5], [10, -5]])

let mirrored = mirror({ normal: [0, 0, 0] }, junk, geometry1, geometry2)
t.is(mirrored[0], junk)
t.is(mirrored[1], geometry1)
t.is(mirrored[2], geometry2)
})
25 changes: 23 additions & 2 deletions packages/modeling/src/operations/transforms/rotate.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const mat4 = require('../../math/mat4')
const { geom2, geom3, path2 } = require('../../geometry')

/**
* Rotate the given object(s) using the given options (if any)
* Rotate the given object(s) using the given options.
* @param {Number[]} angles - angle (RADIANS) of rotations about X, Y, and X axis
* @param {Object|Array} objects - the objects(s) to rotate
* @return {Object|Array} the rotated object(s)
Expand All @@ -15,11 +15,14 @@ const { geom2, geom3, path2 } = require('../../geometry')
*/
const rotate = (angles, ...objects) => {
if (!Array.isArray(angles)) throw new Error('angles must be an array')
if (angles.length !== 3) throw new Error('angles must contain X, Y and Z values')

objects = flatten(objects)
if (objects.length === 0) throw new Error('wrong number of arguments')

// adjust the angles if necessary
angles = angles.slice() // don't modify the original
while (angles.length < 3) angles.push(0)

let yaw = angles[2]
let pitch = angles[1]
let roll = angles[0]
Expand All @@ -35,10 +38,28 @@ const rotate = (angles, ...objects) => {
return results.length === 1 ? results[0] : results
}

/**
* Rotate the given object(s) about the X axis, using the given options.
* @param {Number} angle - angle (RADIANS) of rotations about X
* @param {Object|Array} objects - the objects(s) to rotate
* @return {Object|Array} the rotated object(s)
*/
const rotateX = (angle, ...objects) => rotate([angle, 0, 0], objects)

/**
* Rotate the given object(s) about the Y axis, using the given options.
* @param {Number} angle - angle (RADIANS) of rotations about Y
* @param {Object|Array} objects - the objects(s) to rotate
* @return {Object|Array} the rotated object(s)
*/
const rotateY = (angle, ...objects) => rotate([0, angle, 0], objects)

/**
* Rotate the given object(s) about the Z axis, using the given options.
* @param {Number} angle - angle (RADIANS) of rotations about Z
* @param {Object|Array} objects - the objects(s) to rotate
* @return {Object|Array} the rotated object(s)
*/
const rotateZ = (angle, ...objects) => rotate([0, 0, angle], objects)

module.exports = {
Expand Down
4 changes: 2 additions & 2 deletions packages/modeling/src/operations/transforms/rotate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', t => {
let geometry = geom3.fromPoints(points)

// rotate about X
let rotated = rotate([Math.PI/2, 0, 0], geometry)
let rotated = rotate([Math.PI/2], geometry)
let obs = geom3.toPoints(rotated)
let exp = [
[ new Float32Array([ -2, 12, -7 ]), new Float32Array([ -2, -18, -7 ]),
Expand All @@ -77,7 +77,7 @@ test('rotate: rotating of a geom3 produces expected changes to polygons', t => {
t.deepEqual(obs, exp)

// rotate about Y
rotated = rotate([0, -Math.PI/2, 0], geometry)
rotated = rotate([0, -Math.PI/2], geometry)
obs = geom3.toPoints(rotated)
exp = [
[ new Float32Array([ 12, -7, -2 ]), new Float32Array([ -18, -7, -2 ]),
Expand Down
30 changes: 26 additions & 4 deletions packages/modeling/src/operations/transforms/scale.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const mat4 = require('../../math/mat4')
const { geom2, geom3, path2 } = require('../../geometry')

/**
* Scale the given object(s) using the given options (if any)
* @param {Array} factors - X, Y, Z factors by which to scale the object
* Scale the given object(s) using the given options.
* @param {Number[]} factors - X, Y, Z factors by which to scale the object
* @param {Object|Array} objects - the objects(s) to scale
* @return {Object|Array} the scaled object(s)
*
Expand All @@ -15,12 +15,16 @@ const { geom2, geom3, path2 } = require('../../geometry')
*/
const scale = (factors, ...objects) => {
if (!Array.isArray(factors)) throw new Error('factors must be an array')
if (factors.length !== 3) throw new Error('factors must contain X, Y and Z values')
if (factors[0] <= 0 || factors[1] <= 0 || factors[2] <= 0) throw new Error('factors must be positive')

objects = flatten(objects)
if (objects.length === 0) throw new Error('wrong number of arguments')

// adjust the factors if necessary
factors = factors.slice() // don't modify the original
while (factors.length < 3) factors.push(1)

if (factors[0] <= 0 || factors[1] <= 0 || factors[2] <= 0) throw new Error('factors must be positive')

const matrix = mat4.fromScaling(factors)

const results = objects.map(function (object) {
Expand All @@ -32,10 +36,28 @@ const scale = (factors, ...objects) => {
return results.length === 1 ? results[0] : results
}

/**
* Scale the given object(s) about the X axis using the given options.
* @param {Number} factor - X factor by which to scale the object
* @param {Object|Array} objects - the objects(s) to scale
* @return {Object|Array} the scaled object(s)
*/
const scaleX = (offset, ...objects) => scale([offset, 1, 1], objects)

/**
* Scale the given object(s) about the Y axis using the given options.
* @param {Number} factor - Y factor by which to scale the object
* @param {Object|Array} objects - the objects(s) to scale
* @return {Object|Array} the scaled object(s)
*/
const scaleY = (offset, ...objects) => scale([1, offset, 1], objects)

/**
* Scale the given object(s) about the Z axis using the given options.
* @param {Number} factor - Z factor by which to scale the object
* @param {Object|Array} objects - the objects(s) to scale
* @return {Object|Array} the scaled object(s)
*/
const scaleZ = (offset, ...objects) => scale([1, 1, offset], objects)

module.exports = {
Expand Down
12 changes: 6 additions & 6 deletions packages/modeling/src/operations/transforms/scale.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test('scale: scaling of a path2 produces expected changes to points', t => {
let geometry = path2.fromPoints({}, [[0, 4], [1, 0]])

// scale X
let scaled = scale([3, 1, 1], geometry)
let scaled = scale([3], geometry)
let obs = path2.toPoints(scaled)
let exp = [
new Float32Array([0, 4]),
Expand All @@ -21,7 +21,7 @@ test('scale: scaling of a path2 produces expected changes to points', t => {
t.deepEqual(obs, exp)

// scale Y
scaled = scale([1, 0.5, 1], geometry)
scaled = scale([1, 0.5], geometry)
obs = path2.toPoints(scaled)
exp = [
new Float32Array([0, 2]),
Expand All @@ -38,7 +38,7 @@ test('scale: scaling of a geom2 produces expected changes to points', t => {
let geometry = geom2.fromPoints([[-1, 0], [1, 0], [0, 1]])

// scale X
let scaled = scale([3, 1, 1], geometry)
let scaled = scale([3], geometry)
let obs = geom2.toPoints(scaled)
let exp = [
new Float32Array([-3, 0]),
Expand All @@ -52,7 +52,7 @@ test('scale: scaling of a geom2 produces expected changes to points', t => {
t.deepEqual(obs, exp)

// scale Y
scaled = scale([1, 3, 1], geometry)
scaled = scale([1, 3], geometry)
obs = geom2.toPoints(scaled)
exp = [
new Float32Array([-1, 0]),
Expand All @@ -78,7 +78,7 @@ test('scale: scaling of a geom3 produces expected changes to polygons', t => {
let geometry = geom3.fromPoints(points)

// scale X
let scaled = scale([3, 1, 1], geometry)
let scaled = scale([3], geometry)
let obs = geom3.toPoints(scaled)
let exp = [
[ new Float32Array([ -6, -7, -12 ]), new Float32Array([ -6, -7, 18 ]),
Expand All @@ -101,7 +101,7 @@ test('scale: scaling of a geom3 produces expected changes to polygons', t => {
t.deepEqual(obs, exp)

// scale Y
scaled = scale([1, 0.5, 1], geometry)
scaled = scale([1, 0.5], geometry)
obs = geom3.toPoints(scaled)
exp = [
[ new Float32Array([ -2, -3.5, -12 ]), new Float32Array([ -2, -3.5, 18 ]),
Expand Down
35 changes: 28 additions & 7 deletions packages/modeling/src/operations/transforms/translate.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ const mat4 = require('../../math/mat4')
const { geom2, geom3, path2 } = require('../../geometry')

/**
* Translate the given object(s) using the given options (if any)
* @param {Array} offsets - offsets of which to translate the object
* Translate the given object(s) using the given options.
* @param {Number[]} offset - offset (vector) of which to translate the object
* @param {Object|Array} objects - the objects(s) to translate
* @return {Object|Array} the translated object(s)
*
* @example
* const newsphere = translate({offsets: [5, 0, 10]}, sphere())
* const newsphere = translate([5, 0, 10], sphere())
*/
const translate = (offsets, ...objects) => {
if (!Array.isArray(offsets)) throw new Error('offsets must be an array')
if (offsets.length !== 3) throw new Error('offsets must contain X, Y and Z values')
const translate = (offset, ...objects) => {
if (!Array.isArray(offset)) throw new Error('offset must be an array')

objects = flatten(objects)
if (objects.length === 0) throw new Error('wrong number of arguments')

const matrix = mat4.fromTranslation(offsets)
// adjust the offset if necessary
offset = offset.slice() // don't modify the original
while (offset.length < 3) offset.push(0)

const matrix = mat4.fromTranslation(offset)

const results = objects.map(function (object) {
if (path2.isA(object)) return path2.transform(matrix, object)
Expand All @@ -31,10 +34,28 @@ const translate = (offsets, ...objects) => {
return results.length === 1 ? results[0] : results
}

/**
* Translate the given object(s) along the X axis using the given options.
* @param {Number} offset - X offset of which to translate the object
* @param {Object|Array} objects - the objects(s) to translate
* @return {Object|Array} the translated object(s)
*/
const translateX = (offset, ...objects) => translate([offset, 0, 0], objects)

/**
* Translate the given object(s) along the Y axis using the given options.
* @param {Number} offset - Y offset of which to translate the object
* @param {Object|Array} objects - the objects(s) to translate
* @return {Object|Array} the translated object(s)
*/
const translateY = (offset, ...objects) => translate([0, offset, 0], objects)

/**
* Translate the given object(s) along the Z axis using the given options.
* @param {Number} offset - Z offset of which to translate the object
* @param {Object|Array} objects - the objects(s) to translate
* @return {Object|Array} the translated object(s)
*/
const translateZ = (offset, ...objects) => translate([0, 0, offset], objects)

module.exports = {
Expand Down
12 changes: 6 additions & 6 deletions packages/modeling/src/operations/transforms/translate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test('translate: translating of a path2 produces expected changes to points', t
let line = path2.fromPoints({}, [[0, 0], [1, 0]])

// translate X
let translated = translate([1, 0, 0], line)
let translated = translate([1], line)
let obs = path2.toPoints(translated)
let exp = [
new Float32Array([1, 0]),
Expand All @@ -21,7 +21,7 @@ test('translate: translating of a path2 produces expected changes to points', t
t.deepEqual(obs, exp)

// translate Y
translated = translate([0, 1, 0], line)
translated = translate([0, 1], line)
obs = path2.toPoints(translated)
exp = [
new Float32Array([0, 1]),
Expand All @@ -38,7 +38,7 @@ test('translate: translating of a geom2 produces expected changes to points', t
let geometry = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])

// translate X
let translated = translate([1, 0, 0], geometry)
let translated = translate([1], geometry)
let obs = geom2.toPoints(translated)
let exp = [
new Float32Array([1, 0]),
Expand All @@ -52,7 +52,7 @@ test('translate: translating of a geom2 produces expected changes to points', t
t.deepEqual(obs, exp)

// translate Y
translated = translate([0, 1, 0], geometry)
translated = translate([0, 1], geometry)
obs = geom2.toPoints(translated)
exp = [
new Float32Array([0, 1]),
Expand All @@ -78,7 +78,7 @@ test('translate: translating of a geom3 produces expected changes to polygons',
let geometry = geom3.fromPoints(points)

// translate X
let translated = translate([3, 0, 0], geometry)
let translated = translate([3], geometry)
let obs = geom3.toPoints(translated)
let exp = [
[ new Float32Array([ 1, -7, -12 ]), new Float32Array([ 1, -7, 18 ]),
Expand All @@ -101,7 +101,7 @@ test('translate: translating of a geom3 produces expected changes to polygons',
t.deepEqual(obs, exp)

// translated Y
translated = translate([0, 3, 0], geometry)
translated = translate([0, 3], geometry)
obs = geom3.toPoints(translated)
exp = [
[ new Float32Array([ -2, -4, -12 ]), new Float32Array([ -2, -4, 18 ]),
Expand Down

0 comments on commit 2e9b6bc

Please sign in to comment.