Draws the model in 3D and finds the part under the pointer.
How the precision is kept
The view builds meshes for the graphics card from the built parts, in 32-bit buffers, and shows them at a scale of 0.001 (millimeters to meters). A point picked on a mesh is used to start a drag; the offset that is stored afterwards is snapped to a grid node or a step by the transform code, so no 32-bit value is stored in the model.
Calculations under this policy
3 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 mesh of a part: its profile extruded, with copes cut.
The length is split at each cope and the section clipped there; pieces shorter than 0.01 mm are skipped. The mesh is only drawn.
view3d.js
lines 774 to 803
DetailCraft 0.8.1
/** A part's solid in its own frame (u, v, x) — the profile extruded along +z, copes cut by splitting the length. */
export function partGeometry(p) {
const pr = p.profile, L = p.length;
const copes = p.cuts.filter((c) => c.kind === 'cope');
const cuts = new Set([0, L]);
for (const c of copes) cuts.add(c.end === 'start' ? Math.min(c.length, L) : Math.max(L - c.length, 0));
const xs = [...cuts].sort((a, b) => a - b);
const geos = [];
for (let i = 1; i < xs.length; i++) {
const a = xs[i - 1], b = xs[i];
if (b - a < 0.01) continue;
const mid = (a + b) / 2;
let loops = pr.loops;
for (const c of copes) {
const inCope = c.end === 'start' ? mid < c.length : mid > L - c.length;
if (!inCope) continue;
if (c.side === 'top') loops = loops.map((l, k) => (k === 0 ? clipLoopV(l, pr.depth / 2 - c.depth, true) : null)).filter((l) => l && l.length);
else loops = loops.map((l, k) => (k === 0 ? clipLoopV(l, -pr.depth / 2 + c.depth, false) : null)).filter((l) => l && l.length);
}
if (!loops.length) continue;
const shape = new THREE.Shape(loops[0].map(([u, v]) => new THREE.Vector2(u, v)));
for (const h of loops.slice(1)) shape.holes.push(new THREE.Path(h.map(([u, v]) => new THREE.Vector2(u, v))));
const g = new THREE.ExtrudeGeometry(shape, { depth: b - a, bevelEnabled: false, steps: 1, curveSegments: 1 });
g.translate(0, 0, a);
geos.push(g);
}
const g = geos.length === 1 ? geos[0] : mergeSimple(geos);
if (p.endCuts) cutEnds(g, p);
return g;
}
Mitre ends on the mesh
32-bit, display only
Moves the end vertices of a mesh onto the mitre's cut plane.
Vertices within 1e-6 of an end are moved; positions are read from and written to a 32-bit buffer. The mesh is only drawn.
view3d.js
lines 805 to 815
DetailCraft 0.8.1
/** Plane-cut ends (mitres, 0.7.1 71.20): the end caps' vertices moved onto the cut faces, so the solid ends on the plane. */
function cutEnds(g, p) {
const pos = g.attributes.position, L = p.length;
for (let i = 0; i < pos.count; i++) {
const z = pos.getZ(i);
if (p.endCuts.start && Math.abs(z) < 1e-6) pos.setZ(i, endX(p, 'start', pos.getX(i), pos.getY(i)));
else if (p.endCuts.end && Math.abs(z - L) < 1e-6) pos.setZ(i, endX(p, 'end', pos.getX(i), pos.getY(i)));
}
pos.needsUpdate = true;
g.computeVertexNormals();
}
The part under the pointer
32-bit, display only
Which part a click hits and where the ray meets it.
The hit point comes from a ray against the 32-bit meshes and is divided by 0.001 to get millimeters. It selects a part and starts a drag; it is not stored.
view3d.js
lines 580 to 595
DetailCraft 0.8.1
/** The part under a canvas point (CSS px) and where the ray meets it (mm), clipping respected. */
pickRef(l) {
const ray = new THREE.Raycaster();
ray.setFromCamera(new THREE.Vector2(l.x / l.w * 2 - 1, -(l.y / l.h) * 2 + 1), this.camera);
const planes = this.clipPlanes;
const hits = ray.intersectObjects(this.pickables, false).filter((h) => h.object.visible && planes.every((pl) => pl.distanceToPoint(h.point) >= 0));
// solid steel before see-through concrete, and a reference picture (under everything) last
const rank = (o) => (o.userData.mat === 'picture' ? 2 : o.userData.mat === 'concrete' ? 1 : 0);
hits.sort((a, b) => rank(a.object) - rank(b.object) || a.distance - b.distance);
const h = hits[0];
if (!h) return { ref: null, point: null };
let ref;
if (h.object.userData.bolts) { const b = h.object.userData.bolts[h.instanceId]; ref = b && b.conn ? { kind: 'conn', id: b.conn } : null; }
else ref = h.object.userData.ref;
return { ref, point: [h.point.x / MM, h.point.y / MM, h.point.z / MM] };
}
In detail
Method
Profiles extruded with the three.js library; copes by clipping the section over part of the length; mitre ends by moving the end vertices onto the cut plane.
Number types
32-bit vertex buffers for drawing; 64-bit numbers elsewhere.
Approach: Display only
Computed to draw the screen, often in 32-bit on the graphics processor. No number a person reads, saves or exports comes from it.