abstract-3d 2.9.2 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/lib/abstract-3d.d.ts +4 -1
  2. package/lib/abstract-3d.d.ts.map +1 -1
  3. package/lib/abstract-3d.js +4 -2
  4. package/lib/abstract-3d.js.map +1 -1
  5. package/lib/renderers/dxf/dxf-geometries/dxf-cylinder.d.ts.map +1 -1
  6. package/lib/renderers/dxf/dxf-geometries/dxf-cylinder.js +15 -21
  7. package/lib/renderers/dxf/dxf-geometries/dxf-cylinder.js.map +1 -1
  8. package/lib/renderers/react/react-mesh.d.ts.map +1 -1
  9. package/lib/renderers/react/react-mesh.js +51 -21
  10. package/lib/renderers/react/react-mesh.js.map +1 -1
  11. package/lib/renderers/step/step-geometries/step-cone.d.ts.map +1 -1
  12. package/lib/renderers/step/step-geometries/step-cone.js +1 -9
  13. package/lib/renderers/step/step-geometries/step-cone.js.map +1 -1
  14. package/lib/renderers/step/step-geometries/step-cylinder.d.ts +1 -1
  15. package/lib/renderers/step/step-geometries/step-cylinder.d.ts.map +1 -1
  16. package/lib/renderers/step/step-geometries/step-cylinder.js +2 -2
  17. package/lib/renderers/step/step-geometries/step-cylinder.js.map +1 -1
  18. package/lib/renderers/stl/stl-geometries/stl-cylinder.d.ts +1 -1
  19. package/lib/renderers/stl/stl-geometries/stl-cylinder.d.ts.map +1 -1
  20. package/lib/renderers/stl/stl-geometries/stl-cylinder.js +9 -17
  21. package/lib/renderers/stl/stl-geometries/stl-cylinder.js.map +1 -1
  22. package/lib/renderers/svg/svg-geometries/svg-cylinder.d.ts.map +1 -1
  23. package/lib/renderers/svg/svg-geometries/svg-cylinder.js +11 -9
  24. package/lib/renderers/svg/svg-geometries/svg-cylinder.js.map +1 -1
  25. package/package.json +4 -4
  26. package/src/abstract-3d.ts +8 -2
  27. package/src/renderers/dxf/dxf-geometries/dxf-cylinder.ts +15 -22
  28. package/src/renderers/react/react-mesh.tsx +76 -64
  29. package/src/renderers/step/step-geometries/step-cone.ts +2 -10
  30. package/src/renderers/step/step-geometries/step-cylinder.ts +2 -2
  31. package/src/renderers/stl/stl-geometries/stl-cylinder.ts +9 -17
  32. package/src/renderers/svg/svg-geometries/svg-cylinder.ts +11 -9
@@ -34,8 +34,9 @@ import type {
34
34
  Tube as A3dTube,
35
35
  CircleCurve as A3dCircleCurve,
36
36
  Hole,
37
+ Vec3,
37
38
  } from "../../abstract-3d.js";
38
- import { vec2Scale, vec2Sub, vec2Add, isZero, vec3Scale, vec3, vec3Add, vec3RotCombine, vec3Zero, vec3Rot, equals } from "../../abstract-3d.js";
39
+ import { vec2Scale, vec2Sub, vec2Add, isZero, vec3, equals } from "../../abstract-3d.js";
39
40
  import { planeGeometry } from "./react-image-material.js";
40
41
 
41
42
  extend({
@@ -57,8 +58,7 @@ extend({
57
58
 
58
59
  const CYLINDER_SEGMENTS = 40;
59
60
  const boxGeometry = new BoxGeometry();
60
- const cylinderGeometry = new CylinderGeometry(1, 1, 1, CYLINDER_SEGMENTS, 1);
61
- const cylinderGeometryOpen = new CylinderGeometry(1, 1, 1, CYLINDER_SEGMENTS, 1, true);
61
+ const cylinderGeometries = new Map<string, BufferGeometry>();
62
62
  const coneGeometry = new ConeGeometry(1, 1, 16, 1);
63
63
  const sphereGeometry = new SphereGeometry(1, 12, 12);
64
64
  export const euler = new Euler();
@@ -114,70 +114,36 @@ export function ReactMesh({ mesh, children }: { readonly mesh: Mesh; readonly ch
114
114
  );
115
115
  }
116
116
  case "Cylinder": {
117
- const { pos, radius, rot, length, holes, open, angleStart, angleLength } = mesh.geometry;
118
- const filteredHoles = holes?.filter((h) => !holeIsZero(h));
119
- const hasAngles = angleStart !== undefined && angleLength !== undefined;
120
- const isWhole = !hasAngles || (isZero(Math.PI * 2 - angleLength) && isZero(angleStart));
121
- if (isWhole) {
122
- return !filteredHoles || filteredHoles.length === 0 ? (
123
- <mesh
124
- geometry={open ? cylinderGeometryOpen : cylinderGeometry}
125
- scale={[radius, length, radius]}
126
- position={[pos.x, pos.y, pos.z]}
127
- rotation={[rot?.x ?? 0, rot?.y ?? 0, rot?.z ?? 0]}
128
- castShadow
129
- receiveShadow
130
- >
117
+ const { pos, radius, radiusEnd = radius, rot, length, holes, open, angleStart = 0, angleLength = Math.PI * 2 } = mesh.geometry;
118
+ const isWhole = isZero(Math.PI * 2 - angleLength);
119
+ // Holes are extruded along the length, which can't taper
120
+ const filteredHoles = radiusEnd === radius ? holes?.filter((h) => !holeIsZero(h)) : undefined;
121
+ if (isWhole && filteredHoles && filteredHoles.length > 0) {
122
+ return <ExcrudeCylinder cyl={mesh.geometry}>{children}</ExcrudeCylinder>;
123
+ }
124
+ // Unit geometry scaled by the largest radius, so only the radius ratio needs its own geometry
125
+ const maxRadius = Math.max(radius, radiusEnd);
126
+ const unitTop = radiusEnd / (maxRadius || 1);
127
+ const unitBottom = radius / (maxRadius || 1);
128
+ const transform = {
129
+ scale: [maxRadius, length, maxRadius],
130
+ position: [pos.x, pos.y, pos.z],
131
+ rotation: [rot?.x ?? 0, rot?.y ?? 0, rot?.z ?? 0],
132
+ } as const;
133
+ return isWhole ? (
134
+ <mesh geometry={getCylinderGeometry(unitTop, unitBottom, open ?? false, 0, Math.PI * 2)} {...transform} castShadow receiveShadow>
135
+ {children}
136
+ </mesh>
137
+ ) : (
138
+ <mesh {...transform}>
139
+ <mesh geometry={getCylinderGeometry(unitTop, unitBottom, false, angleStart, angleLength)} castShadow receiveShadow>
131
140
  {children}
132
141
  </mesh>
133
- ) : (
134
- <ExcrudeCylinder cyl={mesh.geometry}>{children}</ExcrudeCylinder>
135
- );
136
- } else {
137
- const angledCylinder = new CylinderGeometry(1, 1, 1, CYLINDER_SEGMENTS, 1, false, angleStart, angleLength);
138
- const angleEnd = angleStart + angleLength;
139
- const halfRadius = radius / 2;
140
- const aStart = angleStart - Math.PI / 2;
141
- const aEnd = angleEnd - Math.PI / 2;
142
- const plane1Rot = vec3RotCombine(rot ?? vec3Zero, vec3(0, aStart, 0));
143
- const plane2Rot = vec3RotCombine(rot ?? vec3Zero, vec3(0, aEnd, 0));
144
- const plane1Pos = vec3Add(vec3Rot(vec3Scale(vec3(Math.cos(aStart), 0, -Math.sin(aStart)), halfRadius), vec3Zero, rot ?? vec3Zero), pos);
145
- const plane2Pos = vec3Add(vec3Rot(vec3Scale(vec3(Math.cos(aEnd), 0, -Math.sin(aEnd)), halfRadius), vec3Zero, rot ?? vec3Zero), pos);
146
- return (
147
- <mesh>
148
- <mesh
149
- geometry={angledCylinder}
150
- scale={[radius, length, radius]}
151
- position={[pos.x, pos.y, pos.z]}
152
- rotation={[rot?.x ?? 0, rot?.y ?? 0, rot?.z ?? 0]}
153
- castShadow
154
- receiveShadow
155
- >
156
- {children}
157
- </mesh>
158
- <mesh
159
- geometry={planeGeometry}
160
- scale={[radius, length, 1]}
161
- position={[plane1Pos.x, plane1Pos.y, plane1Pos.z]}
162
- rotation={[plane1Rot.x, plane1Rot.y, plane1Rot.z]}
163
- castShadow
164
- receiveShadow
165
- >
166
- {children}
167
- </mesh>
168
- <mesh
169
- geometry={planeGeometry}
170
- scale={[radius, length, 1]}
171
- position={[plane2Pos.x, plane2Pos.y, plane2Pos.z]}
172
- rotation={[plane2Rot.x, plane2Rot.y, plane2Rot.z]}
173
- castShadow
174
- receiveShadow
175
- >
176
- {children}
177
- </mesh>
142
+ <mesh geometry={getCylinderCutGeometry(unitTop, unitBottom, angleStart, angleLength)} castShadow receiveShadow>
143
+ {children}
178
144
  </mesh>
179
- );
180
- }
145
+ </mesh>
146
+ );
181
147
  }
182
148
  case "Image":
183
149
  return (
@@ -347,6 +313,52 @@ function ExcrudeShape({ s, children }: { readonly s: Shape_1; readonly children?
347
313
  );
348
314
  }
349
315
 
316
+ function cachedCylinderGeometry(key: string, create: () => BufferGeometry): BufferGeometry {
317
+ const cached = cylinderGeometries.get(key);
318
+ if (cached) {
319
+ return cached;
320
+ }
321
+ const geometry = create();
322
+ cylinderGeometries.set(key, geometry);
323
+ return geometry;
324
+ }
325
+
326
+ function getCylinderGeometry(top: number, bottom: number, open: boolean, angleStart: number, angleLength: number): BufferGeometry {
327
+ return cachedCylinderGeometry(
328
+ `cyl_${top}_${bottom}_${open}_${angleStart}_${angleLength}`,
329
+ () => new CylinderGeometry(top, bottom, 1, CYLINDER_SEGMENTS, 1, open, angleStart, angleLength)
330
+ );
331
+ }
332
+
333
+ const rimPoint = (radius: number, y: number, angle: number): Vec3 => vec3(radius * Math.sin(angle), y, radius * Math.cos(angle));
334
+
335
+ /** The two walls closing an angled cylinder, from the axis out to the rim at angleStart and angleEnd */
336
+ function getCylinderCutGeometry(top: number, bottom: number, angleStart: number, angleLength: number): BufferGeometry {
337
+ return cachedCylinderGeometry(`cut_${top}_${bottom}_${angleStart}_${angleLength}`, () => {
338
+ const [c0, c1] = [vec3(0, -0.5, 0), vec3(0, 0.5, 0)];
339
+ const angleEnd = angleStart + angleLength;
340
+ const [b0, t0] = [rimPoint(bottom, -0.5, angleStart), rimPoint(top, 0.5, angleStart)];
341
+ const [b1, t1] = [rimPoint(bottom, -0.5, angleEnd), rimPoint(top, 0.5, angleEnd)];
342
+ // Unshared vertices so each wall gets its own flat normal, wound opposite so both face outwards
343
+ const triangles = [
344
+ [c0, b0, t0],
345
+ [c0, t0, c1],
346
+ [c0, c1, t1],
347
+ [c0, t1, b1],
348
+ ];
349
+ const g = new BufferGeometry();
350
+ g.setAttribute(
351
+ "position",
352
+ new Float32BufferAttribute(
353
+ triangles.flat().flatMap((v) => [v.x, v.y, v.z]),
354
+ 3
355
+ )
356
+ );
357
+ g.computeVertexNormals();
358
+ return g;
359
+ });
360
+ }
361
+
350
362
  function ExcrudeCylinder({ cyl, children }: { readonly cyl: Cylinder; readonly children?: React.JSX.Element }): React.JSX.Element {
351
363
  const excrudeGeometry = React.useMemo(() => {
352
364
  const shape = new Shape();
@@ -1,15 +1,7 @@
1
- import type { Material, Vec3, Cone, Cylinder } from "../../../abstract-3d.js";
1
+ import type { Material, Vec3, Cone } from "../../../abstract-3d.js";
2
2
  import type { MutableStep } from "../step-encoding.js";
3
3
  import { stepCylinder } from "./step-cylinder.js";
4
4
 
5
5
  export function stepCone(c: Cone, mat: Material, parentPos: Vec3, parentRot: Vec3, m: MutableStep): void {
6
- //convert it into a box
7
- const cylinder: Cylinder = {
8
- type: "Cylinder",
9
- pos: c.pos,
10
- rot: c.rot,
11
- length: c.length,
12
- radius: c.radius,
13
- };
14
- stepCylinder(cylinder, mat, parentPos, parentRot, m, 0.0);
6
+ stepCylinder({ type: "Cylinder", pos: c.pos, rot: c.rot, length: c.length, radius: c.radius, radiusEnd: 0 }, mat, parentPos, parentRot, m);
15
7
  }
@@ -45,7 +45,7 @@ import {
45
45
  const SMALLEST_RADIUS = 1e-4;
46
46
  const GEOMETRY_EPSILON = 1e-12;
47
47
 
48
- export function stepCylinder(c: Cylinder, mat: Material, parentPos: Vec3, parentRot: Vec3, m: MutableStep, rSmall?: number): void {
48
+ export function stepCylinder(c: Cylinder, mat: Material, parentPos: Vec3, parentRot: Vec3, m: MutableStep): void {
49
49
  const h = c.length;
50
50
 
51
51
  if (h <= GEOMETRY_EPSILON) {
@@ -53,7 +53,7 @@ export function stepCylinder(c: Cylinder, mat: Material, parentPos: Vec3, parent
53
53
  }
54
54
 
55
55
  const bottomRadius = Math.max(c.radius, SMALLEST_RADIUS);
56
- const requestedTopRadius = Math.max(rSmall ?? c.radius, SMALLEST_RADIUS);
56
+ const requestedTopRadius = Math.max(c.radiusEnd ?? c.radius, SMALLEST_RADIUS);
57
57
 
58
58
  const radiusTolerance = GEOMETRY_EPSILON * Math.max(1, bottomRadius, requestedTopRadius);
59
59
 
@@ -1,9 +1,8 @@
1
1
  import type { Cylinder, Material, Vec3 } from "../../../abstract-3d.js";
2
- import { vec3TransRot, vec3RotCombine, vec3Zero, vec3, equals, vec3Scale } from "../../../abstract-3d.js";
2
+ import { vec3TransRot, vec3RotCombine, vec3Zero, vec3, equals } from "../../../abstract-3d.js";
3
3
  import { stlPlaneOfVertices } from "../stl-encoding.js";
4
- import { stlPlane } from "./stl-plane.js";
5
4
 
6
- export function stlCylinder(c: Cylinder, m: Material, sides: number, parentPos: Vec3, parentRot: Vec3): string {
5
+ export function stlCylinder(c: Cylinder, _m: Material, sides: number, parentPos: Vec3, parentRot: Vec3): string {
7
6
  let stlString = "";
8
7
  const pos = vec3TransRot(c.pos, parentPos, parentRot);
9
8
  const rot = vec3RotCombine(parentRot, c.rot ?? vec3Zero);
@@ -16,6 +15,7 @@ export function stlCylinder(c: Cylinder, m: Material, sides: number, parentPos:
16
15
  let currentAngle = angleStart;
17
16
 
18
17
  const half = c.length / 2;
18
+ const radiusEnd = c.radiusEnd ?? c.radius;
19
19
  const topPos = vec3tr(0, half, 0);
20
20
  const botPos = vec3tr(0, -half, 0);
21
21
 
@@ -23,10 +23,10 @@ export function stlCylinder(c: Cylinder, m: Material, sides: number, parentPos:
23
23
  const topVec3Array = Array<Vec3>();
24
24
 
25
25
  for (let i = 0; i <= sides; i++) {
26
- const x = Math.sin(currentAngle) * c.radius;
27
- const z = Math.cos(currentAngle) * c.radius;
28
- const currBot = vec3tr(x, -half, z);
29
- const currTop = vec3tr(x, half, z);
26
+ const sin = Math.sin(currentAngle);
27
+ const cos = Math.cos(currentAngle);
28
+ const currBot = vec3tr(sin * c.radius, -half, cos * c.radius);
29
+ const currTop = vec3tr(sin * radiusEnd, half, cos * radiusEnd);
30
30
  botVec3Array.push(currBot);
31
31
  topVec3Array.push(currTop);
32
32
  if (i !== 0) {
@@ -41,16 +41,8 @@ export function stlCylinder(c: Cylinder, m: Material, sides: number, parentPos:
41
41
  }
42
42
 
43
43
  if (!equals(angleStart, angleEnd - Math.PI * 2) && angleLength > 0.0) {
44
- const aStart = angleStart - Math.PI / 2;
45
- const aEnd = angleEnd - Math.PI / 2;
46
- const halfRadius = c.radius / 2;
47
- const plane1Rot = vec3(0, aStart, 0);
48
- const plane2Rot = vec3(0, aEnd, 0);
49
- const plane1Pos = vec3Scale(vec3(Math.cos(aStart), 0, -Math.sin(aStart)), halfRadius);
50
- const plane2Pos = vec3Scale(vec3(Math.cos(aEnd), 0, -Math.sin(aEnd)), halfRadius);
51
- const planeSize = vec3(c.radius, c.length, 1);
52
- stlString += stlPlane({ type: "Plane", pos: plane1Pos, size: planeSize, rot: plane1Rot }, m, pos, rot);
53
- stlString += stlPlane({ type: "Plane", pos: plane2Pos, size: planeSize, rot: plane2Rot }, m, pos, rot);
44
+ stlString += stlPlaneOfVertices(botPos, botVec3Array[0]!, topVec3Array[0]!, topPos);
45
+ stlString += stlPlaneOfVertices(botPos, topPos, topVec3Array[sides]!, botVec3Array[sides]!);
54
46
  }
55
47
 
56
48
  return stlString;
@@ -13,7 +13,8 @@ export function cylinder(
13
13
  parentPos: Vec3,
14
14
  parentRot: Vec3
15
15
  ): ReadonlyArray<zOrderElement> {
16
- const half = vec3(c.radius, c.length / 2, c.radius);
16
+ const half = c.length / 2;
17
+ const radiusEnd = c.radiusEnd ?? c.radius;
17
18
  const pos = vec3TransRot(c.pos, parentPos, parentRot);
18
19
  const rot = vec3RotCombine(parentRot, c.rot ?? vec3Zero);
19
20
  const vec3tr = (p: Vec3): Vec3 => vec3TransRot(p, pos, rot);
@@ -32,10 +33,10 @@ export function cylinder(
32
33
  const botVec3Array = Array<Vec3>();
33
34
  const topVec3Array = Array<Vec3>();
34
35
  for (let i = 0; i <= sides; i++) {
35
- const x = Math.sin(currentAngle) * c.radius;
36
- const z = Math.cos(currentAngle) * c.radius;
37
- const currBot = vec3tr(vec3(x, -half.y, z));
38
- const currTop = vec3tr(vec3(x, half.y, z));
36
+ const sin = Math.sin(currentAngle);
37
+ const cos = Math.cos(currentAngle);
38
+ const currBot = vec3tr(vec3(sin * c.radius, -half, cos * c.radius));
39
+ const currTop = vec3tr(vec3(sin * radiusEnd, half, cos * radiusEnd));
39
40
  botVec3Array.push(currBot);
40
41
  topVec3Array.push(currTop);
41
42
  if (i !== 0) {
@@ -49,11 +50,12 @@ export function cylinder(
49
50
  }
50
51
  if (!c.open) {
51
52
  // Add circle if direcly facing camera
52
- const circleTop = vec3tr(vec3(0, half.y, 0));
53
- const circleBottom = vec3tr(vec3(0, -half.y, 0));
53
+ const circleTop = vec3tr(vec3(0, half, 0));
54
+ const circleBottom = vec3tr(vec3(0, -half, 0));
54
55
  if (equals(circleTop.x, circleBottom.x, 0.1) && equals(circleTop.y, circleBottom.y, 0.1)) {
55
- const circlePos = circleTop.z > circleBottom.z ? circleTop : circleBottom;
56
- zOrderComponents.push(zElem(svgCircle(c.radius, rot, point(circlePos.x, circlePos.y), fill, opacity, stroke, stBW, c.holes), circlePos.z));
56
+ const [circlePos, circleRadius] = circleTop.z > circleBottom.z ? [circleTop, radiusEnd] : [circleBottom, c.radius];
57
+ const holes = radiusEnd === c.radius ? c.holes : undefined;
58
+ zOrderComponents.push(zElem(svgCircle(circleRadius, rot, point(circlePos.x, circlePos.y), fill, opacity, stroke, stBW, holes), circlePos.z));
57
59
  }
58
60
  }
59
61
  return zOrderComponents;