@code3d/core 0.0.1-alpha.0 → 0.0.1-alpha.11
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 +43 -807
- package/THIRD_PARTY.md +40 -0
- package/bld/THIRD_PARTY_NOTICES.txt +182 -0
- package/bld/chunks/chunk-7FIFPADJ.js +1 -0
- package/bld/chunks/chunk-7FIFPADJ.js.map +7 -0
- package/bld/chunks/chunk-CL3E2DE4.js +19 -0
- package/bld/chunks/chunk-CL3E2DE4.js.map +7 -0
- package/bld/chunks/chunk-HZRQUHM2.js +30 -0
- package/bld/chunks/chunk-HZRQUHM2.js.map +7 -0
- package/bld/chunks/chunk-JBVIAMJ6.js +26474 -0
- package/bld/chunks/chunk-JBVIAMJ6.js.map +7 -0
- package/bld/chunks/chunk-QK6ZET47.js +31 -0
- package/bld/chunks/chunk-QK6ZET47.js.map +7 -0
- package/bld/library/bound-solver.d.ts +29 -5
- package/bld/library/bound-solver.d.ts.map +1 -1
- package/bld/library/cached.d.ts +35 -0
- package/bld/library/cached.d.ts.map +1 -0
- package/bld/library/font.d.ts +39 -0
- package/bld/library/font.d.ts.map +1 -0
- package/bld/library/google-font.d.ts +14 -0
- package/bld/library/google-font.d.ts.map +1 -0
- package/bld/library/index.d.ts +10 -2
- package/bld/library/index.d.ts.map +1 -1
- package/bld/library/index.js +94 -3
- package/bld/library/index.js.map +7 -1
- package/bld/library/inspect.d.ts +100 -0
- package/bld/library/inspect.d.ts.map +1 -0
- package/bld/library/kernel-artifact-codec.d.ts.map +1 -1
- package/bld/library/kernel-cache.d.ts +28 -7
- package/bld/library/kernel-cache.d.ts.map +1 -1
- package/bld/library/loft.d.ts.map +1 -1
- package/bld/library/material.d.ts +1 -1
- package/bld/library/material.d.ts.map +1 -1
- package/bld/library/open-cascade.d.ts +4 -0
- package/bld/library/open-cascade.d.ts.map +1 -0
- package/bld/library/relation-solver.d.ts +19 -8
- package/bld/library/relation-solver.d.ts.map +1 -1
- package/bld/library/replicad.d.ts +3 -2
- package/bld/library/replicad.d.ts.map +1 -1
- package/bld/library/replicad.js +10 -24
- package/bld/library/replicad.js.map +7 -1
- package/bld/library/retained-memory.d.ts.map +1 -1
- package/bld/library/runtime.d.ts +518 -155
- package/bld/library/runtime.d.ts.map +1 -1
- package/bld/library/sketch-solver.d.ts +1 -1
- package/bld/library/sketch-solver.d.ts.map +1 -1
- package/bld/library/sketch.d.ts +21 -1
- package/bld/library/sketch.d.ts.map +1 -1
- package/bld/library/text.d.ts +53 -0
- package/bld/library/text.d.ts.map +1 -0
- package/bld/library/three.js +3 -3
- package/bld/library/three.js.map +7 -1
- package/bld/library/topology.d.ts +6 -2
- package/bld/library/topology.d.ts.map +1 -1
- package/bld/node/index.d.ts.map +1 -1
- package/bld/node/index.js +96 -14
- package/bld/node/index.js.map +7 -1
- package/bld/node/replicad.js +13 -3
- package/bld/node/replicad.js.map +7 -1
- package/bld/tooling/index.d.ts +18 -15
- package/bld/tooling/index.d.ts.map +1 -1
- package/bld/tooling/index.js +165 -19
- package/bld/tooling/index.js.map +7 -1
- package/docs/api.md +764 -0
- package/docs/custom-primitives.mdx +58 -0
- package/docs/local-coordinates.md +187 -0
- package/docs/origins-and-rotation.mdx +105 -0
- package/docs/relations.mdx +450 -0
- package/docs/runtime.md +297 -0
- package/docs/shells.mdx +70 -0
- package/docs/sketches.md +83 -0
- package/docs/text.md +23 -0
- package/docs/topology.md +152 -0
- package/docs/values.md +119 -0
- package/package.json +20 -7
- package/src/library/bound-solver.ts +103 -73
- package/src/library/cached.ts +153 -0
- package/src/library/font.ts +186 -0
- package/src/library/google-font.ts +97 -0
- package/src/library/index.ts +53 -5
- package/src/library/inspect.ts +178 -0
- package/src/library/kernel-artifact-codec.ts +129 -20
- package/src/library/kernel-cache.ts +175 -39
- package/src/library/loft.ts +9 -1
- package/src/library/material.ts +3 -4
- package/src/library/open-cascade.ts +13 -0
- package/src/library/relation-solver.ts +230 -113
- package/src/library/replicad.ts +5 -5
- package/src/library/retained-memory.ts +6 -1
- package/src/library/runtime.ts +3769 -1178
- package/src/library/shell.ts +3 -3
- package/src/library/sketch-solver.ts +3 -1
- package/src/library/sketch.ts +112 -13
- package/src/library/text.ts +477 -0
- package/src/library/topology.ts +61 -17
- package/src/library/tsconfig.json +2 -1
- package/src/node/index.ts +14 -3
- package/src/node/tsconfig.json +2 -1
- package/src/tooling/index.ts +64 -51
- package/src/tooling/tsconfig.json +2 -1
- package/bld/library/alignment-geometry.js +0 -322
- package/bld/library/alignment-geometry.js.map +0 -1
- package/bld/library/bound-solver.js +0 -252
- package/bld/library/bound-solver.js.map +0 -1
- package/bld/library/extrude.js +0 -28
- package/bld/library/extrude.js.map +0 -1
- package/bld/library/kernel-artifact-codec.js +0 -148
- package/bld/library/kernel-artifact-codec.js.map +0 -1
- package/bld/library/kernel-cache.js +0 -245
- package/bld/library/kernel-cache.js.map +0 -1
- package/bld/library/kernel-shapes.js +0 -108
- package/bld/library/kernel-shapes.js.map +0 -1
- package/bld/library/loft.js +0 -161
- package/bld/library/loft.js.map +0 -1
- package/bld/library/material.js +0 -176
- package/bld/library/material.js.map +0 -1
- package/bld/library/model-color.js +0 -36
- package/bld/library/model-color.js.map +0 -1
- package/bld/library/open-cascade-error.js +0 -22
- package/bld/library/open-cascade-error.js.map +0 -1
- package/bld/library/relation-solver.js +0 -258
- package/bld/library/relation-solver.js.map +0 -1
- package/bld/library/retained-memory.js +0 -27
- package/bld/library/retained-memory.js.map +0 -1
- package/bld/library/runtime.js +0 -2954
- package/bld/library/runtime.js.map +0 -1
- package/bld/library/shell.js +0 -221
- package/bld/library/shell.js.map +0 -1
- package/bld/library/sketch-curve-intersections.js +0 -117
- package/bld/library/sketch-curve-intersections.js.map +0 -1
- package/bld/library/sketch-curves.js +0 -89
- package/bld/library/sketch-curves.js.map +0 -1
- package/bld/library/sketch-drag-rules.js +0 -522
- package/bld/library/sketch-drag-rules.js.map +0 -1
- package/bld/library/sketch-face.js +0 -72
- package/bld/library/sketch-face.js.map +0 -1
- package/bld/library/sketch-incidence.js +0 -114
- package/bld/library/sketch-incidence.js.map +0 -1
- package/bld/library/sketch-precision.js +0 -55
- package/bld/library/sketch-precision.js.map +0 -1
- package/bld/library/sketch-regions.js +0 -193
- package/bld/library/sketch-regions.js.map +0 -1
- package/bld/library/sketch-solver.js +0 -578
- package/bld/library/sketch-solver.js.map +0 -1
- package/bld/library/sketch.js +0 -650
- package/bld/library/sketch.js.map +0 -1
- package/bld/library/spatial.js +0 -195
- package/bld/library/spatial.js.map +0 -1
- package/bld/library/topology-id.js +0 -67
- package/bld/library/topology-id.js.map +0 -1
- package/bld/library/topology-inspection.js +0 -267
- package/bld/library/topology-inspection.js.map +0 -1
- package/bld/library/topology.js +0 -455
- package/bld/library/topology.js.map +0 -1
package/docs/values.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Model values and measurements
|
|
3
|
+
description: Understand immutable models, geometry measurements and composition.
|
|
4
|
+
sidebar:
|
|
5
|
+
order: 2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Model values and coordinates
|
|
9
|
+
|
|
10
|
+
Operations produce new model values. Building another result must not change an
|
|
11
|
+
already-observable model's geometry, material, topology, or relations.
|
|
12
|
+
|
|
13
|
+
In `part.relate(self => ...)`, the callback parameter represents the new value.
|
|
14
|
+
`part` and any aliases or element references selected from it still refer to the
|
|
15
|
+
original value. Each constraint must involve the callback value.
|
|
16
|
+
|
|
17
|
+
A model's local geometry and its placement in a composition are separate.
|
|
18
|
+
`relate()` records how a part is placed when composed with other parts; observing
|
|
19
|
+
that part alone shows its local geometry. `originOffset()` changes geometry
|
|
20
|
+
coordinates without changing the shape. Position arrays use `[x, y, z]`; scalar
|
|
21
|
+
angles use degrees. Read [local coordinates](local-coordinates.md)
|
|
22
|
+
and [relations](relations.mdx) before mixing
|
|
23
|
+
origin changes, alignment, and rotation.
|
|
24
|
+
|
|
25
|
+
Constraints express `on` and `align` only. Put relative `offset` and `rotate`
|
|
26
|
+
transformations in the `relate` array after the constraints. Zero values add no
|
|
27
|
+
centering or orientation condition; use point or axis alignment to center a part.
|
|
28
|
+
The App's gizmos edit or insert independent array entries.
|
|
29
|
+
|
|
30
|
+
Choose a
|
|
31
|
+
center with `pivot([x,y,z])`, self topology with `pivotVertex(id)`/`axisEdge(id)`,
|
|
32
|
+
or references with `pivotPoint(pointRef)`/`axisLine(lineRef)`; finish each selector
|
|
33
|
+
with `rotate`. Point rotations retain self XYZ axes, including external centers. Consecutive constraints solve jointly; transformations
|
|
34
|
+
then act on that result in order. A later constraint starts a new segment using
|
|
35
|
+
the preceding pose. Independent offsets use fixed composition axes, and rotations
|
|
36
|
+
default to self's current origin. Each completed transformation is one array item,
|
|
37
|
+
for example `[offset(0, 8, 0), rotate(0, 25, 0)]`. Only pivot/axis selections
|
|
38
|
+
chain into `rotate`; completed transformations cannot chain into another operation.
|
|
39
|
+
Keep a selected reference while moving it with
|
|
40
|
+
`pivotVertex(id).pivotOffset(dx, dy, dz).rotate(x, y, z)` or
|
|
41
|
+
`axisLine(axis).axisOffset(dx, dy, dz).rotate(angle)`. Point offsets use self local
|
|
42
|
+
axes; axis offsets use the selected axis frame and preserve its direction.
|
|
43
|
+
Each selector accepts one matching offset, followed by `rotate`.
|
|
44
|
+
See the [transformation example](../../app/examples/constraints/transformations.ts)
|
|
45
|
+
and [placement guide](relations.mdx#transform-a-joint-result).
|
|
46
|
+
|
|
47
|
+
Build readable models from named intermediate values and public operations. A
|
|
48
|
+
profile followed by extrusion, or solids combined with Boolean operations,
|
|
49
|
+
keeps the construction understandable and editable by both people and agents.
|
|
50
|
+
|
|
51
|
+
## Measure geometry for another part
|
|
52
|
+
|
|
53
|
+
`distance(a, b, axis?)` measures models, finite topology, bounds or point anchors
|
|
54
|
+
in their solved placement and returns a normal non-negative number. Omit the axis
|
|
55
|
+
for shortest geometric distance; supply `x`, `y`, `z`, a direction vector or a
|
|
56
|
+
straight edge/axis reference for the gap between projected intervals. Overlap
|
|
57
|
+
returns zero. Existing relations are resolved on demand, before any group is
|
|
58
|
+
needed; subsequent relations do not update the number.
|
|
59
|
+
|
|
60
|
+
See the [measurement reference](api.md#measurements)
|
|
61
|
+
and the [fitted beam example](../../app/examples/operations/distance.ts). They cover
|
|
62
|
+
finite geometry, nested occurrences, axis frames and source-order dependencies.
|
|
63
|
+
|
|
64
|
+
## Geometry measurements
|
|
65
|
+
|
|
66
|
+
`model.bounds(relativeTo?)` returns readonly `minimum`, `maximum` and `size`
|
|
67
|
+
XYZ vectors for tight finite geometry bounds. By default it uses the model's
|
|
68
|
+
own local frame. An explicit reference includes solved placement and nested
|
|
69
|
+
member occurrences in that reference's frame. Empty groups have no finite
|
|
70
|
+
bounds; a source occurring more than once in the reference is ambiguous.
|
|
71
|
+
|
|
72
|
+
`model.position(relativeTo)` returns the model origin in the explicit reference's
|
|
73
|
+
local frame. A model's origin in its own frame is always `[0, 0, 0]`, including
|
|
74
|
+
point models whose geometry may be offset from that origin. Neither query
|
|
75
|
+
changes the model or its placement. These methods are available on every model
|
|
76
|
+
kind, including groups. As model members, `bounds` and `position` are reserved
|
|
77
|
+
names and cannot be used as exposed element names.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import {box, group, offset} from '@code3d/core';
|
|
81
|
+
|
|
82
|
+
const base = box(20, 4, 20);
|
|
83
|
+
const part = box(8, 12, 4).relate(self => [self.on(base.up), offset(20, 0, 0)]);
|
|
84
|
+
const size = part.bounds().size; // [8, 12, 4]
|
|
85
|
+
const origin = part.position(base); // [20, 8, 0]
|
|
86
|
+
const minimum = part.bounds(base).minimum; // [16, 2, -2]
|
|
87
|
+
export default group([base, part]);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Dimension-based primitives and numeric modeling methods retain required TypeScript
|
|
91
|
+
signatures while providing runtime defaults for omitted or `undefined` values.
|
|
92
|
+
Rotations and displacements default to zero, scaling to one, extrusion distance
|
|
93
|
+
to ten, and fillet radius, chamfer distance and shell thickness to one. Relation
|
|
94
|
+
rotation selectors use the same angle defaults; `pivot()` defaults to local zero.
|
|
95
|
+
Explicit invalid values retain their normal errors. These defaults work in
|
|
96
|
+
ordinary JavaScript execution as well as App previews.
|
|
97
|
+
|
|
98
|
+
The App displays defaults as placeholders without inserting source arguments.
|
|
99
|
+
Committing a spatial drag fills all remaining omitted defaults in that operation;
|
|
100
|
+
for example, dragging the X ring of `rotate()` writes `rotate(angle, 0, 0)`. The
|
|
101
|
+
edit and completion share one undo step. Use explicit dimensions in finished
|
|
102
|
+
models; the [reference](api.md#runtime-defaults-while-editing)
|
|
103
|
+
lists the actual defaults.
|
|
104
|
+
|
|
105
|
+
Topology capabilities follow dimension: vertices expose vertex selection, edges
|
|
106
|
+
add edge selection, and faces and solids add surface selection. Only solids
|
|
107
|
+
provide `fillet`, `chamfer`, and `shell`. Groups compose values and support
|
|
108
|
+
relations, exposed elements, and materials without pretending to be geometry.
|
|
109
|
+
|
|
110
|
+
Groups are model values and can be nested directly with `group([inner, other])`,
|
|
111
|
+
including in mixed `Model[]` collections. Nesting preserves each group's hierarchy;
|
|
112
|
+
`expose()` adds named references when callers need to address members.
|
|
113
|
+
|
|
114
|
+
A topology ID belongs to its owning model and element kind. It is a number or a
|
|
115
|
+
flat numeric path, such as `.edge([1, 3])`. Local edits (`fillet`, `chamfer`, `shell`)
|
|
116
|
+
preserve one-to-one IDs and allocate fresh IDs for new elements without reusing
|
|
117
|
+
retired numbers. Loft, extrusion and Booleans prefix inherited IDs by input;
|
|
118
|
+
transforms preserve complete paths. Inspect the result after topology changes
|
|
119
|
+
instead of assuming IDs from a different model still apply.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code3d/core",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.11",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
"author": "vilicvane",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"prepack": "
|
|
12
|
-
"test": "
|
|
13
|
-
"test:run": "node --test \"test/*.test.{mjs,ts}\"",
|
|
14
|
-
"test:types": "node ../../node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json"
|
|
11
|
+
"prepack": "npm run build",
|
|
12
|
+
"test": "npm run build && npm run test:types && npm run test:run",
|
|
13
|
+
"test:run": "node --import ../../test/source-loader.mjs --test --test-concurrency=1 \"test/*.test.{mjs,ts}\"",
|
|
14
|
+
"test:types": "node ../../node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
|
|
15
|
+
"build": "node ../../scripts/build-packages.mjs @code3d/core"
|
|
15
16
|
},
|
|
16
17
|
"exports": {
|
|
17
18
|
".": {
|
|
@@ -39,15 +40,27 @@
|
|
|
39
40
|
"bld",
|
|
40
41
|
"THIRD_PARTY.md",
|
|
41
42
|
"README.md",
|
|
42
|
-
"src"
|
|
43
|
+
"src",
|
|
44
|
+
"docs"
|
|
43
45
|
],
|
|
44
46
|
"dependencies": {
|
|
45
47
|
"@code3d/opencascade": "0.0.1-alpha.0",
|
|
46
|
-
"@ctrl/tinycolor": "^4.2.0",
|
|
47
48
|
"@salusoft89/planegcs": "1.2.0",
|
|
49
|
+
"@types/emscripten": "1.41.5",
|
|
48
50
|
"@types/three": "^0.185.4",
|
|
51
|
+
"harfbuzzjs": "1.6.1",
|
|
52
|
+
"manifold-3d": "3.0.1",
|
|
49
53
|
"replicad": "^1.0.1",
|
|
50
54
|
"replicad-opencascadejs": "1.0.0",
|
|
51
55
|
"three": "^0.185.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@ctrl/tinycolor": "^4.2.0",
|
|
59
|
+
"flo-boolean": "5.0.8"
|
|
60
|
+
},
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+https://github.com/vilicvane/code3d.git",
|
|
64
|
+
"directory": "packages/core"
|
|
52
65
|
}
|
|
53
66
|
}
|
|
@@ -7,17 +7,56 @@ import {
|
|
|
7
7
|
origin,
|
|
8
8
|
rotateVector,
|
|
9
9
|
rotation,
|
|
10
|
-
transformsAreEquivalent,
|
|
11
10
|
type Quaternion,
|
|
12
11
|
type RigidTransform,
|
|
13
12
|
type Vec3,
|
|
14
13
|
} from './spatial.js';
|
|
15
14
|
|
|
16
15
|
export type Bounds = readonly [minimum: Vec3, maximum: Vec3];
|
|
17
|
-
export type
|
|
18
|
-
| Readonly<{
|
|
19
|
-
| Readonly<{
|
|
16
|
+
export type ExternalRotation =
|
|
17
|
+
| Readonly<{axis: RigidTransform; body: number; angle: number}>
|
|
18
|
+
| Readonly<{
|
|
19
|
+
point: Vec3;
|
|
20
|
+
body: number;
|
|
21
|
+
rotation: Quaternion;
|
|
22
|
+
displacement: Vec3;
|
|
23
|
+
}>;
|
|
24
|
+
export type BodyRotation = Readonly<{local: RigidTransform}> | ExternalRotation;
|
|
20
25
|
|
|
26
|
+
/** External center relative to its owning body; point references retain self's axes. */
|
|
27
|
+
export function externalRotationFrame(
|
|
28
|
+
action: ExternalRotation,
|
|
29
|
+
self: Quaternion,
|
|
30
|
+
reference: Quaternion,
|
|
31
|
+
inverse = false,
|
|
32
|
+
): {center: Vec3; quaternion: Quaternion} {
|
|
33
|
+
if ('axis' in action) {
|
|
34
|
+
const axis = composeTransforms(rotation(reference), action.axis);
|
|
35
|
+
return {
|
|
36
|
+
center: axis.position,
|
|
37
|
+
quaternion: axisRotation(axis, inverse ? -action.angle : action.angle)
|
|
38
|
+
.quaternion,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const local = inverse
|
|
42
|
+
? invertTransform(rotation(action.rotation))
|
|
43
|
+
: rotation(action.rotation);
|
|
44
|
+
const before = inverse
|
|
45
|
+
? composeTransforms(rotation(self), local)
|
|
46
|
+
: rotation(self);
|
|
47
|
+
return {
|
|
48
|
+
center: addVectors(
|
|
49
|
+
rotateVector(action.point, reference),
|
|
50
|
+
rotateVector(action.displacement, before.quaternion),
|
|
51
|
+
),
|
|
52
|
+
quaternion: composeTransforms(
|
|
53
|
+
composeTransforms(before, local),
|
|
54
|
+
invertTransform(before),
|
|
55
|
+
).quaternion,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type BodyAction = BodyRotation | Readonly<{offset: Vec3}>;
|
|
21
60
|
export type BodyRelation = Readonly<{
|
|
22
61
|
id: string;
|
|
23
62
|
source: Readonly<{
|
|
@@ -26,11 +65,16 @@ export type BodyRelation = Readonly<{
|
|
|
26
65
|
bounds: (orientation: Quaternion) => Bounds;
|
|
27
66
|
}>;
|
|
28
67
|
target: Readonly<{body: number; transform: RigidTransform; facing: 1 | -1}>;
|
|
29
|
-
offset: Vec3 | undefined;
|
|
30
|
-
rotations: readonly BodyRotation[];
|
|
31
68
|
}>;
|
|
32
69
|
|
|
33
|
-
export type Body = Readonly<{
|
|
70
|
+
export type Body = Readonly<{
|
|
71
|
+
name: string;
|
|
72
|
+
relations: readonly BodyRelation[];
|
|
73
|
+
/** Pose inherited from the preceding placement segment. */
|
|
74
|
+
initial?: RigidTransform;
|
|
75
|
+
/** Independent actions apply after the joint constraint solution. */
|
|
76
|
+
transformations?: readonly BodyAction[];
|
|
77
|
+
}>;
|
|
34
78
|
type AffinePoint = {constant: Vec3; columns: Vec3[]};
|
|
35
79
|
type AffinePose = {position: AffinePoint; quaternion: Quaternion};
|
|
36
80
|
type Equation = {id: string; coefficients: number[]; value: number};
|
|
@@ -74,37 +118,21 @@ export function solveBodies(
|
|
|
74
118
|
'Cyclic external rotation axes cannot determine an authored orientation.',
|
|
75
119
|
);
|
|
76
120
|
visiting.add(index);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
pose
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
return pose;
|
|
94
|
-
});
|
|
95
|
-
const pose = candidates[0] ?? identityRigidTransform;
|
|
96
|
-
if (
|
|
97
|
-
candidates.some(
|
|
98
|
-
candidate =>
|
|
99
|
-
!transformsAreEquivalent(
|
|
100
|
-
rotation(candidate.quaternion),
|
|
101
|
-
rotation(pose.quaternion),
|
|
102
|
-
),
|
|
103
|
-
)
|
|
104
|
-
) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`Conflicting explicit rotations for ${bodies[index].name}.`,
|
|
107
|
-
);
|
|
121
|
+
let pose = bodies[index].initial ?? identityRigidTransform;
|
|
122
|
+
for (const action of bodies[index].transformations ?? []) {
|
|
123
|
+
if ('offset' in action) continue;
|
|
124
|
+
pose =
|
|
125
|
+
'local' in action
|
|
126
|
+
? composeTransforms(pose, action.local)
|
|
127
|
+
: 'point' in action
|
|
128
|
+
? composeTransforms(pose, rotation(action.rotation))
|
|
129
|
+
: composeTransforms(
|
|
130
|
+
axisRotation(
|
|
131
|
+
composeTransforms(seed(action.body), action.axis),
|
|
132
|
+
action.angle,
|
|
133
|
+
),
|
|
134
|
+
pose,
|
|
135
|
+
);
|
|
108
136
|
}
|
|
109
137
|
visiting.delete(index);
|
|
110
138
|
seeds.set(index, pose);
|
|
@@ -120,19 +148,18 @@ export function solveBodies(
|
|
|
120
148
|
const known = positioned.get(index);
|
|
121
149
|
if (known) return known;
|
|
122
150
|
let pose: AffinePose = {
|
|
123
|
-
quaternion: identityRigidTransform.quaternion,
|
|
151
|
+
quaternion: (bodies[index].initial ?? identityRigidTransform).quaternion,
|
|
124
152
|
position: {
|
|
125
|
-
constant:
|
|
153
|
+
constant: (bodies[index].initial ?? identityRigidTransform).position,
|
|
126
154
|
columns: variables.flatMap(body =>
|
|
127
155
|
axes.map(axis => (body === index ? axis : origin)),
|
|
128
156
|
),
|
|
129
157
|
},
|
|
130
158
|
};
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if ('local' in action) {
|
|
159
|
+
for (const action of bodies[index].transformations ?? []) {
|
|
160
|
+
if ('offset' in action) {
|
|
161
|
+
pose = {...pose, position: shiftPoint(pose.position, action.offset)};
|
|
162
|
+
} else if ('local' in action) {
|
|
136
163
|
pose = {
|
|
137
164
|
position: shiftPoint(
|
|
138
165
|
pose.position,
|
|
@@ -145,12 +172,12 @@ export function solveBodies(
|
|
|
145
172
|
};
|
|
146
173
|
} else {
|
|
147
174
|
const axisPose = positionBody(action.body);
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
175
|
+
const {center, quaternion: q} = externalRotationFrame(
|
|
176
|
+
action,
|
|
177
|
+
pose.quaternion,
|
|
178
|
+
axisPose.quaternion,
|
|
151
179
|
);
|
|
152
|
-
const
|
|
153
|
-
const axisPoint = shiftPoint(axisPose.position, axis.position);
|
|
180
|
+
const axisPoint = shiftPoint(axisPose.position, center);
|
|
154
181
|
const rotatedAxis = rotatePoint(axisPoint, q);
|
|
155
182
|
pose = {
|
|
156
183
|
position: addPoints(
|
|
@@ -173,16 +200,19 @@ export function solveBodies(
|
|
|
173
200
|
const preferences = new Map<string, Equation>();
|
|
174
201
|
bodies.forEach((body, owner) =>
|
|
175
202
|
body.relations.forEach(relation => {
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
203
|
+
// Every contact constrains the joint result before independent transforms.
|
|
204
|
+
const baseline = undoActions(
|
|
205
|
+
poses[owner],
|
|
206
|
+
body.transformations ?? [],
|
|
207
|
+
poses,
|
|
208
|
+
);
|
|
181
209
|
for (let axis = 0; axis < 3; axis++) {
|
|
182
210
|
const preference = {
|
|
183
211
|
id: relation.id,
|
|
184
212
|
coefficients: baseline.position.columns.map(column => column[axis]),
|
|
185
|
-
value:
|
|
213
|
+
value:
|
|
214
|
+
(body.initial?.position[axis] ?? 0) -
|
|
215
|
+
baseline.position.constant[axis],
|
|
186
216
|
};
|
|
187
217
|
preferences.set(
|
|
188
218
|
JSON.stringify([preference.coefficients, preference.value]),
|
|
@@ -212,14 +242,8 @@ export function solveBodies(
|
|
|
212
242
|
source.position,
|
|
213
243
|
rotateVector(center, targetFrame.quaternion),
|
|
214
244
|
);
|
|
215
|
-
const targetPoint = shiftPoint(
|
|
216
|
-
|
|
217
|
-
addVectors(
|
|
218
|
-
targetFrame.position,
|
|
219
|
-
rotateVector(relation.offset ?? origin, targetFrame.quaternion),
|
|
220
|
-
),
|
|
221
|
-
);
|
|
222
|
-
for (const localAxis of relation.offset ? axes : [axes[1]]) {
|
|
245
|
+
const targetPoint = shiftPoint(target.position, targetFrame.position);
|
|
246
|
+
for (const localAxis of [axes[1]]) {
|
|
223
247
|
const normal = rotateVector(localAxis, targetFrame.quaternion);
|
|
224
248
|
equations.push({
|
|
225
249
|
id: relation.id,
|
|
@@ -272,13 +296,18 @@ function addPoints(left: AffinePoint, right: AffinePoint): AffinePoint {
|
|
|
272
296
|
};
|
|
273
297
|
}
|
|
274
298
|
|
|
275
|
-
function
|
|
299
|
+
function undoActions(
|
|
276
300
|
pose: AffinePose,
|
|
277
|
-
actions: readonly
|
|
301
|
+
actions: readonly BodyAction[],
|
|
278
302
|
poses: readonly AffinePose[],
|
|
279
303
|
): AffinePose {
|
|
280
304
|
for (const action of [...actions].reverse()) {
|
|
281
|
-
if ('
|
|
305
|
+
if ('offset' in action) {
|
|
306
|
+
pose = {
|
|
307
|
+
...pose,
|
|
308
|
+
position: shiftPoint(pose.position, negateVector(action.offset)),
|
|
309
|
+
};
|
|
310
|
+
} else if ('local' in action) {
|
|
282
311
|
const inverse = invertTransform(action.local);
|
|
283
312
|
pose = {
|
|
284
313
|
position: shiftPoint(
|
|
@@ -292,12 +321,13 @@ function undoRotations(
|
|
|
292
321
|
};
|
|
293
322
|
} else {
|
|
294
323
|
const axisPose = poses[action.body];
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
324
|
+
const {center, quaternion: q} = externalRotationFrame(
|
|
325
|
+
action,
|
|
326
|
+
pose.quaternion,
|
|
327
|
+
axisPose.quaternion,
|
|
328
|
+
true,
|
|
298
329
|
);
|
|
299
|
-
const
|
|
300
|
-
const axisPoint = shiftPoint(axisPose.position, axis.position);
|
|
330
|
+
const axisPoint = shiftPoint(axisPose.position, center);
|
|
301
331
|
const rotatedAxis = rotatePoint(axisPoint, q);
|
|
302
332
|
pose = {
|
|
303
333
|
position: addPoints(
|
|
@@ -316,7 +346,7 @@ function undoRotations(
|
|
|
316
346
|
}
|
|
317
347
|
|
|
318
348
|
/** Solve Ax=b, then minimize contact-stage displacement in its nullspace. */
|
|
319
|
-
function solveTranslations(
|
|
349
|
+
export function solveTranslations(
|
|
320
350
|
equations: readonly Equation[],
|
|
321
351
|
preferences: readonly Equation[],
|
|
322
352
|
dimension: number,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {encodeKernelArtifact} from './kernel-artifact-codec.js';
|
|
2
|
+
import {
|
|
3
|
+
acceptKernelOperation,
|
|
4
|
+
evaluateCachedArtifact,
|
|
5
|
+
findKernelOperation,
|
|
6
|
+
findKernelOperations,
|
|
7
|
+
kernelContentId,
|
|
8
|
+
kernelOperationKey,
|
|
9
|
+
type CacheCodec,
|
|
10
|
+
type KernelArtifact,
|
|
11
|
+
type KernelOperationKey,
|
|
12
|
+
type KernelValueLifecycle,
|
|
13
|
+
} from './kernel-cache.js';
|
|
14
|
+
import {estimateRetainedBytes} from './retained-memory.js';
|
|
15
|
+
|
|
16
|
+
export type CacheOptions<Value> = CacheCodec<Value>;
|
|
17
|
+
|
|
18
|
+
const identities = new WeakMap<Function, string>();
|
|
19
|
+
const sessionIdentities = new WeakMap<Function, string>();
|
|
20
|
+
const session = crypto.randomUUID();
|
|
21
|
+
let nextIdentity = 0;
|
|
22
|
+
|
|
23
|
+
/** The compiler supplies the definition and its static dependency fingerprint. */
|
|
24
|
+
export function identifyCachedFunction<Fn extends Function>(
|
|
25
|
+
fn: Fn,
|
|
26
|
+
identity: string,
|
|
27
|
+
): Fn {
|
|
28
|
+
// Identity belongs to this cache definition (including its codec), not to
|
|
29
|
+
// every other use of the author's original function object.
|
|
30
|
+
const identified = function (this: unknown, ...args: unknown[]) {
|
|
31
|
+
return Reflect.apply(fn, this, args);
|
|
32
|
+
} as unknown as Fn;
|
|
33
|
+
identities.set(identified, identity);
|
|
34
|
+
return identified;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function functionIdentity(fn: Function): {id: string; persistent: boolean} {
|
|
38
|
+
const identity = identities.get(fn);
|
|
39
|
+
if (identity) return {id: identity, persistent: true};
|
|
40
|
+
let id = sessionIdentities.get(fn);
|
|
41
|
+
if (!id) {
|
|
42
|
+
id = `${session}:${nextIdentity++}`;
|
|
43
|
+
sessionIdentities.set(fn, id);
|
|
44
|
+
}
|
|
45
|
+
// Without compiler fingerprints, object identity is the only sound identity
|
|
46
|
+
// for a JavaScript closure. It still shares the process-wide memory budget.
|
|
47
|
+
return {id, persistent: false};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dataLifecycle: KernelValueLifecycle<unknown> = {
|
|
51
|
+
estimateBytes: estimateRetainedBytes,
|
|
52
|
+
retain: value => value,
|
|
53
|
+
instantiate: value => value,
|
|
54
|
+
release() {},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Memoizes a synchronous, deterministic computation in the shared cache.
|
|
59
|
+
* Omit args to create a function, or pass an argument tuple to return its value.
|
|
60
|
+
* Pass changing captured state as arguments and treat returned data as immutable.
|
|
61
|
+
* The engine fingerprints definitions and dependencies for persistent reuse.
|
|
62
|
+
* Outside the engine, function identity provides process-local memory reuse.
|
|
63
|
+
* Custom encoder/decoder pairs run only when writing/restoring persistent data;
|
|
64
|
+
* memory hits return the retained value without decoding or copying it.
|
|
65
|
+
*/
|
|
66
|
+
export function cache<Args extends unknown[], Value>(
|
|
67
|
+
compute: (
|
|
68
|
+
...args: Args
|
|
69
|
+
) => Value & (Value extends PromiseLike<unknown> ? never : unknown),
|
|
70
|
+
args: Readonly<NoInfer<Args>>,
|
|
71
|
+
options?: CacheOptions<NoInfer<Value>>,
|
|
72
|
+
): Value;
|
|
73
|
+
export function cache<Args extends unknown[], Value>(
|
|
74
|
+
compute: (
|
|
75
|
+
...args: Args
|
|
76
|
+
) => Value & (Value extends PromiseLike<unknown> ? never : unknown),
|
|
77
|
+
args?: undefined,
|
|
78
|
+
options?: CacheOptions<NoInfer<Value>>,
|
|
79
|
+
): (...args: Args) => Value;
|
|
80
|
+
export function cache<Args extends unknown[], Value>(
|
|
81
|
+
compute: (
|
|
82
|
+
...args: Args
|
|
83
|
+
) => Value & (Value extends PromiseLike<unknown> ? never : unknown),
|
|
84
|
+
args?: Readonly<Args>,
|
|
85
|
+
options?: CacheOptions<Value>,
|
|
86
|
+
): Value | ((...args: Args) => Value) {
|
|
87
|
+
const operation = cachedArtifact(
|
|
88
|
+
(...args: Args): Value => {
|
|
89
|
+
const value = compute(...args);
|
|
90
|
+
if (
|
|
91
|
+
value &&
|
|
92
|
+
typeof value === 'object' &&
|
|
93
|
+
'then' in value &&
|
|
94
|
+
typeof value.then === 'function'
|
|
95
|
+
)
|
|
96
|
+
throw new Error('cache() requires a synchronous computation.');
|
|
97
|
+
return value;
|
|
98
|
+
},
|
|
99
|
+
{identity: compute, codec: options},
|
|
100
|
+
);
|
|
101
|
+
return args === undefined
|
|
102
|
+
? (...args: Args) => operation(...args).value
|
|
103
|
+
: operation(...args).value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Internal artifact form also supports native ownership and remote admission. */
|
|
107
|
+
export function cachedArtifact<Args extends unknown[], Value>(
|
|
108
|
+
compute: (...args: Args) => Value,
|
|
109
|
+
{
|
|
110
|
+
key,
|
|
111
|
+
lifecycle = dataLifecycle as KernelValueLifecycle<Value>,
|
|
112
|
+
codec,
|
|
113
|
+
identity = compute,
|
|
114
|
+
namespace = 'cached',
|
|
115
|
+
}: {
|
|
116
|
+
key?: (...args: Args) => KernelOperationKey;
|
|
117
|
+
lifecycle?: KernelValueLifecycle<Value>;
|
|
118
|
+
codec?: CacheCodec<Value> | false;
|
|
119
|
+
identity?: Function;
|
|
120
|
+
namespace?: string;
|
|
121
|
+
} = {},
|
|
122
|
+
) {
|
|
123
|
+
const definition = functionIdentity(identity);
|
|
124
|
+
const persistence = key || definition.persistent ? codec : false;
|
|
125
|
+
const operationKey =
|
|
126
|
+
key ??
|
|
127
|
+
((...args: Args) =>
|
|
128
|
+
kernelOperationKey(
|
|
129
|
+
namespace,
|
|
130
|
+
[definition.id, kernelContentId(encodeKernelArtifact('', args))],
|
|
131
|
+
[],
|
|
132
|
+
));
|
|
133
|
+
const find = (key: KernelOperationKey) =>
|
|
134
|
+
findKernelOperation(key, lifecycle, persistence);
|
|
135
|
+
const findMany = (keys: readonly KernelOperationKey[]) =>
|
|
136
|
+
findKernelOperations(keys, lifecycle, persistence);
|
|
137
|
+
const accept = (
|
|
138
|
+
key: KernelOperationKey,
|
|
139
|
+
value: Value,
|
|
140
|
+
milliseconds: number,
|
|
141
|
+
) => acceptKernelOperation(key, lifecycle, value, milliseconds, persistence);
|
|
142
|
+
return Object.assign(
|
|
143
|
+
(...args: Args): KernelArtifact<Value> => {
|
|
144
|
+
return evaluateCachedArtifact(
|
|
145
|
+
operationKey(...args),
|
|
146
|
+
lifecycle,
|
|
147
|
+
() => compute(...args),
|
|
148
|
+
persistence,
|
|
149
|
+
);
|
|
150
|
+
},
|
|
151
|
+
{key: operationKey, find, findMany, accept},
|
|
152
|
+
);
|
|
153
|
+
}
|