Resources Numerical accuracy Calculation policy

Drawn floors: outline, openings and quantities

Checks a floor outline drawn by hand, cuts openings in it, and works out its perimeter, area and volume.

How the precision is kept

Area is the shoelace sum and perimeter a sum of edge lengths, in 64-bit plan millimeters. The outline is saved as text with 3 decimals of the file's own length unit and rebuilt from that text when the floor is read, so its resolution depends on the unit: 0.001 mm in a file in millimeters, 1 mm in a file in meters. Crossing and inside tests use the sign of a cross product with no tolerance.

Open item. Save a drawn floor's outline in millimeters with full precision, so that a file whose unit is the meter does not round it to 1 mm.

Calculations under this policy

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

model-floors.js BimCraft 0.8.1 64-bit 7 calculations Reviewed

Tests tools/bimcraft-test/draw06-test.mjs, tools/bimcraft-test/modify-test.mjs

Area of an outline

64-bit

The signed area of a polygon.

A = ½ Σ (xᵢ yᵢ₊₁ − yᵢ xᵢ₊₁)

The sum uses the corners' own plan coordinates, not coordinates measured from the first corner, so its terms grow with the floor's distance from the story's origin and cancel in the sum.

  • model-floors.js
  • line 31
  • BimCraft 0.8.1
const area = (p) => { let s = 0; for (let i = 0; i < p.length; i++) { const a = p[i], b = p[(i + 1) % p.length]; s += a[0] * b[1] - a[1] * b[0]; } return s / 2; };

Perimeter of an outline

64-bit

The total length of the outline's edges.

P = Σ |pᵢ₊₁ − pᵢ|

A 64-bit sum of Math.hypot edge lengths added in order.

  • model-floors.js
  • line 32
  • BimCraft 0.8.1
const perim = (p) => p.reduce((s, a, i) => { const b = p[(i + 1) % p.length]; return s + Math.hypot(b[0] - a[0], b[1] - a[1]); }, 0);

Do two edges cross

64-bit

A yes or no on whether two segments properly cross.

cross when o(a, b, c) × o(a, b, d) < 0 and o(c, d, a) × o(c, d, b) < 0

Each orientation is the sign of a 64-bit cross product with no tolerance and no exact arithmetic, so a nearly collinear case is decided by rounding. A product of exactly zero, an end touching the other segment, counts as not crossing.

  • model-floors.js
  • lines 33 to 36
  • BimCraft 0.8.1
function crosses(a, b, c, d) {
  const o = (p, q, r) => Math.sign((q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]));
  return o(a, b, c) * o(a, b, d) < 0 && o(c, d, a) * o(c, d, b) < 0;
}

Is the outline usable

64-bit

Refuses an outline with fewer than 3 corners, one that crosses itself, or one with almost no area.

refuse when |A| < 10 000 mm²

The area limit is 10 000 mm², an absolute value. Self-crossing is tested on every pair of edges that are not neighbors.

  • model-floors.js
  • lines 45 to 49
  • BimCraft 0.8.1
export function polyProblem(points) {
  if (!points || points.length < 3) return 'model.err.fl.shape';
  if (selfCrossing(points) || Math.abs(area(points)) < 10000) return 'model.err.fl.shape';   // under 0.01 m²
  return null;
}

Is a point inside the outline

64-bit

The even-odd test used to check that an opening lies in its floor.

A ray is cast along x and crossings are counted; the crossing's x comes from one division by the edge's height, which is never zero for an edge the test accepts. No tolerance: a point on the boundary can fall either way.

  • model-floors.js
  • lines 50 to 57
  • BimCraft 0.8.1
function inPoly(p, poly) {
  let c = false;
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
    const a = poly[i], b = poly[j];
    if ((a[1] > p[1]) !== (b[1] > p[1]) && p[0] < (b[0] - a[0]) * (p[1] - a[1]) / (b[1] - a[1]) + a[0]) c = !c;
  }
  return c;
}

Saving and reading the outline

64-bit

The outline's points written as text in the file's length unit, and read back.

text = round(x ÷ unit, 3) , round(y ÷ unit, 3)

Each coordinate is converted to the file's unit and rounded to 3 decimals. In a file whose unit is the millimeter that is 0.001 mm; in a file in meters it is 1 mm. The floor's outline, and the area reported for it, are rebuilt from this text.

  • model-floors.js
  • lines 63 to 64
  • BimCraft 0.8.1
const ptsText = (p) => p.map((q) => round(q[0], 3) + ',' + round(q[1], 3)).join(';');
const ptsOf = (s) => String(s || '').split(';').map((x) => x.split(',').map(Number)).filter((q) => q.length === 2 && q.every(Number.isFinite));

Perimeter, area and volume written on a floor

64-bit

The slab quantities, gross and net of the openings.

net area = A − Σ |hole|; volume = area × thickness

Hole areas are made positive and added, then subtracted once from the gross area; volumes are that area times the thickness. The model writer stores areas and volumes rounded to 6 decimals of a square or cubic meter.

  • model-floors.js
  • lines 91 to 92
  • BimCraft 0.8.1
const hArea = holes.reduce((s, h) => s + Math.abs(area(h.points)), 0), A = Math.abs(area(pts));
w.upsertQto(f.id, 'Qto_SlabBaseQuantities', [['Width', 'length', f.thickness], ['Perimeter', 'length', perim(pts)], ['GrossArea', 'area', A], ['NetArea', 'area', A - hArea], ['GrossVolume', 'volume', A * f.thickness], ['NetVolume', 'volume', (A - hArea) * f.thickness]]);

In detail

Method
Shoelace area; even-odd ray casting for a point in the outline; segment crossing by the signs of four orientation tests.
Number types
64-bit numbers; outline points as text 'x,y;…' in the file.
Tolerances
An outline under 10 000 mm² (0.01 m²) is refused. Thickness under 10 mm is refused. No tolerance in the crossing and inside tests. Saved points have 3 decimals of the file's unit.
Checked by
tools/bimcraft-test/draw06-test.mjs calls polyProblem, placeDrawnFloor and addFloorOpening in Node.
Limits
Edges that only touch, or lie along each other, are not counted as crossing.

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