Shows how tight the openings between reinforcing bars are for concrete to pass, and reports the tightest place.
How the precision is kept
Openings and ratios are computed in 64-bit at the center of each grid cell, and the worst place and the count of tight cells are taken from those 64-bit values. The map itself is stored in two Float32Array. The share of tight cells that is shown to the person is counted again from the 32-bit copy.
Open item. Count the tight share from the 64-bit ratios, as the over count in congestion already does, instead of reading the 32-bit map back.
Calculations under this policy
6 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.
congestion.jsDetailCraft 0.8.1 32-bit in part6 calculationsReviewed
Tests tools/detailcraft-test/test-congestion.mjs
Clear spacing a bar asks for
64-bit
The least clear distance between parallel bars for a bar size and aggregate.
25.4 mm is one inch exactly. The 4/3 rule is evaluated as aggregate × 4 ÷ 3 in 64-bit. The result is a maximum of positive values, so nothing cancels.
congestion.js
lines 21 to 25
DetailCraft 0.8.1
/** The clear spacing a bar of this size asks for between parallel bars (mm). */
export function clearNeed(bar, { aggregate = 0 } = {}) {
if (bar.set === 'bs') return Math.max(bar.db, aggregate ? aggregate + 5 : 0, 20);
return Math.max(25.4, bar.db, aggregate ? aggregate * 4 / 3 : 0);
}
Free gap at a point
64-bit
The length of the free stretch that contains a point, between the bars either side of it.
gap = right − left; 0 inside a bar
Comparisons and one subtraction in 64-bit. A point exactly on a bar's edge counts as inside the bar, because the test uses ≤ and ≥.
congestion.js
lines 27 to 36
DetailCraft 0.8.1
/** The free interval containing `at` among blocking intervals [a, b] on a line from lo to hi. Returns its length (0 when `at` is inside a bar). */
function gapAt(at, intervals, lo, hi) {
let left = lo, right = hi;
for (const [a, b] of intervals) {
if (at >= a && at <= b) return 0;
if (b <= at && b > left) left = b;
if (a >= at && a < right) right = a;
}
return Math.max(0, right - left);
}
Need and grid size
64-bit
Sets what the concrete needs and how fine the map is.
cell = max(5, given, or max(10, ⌈max(L, B) ÷ 160 ÷ 5⌉ × 5)); columns = round(L ÷ cell)
The cell is at least 5 mm and by default about 1/160 of the longer side, rounded up to a multiple of 5 mm. Column and row counts are rounded, so the cell actually used is L ÷ columns by B ÷ rows.
Fills the map and finds the tightest opening and the number of cells that are too tight.
opening = min of the gaps in each layer; ratio = need ÷ opening
Gap and ratio are 64-bit locals, and worst and over are taken from them before anything is read back. The two maps are Float32Array copies, which the drawing and tightShare use. A cell inside a bar gets need ÷ 0.5 as its ratio and is not counted as tight.
congestion.js
lines 71 to 88
DetailCraft 0.8.1
const values = new Float32Array(cols * rows), opening = new Float32Array(cols * rows);
let worst = { x: 0, y: 0, ratio: 0, opening: Infinity }, over = 0;
const colLists = [];
for (let i = 0; i < cols; i++) { const x = x0 + (i + 0.5) * cw; colLists.push([throughY(LAYER_Y, x), throughY(LAYER_H, x)]); }
for (let j = 0; j < rows; j++) {
const y = y0 + (j + 0.5) * ch, rowX = through(LAYER_X, y), rowH = through(LAYER_H, y);
for (let i = 0; i < cols; i++) {
const x = x0 + (i + 0.5) * cw;
const gap = Math.min(gapAt(x, rowX, -L / 2 + c, L / 2 - c), gapAt(y, colLists[i][0], -B / 2 + c, B / 2 - c),
gapAt(x, rowH, -L / 2 + c, L / 2 - c), gapAt(y, colLists[i][1], -B / 2 + c, B / 2 - c));
opening[j * cols + i] = gap;
const ratio = gap > 0 ? need / gap : need / 0.5; // inside a bar: its own number, large
values[j * cols + i] = ratio;
if (gap > 0 && ratio > 1) over++; // a cell inside a bar is a bar, not a tight opening
if (gap > 0 && ratio > worst.ratio) worst = { x, y, ratio, opening: gap };
}
}
return { cols, rows, cell: cw, x0, y0, values, opening, need, worst, over, share: over / Math.max(1, values.reduce((n, _, k) => n + (opening[k] > 0 ? 1 : 0), 0)), bars, aggregate: Number(opts.aggregate) || 0, vibrator: Number(opts.vibrator) || 0 };
Share of the open area that is too tight
32-bit in part
The fraction of free cells whose ratio is over 1.
share = cells with ratio > 1 ÷ cells with opening > 0
Both tests read the Float32Array maps. A ratio between 1 and about 1.00000006 is stored as exactly 1 in 32 bits and is not counted here, although congestion counted it in over. This share decides whether a problem is reported and is the percentage shown.
congestion.js
lines 91 to 99
DetailCraft 0.8.1
/**
* The cells over 1 that are NOT a bar itself: concrete has to get down there. Cells inside a bar are ratio need/0.5 and skipped
* (a bar is not an opening). Returns the share of the free cells that are too tight.
*/
export function tightShare(map) {
let free = 0, tight = 0;
for (let k = 0; k < map.values.length; k++) { if (map.opening[k] <= 0) continue; free++; if (map.values[k] > 1) tight++; }
return free ? tight / free : 0;
}
The congestion problem
32-bit in part
The warning or error listed for a footing whose steel is too tight.
Nothing is reported unless the worst 64-bit ratio exceeds 1 and the 32-bit share is above 0. Opening and need are rounded to 0.1 mm, the share to a whole percent and the position to a whole millimeter, in the message and in the stored required and found values. It is an error when the opening is under half the need, otherwise a warning.
congestion.js
lines 113 to 120
DetailCraft 0.8.1
export function congestionProblems(map, { label = '', id = '', t = null, footing = null } = {}) {
const share = tightShare(map);
if (map.worst.ratio <= 1 || share === 0) return [];
const vars = { mark: label, opening: Math.round(map.worst.opening * 10) / 10, need: Math.round(map.need * 10) / 10, pct: Math.round(share * 100), x: Math.round(map.worst.x), y: Math.round(map.worst.y) };
const text = t ? t('cong.prob.tight', vars) : `${label}: the tightest place leaves ${vars.opening} mm for concrete to get down; ${vars.need} mm is needed (${vars.pct}% of the open area is tighter).`;
return [{ id: 'cong:tight:' + id, code: 'cong.tight', severity: map.worst.opening < map.need / 2 ? 'error' : 'warning', msg: { key: 'cong.prob.tight', vars }, text, about: [{ kind: 'footing', id, label }],
source: { kind: 'engine', name: 'detailcraft.congestion' }, req: { op: '>=', value: Math.round(map.need * 10) / 10, unit: 'mm' }, found: { value: vars.opening, unit: 'mm' } }];
}
In detail
Method
Need = the largest of the vibrator diameter and each bar's clear spacing rule: max(25.4 mm, d_b, 4/3 × aggregate), or for BS bars max(d_b, aggregate + 5 mm, 20 mm); the header cites ACI 318-19 25.2.1 and EN 1992-1-1 8.2. Opening = the narrowest free gap in any one layer of steel at the cell center. Value = need ÷ opening.
Number types
64-bit numbers for gaps and ratios; Float32Array for the two stored maps.
Tolerances
A starter bar within 1e-9 of the center in both directions has its hook placed along x.
Limits
Sampled at cell centers: a gap narrower than a cell can fall between samples. The reported opening and need are rounded to 0.1 mm and the share to a whole percent.
Approach: 32-bit step, flagged
One step stores or exchanges 32-bit values that reach a number a person reads. It is listed here with its open item, and the step is either moved to 64-bit or the app will warn where the number is read.