diff --git a/src/anchor.js b/src/anchor.js index 73fde64..0a06d96 100644 --- a/src/anchor.js +++ b/src/anchor.js @@ -28,7 +28,7 @@ const aggregators = { } } -const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), mirror=false) => units => { +const anchor = exports.parse = (raw, name, points={}, start=new Point(), mirror=false) => units => { // // Anchor type handling @@ -39,8 +39,8 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), } else if (a.type(raw)() == 'array') { - // recursive call with incremental default_point mods, according to `affect`s - let current = default_point.clone() + // recursive call with incremental start mods, according to `affect`s + let current = start.clone() let index = 1 for (const step of raw) { current = anchor(step, `${name}[${index++}]`, points, current, mirror)(units) @@ -48,13 +48,13 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), return current } - a.unexpected(raw, name, ['ref', 'aggregate', 'orient', 'shift', 'rotate', 'affect']) + a.unexpected(raw, name, ['ref', 'aggregate', 'orient', 'shift', 'rotate', 'affect', 'resist']) // // Reference or aggregate handling // - let point = default_point.clone() + let point = start.clone() if (raw.ref !== undefined && raw.aggregate !== undefined) { throw new Error(`Fields "ref" and "aggregate" cannot appear together in anchor "${name}"!`) } @@ -67,7 +67,7 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), point = points[parsed_ref].clone() // recursive case } else { - point = anchor(raw.ref, `${name}.ref`, points, default_point, mirror)(units) + point = anchor(raw.ref, `${name}.ref`, points, start, mirror)(units) } } @@ -80,7 +80,7 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), const parts = [] let index = 1 for (const part of raw.aggregate.parts) { - parts.push(anchor(part, `${name}.aggregate.parts[${index++}]`, points, default_point, mirror)(units)) + parts.push(anchor(part, `${name}.aggregate.parts[${index++}]`, points, start, mirror)(units)) } point = aggregators[raw.aggregate.method](raw.aggregate, `${name}.aggregate`, parts) @@ -90,14 +90,15 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), // Actual orient/shift/rotate/affect handling // + resist = a.sane(raw.resist || false, `${name}.resist`, 'boolean')() const rotator = (config, name, point) => { // simple case: number gets added to point rotation if (a.type(config)(units) == 'number') { let angle = a.sane(config, name, 'number')(units) - point.rotate(angle, false) + point.rotate(angle, false, resist) // recursive case: points turns "towards" target anchor } else { - const target = anchor(config, name, points, default_point, mirror)(units) + const target = anchor(config, name, points, start, mirror)(units) point.r = point.angle(target) } } @@ -107,14 +108,14 @@ const anchor = exports.parse = (raw, name, points={}, default_point=new Point(), } if (raw.shift !== undefined) { let xyval = a.wh(raw.shift, `${name}.shift`)(units) - point.shift(xyval) + point.shift(xyval, true, resist) } if (raw.rotate !== undefined) { rotator(raw.rotate, `${name}.rotate`, point) } if (raw.affect !== undefined) { const candidate = point.clone() - point = default_point.clone() + point = start.clone() point.meta = candidate.meta let affect = raw.affect if (a.type(affect)() == 'string') affect = affect.split('') diff --git a/src/point.js b/src/point.js index d6d5e12..160e1fa 100644 --- a/src/point.js +++ b/src/point.js @@ -24,8 +24,8 @@ module.exports = class Point { [this.x, this.y] = val } - shift(s, relative=true) { - s[0] *= this.meta.mirrored ? -1 : 1 + shift(s, relative=true, resist=false) { + s[0] *= (!resist && this.meta.mirrored) ? -1 : 1 if (relative) { s = m.point.rotate(s, this.r) } @@ -34,8 +34,8 @@ module.exports = class Point { return this } - rotate(angle, origin=[0, 0]) { - angle *= this.meta.mirrored ? -1 : 1 + rotate(angle, origin=[0, 0], resist=false) { + angle *= (!resist && this.meta.mirrored) ? -1 : 1 if (origin) { this.p = m.point.rotate(this.p, angle, origin) } diff --git a/test/unit/anchor.js b/test/unit/anchor.js index 135ad2a..c9b3dd1 100644 --- a/test/unit/anchor.js +++ b/test/unit/anchor.js @@ -126,6 +126,30 @@ describe('Anchor', function() { ) }) + it('resist', function() { + const p = new Point(0, 0, 0, {mirrored: true}) // origin, but mirrored + + // resistance should be correctly propagated for shifts + check( + parse({shift: [1, 1]}, 'name', {}, p)(), + [-1, 1, 0, {mirrored: true}] + ) + check( + parse({shift: [1, 1], resist: true}, 'name', {}, p)(), + [1, 1, 0, {mirrored: true}] + ) + + // ...and orients/rotations too + check( + parse({rotate: 10}, 'name', {}, p)(), + [0, 0, -10, {mirrored: true}] + ) + check( + parse({rotate: 10, resist: true}, 'name', {}, p)(), + [0, 0, 10, {mirrored: true}] + ) + }) + it('string', function() { // basic string form check( diff --git a/test/unit/point.js b/test/unit/point.js index 5f4b03f..33ef530 100644 --- a/test/unit/point.js +++ b/test/unit/point.js @@ -36,7 +36,7 @@ describe('Point', function() { it('shifting', function() { const p = new Point(0, 0, -90) // at origin, "looking right" - // absolute shift up and left, should be up and left + // non-relative shift up and left, should be up and left check(p.clone().shift([-1, 1], false), [-1, 1, -90, {}]) // relative shift up and left, should be up and right check(p.clone().shift([-1, 1]), [1, 1, -90, {}]) @@ -50,6 +50,19 @@ describe('Point', function() { check(p.clone().rotate(-90, [1, 1]), [1, 2, -90, {}]) }) + it('resistance', function() { + const p = new Point(0, 0, 0, {mirrored: true}) // origin, but mirrored + // non-relative shift up and left, mirroring changes it to up and right + check(p.clone().shift([-1, 1], false), [1, 1, 0, {mirrored: true}]) + // ...but resistance keeps it up and left + check(p.clone().shift([-1, 1], false, true), [-1, 1, 0, {mirrored: true}]) + + // mirroring changes rotation direction, too + check(p.clone().rotate(-90), [0, 0, 90, {mirrored: true}]) + // ...but not when resistance is applied + check(p.clone().rotate(-90, false, true), [0, 0, -90, {mirrored: true}]) + }) + it('mirroring', function() { const p = new Point(0, 1, 0) // make sure mirroring inverts rotation, as well as positions correctly