@ifc-lite/renderer 1.43.0 → 1.44.1
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.
- package/dist/camera-animation.d.ts +20 -32
- package/dist/camera-animation.d.ts.map +1 -1
- package/dist/camera-animation.js +71 -358
- package/dist/camera-animation.js.map +1 -1
- package/dist/camera-controls.d.ts +11 -51
- package/dist/camera-controls.d.ts.map +1 -1
- package/dist/camera-controls.js +140 -12
- package/dist/camera-controls.js.map +1 -1
- package/dist/camera-first-person.d.ts +52 -0
- package/dist/camera-first-person.d.ts.map +1 -0
- package/dist/camera-first-person.js +119 -0
- package/dist/camera-first-person.js.map +1 -0
- package/dist/camera-framing.d.ts +86 -0
- package/dist/camera-framing.d.ts.map +1 -0
- package/dist/camera-framing.js +230 -0
- package/dist/camera-framing.js.map +1 -0
- package/dist/camera-guards.d.ts +148 -0
- package/dist/camera-guards.d.ts.map +1 -0
- package/dist/camera-guards.js +150 -0
- package/dist/camera-guards.js.map +1 -0
- package/dist/camera-matrices.d.ts +14 -0
- package/dist/camera-matrices.d.ts.map +1 -0
- package/dist/camera-matrices.js +146 -0
- package/dist/camera-matrices.js.map +1 -0
- package/dist/camera-preset-view.d.ts +56 -0
- package/dist/camera-preset-view.d.ts.map +1 -0
- package/dist/camera-preset-view.js +180 -0
- package/dist/camera-preset-view.js.map +1 -0
- package/dist/camera-projection.d.ts +1 -1
- package/dist/camera-projection.d.ts.map +1 -1
- package/dist/camera-projection.js +83 -30
- package/dist/camera-projection.js.map +1 -1
- package/dist/camera-state.d.ts +62 -0
- package/dist/camera-state.d.ts.map +1 -0
- package/dist/camera-state.js +5 -0
- package/dist/camera-state.js.map +1 -0
- package/dist/camera.d.ts +22 -11
- package/dist/camera.d.ts.map +1 -1
- package/dist/camera.js +75 -120
- package/dist/camera.js.map +1 -1
- package/dist/environment.d.ts.map +1 -1
- package/dist/environment.js +23 -1
- package/dist/environment.js.map +1 -1
- package/dist/fill-bridge-anchor.d.ts +32 -0
- package/dist/fill-bridge-anchor.d.ts.map +1 -0
- package/dist/fill-bridge-anchor.js +82 -0
- package/dist/fill-bridge-anchor.js.map +1 -0
- package/dist/fill-predicates.d.ts +44 -0
- package/dist/fill-predicates.d.ts.map +1 -0
- package/dist/fill-predicates.js +65 -0
- package/dist/fill-predicates.js.map +1 -0
- package/dist/fill-triangulate.d.ts +102 -0
- package/dist/fill-triangulate.d.ts.map +1 -0
- package/dist/fill-triangulate.js +333 -0
- package/dist/fill-triangulate.js.map +1 -0
- package/dist/index.d.ts +154 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +294 -45
- package/dist/index.js.map +1 -1
- package/dist/math.d.ts +49 -1
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +122 -67
- package/dist/math.js.map +1 -1
- package/dist/renderer-symbolic-overlays.d.ts.map +1 -1
- package/dist/renderer-symbolic-overlays.js +20 -25
- package/dist/renderer-symbolic-overlays.js.map +1 -1
- package/dist/scene-geometry.d.ts +32 -0
- package/dist/scene-geometry.d.ts.map +1 -1
- package/dist/scene-geometry.js +66 -0
- package/dist/scene-geometry.js.map +1 -1
- package/dist/scene.d.ts +25 -0
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +91 -109
- package/dist/scene.js.map +1 -1
- package/dist/section-2d-lift.d.ts +109 -0
- package/dist/section-2d-lift.d.ts.map +1 -0
- package/dist/section-2d-lift.js +168 -0
- package/dist/section-2d-lift.js.map +1 -0
- package/dist/section-2d-line-buffer.d.ts +58 -0
- package/dist/section-2d-line-buffer.d.ts.map +1 -0
- package/dist/section-2d-line-buffer.js +109 -0
- package/dist/section-2d-line-buffer.js.map +1 -0
- package/dist/section-2d-overlay.d.ts +28 -77
- package/dist/section-2d-overlay.d.ts.map +1 -1
- package/dist/section-2d-overlay.js +161 -593
- package/dist/section-2d-overlay.js.map +1 -1
- package/dist/section-plane-basis.d.ts +22 -3
- package/dist/section-plane-basis.d.ts.map +1 -1
- package/dist/section-plane-basis.js +51 -10
- package/dist/section-plane-basis.js.map +1 -1
- package/dist/section-plane.d.ts.map +1 -1
- package/dist/section-plane.js +10 -1
- package/dist/section-plane.js.map +1 -1
- package/dist/shaders/section-2d-overlay.wgsl.d.ts +102 -0
- package/dist/shaders/section-2d-overlay.wgsl.d.ts.map +1 -0
- package/dist/shaders/section-2d-overlay.wgsl.js +281 -0
- package/dist/shaders/section-2d-overlay.wgsl.js.map +1 -0
- package/dist/symbolic-overlay-pipelines.d.ts +10 -39
- package/dist/symbolic-overlay-pipelines.d.ts.map +1 -1
- package/dist/symbolic-overlay-pipelines.js +20 -163
- package/dist/symbolic-overlay-pipelines.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* WGSL shaders for the section 2D overlay (cut-cap fills + overlay lines).
|
|
6
|
+
*
|
|
7
|
+
* Two pipelines share this file because they deliberately share ONE 160-byte
|
|
8
|
+
* uniform LAYOUT: the fill shader reads `viewProj … params2` (the first 144 B)
|
|
9
|
+
* and the line shader reads `viewProj`, `planeOffset` and the appended
|
|
10
|
+
* `lineColor` at byte offset 144. They share the layout, not the storage — each
|
|
11
|
+
* draw site gets its own record in one buffer, addressed by a dynamic
|
|
12
|
+
* bind-group offset; see {@link SECTION_2D_UNIFORM_SLOT_INDEX}.
|
|
13
|
+
* The two `struct Uniforms` declarations below
|
|
14
|
+
* must therefore stay field-for-field compatible, and every TypeScript writer
|
|
15
|
+
* must address the buffer through `SECTION_2D_UNIFORM_SLOTS` rather than a
|
|
16
|
+
* hand-written index. `section-2d-uniform-layout.test.ts` parses both structs
|
|
17
|
+
* out of these sources and fails if either drifts from the slot table.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Float32 indices of each field in the shared uniform buffer.
|
|
21
|
+
*
|
|
22
|
+
* WGSL std140-style layout: `mat4x4<f32>` is 64 B (16 floats) and each
|
|
23
|
+
* `vec4<f32>` is 16 B (4 floats), all naturally aligned, so the offsets are a
|
|
24
|
+
* simple running sum. These are FLOAT offsets — multiply by 4 for the byte
|
|
25
|
+
* offsets quoted in the shader comments.
|
|
26
|
+
*/
|
|
27
|
+
export const SECTION_2D_UNIFORM_SLOTS = {
|
|
28
|
+
/** mat4x4<f32> — floats 0..15 (bytes 0..63) */
|
|
29
|
+
viewProj: 0,
|
|
30
|
+
/** vec4<f32> — floats 16..19 (bytes 64..79) */
|
|
31
|
+
planeOffset: 16,
|
|
32
|
+
/** vec4<f32> — floats 20..23 (bytes 80..95) */
|
|
33
|
+
capFillColor: 20,
|
|
34
|
+
/** vec4<f32> — floats 24..27 (bytes 96..111) */
|
|
35
|
+
capStrokeColor: 24,
|
|
36
|
+
/** vec4<f32> x=patternId, y=spacingPx, z=angleRad, w=widthPx — floats 28..31 */
|
|
37
|
+
params: 28,
|
|
38
|
+
/** vec4<f32> x=secondaryAngleRad, y,z,w reserved — floats 32..35 */
|
|
39
|
+
params2: 32,
|
|
40
|
+
/** vec4<f32> overlay / section-cut line colour — floats 36..39 (bytes 144..159) */
|
|
41
|
+
lineColor: 36,
|
|
42
|
+
};
|
|
43
|
+
/** Total float count of one uniform record (40 floats = 160 bytes). */
|
|
44
|
+
export const SECTION_2D_UNIFORM_FLOATS = 40;
|
|
45
|
+
/** Total byte size of one uniform record. */
|
|
46
|
+
export const SECTION_2D_UNIFORM_BYTES = SECTION_2D_UNIFORM_FLOATS * 4;
|
|
47
|
+
/**
|
|
48
|
+
* One uniform slot per draw site.
|
|
49
|
+
*
|
|
50
|
+
* The overlay renderer issues up to six draws into a **single** render pass —
|
|
51
|
+
* the section cut cap plus five world-space line families — and every one of
|
|
52
|
+
* them needs a different `lineColor` (and, for the cap, a different
|
|
53
|
+
* `planeOffset` and the whole cap-style tail). With one record shared between
|
|
54
|
+
* them the last `queue.writeBuffer` before submit is what the GPU sees for all
|
|
55
|
+
* six: `writeBuffer` is a *queue* operation, so it is applied before the
|
|
56
|
+
* command buffer that references it executes, no matter where in the encoding
|
|
57
|
+
* it was issued. Every earlier draw therefore rendered with the last draw's
|
|
58
|
+
* uniforms — most visibly, the clash box's colour bleeding onto the annotation,
|
|
59
|
+
* alignment, grid and DXF overlays, and the cap losing its fill colour and
|
|
60
|
+
* hatch to the zeroed tail the line draws write.
|
|
61
|
+
*
|
|
62
|
+
* Each site owning a fixed slot, addressed with a dynamic bind-group offset,
|
|
63
|
+
* is what makes the six draws independent while keeping the buffer, the layout
|
|
64
|
+
* and the bind group under the single owner issue #2456 insisted on. A slot per
|
|
65
|
+
* site (rather than a bump allocator) needs no per-frame reset hook: each site
|
|
66
|
+
* draws at most once per pass.
|
|
67
|
+
*/
|
|
68
|
+
export const SECTION_2D_UNIFORM_SLOT_INDEX = {
|
|
69
|
+
/** The section cut cap: fill + outline, which share one record by design. */
|
|
70
|
+
sectionCut: 0,
|
|
71
|
+
annotation: 1,
|
|
72
|
+
alignment: 2,
|
|
73
|
+
grid: 3,
|
|
74
|
+
dxf: 4,
|
|
75
|
+
clashBox: 5,
|
|
76
|
+
};
|
|
77
|
+
/** How many uniform records the shared buffer holds. */
|
|
78
|
+
export const SECTION_2D_UNIFORM_SLOT_COUNT = 6;
|
|
79
|
+
/**
|
|
80
|
+
* Byte stride between uniform slots for `device`.
|
|
81
|
+
*
|
|
82
|
+
* A dynamic offset must be a multiple of `minUniformBufferOffsetAlignment`,
|
|
83
|
+
* which a device reports for itself (256 is the guaranteed-supported default,
|
|
84
|
+
* but a device may allow less). Round the 160-byte record up to that alignment
|
|
85
|
+
* rather than assuming either number.
|
|
86
|
+
*/
|
|
87
|
+
export function sectionUniformSlotStride(device) {
|
|
88
|
+
const alignment = device.limits?.minUniformBufferOffsetAlignment ?? 256;
|
|
89
|
+
const safe = Number.isFinite(alignment) && alignment > 0 ? alignment : 256;
|
|
90
|
+
return Math.ceil(SECTION_2D_UNIFORM_BYTES / safe) * safe;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fill shader for the cut cap.
|
|
94
|
+
*
|
|
95
|
+
* Applies the user-defined cap style (fill colour + screen-space hatch) on top
|
|
96
|
+
* of the EXACT 2D section polygons produced by SectionCutter. Per-vertex colour
|
|
97
|
+
* DRIVES the fill when a polygon opts in (alpha >= 0): a material-layer
|
|
98
|
+
* wall/slab fills each layer with its own IfcMaterial colour, matching the 3D
|
|
99
|
+
* build-up. Polygons that pass the sentinel alpha -1 fall back to the uniform
|
|
100
|
+
* cap style, so single-material cuts read as one architectural section exactly
|
|
101
|
+
* as before. Hatch + stroke apply over either base.
|
|
102
|
+
*/
|
|
103
|
+
export const SECTION_2D_CAP_FILL_WGSL = /* wgsl */ `
|
|
104
|
+
struct Uniforms {
|
|
105
|
+
viewProj: mat4x4<f32>,
|
|
106
|
+
planeOffset: vec4<f32>, // Small offset to render slightly in front of section plane
|
|
107
|
+
capFillColor: vec4<f32>,
|
|
108
|
+
capStrokeColor: vec4<f32>,
|
|
109
|
+
// x=patternId, y=spacingPx, z=angleRad, w=widthPx
|
|
110
|
+
params: vec4<f32>,
|
|
111
|
+
// x=secondaryAngleRad, y,z,w reserved
|
|
112
|
+
params2: vec4<f32>,
|
|
113
|
+
}
|
|
114
|
+
@binding(0) @group(0) var<uniform> uniforms: Uniforms;
|
|
115
|
+
|
|
116
|
+
struct VertexInput {
|
|
117
|
+
@location(0) position: vec3<f32>,
|
|
118
|
+
@location(1) color: vec4<f32>,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
struct VertexOutput {
|
|
122
|
+
@builtin(position) position: vec4<f32>,
|
|
123
|
+
@location(0) color: vec4<f32>,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@vertex
|
|
127
|
+
fn vs_main(input: VertexInput) -> VertexOutput {
|
|
128
|
+
var output: VertexOutput;
|
|
129
|
+
let offsetPos = input.position + uniforms.planeOffset.xyz;
|
|
130
|
+
output.position = uniforms.viewProj * vec4<f32>(offsetPos, 1.0);
|
|
131
|
+
output.color = input.color;
|
|
132
|
+
return output;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Screen-space hatch pattern helpers (ported from section-cap.wgsl).
|
|
136
|
+
fn lineMask(u: f32, s: f32, w: f32) -> f32 {
|
|
137
|
+
let f = fract(u / s) * s;
|
|
138
|
+
let d = min(f, s - f);
|
|
139
|
+
return 1.0 - smoothstep(w * 0.5, w * 0.5 + 1.0, d);
|
|
140
|
+
}
|
|
141
|
+
fn rotate(p: vec2<f32>, a: f32) -> vec2<f32> {
|
|
142
|
+
let c = cos(a);
|
|
143
|
+
let s = sin(a);
|
|
144
|
+
return vec2<f32>(c * p.x - s * p.y, s * p.x + c * p.y);
|
|
145
|
+
}
|
|
146
|
+
fn hatchIntensity(fragCoord: vec2<f32>, patternId: u32, spacing: f32, angle: f32, width: f32, angle2: f32) -> f32 {
|
|
147
|
+
let p = fragCoord;
|
|
148
|
+
if (patternId == 0u) { return 0.0; } // solid
|
|
149
|
+
if (patternId == 1u) { // diagonal
|
|
150
|
+
let r = rotate(p, angle);
|
|
151
|
+
return lineMask(r.x, spacing, width);
|
|
152
|
+
}
|
|
153
|
+
if (patternId == 2u) { // cross-hatch
|
|
154
|
+
let r = rotate(p, angle);
|
|
155
|
+
let r2 = rotate(p, angle2);
|
|
156
|
+
return max(lineMask(r.x, spacing, width), lineMask(r2.x, spacing, width));
|
|
157
|
+
}
|
|
158
|
+
if (patternId == 3u) { return lineMask(p.y, spacing, width); } // horizontal
|
|
159
|
+
if (patternId == 4u) { return lineMask(p.x, spacing, width); } // vertical
|
|
160
|
+
if (patternId == 5u) {
|
|
161
|
+
// Concrete (ISO 128-50): clean regular dot grid. The previous
|
|
162
|
+
// version layered dashes on top which looked noisy and broken.
|
|
163
|
+
// Dots sit at every grid intersection; radius scales with
|
|
164
|
+
// stroke width so the user's width slider works consistently.
|
|
165
|
+
let gx = p.x - round(p.x / spacing) * spacing;
|
|
166
|
+
let gy = p.y - round(p.y / spacing) * spacing;
|
|
167
|
+
let d = sqrt(gx * gx + gy * gy);
|
|
168
|
+
let radius = max(1.0, width * 1.2);
|
|
169
|
+
return 1.0 - smoothstep(radius, radius + 1.0, d);
|
|
170
|
+
}
|
|
171
|
+
if (patternId == 6u) { // brick
|
|
172
|
+
let bandH = spacing;
|
|
173
|
+
let band = floor(p.y / bandH);
|
|
174
|
+
// Signed parity. p is @builtin(position) today, so it is a
|
|
175
|
+
// framebuffer coordinate and never negative — but u32() of a
|
|
176
|
+
// negative float is an out-of-range conversion in WGSL, and the
|
|
177
|
+
// moment this hatch is fed anything but fragCoord (a world- or
|
|
178
|
+
// drawing-space variant) the running bond would break across
|
|
179
|
+
// y = 0 in a backend-dependent way. i32(-1) & 1 is 1, so the
|
|
180
|
+
// parity alternates correctly in both directions.
|
|
181
|
+
let offset = select(0.0, bandH, (i32(band) & 1) == 1);
|
|
182
|
+
let horiz = lineMask(p.y, bandH, width);
|
|
183
|
+
let vertPos = p.x + offset * 0.5;
|
|
184
|
+
let vert = step(fract(vertPos / (bandH * 2.0)), 0.02);
|
|
185
|
+
return max(horiz, vert);
|
|
186
|
+
}
|
|
187
|
+
if (patternId == 7u) { // insulation
|
|
188
|
+
let y = spacing * 0.5 * sin(p.x * 6.2831853 / spacing) + p.y;
|
|
189
|
+
return lineMask(y, spacing, width);
|
|
190
|
+
}
|
|
191
|
+
return 0.0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
struct FragOut {
|
|
195
|
+
@location(0) color: vec4<f32>,
|
|
196
|
+
@location(1) objectId: vec4<f32>,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@fragment
|
|
200
|
+
fn fs_main(input: VertexOutput) -> FragOut {
|
|
201
|
+
let patternId = u32(uniforms.params.x + 0.5);
|
|
202
|
+
let spacing = max(2.0, uniforms.params.y);
|
|
203
|
+
let angle = uniforms.params.z;
|
|
204
|
+
let width = max(1.0, uniforms.params.w);
|
|
205
|
+
let angle2 = uniforms.params2.x;
|
|
206
|
+
|
|
207
|
+
let h = hatchIntensity(input.position.xy, patternId, spacing, angle, width, angle2);
|
|
208
|
+
// Per-polygon colour (a material-layer slab fills with its own
|
|
209
|
+
// IfcMaterial RGBA) overrides the uniform cap fill when present.
|
|
210
|
+
// Polygons without a colour carry the sentinel alpha -1 and fall back
|
|
211
|
+
// to the user's cap style, byte-identically. Hatch + stroke apply over
|
|
212
|
+
// whichever base is chosen, so the architectural hatch still works.
|
|
213
|
+
let useVertex = input.color.a >= 0.0;
|
|
214
|
+
let baseFill = select(uniforms.capFillColor, input.color, useVertex);
|
|
215
|
+
let rgb = mix(baseFill.rgb, uniforms.capStrokeColor.rgb, h * uniforms.capStrokeColor.a);
|
|
216
|
+
let a = max(baseFill.a, h * uniforms.capStrokeColor.a);
|
|
217
|
+
|
|
218
|
+
var out: FragOut;
|
|
219
|
+
out.color = vec4<f32>(rgb, a);
|
|
220
|
+
out.objectId = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
`;
|
|
224
|
+
/**
|
|
225
|
+
* Line shader for the section-cut outline and the standalone world-space line
|
|
226
|
+
* overlays (annotation / alignment / grid / DXF / clash box).
|
|
227
|
+
*/
|
|
228
|
+
export const SECTION_2D_OVERLAY_LINE_WGSL = /* wgsl */ `
|
|
229
|
+
// Mirrors the fill shader's uniform layout so both pipelines can share
|
|
230
|
+
// one buffer; the line shader only reads viewProj, planeOffset and the
|
|
231
|
+
// appended lineColor (byte offset 144). capFillColor/… are unused here but
|
|
232
|
+
// declared for correct field offsets.
|
|
233
|
+
struct Uniforms {
|
|
234
|
+
viewProj: mat4x4<f32>,
|
|
235
|
+
planeOffset: vec4<f32>,
|
|
236
|
+
capFillColor: vec4<f32>,
|
|
237
|
+
capStrokeColor: vec4<f32>,
|
|
238
|
+
params: vec4<f32>,
|
|
239
|
+
params2: vec4<f32>,
|
|
240
|
+
lineColor: vec4<f32>,
|
|
241
|
+
}
|
|
242
|
+
@binding(0) @group(0) var<uniform> uniforms: Uniforms;
|
|
243
|
+
|
|
244
|
+
struct VertexInput {
|
|
245
|
+
@location(0) position: vec3<f32>,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
struct VertexOutput {
|
|
249
|
+
@builtin(position) position: vec4<f32>,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@vertex
|
|
253
|
+
fn vs_main(input: VertexInput) -> VertexOutput {
|
|
254
|
+
var output: VertexOutput;
|
|
255
|
+
let offsetPos = input.position + uniforms.planeOffset.xyz;
|
|
256
|
+
let clip = uniforms.viewProj * vec4<f32>(offsetPos, 1.0);
|
|
257
|
+
// Reverse-Z decal nudge for lines coplanar with model faces
|
|
258
|
+
// (issue #812). WebGPU forbids depthStencil.depthBias on non-
|
|
259
|
+
// triangle topologies, so we do the equivalent in clip space:
|
|
260
|
+
// adding a small positive multiple of clip.w raises NDC z by a
|
|
261
|
+
// constant after the w-divide, which under reverse-Z means
|
|
262
|
+
// "slightly closer" — enough to beat MSAA jitter on annotation
|
|
263
|
+
// lines that ride exactly on a wall/floor.
|
|
264
|
+
output.position = vec4<f32>(clip.x, clip.y, clip.z + 5e-5 * clip.w, clip.w);
|
|
265
|
+
return output;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
struct FragOutLine {
|
|
269
|
+
@location(0) color: vec4<f32>,
|
|
270
|
+
@location(1) objectId: vec4<f32>,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@fragment
|
|
274
|
+
fn fs_main(input: VertexOutput) -> FragOutLine {
|
|
275
|
+
var out: FragOutLine;
|
|
276
|
+
out.color = uniforms.lineColor; // consumer-themeable (defaults black)
|
|
277
|
+
out.objectId = vec4<f32>(0.0, 0.0, 0.0, 0.0);
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
`;
|
|
281
|
+
//# sourceMappingURL=section-2d-overlay.wgsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-2d-overlay.wgsl.js","sourceRoot":"","sources":["../../src/shaders/section-2d-overlay.wgsl.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAE/D;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,+CAA+C;IAC/C,QAAQ,EAAE,CAAC;IACX,+CAA+C;IAC/C,WAAW,EAAE,EAAE;IACf,+CAA+C;IAC/C,YAAY,EAAE,EAAE;IAChB,gDAAgD;IAChD,cAAc,EAAE,EAAE;IAClB,gFAAgF;IAChF,MAAM,EAAE,EAAE;IACV,oEAAoE;IACpE,OAAO,EAAE,EAAE;IACX,mFAAmF;IACnF,SAAS,EAAE,EAAE;CACL,CAAC;AAEX,uEAAuE;AACvE,MAAM,CAAC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAE5C,6CAA6C;AAC7C,MAAM,CAAC,MAAM,wBAAwB,GAAG,yBAAyB,GAAG,CAAC,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,6EAA6E;IAC7E,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;IACZ,IAAI,EAAE,CAAC;IACP,GAAG,EAAE,CAAC;IACN,QAAQ,EAAE,CAAC;CACH,CAAC;AAEX,wDAAwD;AACxD,MAAM,CAAC,MAAM,6BAA6B,GAAG,CAAC,CAAC;AAE/C;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,MAAiB;IACxD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,+BAA+B,IAAI,GAAG,CAAC;IACxE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3E,OAAO,IAAI,CAAC,IAAI,CAAC,wBAAwB,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAwH5C,CAAC;AAER;;;GAGG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoDhD,CAAC"}
|
|
@@ -5,11 +5,9 @@
|
|
|
5
5
|
* viewProj)` entry point that the caller invokes from inside an existing
|
|
6
6
|
* RGBA-blended render pass.
|
|
7
7
|
*
|
|
8
|
-
* Triangulation
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Pathological concave shapes with many holes may show tessellation glitches —
|
|
12
|
-
* an `earcut` upgrade is straightforward when it matters.
|
|
8
|
+
* Triangulation lives in `fill-triangulate.ts` and is shared with the section
|
|
9
|
+
* cut caps: rings are nested by the even-odd rule, holes are bridged in, and
|
|
10
|
+
* the result is ear-clipped.
|
|
13
11
|
*/
|
|
14
12
|
import { SymbolicTextAtlas } from './symbolic-text-atlas.js';
|
|
15
13
|
export interface SymbolicFillInput {
|
|
@@ -63,12 +61,14 @@ export declare class SymbolicFillPipeline {
|
|
|
63
61
|
constructor(device: GPUDevice, presentationFormat: GPUTextureFormat, sampleCount?: number);
|
|
64
62
|
private init;
|
|
65
63
|
/**
|
|
66
|
-
* Upload a list of fill regions. Each region is triangulated
|
|
67
|
-
*
|
|
64
|
+
* Upload a list of fill regions. Each region is triangulated by the shared
|
|
65
|
+
* even-odd triangulator (`fill-triangulate.ts`) into a single vertex buffer.
|
|
68
66
|
*
|
|
69
|
-
* Pass an empty array to clear.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
67
|
+
* Pass an empty array to clear. Rings with fewer than 3 vertices are
|
|
68
|
+
* dropped, and so is any ring set that yields no triangle at all (a fully
|
|
69
|
+
* collinear bound). Inner bounds themselves are never dropped: since #2516
|
|
70
|
+
* the bridge always finds an anchor, so a hole cannot fall out of the
|
|
71
|
+
* result the way it used to when it "couldn't be merged".
|
|
72
72
|
*/
|
|
73
73
|
upload(fills: readonly SymbolicFillInput[]): void;
|
|
74
74
|
hasGeometry(): boolean;
|
|
@@ -107,33 +107,4 @@ export declare class SymbolicTextPipeline {
|
|
|
107
107
|
render(pass: GPURenderPassEncoder, viewProj: Float32Array, viewportPxWidth: number, viewportPxHeight: number, cameraRight: readonly [number, number, number], cameraUp: readonly [number, number, number], targetGlyphPx?: number): void;
|
|
108
108
|
destroy(): void;
|
|
109
109
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Stitch each hole into the outer ring with a single bridge edge so the
|
|
112
|
-
* result is a simple polygon ear-clipping can handle. Mirrors mapbox/earcut's
|
|
113
|
-
* `eliminateHoles` pass:
|
|
114
|
-
*
|
|
115
|
-
* 1. For each hole, pick its rightmost (max-x) vertex as the bridge start.
|
|
116
|
-
* 2. Sort holes by descending bridge-start x so outer holes go in first.
|
|
117
|
-
* 3. Walk the outer ring for the vertex on the right side of the bridge
|
|
118
|
-
* that's "visible" from the hole — closest match wins.
|
|
119
|
-
* 4. Splice the hole (rotated to start at its bridge vertex, and reversed
|
|
120
|
-
* so its winding opposes the outer ring) into the outer ring at the
|
|
121
|
-
* anchor, closing both ends back to their starts to form a zero-area
|
|
122
|
-
* bridge edge.
|
|
123
|
-
*
|
|
124
|
-
* This breaks under pathological inputs (overlapping holes, holes outside
|
|
125
|
-
* the outer ring) but those don't occur in well-formed `IfcAnnotationFillArea`
|
|
126
|
-
* geometry — the IFC schema requires the outer bound to contain all inner
|
|
127
|
-
* bounds.
|
|
128
|
-
*/
|
|
129
|
-
export type Pt = {
|
|
130
|
-
x: number;
|
|
131
|
-
z: number;
|
|
132
|
-
};
|
|
133
|
-
export declare function joinHoles(outer: Pt[], holes: Pt[][]): Pt[];
|
|
134
|
-
/** Ear-clipping triangulation of a simple polygon. Returns triangle vertex indices. */
|
|
135
|
-
export declare function earClip(ring: ReadonlyArray<{
|
|
136
|
-
x: number;
|
|
137
|
-
z: number;
|
|
138
|
-
}>): number[][];
|
|
139
110
|
//# sourceMappingURL=symbolic-overlay-pipelines.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbolic-overlay-pipelines.d.ts","sourceRoot":"","sources":["../src/symbolic-overlay-pipelines.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"symbolic-overlay-pipelines.d.ts","sourceRoot":"","sources":["../src/symbolic-overlay-pipelines.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,15 +8,14 @@
|
|
|
8
8
|
* viewProj)` entry point that the caller invokes from inside an existing
|
|
9
9
|
* RGBA-blended render pass.
|
|
10
10
|
*
|
|
11
|
-
* Triangulation
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Pathological concave shapes with many holes may show tessellation glitches —
|
|
15
|
-
* an `earcut` upgrade is straightforward when it matters.
|
|
11
|
+
* Triangulation lives in `fill-triangulate.ts` and is shared with the section
|
|
12
|
+
* cut caps: rings are nested by the even-odd rule, holes are bridged in, and
|
|
13
|
+
* the result is ear-clipped.
|
|
16
14
|
*/
|
|
17
15
|
import { SymbolicTextAtlas } from './symbolic-text-atlas.js';
|
|
18
16
|
import { SYMBOLIC_FILL_WGSL, SYMBOLIC_TEXT_WGSL, } from './shaders/symbolic-overlay.wgsl.js';
|
|
19
17
|
import { PIPELINE_CONSTANTS } from './constants.js';
|
|
18
|
+
import { triangulateRings } from './fill-triangulate.js';
|
|
20
19
|
const FILL_VERTEX_STRIDE_BYTES = (3 + 4) * 4; // pos.xyz + color.rgba, 4 bytes each
|
|
21
20
|
const TEXT_INSTANCE_STRIDE_BYTES = (3 + 3 + 3 + 4 + 4 + 3 + 1 + 1 + 4 + 1) * 4;
|
|
22
21
|
// origin.xyz + rightAxis.xyz + upAxis.xyz + uvBounds.xyzw + color.rgba
|
|
@@ -137,12 +136,14 @@ export class SymbolicFillPipeline {
|
|
|
137
136
|
});
|
|
138
137
|
}
|
|
139
138
|
/**
|
|
140
|
-
* Upload a list of fill regions. Each region is triangulated
|
|
141
|
-
*
|
|
139
|
+
* Upload a list of fill regions. Each region is triangulated by the shared
|
|
140
|
+
* even-odd triangulator (`fill-triangulate.ts`) into a single vertex buffer.
|
|
142
141
|
*
|
|
143
|
-
* Pass an empty array to clear.
|
|
144
|
-
*
|
|
145
|
-
*
|
|
142
|
+
* Pass an empty array to clear. Rings with fewer than 3 vertices are
|
|
143
|
+
* dropped, and so is any ring set that yields no triangle at all (a fully
|
|
144
|
+
* collinear bound). Inner bounds themselves are never dropped: since #2516
|
|
145
|
+
* the bridge always finds an anchor, so a hole cannot fall out of the
|
|
146
|
+
* result the way it used to when it "couldn't be merged".
|
|
146
147
|
*/
|
|
147
148
|
upload(fills) {
|
|
148
149
|
this.init();
|
|
@@ -607,14 +608,14 @@ function parseBoxAlignment(s) {
|
|
|
607
608
|
return { horizontal, vertical };
|
|
608
609
|
}
|
|
609
610
|
/**
|
|
610
|
-
* Triangulate a
|
|
611
|
-
*
|
|
612
|
-
* fill pipeline's vertex layout
|
|
613
|
-
* < 3 valid vertices are silently dropped.
|
|
611
|
+
* Triangulate a fill region (outer bound plus inner bounds) and append every
|
|
612
|
+
* output triangle to `stream` as 3 x (x, y, z, r, g, b, a) entries, matching
|
|
613
|
+
* the fill pipeline's vertex layout.
|
|
614
614
|
*
|
|
615
|
-
*
|
|
616
|
-
* vertices
|
|
617
|
-
*
|
|
615
|
+
* `holesOffsets` splits the flat `points` buffer into rings; rings with fewer
|
|
616
|
+
* than 3 vertices are dropped. Which of those rings are filled and which are
|
|
617
|
+
* voids is decided by nesting, not by position, so an inner bound that itself
|
|
618
|
+
* contains an island fills that island (see `fill-triangulate.ts`).
|
|
618
619
|
*/
|
|
619
620
|
function triangulateFillTo(stream, fill) {
|
|
620
621
|
const { points, holesOffsets, worldY, color } = fill;
|
|
@@ -625,7 +626,6 @@ function triangulateFillTo(stream, fill) {
|
|
|
625
626
|
const ringStarts = [0, ...Array.from(holesOffsets), totalVerts];
|
|
626
627
|
if (ringStarts.length < 2)
|
|
627
628
|
return;
|
|
628
|
-
// Pull each ring's vertices out.
|
|
629
629
|
const rings = [];
|
|
630
630
|
for (let r = 0; r < ringStarts.length - 1; r++) {
|
|
631
631
|
const start = ringStarts[r];
|
|
@@ -640,155 +640,12 @@ function triangulateFillTo(stream, fill) {
|
|
|
640
640
|
}
|
|
641
641
|
if (rings.length === 0)
|
|
642
642
|
return;
|
|
643
|
-
|
|
644
|
-
// rightmost vertex to the nearest outer-ring vertex (the same approach
|
|
645
|
-
// earcut.js takes for polygon-with-holes input). Ear-clipping then runs on
|
|
646
|
-
// the resulting simple polygon. The hole's winding is reversed first so the
|
|
647
|
-
// combined ring's signed area stays consistent and the ear-test sign holds.
|
|
648
|
-
const outer = rings[0];
|
|
649
|
-
const holes = rings.slice(1);
|
|
650
|
-
const stitched = holes.length === 0 ? outer : joinHoles(outer, holes);
|
|
651
|
-
const triangles = earClip(stitched);
|
|
643
|
+
const { points: verts, triangles } = triangulateRings(rings);
|
|
652
644
|
for (const tri of triangles) {
|
|
653
645
|
for (const idx of tri) {
|
|
654
|
-
const v =
|
|
646
|
+
const v = verts[idx];
|
|
655
647
|
stream.push(v.x, worldY, v.z, color[0], color[1], color[2], color[3]);
|
|
656
648
|
}
|
|
657
649
|
}
|
|
658
650
|
}
|
|
659
|
-
export function joinHoles(outer, holes) {
|
|
660
|
-
if (holes.length === 0)
|
|
661
|
-
return outer;
|
|
662
|
-
const sorted = holes
|
|
663
|
-
.map((h) => {
|
|
664
|
-
let bestI = 0;
|
|
665
|
-
for (let i = 1; i < h.length; i++) {
|
|
666
|
-
if (h[i].x > h[bestI].x)
|
|
667
|
-
bestI = i;
|
|
668
|
-
}
|
|
669
|
-
return { ring: h, startIdx: bestI, startX: h[bestI].x, startZ: h[bestI].z };
|
|
670
|
-
})
|
|
671
|
-
.sort((a, b) => b.startX - a.startX);
|
|
672
|
-
let result = outer.slice();
|
|
673
|
-
for (const { ring, startIdx, startX, startZ } of sorted) {
|
|
674
|
-
// Find the outer-ring index with the smallest distance to the bridge
|
|
675
|
-
// start, preferring vertices to the right (x > startX). When nothing is
|
|
676
|
-
// to the right (hole touches the outer ring's right edge), fall back to
|
|
677
|
-
// global nearest.
|
|
678
|
-
let bestIdx = -1;
|
|
679
|
-
let bestDist = Infinity;
|
|
680
|
-
for (let i = 0; i < result.length; i++) {
|
|
681
|
-
const p = result[i];
|
|
682
|
-
if (p.x <= startX)
|
|
683
|
-
continue;
|
|
684
|
-
const d = (p.x - startX) * (p.x - startX) + (p.z - startZ) * (p.z - startZ);
|
|
685
|
-
if (d < bestDist) {
|
|
686
|
-
bestDist = d;
|
|
687
|
-
bestIdx = i;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
if (bestIdx < 0) {
|
|
691
|
-
for (let i = 0; i < result.length; i++) {
|
|
692
|
-
const p = result[i];
|
|
693
|
-
const d = (p.x - startX) * (p.x - startX) + (p.z - startZ) * (p.z - startZ);
|
|
694
|
-
if (d < bestDist) {
|
|
695
|
-
bestDist = d;
|
|
696
|
-
bestIdx = i;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
if (bestIdx < 0)
|
|
701
|
-
continue;
|
|
702
|
-
// Hole reversed so its winding opposes outer (outer is CCW after earClip
|
|
703
|
-
// normalisation; holes should be CW for the combined ring's area to come
|
|
704
|
-
// out right). Rotate to start at the bridge vertex.
|
|
705
|
-
const reversed = ring.slice().reverse();
|
|
706
|
-
const reversedStartIdx = ring.length - 1 - startIdx;
|
|
707
|
-
const rotated = [
|
|
708
|
-
...reversed.slice(reversedStartIdx),
|
|
709
|
-
...reversed.slice(0, reversedStartIdx),
|
|
710
|
-
];
|
|
711
|
-
result = [
|
|
712
|
-
...result.slice(0, bestIdx + 1),
|
|
713
|
-
...rotated,
|
|
714
|
-
rotated[0],
|
|
715
|
-
result[bestIdx],
|
|
716
|
-
...result.slice(bestIdx + 1),
|
|
717
|
-
];
|
|
718
|
-
}
|
|
719
|
-
return result;
|
|
720
|
-
}
|
|
721
|
-
/** Ear-clipping triangulation of a simple polygon. Returns triangle vertex indices. */
|
|
722
|
-
export function earClip(ring) {
|
|
723
|
-
const n = ring.length;
|
|
724
|
-
if (n < 3)
|
|
725
|
-
return [];
|
|
726
|
-
if (n === 3)
|
|
727
|
-
return [[0, 1, 2]];
|
|
728
|
-
// Working list of indices.
|
|
729
|
-
const indices = [];
|
|
730
|
-
// Determine winding: positive shoelace = CCW; otherwise reverse so the
|
|
731
|
-
// ear-test below uses a consistent sign.
|
|
732
|
-
let area = 0;
|
|
733
|
-
for (let i = 0; i < n; i++) {
|
|
734
|
-
const a = ring[i];
|
|
735
|
-
const b = ring[(i + 1) % n];
|
|
736
|
-
area += (a.x * b.z) - (b.x * a.z);
|
|
737
|
-
}
|
|
738
|
-
const ccw = area > 0;
|
|
739
|
-
for (let i = 0; i < n; i++) {
|
|
740
|
-
indices.push(ccw ? i : n - 1 - i);
|
|
741
|
-
}
|
|
742
|
-
const triangles = [];
|
|
743
|
-
let safety = indices.length * indices.length;
|
|
744
|
-
while (indices.length > 3 && safety-- > 0) {
|
|
745
|
-
let found = false;
|
|
746
|
-
for (let i = 0; i < indices.length; i++) {
|
|
747
|
-
const ia = indices[(i + indices.length - 1) % indices.length];
|
|
748
|
-
const ib = indices[i];
|
|
749
|
-
const ic = indices[(i + 1) % indices.length];
|
|
750
|
-
const a = ring[ia];
|
|
751
|
-
const b = ring[ib];
|
|
752
|
-
const c = ring[ic];
|
|
753
|
-
// Convex check (cross product of (b-a) × (c-b) > 0 for CCW).
|
|
754
|
-
const cross = (b.x - a.x) * (c.z - b.z) - (b.z - a.z) * (c.x - b.x);
|
|
755
|
-
if (cross <= 0)
|
|
756
|
-
continue;
|
|
757
|
-
// No other vertex inside triangle (a, b, c).
|
|
758
|
-
let containsOther = false;
|
|
759
|
-
for (let j = 0; j < indices.length; j++) {
|
|
760
|
-
const ij = indices[j];
|
|
761
|
-
if (ij === ia || ij === ib || ij === ic)
|
|
762
|
-
continue;
|
|
763
|
-
if (pointInTriangle(ring[ij], a, b, c)) {
|
|
764
|
-
containsOther = true;
|
|
765
|
-
break;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
if (containsOther)
|
|
769
|
-
continue;
|
|
770
|
-
triangles.push([ia, ib, ic]);
|
|
771
|
-
indices.splice(i, 1);
|
|
772
|
-
found = true;
|
|
773
|
-
break;
|
|
774
|
-
}
|
|
775
|
-
if (!found)
|
|
776
|
-
break; // degenerate polygon — emit what we have
|
|
777
|
-
}
|
|
778
|
-
if (indices.length === 3) {
|
|
779
|
-
triangles.push([indices[0], indices[1], indices[2]]);
|
|
780
|
-
}
|
|
781
|
-
return triangles;
|
|
782
|
-
}
|
|
783
|
-
function pointInTriangle(p, a, b, c) {
|
|
784
|
-
const s1 = sign(p, a, b);
|
|
785
|
-
const s2 = sign(p, b, c);
|
|
786
|
-
const s3 = sign(p, c, a);
|
|
787
|
-
const hasNeg = s1 < 0 || s2 < 0 || s3 < 0;
|
|
788
|
-
const hasPos = s1 > 0 || s2 > 0 || s3 > 0;
|
|
789
|
-
return !(hasNeg && hasPos);
|
|
790
|
-
}
|
|
791
|
-
function sign(p1, p2, p3) {
|
|
792
|
-
return (p1.x - p3.x) * (p2.z - p3.z) - (p2.x - p3.x) * (p1.z - p3.z);
|
|
793
|
-
}
|
|
794
651
|
//# sourceMappingURL=symbolic-overlay-pipelines.js.map
|