kireji 0.6.8 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/app/kireji/description +1 -1
- package/src/app/kireji/editor/point.js +0 -1
- package/src/app/kireji/editor/sections/state-space/part.html_.js +5 -5
- package/src/app/kireji/editor/static.css +7 -0
- package/src/app/kireji/editor/tab-group/mathML-subpart.js +8 -0
- package/src/app/kireji/sidebar/static.css +51 -15
- package/src/app/kireji/static.css +0 -4
- package/src/app/kireji/tool-bar/part.css +1 -1
- package/src/build.js +58 -1
- package/src/part.css_.js +14 -0
- package/src/part.html_.js +1 -1
- package/src/parts/abstract/clip/mathML-subpart.js +4 -0
- package/src/parts/abstract/match/mathML-subpart.js +9 -0
- package/src/parts/abstract/mesh/build.js +84 -0
- package/src/parts/abstract/mesh/constants.js +1 -0
- package/src/parts/abstract/mesh/data-get.js +11 -0
- package/src/parts/abstract/mesh/mathML-subpart.js +10 -0
- package/src/parts/abstract/mesh/model_.js +1 -0
- package/src/parts/abstract/mesh/part.json +18 -0
- package/src/parts/abstract/mesh/point-tri-contains.js +14 -0
- package/src/parts/abstract/mesh/point-tri-that-contains.js +10 -0
- package/src/parts/abstract/mesh/ray-cast.js +262 -0
- package/src/parts/abstract/mesh/routeID-distribute.js +42 -0
- package/src/parts/abstract/mesh/routeID-model-to.js +5 -0
- package/src/parts/abstract/mesh/type.d.ts +95 -0
- package/src/parts/abstract/mix/mathML-subpart.js +9 -0
- package/src/parts/abstract/part/mathML-subpart.js +3 -0
- package/src/parts/abstract/part/mathML.js +22 -0
- package/src/parts/abstract/part/part.json +11 -0
- package/src/parts/abstract/part/routeID-update.js +5 -5
- package/src/parts/abstract/part/type.d.ts +4 -0
- package/src/parts/abstract/part-mask/mathML-subpart.js +1 -0
- package/src/parts/abstract/part-outliner/itemHTML-recursive.js +1 -1
- package/src/parts/abstract/permutation/mathML-subpart.js +10 -0
- package/src/parts/abstract/scroller/mathML-subpart.js +1 -0
- package/src/parts/abstract/scroller/onscroll.js +3 -1
- package/src/parts/core/hot-keys/async-install.js +18 -8
- package/src/parts/core/hot-keys/constants.js +4 -2
- package/src/parts/desktop/windows/superset-get.js +1 -1
- package/src/{part.css → static.css} +0 -10
- package/src/type.d.ts +14 -2
- package/src/app/kireji/editor/tab-group/equation.html_.js +0 -42
- package/src/parts/abstract/boolean/equation-variable.html +0 -1
- package/src/parts/abstract/box/build.js +0 -24
- package/src/parts/abstract/box/constants.js +0 -1
- package/src/parts/abstract/box/description-abstract +0 -1
- package/src/parts/abstract/box/dimensions_.js +0 -1
- package/src/parts/abstract/box/equation.html_.js +0 -1
- package/src/parts/abstract/box/model_.js +0 -1
- package/src/parts/abstract/box/part.json +0 -3
- package/src/parts/abstract/box/routeID-distribute.js +0 -7
- package/src/parts/abstract/box/routeID-model-to.js +0 -18
- package/src/parts/abstract/box/title-abstract +0 -1
- package/src/parts/abstract/box/type.d.ts +0 -20
- package/src/parts/abstract/match/equation.html_.js +0 -11
- package/src/parts/abstract/mix/equation.html_.js +0 -11
- package/src/parts/abstract/part/equation-variable.html_.js +0 -1
- package/src/parts/abstract/part/equation.html_.js +0 -3
- package/src/parts/abstract/part-mask/equation-variable.html_.js +0 -1
- package/src/parts/abstract/part-mask/equation.html_.js +0 -1
- package/src/parts/abstract/permutation/equation.html_.js +0 -1
- package/src/parts/abstract/scroller/equation-variable.html +0 -1
- package/src/parts/abstract/scroller/equation.html +0 -1
- package/src/parts/user/part.json +0 -3
- package/src/parts/user/title +0 -1
- package/src/parts/user/type.d.ts +0 -2
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Define a safe result: the current position before casting.
|
|
2
|
+
const safeIterationResult = {
|
|
3
|
+
hit: false,
|
|
4
|
+
triIndex: mesh.triIndex,
|
|
5
|
+
point: { ...mesh.position },
|
|
6
|
+
forceVector: FORCE_VECTOR
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Obtain the speed of the force vector, which will be used later if we need to slide along the boundary.
|
|
10
|
+
const speed = Vector.magnitude(FORCE_VECTOR)
|
|
11
|
+
|
|
12
|
+
// If there's no motion, nothing will happen; return the safe result.
|
|
13
|
+
if (speed === 0)
|
|
14
|
+
return safeIterationResult
|
|
15
|
+
|
|
16
|
+
// Otherwise, initialize timing data as though the vector doesn't intersect any grid lines.
|
|
17
|
+
const timeOfNextIntersection = { x: Infinity, y: Infinity }
|
|
18
|
+
const timeBetweenIntersections = { ...timeOfNextIntersection }
|
|
19
|
+
|
|
20
|
+
// Set the clock to zero.
|
|
21
|
+
let time = 0
|
|
22
|
+
|
|
23
|
+
function initializeRayIntersectionSchedule(alsoComputeIntersectionInterval) {
|
|
24
|
+
|
|
25
|
+
// If the force vector isn't parallel to the x axis...
|
|
26
|
+
if (FORCE_VECTOR.x !== 0) {
|
|
27
|
+
|
|
28
|
+
// Determine the x coordinate of the first grid line the ray may intersect.
|
|
29
|
+
const nextGridX = Math.floor(safeIterationResult.point.x) + (FORCE_VECTOR.x > 0)
|
|
30
|
+
|
|
31
|
+
// Compute the exact moment that intersection will happen.
|
|
32
|
+
timeOfNextIntersection.x = time + (nextGridX - safeIterationResult.point.x) / FORCE_VECTOR.x
|
|
33
|
+
|
|
34
|
+
// Compute the constant time between each grid line intersection.
|
|
35
|
+
if (alsoComputeIntersectionInterval)
|
|
36
|
+
timeBetweenIntersections.x = 1 / Math.abs(FORCE_VECTOR.x)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Perform the same steps for the y-axis.
|
|
40
|
+
if (FORCE_VECTOR.y !== 0) {
|
|
41
|
+
const nextGridY = Math.floor(safeIterationResult.point.y) + (FORCE_VECTOR.y > 0)
|
|
42
|
+
timeOfNextIntersection.y = time + (nextGridY - safeIterationResult.point.y) / FORCE_VECTOR.y
|
|
43
|
+
if (alsoComputeIntersectionInterval)
|
|
44
|
+
timeBetweenIntersections.y = 1 / Math.abs(FORCE_VECTOR.y)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
initializeRayIntersectionSchedule(true)
|
|
49
|
+
|
|
50
|
+
const start = _.now
|
|
51
|
+
|
|
52
|
+
let iteration = 0
|
|
53
|
+
|
|
54
|
+
// Start iterating on the ray cast.
|
|
55
|
+
while (true) {
|
|
56
|
+
|
|
57
|
+
iteration++
|
|
58
|
+
|
|
59
|
+
// Emergency exit the loop.
|
|
60
|
+
if (_.now - start >= (DELTA_TIME * 1000))
|
|
61
|
+
throw { error: "LOCKED WHILE LOOP", processingTime: _.now - start, iteration, DELTA_TIME, FORCE_VECTOR, speed, safeIterationResult, timeOfNextIntersection, timeBetweenIntersections }
|
|
62
|
+
|
|
63
|
+
// Pick the dimension whose grid intersection will happen next.
|
|
64
|
+
const dimension = timeOfNextIntersection.x < timeOfNextIntersection.y ? "x" : "y"
|
|
65
|
+
|
|
66
|
+
// Note how much time must elapse to reach that intersection.
|
|
67
|
+
let timeElapsedDuringThisIteration = timeOfNextIntersection[dimension] - time
|
|
68
|
+
|
|
69
|
+
// Set the clock to the moment of that intersection.
|
|
70
|
+
time += timeElapsedDuringThisIteration
|
|
71
|
+
|
|
72
|
+
// Note if this is a case where the grid intersection is reached after the time limit.
|
|
73
|
+
let ranOutOfTime = time >= DELTA_TIME
|
|
74
|
+
|
|
75
|
+
// In that case...
|
|
76
|
+
if (ranOutOfTime) {
|
|
77
|
+
|
|
78
|
+
// Measure how far we are past the time limit.
|
|
79
|
+
const overshootTime = time - DELTA_TIME
|
|
80
|
+
|
|
81
|
+
// Adjust how much time we say elapsed.
|
|
82
|
+
timeElapsedDuringThisIteration -= overshootTime
|
|
83
|
+
|
|
84
|
+
// Set the block back to the last possible moment.
|
|
85
|
+
time = DELTA_TIME
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Construct the next point along the ray, given the elapsed time.
|
|
89
|
+
const point = Vector.add(safeIterationResult.point, Vector.multiply(FORCE_VECTOR, timeElapsedDuringThisIteration))
|
|
90
|
+
|
|
91
|
+
// Check if the point is outside the mesh boundary.
|
|
92
|
+
const triIndex = mesh.triThatContainsPoint(point)
|
|
93
|
+
|
|
94
|
+
// If it is...
|
|
95
|
+
if (triIndex === -1) {
|
|
96
|
+
|
|
97
|
+
// We are at a boundary pixel. Let's move our position to exactly the center of our current point.
|
|
98
|
+
// safeIterationResult.point = Vector.add(Vector.floor(safeIterationResult.point), 0.5)
|
|
99
|
+
|
|
100
|
+
// If we have permission to simulate sliding...
|
|
101
|
+
if (ENABLE_SLIDING) {
|
|
102
|
+
|
|
103
|
+
// Walk back the clock to before the ray hit.
|
|
104
|
+
time -= timeElapsedDuringThisIteration
|
|
105
|
+
|
|
106
|
+
const neighbor = {
|
|
107
|
+
dot: -Infinity,
|
|
108
|
+
point: null,
|
|
109
|
+
triIndex: null,
|
|
110
|
+
direction: {
|
|
111
|
+
x: null,
|
|
112
|
+
y: null
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let boundaryAppearsFlat = false
|
|
117
|
+
|
|
118
|
+
// Search the local neighborhood of points for one that we can safely move to.
|
|
119
|
+
for (let x = -1; x <= 1; x++) for (let y = -1; y <= 1; y++) {
|
|
120
|
+
|
|
121
|
+
// Exclude the point itself
|
|
122
|
+
if (!x && !y)
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
const direction = { x, y }
|
|
126
|
+
|
|
127
|
+
// Find out how much of the force vector is cancelled out by going to this neighbor.
|
|
128
|
+
const dot = Vector.dot(Vector.normalize(direction), Vector.normalize(FORCE_VECTOR))
|
|
129
|
+
|
|
130
|
+
// If it would require traveling "against" or perpendicular to the force vector, exclude it from consideration.
|
|
131
|
+
if (dot < 0)
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
// Get the position of the center of the neighbor.
|
|
135
|
+
const point = Vector.add(Vector.floor(Vector.add(safeIterationResult.point, direction)), 0.5)
|
|
136
|
+
const triIndex = mesh.triThatContainsPoint(point)
|
|
137
|
+
|
|
138
|
+
// If it isn't part of the mesh, exclude it from consideration.
|
|
139
|
+
if (triIndex === -1)
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
// Store the neighbor which most agrees with the force vector.
|
|
143
|
+
if (dot >= neighbor.dot) {
|
|
144
|
+
if (dot < 0.05 && neighbor.dot !== -Infinity && neighbor.dot < 0.05) {
|
|
145
|
+
boundaryAppearsFlat = true
|
|
146
|
+
// Boundary is "flat". Don't allow perpendicular motion.
|
|
147
|
+
neighbor.dot = -Infinity
|
|
148
|
+
neighbor.point = null
|
|
149
|
+
neighbor.triIndex = null
|
|
150
|
+
neighbor.direction.x = null
|
|
151
|
+
neighbor.direction.y = null
|
|
152
|
+
} else {
|
|
153
|
+
boundaryAppearsFlat = false
|
|
154
|
+
neighbor.dot = dot
|
|
155
|
+
neighbor.point = point
|
|
156
|
+
neighbor.triIndex = triIndex
|
|
157
|
+
neighbor.direction = direction
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (boundaryAppearsFlat) {
|
|
163
|
+
// TODO: Consider using the line through the opposing neighbors to search for asymmetry in the mesh boundary.
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If there is no neighboring point, we are "stuck". The ray cast ends here.
|
|
167
|
+
if (!neighbor.point) {
|
|
168
|
+
|
|
169
|
+
// Return the safe result data.
|
|
170
|
+
safeIterationResult.hit = true
|
|
171
|
+
return safeIterationResult
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Compute travel distance to the center of the neighbor.
|
|
175
|
+
const vectorToNeighbor = Vector.subtract(neighbor.point, safeIterationResult.point)
|
|
176
|
+
const distance = Vector.magnitude(vectorToNeighbor)
|
|
177
|
+
|
|
178
|
+
// Construct a force vector pointing to the chosen neighbor.
|
|
179
|
+
// TODO: speed should actually be reduced.
|
|
180
|
+
const forceVectorToNeighbor = Vector.multiply(Vector.normalize(vectorToNeighbor), speed)
|
|
181
|
+
|
|
182
|
+
// Compute travel time to reach the neighbor.
|
|
183
|
+
timeElapsedDuringThisIteration = distance / speed
|
|
184
|
+
|
|
185
|
+
// Move the clock ahead to our arrival at the neighbor.
|
|
186
|
+
time += timeElapsedDuringThisIteration
|
|
187
|
+
|
|
188
|
+
// Check again if we ran out of time.
|
|
189
|
+
ranOutOfTime = time >= DELTA_TIME
|
|
190
|
+
if (ranOutOfTime) {
|
|
191
|
+
const overshootTime = time - DELTA_TIME
|
|
192
|
+
timeElapsedDuringThisIteration -= overshootTime
|
|
193
|
+
time = DELTA_TIME
|
|
194
|
+
|
|
195
|
+
// Construct the point along the way to the neighbor.
|
|
196
|
+
const point = Vector.add(safeIterationResult.point, Vector.multiply(forceVectorToNeighbor, timeElapsedDuringThisIteration))
|
|
197
|
+
const triIndex = mesh.triThatContainsPoint(point)
|
|
198
|
+
safeIterationResult.forceVector = forceVectorToNeighbor
|
|
199
|
+
|
|
200
|
+
if (triIndex === -1) {
|
|
201
|
+
// We didn't have enough time to overcome the diagonal boundary.
|
|
202
|
+
|
|
203
|
+
if (speed * DELTA_TIME > Math.SQRT2) {
|
|
204
|
+
// The current speed is enough to overcome diagonal corners.
|
|
205
|
+
|
|
206
|
+
// Return the previous safe point, chalking the lost time up to friction.
|
|
207
|
+
return safeIterationResult
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// There is not enough velocity to overcome the diagonal. Use random chance.
|
|
211
|
+
if (Math.random() < Math.SQRT1_2) {
|
|
212
|
+
safeIterationResult.point = neighbor.point
|
|
213
|
+
safeIterationResult.triIndex = neighbor.triIndex
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return safeIterationResult
|
|
217
|
+
} else {
|
|
218
|
+
|
|
219
|
+
// Go to the partial point.
|
|
220
|
+
safeIterationResult.point = point
|
|
221
|
+
safeIterationResult.triIndex = triIndex
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return safeIterationResult
|
|
225
|
+
|
|
226
|
+
// TODO: Consider moving toward a 4-point neighbor instead.
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Set the neighbor as the new safe point.
|
|
230
|
+
safeIterationResult.point = neighbor.point
|
|
231
|
+
safeIterationResult.triIndex = neighbor.triIndex
|
|
232
|
+
safeIterationResult.forceVector = FORCE_VECTOR
|
|
233
|
+
|
|
234
|
+
// The normal ray intersection schedule has changed.
|
|
235
|
+
initializeRayIntersectionSchedule()
|
|
236
|
+
|
|
237
|
+
// Proceed with normal ray casting behavior from this point.
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
} else {
|
|
241
|
+
|
|
242
|
+
// Discard the unsafe point and return the safe result data.
|
|
243
|
+
safeIterationResult.hit = true
|
|
244
|
+
return safeIterationResult
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
// Otherwise, update the safe result.
|
|
250
|
+
safeIterationResult.point = point
|
|
251
|
+
safeIterationResult.triIndex = triIndex
|
|
252
|
+
|
|
253
|
+
// If we ran out of time...
|
|
254
|
+
if (ranOutOfTime) {
|
|
255
|
+
|
|
256
|
+
// We're done. Return the safe point.
|
|
257
|
+
return safeIterationResult
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Otherwise, Prepare for the next grid line time of this dimension.
|
|
261
|
+
timeOfNextIntersection[dimension] += timeBetweenIntersections[dimension]
|
|
262
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
mesh.updateRouteID(ROUTE_ID)
|
|
2
|
+
|
|
3
|
+
// Binary search better than embedded match.
|
|
4
|
+
mesh.triIndex = (() => {
|
|
5
|
+
|
|
6
|
+
let low = 0
|
|
7
|
+
let high = mesh.triTable.length - 1
|
|
8
|
+
|
|
9
|
+
while (low <= high) {
|
|
10
|
+
|
|
11
|
+
const mid = (low + high) >>> 1
|
|
12
|
+
const triData = mesh.triTable[mid]
|
|
13
|
+
const nextTriData = mesh.triTable[mid + 1]
|
|
14
|
+
|
|
15
|
+
if (ROUTE_ID < triData.offset)
|
|
16
|
+
high = mid - 1
|
|
17
|
+
else if (nextTriData && ROUTE_ID >= nextTriData.offset)
|
|
18
|
+
low = mid + 1
|
|
19
|
+
else
|
|
20
|
+
return mid
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Fallback.
|
|
24
|
+
return 0
|
|
25
|
+
})()
|
|
26
|
+
|
|
27
|
+
ROUTE_ID -= mesh.triTable[mesh.triIndex].offset
|
|
28
|
+
|
|
29
|
+
// Embedded match can become binary search later.
|
|
30
|
+
for (const row of mesh.triTable[mesh.triIndex].rows) {
|
|
31
|
+
|
|
32
|
+
if (!row)
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
const rowWidth = BigInt(row.range.max - row.range.min + 1)
|
|
36
|
+
|
|
37
|
+
if (ROUTE_ID < row.offset + rowWidth) {
|
|
38
|
+
mesh.position.y = row.y
|
|
39
|
+
mesh.position.x = row.range.min + Number(ROUTE_ID - row.offset)
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// TODO: Validate MODEL and handle out-of-range points.
|
|
2
|
+
const triIndex = mesh.triThatContainsPoint(MODEL)
|
|
3
|
+
const triData = mesh.triTable[triIndex]
|
|
4
|
+
const row = triData.rows[Math.floor(MODEL.y) - triData.range.min]
|
|
5
|
+
return triData.offset + row.offset + BigInt(Math.floor(MODEL.x) - row.range.min)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
declare interface IMesh<TOwner>
|
|
2
|
+
extends IPart<TOwner, null> {
|
|
3
|
+
|
|
4
|
+
// Serialized Properties.
|
|
5
|
+
readonly getData(): IMeshData
|
|
6
|
+
/** Casts a ray from the current mesh position along the force vector direction for the given delta time and returns a summary of the results. @param FORCE_VECTOR the force vector represent the position the uninterrupted ray will arrive at in one second. @param DELTA_TIME the duration of the time cast in seconds. @param ENABLE_SLIDING whether or not to enable the ray to "wrap" along the mesh boundary instead of stopping cold. */
|
|
7
|
+
readonly castRay(FORCE_VECTOR: IVector2, DELTA_TIME: number, ENABLE_SLIDING: boolean): {
|
|
8
|
+
/** Whether or not the ray hit the mesh boundary. */
|
|
9
|
+
readonly hit: boolean
|
|
10
|
+
/** The tri the ray most recently occupied when it hit the boundary or the index of the boundary where the ray stopped if there is no hit. */
|
|
11
|
+
readonly triIndex: IMeshTriIndex
|
|
12
|
+
/** The rounded cell position where the cast ray landed. */
|
|
13
|
+
readonly position: IVector2
|
|
14
|
+
/** The force vector used to cast the ray, which might be different from the input if sliding is enabled as it may point along the direction of the most recent sliding iteration. */
|
|
15
|
+
readonly forceVector: IVector2
|
|
16
|
+
}
|
|
17
|
+
/** Checks if a point (x, y) rounds to a valid pixel within the specified tri's memoized row data. */
|
|
18
|
+
readonly triContainsPoint(TRI_INDEX: IMeshTriIndex, POINT: IVector2): boolean
|
|
19
|
+
/** Returns whether or not the given point is in a tri. @returns the index of the tri that contains the point. -1 otherwise. */
|
|
20
|
+
readonly triThatContainsPoint(POINT: IVector2): IMeshTriIndex
|
|
21
|
+
|
|
22
|
+
// Runtime Properties.
|
|
23
|
+
/** The memoization data of every tri in the mesh, stored by tri index at build-time. */
|
|
24
|
+
readonly triTable: IMeshTriData[]
|
|
25
|
+
/** The index of the current tri in the mesh. */
|
|
26
|
+
readonly triIndex?: IMeshTriIndex
|
|
27
|
+
/** The current position of the state in the mesh. */
|
|
28
|
+
readonly position: IVector2
|
|
29
|
+
readonly manifest: IMeshManifest
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare interface IMeshTriData {
|
|
33
|
+
/** The original point array of the tri as taken from `mesh.getData()`. */
|
|
34
|
+
readonly points: IMeshTriPoints
|
|
35
|
+
/** The y-axis range of the tri. */
|
|
36
|
+
readonly range: {
|
|
37
|
+
/** The y position of the top-most row of the tri. */
|
|
38
|
+
readonly min: number
|
|
39
|
+
/** The y position of the bottom-most row of the tri. */
|
|
40
|
+
readonly max: number
|
|
41
|
+
}
|
|
42
|
+
/** The rows of pixels in the tri. */
|
|
43
|
+
readonly rows: IMeshTriDataRow[]
|
|
44
|
+
/** The offset of the tri in the overall mesh. */
|
|
45
|
+
readonly offset: bigint
|
|
46
|
+
/** The total number of pixels in the tri. */
|
|
47
|
+
readonly cardinality: bigint
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare interface IMeshTriDataRow {
|
|
51
|
+
/** The y position of this row of the tri's space. */
|
|
52
|
+
readonly y: number
|
|
53
|
+
/** The x-axis range of the row. */
|
|
54
|
+
readonly range: {
|
|
55
|
+
/** The smallest x position that is within the tri row. */
|
|
56
|
+
readonly min: number
|
|
57
|
+
/** The largest x position that is within the tri row. */
|
|
58
|
+
readonly max: number
|
|
59
|
+
}
|
|
60
|
+
/** The bigint offset of this row in the overall tri. */
|
|
61
|
+
readonly offset: bigint
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
declare interface IMeshManifest
|
|
66
|
+
extends IPartManifest {
|
|
67
|
+
/** The available collision vertices as a flat array of 2D coordinates. Used for defining the world's collision tris. */
|
|
68
|
+
readonly points: number[],
|
|
69
|
+
/** The list of collision tris, as a flat array of point triples. Used to define the walkable area of the world. */
|
|
70
|
+
readonly tris: number[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
declare type IMeshData = [IMeshPoint[], IMeshTri[]]
|
|
74
|
+
|
|
75
|
+
declare type IMeshTri =
|
|
76
|
+
[IMeshPointIndex, IMeshPointIndex, IMeshPointIndex]
|
|
77
|
+
|
|
78
|
+
declare type IMeshPointIndex =
|
|
79
|
+
number
|
|
80
|
+
|
|
81
|
+
declare type IMeshTriPoints =
|
|
82
|
+
[IMeshPoint, IMeshPoint, IMeshPoint]
|
|
83
|
+
|
|
84
|
+
declare type IMeshPoint =
|
|
85
|
+
[x: number, y: number]
|
|
86
|
+
|
|
87
|
+
declare type IMeshTriIndex =
|
|
88
|
+
number
|
|
89
|
+
|
|
90
|
+
declare type IMeshAny =
|
|
91
|
+
IMesh<IPartAny>
|
|
92
|
+
|
|
93
|
+
declare const mesh: IMeshAny
|
|
94
|
+
|
|
95
|
+
declare const TRI: IMeshTriData
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const defaultTerm = /* xml */`<mn>1</mn>`
|
|
2
|
+
|
|
3
|
+
if (DEPTH <= 0 || part.length === 0)
|
|
4
|
+
// Strip the "<math>" tags off.
|
|
5
|
+
return [mix.length ? /* xml */`<mo largeop="true">∏</mo><msub><mi>𝑘</mi><msub><mi>𝑝</mi><mi>${part.key ?? "ecosystem"}</mi></msub></msub>` : defaultTerm]
|
|
6
|
+
|
|
7
|
+
const operator = "<mo class=product>⋅</mo>"
|
|
8
|
+
const factors = part.map(subpart => subpart.mathML(DEPTH - 1, "none", LABELS, true, false)).filter(mathML => mathML !== defaultTerm && mathML !== `<mrow>${defaultTerm}</mrow>`)
|
|
9
|
+
return factors.length ? factors.flatMap(factor => [factor, operator]).slice(0, -1) : [defaultTerm]
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const cardinality = part.cardinality
|
|
2
|
+
const cardinalityAsString = instances.includes(part) ? cardinality.toString() : null
|
|
3
|
+
return [cardinalityAsString.length < 16 ? `<mn>${cardinalityAsString}</mn>` : scientific(cardinality, true).slice(6, -7).split("<mo>⋅</mo>").flatMap(term => [term, "<mo>⋅</mo>"]).slice(0, -1)]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const equationVariable = EQUATION_TYPE === "variable" || EQUATION_TYPE === "both" ? `${recurse(0, "none", false, false)}<mo>=</mo>` : ""
|
|
2
|
+
const equationValue = EQUATION_TYPE === "value" || EQUATION_TYPE === "both" ? `<mo>=</mo>${part.cardinality.toString().length <= 15 ? `<mn>${part.cardinality}</mn>` : `<mrow>${scientific(part.cardinality, true).slice(6, -7)}</mrow>`}` : ""
|
|
3
|
+
const expression = (() => {
|
|
4
|
+
|
|
5
|
+
if (DEPTH <= 0)
|
|
6
|
+
return `<msub><mi>𝑘</mi><mi>${part.key ?? "ecosystem"}</mi></msub>`
|
|
7
|
+
|
|
8
|
+
const terms = part.subpartMathML(DEPTH - 1, LABELS)
|
|
9
|
+
|
|
10
|
+
const needsParentheses = PARENTHESIZE && terms.length > 1
|
|
11
|
+
|
|
12
|
+
debug(terms)
|
|
13
|
+
|
|
14
|
+
// Allow "<mrow>" tag nesting for parentheses.
|
|
15
|
+
return (LABELS ? "<munder>" + (needsParentheses ? "" : "<mrow>") : "") + (needsParentheses ? "<mrow class=parenthetic><mo>(</mo>" : "") + terms.join("") + (needsParentheses ? `<mo>)</mo></mrow>` : "") + (LABELS ? (needsParentheses ? "" : "</mrow>") + `<munder><mo stretchy="true">⏟</mo>${recurse(0, "none", false, false)}</munder></munder>` : "")
|
|
16
|
+
})()
|
|
17
|
+
|
|
18
|
+
// TODO: handle parenthesis!
|
|
19
|
+
|
|
20
|
+
const showBracketBelow = DEPTH > 2
|
|
21
|
+
|
|
22
|
+
return /* html */`${WRAP_IN_MATH_TAG ? "<math displaystyle=true><mrow>" : ""}${equationVariable}${expression}${equationValue}${WRAP_IN_MATH_TAG ? "</mrow></math>" : ""}`
|
|
@@ -75,6 +75,17 @@
|
|
|
75
75
|
],
|
|
76
76
|
"manifest-resolve-part-from": [
|
|
77
77
|
"PROPERTY_KEY"
|
|
78
|
+
],
|
|
79
|
+
"mathML": [
|
|
80
|
+
"DEPTH = 0",
|
|
81
|
+
"EQUATION_TYPE = \"none\"",
|
|
82
|
+
"LABELS = false",
|
|
83
|
+
"PARENTHESIZE = false",
|
|
84
|
+
"WRAP_IN_MATH_TAG = true"
|
|
85
|
+
],
|
|
86
|
+
"mathML-subpart": [
|
|
87
|
+
"DEPTH",
|
|
88
|
+
"LABELS"
|
|
78
89
|
]
|
|
79
90
|
}
|
|
80
91
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
if (part.isAbstract)
|
|
2
|
-
throw new Error(`You can't set the route ID of an abstract part. (${part.host})`)
|
|
2
|
+
throw new Error(`Route ID Update Error: You can't set the route ID of an abstract part. (${part.host})`)
|
|
3
3
|
|
|
4
4
|
if (ROUTE_ID === undefined)
|
|
5
|
-
throw new Error(`The ROUTE_ID argument is missing. ` + part.host)
|
|
5
|
+
throw new Error(`Route ID Update Error: The ROUTE_ID argument is missing. ` + part.host)
|
|
6
6
|
|
|
7
7
|
if (typeof ROUTE_ID !== "bigint" || ROUTE_ID < -1n)
|
|
8
|
-
throw new Error(`Route ID is invalid. ` + part.host)
|
|
8
|
+
throw new Error(`Route ID Update Error: Route ID is invalid. ` + part.host)
|
|
9
9
|
|
|
10
10
|
if (ROUTE_ID >= part.cardinality)
|
|
11
|
-
throw new Error(`Route ID (${ROUTE_ID}) out of range (max = ${part.cardinality - 1n}). ` + part.host)
|
|
11
|
+
throw new Error(`Route ID Update Error: Route ID (${ROUTE_ID}) out of range (max = ${part.cardinality - 1n}). ` + part.host)
|
|
12
12
|
|
|
13
13
|
part.previousRouteID = part.routeID
|
|
14
14
|
part.routeID = ROUTE_ID
|
|
@@ -20,6 +20,6 @@ part.justDisabled = !part.enabled && part.wasEnabled
|
|
|
20
20
|
part.deltaRouteID = part.routeID - part.previousRouteID
|
|
21
21
|
|
|
22
22
|
if (part.deltaRouteID === 0n)
|
|
23
|
-
warn(`Reassigned route ID (${part.routeID}) to part.\n ${part[".."] ? `${part[".."].key} {\n ${part.key} : "${part.host}" // <--- this part \n }` : part.host}`)
|
|
23
|
+
warn(`Route ID Update Warning: Reassigned route ID (${part.routeID}) to part.\n ${part[".."] ? `${part[".."].key} {\n ${part.key} : "${part.host}" // <--- this part \n }` : part.host}`)
|
|
24
24
|
|
|
25
25
|
part.dirty = true
|
|
@@ -109,6 +109,10 @@ declare interface IPart<TOwner, TSubpart>
|
|
|
109
109
|
readonly resolveOwnerOfManifest(PROPERTY_KEY: string): IPartAny | null
|
|
110
110
|
/** Returns a part whose host was provided as a (potentially relative) host name using the given property key in the part's manifest. This method uses the nearest part on which the manifest key is actually defined as the base for resolving any relative host name. Returns null if the given property does not exist anywhere in the chain. */
|
|
111
111
|
readonly resolvePartFromManifest(PROPERTY_KEY: string): IPartAny | null
|
|
112
|
+
/** Generates a returns a MathML string that depicts the cardinality equation of the platform. @param DEPTH the number of levels deep to expand the terms of the equation (up to Infinity) @param AS_EQUATION when true, begins the expression with something like `<math><msub><mi>k</mi><mi>part key</mi></msub><mo>=</mo>...` @param PARENTHESIZE whether or not to add parenthesis around the expression (useful when it will be nested in a larger one) (they will only be added if the resulting expression is not a single term). */
|
|
113
|
+
readonly mathML(DEPTH: number = 0, EQUATION_TYPE: string = "none", LABELS: boolean = false, PARENTHESIZE: boolean = false, WRAP_IN_MATH_TAG: boolean = true): string
|
|
114
|
+
/** Returns an array of string terms and operators which, when joined, depict the part's cardinality collecting arithmetic. */
|
|
115
|
+
readonly subpartMathML(DEPTH: number, LABELS: boolean): string[]
|
|
112
116
|
|
|
113
117
|
// Runtime Properties.
|
|
114
118
|
/** The parent part.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
return [/* xml */`<msup><mn>2</mn><mn>${partMask.superset.length}</mn></msup>`]
|
|
@@ -6,7 +6,7 @@ return partOutliner.getChildren(SUBJECT).map((childPart, i, childArray) => {
|
|
|
6
6
|
const symbol = `<img src="${childPart.placeholderImage("part.png")}"/>`
|
|
7
7
|
const handle = hasSubparts ? `<svg ${partOutliner.pointAttr("togglePoint", folderIndex)} xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 2 2" class="explore-toggle"><line x1="-0.41" y1="0" x2="0.41" y2="0" stroke-width="0.2" stroke-linecap="round" />${isOpen ? "" : `<line x1="0" y1="-0.41" x2="0" y2="0.41" stroke-width="0.2" stroke-linecap="round" />`}</svg>` : ""
|
|
8
8
|
const label = `<span class="label${childPart.isAbstract ? " abstract" : ""}">${partOutliner.getLabel(childPart)}</span>`
|
|
9
|
-
const summary = `<summary ${partOutliner.pointAttr("point", partIndex)} data-index="${partIndex}"${partOutliner.isActive(childPart) ? " data-active" : ""}${IS_LAST_OF_TYPE && !hasSubparts ? ' id="lastOutlinerItem"' : ""}
|
|
9
|
+
const summary = `<summary ${partOutliner.pointAttr("point", partIndex)} data-index="${partIndex}"${partOutliner.isActive(childPart) ? " data-active" : ""}${IS_LAST_OF_TYPE && !hasSubparts ? ' id="lastOutlinerItem"' : ""}><outliner-spacer style="--depth:${DEPTH + +!hasSubparts}"></outliner-spacer>${handle}${symbol}${label}</summary>`
|
|
10
10
|
return `<details${hasSubparts ? "" : ` class=empty`} ${isOpen ? "open" : ""}>${summary}${recurse(childPart, DEPTH + 1, IS_LAST_OF_TYPE && i === childArray.length - 1)}</details>`
|
|
11
11
|
}).join("")
|
|
12
12
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const n = permutation.supersetSize
|
|
2
|
+
|
|
3
|
+
// TODO: factor in other aspects when expanding to the collection type.
|
|
4
|
+
|
|
5
|
+
return [
|
|
6
|
+
"<mn>1</mn>",
|
|
7
|
+
"<mo>+</mo>",
|
|
8
|
+
`<munderover><mo>∑</mo><mrow><mi>𝑛</mi><mo>=</mo><mn>1</mn></mrow><mi>${n}</mi></munderover>`,
|
|
9
|
+
`<munderover><mo>∏</mo><mrow><mi>i</mi><mo>=</mo><mn>1</mn></mrow><mrow><mi>𝑛</mi></mrow></munderover><mrow><mn>${permutation.payloadCardinality}</mn><mo>(</mo><mi>${n}</mi><mo>-</mo><mi>i</mi><mo>+</mo><mn>1</mn><mo>)</mo></mrow>`
|
|
10
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
return [`<msup><mn>10</mn><mn>4</mn></msup>`]
|
|
@@ -37,6 +37,7 @@ globalThis.addEventListener("keyup", keyboardEvent => {
|
|
|
37
37
|
})
|
|
38
38
|
|
|
39
39
|
globalThis.addEventListener("keydown", keyboardEvent => {
|
|
40
|
+
|
|
40
41
|
if (!keyboardEvent.repeat) {
|
|
41
42
|
/* This handles the edge case when the user is holding modifier keys that
|
|
42
43
|
* they pressed while not focused on this instance of the ecosystem. */
|
|
@@ -64,15 +65,24 @@ globalThis.addEventListener("keydown", keyboardEvent => {
|
|
|
64
65
|
|
|
65
66
|
hotKeys.pressed.add(keyboardEvent.code)
|
|
66
67
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const doCombo = () => {
|
|
69
|
+
const combo = hotKeys.combo
|
|
70
|
+
const methodName = JSON.parse(_.application["hot-keys.json"] ?? "{}")[combo] ?? hotKeys.table[combo]
|
|
71
|
+
const method = methodName && (_.application[methodName] ?? hotKeys[methodName])
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
if (methodName) {
|
|
74
|
+
keyboardEvent.preventDefault()
|
|
75
|
+
if (typeof method === "function")
|
|
76
|
+
method()
|
|
77
|
+
// else warn(`Hot Keys Warning: method called ${methodName} is not defined on either the hot keys manager or the current application.`)
|
|
78
|
+
}
|
|
76
79
|
}
|
|
80
|
+
|
|
81
|
+
// Prevent a bug when the user presses keys too early.
|
|
82
|
+
if (client.hydrated)
|
|
83
|
+
doCombo()
|
|
84
|
+
else
|
|
85
|
+
client.promise.then(doCombo)
|
|
86
|
+
|
|
77
87
|
}
|
|
78
88
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
return Object.values(_.applications)
|
|
1
|
+
return Object.values(_.applications).filter(application => application !== _.parts.desktop)
|
|
@@ -30,7 +30,6 @@ body {
|
|
|
30
30
|
--system-ui-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Mono", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Consolas", "Courier New", monospace;
|
|
31
31
|
--menu-tween: 0;
|
|
32
32
|
--h: 100vh;
|
|
33
|
-
--warning-height: 28px;
|
|
34
33
|
--wallpaper-height: calc(var(--h) - var(--task-bar-height) - var(--warning-height) - var(--title-bar-height));
|
|
35
34
|
--wallpaper-width: 100vw;
|
|
36
35
|
--padding: calc(3.5 * var(--spacing));
|
|
@@ -292,15 +291,6 @@ body.dark warning- {
|
|
|
292
291
|
background-color: #644c08;
|
|
293
292
|
}
|
|
294
293
|
|
|
295
|
-
@media (width < 390px) {
|
|
296
|
-
|
|
297
|
-
html,
|
|
298
|
-
body {
|
|
299
|
-
--warning-height: 49px;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
294
|
#fullscreen-tray-item,
|
|
305
295
|
#share-tray-item {
|
|
306
296
|
display: none !important;
|
package/src/type.d.ts
CHANGED
|
@@ -283,7 +283,7 @@ declare function hydratePartsRecursive(part: string | IPartAny, domains: string[
|
|
|
283
283
|
declare function resolveRelativeHost(relativeHost: string, base: string | string[]): string
|
|
284
284
|
/** The immutable list of runtime instances for the root space, in order of when the were reached during recursive part hydration. */
|
|
285
285
|
declare const instances: IPartAny[]
|
|
286
|
-
/** The immutable list of every part
|
|
286
|
+
/** The immutable list of every part of the root space, in order of when the were reached during recursive part hydration. */
|
|
287
287
|
declare const allParts: IPartAny[]
|
|
288
288
|
/** The immutable list of every part host and filename combination, in order of when the were reached during recursive part hydration. */
|
|
289
289
|
declare const allSubjects: [host: string, filename?: string]
|
|
@@ -308,6 +308,18 @@ declare class FenwickTree {
|
|
|
308
308
|
declare class Vector {
|
|
309
309
|
static magnitude(vector: IVector): number
|
|
310
310
|
static normalize(vector: IVector): IVector
|
|
311
|
+
static sign(vector: IVector): IVector
|
|
312
|
+
static floor(vector: IVector): IVector
|
|
313
|
+
/** Performs the given binary operation on the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
|
|
314
|
+
static operate(value1: IVector | number, value2: IVector | number, operation: (a, b) => number): IVector | number
|
|
315
|
+
/** Adds the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
|
|
316
|
+
static add(value1: IVector | number, value2: IVector | number): IVector | number
|
|
317
|
+
/** Subtracts the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
|
|
318
|
+
static subtract(value1: IVector | number, value2: IVector | number): IVector | number
|
|
319
|
+
/** Multiplies the two values, which can each be either a vector or a number. If at least one of the values is a vector, returns a vector. Otherwise, returns a number. */
|
|
320
|
+
static multiply(value1: IVector | number, value2: IVector | number): IVector | number
|
|
321
|
+
static dot(vector1: IVector, vector2: IVector): number
|
|
311
322
|
}
|
|
312
323
|
|
|
313
|
-
declare type IVector = Record<string, number>
|
|
324
|
+
declare type IVector = Record<string, number>
|
|
325
|
+
declare type IVector2 = { x: number, y: number }
|