← Natura·Docs/Modules/Source

verbs/flee.js

40 lines · in natura-src-1.0.0.zip as natura/verbs/flee.js · a module · its entry in the module reference

// verbs/flee.js -- ONE life ontology verb, one concern: move AWAY from a local threat (the inverse of forage). Pure
// and RENDER-FREE. Generalized from the CANONICAL herbivore flight in ecosystem.js (a grazer sums an away-vector
// from predators within a NEAR radius, goes WARY at a wider PERCEIVE radius) -- lifted OUT of that one system so
// every life system shares it instead of re-implementing it inline (the bad habit these steers are correcting).
// Universal: a butterfly from a hunting bird, a herd from a wolf, a shoal from a shark. UMD.
(function (root, factory) {
  if (typeof module !== 'undefined' && module.exports) module.exports = factory();
  else (root.NaturaVerbs = root.NaturaVerbs || {}).flee = factory();
})(typeof self !== 'undefined' ? self : this, function () {
  'use strict';
  // flee(pos, threats, opts) -> { fx, fy (escape vector), wary, fleeing, dist }
  //   pos = {x,y} ; threats = [{x,y}] ; opts = {
  //     near:0.12       -- bolt inside this radius
  //     perceive:near*2 -- go wary (but not yet bolt) inside this radius
  //     raw:false       -- false: sum UNIT away-directions (each threat equal weight) and return a NORMALIZED unit
  //                        escape vector (a butterfly, a bird -- direction is all that matters). true: sum the RAW
  //                        away-displacements (a nearer threat within `near` pushes softer than a farther one -- the
  //                        distance-weighted herd flight tuned in ecosystem.js) and return that sum UN-normalized.
  //     delta:(pos,threat)->[dx,dy]  -- the away-vector metric; default euclidean. A WRAPPED world passes its own
  //                        toroidal delta so the verb stays geometry-agnostic (it never assumes a flat plane).
  //   }
  function flee(pos, threats, opts) {
    opts = opts || {};
    const near = opts.near != null ? opts.near : 0.12, perceive = opts.perceive != null ? opts.perceive : near * 2;
    const raw = !!opts.raw, delta = opts.delta || euclid;
    const n2 = near * near, p2 = perceive * perceive;
    let fx = 0, fy = 0, wary = false, fleeing = false, nd = Infinity;
    for (let i = 0; i < threats.length; i++) {
      const d = delta(pos, threats[i]), dx = d[0], dy = d[1], d2 = dx * dx + dy * dy;
      if (d2 < p2) { wary = true;
        if (d2 < n2) { fleeing = true; nd = Math.min(nd, d2);
          if (raw) { fx += dx; fy += dy; }                                          // distance-weighted, left un-normalized
          else { const dm = Math.sqrt(d2) || 1e-4; fx += dx / dm; fy += dy / dm; } } }   // equal-weight unit directions
    }
    if (!raw) { const m = Math.sqrt(fx * fx + fy * fy) || 1; fx /= m; fy /= m; }    // normalize only the unit-mode escape
    return { fx: fx, fy: fy, wary: wary, fleeing: fleeing, dist: Math.sqrt(nd) };
  }
  function euclid(p, t) { return [p.x - t.x, p.y - t.y]; }
  return { flee };
});