Resources Numerical accuracy Calculation policy

Starter library: plants and entourage sizes

Builds a plant or entourage component at the size the plant list gives.

How the precision is kept

Entourage parts are stored in millimeters and divided by 1000 into the model's meters. A plant is scaled so its measured extent matches the species' spread and height. All arithmetic is 64-bit.

Calculations under this policy

2 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

starter.js ModelCraft 0.8.1 64-bit 2 calculations Reviewed

Tests bin/test-modelcraft.mjs

Entourage parts into the model

64-bit

Converts each part's triangles from millimeters to meters.

p (m) = p (mm) ÷ 1000

Each coordinate is divided by 1000, which is correctly rounded. A scale, when given, is placed in the part's placement matrix and not multiplied into the points.

  • starter.js
  • lines 96 to 109
  • ModelCraft 0.8.1
/** One entourage item's parts into component c; `recolor` {part name: '#rrggbb'} and `omit` [part names] are how a scene changes the season (0.8 EG.61). */
function entourageParts(m, c, e, recolor = {}, omit = [], scale = null) {
  for (const p of e.parts) {
    if (omit.includes(p.name)) continue;
    const polys = [], color = p.alpha < 1 ? 'mat:glass-tinted' : (recolor[p.name] || p.color);
    for (let i = 0; i < p.I.length; i += 3) {
      const v = (k) => [p.P[p.I[i + k] * 3] / 1000, p.P[p.I[i + k] * 3 + 1] / 1000, p.P[p.I[i + k] * 3 + 2] / 1000];
      polys.push({ loops: [[v(0), v(1), v(2)]], color });
    }
    const d = meshDef(m, polys, part(p.name), 'group');
    softenEdges(d, [...d.edges.keys()], e.cat === 'site/vehicles' ? 25 : 75);
    m.place(c, d.id, scale ? [scale[0], 0, 0, 0, 0, scale[1], 0, 0, 0, 0, scale[2], 0, 0, 0, 0, 1] : ident());
  }
}

Plant scaled to its listed size

64-bit

The scale that makes a plant model as wide and tall as the species' data says.

s_xy = 1000 × spread ÷ max(Δx, Δy, 1); s_z = 1000 × height ÷ max(z_max, 1)

The extent is the min and max over the parts' own points. The height divides by the top z, taking the base as z = 0. Turf is a square slab of the spread with a thickness of at least 0.02.

  • starter.js
  • lines 133 to 157
  • ModelCraft 0.8.1
/**
 * A plant of the shared plant list (plants/plants.js, 0.8 LD.1) as a new component: the entourage model nearest its form
 * (`modelOf`), scaled to the species' size at maturity (or at planting with which 'plant'); turf is a thin green slab.
 * Call inside model.change. `starter` = 'plant:<key>' so picking the same plant again places another copy.
 */
export function buildPlant(model, sp, name, { which = 'mature' } = {}) {
  const sz = (which === 'plant' && sp.size.plant) || sp.size.mature;
  const c = model.newDef('component', model.uniqueName(name || sp.botanical));
  const e = ENTOURAGE.find((x) => 'entourage:' + x.slug === modelOf(sp));
  if (e) {
    // the parts' own extent (e.size is the nominal one), so the plant ends up the size the list says
    let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity, z1 = 0;
    for (const p of e.parts) for (let i = 0; i < p.P.length; i += 3) { x0 = Math.min(x0, p.P[i]); x1 = Math.max(x1, p.P[i]); y0 = Math.min(y0, p.P[i + 1]); y1 = Math.max(y1, p.P[i + 1]); z1 = Math.max(z1, p.P[i + 2]); }
    const sxy = (sz.s * 1000) / Math.max(x1 - x0, y1 - y0, 1);
    entourageParts(model, c, e, {}, [], [sxy, sxy, (sz.h * 1000) / Math.max(z1, 1)]);
  } else {
    const d = model.newDef('group', part('Turf'));
    addPolygon(d, rectPts([-sz.s / 2, -sz.s / 2, 0], [1, 0, 0], [0, 1, 0], sz.s, sz.s));
    pushPull(d, [...d.faces.keys()][0], Math.max(0.02, sz.h));
    paintFaces(d, [...d.faces.keys()], GREEN);
    model.place(c, d.id, ident());
  }
  c.starter = 'plant:' + sp.key;
  return c;
}

In detail

Method
Scale factor = wanted size in millimeters ÷ measured extent of the parts.
Number types
64-bit numbers.
Tolerances
The divisor is never below 1 (millimeter).
Limits
Spread uses the larger of the two plan extents, so the smaller one ends up below the listed spread.

Approach: 64-bit, direct

Every value is a 64-bit number from input to result. Formulas are closed form or a direct solve, and every comparison uses a stated tolerance.

Every policy that takes this approach