Resources Numerical accuracy Calculation policy

Scattered plants, rocks and people: how many and where

Works out how many items a scatter set asks for from the area it covers and its density, and picks the points they stand on.

How the precision is kept

The target area is summed in 64-bit from the target's world triangles (a Float64Array by the file's own description), and the count is that area times the density, rounded to a whole item. Which points are used is set by a seeded random sequence, so the same set and seed give the same items. The placement matrices that result are stored in a Float32Array and only feed the renderer.

Calculations under this policy

4 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.

rt/rt-scatter.js BimCraft 0.8.1 64-bit 4 calculations Reviewed

Tests tools/rt/test.mjs

Target area and the count asked for

64-bit

The area of the faces a scatter set covers and the number of items its density asks for.

area = Σ ½ |e₁ × e₂|; asked = min(2,000,000, round(area × density)); paint: Σ max(1, round(π r² × density))

Areas are added in triangle order into one 64-bit total, which also serves as the cumulative table in a Float64Array. The count is rounded to a whole number with Math.round. The budget is truncated with a bitwise OR, which acts on a count of instances, not on a measured value.

  • rt/rt-scatter.js
  • lines 74 to 90
  • BimCraft 0.8.1
const cdf = new Float64Array(nT), nrm = new Float64Array(nT * 3);
let area = 0;
for (let i = 0; i < nT; i++) {
  const o = i * 9;
  const ax = tris[o + 3] - tris[o], ay = tris[o + 4] - tris[o + 1], az = tris[o + 5] - tris[o + 2];
  const bx = tris[o + 6] - tris[o], by = tris[o + 7] - tris[o + 1], bz = tris[o + 8] - tris[o + 2];
  const cx = ay * bz - az * by, cy = az * bx - ax * bz, cz = ax * by - ay * bx, l = Math.hypot(cx, cy, cz);
  area += l / 2; cdf[i] = area;
  let nx = cx / (l || 1), ny = cy / (l || 1), nz = cz / (l || 1);
  if (nx * U[0] + ny * U[1] + nz * U[2] < 0) { nx = -nx; ny = -ny; nz = -nz; }   // faces scatter on their upper side
  nrm[i * 3] = nx; nrm[i * 3 + 1] = ny; nrm[i * 3 + 2] = nz;
}
let candidates = 0;
if (S.mode === 'fill') candidates = Math.round(area * S.density);
else for (const s of S.strokes) candidates += Math.max(1, Math.round(Math.PI * s[3] * s[3] * S.density));
const wanted = Math.min(MAX_INSTANCES, candidates);
const cap = Math.min(wanted, Math.max(0, budget | 0));

A random point on the target

64-bit

Picks a triangle in proportion to its area and a point inside it.

P = A + a (B − A) + b (C − A), (a, b) reflected when a + b > 1

The triangle is found by binary search of the 64-bit cumulative areas; the index halving is a bit shift on an index. The point is a 64-bit blend of the three corners.

  • rt/rt-scatter.js
  • lines 147 to 153
  • BimCraft 0.8.1
const u = R() * area;
let lo = 0, hi = nT - 1;
while (lo < hi) { const mid = (lo + hi) >> 1; if (cdf[mid] < u) lo = mid + 1; else hi = mid; }
let a = R(), b = R();
if (a + b > 1) { a = 1 - a; b = 1 - b; }
const o = lo * 9;
for (let k = 0; k < 3; k++) { p[k] = tris[o + k] + (tris[o + 3 + k] - tris[o + k]) * a + (tris[o + 6 + k] - tris[o + k]) * b; n[k] = nrm[lo * 3 + k]; }

Dropping a point onto the target

64-bit

Where a vertical ray from a brush dab meets a target triangle.

t = (e₂·q) ÷ det, det = e₁·(d × e₂)

Möller–Trumbore in 64-bit. The ray starts 1,000 m above the point, so t is near 1,000 and the hit keeps correspondingly fewer digits after the decimal point. A determinant under 1e-12 is treated as parallel; the threshold is absolute, not scaled to the triangle.

  • rt/rt-scatter.js
  • lines 184 to 198
  • BimCraft 0.8.1
/** Möller–Trumbore, two-sided: t > 0 or −1. */
function rayTri(T, o, from, d) {
  const e1x = T[o + 3] - T[o], e1y = T[o + 4] - T[o + 1], e1z = T[o + 5] - T[o + 2];
  const e2x = T[o + 6] - T[o], e2y = T[o + 7] - T[o + 1], e2z = T[o + 8] - T[o + 2];
  const px = d[1] * e2z - d[2] * e2y, py = d[2] * e2x - d[0] * e2z, pz = d[0] * e2y - d[1] * e2x;
  const det = e1x * px + e1y * py + e1z * pz;
  if (Math.abs(det) < 1e-12) return -1;
  const inv = 1 / det, tx = from[0] - T[o], ty = from[1] - T[o + 1], tz = from[2] - T[o + 2];
  const u = (tx * px + ty * py + tz * pz) * inv;
  if (u < 0 || u > 1) return -1;
  const qx = ty * e1z - tz * e1y, qy = tz * e1x - tx * e1z, qz = tx * e1y - ty * e1x;
  const v = (d[0] * qx + d[1] * qy + d[2] * qz) * inv;
  if (v < 0 || u + v > 1) return -1;
  return (e2x * qx + e2y * qy + e2z * qz) * inv;
}

Slope and height limits

64-bit

Drops a candidate that stands on too steep a face or outside the height range.

keep when n·up ≥ cos(slope) − 1e-9 and min ≤ p·up ≤ max

Dot products in 64-bit against the cosine of the slope limit, with an absolute slack of 1e-9 so a face exactly at the limit is kept.

  • rt/rt-scatter.js
  • lines 163 to 166
  • BimCraft 0.8.1
if (dot(n, U) < cosMax - 1e-9) continue;
const hgt = dot(p, U);
if ((S.height[0] !== null && hgt < S.height[0]) || (S.height[1] !== null && hgt > S.height[1])) continue;
if (!clear()) continue;

In detail

Method
Triangle area as half the length of a cross product, added in triangle order into a running total used as a cumulative table; a point by binary search of that table and two random numbers folded into the triangle; paint mode drops points onto the target by a two-sided Möller–Trumbore ray test.
Number types
Float64Array for the area table and normals; 64-bit numbers for points; Float32Array for the instance matrices; Uint16Array for item indexes.
Tolerances
Ray and triangle parallel when the determinant is under 1e-12 (absolute). Slope limit compared with cos(slope) − 1e-9. Caps: 2,000,000 instances, density 400 per m², 4,000 brush dabs.
Limits
The count placed can be lower than the count asked for: slope, height and clearance filters and the instance budget remove candidates. Instance positions are kept to 32-bit in the matrices.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach