@weasel-js/kernel3d 1.15.2 → 1.16.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/README.md +38 -2
- package/dist/index.d.ts +141 -1
- package/dist/index.js +338 -2
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -4,8 +4,9 @@ Poses, an orbit camera, ray picking and screen-projected chrome geometry for
|
|
|
4
4
|
weasel, over `@weasel-js/core`'s scene graph and dispatcher.
|
|
5
5
|
|
|
6
6
|
**It hosts a renderer rather than owning one.** A consumer brings its own — a
|
|
7
|
-
bespoke WebGL pass, three.js, anything — and the kernel hands it poses.
|
|
8
|
-
here touches WebGL
|
|
7
|
+
bespoke WebGL pass, three.js, anything — and the kernel hands it poses. The one
|
|
8
|
+
thing here that touches WebGL is the line pass (below), which that renderer
|
|
9
|
+
calls into its own context.
|
|
9
10
|
|
|
10
11
|
```ts
|
|
11
12
|
import { createScene } from '@weasel-js/core';
|
|
@@ -110,6 +111,41 @@ makes a pane a target, unprojecting onto the same ground, and `cameraOfAim3d`
|
|
|
110
111
|
gives back the camera a dragged aim stands for — what an `AimSource`'s `setAim`
|
|
111
112
|
writes.
|
|
112
113
|
|
|
114
|
+
## Lines
|
|
115
|
+
|
|
116
|
+
A stroke has no mesh a renderer can draw, so the kernel ships a pass for it.
|
|
117
|
+
`packLines` turns polylines into vertices, and `createLinePass(gl)` draws them
|
|
118
|
+
into a WebGL2 context the host owns, between its own draws:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const pass = createLinePass(gl);
|
|
122
|
+
pass.draw(
|
|
123
|
+
packLines([{ runs: [{ points }], color: [1, 0.4, 0.2, 1], width: 4, trim: { end: 0.6 }, model }]),
|
|
124
|
+
cameraViewProjection(camera, width / height),
|
|
125
|
+
{ width, height }, // the viewport in CSS pixels
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- **Widths are pixels.** Each line is widened after projection, so its shaft and
|
|
130
|
+
head are the same size near the camera and far from it. Corners miter along
|
|
131
|
+
the bisector, up to a limit past which a hairpin keeps a notch.
|
|
132
|
+
- **A trim is a `StrokeTrim`, measured in the line's own space** before `model`
|
|
133
|
+
places it, through geom's length table and stroke's `resolveTrim`, as a 2D
|
|
134
|
+
stroke's is. It is applied when packing, so an animated trim repacks.
|
|
135
|
+
- **A head caps each open run's end**: `HEAD` by default, a `HeadShape` to set
|
|
136
|
+
one, `null` for none. It is capped at a share of the line's length on screen,
|
|
137
|
+
and the shaft narrows by what the cap took. It stands back from the tip along
|
|
138
|
+
its own segment's direction on screen, so a head longer than its segment
|
|
139
|
+
stays on that segment's line. A closed subpath, or a run cut from one, has no
|
|
140
|
+
head.
|
|
141
|
+
- **A segment with an end behind the eye is dropped**, not drawn across the
|
|
142
|
+
screen.
|
|
143
|
+
|
|
144
|
+
Depth is tested against the bound buffer by default and never written; pass
|
|
145
|
+
`{ depthTest: false }` to draw over everything. The pass puts back the GL state
|
|
146
|
+
it changes. A three.js host calls it after `renderer.render`, with
|
|
147
|
+
`renderer.getContext()`, and then `renderer.resetState()`.
|
|
148
|
+
|
|
113
149
|
## Math
|
|
114
150
|
|
|
115
151
|
Vectors, quaternions, 4×4 matrices and ray intersection live in
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Vec3, Mat4, Quat, Aabb, Ray } from '@weasel-js/geom/3d';
|
|
2
2
|
import { PoseComposition, SceneNode, Scene, NodeId, SnapSource, SnapOutcomeStore, AreaSelectDep, NodeAtPointDep, PoseDescriptor, SnapDep, InsertExtras, InsertDep, ViewAim, AimSpace, SnapMark, OngoingHandle, Action, Tool } from '@weasel-js/core';
|
|
3
3
|
import { KitSettingsStore } from '@weasel-js/react';
|
|
4
|
+
import { StrokeTrim } from '@weasel-js/paint';
|
|
4
5
|
import * as _weasel_js_prefs from '@weasel-js/prefs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -464,6 +465,145 @@ declare function collectOverlayBoxes(handles: Iterable<OngoingHandle>, origin: {
|
|
|
464
465
|
y: number;
|
|
465
466
|
}): ChromeBox[];
|
|
466
467
|
|
|
468
|
+
/**
|
|
469
|
+
* The numbers an arrowhead is cut from, and how a line's shaft and head share
|
|
470
|
+
* its length on screen. The line pass's vertex shader repeats {@link layoutLine}
|
|
471
|
+
* in GLSL; the two must agree.
|
|
472
|
+
*/
|
|
473
|
+
/**
|
|
474
|
+
* An arrowhead, set by the shaft it caps: a line is set by its width alone and
|
|
475
|
+
* its head follows, so a wide line is still an arrow rather than a band with a
|
|
476
|
+
* small head poking out of it.
|
|
477
|
+
*/
|
|
478
|
+
interface HeadShape {
|
|
479
|
+
/** The head's length, in multiples of half the shaft's width. */
|
|
480
|
+
lenRatio: number;
|
|
481
|
+
/** The most of the line's on-screen length the head may take. */
|
|
482
|
+
maxShare: number;
|
|
483
|
+
/** Half the head's base as a share of its length. The cap shortens a head; it never reshapes it. */
|
|
484
|
+
aspect: number;
|
|
485
|
+
}
|
|
486
|
+
/** A 16 px head 14 px across on a 4 px shaft, taking at most half the line. */
|
|
487
|
+
declare const HEAD: HeadShape;
|
|
488
|
+
/**
|
|
489
|
+
* How far the shaft runs on under the head, as a share of the head's length:
|
|
490
|
+
* enough to close the seam between them, little enough that a curving shaft
|
|
491
|
+
* stays inside the triangle.
|
|
492
|
+
*/
|
|
493
|
+
declare const HEAD_OVERDRAW = 0.1;
|
|
494
|
+
/** The head a shaft carries, in pixels, and the shaft that goes under it. */
|
|
495
|
+
interface HeadSize {
|
|
496
|
+
/** The head's length. */
|
|
497
|
+
headLen: number;
|
|
498
|
+
/** Half the head's base. */
|
|
499
|
+
headHalf: number;
|
|
500
|
+
/** Half the shaft's width: narrowed by whatever the cap took off the head. */
|
|
501
|
+
shaftHalf: number;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* The head `shaftHalf` carries on a line `linePx` long on screen. The head is
|
|
505
|
+
* capped at `head.maxShare` of the line, and the shaft narrows by the share
|
|
506
|
+
* the cap took, so a short line is the same arrow drawn smaller rather than a
|
|
507
|
+
* full-width band with a stub on the end. Left out, `linePx` caps nothing.
|
|
508
|
+
*/
|
|
509
|
+
declare function headFor(shaftHalf: number, linePx?: number, head?: HeadShape): HeadSize;
|
|
510
|
+
/** {@link HeadSize}, plus where along the line its shaft stops. */
|
|
511
|
+
interface LineLayout extends HeadSize {
|
|
512
|
+
/** Where the shaft ends, in the line's own length units. */
|
|
513
|
+
shaftEnd: number;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* How a line `length` long in its own units, drawn at `pxPerUnit` pixels per
|
|
517
|
+
* unit near its tip, splits between shaft and head. A line with no head
|
|
518
|
+
* (`head` null) or no known scale runs its shaft to the end.
|
|
519
|
+
*/
|
|
520
|
+
declare function layoutLine(shaftHalf: number, length: number, pxPerUnit: number, head: HeadShape | null): LineLayout;
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Lines as the line pass's vertices. Trimming happens here, on the CPU and in
|
|
524
|
+
* the line's own space; widths and the head are pixels, laid out by the shader
|
|
525
|
+
* once the camera is known.
|
|
526
|
+
*/
|
|
527
|
+
|
|
528
|
+
/** One subpath of a {@link Line3}: points joined by straight segments. */
|
|
529
|
+
interface LineRun3 {
|
|
530
|
+
points: readonly Vec3[];
|
|
531
|
+
/** Whether a closing edge runs from the last point back to the first. */
|
|
532
|
+
closed?: boolean;
|
|
533
|
+
}
|
|
534
|
+
/** A polyline drawn a fixed number of pixels wide, whatever its depth. */
|
|
535
|
+
interface Line3 {
|
|
536
|
+
/** Subpaths in pen order, which is the order a trim runs. */
|
|
537
|
+
runs: readonly LineRun3[];
|
|
538
|
+
/** Straight (not premultiplied) RGBA, each 0–1. */
|
|
539
|
+
color: readonly [number, number, number, number];
|
|
540
|
+
/** The shaft's width in CSS pixels. Default 4. */
|
|
541
|
+
width?: number;
|
|
542
|
+
/** The head at each open run's end; null for none. Default {@link HEAD}. */
|
|
543
|
+
head?: HeadShape | null;
|
|
544
|
+
/** The stretch drawn, measured in the line's own space. */
|
|
545
|
+
trim?: StrokeTrim;
|
|
546
|
+
/** From the line's own space to the world. Default identity. */
|
|
547
|
+
model?: Mat4;
|
|
548
|
+
}
|
|
549
|
+
/** A visible stretch of a line: what the pass draws as one shaft and at most one head. */
|
|
550
|
+
interface VisibleRun3 {
|
|
551
|
+
/** In the line's own space. */
|
|
552
|
+
points: Vec3[];
|
|
553
|
+
closed: boolean;
|
|
554
|
+
headed: boolean;
|
|
555
|
+
}
|
|
556
|
+
/** Floats per vertex; see {@link packLines} for the layout. */
|
|
557
|
+
declare const LINE_FLOATS = 29;
|
|
558
|
+
/**
|
|
559
|
+
* The stretches of `line` its trim leaves, one per subpath the trim reaches.
|
|
560
|
+
* A closed subpath the trim covers whole stays closed; one it cuts is open and,
|
|
561
|
+
* as a closed subpath takes no marker, carries no head.
|
|
562
|
+
*/
|
|
563
|
+
declare function visibleRuns(line: Line3): VisibleRun3[];
|
|
564
|
+
/**
|
|
565
|
+
* `lines` as the line pass draws them: six vertices a segment, three more for
|
|
566
|
+
* a head, `LINE_FLOATS` floats each, laid out as
|
|
567
|
+
*
|
|
568
|
+
* | floats | holds |
|
|
569
|
+
* |---|---|
|
|
570
|
+
* | 0–2, 3–5 | the segment's two ends, in the world |
|
|
571
|
+
* | 6–8 | the point past this vertex's own end, so the shaft can miter |
|
|
572
|
+
* | 9, 10 | role and side |
|
|
573
|
+
* | 11–13 | the segment's start and end along its run, and the run's length, in the line's own units |
|
|
574
|
+
* | 14–17 | half the shaft's width in pixels, then the head's `lenRatio`, `maxShare` and `aspect` (`lenRatio` 0: no head) |
|
|
575
|
+
* | 18–20, 21–23, 24 | the run's tip, the start of its last segment, and that segment's length: one screen scale for every vertex of the run |
|
|
576
|
+
* | 25–28 | straight RGBA |
|
|
577
|
+
*/
|
|
578
|
+
declare function packLines(lines: readonly Line3[]): Float32Array;
|
|
579
|
+
|
|
580
|
+
/** Options for {@link LinePass.draw}. */
|
|
581
|
+
interface LineDrawOptions {
|
|
582
|
+
/** Hide the parts of a line behind what the bound depth buffer holds. Default true. */
|
|
583
|
+
depthTest?: boolean;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Lines drawn into a WebGL2 context the host owns. The kernel hosts a renderer
|
|
587
|
+
* rather than owning one; this is a pass that renderer calls, between its own.
|
|
588
|
+
*/
|
|
589
|
+
interface LinePass {
|
|
590
|
+
/**
|
|
591
|
+
* Draw `vertices`, as {@link packLines} lays them out, into the bound
|
|
592
|
+
* framebuffer under the host's `gl.viewport`. `viewProjection` takes world
|
|
593
|
+
* points to clip space; `size` is that viewport in CSS pixels, the unit a
|
|
594
|
+
* line's width is given in. Depth is tested, not written, and blending is
|
|
595
|
+
* straight alpha. The capabilities, depth mask, blend function, program,
|
|
596
|
+
* vertex array and array buffer it changes are put back as it found them.
|
|
597
|
+
*/
|
|
598
|
+
draw(vertices: Float32Array, viewProjection: Mat4, size: {
|
|
599
|
+
width: number;
|
|
600
|
+
height: number;
|
|
601
|
+
}, options?: LineDrawOptions): void;
|
|
602
|
+
dispose(): void;
|
|
603
|
+
}
|
|
604
|
+
/** A {@link LinePass} on `gl`: one program, one vertex array, one buffer refilled each draw. */
|
|
605
|
+
declare function createLinePass(gl: WebGL2RenderingContext): LinePass;
|
|
606
|
+
|
|
467
607
|
/**
|
|
468
608
|
* Camera tools and their actions.
|
|
469
609
|
*
|
|
@@ -556,4 +696,4 @@ declare const kernel3dKitSettingsSchema: Omit<_weasel_js_prefs.PrefGroup, "child
|
|
|
556
696
|
};
|
|
557
697
|
};
|
|
558
698
|
|
|
559
|
-
export { type Aim3d, type Aim3dOptions, type Camera3d, type Camera3dDep, type ChromeBox, DEFAULT_ORBIT_LIMITS, type Footprint, type Frustum3d, type GroundGridSourceOptions, MAX_PITCH, type Node3d, type NodeBounds, type Orbit3dKitSettings, type OrbitLimits, POSE3_COMPOSITION, type Pose3, type Scene3d, type ScreenBox, type ScreenLine, type ScreenPoint, type Snap3dOptions, UNIT_CUBE, type Viewport3d, type ViewportSource, type World3d, aabbOfPose, aimOf3d, aimSpace3d, alignPlaneSource, boxSnapSource, cameraEye, cameraOfAim3d, cameraView, cameraViewProjection, clampCamera, collectOverlayBoxes, createAreaSelect, createCamera, createInsert, createNodeAtPoint, createPoseDescriptor, createSnap, dollyAction, dollyBy, dollyToward, groundGridSource, homeAction, kernel3dKitSettingsSchema, ndcToScreen, orbitAction, orbitBy, panAction, panBy, pinchDollyAction, pose3, poseMatrix, projectAabbToScreen, projectPoint, projectSnapMarks, rayThroughScreenPoint, screenBoxOf, screenToNdc, useOrbitTool, worldPoseOf };
|
|
699
|
+
export { type Aim3d, type Aim3dOptions, type Camera3d, type Camera3dDep, type ChromeBox, DEFAULT_ORBIT_LIMITS, type Footprint, type Frustum3d, type GroundGridSourceOptions, HEAD, HEAD_OVERDRAW, type HeadShape, type HeadSize, LINE_FLOATS, type Line3, type LineDrawOptions, type LineLayout, type LinePass, type LineRun3, MAX_PITCH, type Node3d, type NodeBounds, type Orbit3dKitSettings, type OrbitLimits, POSE3_COMPOSITION, type Pose3, type Scene3d, type ScreenBox, type ScreenLine, type ScreenPoint, type Snap3dOptions, UNIT_CUBE, type Viewport3d, type ViewportSource, type VisibleRun3, type World3d, aabbOfPose, aimOf3d, aimSpace3d, alignPlaneSource, boxSnapSource, cameraEye, cameraOfAim3d, cameraView, cameraViewProjection, clampCamera, collectOverlayBoxes, createAreaSelect, createCamera, createInsert, createLinePass, createNodeAtPoint, createPoseDescriptor, createSnap, dollyAction, dollyBy, dollyToward, groundGridSource, headFor, homeAction, kernel3dKitSettingsSchema, layoutLine, ndcToScreen, orbitAction, orbitBy, packLines, panAction, panBy, pinchDollyAction, pose3, poseMatrix, projectAabbToScreen, projectPoint, projectSnapMarks, rayThroughScreenPoint, screenBoxOf, screenToNdc, useOrbitTool, visibleRuns, worldPoseOf };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { lookAt, multiply, perspective, add, scale, quatIdentity, compose, transformAabb, quatMultiply, quatConjugate, quatRotate, sub, invert, transformPoint, normalize, transformPoint4, distance, dot, len, aabbOfPoints, intersectRayPlane, pointOnRay, cross, intersectRayAabb } from '@weasel-js/geom/3d';
|
|
2
|
-
import { clamp, rectsOverlap } from '@weasel-js/geom';
|
|
2
|
+
import { clamp, lengthTable, curveSpan, rectsOverlap } from '@weasel-js/geom';
|
|
3
3
|
import { scenePoseFrame, createSnapOutcomeStore, assertSnapSourceIds, DEFAULT_SNAP_PX, overlaysOf, resolveOverlays, defineTool, snapSourcesFor, resolvedSnapMarks, SNAP_AXIS_STEP, gatherSnapCandidates, scenePoseAdapter, composeWorldPose } from '@weasel-js/core';
|
|
4
4
|
import { isDev } from '@weasel-js/react';
|
|
5
5
|
import { closestOnFlat, resolveSnap, createBvh, stepLine, createSettledCache } from '@weasel-js/geom/nd';
|
|
6
|
+
import { resolveTrim } from '@weasel-js/stroke';
|
|
6
7
|
import { useMemo } from 'react';
|
|
7
8
|
import { pref } from '@weasel-js/prefs';
|
|
8
9
|
|
|
@@ -1125,6 +1126,341 @@ function collectOverlayBoxes(handles, origin) {
|
|
|
1125
1126
|
}
|
|
1126
1127
|
return boxes;
|
|
1127
1128
|
}
|
|
1129
|
+
|
|
1130
|
+
// src/lines/head.ts
|
|
1131
|
+
var HEAD = { lenRatio: 8, maxShare: 0.5, aspect: 7 / 16 };
|
|
1132
|
+
var HEAD_OVERDRAW = 0.1;
|
|
1133
|
+
function headFor(shaftHalf, linePx = Number.POSITIVE_INFINITY, head = HEAD) {
|
|
1134
|
+
const wanted = shaftHalf * head.lenRatio;
|
|
1135
|
+
const headLen = Math.max(0, Math.min(wanted, head.maxShare * linePx));
|
|
1136
|
+
const headHalf = headLen * head.aspect;
|
|
1137
|
+
const shrunk = wanted > 0 ? shaftHalf * headLen / wanted : shaftHalf;
|
|
1138
|
+
return { headLen, headHalf, shaftHalf: wanted > 0 ? Math.min(shrunk, headHalf) : shrunk };
|
|
1139
|
+
}
|
|
1140
|
+
function layoutLine(shaftHalf, length, pxPerUnit, head) {
|
|
1141
|
+
if (!head || !(pxPerUnit > 0)) return { headLen: 0, headHalf: 0, shaftHalf, shaftEnd: length };
|
|
1142
|
+
const size = headFor(shaftHalf, pxPerUnit * length, head);
|
|
1143
|
+
return { ...size, shaftEnd: length - size.headLen / pxPerUnit * (1 - HEAD_OVERDRAW) };
|
|
1144
|
+
}
|
|
1145
|
+
var LINE_FLOATS = 29;
|
|
1146
|
+
var ROLE_BACK = 0;
|
|
1147
|
+
var ROLE_SHAFT_END = 1;
|
|
1148
|
+
var ROLE_HEAD_BASE = 2;
|
|
1149
|
+
var ROLE_TIP = 3;
|
|
1150
|
+
var SHAFT_CORNERS = [
|
|
1151
|
+
[ROLE_BACK, -1],
|
|
1152
|
+
[ROLE_BACK, 1],
|
|
1153
|
+
[ROLE_SHAFT_END, 1],
|
|
1154
|
+
[ROLE_BACK, -1],
|
|
1155
|
+
[ROLE_SHAFT_END, 1],
|
|
1156
|
+
[ROLE_SHAFT_END, -1]
|
|
1157
|
+
];
|
|
1158
|
+
var HEAD_CORNERS = [[ROLE_HEAD_BASE, -1], [ROLE_HEAD_BASE, 1], [ROLE_TIP, 0]];
|
|
1159
|
+
var flat = (points) => points.flatMap((p) => [p.x, p.y, p.z]);
|
|
1160
|
+
var lerp3 = (a, b, t) => ({ x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t, z: a.z + (b.z - a.z) * t });
|
|
1161
|
+
var dist = (a, b) => Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
|
|
1162
|
+
function visibleRuns(line) {
|
|
1163
|
+
const runs = line.runs.filter((r) => r.points.length >= 2);
|
|
1164
|
+
if (runs.length === 0) return [];
|
|
1165
|
+
const table = lengthTable(runs.map((r) => ({ points: flat(r.points), closed: !!r.closed })), 3);
|
|
1166
|
+
const span = resolveTrim(table, line.trim);
|
|
1167
|
+
const cut = span && curveSpan(table, span);
|
|
1168
|
+
if (!cut) return [];
|
|
1169
|
+
const out = [];
|
|
1170
|
+
for (let r = cut.from.run; r <= cut.to.run; r++) {
|
|
1171
|
+
const run = windowRun(runs[r], r, cut);
|
|
1172
|
+
if (run) out.push({ ...run, headed: !run.closed && !runs[r].closed && line.head !== null });
|
|
1173
|
+
}
|
|
1174
|
+
return out;
|
|
1175
|
+
}
|
|
1176
|
+
function windowRun(run, r, cut) {
|
|
1177
|
+
const pts = run.points;
|
|
1178
|
+
const n = pts.length;
|
|
1179
|
+
const segments = run.closed ? n : n - 1;
|
|
1180
|
+
const at = (i) => pts[i % n];
|
|
1181
|
+
const from = r === cut.from.run ? cut.from : { segment: 0, t: 0 };
|
|
1182
|
+
const to = r === cut.to.run ? cut.to : { segment: segments - 1, t: 1 };
|
|
1183
|
+
if (run.closed && from.segment === 0 && from.t === 0 && to.segment === segments - 1 && to.t === 1) {
|
|
1184
|
+
return { points: [...pts], closed: true };
|
|
1185
|
+
}
|
|
1186
|
+
const out = [lerp3(at(from.segment), at(from.segment + 1), from.t)];
|
|
1187
|
+
for (let k = from.segment + 1; k <= to.segment; k++) out.push(at(k));
|
|
1188
|
+
out.push(lerp3(at(to.segment), at(to.segment + 1), to.t));
|
|
1189
|
+
const kept = out.filter((p, i) => i === 0 || dist(out[i - 1], p) > 0);
|
|
1190
|
+
return kept.length >= 2 ? { points: kept, closed: false } : null;
|
|
1191
|
+
}
|
|
1192
|
+
function packLines(lines) {
|
|
1193
|
+
const chunks = [];
|
|
1194
|
+
for (const line of lines) {
|
|
1195
|
+
const shaftHalf = (line.width ?? 4) / 2;
|
|
1196
|
+
const head = line.head === void 0 ? HEAD : line.head;
|
|
1197
|
+
const toWorld = line.model ? (p) => transformPoint(line.model, p) : (p) => p;
|
|
1198
|
+
for (const run of visibleRuns(line)) {
|
|
1199
|
+
packRun(chunks, run, run.points.map(toWorld), shaftHalf, run.headed ? head : null, line.color);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
return Float32Array.from(chunks);
|
|
1203
|
+
}
|
|
1204
|
+
function packRun(out, run, world, shaftHalf, head, color) {
|
|
1205
|
+
const n = world.length;
|
|
1206
|
+
const segments = run.closed ? n : n - 1;
|
|
1207
|
+
const at = (i) => world[(i % n + n) % n];
|
|
1208
|
+
const along = [0];
|
|
1209
|
+
for (let i = 0; i < segments; i++) along.push(along[i] + dist(run.points[i], run.points[(i + 1) % n]));
|
|
1210
|
+
const total = along[segments];
|
|
1211
|
+
const apex = at(segments);
|
|
1212
|
+
const apexBack = at(segments - 1);
|
|
1213
|
+
const reach = total - along[segments - 1];
|
|
1214
|
+
const shape = head ? [head.lenRatio, head.maxShare, head.aspect] : [0, 0, 0];
|
|
1215
|
+
for (let i = 0; i < segments; i++) {
|
|
1216
|
+
const a = at(i);
|
|
1217
|
+
const b = at(i + 1);
|
|
1218
|
+
const before = run.closed || i > 0 ? at(i - 1) : a;
|
|
1219
|
+
const after = run.closed || i + 2 <= n - 1 ? at(i + 2) : b;
|
|
1220
|
+
const corners = head && i === segments - 1 ? [...SHAFT_CORNERS, ...HEAD_CORNERS] : SHAFT_CORNERS;
|
|
1221
|
+
for (const [role, side] of corners) {
|
|
1222
|
+
const adj = role === ROLE_BACK ? before : role === ROLE_SHAFT_END ? after : b;
|
|
1223
|
+
out.push(
|
|
1224
|
+
a.x,
|
|
1225
|
+
a.y,
|
|
1226
|
+
a.z,
|
|
1227
|
+
b.x,
|
|
1228
|
+
b.y,
|
|
1229
|
+
b.z,
|
|
1230
|
+
adj.x,
|
|
1231
|
+
adj.y,
|
|
1232
|
+
adj.z,
|
|
1233
|
+
role,
|
|
1234
|
+
side,
|
|
1235
|
+
along[i],
|
|
1236
|
+
along[i + 1],
|
|
1237
|
+
total,
|
|
1238
|
+
shaftHalf,
|
|
1239
|
+
...shape,
|
|
1240
|
+
apex.x,
|
|
1241
|
+
apex.y,
|
|
1242
|
+
apex.z,
|
|
1243
|
+
apexBack.x,
|
|
1244
|
+
apexBack.y,
|
|
1245
|
+
apexBack.z,
|
|
1246
|
+
reach,
|
|
1247
|
+
...color
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// src/lines/pass.glsl.ts
|
|
1254
|
+
var LINE_VS = `#version 300 es
|
|
1255
|
+
precision highp float;
|
|
1256
|
+
layout(location = 0) in vec3 a_from;
|
|
1257
|
+
layout(location = 1) in vec3 a_to;
|
|
1258
|
+
layout(location = 2) in vec3 a_adj;
|
|
1259
|
+
layout(location = 3) in vec2 a_corner;
|
|
1260
|
+
layout(location = 4) in vec3 a_along;
|
|
1261
|
+
layout(location = 5) in vec4 a_shape;
|
|
1262
|
+
layout(location = 6) in vec3 a_apex;
|
|
1263
|
+
layout(location = 7) in vec4 a_apexBack;
|
|
1264
|
+
layout(location = 8) in vec4 a_color;
|
|
1265
|
+
uniform mat4 u_viewProjection;
|
|
1266
|
+
/* Half the viewport in CSS pixels: ndc times this is pixels. */
|
|
1267
|
+
uniform vec2 u_halfViewport;
|
|
1268
|
+
out vec4 v_color;
|
|
1269
|
+
const float OVERDRAW = ${HEAD_OVERDRAW.toFixed(3)};
|
|
1270
|
+
/* How far a bisector may stretch before the corner is let go; a hairpin would run to infinity. */
|
|
1271
|
+
const float MITER_LIMIT = 4.0;
|
|
1272
|
+
const vec4 CULLED = vec4(0.0, 0.0, 2.0, 1.0);
|
|
1273
|
+
|
|
1274
|
+
vec2 screenOf(vec4 c) { return c.xy / c.w * u_halfViewport; }
|
|
1275
|
+
|
|
1276
|
+
void main() {
|
|
1277
|
+
v_color = a_color;
|
|
1278
|
+
vec4 ca = u_viewProjection * vec4(a_from, 1.0);
|
|
1279
|
+
vec4 cb = u_viewProjection * vec4(a_to, 1.0);
|
|
1280
|
+
/* An end behind the eye projects to a line across the whole screen; drop the segment instead. */
|
|
1281
|
+
if (ca.w <= 0.0 || cb.w <= 0.0) { gl_Position = CULLED; return; }
|
|
1282
|
+
vec2 sa = screenOf(ca);
|
|
1283
|
+
vec2 sb = screenOf(cb);
|
|
1284
|
+
vec2 d = sb - sa;
|
|
1285
|
+
float L = length(d);
|
|
1286
|
+
vec2 dir = L > 0.0 ? d / L : vec2(1.0, 0.0);
|
|
1287
|
+
vec2 perp = vec2(-dir.y, dir.x);
|
|
1288
|
+
float role = a_corner.x;
|
|
1289
|
+
float side = a_corner.y;
|
|
1290
|
+
float s0 = a_along.x;
|
|
1291
|
+
float s1 = a_along.y;
|
|
1292
|
+
float total = a_along.z;
|
|
1293
|
+
float span = s1 - s0;
|
|
1294
|
+
|
|
1295
|
+
/* Pixels per unit near the tip, the same for every segment of the run; this segment's own where the tip is unseen. */
|
|
1296
|
+
float density = span > 0.0 ? L / span : 0.0;
|
|
1297
|
+
vec4 cp = u_viewProjection * vec4(a_apex, 1.0);
|
|
1298
|
+
vec4 cq = u_viewProjection * vec4(a_apexBack.xyz, 1.0);
|
|
1299
|
+
float reach = a_apexBack.w;
|
|
1300
|
+
if (reach > 0.0 && cp.w > 0.0 && cq.w > 0.0) {
|
|
1301
|
+
float px = length(screenOf(cp) - screenOf(cq));
|
|
1302
|
+
if (px > 0.0) density = px / reach;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/* layoutLine in head.ts, repeated. */
|
|
1306
|
+
float shaftHalf = a_shape.x;
|
|
1307
|
+
float wanted = shaftHalf * a_shape.y;
|
|
1308
|
+
float headPx = 0.0;
|
|
1309
|
+
float headHalf = 0.0;
|
|
1310
|
+
float shaft = shaftHalf;
|
|
1311
|
+
float shaftEnd = total;
|
|
1312
|
+
if (wanted > 0.0 && density > 0.0) {
|
|
1313
|
+
headPx = max(0.0, min(wanted, a_shape.z * density * total));
|
|
1314
|
+
headHalf = headPx * a_shape.w;
|
|
1315
|
+
shaft = min(shaftHalf * headPx / wanted, headHalf);
|
|
1316
|
+
shaftEnd = total - headPx / density * (1.0 - OVERDRAW);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if (role > ${(ROLE_SHAFT_END + 0.5).toFixed(1)}) {
|
|
1320
|
+
if (headPx <= 0.0) { gl_Position = CULLED; return; }
|
|
1321
|
+
if (role > ${(ROLE_TIP - 0.5).toFixed(1)}) { gl_Position = cb; return; }
|
|
1322
|
+
vec2 base = sb - dir * headPx + perp * side * headHalf;
|
|
1323
|
+
gl_Position = vec4(base / u_halfViewport * cb.w, cb.z, cb.w);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
float to = min(s1, shaftEnd);
|
|
1328
|
+
if (to <= s0 || span <= 0.0) { gl_Position = CULLED; return; }
|
|
1329
|
+
bool atEnd = role > 0.5;
|
|
1330
|
+
/* Clip space is linear in the world, so a fraction of the segment lands at its true depth. */
|
|
1331
|
+
vec4 c = mix(ca, cb, atEnd ? (to - s0) / span : 0.0);
|
|
1332
|
+
vec2 miter = perp;
|
|
1333
|
+
float stretch = 1.0;
|
|
1334
|
+
/* Only an end that meets its neighbor miters; a shaft cut short under the head stays square. */
|
|
1335
|
+
if (!atEnd || to >= s1) {
|
|
1336
|
+
vec4 cj = u_viewProjection * vec4(a_adj, 1.0);
|
|
1337
|
+
if (cj.w > 0.0) {
|
|
1338
|
+
vec2 sj = screenOf(cj);
|
|
1339
|
+
vec2 e = atEnd ? sj - sb : sa - sj;
|
|
1340
|
+
float eL = length(e);
|
|
1341
|
+
if (eL > 0.0) {
|
|
1342
|
+
vec2 o = e / eL;
|
|
1343
|
+
vec2 bisect = perp + vec2(-o.y, o.x);
|
|
1344
|
+
float bL = length(bisect);
|
|
1345
|
+
if (bL > 0.0001) {
|
|
1346
|
+
vec2 m = bisect / bL;
|
|
1347
|
+
float cosHalf = dot(m, perp);
|
|
1348
|
+
if (cosHalf > 1.0 / MITER_LIMIT) { miter = m; stretch = 1.0 / cosHalf; }
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
c.xy += miter * side * shaft * stretch / u_halfViewport * c.w;
|
|
1354
|
+
gl_Position = c;
|
|
1355
|
+
}`;
|
|
1356
|
+
var LINE_FS = `#version 300 es
|
|
1357
|
+
precision mediump float;
|
|
1358
|
+
in vec4 v_color;
|
|
1359
|
+
out vec4 fragColor;
|
|
1360
|
+
void main() {
|
|
1361
|
+
fragColor = v_color;
|
|
1362
|
+
}`;
|
|
1363
|
+
|
|
1364
|
+
// src/lines/pass.ts
|
|
1365
|
+
var ATTRIBUTES = [
|
|
1366
|
+
[3, 0],
|
|
1367
|
+
[3, 3],
|
|
1368
|
+
[3, 6],
|
|
1369
|
+
[2, 9],
|
|
1370
|
+
[3, 11],
|
|
1371
|
+
[4, 14],
|
|
1372
|
+
[3, 18],
|
|
1373
|
+
[4, 21],
|
|
1374
|
+
[4, 25]
|
|
1375
|
+
];
|
|
1376
|
+
function compile(gl, type, source) {
|
|
1377
|
+
const shader = gl.createShader(type);
|
|
1378
|
+
gl.shaderSource(shader, source);
|
|
1379
|
+
gl.compileShader(shader);
|
|
1380
|
+
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS) && !gl.isContextLost()) {
|
|
1381
|
+
const log = gl.getShaderInfoLog(shader);
|
|
1382
|
+
gl.deleteShader(shader);
|
|
1383
|
+
throw new Error(`kernel3d line pass: shader failed to compile: ${log}`);
|
|
1384
|
+
}
|
|
1385
|
+
return shader;
|
|
1386
|
+
}
|
|
1387
|
+
function link(gl) {
|
|
1388
|
+
const program = gl.createProgram();
|
|
1389
|
+
const vs = compile(gl, gl.VERTEX_SHADER, LINE_VS);
|
|
1390
|
+
const fs = compile(gl, gl.FRAGMENT_SHADER, LINE_FS);
|
|
1391
|
+
gl.attachShader(program, vs);
|
|
1392
|
+
gl.attachShader(program, fs);
|
|
1393
|
+
gl.linkProgram(program);
|
|
1394
|
+
gl.deleteShader(vs);
|
|
1395
|
+
gl.deleteShader(fs);
|
|
1396
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS) && !gl.isContextLost()) {
|
|
1397
|
+
const log = gl.getProgramInfoLog(program);
|
|
1398
|
+
gl.deleteProgram(program);
|
|
1399
|
+
throw new Error(`kernel3d line pass: program failed to link: ${log}`);
|
|
1400
|
+
}
|
|
1401
|
+
return program;
|
|
1402
|
+
}
|
|
1403
|
+
function createLinePass(gl) {
|
|
1404
|
+
const program = link(gl);
|
|
1405
|
+
const viewProjection = gl.getUniformLocation(program, "u_viewProjection");
|
|
1406
|
+
const halfViewport = gl.getUniformLocation(program, "u_halfViewport");
|
|
1407
|
+
const buffer = gl.createBuffer();
|
|
1408
|
+
const vao = gl.createVertexArray();
|
|
1409
|
+
const prevVao = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
|
|
1410
|
+
const prevBuffer = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
|
|
1411
|
+
gl.bindVertexArray(vao);
|
|
1412
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1413
|
+
ATTRIBUTES.forEach(([size, offset], loc) => {
|
|
1414
|
+
gl.enableVertexAttribArray(loc);
|
|
1415
|
+
gl.vertexAttribPointer(loc, size, gl.FLOAT, false, LINE_FLOATS * 4, offset * 4);
|
|
1416
|
+
});
|
|
1417
|
+
gl.bindVertexArray(prevVao);
|
|
1418
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, prevBuffer);
|
|
1419
|
+
const matrix = new Float32Array(16);
|
|
1420
|
+
return {
|
|
1421
|
+
draw(vertices, vp, size, options = {}) {
|
|
1422
|
+
if (vertices.length === 0) return;
|
|
1423
|
+
const was = {
|
|
1424
|
+
program: gl.getParameter(gl.CURRENT_PROGRAM),
|
|
1425
|
+
vao: gl.getParameter(gl.VERTEX_ARRAY_BINDING),
|
|
1426
|
+
buffer: gl.getParameter(gl.ARRAY_BUFFER_BINDING),
|
|
1427
|
+
blend: gl.isEnabled(gl.BLEND),
|
|
1428
|
+
depth: gl.isEnabled(gl.DEPTH_TEST),
|
|
1429
|
+
cull: gl.isEnabled(gl.CULL_FACE),
|
|
1430
|
+
depthMask: gl.getParameter(gl.DEPTH_WRITEMASK),
|
|
1431
|
+
blendFunc: [gl.BLEND_SRC_RGB, gl.BLEND_DST_RGB, gl.BLEND_SRC_ALPHA, gl.BLEND_DST_ALPHA].map((p) => gl.getParameter(p))
|
|
1432
|
+
};
|
|
1433
|
+
gl.useProgram(program);
|
|
1434
|
+
matrix.set(vp);
|
|
1435
|
+
gl.uniformMatrix4fv(viewProjection, false, matrix);
|
|
1436
|
+
gl.uniform2f(halfViewport, Math.max(1, size.width) / 2, Math.max(1, size.height) / 2);
|
|
1437
|
+
gl.enable(gl.BLEND);
|
|
1438
|
+
gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
1439
|
+
gl.disable(gl.CULL_FACE);
|
|
1440
|
+
if (options.depthTest ?? true) gl.enable(gl.DEPTH_TEST);
|
|
1441
|
+
else gl.disable(gl.DEPTH_TEST);
|
|
1442
|
+
gl.depthMask(false);
|
|
1443
|
+
gl.bindVertexArray(vao);
|
|
1444
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
|
|
1445
|
+
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
|
|
1446
|
+
gl.drawArrays(gl.TRIANGLES, 0, vertices.length / LINE_FLOATS);
|
|
1447
|
+
gl.useProgram(was.program);
|
|
1448
|
+
gl.bindVertexArray(was.vao);
|
|
1449
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, was.buffer);
|
|
1450
|
+
const set = (cap, on) => on ? gl.enable(cap) : gl.disable(cap);
|
|
1451
|
+
set(gl.BLEND, was.blend);
|
|
1452
|
+
set(gl.DEPTH_TEST, was.depth);
|
|
1453
|
+
set(gl.CULL_FACE, was.cull);
|
|
1454
|
+
gl.depthMask(was.depthMask);
|
|
1455
|
+
gl.blendFuncSeparate(...was.blendFunc);
|
|
1456
|
+
},
|
|
1457
|
+
dispose() {
|
|
1458
|
+
gl.deleteVertexArray(vao);
|
|
1459
|
+
gl.deleteBuffer(buffer);
|
|
1460
|
+
gl.deleteProgram(program);
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1128
1464
|
var ORBIT_RATE = 6e-3;
|
|
1129
1465
|
var DOLLY_RATE = 15e-4;
|
|
1130
1466
|
function screenDelta(move) {
|
|
@@ -1260,6 +1596,6 @@ var kernel3dKitSettingsSchema = pref.group("3D camera", {
|
|
|
1260
1596
|
})
|
|
1261
1597
|
});
|
|
1262
1598
|
|
|
1263
|
-
export { DEFAULT_ORBIT_LIMITS, MAX_PITCH, POSE3_COMPOSITION, UNIT_CUBE, aabbOfPose, aimOf3d, aimSpace3d, alignPlaneSource, boxSnapSource, cameraEye, cameraOfAim3d, cameraView, cameraViewProjection, clampCamera, collectOverlayBoxes, createAreaSelect, createCamera, createInsert, createNodeAtPoint, createPoseDescriptor, createSnap, dollyAction, dollyBy, dollyToward, groundGridSource, homeAction, kernel3dKitSettingsSchema, ndcToScreen, orbitAction, orbitBy, panAction, panBy, pinchDollyAction, pose3, poseMatrix, projectAabbToScreen, projectPoint, projectSnapMarks, rayThroughScreenPoint, screenBoxOf, screenToNdc, useOrbitTool, worldPoseOf };
|
|
1599
|
+
export { DEFAULT_ORBIT_LIMITS, HEAD, HEAD_OVERDRAW, LINE_FLOATS, MAX_PITCH, POSE3_COMPOSITION, UNIT_CUBE, aabbOfPose, aimOf3d, aimSpace3d, alignPlaneSource, boxSnapSource, cameraEye, cameraOfAim3d, cameraView, cameraViewProjection, clampCamera, collectOverlayBoxes, createAreaSelect, createCamera, createInsert, createLinePass, createNodeAtPoint, createPoseDescriptor, createSnap, dollyAction, dollyBy, dollyToward, groundGridSource, headFor, homeAction, kernel3dKitSettingsSchema, layoutLine, ndcToScreen, orbitAction, orbitBy, packLines, panAction, panBy, pinchDollyAction, pose3, poseMatrix, projectAabbToScreen, projectPoint, projectSnapMarks, rayThroughScreenPoint, screenBoxOf, screenToNdc, useOrbitTool, visibleRuns, worldPoseOf };
|
|
1264
1600
|
//# sourceMappingURL=index.js.map
|
|
1265
1601
|
//# sourceMappingURL=index.js.map
|