kireji 0.12.0 → 0.13.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kireji",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "A web framework for stateful, entropy-perfect, multi-origin web applications. Currently in alpha. Expect breaking changes for version 0. Use with caution!",
5
5
  "files": [
6
6
  "src/",
@@ -1,7 +1,5 @@
1
1
  const { model } = _
2
2
 
3
- debug('yes, it is translating ...')
4
-
5
3
  if (!("app" in model))
6
4
  model.app = {}
7
5
 
@@ -20,14 +18,6 @@ if (PATHNAME === "/") {
20
18
 
21
19
  const parts = PATHNAME.split("/").slice(1)
22
20
 
23
- debug({
24
- parts,
25
- badSubpath: parts[0] !== "issues",
26
- missingCode: !parts[1],
27
- wrongLength: parts.length !== 2,
28
- isNaN: isNaN(parts[1]),
29
- notFound: !(parts[1] in kirejiIssueTracker.sections.issues)
30
- })
31
21
  if (parts[0] !== "issues" || !parts[1] || parts.length !== 2 || isNaN(parts[1]) || !(parts[1] in kirejiIssueTracker.sections.issues))
32
22
  throw "Unknown Canonical Path: " + PATHNAME
33
23
 
package/src/build.js CHANGED
@@ -147,7 +147,16 @@ function ƒ(_, compressedSubjectOrigins) {
147
147
  return this.operate(value1, value2, (a, b) => a * b)
148
148
  }
149
149
  static dot(vector1, vector2) {
150
- return vector1.x * vector2.x + vector1.y * vector2.y
150
+ let result = 0
151
+ const dimensions = Object.keys(vector1)
152
+
153
+ if (dimensions.some(key => !(key in vector2)) || Object.keys(vector2).some(key => !(key in vector1)))
154
+ throw new Error(`Vector Dot Product Error: the two vectors do not have the same keys.`)
155
+
156
+ for (const dimension of dimensions)
157
+ result += vector1[dimension] * vector2[dimension]
158
+
159
+ return result
151
160
  }
152
161
  }
153
162
  class FenwickTree {
@@ -1,7 +1,7 @@
1
1
  mesh.define({
2
2
  triTable: { value: [] },
3
3
  triIndex: { value: -1, writable: true },
4
- position: { value: { x: null, y: null } },
4
+ position: { value: { x: null, y: null, z: null } },
5
5
  data: {
6
6
  resolve() {
7
7
  return mesh.getData()
@@ -23,21 +23,21 @@ mesh.define({
23
23
  // Recover the true points of the tri.
24
24
  const points = tri.map(pointID => pointList[pointID])
25
25
 
26
- // Get just the y-coordinate of all the points.
27
- const yPoints = points.map(point => point[1])
26
+ // Get just the z-coordinate of all the points.
27
+ const zPoints = points.map(point => point[2])
28
28
 
29
- // Estimate the y-range of this tri.
30
- const range = {
31
- min: Math.min(...yPoints),
32
- max: Math.max(...yPoints)
29
+ // Estimate the z-range of this tri.
30
+ const zRange = {
31
+ min: Math.min(...zPoints),
32
+ max: Math.max(...zPoints)
33
33
  }
34
34
 
35
35
  // Iterate over the range inclusively to populate each row subspace.
36
36
  const rows = []
37
- for (let rowID = range.min; rowID <= range.max; rowID++) {
37
+ for (let z = zRange.min; z <= zRange.max; z++) {
38
38
 
39
39
  // This irrational offset ensures that all grid points lie vertically in at most one triangular region.
40
- const offsetRowID = rowID + (Math.PI / 3.141 - 0.5)
40
+ const offsetRowID = z + (Math.PI / 3.141 - 0.5)
41
41
 
42
42
  // Iterate over the 3 lines in order to determine where the edges intersect the given row.
43
43
  const intersections = []
@@ -46,35 +46,45 @@ mesh.define({
46
46
  const pointA = points[edgeID]
47
47
  const pointB = points[(edgeID + 1) % 3]
48
48
 
49
- if ((pointA[1] <= offsetRowID && pointB[1] > offsetRowID) || (pointB[1] <= offsetRowID && pointA[1] > offsetRowID))
50
- intersections.push(pointA[0] + ((offsetRowID - pointA[1]) / (pointB[1] - pointA[1])) * (pointB[0] - pointA[0]))
49
+ if ((pointA[2] <= offsetRowID && pointB[2] > offsetRowID) || (pointB[2] <= offsetRowID && pointA[2] > offsetRowID)) {
50
+ const t = (offsetRowID - pointA[2]) / (pointB[2] - pointA[2])
51
+ intersections.push({
52
+ x: pointA[0] + t * (pointB[0] - pointA[0]),
53
+ y: pointA[1] + t * (pointB[1] - pointA[1])
54
+ })
55
+ }
51
56
  }
52
57
 
53
58
  if (intersections.length < 2) {
54
- // This row doesn't have a full pixel. Adjust the range.
55
- if (rowID <= range.min + 1) range.min = rowID + 1
56
- else if (rowID >= range.max - 1) range.max = rowID - 1
59
+ // This row doesn't have a full pixel. Adjust the z-range.
60
+ if (z <= zRange.min + 1) zRange.min = z + 1
61
+ else if (z >= zRange.max - 1) zRange.max = z - 1
57
62
  else throw new Error("Unexpected tri geometry found in mesh.")
58
63
  continue
59
64
  }
60
65
 
66
+ const [min, max] = intersections[0].x <= intersections[1].x
67
+ ? [intersections[0], intersections[1]]
68
+ : [intersections[1], intersections[0]]
69
+
70
+ min.x = Math.ceil(min.x)
71
+ max.x = Math.ceil(max.x) - 1
72
+
61
73
  /** @type {IMeshTriDataRow} */
62
74
  const row = {
63
- y: rowID,
64
- range: {
65
- min: Math.ceil(Math.min(...intersections)),
66
- max: Math.ceil(Math.max(...intersections)) - 1,
67
- },
68
- offset: triCardinality
75
+ z,
76
+ xyRange: { min, max },
77
+ offset: triCardinality,
78
+ cardinality: BigInt(max.x - min.x + 1)
69
79
  }
70
80
  rows.push(row)
71
- triCardinality += BigInt(row.range.max - row.range.min + 1)
81
+ triCardinality += row.cardinality
72
82
  }
73
83
 
74
84
  // Store the tri.
75
85
  mesh.triTable.push({
76
86
  points,
77
- range,
87
+ zRange,
78
88
  rows,
79
89
  offset: meshCardinality,
80
90
  cardinality: triCardinality
@@ -2,9 +2,8 @@ const { points, tris } = mesh.manifest
2
2
 
3
3
  const data = { collision: [[], []] }
4
4
 
5
- // TODO: add a y coordinate use a default 0 for 2D meshes.
6
5
  for (let index = 0; index < points.length; index += 2)
7
- data.collision[0].push([Math.round(points[index]), Math.round(points[index + 1])])
6
+ data.collision[0].push([Math.round(points[index]), 0, Math.round(points[index + 1])])
8
7
 
9
8
  for (let index = 0; index < tris.length; index += 3)
10
9
  data.collision[1].push([tris[index], tris[index + 1], tris[index + 2]])
@@ -1,14 +1,14 @@
1
- const roundedY = Math.floor(POINT.y)
1
+ const flooredZ = Math.floor(POINT.z)
2
2
  const triData = mesh.triTable[TRI_INDEX]
3
3
 
4
- if (roundedY < triData.range.min || roundedY > triData.range.max)
4
+ if (flooredZ < triData.zRange.min || flooredZ > triData.zRange.max)
5
5
  return false
6
6
 
7
- const row = triData.rows[roundedY - triData.range.min]
7
+ const row = triData.rows[flooredZ - triData.zRange.min]
8
8
 
9
9
  if (!row)
10
10
  return false
11
11
 
12
12
  const roundedX = Math.floor(POINT.x)
13
13
 
14
- return roundedX >= row.range.min && roundedX <= row.range.max
14
+ return roundedX >= row.xyRange.min.x && roundedX <= row.xyRange.max.x
@@ -1,259 +1,282 @@
1
1
  // Define a safe result: the current position before casting.
2
+ /** @type {IMeshRayCastResult} */
2
3
  const safeIterationResult = {
3
4
  hit: false,
4
5
  triIndex: mesh.triIndex,
5
- point: { ...mesh.position },
6
+ point: {
7
+ x: mesh.position.x,
8
+ y: 0, // Exclude y from ray cast calculations for now.
9
+ z: mesh.position.z
10
+ },
6
11
  forceVector: FORCE_VECTOR
7
12
  }
8
13
 
9
14
  // Obtain the speed of the force vector, which will be used later if we need to slide along the boundary.
10
15
  const speed = Vector.magnitude(FORCE_VECTOR)
11
16
 
12
- // If there's no motion, nothing will happen. Return the safe result.
13
- if (speed === 0)
14
- return safeIterationResult
17
+ // Only cast a ray if there's movement happening.
18
+ if (speed !== 0) {
15
19
 
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 }
20
+ // Initialize timing data as though the vector doesn't intersect any grid lines.
21
+ const timeOfNextIntersection = { x: Infinity, z: Infinity }
22
+ const timeBetweenIntersections = { ...timeOfNextIntersection }
19
23
 
20
- // Set the clock to zero.
21
- let time = 0
24
+ // Set the clock to zero.
25
+ let time = 0
22
26
 
23
- function initializeRayIntersectionSchedule(alsoComputeIntersectionInterval) {
27
+ function initializeRayIntersectionSchedule(alsoComputeIntersectionInterval) {
24
28
 
25
- // If the force vector isn't parallel to the x axis...
26
- if (FORCE_VECTOR.x !== 0) {
29
+ // If the force vector isn't parallel to the x axis...
30
+ if (FORCE_VECTOR.x !== 0) {
27
31
 
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)
32
+ // Determine the x coordinate of the first grid line the ray may intersect.
33
+ const nextGridX = Math.floor(safeIterationResult.point.x) + (FORCE_VECTOR.x > 0)
30
34
 
31
- // Compute the exact moment that intersection will happen.
32
- timeOfNextIntersection.x = time + (nextGridX - safeIterationResult.point.x) / FORCE_VECTOR.x
35
+ // Compute the exact moment that intersection will happen.
36
+ timeOfNextIntersection.x = time + (nextGridX - safeIterationResult.point.x) / FORCE_VECTOR.x
33
37
 
34
- // Compute the constant time between each grid line intersection.
35
- if (alsoComputeIntersectionInterval)
36
- timeBetweenIntersections.x = 1 / Math.abs(FORCE_VECTOR.x)
37
- }
38
+ // Compute the constant time between each grid line intersection.
39
+ if (alsoComputeIntersectionInterval)
40
+ timeBetweenIntersections.x = 1 / Math.abs(FORCE_VECTOR.x)
41
+ }
38
42
 
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)
43
+ // Perform the same steps for the z-axis.
44
+ if (FORCE_VECTOR.z !== 0) {
45
+ const nextGridZ = Math.floor(safeIterationResult.point.z) + (FORCE_VECTOR.z > 0)
46
+ timeOfNextIntersection.z = time + (nextGridZ - safeIterationResult.point.z) / FORCE_VECTOR.z
47
+ if (alsoComputeIntersectionInterval)
48
+ timeBetweenIntersections.z = 1 / Math.abs(FORCE_VECTOR.z)
49
+ }
45
50
  }
46
- }
47
51
 
48
- initializeRayIntersectionSchedule(true)
52
+ initializeRayIntersectionSchedule(true)
49
53
 
50
- const start = _.now
54
+ const start = _.now
51
55
 
52
- let iteration = 0
56
+ let iteration = 0
53
57
 
54
- // Start iterating on the ray cast.
55
- while (true) {
58
+ // Start iterating on the ray cast.
59
+ castRay: while (true) {
56
60
 
57
- iteration++
61
+ iteration++
58
62
 
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 }
63
+ // Emergency exit the loop.
64
+ if (_.now - start >= 2000) {
65
+ warn("Mesh ray cast calculation exceeded the maximum allowable processing time of 2 seconds.", { processingTime: _.now - start, iteration, DELTA_TIME, FORCE_VECTOR, speed, safeIterationResult, timeOfNextIntersection, timeBetweenIntersections })
66
+ break castRay
67
+ }
62
68
 
63
- // Pick the dimension whose grid intersection will happen next.
64
- const dimension = timeOfNextIntersection.x < timeOfNextIntersection.y ? "x" : "y"
69
+ // Pick the dimension whose grid intersection will happen next.
70
+ const dimension = timeOfNextIntersection.x < timeOfNextIntersection.z ? "x" : "z"
65
71
 
66
- // Note how much time must elapse to reach that intersection.
67
- let timeElapsedDuringThisIteration = timeOfNextIntersection[dimension] - time
72
+ // Note how much time must elapse to reach that intersection.
73
+ let timeElapsedDuringThisIteration = timeOfNextIntersection[dimension] - time
68
74
 
69
- // Set the clock to the moment of that intersection.
70
- time += timeElapsedDuringThisIteration
75
+ // Set the clock to the moment of that intersection.
76
+ time += timeElapsedDuringThisIteration
71
77
 
72
- // Note if this is a case where the grid intersection is reached after the time limit.
73
- let ranOutOfTime = time >= DELTA_TIME
78
+ // Note if this is a case where the grid intersection is reached after the time limit.
79
+ let ranOutOfTime = time >= DELTA_TIME
74
80
 
75
- // In that case...
76
- if (ranOutOfTime) {
81
+ // In that case...
82
+ if (ranOutOfTime) {
77
83
 
78
- // Measure how far we are past the time limit.
79
- const overshootTime = time - DELTA_TIME
84
+ // Measure how far we are past the time limit.
85
+ const overshootTime = time - DELTA_TIME
80
86
 
81
- // Adjust how much time we say elapsed.
82
- timeElapsedDuringThisIteration -= overshootTime
87
+ // Adjust how much time we say elapsed.
88
+ timeElapsedDuringThisIteration -= overshootTime
83
89
 
84
- // Set the block back to the last possible moment.
85
- time = DELTA_TIME
86
- }
90
+ // Set the block back to the last possible moment.
91
+ time = DELTA_TIME
92
+ }
87
93
 
88
- // Construct the next point along the ray, given the elapsed time.
89
- const point = Vector.add(safeIterationResult.point, Vector.multiply(FORCE_VECTOR, timeElapsedDuringThisIteration))
94
+ // Construct the next point along the ray, given the elapsed time.
95
+ const point = Vector.add(safeIterationResult.point, Vector.multiply(FORCE_VECTOR, timeElapsedDuringThisIteration))
90
96
 
91
- // Check if the point is outside the mesh boundary.
92
- const triIndex = mesh.triThatContainsPoint(point)
97
+ // Check if the point is outside the mesh boundary.
98
+ const triIndex = mesh.triThatContainsPoint(point)
93
99
 
94
- // If it is...
95
- if (triIndex === -1) {
100
+ // If it is...
101
+ if (triIndex === -1) {
96
102
 
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)
103
+ // We are at a boundary pixel. Let's move our position to exactly the center of our current point.
104
+ // safeIterationResult.point = Vector.add(Vector.floor(safeIterationResult.point), 0.5)
99
105
 
100
- // If we have permission to simulate sliding...
101
- if (ENABLE_SLIDING) {
106
+ // If we have permission to simulate sliding...
107
+ if (ENABLE_SLIDING) {
102
108
 
103
- // Walk back the clock to before the ray hit.
104
- time -= timeElapsedDuringThisIteration
109
+ // Walk back the clock to before the ray hit.
110
+ time -= timeElapsedDuringThisIteration
105
111
 
106
- const neighbor = {
107
- dot: -Infinity,
108
- point: null,
109
- triIndex: null,
110
- direction: {
111
- x: null,
112
- y: null
112
+ const neighbor = {
113
+ dot: -Infinity,
114
+ point: null,
115
+ triIndex: null,
116
+ direction: {
117
+ x: null,
118
+ z: null
119
+ }
113
120
  }
114
- }
115
121
 
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
122
+ let boundaryAppearsFlat = false
123
+
124
+ // Search the local neighborhood of points for one that we can safely move to.
125
+ for (let x = -1; x <= 1; x++) for (let z = -1; z <= 1; z++) {
126
+
127
+ // Exclude the point itself
128
+ if (!x && !z)
129
+ continue
130
+
131
+ const direction = { x, y: 0, z }
132
+
133
+ // Find out how much of the force vector is cancelled out by going to this neighbor.
134
+ const dot = Vector.dot(Vector.normalize(direction), Vector.normalize(FORCE_VECTOR))
135
+
136
+ // If it would require traveling "against" or perpendicular to the force vector, exclude it from consideration.
137
+ if (dot < 0)
138
+ continue
139
+
140
+ // Get the position of the center of the neighbor.
141
+ const point = Vector.add(Vector.floor(Vector.add(safeIterationResult.point, direction)), 0.5)
142
+ const triIndex = mesh.triThatContainsPoint(point)
143
+
144
+ // If it isn't part of the mesh, exclude it from consideration.
145
+ if (triIndex === -1)
146
+ continue
147
+
148
+ // Store the neighbor which most agrees with the force vector.
149
+ if (dot >= neighbor.dot) {
150
+ if (dot < 0.05 && neighbor.dot !== -Infinity && neighbor.dot < 0.05) {
151
+ boundaryAppearsFlat = true
152
+ // Boundary is "flat". Don't allow perpendicular motion.
153
+ neighbor.dot = -Infinity
154
+ neighbor.point = null
155
+ neighbor.triIndex = null
156
+ neighbor.direction.x = null
157
+ neighbor.direction.z = null
158
+ } else {
159
+ boundaryAppearsFlat = false
160
+ neighbor.dot = dot
161
+ neighbor.point = point
162
+ neighbor.triIndex = triIndex
163
+ neighbor.direction = direction
164
+ }
165
+ }
166
+ }
133
167
 
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)
168
+ if (boundaryAppearsFlat) {
169
+ // Do nothing for now...
170
+ }
137
171
 
138
- // If it isn't part of the mesh, exclude it from consideration.
139
- if (triIndex === -1)
140
- continue
172
+ // If there is no neighboring point, we are "stuck". The ray cast ends here.
173
+ if (!neighbor.point) {
141
174
 
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
- }
175
+ // Return the safe result data.
176
+ safeIterationResult.hit = true
177
+ break castRay
159
178
  }
160
- }
161
179
 
162
- if (boundaryAppearsFlat) {
163
- // Do nothing for now...
164
- }
180
+ // Compute travel distance to the center of the neighbor.
181
+ const vectorToNeighbor = Vector.subtract(neighbor.point, safeIterationResult.point)
182
+ const distance = Vector.magnitude(vectorToNeighbor)
165
183
 
166
- // If there is no neighboring point, we are "stuck". The ray cast ends here.
167
- if (!neighbor.point) {
184
+ // Construct a force vector pointing to the chosen neighbor.
185
+ const forceVectorToNeighbor = Vector.multiply(Vector.normalize(vectorToNeighbor), speed)
168
186
 
169
- // Return the safe result data.
170
- safeIterationResult.hit = true
171
- return safeIterationResult
172
- }
187
+ // Compute travel time to reach the neighbor.
188
+ timeElapsedDuringThisIteration = distance / speed
173
189
 
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)
190
+ // Move the clock ahead to our arrival at the neighbor.
191
+ time += timeElapsedDuringThisIteration
177
192
 
178
- // Construct a force vector pointing to the chosen neighbor.
179
- const forceVectorToNeighbor = Vector.multiply(Vector.normalize(vectorToNeighbor), speed)
193
+ // Check again if we ran out of time.
194
+ ranOutOfTime = time >= DELTA_TIME
195
+ if (ranOutOfTime) {
196
+ const overshootTime = time - DELTA_TIME
197
+ timeElapsedDuringThisIteration -= overshootTime
198
+ time = DELTA_TIME
180
199
 
181
- // Compute travel time to reach the neighbor.
182
- timeElapsedDuringThisIteration = distance / speed
200
+ // Construct the point along the way to the neighbor.
201
+ const point = Vector.add(safeIterationResult.point, Vector.multiply(forceVectorToNeighbor, timeElapsedDuringThisIteration))
202
+ const triIndex = mesh.triThatContainsPoint(point)
203
+ safeIterationResult.forceVector = forceVectorToNeighbor
183
204
 
184
- // Move the clock ahead to our arrival at the neighbor.
185
- time += timeElapsedDuringThisIteration
205
+ if (triIndex === -1) {
206
+ // We didn't have enough time to overcome the diagonal boundary.
186
207
 
187
- // Check again if we ran out of time.
188
- ranOutOfTime = time >= DELTA_TIME
189
- if (ranOutOfTime) {
190
- const overshootTime = time - DELTA_TIME
191
- timeElapsedDuringThisIteration -= overshootTime
192
- time = DELTA_TIME
208
+ if (speed * DELTA_TIME > Math.SQRT2) {
209
+ // The current speed is enough to overcome diagonal corners.
193
210
 
194
- // Construct the point along the way to the neighbor.
195
- const point = Vector.add(safeIterationResult.point, Vector.multiply(forceVectorToNeighbor, timeElapsedDuringThisIteration))
196
- const triIndex = mesh.triThatContainsPoint(point)
197
- safeIterationResult.forceVector = forceVectorToNeighbor
211
+ // Return the previous safe point, chalking the lost time up to friction.
212
+ break castRay
213
+ }
198
214
 
199
- if (triIndex === -1) {
200
- // We didn't have enough time to overcome the diagonal boundary.
215
+ // There is not enough velocity to overcome the diagonal. Use random chance.
216
+ if (Math.random() < Math.SQRT1_2) {
217
+ safeIterationResult.point = neighbor.point
218
+ safeIterationResult.triIndex = neighbor.triIndex
219
+ }
201
220
 
202
- if (speed * DELTA_TIME > Math.SQRT2) {
203
- // The current speed is enough to overcome diagonal corners.
204
-
205
- // Return the previous safe point, chalking the lost time up to friction.
206
- return safeIterationResult
207
- }
221
+ break castRay
222
+ } else {
208
223
 
209
- // There is not enough velocity to overcome the diagonal. Use random chance.
210
- if (Math.random() < Math.SQRT1_2) {
211
- safeIterationResult.point = neighbor.point
212
- safeIterationResult.triIndex = neighbor.triIndex
224
+ // Go to the partial point.
225
+ safeIterationResult.point = point
226
+ safeIterationResult.triIndex = triIndex
213
227
  }
214
228
 
215
- return safeIterationResult
216
- } else {
217
-
218
- // Go to the partial point.
219
- safeIterationResult.point = point
220
- safeIterationResult.triIndex = triIndex
229
+ break castRay
221
230
  }
222
231
 
223
- return safeIterationResult
224
- }
225
-
226
- // Set the neighbor as the new safe point.
227
- safeIterationResult.point = neighbor.point
228
- safeIterationResult.triIndex = neighbor.triIndex
229
- safeIterationResult.forceVector = FORCE_VECTOR
232
+ // Set the neighbor as the new safe point.
233
+ safeIterationResult.point = neighbor.point
234
+ safeIterationResult.triIndex = neighbor.triIndex
235
+ safeIterationResult.forceVector = FORCE_VECTOR
230
236
 
231
- // The normal ray intersection schedule has changed.
232
- initializeRayIntersectionSchedule()
237
+ // The normal ray intersection schedule has changed.
238
+ initializeRayIntersectionSchedule()
233
239
 
234
- // Proceed with normal ray casting behavior from this point.
235
- continue
240
+ // Proceed with normal ray casting behavior from this point.
241
+ continue
236
242
 
237
- } else {
243
+ } else {
238
244
 
239
- // Discard the unsafe point. Return the safe result data.
240
- safeIterationResult.hit = true
241
- return safeIterationResult
245
+ // Discard the unsafe point. Return the safe result data.
246
+ safeIterationResult.hit = true
247
+ break castRay
248
+ }
242
249
  }
243
- }
244
250
 
245
251
 
246
- // Otherwise, update the safe result.
247
- safeIterationResult.point = point
248
- safeIterationResult.triIndex = triIndex
252
+ // Otherwise, update the safe result.
253
+ safeIterationResult.point = point
254
+ safeIterationResult.triIndex = triIndex
249
255
 
250
- // If we ran out of time...
251
- if (ranOutOfTime) {
256
+ // If we ran out of time...
257
+ if (ranOutOfTime) {
252
258
 
253
- // We're done. Return the safe point.
254
- return safeIterationResult
259
+ // We're done. Return the safe point.
260
+ break castRay
261
+ }
262
+
263
+ // Otherwise, Prepare for the next grid line time of this dimension.
264
+ timeOfNextIntersection[dimension] += timeBetweenIntersections[dimension]
255
265
  }
266
+ }
256
267
 
257
- // Otherwise, Prepare for the next grid line time of this dimension.
258
- timeOfNextIntersection[dimension] += timeBetweenIntersections[dimension]
259
- }
268
+ // Interpolate the y position of the collision mesh at the given { x, y } coordinates.
269
+ const triData = mesh.triTable[safeIterationResult.triIndex]
270
+ const flooredZ = Math.floor(safeIterationResult.point.z)
271
+ const row = triData.rows[flooredZ - triData.zRange.min]
272
+ const t = (safeIterationResult.point.x - row.xyRange.min.x) / Number(row.cardinality)
273
+ safeIterationResult.point.y = row.xyRange.min.y + t * (row.xyRange.max.y - row.xyRange.min.y)
274
+
275
+ if (isNaN(safeIterationResult.point.y))
276
+ throw {
277
+ triData,
278
+ flooredZ,
279
+ row,
280
+ t
281
+ }
282
+ return safeIterationResult
@@ -35,11 +35,11 @@ for (const row of mesh.triTable[mesh.triIndex].rows) {
35
35
  if (!row)
36
36
  continue
37
37
 
38
- const rowWidth = BigInt(row.range.max - row.range.min + 1)
38
+ const rowWidth = BigInt(row.xyRange.max.x - row.xyRange.min.x + 1)
39
39
 
40
40
  if (ROUTE_ID < row.offset + rowWidth) {
41
- mesh.position.y = row.y
42
- mesh.position.x = row.range.min + Number(ROUTE_ID - row.offset)
41
+ mesh.position.x = row.xyRange.min.x + Number(ROUTE_ID - row.offset)
42
+ mesh.position.z = row.z
43
43
  break
44
44
  }
45
45
  }
@@ -1,4 +1,4 @@
1
1
  const triIndex = mesh.triThatContainsPoint(MODEL)
2
2
  const triData = mesh.triTable[triIndex]
3
- const row = triData.rows[Math.floor(MODEL.y) - triData.range.min]
4
- return triData.offset + row.offset + BigInt(Math.floor(MODEL.x) - row.range.min)
3
+ const row = triData.rows[Math.floor(MODEL.z) - triData.zRange.min]
4
+ return triData.offset + row.offset + BigInt(Math.floor(MODEL.x) - row.xyRange.min.x)
@@ -4,20 +4,11 @@ declare interface IMesh<TOwner>
4
4
  // Serialized Properties.
5
5
  readonly getData(): IMeshData
6
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
7
+ readonly castRay(FORCE_VECTOR: IVector3, DELTA_TIME: number, ENABLE_SLIDING: boolean): IMeshRayCastResult
8
+ /** Checks if a point (x, y, z) rounds to a valid pixel within the specified tri's memoized row data. */
9
+ readonly triContainsPoint(TRI_INDEX: IMeshTriIndex, POINT: IVector3): boolean
19
10
  /** 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
11
+ readonly triThatContainsPoint(POINT: IVector3): IMeshTriIndex
21
12
 
22
13
  // Runtime Properties.
23
14
  /** The memoization data of every tri in the mesh, stored by tri index at build-time. */
@@ -25,20 +16,31 @@ declare interface IMesh<TOwner>
25
16
  /** The index of the current tri in the mesh. */
26
17
  readonly triIndex?: IMeshTriIndex
27
18
  /** The current position of the state in the mesh. */
28
- readonly position: IVector2
19
+ readonly position: IVector3
29
20
  readonly manifest: IMeshManifest
30
21
  /** A cache of the pre-processed geometry data obtained by running the getData method. */
31
22
  readonly data: IMeshData
32
23
  }
33
24
 
25
+ declare interface IMeshRayCastResult {
26
+ /** Whether or not the ray hit the mesh boundary. */
27
+ readonly hit: boolean
28
+ /** 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. */
29
+ readonly triIndex: IMeshTriIndex
30
+ /** The rounded cell position where the cast ray landed. */
31
+ readonly point: IVector3
32
+ /** 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. */
33
+ readonly forceVector: IVector3
34
+ }
35
+
34
36
  declare interface IMeshTriData {
35
37
  /** The original point array of the tri as taken from `mesh.getData()`. */
36
38
  readonly points: IMeshTriPoints
37
- /** The y-axis range of the tri. */
38
- readonly range: {
39
- /** The y position of the top-most row of the tri. */
39
+ /** The z-axis range of the tri. */
40
+ readonly zRange: {
41
+ /** The z position of the top-most row of the tri. */
40
42
  readonly min: number
41
- /** The y position of the bottom-most row of the tri. */
43
+ /** The z position of the bottom-most row of the tri. */
42
44
  readonly max: number
43
45
  }
44
46
  /** The rows of pixels in the tri. */
@@ -50,17 +52,25 @@ declare interface IMeshTriData {
50
52
  }
51
53
 
52
54
  declare interface IMeshTriDataRow {
53
- /** The y position of this row of the tri's space. */
54
- readonly y: number
55
- /** The x-axis range of the row. */
56
- readonly range: {
55
+ /** The z position of this row of the tri's space. */
56
+ readonly z: number
57
+ /** The x-axis range of the row stored with the corresponding y-axis coordinate at those two points. */
58
+ readonly xyRange: {
57
59
  /** The smallest x position that is within the tri row. */
58
- readonly min: number
60
+ readonly min: {
61
+ readonly x: number,
62
+ readonly y: number
63
+ }
59
64
  /** The largest x position that is within the tri row. */
60
- readonly max: number
65
+ readonly max: {
66
+ readonly x: number,
67
+ readonly y: number
68
+ }
61
69
  }
62
70
  /** The bigint offset of this row in the overall tri. */
63
71
  readonly offset: bigint
72
+ /** The number of points in the given row. */
73
+ readonly cardinality: bigint
64
74
  }
65
75
 
66
76
 
@@ -86,7 +96,7 @@ declare type IMeshTriPoints =
86
96
  [IMeshPoint, IMeshPoint, IMeshPoint]
87
97
 
88
98
  declare type IMeshPoint =
89
- [x: number, y: number]
99
+ [x: number, y: number, z: number]
90
100
 
91
101
  declare type IMeshTriIndex =
92
102
  number
@@ -9,8 +9,6 @@ const expression = (() => {
9
9
 
10
10
  const needsParentheses = PARENTHESIZE && terms.length > 1
11
11
 
12
- debug(terms)
13
-
14
12
  // Allow "<mrow>" tag nesting for parentheses.
15
13
  return (LABELS ? "<munder>" + (needsParentheses ? "" : "<mrow>") : "") + (needsParentheses ? "<mrow class=parenthetic><mo>(</mo>" : "") + terms.join("") + (needsParentheses ? `<mo>)</mo></mrow>` : "") + (LABELS ? (needsParentheses ? "" : "</mrow>") + `<munder><mo stretchy="true">&#x23df;</mo>${recurse(0, "none", false, false)}</munder></munder>` : "")
16
14
  })()
@@ -1,6 +1,4 @@
1
1
  if (scroller.skipRouteIDUpdate || !client.hydrated) {
2
- if (!client.hydrated)
3
- debug('onscroll from within the hydration phase')
4
2
  scroller.skipRouteIDUpdate = false
5
3
  } else {
6
4
  const maxY = scroller.container.scrollHeight
@@ -0,0 +1,28 @@
1
+ if (!document.fullscreenElement) {
2
+
3
+ if (FORCE_STATE === false)
4
+ return FORCE_STATE
5
+
6
+ if (document.documentElement.requestFullscreen) {
7
+ document.documentElement.requestFullscreen()
8
+ } else if (document.documentElement.webkitRequestFullscreen) {
9
+ document.documentElement.webkitRequestFullscreen()
10
+ } else if (document.documentElement.msRequestFullscreen) {
11
+ document.documentElement.msRequestFullscreen()
12
+ }
13
+ return true
14
+ } else {
15
+
16
+ if (FORCE_STATE === true)
17
+ return FORCE_STATE
18
+
19
+ if (document.exitFullscreen) {
20
+ document.exitFullscreen()
21
+ } else if (document.webkitExitFullscreen) {
22
+ document.webkitExitFullscreen()
23
+ } else if (document.msExitFullscreen) {
24
+ document.msExitFullscreen()
25
+ }
26
+ }
27
+
28
+ return false
@@ -1,3 +1,8 @@
1
1
  {
2
- "extends": "facet"
2
+ "extends": "facet",
3
+ "methods": {
4
+ "fullscreen-toggle": [
5
+ "FORCE_STATE"
6
+ ]
7
+ }
3
8
  }
@@ -4,6 +4,10 @@ interface IAgent
4
4
  // Runtime Properties.
5
5
  readonly isMac: boolean
6
6
  readonly isSafari: boolean
7
+
8
+ // Serialized Properties.
9
+ /** Toggles the native fullscreen feature (if available) and returns the resulting state. If FORCE_STATE is provided, sets the fullscreen mode to the given state, instead of simply toggling it. */
10
+ readonly toggleFullscreen(FORCE_STATE?: boolean = undefined)
7
11
  }
8
12
 
9
13
  declare const agent: IAgent
@@ -0,0 +1 @@
1
+ const aboutEcosystem = this
@@ -0,0 +1 @@
1
+ return `<a ${_.application === aboutEcosystem ? "disabled" : _.pointAttr()} href=https://${aboutEcosystem.host}>About</a>`
@@ -0,0 +1,3 @@
1
+ #about_desktop_parts {
2
+ text-align: center;
3
+ }
@@ -7,15 +7,14 @@ const longestHost = appNames.reduce((a, b) => (a.length >= b.length ? a : b))
7
7
  const worstCaseURL = `https://${longestHost}/${_.version}/${encodeSegment(_.cardinality - 1n)}/`
8
8
  const maxURLLength = 2000
9
9
  const stateSizeInBits = toBits(_.cardinality - 1n, false)
10
+ const stateSizeInCharms = toCharms(_.cardinality - 1n, false)
10
11
 
11
12
  return /* html */`
12
13
  <h1>Demo Ecosystem</h1>
13
14
  ${_.parts.core.update["part.html"]}
14
- <ul>
15
- <li>Apps: ${appNames.length}</li>
16
- <li>JS Size: ${sizeInMB} MB (${sizeInKB} KB)</li>
17
- <li>JS Usage: ${sizeInMB / maxSizeInMB * 100}%</li>
18
- <li>State Size: ${stateSizeInBits} bits (${stateSizeInBits / 8000} KB)</li>
19
- <li>Worst URL: ${worstCaseURL.length} characters</li>
20
- <li>URL Usage: ${worstCaseURL.length / maxURLLength * 100}%</li>
21
- </ul>`
15
+ <hr>
16
+ <span>Installed Apps: ${appNames.length}</span>
17
+ <br>
18
+ <span>JS Usage: ${Math.round(sizeInMB / maxSizeInMB * 100)}% (${Math.round(sizeInMB * 100) / 100} MB / ${maxSizeInMB} MB)</span>
19
+ <br>
20
+ <span>URL Usage: ${Math.round(worstCaseURL.length / maxURLLength * 100)}% (${worstCaseURL.length} ch / ${maxURLLength} ch)</span>`
@@ -0,0 +1,6 @@
1
+ declare interface IAboutApplication
2
+ extends IApplication<IDesktop> {
3
+
4
+ }
5
+
6
+ declare const aboutEcosystem: IAboutApplication
@@ -33,6 +33,10 @@ title-bar>.part-icon {
33
33
  position: relative;
34
34
  }
35
35
 
36
+ .toggle-control:hover {
37
+ color: var(--accent);
38
+ }
39
+
36
40
  .toggle-control .base {
37
41
  width: 100%;
38
42
  border-radius: var(--spacing);
@@ -115,10 +119,6 @@ task-menu {
115
119
  width: auto;
116
120
  }
117
121
 
118
- task-menu [disabled] {
119
- display: none !important;
120
- }
121
-
122
122
  task-menu hr {
123
123
  display: none;
124
124
  }
@@ -148,6 +148,7 @@ task-menu>* {
148
148
  }
149
149
 
150
150
  #settings>span,
151
+ #settings>a,
151
152
  .toggle-control {
152
153
  gap: 1ch;
153
154
  height: var(--spacing);
@@ -172,7 +173,6 @@ task-menu>* {
172
173
  top: 2px;
173
174
  }
174
175
 
175
-
176
176
  .toggle-control[data-state="enabled"] .handle {
177
177
  left: calc(var(--spacing) + 2px);
178
178
  }
@@ -199,8 +199,13 @@ task-menu>* {
199
199
  color: var(--accent);
200
200
  }
201
201
 
202
+ #settings>a[disabled] {
203
+ color: var(--fg-mode-er);
204
+ cursor: default;
205
+ }
206
+
202
207
  .task-link:not([data-here]):hover,
203
- #update-control:hover {
208
+ #settings>a:not([disabled]):hover {
204
209
  color: var(--accent);
205
210
  cursor: pointer;
206
211
  }
@@ -1,6 +1,6 @@
1
1
  const menuApplicationsCount = Object.keys(_.menuApplications).length
2
- const controlLinesCount = 2
3
- const separatorCount = 2
2
+ const controlLinesCount = 3
3
+ const separatorCount = 1
4
4
  return part["static.css"] + `
5
5
  task-menu::after {
6
6
  content: "${_.application.fancyTitle}";
@@ -112,7 +112,7 @@ scroll-bar>thumb- {
112
112
  }
113
113
 
114
114
  .task-link>a:hover,
115
- #update-control:hover {
115
+ #settings>a:not([disabled]):hover {
116
116
  background: var(--accent);
117
117
  color: var(--bg-mode-est);
118
118
  }
@@ -255,13 +255,14 @@ hr {
255
255
  }
256
256
 
257
257
  [disabled],
258
- [disabled] * {
258
+ [disabled] *,
259
+ a[disabled] {
259
260
  pointer-events: none;
260
261
  color: var(--bg-dark-er);
261
262
  text-shadow: 1px 1px var(--bg-light-est);
262
263
  }
263
264
 
264
- body.dark :is([disabled], [disabled] *) {
265
+ body.dark :is([disabled], [disabled] *, a[disabled]) {
265
266
  color: var(--bg-dark-est);
266
267
  text-shadow: 1px 1px var(--bg-light-er);
267
268
  }
@@ -1,4 +1,5 @@
1
1
  const menu = this
2
2
  const update = _.parts.core.update
3
3
  const color = _.parts.desktop.color
4
- const era = _.parts.desktop.era
4
+ const era = _.parts.desktop.era
5
+ const aboutEcosystem = _.parts.desktop.about
@@ -3,7 +3,7 @@ const
3
3
  sections = []
4
4
 
5
5
  // if (_.includeUpdates === "full" || (!production && _.includeUpdates === "local-only"))
6
- // controls.push(update["part.html"])
6
+ controls.push(aboutEcosystem["menu-item.html"])
7
7
 
8
8
  if (_.includeColor === "full" || (!production && _.includeColor.startsWith("debug-")))
9
9
  controls.push(color["part.html"])
@@ -1,22 +1,6 @@
1
1
  pointer.handle({
2
2
  click() {
3
- if (!document.fullscreenElement) {
4
- if (document.documentElement.requestFullscreen) {
5
- document.documentElement.requestFullscreen()
6
- } else if (document.documentElement.webkitRequestFullscreen) {
7
- document.documentElement.webkitRequestFullscreen()
8
- } else if (document.documentElement.msRequestFullscreen) {
9
- document.documentElement.msRequestFullscreen()
10
- }
11
- } else {
12
- if (document.exitFullscreen) {
13
- document.exitFullscreen()
14
- } else if (document.webkitExitFullscreen) {
15
- document.webkitExitFullscreen()
16
- } else if (document.msExitFullscreen) {
17
- document.msExitFullscreen()
18
- }
19
- }
3
+ _.parts.core.agent.toggleFullScreen()
20
4
  },
21
5
  POINTER_EVENT,
22
6
  TARGET_ELEMENT,
package/src/static.css CHANGED
@@ -191,6 +191,7 @@ task-menu {
191
191
  }
192
192
 
193
193
  #settings>span,
194
+ #settings>a,
194
195
  .toggle-control {
195
196
  display: flex;
196
197
  justify-content: space-between;
@@ -198,6 +199,14 @@ task-menu {
198
199
  gap: 5px;
199
200
  }
200
201
 
202
+ #settings>a:not([disabled]) {
203
+ color: inherit;
204
+ }
205
+
206
+ #settings>a {
207
+ text-decoration: none;
208
+ }
209
+
201
210
  .task-link {
202
211
  display: contents;
203
212
  }
package/src/type.d.ts CHANGED
@@ -415,6 +415,7 @@ declare class Vector {
415
415
 
416
416
  declare type IVector = Record<string, number>
417
417
  declare type IVector2 = { x: number, y: number }
418
+ declare type IVector3 = { x: number, y: number, z: number }
418
419
 
419
420
  declare type KirejiConfigColor =
420
421
  /** The color component will never be included. */