ergogen/src/anchor.js

131 lines
4.2 KiB
JavaScript
Raw Normal View History

2021-01-01 00:50:04 +01:00
const u = require('./utils')
const a = require('./assert')
const Point = require('./point')
2022-01-09 22:56:05 +01:00
const mirror_ref = exports.mirror = (ref, mirror=true) => {
2021-07-18 16:03:45 +02:00
if (mirror) {
if (ref.startsWith('mirror_')) {
return ref.substring(7)
}
2022-01-09 22:56:05 +01:00
return 'mirror_' + ref
2021-07-18 16:03:45 +02:00
}
return ref
}
2022-02-27 11:11:45 +01:00
const aggregator_common = ['parts', 'method']
const aggregators = {
average: (config, name, parts) => {
a.unexpected(config, name, aggregator_common)
let x = 0, y = 0, r = 0
const len = parts.length
for (const part of parts) {
x += part.x
y += part.y
r += part.r
}
return new Point(x / len, y / len, r / len)
}
}
const anchor = exports.parse = (raw, name, points={}, start=new Point(), mirror=false) => units => {
2022-02-27 11:11:45 +01:00
//
// Anchor type handling
//
if (a.type(raw)() == 'string') {
raw = {ref: raw}
}
else if (a.type(raw)() == 'array') {
// recursive call with incremental start mods, according to `affect`s
let current = start.clone()
2022-02-27 11:11:45 +01:00
let index = 1
2021-01-01 00:50:04 +01:00
for (const step of raw) {
2022-02-27 11:11:45 +01:00
current = anchor(step, `${name}[${index++}]`, points, current, mirror)(units)
2021-01-01 00:50:04 +01:00
}
return current
}
2022-02-27 11:11:45 +01:00
a.unexpected(raw, name, ['ref', 'aggregate', 'orient', 'shift', 'rotate', 'affect', 'resist'])
2022-02-27 11:11:45 +01:00
//
// Reference or aggregate handling
//
let point = start.clone()
2022-02-27 11:11:45 +01:00
if (raw.ref !== undefined && raw.aggregate !== undefined) {
throw new Error(`Fields "ref" and "aggregate" cannot appear together in anchor "${name}"!`)
}
2021-01-01 00:50:04 +01:00
if (raw.ref !== undefined) {
2022-02-27 11:11:45 +01:00
// base case, resolve directly
if (a.type(raw.ref)() == 'string') {
2021-07-18 16:03:45 +02:00
const parsed_ref = mirror_ref(raw.ref, mirror)
a.assert(points[parsed_ref], `Unknown point reference "${parsed_ref}" in anchor "${name}"!`)
point = points[parsed_ref].clone()
2022-02-27 11:11:45 +01:00
// recursive case
} else {
point = anchor(raw.ref, `${name}.ref`, points, start, mirror)(units)
2021-01-01 00:50:04 +01:00
}
}
2022-02-27 11:11:45 +01:00
if (raw.aggregate !== undefined) {
raw.aggregate = a.sane(raw.aggregate, `${name}.aggregate`, 'object')()
raw.aggregate.method = a.sane(raw.aggregate.method || 'average', `${name}.aggregate.method`, 'string')()
a.assert(aggregators[raw.aggregate.method], `Unknown aggregator method "${raw.aggregate.method}" in anchor "${name}"!`)
raw.aggregate.parts = a.sane(raw.aggregate.parts || [], `${name}.aggregate.parts`, 'array')()
const parts = []
let index = 1
for (const part of raw.aggregate.parts) {
parts.push(anchor(part, `${name}.aggregate.parts[${index++}]`, points, start, mirror)(units))
2022-02-27 11:11:45 +01:00
}
point = aggregators[raw.aggregate.method](raw.aggregate, `${name}.aggregate`, parts)
}
//
// 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, resist)
// recursive case: points turns "towards" target anchor
} else {
const target = anchor(config, name, points, start, mirror)(units)
point.r = point.angle(target)
}
}
2021-01-01 00:50:04 +01:00
if (raw.orient !== undefined) {
rotator(raw.orient, `${name}.orient`, point)
2021-01-01 00:50:04 +01:00
}
if (raw.shift !== undefined) {
2021-05-22 17:58:26 +02:00
let xyval = a.wh(raw.shift, `${name}.shift`)(units)
point.shift(xyval, true, resist)
2021-01-01 00:50:04 +01:00
}
if (raw.rotate !== undefined) {
rotator(raw.rotate, `${name}.rotate`, point)
2021-01-01 00:50:04 +01:00
}
if (raw.affect !== undefined) {
const candidate = point.clone()
point = start.clone()
point.meta = candidate.meta
2021-05-22 17:58:26 +02:00
let affect = raw.affect
2021-01-01 21:46:01 +01:00
if (a.type(affect)() == 'string') affect = affect.split('')
2021-01-01 00:50:04 +01:00
affect = a.strarr(affect, `${name}.affect`)
let i = 0
2021-05-22 17:58:26 +02:00
for (const aff of affect) {
a.in(aff, `${name}.affect[${++i}]`, ['x', 'y', 'r'])
point[aff] = candidate[aff]
2021-01-01 00:50:04 +01:00
}
}
2022-02-27 11:11:45 +01:00
2021-01-01 00:50:04 +01:00
return point
}