Writes the whole project as an IFC file: coordinates, alignment parameters, lengths, areas, volumes and unit factors as text.
How the precision is kept
Values are computed in 64-bit and then written as decimal text with a fixed number of decimal places: 6 for coordinates, lengths, areas, volumes and other quantities, 9 for ratios, speeds and spiral constants, 12 for directions and gradients, 15 for polynomial coefficients and 16 for the unit factor. Coordinates are written relative to a file origin rounded to 100 units, so 6 decimals is one millionth of a file unit. The file therefore carries fewer digits than the project holds.
Open item. Write coordinates, alignment parameters and quantities with enough significant digits to read back the same 64-bit number (up to 17) instead of a fixed number of decimal places.
Calculations under this policy
25 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.
text = v.toFixed(d) with trailing zeros removed; d = 6 unless stated
The value is rounded to d decimal places, 6 by default, so the file holds an absolute resolution of 1e-6 rather than 15 significant digits. A value below half of the last place is written as 0, and a value that is not finite is written as 0.
ifc-civil.js
line 58
CivilCraft 0.8.1
const R = (v, d = 6) => { if (!fin(v)) return '0.'; let s = (Math.abs(v) < 0.5 * 10 ** -d ? 0 : v).toFixed(d).replace(/(\.\d*?)0+$/, '$1'); if (!s.includes('.')) s += '.'; return s === '-0.' ? '0.' : s; };
Points and directions
64-bit
Writes a Cartesian point and a direction.
Point coordinates are written with 6 decimals and direction ratios with 12. Identical text is written once and shared.
The local origin that coordinates are written from: the middle of the content rounded to 100 units.
origin = round((min + max) ÷ 200) × 100
The origin is a whole multiple of 100, so subtracting it from a coordinate removes the large part without adding a fraction. Elevations are not shifted (z origin 0).
ifc-civil.js
lines 131 to 144
CivilCraft 0.8.1
/** The middle of everything, rounded to 100 units (z stays 0: elevations are written as they are). */
export function fileOrigin(m) {
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
const see = (x, y) => { if (fin(x) && fin(y)) { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); } };
for (const a of m.alignments) for (const q of a.poly) see(q.x, q.y);
for (const s of m.surfaces) for (const q of s.pts) see(q.x, q.y);
for (const p of m.pads) for (let i = 0; i < (p.top?.pos.length || 0); i += 3) see(p.top.pos[i], p.top.pos[i + 1]);
for (const s of m.network?.structures || []) see(s.x, s.y);
for (const r of m.parcels) for (const q of r.pts) see(q.x, q.y);
for (const e of m.landscape) if (e.at) see(e.at.x, e.at.y);
for (const ms of m.masses) for (const q of ms.pts) see(q.x, q.y);
if (!fin(x0)) return { x: 0, y: 0, z: 0 };
return { x: Math.round((x0 + x1) / 200) * 100, y: Math.round((y0 + y1) / 200) * 100, z: 0 };
}
Unit factor to SI
Exact
Meters per file unit, and the conversion of areas and volumes to square and cubic meters.
um = 1, 0.3048 or 1200 ÷ 3937; m² = a × um²; m³ = v × um³
The international foot 0.3048 is exact and the US survey foot is formed as the fraction 1200 ÷ 3937 (FT_US_M in ifc-massing.js). Area and volume factors are built by repeated multiplication by um.
ifc-civil.js
line 151
CivilCraft 0.8.1
const unit = fileUnit(units, crs), um = unit === 'm' ? 1 : unit === 'ft' ? 0.3048 : FT_US_M, m2 = (a) => a * um * um, m3 = (v) => v * um * um * um;
Conversion factor in the file
64-bit
Writes the foot's length in meters into the file's unit definition.
The factor is written with 16 decimals. For 1200 ÷ 3937 that gives 0.3048006096012192, which reads back as the same 64-bit number; 0.3048 is written as 0.3048.
Writes lengths, areas, volumes, ratios, speeds and counts in property sets and quantity sets.
Lengths, areas, volumes, counts and weights are written with 6 decimals; ratios and speeds with 9. Areas and volumes are converted to SI first and rounded after. A positive length below 1e-6 is written as 1e-6, and integer and count values are rounded to whole numbers.
The area of a surface, corridor part or slope in square meters.
area = Σ |(b − a) × (c − a)| ÷ 2 × um²
Triangle areas from differences of coordinates, added in a plain running sum, then multiplied by the squared unit factor.
ifc-civil.js
line 152
CivilCraft 0.8.1
const meshArea = (m) => { let s = 0; for (let i = 0; i < m.idx.length; i += 3) s += triArea(m.pos, m.idx[i], m.idx[i + 1], m.idx[i + 2]); return m2(s); };
Triangle meshes
64-bit
Writes a surface or solid as a point list and triangles.
x′ = x − origin_x, written with 6 decimals
Each vertex has the file origin subtracted and is then rounded to 6 decimals. Two vertices closer than that can be written as the same coordinates; the triangle indexes are not changed.
Writes a latitude or longitude as degrees, minutes, seconds and millionths of a second.
Degrees, minutes and seconds are taken with Math.floor and the remainder is rounded to a millionth of an arc second, the resolution of the IFC compound angle. The remainder is found by repeated subtraction and multiplication by 60.
The grid easting and northing of the file origin, and the rotation and scale of a local ground system.
E₀ = to_x + s·(cos r·Δx − sin r·Δy); N₀ = to_y + s·(sin r·Δx + cos r·Δy)
Eastings, northings and height are written with 6 decimals; the rotation as cos and sin with 12 decimals and the scale with 12. Without a local system the easting and northing are the whole-number origin itself.
Writes each alignment's PIs and PVIs into a property set, which is what CivilCraft reads back from IFC4 and IFC2x3 files.
x, y, radius, spiral lengths, stations, elevations and curve lengths are rounded to 6 decimals with toFixed(6), and cant to 3 decimals. The PI coordinates here are absolute project coordinates, not shifted to the file origin, so with coordinates in the millions 6 decimals is 13 significant digits.
Decides whether two neighboring segments are written as continuous, same direction, or same direction and curvature.
Directions count as equal within 1e-7 rad, after the difference is wrapped through atan2(sin, cos). Curvatures count as equal within 1e-9 (absolute, per file unit), and an end curvature below 1e-12 counts as straight.
ifc-civil.js
lines 359 to 363
CivilCraft 0.8.1
const sameDir = (d1, d2) => Math.abs(Math.atan2(Math.sin(d1 - d2), Math.cos(d1 - d2))) < 1e-7;
let trans;
if (!nx) trans = Math.abs(kEnd) < 1e-12 ? '.CONTSAMEGRADIENTSAMECURVATURE.' : '.CONTSAMEGRADIENT.';
else if (!sameDir(dirEnd, nx.dir)) trans = '.CONTINUOUS.';
else trans = Math.abs(kEnd - kNext) < 1e-9 ? '.CONTSAMEGRADIENTSAMECURVATURE.' : '.CONTSAMEGRADIENT.';
Spiral terms
64-bit
The constants of Bloss, cosine, sine and Helmert transition curves.
A_i = L × |a_i|^(−1/n) × sign a_i; f = L ÷ R₁ − L ÷ R₀; a₀ = L ÷ R₀
Each term is one 64-bit expression written with 9 decimals. f is a difference of two curvature terms; for a spiral between a straight and a curve one of them is zero, so nothing cancels.
The coefficient of y = x³ ÷ (6·R·Lp) for a cubic transition.
A₃ = 1 ÷ (6 × R × Lp)
The coefficient is written with 15 decimal places, which is an absolute resolution on a very small number: for R = 1000 and Lp = 100 the text is 0.000001666666667, 10 significant digits, and fewer for larger curves.
Writes each grade or vertical curve: start distance, height, gradients, and the curve as a line or a polynomial.
C = (g₁ − g₀) ÷ (2L); line length = L ÷ cos(atan g₀); curve length = parabola length
Heights and lengths are written with 6 decimals, gradients with 12 and the parabola coefficient C with 15 decimal places (absolute, so a flat curve keeps fewer significant digits). The placement direction is cos and sin of atan(g₀).
The true length of a vertical curve y = A + Bx + Cx² over its horizontal length.
F(x) = (d·√(1 + d²) + asinh d) ÷ 4C, d = B + 2Cx; length = F(L) − F(0)
Closed form. The two F values are each divided by 4C and then subtracted, so for a small C they are large and nearly equal and the difference loses digits; below |C| = 1e-15 the straight-line formula is used instead.
ifc-civil.js
lines 125 to 130
CivilCraft 0.8.1
/** Length of y = A + Bx + Cx² over 0 … L (closed form; a straight line when C = 0). */
export function parabolaLength(B, C, Lh) {
if (Math.abs(C) < 1e-15) return Lh * Math.hypot(1, B);
const F = (x) => { const d = B + 2 * C * x, r = Math.hypot(1, d); return (d * r + Math.asinh(d)) / (4 * C); };
return F(Lh) - F(0);
}
Cant axis
64-bit
The leaning axis of a cant segment from the height difference of the two rails.
The square root argument is clamped at zero. Cant values in millimeters are converted to file units by ÷ 1000 ÷ um.
ifc-civil.js
line 285
CivilCraft 0.8.1
const axis = (half) => { const dy = 2 * half, dz = Math.sqrt(Math.max(0, base * base - dy * dy)); return w.dir([0, dy / base, dz / base]); };
Helmert cant halves
64-bit
The two second-order spirals of a Helmert cant transition and the slope where they meet.
slope = (L ÷ 2)² × (2·(L ÷ 2) ÷ A₂³)
The slope at the middle is computed from +A2a, the quadratic term parsed back from its text already rounded to 9 decimals, not from the unrounded value.
A pipe as a circle swept from the upstream to the downstream invert.
L₃ = hypot(Δx, Δy, Δz); axis = Δ ÷ L₃; start z = invert + D ÷ 2
The axis is the 3D difference divided by its length; pipes shorter than 1e-9 are skipped. The reference direction divides by the plan length of the axis, or by 1 when the pipe is vertical. The swept depth is raised to at least 1e-6.
The weight of each rail from its length and section.
weight = length × um × kg per meter
The length in file units is converted to meters by the unit factor and multiplied by the section's mass per meter; the result is written with 6 decimals.
Converts a design speed in km/h or mph for the road design property.
m/s = km/h ÷ 3.6; m/s = mph × 0.44704
0.44704 is the exact meters per second in a mile per hour; the km/h factor is 1 ÷ 3.6 formed by division. The converted speed is written with 9 decimals.
ifc-civil.js
line 272
CivilCraft 0.8.1
const MPS = { 'km/h': 1 / 3.6, mph: 0.44704 };
Building mass footprint and height
64-bit
The footprint area, height and gross floor area written for each building mass.
The shoelace sum uses absolute project coordinates with no shift to a local origin. The sign of the sum decides whether the outline is reversed before it is written.
ifc-civil.js
lines 813 to 816
CivilCraft 0.8.1
let P = m.pts, a = 0;
for (let i = 0; i < P.length; i++) { const p = P[i], q = P[(i + 1) % P.length]; a += p.x * q.y - q.x * p.y; }
if (a < 0) P = P.slice().reverse();
const Hm = m.floors * m.floorHeight, fp = Math.abs(a / 2);
In detail
Method
ISO 10303-21 text; each real through toFixed(d) with trailing zeros removed. Areas and volumes converted to SI with the unit factor before rounding.
Number types
64-bit numbers, written as text with 6 to 16 decimal places.
Tolerances
Values smaller than half a unit in the last written place become 0. Positive lengths are raised to at least 1e-6. Same direction between segments: 1e-7 rad. Same curvature: 1e-9. Parabola treated as a line when |C| < 1e-15. Pipes shorter than 1e-9 are left out.
Limits
A fixed number of decimals is an absolute resolution: small values such as a cubic coefficient keep few significant digits. The PI and PVI table that CivilCraft reads back from IFC4 and IFC2x3 files is rounded to 6 decimals. Latitude and longitude are written to a millionth of an arc second, which is the IFC format's own resolution. The geometric context states a precision of 1e-5.
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.