Skip to content

Commit b17e0dd

Browse files
committed
svg/dxf render scenes
1 parent cecce15 commit b17e0dd

File tree

5 files changed

+127
-77
lines changed

5 files changed

+127
-77
lines changed

packages/abstract-3d/src/abstract-3d.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ export const boundsZero: Bounds = bounds(0, 0);
304304
export const bounds2Zero: Bounds2 = bounds2(vec2Zero, vec2Zero);
305305
export const bounds3Zero: Bounds3 = bounds3(vec3Zero, vec3Zero);
306306

307+
export const bounds2ToSize = (bounds: Bounds2): Vec2 => vec2(bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y);
308+
307309
export const bounds3ToSize = (bounds: Bounds3): Vec3 =>
308310
vec3(bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y, bounds.max.z - bounds.min.z);
309311

@@ -342,11 +344,27 @@ export function bounds3FromPosAndSize(pos: Vec3, size: Vec3): Bounds3 {
342344
return bounds3(vec3(pos.x - halfX, pos.y - halfY, pos.z - halfZ), vec3(pos.x + halfX, pos.y + halfY, pos.z + halfZ));
343345
}
344346

345-
export const bounds2Merge = (a: Bounds2, b: Bounds2): Bounds2 => {
346-
return bounds2(
347-
vec2(Math.min(a.min.x, b.min.x), Math.min(a.min.y, b.min.y)),
348-
vec2(Math.max(a.max.x, b.max.x), Math.max(a.max.y, b.max.y))
349-
);
347+
export const bounds2Merge = (...bounds: ReadonlyArray<Bounds2>): Bounds2 => {
348+
if (bounds.length === 0) {
349+
return bounds2Zero;
350+
}
351+
const min = vec2Dupl(Number.MAX_VALUE) as { x: number; y: number };
352+
const max = vec2Dupl(-Number.MAX_VALUE) as { x: number; y: number };
353+
bounds.forEach((b) => {
354+
if (b.min.x < min.x) {
355+
min.x = b.min.x;
356+
}
357+
if (b.min.y < min.y) {
358+
min.y = b.min.y;
359+
}
360+
if (b.max.x > max.x) {
361+
max.x = b.max.x;
362+
}
363+
if (b.max.y > max.y) {
364+
max.y = b.max.y;
365+
}
366+
});
367+
return bounds2(min, max);
350368
};
351369

352370
export const bounds3Merge = (...bounds: ReadonlyArray<Bounds3>): Bounds3 => {

packages/abstract-3d/src/renderers/dxf/dxf-encoding.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
/* eslint-disable max-lines */
22
import { Vec3 } from "../../abstract-3d.js";
3+
import { generateUUID } from "three/src/math/MathUtils.js";
34

45
//this doesn't have to be ordered, it can be completely random.
56
//the only requirement is that all of them are unique. So handling
67
//it like this makes the format more deterministic and less prone to
78
//errors
8-
function dxfHandle(handleRef: { handle: number }): string {
9+
10+
export function dxf(groups: string, center: Vec3, size: Vec3): string {
11+
const id = generateUUID();
12+
return dxfHeader(size, center, id) + groups + dxfFooter(id);
13+
}
14+
15+
export type Handle = { handle: number };
16+
17+
function dxfHandle(handleRef: Handle): string {
918
return (++handleRef.handle).toString(16).toUpperCase();
1019
}
1120

@@ -20,7 +29,7 @@ export const dxf3DFACE = (
2029
vec3: Vec3,
2130
vec4: Vec3,
2231
color: number,
23-
handleRef: { handle: number }
32+
handleRef: Handle
2433
): string => ` 0
2534
3DFACE
2635
5
@@ -110,7 +119,7 @@ ${fontSize}
110119
${text}
111120
`;
112121

113-
export const dxf3DLine = (start: Vec3, end: Vec3, color: string, handleRef: { handle: number }): string =>
122+
export const dxf3DLine = (start: Vec3, end: Vec3, color: string, handleRef: Handle): string =>
114123
` 0
115124
LINE
116125
5
@@ -135,13 +144,7 @@ ${dxfRound(end.y)}
135144
${dxfRound(end.z)}
136145
`;
137146

138-
export const dxf3DEllipse = (
139-
center: Vec3,
140-
major: Vec3,
141-
minor: Vec3,
142-
color: string,
143-
handleRef: { handle: number }
144-
): string =>
147+
export const dxf3DEllipse = (center: Vec3, major: Vec3, minor: Vec3, color: string, handleRef: Handle): string =>
145148
` 0
146149
ELLIPSE
147150
5

packages/abstract-3d/src/renderers/dxf/dxf-geometries/dxf-cylinder.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,7 @@ import { color } from "../color.js";
1313
import { dxf3DFACE } from "../dxf-encoding.js";
1414
import { dxfPlane } from "./dxf-plane.js";
1515

16-
export function dxfCylinder(
17-
c: Cylinder,
18-
m: Material,
19-
sides: number,
20-
parentPos: Vec3,
21-
parentRot: Vec3,
22-
handleRef: { handle: number }
23-
): string {
16+
export function dxfCylinder(c: Cylinder, m: Material, sides: number, parentPos: Vec3, parentRot: Vec3, Handle): string {
2417
const angleStart = c.angleStart ?? 0.0;
2518
const angleLength = c.angleLength ?? Math.PI * 2;
2619
const angleEnd = angleStart + angleLength;

packages/abstract-3d/src/renderers/dxf/dxf.ts

Lines changed: 42 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,67 @@ import {
99
group,
1010
bounds3ToSize,
1111
vec3,
12-
boundsScene,
1312
rotationForCameraPos,
13+
bounds3FromPosAndSize,
14+
Bounds3,
15+
bounds3Merge,
16+
bounds3Center,
1417
} from "../../abstract-3d.js";
15-
import { dxfFooter, dxfHeader } from "./dxf-encoding.js";
18+
import { dxf, Handle } from "./dxf-encoding.js";
1619
import { dxfPlane } from "./dxf-geometries/dxf-plane.js";
1720
import { dxfBox } from "./dxf-geometries/dxf-box.js";
1821
import { dxfCylinder } from "./dxf-geometries/dxf-cylinder.js";
1922
import { dxfCone } from "./dxf-geometries/dxf-cone.js";
2023
import { dxfPolygon } from "./dxf-geometries/dxf-polygon.js";
21-
import { generateUUID } from "three/src/math/MathUtils.js";
2224
import { Optional } from "../shared.js";
2325

2426
const DEFAULT_CYLINDER_SIDE_COUNT = 18;
2527

2628
export type DxfOrigin = "BottomLeftFront" | "Center";
2729
export type DxfOptions = { readonly view: View; readonly origin: DxfOrigin; readonly cylinderSideCount: number };
2830

29-
export const render = (scene: Scene, options: Optional<DxfOptions>): string => {
31+
export function renderScenes(
32+
scenes: ReadonlyArray<{ readonly scene: Scene; readonly options?: Optional<DxfOptions>; readonly pos: Vec3 }>
33+
): string {
34+
let allGroups = "";
35+
const allBounds = Array<Bounds3>();
36+
const handle = { handle: 0x1000 };
37+
for (const view of scenes) {
38+
const { groups } = dxfGroups(view.scene, { ...view.options, view: undefined, origin: undefined }, view.pos, handle);
39+
allGroups += groups;
40+
allBounds.push(bounds3FromPosAndSize(view.scene.center_deprecated ?? vec3Zero, view.scene.size_deprecated));
41+
}
42+
const bounds = bounds3Merge(...allBounds);
43+
return dxf(allGroups, bounds3Center(bounds), bounds3ToSize(bounds));
44+
}
45+
46+
export const render = (scene: Scene, options?: Optional<DxfOptions>): string => {
47+
const center = scene.center_deprecated ?? vec3Zero;
48+
const bounds = bounds3FromPosAndSize(center, scene.size_deprecated);
49+
const offset =
50+
options?.origin === "Center" ? vec3Zero : vec3(Math.abs(bounds.min.x), Math.abs(bounds.min.y), -bounds.max.z);
51+
const res = dxfGroups(scene, options, offset, { handle: 0x1000 });
52+
return dxf(res.groups, center, scene.size_deprecated);
53+
};
54+
55+
const dxfGroups = (
56+
scene: Scene,
57+
options: Optional<DxfOptions> | undefined,
58+
offset: Vec3,
59+
handleRef: Handle //make sure we start with a value higher than any other handle id's used in the header
60+
): { readonly groups: string } => {
3061
const opts: DxfOptions = {
31-
view: options.view ?? "front",
32-
origin: options.origin ?? "BottomLeftFront",
62+
view: options?.view ?? "front",
63+
origin: options?.origin ?? "BottomLeftFront",
3364
cylinderSideCount: DEFAULT_CYLINDER_SIDE_COUNT,
3465
};
3566
const unitRot = vec3RotCombine(rotationForCameraPos(opts.view), scene.rotation_deprecated ?? vec3Zero);
36-
const boundingBox = boundsScene(scene);
37-
const boundingBoxSize = bounds3ToSize(boundingBox);
38-
const center = vec3Zero;
39-
const offset =
40-
opts.origin === "Center"
41-
? vec3Zero
42-
: vec3(Math.abs(boundingBox.min.x), Math.abs(boundingBox.min.y), -boundingBox.max.z);
67+
const center = scene.center_deprecated ?? vec3Zero;
4368
const groupRoot = group([], offset, vec3Zero, scene.groups);
44-
const id = generateUUID();
45-
const handleRef = { handle: 0x1000 }; //make sure we start with a value higher than any other handle id's used in the header
46-
return (
47-
dxfHeader(boundingBoxSize, center, id) + dxfGroup(groupRoot, center, unitRot, options, handleRef) + dxfFooter(id)
48-
);
69+
return { groups: dxfGroup(groupRoot, center, unitRot, opts, handleRef) };
4970
};
5071

51-
function dxfGroup(
52-
g: Group,
53-
parentPos: Vec3,
54-
parentRot: Vec3,
55-
options: Optional<DxfOptions>,
56-
handleRef: { handle: number }
57-
): string {
72+
function dxfGroup(g: Group, parentPos: Vec3, parentRot: Vec3, options: DxfOptions, handleRef: Handle): string {
5873
const pos = vec3TransRot(g.pos, parentPos, parentRot);
5974
const rot = vec3RotCombine(parentRot, g.rot ?? vec3Zero);
6075
return (
@@ -67,30 +82,10 @@ function dxfGroup(
6782
return a + dxfBox(c.geometry, c.material, pos, rot, handleRef);
6883
}
6984
case "Cylinder": {
70-
return (
71-
a +
72-
dxfCylinder(
73-
c.geometry,
74-
c.material,
75-
options.cylinderSideCount ?? DEFAULT_CYLINDER_SIDE_COUNT,
76-
pos,
77-
rot,
78-
handleRef
79-
)
80-
);
85+
return a + dxfCylinder(c.geometry, c.material, options.cylinderSideCount, pos, rot, handleRef);
8186
}
8287
case "Cone": {
83-
return (
84-
a +
85-
dxfCone(
86-
c.geometry,
87-
c.material,
88-
options.cylinderSideCount ?? DEFAULT_CYLINDER_SIDE_COUNT,
89-
pos,
90-
rot,
91-
handleRef
92-
)
93-
);
88+
return a + dxfCone(c.geometry, c.material, options.cylinderSideCount, pos, rot, handleRef);
9489
}
9590
case "Polygon": {
9691
return a + dxfPolygon(c.geometry, c.material, pos, rot, handleRef);

packages/abstract-3d/src/renderers/svg/svg.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import {
1616
Material,
1717
rotationForCameraPos,
1818
sizeCenterForCameraPos,
19+
Bounds2,
20+
bounds2FromPosAndSize,
21+
bounds2ToSize,
22+
bounds2Merge,
23+
vec2Zero,
1924
} from "../../abstract-3d.js";
2025
import { SvgOptions, zOrderElement } from "./svg-geometries/shared.js";
2126
import { box } from "./svg-geometries/svg-box.js";
@@ -29,10 +34,51 @@ import { cone } from "./svg-geometries/svg-cone.js";
2934
import { Optional } from "../shared.js";
3035
import { svg } from "./svg-encoding.js";
3136

37+
export function renderScenes(
38+
scenes: ReadonlyArray<{ readonly scene: Scene; readonly options?: Optional<SvgOptions>; readonly pos: Vec2 }>
39+
): {
40+
readonly image: string;
41+
readonly width: number;
42+
readonly height: number;
43+
} {
44+
const allElements = Array<zOrderElement>();
45+
const bounds = Array<Bounds2>();
46+
for (const view of scenes) {
47+
const { elements, width, height } = renderInternal(
48+
view.scene,
49+
{ ...view.options, view: undefined, rotation: undefined },
50+
view.pos
51+
);
52+
allElements.push(...elements);
53+
bounds.push(bounds2FromPosAndSize(view.pos, vec2(width, height)));
54+
}
55+
const size = bounds2ToSize(bounds2Merge(...bounds));
56+
const image = svg(
57+
size.x,
58+
size.y,
59+
allElements.reduce((a, { element }) => `${a} ${element}`, "")
60+
);
61+
return { image, width: size.x, height: size.y };
62+
}
63+
3264
export function render(
3365
scene: Scene,
3466
options?: Optional<SvgOptions>
3567
): { readonly image: string; readonly width: number; readonly height: number } {
68+
const { elements, width, height } = renderInternal(scene, options, vec2Zero);
69+
const image = svg(
70+
width,
71+
height,
72+
elements.reduce((a, { element }) => `${a} ${element}`, "")
73+
);
74+
return { image, width, height };
75+
}
76+
77+
function renderInternal(
78+
scene: Scene,
79+
options: Optional<SvgOptions> | undefined,
80+
offset: Vec2
81+
): { readonly elements: ReadonlyArray<zOrderElement>; readonly width: number; readonly height: number } {
3682
const opts: SvgOptions = {
3783
view: options?.view ?? "front",
3884
stroke: options?.stroke ?? 2,
@@ -65,7 +111,7 @@ export function render(
65111
const centerAdj = vec3(center.x - opts.stroke * 0.75, center.y + opts.stroke * 0.75, center.z);
66112
const elements = Array<zOrderElement>();
67113
const point = (x: number, y: number): Vec2 =>
68-
vec2(-centerAdj.x + unitHalfSize.x + x * factor, centerAdj.y + unitHalfSize.y - y * factor);
114+
vec2(-centerAdj.x + unitHalfSize.x + x * factor + offset.x, centerAdj.y + unitHalfSize.y - y * factor + offset.y);
69115

70116
for (const g of scene.groups) {
71117
const pos = vec3Rot(g.pos, unitPos, unitRot);
@@ -87,12 +133,7 @@ export function render(
87133
}
88134
}
89135

90-
const image = svg(
91-
width,
92-
height,
93-
elements.reduce((a, { element }) => `${a} ${element}`, "")
94-
);
95-
return { image, width, height };
136+
return { elements, width, height };
96137
}
97138

98139
function svgGroup(

0 commit comments

Comments
 (0)