Resources Numerical accuracy Calculation policy

3D view

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.

view3d.js DetailCraft 0.8.1 32-bit, display only 3 calculations Reviewed

Tests None found by the review

A part's solid for drawing

32-bit, display only

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.

Every policy that takes this approach