Turns gizmo drags, clicks on the ground plane and edge extrusions in the Form workspace into new positions of the control cage that defines a freeform shape.
How the precision is kept
The cage's vertices are plain arrays of 64-bit numbers and the edits here are short 64-bit expressions. A point clicked on the ground plane is rounded to 0.001 before it is stored. The transform itself is applied by form.js; this file builds the matrix from the gizmo with three.js Matrix4. Drawing buffers and analysis colors in this file are 32-bit and only feed the screen.
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.
The transform applied to the selected cage vertices while the gizmo is dragged.
M = W_now × W_start⁻¹
The matrix is the pivot's current world matrix times the inverse saved when the drag began, both computed by three.js Matrix4, and is always applied to the cage as it was at the start of the drag, so repeated updates during one drag do not accumulate. The application to the vertices is in form.js transform.
The position stored when a face is drawn by clicks.
existing vertex within 9 px, else ray ∩ plane y = 0 rounded to 0.001
A click near an existing cage vertex copies that vertex exactly. Otherwise the ray's hit on the ground plane is rounded with Math.round(v × 1000) ÷ 1000 before it is kept, so the stored coordinates are multiples of 0.001.
ws-form.js
lines 249 to 264
PartCraft 0.8.1
function faceClick(host, x, y) {
const { THREE, view } = host;
const cage = cageOf(host) || FM.emptyForm();
let p = null;
cage.verts.forEach((q) => { const s = project(host, q); if (!p && Math.hypot(s[0] - x, s[1] - y) < 9) p = [...q]; });
if (!p) {
const ray = view.ray(x, y).ray;
const hit = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), new THREE.Vector3());
if (!hit) { host.status(t('form.face.click_ground')); return; }
p = hit.toArray().map((v) => Math.round(v * 1000) / 1000);
}
S.faceDots.push(p);
drawDots(host);
if (S.faceDots.length === 4) finishFace(host);
else host.status(t('form.face.point', { n: S.faceDots.length }), true);
}
Direction of an edge extrusion
64-bit
The direction and distance open edges are extruded by.
d = Σ (edge middle − face center); offset = d ÷ |d| × distance
Face centers and edge middles are 64-bit means of cage vertices. The sum is normalized with Math.hypot, with 1 used when its length is zero, in which case the offset is zero.
ws-form.js
lines 296 to 304
PartCraft 0.8.1
let d = [0, 0, 0];
for (const k of S.sel.edges) {
const e = topo.E.get(k); if (!e || e.f.length !== 1) throw new Error(t('form.err.extrude_open'));
const f = c.faces[e.f[0]]; const cen = f.reduce((s, v) => s.map((x, i) => x + c.verts[v][i] / f.length), [0, 0, 0]);
const mid = [0, 1, 2].map((i) => (c.verts[e.a][i] + c.verts[e.b][i]) / 2);
d = d.map((x, i) => x + mid[i] - cen[i]);
}
const l = Math.hypot(...d) || 1;
return FM.extrudeEdges(c, S.sel.edges, d.map((x) => x / l * S.dist));
Tolerance for joining a new face
64-bit
The distance within which a new face's points join existing cage vertices.
tol = 10⁻⁶ × max(1, size setting)
The tolerance is scaled to the workspace's primitive size setting, not to the cage's actual extent, with a floor of 1e-6. The merge itself is done by addFace in form.js.
Gizmo move, rotate and scale as M = pivot world matrix × inverse of the matrix at the start of the drag. Clicked face points from a ray against the plane y = 0. Edge extrusion along the normalized sum of (edge middle − face center).
Number types
64-bit numbers for cage vertices and directions; Float32Array only for display buffers and vertex colors.
Tolerances
A clicked point reuses a cage vertex within 9 pixels. A new ground point is rounded to 0.001. New faces are merged with a tolerance of 1e-6 × max(1, the primitive size setting).
Limits
Only points clicked on the ground plane are rounded to 0.001; positions dragged with the gizmo are not rounded in this file.
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.