@teachinglab/omd 0.6.0 → 0.6.2
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 +257 -251
- package/README.old.md +137 -137
- package/canvas/core/canvasConfig.js +202 -202
- package/canvas/drawing/segment.js +167 -167
- package/canvas/drawing/stroke.js +385 -385
- package/canvas/events/eventManager.js +444 -444
- package/canvas/events/pointerEventHandler.js +262 -262
- package/canvas/index.js +48 -48
- package/canvas/tools/PointerTool.js +71 -71
- package/canvas/tools/tool.js +222 -222
- package/canvas/utils/boundingBox.js +377 -377
- package/canvas/utils/mathUtils.js +258 -258
- package/docs/api/configuration-options.md +198 -198
- package/docs/api/eventManager.md +82 -82
- package/docs/api/focusFrameManager.md +144 -144
- package/docs/api/index.md +105 -105
- package/docs/api/main.md +62 -62
- package/docs/api/omdBinaryExpressionNode.md +86 -86
- package/docs/api/omdCanvas.md +83 -83
- package/docs/api/omdConfigManager.md +112 -112
- package/docs/api/omdConstantNode.md +52 -52
- package/docs/api/omdDisplay.md +87 -87
- package/docs/api/omdEquationNode.md +174 -174
- package/docs/api/omdEquationSequenceNode.md +258 -258
- package/docs/api/omdEquationStack.md +192 -192
- package/docs/api/omdFunctionNode.md +82 -82
- package/docs/api/omdGroupNode.md +78 -78
- package/docs/api/omdHelpers.md +87 -87
- package/docs/api/omdLeafNode.md +85 -85
- package/docs/api/omdNode.md +201 -201
- package/docs/api/omdOperationDisplayNode.md +117 -117
- package/docs/api/omdOperatorNode.md +91 -91
- package/docs/api/omdParenthesisNode.md +133 -133
- package/docs/api/omdPopup.md +191 -191
- package/docs/api/omdPowerNode.md +131 -131
- package/docs/api/omdRationalNode.md +144 -144
- package/docs/api/omdSequenceNode.md +128 -128
- package/docs/api/omdSimplification.md +78 -78
- package/docs/api/omdSqrtNode.md +144 -144
- package/docs/api/omdStepVisualizer.md +146 -146
- package/docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/docs/api/omdStepVisualizerLayout.md +70 -70
- package/docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/docs/api/omdToolbar.md +130 -130
- package/docs/api/omdTranscriptionService.md +95 -95
- package/docs/api/omdTreeDiff.md +169 -169
- package/docs/api/omdUnaryExpressionNode.md +137 -137
- package/docs/api/omdUtilities.md +82 -82
- package/docs/api/omdVariableNode.md +123 -123
- package/docs/api/selectTool.md +74 -74
- package/docs/api/simplificationEngine.md +97 -97
- package/docs/api/simplificationRules.md +76 -76
- package/docs/api/simplificationUtils.md +64 -64
- package/docs/api/transcribe.md +43 -43
- package/docs/api-reference.md +85 -85
- package/docs/index.html +453 -453
- package/docs/index.md +38 -38
- package/docs/omd-objects.md +258 -258
- package/index.js +79 -79
- package/jsvg/index.js +3 -0
- package/jsvg/jsvg.js +898 -898
- package/jsvg/jsvgComponents.js +357 -358
- package/npm-docs/DOCUMENTATION_SUMMARY.md +220 -220
- package/npm-docs/README.md +251 -251
- package/npm-docs/api/api-reference.md +85 -85
- package/npm-docs/api/configuration-options.md +198 -198
- package/npm-docs/api/eventManager.md +82 -82
- package/npm-docs/api/expression-nodes.md +561 -561
- package/npm-docs/api/focusFrameManager.md +144 -144
- package/npm-docs/api/index.md +105 -105
- package/npm-docs/api/main.md +62 -62
- package/npm-docs/api/omdBinaryExpressionNode.md +86 -86
- package/npm-docs/api/omdCanvas.md +83 -83
- package/npm-docs/api/omdConfigManager.md +112 -112
- package/npm-docs/api/omdConstantNode.md +52 -52
- package/npm-docs/api/omdDisplay.md +87 -87
- package/npm-docs/api/omdEquationNode.md +174 -174
- package/npm-docs/api/omdEquationSequenceNode.md +258 -258
- package/npm-docs/api/omdEquationStack.md +192 -192
- package/npm-docs/api/omdFunctionNode.md +82 -82
- package/npm-docs/api/omdGroupNode.md +78 -78
- package/npm-docs/api/omdHelpers.md +87 -87
- package/npm-docs/api/omdLeafNode.md +85 -85
- package/npm-docs/api/omdNode.md +201 -201
- package/npm-docs/api/omdOperationDisplayNode.md +117 -117
- package/npm-docs/api/omdOperatorNode.md +91 -91
- package/npm-docs/api/omdParenthesisNode.md +133 -133
- package/npm-docs/api/omdPopup.md +191 -191
- package/npm-docs/api/omdPowerNode.md +131 -131
- package/npm-docs/api/omdRationalNode.md +144 -144
- package/npm-docs/api/omdSequenceNode.md +128 -128
- package/npm-docs/api/omdSimplification.md +78 -78
- package/npm-docs/api/omdSqrtNode.md +144 -144
- package/npm-docs/api/omdStepVisualizer.md +146 -146
- package/npm-docs/api/omdStepVisualizerHighlighting.md +65 -65
- package/npm-docs/api/omdStepVisualizerInteractiveSteps.md +108 -108
- package/npm-docs/api/omdStepVisualizerLayout.md +70 -70
- package/npm-docs/api/omdStepVisualizerNodeUtils.md +140 -140
- package/npm-docs/api/omdStepVisualizerTextBoxes.md +76 -76
- package/npm-docs/api/omdToolbar.md +130 -130
- package/npm-docs/api/omdTranscriptionService.md +95 -95
- package/npm-docs/api/omdTreeDiff.md +169 -169
- package/npm-docs/api/omdUnaryExpressionNode.md +137 -137
- package/npm-docs/api/omdUtilities.md +82 -82
- package/npm-docs/api/omdVariableNode.md +123 -123
- package/npm-docs/api/selectTool.md +74 -74
- package/npm-docs/api/simplificationEngine.md +97 -97
- package/npm-docs/api/simplificationRules.md +76 -76
- package/npm-docs/api/simplificationUtils.md +64 -64
- package/npm-docs/api/transcribe.md +43 -43
- package/npm-docs/guides/equations.md +854 -854
- package/npm-docs/guides/factory-functions.md +354 -354
- package/npm-docs/guides/getting-started.md +318 -318
- package/npm-docs/guides/quick-examples.md +525 -525
- package/npm-docs/guides/visualizations.md +682 -682
- package/npm-docs/index.html +12 -0
- package/npm-docs/json-schemas.md +826 -826
- package/omd/config/omdConfigManager.js +279 -267
- package/omd/core/index.js +158 -158
- package/omd/core/omdEquationStack.js +546 -546
- package/omd/core/omdUtilities.js +113 -113
- package/omd/display/omdDisplay.js +969 -962
- package/omd/display/omdToolbar.js +501 -501
- package/omd/nodes/omdBinaryExpressionNode.js +459 -459
- package/omd/nodes/omdConstantNode.js +141 -141
- package/omd/nodes/omdEquationNode.js +1327 -1327
- package/omd/nodes/omdFunctionNode.js +351 -351
- package/omd/nodes/omdGroupNode.js +67 -67
- package/omd/nodes/omdLeafNode.js +76 -76
- package/omd/nodes/omdNode.js +556 -556
- package/omd/nodes/omdOperationDisplayNode.js +321 -321
- package/omd/nodes/omdOperatorNode.js +108 -108
- package/omd/nodes/omdParenthesisNode.js +292 -292
- package/omd/nodes/omdPowerNode.js +235 -235
- package/omd/nodes/omdRationalNode.js +295 -295
- package/omd/nodes/omdSqrtNode.js +307 -307
- package/omd/nodes/omdUnaryExpressionNode.js +227 -227
- package/omd/nodes/omdVariableNode.js +122 -122
- package/omd/simplification/omdSimplification.js +140 -140
- package/omd/simplification/omdSimplificationEngine.js +887 -887
- package/omd/simplification/package.json +5 -5
- package/omd/simplification/rules/binaryRules.js +1037 -1037
- package/omd/simplification/rules/functionRules.js +111 -111
- package/omd/simplification/rules/index.js +48 -48
- package/omd/simplification/rules/parenthesisRules.js +19 -19
- package/omd/simplification/rules/powerRules.js +143 -143
- package/omd/simplification/rules/rationalRules.js +725 -725
- package/omd/simplification/rules/sqrtRules.js +48 -48
- package/omd/simplification/rules/unaryRules.js +37 -37
- package/omd/simplification/simplificationRules.js +31 -31
- package/omd/simplification/simplificationUtils.js +1055 -1055
- package/omd/step-visualizer/omdStepVisualizer.js +947 -947
- package/omd/step-visualizer/omdStepVisualizerHighlighting.js +246 -246
- package/omd/step-visualizer/omdStepVisualizerLayout.js +892 -892
- package/omd/step-visualizer/omdStepVisualizerTextBoxes.js +200 -200
- package/omd/utils/aiNextEquationStep.js +106 -106
- package/omd/utils/omdNodeOverlay.js +638 -638
- package/omd/utils/omdPopup.js +1203 -1203
- package/omd/utils/omdStepVisualizerInteractiveSteps.js +684 -684
- package/omd/utils/omdStepVisualizerNodeUtils.js +267 -267
- package/omd/utils/omdTranscriptionService.js +123 -123
- package/omd/utils/omdTreeDiff.js +733 -733
- package/package.json +59 -56
- package/readme.html +184 -120
- package/src/index.js +74 -74
- package/src/json-schemas.md +576 -576
- package/src/omd-json-samples.js +147 -147
- package/src/omdApp.js +391 -391
- package/src/omdAppCanvas.js +335 -335
- package/src/omdBalanceHanger.js +199 -199
- package/src/omdColor.js +13 -13
- package/src/omdCoordinatePlane.js +541 -541
- package/src/omdExpression.js +115 -115
- package/src/omdFactory.js +150 -150
- package/src/omdFunction.js +114 -114
- package/src/omdMetaExpression.js +290 -290
- package/src/omdNaturalExpression.js +563 -563
- package/src/omdNode.js +383 -383
- package/src/omdNumber.js +52 -52
- package/src/omdNumberLine.js +114 -112
- package/src/omdNumberTile.js +118 -118
- package/src/omdOperator.js +72 -72
- package/src/omdPowerExpression.js +91 -91
- package/src/omdProblem.js +259 -259
- package/src/omdRatioChart.js +251 -251
- package/src/omdRationalExpression.js +114 -114
- package/src/omdSampleData.js +215 -215
- package/src/omdShapes.js +512 -512
- package/src/omdSpinner.js +151 -151
- package/src/omdString.js +49 -49
- package/src/omdTable.js +498 -498
- package/src/omdTapeDiagram.js +244 -244
- package/src/omdTerm.js +91 -91
- package/src/omdTileEquation.js +349 -349
- package/src/omdUtils.js +84 -84
- package/src/omdVariable.js +51 -51
|
@@ -1,378 +1,378 @@
|
|
|
1
|
-
export class BoundingBox {
|
|
2
|
-
/**
|
|
3
|
-
* @param {number} [x=0] - X coordinate
|
|
4
|
-
* @param {number} [y=0] - Y coordinate
|
|
5
|
-
* @param {number} [width=0] - Width
|
|
6
|
-
* @param {number} [height=0] - Height
|
|
7
|
-
*/
|
|
8
|
-
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
9
|
-
this.x = x;
|
|
10
|
-
this.y = y;
|
|
11
|
-
this.width = width;
|
|
12
|
-
this.height = height;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Set bounding box values
|
|
17
|
-
* @param {number} x - X coordinate
|
|
18
|
-
* @param {number} y - Y coordinate
|
|
19
|
-
* @param {number} width - Width
|
|
20
|
-
* @param {number} height - Height
|
|
21
|
-
*/
|
|
22
|
-
set(x, y, width, height) {
|
|
23
|
-
this.x = x;
|
|
24
|
-
this.y = y;
|
|
25
|
-
this.width = width;
|
|
26
|
-
this.height = height;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Get left edge
|
|
31
|
-
* @returns {number} Left X coordinate
|
|
32
|
-
*/
|
|
33
|
-
get left() {
|
|
34
|
-
return this.x;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get right edge
|
|
39
|
-
* @returns {number} Right X coordinate
|
|
40
|
-
*/
|
|
41
|
-
get right() {
|
|
42
|
-
return this.x + this.width;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get top edge
|
|
47
|
-
* @returns {number} Top Y coordinate
|
|
48
|
-
*/
|
|
49
|
-
get top() {
|
|
50
|
-
return this.y;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get bottom edge
|
|
55
|
-
* @returns {number} Bottom Y coordinate
|
|
56
|
-
*/
|
|
57
|
-
get bottom() {
|
|
58
|
-
return this.y + this.height;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get center X coordinate
|
|
63
|
-
* @returns {number} Center X
|
|
64
|
-
*/
|
|
65
|
-
get centerX() {
|
|
66
|
-
return this.x + this.width / 2;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get center Y coordinate
|
|
71
|
-
* @returns {number} Center Y
|
|
72
|
-
*/
|
|
73
|
-
get centerY() {
|
|
74
|
-
return this.y + this.height / 2;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get center point
|
|
79
|
-
* @returns {Object} {x, y} center coordinates
|
|
80
|
-
*/
|
|
81
|
-
get center() {
|
|
82
|
-
return {
|
|
83
|
-
x: this.centerX,
|
|
84
|
-
y: this.centerY
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Check if point is inside bounding box
|
|
90
|
-
* @param {number} x - X coordinate
|
|
91
|
-
* @param {number} y - Y coordinate
|
|
92
|
-
* @param {number} [tolerance=0] - Tolerance for edge cases
|
|
93
|
-
* @returns {boolean} True if point is inside
|
|
94
|
-
*/
|
|
95
|
-
containsPoint(x, y, tolerance = 0) {
|
|
96
|
-
return x >= this.left - tolerance &&
|
|
97
|
-
x <= this.right + tolerance &&
|
|
98
|
-
y >= this.top - tolerance &&
|
|
99
|
-
y <= this.bottom + tolerance;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Check if this bounding box intersects with another
|
|
104
|
-
* @param {BoundingBox} other - Other bounding box
|
|
105
|
-
* @returns {boolean} True if boxes intersect
|
|
106
|
-
*/
|
|
107
|
-
intersects(other) {
|
|
108
|
-
return !(this.right < other.left ||
|
|
109
|
-
other.right < this.left ||
|
|
110
|
-
this.bottom < other.top ||
|
|
111
|
-
other.bottom < this.top);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Check if this bounding box completely contains another
|
|
116
|
-
* @param {BoundingBox} other - Other bounding box
|
|
117
|
-
* @returns {boolean} True if this box contains the other
|
|
118
|
-
*/
|
|
119
|
-
contains(other) {
|
|
120
|
-
return this.left <= other.left &&
|
|
121
|
-
this.right >= other.right &&
|
|
122
|
-
this.top <= other.top &&
|
|
123
|
-
this.bottom >= other.bottom;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Expand bounding box to include a point
|
|
128
|
-
* @param {number} x - X coordinate
|
|
129
|
-
* @param {number} y - Y coordinate
|
|
130
|
-
*/
|
|
131
|
-
expandToIncludePoint(x, y) {
|
|
132
|
-
if (this.width === 0 && this.height === 0) {
|
|
133
|
-
// First point
|
|
134
|
-
this.x = x;
|
|
135
|
-
this.y = y;
|
|
136
|
-
this.width = 0;
|
|
137
|
-
this.height = 0;
|
|
138
|
-
} else {
|
|
139
|
-
const newLeft = Math.min(this.left, x);
|
|
140
|
-
const newTop = Math.min(this.top, y);
|
|
141
|
-
const newRight = Math.max(this.right, x);
|
|
142
|
-
const newBottom = Math.max(this.bottom, y);
|
|
143
|
-
|
|
144
|
-
this.x = newLeft;
|
|
145
|
-
this.y = newTop;
|
|
146
|
-
this.width = newRight - newLeft;
|
|
147
|
-
this.height = newBottom - newTop;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Expand bounding box to include another bounding box
|
|
153
|
-
* @param {BoundingBox} other - Other bounding box
|
|
154
|
-
*/
|
|
155
|
-
expandToIncludeBox(other) {
|
|
156
|
-
if (other.width === 0 && other.height === 0) return;
|
|
157
|
-
|
|
158
|
-
if (this.width === 0 && this.height === 0) {
|
|
159
|
-
this.set(other.x, other.y, other.width, other.height);
|
|
160
|
-
} else {
|
|
161
|
-
const newLeft = Math.min(this.left, other.left);
|
|
162
|
-
const newTop = Math.min(this.top, other.top);
|
|
163
|
-
const newRight = Math.max(this.right, other.right);
|
|
164
|
-
const newBottom = Math.max(this.bottom, other.bottom);
|
|
165
|
-
|
|
166
|
-
this.x = newLeft;
|
|
167
|
-
this.y = newTop;
|
|
168
|
-
this.width = newRight - newLeft;
|
|
169
|
-
this.height = newBottom - newTop;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Get intersection with another bounding box
|
|
175
|
-
* @param {BoundingBox} other - Other bounding box
|
|
176
|
-
* @returns {BoundingBox|null} Intersection box or null if no intersection
|
|
177
|
-
*/
|
|
178
|
-
getIntersection(other) {
|
|
179
|
-
if (!this.intersects(other)) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const left = Math.max(this.left, other.left);
|
|
184
|
-
const top = Math.max(this.top, other.top);
|
|
185
|
-
const right = Math.min(this.right, other.right);
|
|
186
|
-
const bottom = Math.min(this.bottom, other.bottom);
|
|
187
|
-
|
|
188
|
-
return new BoundingBox(left, top, right - left, bottom - top);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Get union with another bounding box
|
|
193
|
-
* @param {BoundingBox} other - Other bounding box
|
|
194
|
-
* @returns {BoundingBox} Union bounding box
|
|
195
|
-
*/
|
|
196
|
-
getUnion(other) {
|
|
197
|
-
if (this.width === 0 && this.height === 0) {
|
|
198
|
-
return other.clone();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (other.width === 0 && other.height === 0) {
|
|
202
|
-
return this.clone();
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const left = Math.min(this.left, other.left);
|
|
206
|
-
const top = Math.min(this.top, other.top);
|
|
207
|
-
const right = Math.max(this.right, other.right);
|
|
208
|
-
const bottom = Math.max(this.bottom, other.bottom);
|
|
209
|
-
|
|
210
|
-
return new BoundingBox(left, top, right - left, bottom - top);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Inflate (expand) bounding box by amount
|
|
215
|
-
* @param {number} amount - Amount to inflate (positive to expand, negative to shrink)
|
|
216
|
-
*/
|
|
217
|
-
inflate(amount) {
|
|
218
|
-
this.x -= amount;
|
|
219
|
-
this.y -= amount;
|
|
220
|
-
this.width += amount * 2;
|
|
221
|
-
this.height += amount * 2;
|
|
222
|
-
|
|
223
|
-
// Ensure width/height don't go negative
|
|
224
|
-
this.width = Math.max(0, this.width);
|
|
225
|
-
this.height = Math.max(0, this.height);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Move bounding box by offset
|
|
230
|
-
* @param {number} dx - X offset
|
|
231
|
-
* @param {number} dy - Y offset
|
|
232
|
-
*/
|
|
233
|
-
move(dx, dy) {
|
|
234
|
-
this.x += dx;
|
|
235
|
-
this.y += dy;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Scale bounding box by factor
|
|
240
|
-
* @param {number} scale - Scale factor
|
|
241
|
-
* @param {number} [originX] - Scale origin X (defaults to center)
|
|
242
|
-
* @param {number} [originY] - Scale origin Y (defaults to center)
|
|
243
|
-
*/
|
|
244
|
-
scale(scale, originX = this.centerX, originY = this.centerY) {
|
|
245
|
-
const newWidth = this.width * scale;
|
|
246
|
-
const newHeight = this.height * scale;
|
|
247
|
-
|
|
248
|
-
this.x = originX - (originX - this.x) * scale;
|
|
249
|
-
this.y = originY - (originY - this.y) * scale;
|
|
250
|
-
this.width = newWidth;
|
|
251
|
-
this.height = newHeight;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Get area of bounding box
|
|
256
|
-
* @returns {number} Area
|
|
257
|
-
*/
|
|
258
|
-
getArea() {
|
|
259
|
-
return this.width * this.height;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Get perimeter of bounding box
|
|
264
|
-
* @returns {number} Perimeter
|
|
265
|
-
*/
|
|
266
|
-
getPerimeter() {
|
|
267
|
-
return 2 * (this.width + this.height);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Check if bounding box is empty (zero area)
|
|
272
|
-
* @returns {boolean} True if empty
|
|
273
|
-
*/
|
|
274
|
-
isEmpty() {
|
|
275
|
-
return this.width <= 0 || this.height <= 0;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Check if bounding box is valid
|
|
280
|
-
* @returns {boolean} True if valid
|
|
281
|
-
*/
|
|
282
|
-
isValid() {
|
|
283
|
-
return !isNaN(this.x) && !isNaN(this.y) &&
|
|
284
|
-
!isNaN(this.width) && !isNaN(this.height) &&
|
|
285
|
-
this.width >= 0 && this.height >= 0;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Calculate distance from point to bounding box
|
|
290
|
-
* @param {number} x - X coordinate
|
|
291
|
-
* @param {number} y - Y coordinate
|
|
292
|
-
* @returns {number} Distance (0 if point is inside)
|
|
293
|
-
*/
|
|
294
|
-
distanceToPoint(x, y) {
|
|
295
|
-
if (this.containsPoint(x, y)) {
|
|
296
|
-
return 0;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const dx = Math.max(0, Math.max(this.left - x, x - this.right));
|
|
300
|
-
const dy = Math.max(0, Math.max(this.top - y, y - this.bottom));
|
|
301
|
-
|
|
302
|
-
return Math.sqrt(dx * dx + dy * dy);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Get corners of bounding box
|
|
307
|
-
* @returns {Array<Object>} Array of {x, y} corner points
|
|
308
|
-
*/
|
|
309
|
-
getCorners() {
|
|
310
|
-
return [
|
|
311
|
-
{ x: this.left, y: this.top }, // Top-left
|
|
312
|
-
{ x: this.right, y: this.top }, // Top-right
|
|
313
|
-
{ x: this.right, y: this.bottom }, // Bottom-right
|
|
314
|
-
{ x: this.left, y: this.bottom } // Bottom-left
|
|
315
|
-
];
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Create a copy of this bounding box
|
|
320
|
-
* @returns {BoundingBox} New bounding box instance
|
|
321
|
-
*/
|
|
322
|
-
clone() {
|
|
323
|
-
return new BoundingBox(this.x, this.y, this.width, this.height);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Get string representation
|
|
328
|
-
* @returns {string} String representation
|
|
329
|
-
*/
|
|
330
|
-
toString() {
|
|
331
|
-
return `BoundingBox(${this.x}, ${this.y}, ${this.width}, ${this.height})`;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Convert to JSON object
|
|
336
|
-
* @returns {Object} JSON representation
|
|
337
|
-
*/
|
|
338
|
-
toJSON() {
|
|
339
|
-
return {
|
|
340
|
-
x: this.x,
|
|
341
|
-
y: this.y,
|
|
342
|
-
width: this.width,
|
|
343
|
-
height: this.height
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Create bounding box from JSON object
|
|
349
|
-
* @param {Object} json - JSON representation
|
|
350
|
-
* @returns {BoundingBox} New bounding box instance
|
|
351
|
-
*/
|
|
352
|
-
static fromJSON(json) {
|
|
353
|
-
return new BoundingBox(json.x, json.y, json.width, json.height);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Create bounding box from array of points
|
|
358
|
-
* @param {Array<Object>} points - Array of {x, y} points
|
|
359
|
-
* @returns {BoundingBox} Bounding box containing all points
|
|
360
|
-
*/
|
|
361
|
-
static fromPoints(points) {
|
|
362
|
-
if (points.length === 0) {
|
|
363
|
-
return new BoundingBox();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
let minX = Infinity, minY = Infinity;
|
|
367
|
-
let maxX = -Infinity, maxY = -Infinity;
|
|
368
|
-
|
|
369
|
-
points.forEach(point => {
|
|
370
|
-
minX = Math.min(minX, point.x);
|
|
371
|
-
minY = Math.min(minY, point.y);
|
|
372
|
-
maxX = Math.max(maxX, point.x);
|
|
373
|
-
maxY = Math.max(maxY, point.y);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
return new BoundingBox(minX, minY, maxX - minX, maxY - minY);
|
|
377
|
-
}
|
|
1
|
+
export class BoundingBox {
|
|
2
|
+
/**
|
|
3
|
+
* @param {number} [x=0] - X coordinate
|
|
4
|
+
* @param {number} [y=0] - Y coordinate
|
|
5
|
+
* @param {number} [width=0] - Width
|
|
6
|
+
* @param {number} [height=0] - Height
|
|
7
|
+
*/
|
|
8
|
+
constructor(x = 0, y = 0, width = 0, height = 0) {
|
|
9
|
+
this.x = x;
|
|
10
|
+
this.y = y;
|
|
11
|
+
this.width = width;
|
|
12
|
+
this.height = height;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Set bounding box values
|
|
17
|
+
* @param {number} x - X coordinate
|
|
18
|
+
* @param {number} y - Y coordinate
|
|
19
|
+
* @param {number} width - Width
|
|
20
|
+
* @param {number} height - Height
|
|
21
|
+
*/
|
|
22
|
+
set(x, y, width, height) {
|
|
23
|
+
this.x = x;
|
|
24
|
+
this.y = y;
|
|
25
|
+
this.width = width;
|
|
26
|
+
this.height = height;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get left edge
|
|
31
|
+
* @returns {number} Left X coordinate
|
|
32
|
+
*/
|
|
33
|
+
get left() {
|
|
34
|
+
return this.x;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get right edge
|
|
39
|
+
* @returns {number} Right X coordinate
|
|
40
|
+
*/
|
|
41
|
+
get right() {
|
|
42
|
+
return this.x + this.width;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get top edge
|
|
47
|
+
* @returns {number} Top Y coordinate
|
|
48
|
+
*/
|
|
49
|
+
get top() {
|
|
50
|
+
return this.y;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get bottom edge
|
|
55
|
+
* @returns {number} Bottom Y coordinate
|
|
56
|
+
*/
|
|
57
|
+
get bottom() {
|
|
58
|
+
return this.y + this.height;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get center X coordinate
|
|
63
|
+
* @returns {number} Center X
|
|
64
|
+
*/
|
|
65
|
+
get centerX() {
|
|
66
|
+
return this.x + this.width / 2;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get center Y coordinate
|
|
71
|
+
* @returns {number} Center Y
|
|
72
|
+
*/
|
|
73
|
+
get centerY() {
|
|
74
|
+
return this.y + this.height / 2;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get center point
|
|
79
|
+
* @returns {Object} {x, y} center coordinates
|
|
80
|
+
*/
|
|
81
|
+
get center() {
|
|
82
|
+
return {
|
|
83
|
+
x: this.centerX,
|
|
84
|
+
y: this.centerY
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if point is inside bounding box
|
|
90
|
+
* @param {number} x - X coordinate
|
|
91
|
+
* @param {number} y - Y coordinate
|
|
92
|
+
* @param {number} [tolerance=0] - Tolerance for edge cases
|
|
93
|
+
* @returns {boolean} True if point is inside
|
|
94
|
+
*/
|
|
95
|
+
containsPoint(x, y, tolerance = 0) {
|
|
96
|
+
return x >= this.left - tolerance &&
|
|
97
|
+
x <= this.right + tolerance &&
|
|
98
|
+
y >= this.top - tolerance &&
|
|
99
|
+
y <= this.bottom + tolerance;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if this bounding box intersects with another
|
|
104
|
+
* @param {BoundingBox} other - Other bounding box
|
|
105
|
+
* @returns {boolean} True if boxes intersect
|
|
106
|
+
*/
|
|
107
|
+
intersects(other) {
|
|
108
|
+
return !(this.right < other.left ||
|
|
109
|
+
other.right < this.left ||
|
|
110
|
+
this.bottom < other.top ||
|
|
111
|
+
other.bottom < this.top);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if this bounding box completely contains another
|
|
116
|
+
* @param {BoundingBox} other - Other bounding box
|
|
117
|
+
* @returns {boolean} True if this box contains the other
|
|
118
|
+
*/
|
|
119
|
+
contains(other) {
|
|
120
|
+
return this.left <= other.left &&
|
|
121
|
+
this.right >= other.right &&
|
|
122
|
+
this.top <= other.top &&
|
|
123
|
+
this.bottom >= other.bottom;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Expand bounding box to include a point
|
|
128
|
+
* @param {number} x - X coordinate
|
|
129
|
+
* @param {number} y - Y coordinate
|
|
130
|
+
*/
|
|
131
|
+
expandToIncludePoint(x, y) {
|
|
132
|
+
if (this.width === 0 && this.height === 0) {
|
|
133
|
+
// First point
|
|
134
|
+
this.x = x;
|
|
135
|
+
this.y = y;
|
|
136
|
+
this.width = 0;
|
|
137
|
+
this.height = 0;
|
|
138
|
+
} else {
|
|
139
|
+
const newLeft = Math.min(this.left, x);
|
|
140
|
+
const newTop = Math.min(this.top, y);
|
|
141
|
+
const newRight = Math.max(this.right, x);
|
|
142
|
+
const newBottom = Math.max(this.bottom, y);
|
|
143
|
+
|
|
144
|
+
this.x = newLeft;
|
|
145
|
+
this.y = newTop;
|
|
146
|
+
this.width = newRight - newLeft;
|
|
147
|
+
this.height = newBottom - newTop;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Expand bounding box to include another bounding box
|
|
153
|
+
* @param {BoundingBox} other - Other bounding box
|
|
154
|
+
*/
|
|
155
|
+
expandToIncludeBox(other) {
|
|
156
|
+
if (other.width === 0 && other.height === 0) return;
|
|
157
|
+
|
|
158
|
+
if (this.width === 0 && this.height === 0) {
|
|
159
|
+
this.set(other.x, other.y, other.width, other.height);
|
|
160
|
+
} else {
|
|
161
|
+
const newLeft = Math.min(this.left, other.left);
|
|
162
|
+
const newTop = Math.min(this.top, other.top);
|
|
163
|
+
const newRight = Math.max(this.right, other.right);
|
|
164
|
+
const newBottom = Math.max(this.bottom, other.bottom);
|
|
165
|
+
|
|
166
|
+
this.x = newLeft;
|
|
167
|
+
this.y = newTop;
|
|
168
|
+
this.width = newRight - newLeft;
|
|
169
|
+
this.height = newBottom - newTop;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get intersection with another bounding box
|
|
175
|
+
* @param {BoundingBox} other - Other bounding box
|
|
176
|
+
* @returns {BoundingBox|null} Intersection box or null if no intersection
|
|
177
|
+
*/
|
|
178
|
+
getIntersection(other) {
|
|
179
|
+
if (!this.intersects(other)) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const left = Math.max(this.left, other.left);
|
|
184
|
+
const top = Math.max(this.top, other.top);
|
|
185
|
+
const right = Math.min(this.right, other.right);
|
|
186
|
+
const bottom = Math.min(this.bottom, other.bottom);
|
|
187
|
+
|
|
188
|
+
return new BoundingBox(left, top, right - left, bottom - top);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get union with another bounding box
|
|
193
|
+
* @param {BoundingBox} other - Other bounding box
|
|
194
|
+
* @returns {BoundingBox} Union bounding box
|
|
195
|
+
*/
|
|
196
|
+
getUnion(other) {
|
|
197
|
+
if (this.width === 0 && this.height === 0) {
|
|
198
|
+
return other.clone();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (other.width === 0 && other.height === 0) {
|
|
202
|
+
return this.clone();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const left = Math.min(this.left, other.left);
|
|
206
|
+
const top = Math.min(this.top, other.top);
|
|
207
|
+
const right = Math.max(this.right, other.right);
|
|
208
|
+
const bottom = Math.max(this.bottom, other.bottom);
|
|
209
|
+
|
|
210
|
+
return new BoundingBox(left, top, right - left, bottom - top);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Inflate (expand) bounding box by amount
|
|
215
|
+
* @param {number} amount - Amount to inflate (positive to expand, negative to shrink)
|
|
216
|
+
*/
|
|
217
|
+
inflate(amount) {
|
|
218
|
+
this.x -= amount;
|
|
219
|
+
this.y -= amount;
|
|
220
|
+
this.width += amount * 2;
|
|
221
|
+
this.height += amount * 2;
|
|
222
|
+
|
|
223
|
+
// Ensure width/height don't go negative
|
|
224
|
+
this.width = Math.max(0, this.width);
|
|
225
|
+
this.height = Math.max(0, this.height);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Move bounding box by offset
|
|
230
|
+
* @param {number} dx - X offset
|
|
231
|
+
* @param {number} dy - Y offset
|
|
232
|
+
*/
|
|
233
|
+
move(dx, dy) {
|
|
234
|
+
this.x += dx;
|
|
235
|
+
this.y += dy;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Scale bounding box by factor
|
|
240
|
+
* @param {number} scale - Scale factor
|
|
241
|
+
* @param {number} [originX] - Scale origin X (defaults to center)
|
|
242
|
+
* @param {number} [originY] - Scale origin Y (defaults to center)
|
|
243
|
+
*/
|
|
244
|
+
scale(scale, originX = this.centerX, originY = this.centerY) {
|
|
245
|
+
const newWidth = this.width * scale;
|
|
246
|
+
const newHeight = this.height * scale;
|
|
247
|
+
|
|
248
|
+
this.x = originX - (originX - this.x) * scale;
|
|
249
|
+
this.y = originY - (originY - this.y) * scale;
|
|
250
|
+
this.width = newWidth;
|
|
251
|
+
this.height = newHeight;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get area of bounding box
|
|
256
|
+
* @returns {number} Area
|
|
257
|
+
*/
|
|
258
|
+
getArea() {
|
|
259
|
+
return this.width * this.height;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get perimeter of bounding box
|
|
264
|
+
* @returns {number} Perimeter
|
|
265
|
+
*/
|
|
266
|
+
getPerimeter() {
|
|
267
|
+
return 2 * (this.width + this.height);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check if bounding box is empty (zero area)
|
|
272
|
+
* @returns {boolean} True if empty
|
|
273
|
+
*/
|
|
274
|
+
isEmpty() {
|
|
275
|
+
return this.width <= 0 || this.height <= 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Check if bounding box is valid
|
|
280
|
+
* @returns {boolean} True if valid
|
|
281
|
+
*/
|
|
282
|
+
isValid() {
|
|
283
|
+
return !isNaN(this.x) && !isNaN(this.y) &&
|
|
284
|
+
!isNaN(this.width) && !isNaN(this.height) &&
|
|
285
|
+
this.width >= 0 && this.height >= 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Calculate distance from point to bounding box
|
|
290
|
+
* @param {number} x - X coordinate
|
|
291
|
+
* @param {number} y - Y coordinate
|
|
292
|
+
* @returns {number} Distance (0 if point is inside)
|
|
293
|
+
*/
|
|
294
|
+
distanceToPoint(x, y) {
|
|
295
|
+
if (this.containsPoint(x, y)) {
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const dx = Math.max(0, Math.max(this.left - x, x - this.right));
|
|
300
|
+
const dy = Math.max(0, Math.max(this.top - y, y - this.bottom));
|
|
301
|
+
|
|
302
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get corners of bounding box
|
|
307
|
+
* @returns {Array<Object>} Array of {x, y} corner points
|
|
308
|
+
*/
|
|
309
|
+
getCorners() {
|
|
310
|
+
return [
|
|
311
|
+
{ x: this.left, y: this.top }, // Top-left
|
|
312
|
+
{ x: this.right, y: this.top }, // Top-right
|
|
313
|
+
{ x: this.right, y: this.bottom }, // Bottom-right
|
|
314
|
+
{ x: this.left, y: this.bottom } // Bottom-left
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Create a copy of this bounding box
|
|
320
|
+
* @returns {BoundingBox} New bounding box instance
|
|
321
|
+
*/
|
|
322
|
+
clone() {
|
|
323
|
+
return new BoundingBox(this.x, this.y, this.width, this.height);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get string representation
|
|
328
|
+
* @returns {string} String representation
|
|
329
|
+
*/
|
|
330
|
+
toString() {
|
|
331
|
+
return `BoundingBox(${this.x}, ${this.y}, ${this.width}, ${this.height})`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Convert to JSON object
|
|
336
|
+
* @returns {Object} JSON representation
|
|
337
|
+
*/
|
|
338
|
+
toJSON() {
|
|
339
|
+
return {
|
|
340
|
+
x: this.x,
|
|
341
|
+
y: this.y,
|
|
342
|
+
width: this.width,
|
|
343
|
+
height: this.height
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Create bounding box from JSON object
|
|
349
|
+
* @param {Object} json - JSON representation
|
|
350
|
+
* @returns {BoundingBox} New bounding box instance
|
|
351
|
+
*/
|
|
352
|
+
static fromJSON(json) {
|
|
353
|
+
return new BoundingBox(json.x, json.y, json.width, json.height);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Create bounding box from array of points
|
|
358
|
+
* @param {Array<Object>} points - Array of {x, y} points
|
|
359
|
+
* @returns {BoundingBox} Bounding box containing all points
|
|
360
|
+
*/
|
|
361
|
+
static fromPoints(points) {
|
|
362
|
+
if (points.length === 0) {
|
|
363
|
+
return new BoundingBox();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
let minX = Infinity, minY = Infinity;
|
|
367
|
+
let maxX = -Infinity, maxY = -Infinity;
|
|
368
|
+
|
|
369
|
+
points.forEach(point => {
|
|
370
|
+
minX = Math.min(minX, point.x);
|
|
371
|
+
minY = Math.min(minY, point.y);
|
|
372
|
+
maxX = Math.max(maxX, point.x);
|
|
373
|
+
maxY = Math.max(maxY, point.y);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
return new BoundingBox(minX, minY, maxX - minX, maxY - minY);
|
|
377
|
+
}
|
|
378
378
|
}
|