|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = ["Point", "Rect"]; |
|
6 |
|
7 /** |
|
8 * Simple Point class. |
|
9 * |
|
10 * Any method that takes an x and y may also take a point. |
|
11 */ |
|
12 this.Point = function Point(x, y) { |
|
13 this.set(x, y); |
|
14 } |
|
15 |
|
16 Point.prototype = { |
|
17 clone: function clone() { |
|
18 return new Point(this.x, this.y); |
|
19 }, |
|
20 |
|
21 set: function set(x, y) { |
|
22 this.x = x; |
|
23 this.y = y; |
|
24 return this; |
|
25 }, |
|
26 |
|
27 equals: function equals(x, y) { |
|
28 return this.x == x && this.y == y; |
|
29 }, |
|
30 |
|
31 toString: function toString() { |
|
32 return "(" + this.x + "," + this.y + ")"; |
|
33 }, |
|
34 |
|
35 map: function map(f) { |
|
36 this.x = f.call(this, this.x); |
|
37 this.y = f.call(this, this.y); |
|
38 return this; |
|
39 }, |
|
40 |
|
41 add: function add(x, y) { |
|
42 this.x += x; |
|
43 this.y += y; |
|
44 return this; |
|
45 }, |
|
46 |
|
47 subtract: function subtract(x, y) { |
|
48 this.x -= x; |
|
49 this.y -= y; |
|
50 return this; |
|
51 }, |
|
52 |
|
53 scale: function scale(s) { |
|
54 this.x *= s; |
|
55 this.y *= s; |
|
56 return this; |
|
57 }, |
|
58 |
|
59 isZero: function() { |
|
60 return this.x == 0 && this.y == 0; |
|
61 } |
|
62 }; |
|
63 |
|
64 (function() { |
|
65 function takePointOrArgs(f) { |
|
66 return function(arg1, arg2) { |
|
67 if (arg2 === undefined) |
|
68 return f.call(this, arg1.x, arg1.y); |
|
69 else |
|
70 return f.call(this, arg1, arg2); |
|
71 }; |
|
72 } |
|
73 |
|
74 for each (let f in ['add', 'subtract', 'equals', 'set']) |
|
75 Point.prototype[f] = takePointOrArgs(Point.prototype[f]); |
|
76 })(); |
|
77 |
|
78 |
|
79 /** |
|
80 * Rect is a simple data structure for representation of a rectangle supporting |
|
81 * many basic geometric operations. |
|
82 * |
|
83 * NOTE: Since its operations are closed, rectangles may be empty and will report |
|
84 * non-positive widths and heights in that case. |
|
85 */ |
|
86 |
|
87 this.Rect = function Rect(x, y, w, h) { |
|
88 this.left = x; |
|
89 this.top = y; |
|
90 this.right = x + w; |
|
91 this.bottom = y + h; |
|
92 }; |
|
93 |
|
94 Rect.fromRect = function fromRect(r) { |
|
95 return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top); |
|
96 }; |
|
97 |
|
98 Rect.prototype = { |
|
99 get x() { return this.left; }, |
|
100 get y() { return this.top; }, |
|
101 get width() { return this.right - this.left; }, |
|
102 get height() { return this.bottom - this.top; }, |
|
103 set x(v) { |
|
104 let diff = this.left - v; |
|
105 this.left = v; |
|
106 this.right -= diff; |
|
107 }, |
|
108 set y(v) { |
|
109 let diff = this.top - v; |
|
110 this.top = v; |
|
111 this.bottom -= diff; |
|
112 }, |
|
113 set width(v) { this.right = this.left + v; }, |
|
114 set height(v) { this.bottom = this.top + v; }, |
|
115 |
|
116 isEmpty: function isEmpty() { |
|
117 return this.left >= this.right || this.top >= this.bottom; |
|
118 }, |
|
119 |
|
120 setRect: function(x, y, w, h) { |
|
121 this.left = x; |
|
122 this.top = y; |
|
123 this.right = x+w; |
|
124 this.bottom = y+h; |
|
125 |
|
126 return this; |
|
127 }, |
|
128 |
|
129 setBounds: function(l, t, r, b) { |
|
130 this.top = t; |
|
131 this.left = l; |
|
132 this.bottom = b; |
|
133 this.right = r; |
|
134 |
|
135 return this; |
|
136 }, |
|
137 |
|
138 equals: function equals(other) { |
|
139 return other != null && |
|
140 (this.isEmpty() && other.isEmpty() || |
|
141 this.top == other.top && |
|
142 this.left == other.left && |
|
143 this.bottom == other.bottom && |
|
144 this.right == other.right); |
|
145 }, |
|
146 |
|
147 clone: function clone() { |
|
148 return new Rect(this.left, this.top, this.right - this.left, this.bottom - this.top); |
|
149 }, |
|
150 |
|
151 center: function center() { |
|
152 if (this.isEmpty()) |
|
153 throw "Empty rectangles do not have centers"; |
|
154 return new Point(this.left + (this.right - this.left) / 2, |
|
155 this.top + (this.bottom - this.top) / 2); |
|
156 }, |
|
157 |
|
158 copyFrom: function(other) { |
|
159 this.top = other.top; |
|
160 this.left = other.left; |
|
161 this.bottom = other.bottom; |
|
162 this.right = other.right; |
|
163 |
|
164 return this; |
|
165 }, |
|
166 |
|
167 translate: function(x, y) { |
|
168 this.left += x; |
|
169 this.right += x; |
|
170 this.top += y; |
|
171 this.bottom += y; |
|
172 |
|
173 return this; |
|
174 }, |
|
175 |
|
176 toString: function() { |
|
177 return "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]"; |
|
178 }, |
|
179 |
|
180 /** return a new rect that is the union of that one and this one */ |
|
181 union: function(other) { |
|
182 return this.clone().expandToContain(other); |
|
183 }, |
|
184 |
|
185 contains: function(other) { |
|
186 if (other.isEmpty()) return true; |
|
187 if (this.isEmpty()) return false; |
|
188 |
|
189 return (other.left >= this.left && |
|
190 other.right <= this.right && |
|
191 other.top >= this.top && |
|
192 other.bottom <= this.bottom); |
|
193 }, |
|
194 |
|
195 intersect: function(other) { |
|
196 return this.clone().restrictTo(other); |
|
197 }, |
|
198 |
|
199 intersects: function(other) { |
|
200 if (this.isEmpty() || other.isEmpty()) |
|
201 return false; |
|
202 |
|
203 let x1 = Math.max(this.left, other.left); |
|
204 let x2 = Math.min(this.right, other.right); |
|
205 let y1 = Math.max(this.top, other.top); |
|
206 let y2 = Math.min(this.bottom, other.bottom); |
|
207 return x1 < x2 && y1 < y2; |
|
208 }, |
|
209 |
|
210 /** Restrict area of this rectangle to the intersection of both rectangles. */ |
|
211 restrictTo: function restrictTo(other) { |
|
212 if (this.isEmpty() || other.isEmpty()) |
|
213 return this.setRect(0, 0, 0, 0); |
|
214 |
|
215 let x1 = Math.max(this.left, other.left); |
|
216 let x2 = Math.min(this.right, other.right); |
|
217 let y1 = Math.max(this.top, other.top); |
|
218 let y2 = Math.min(this.bottom, other.bottom); |
|
219 // If width or height is 0, the intersection was empty. |
|
220 return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1)); |
|
221 }, |
|
222 |
|
223 /** Expand this rectangle to the union of both rectangles. */ |
|
224 expandToContain: function expandToContain(other) { |
|
225 if (this.isEmpty()) return this.copyFrom(other); |
|
226 if (other.isEmpty()) return this; |
|
227 |
|
228 let l = Math.min(this.left, other.left); |
|
229 let r = Math.max(this.right, other.right); |
|
230 let t = Math.min(this.top, other.top); |
|
231 let b = Math.max(this.bottom, other.bottom); |
|
232 return this.setRect(l, t, r-l, b-t); |
|
233 }, |
|
234 |
|
235 /** |
|
236 * Expands to the smallest rectangle that contains original rectangle and is bounded |
|
237 * by lines with integer coefficients. |
|
238 */ |
|
239 expandToIntegers: function round() { |
|
240 this.left = Math.floor(this.left); |
|
241 this.top = Math.floor(this.top); |
|
242 this.right = Math.ceil(this.right); |
|
243 this.bottom = Math.ceil(this.bottom); |
|
244 return this; |
|
245 }, |
|
246 |
|
247 scale: function scale(xscl, yscl) { |
|
248 this.left *= xscl; |
|
249 this.right *= xscl; |
|
250 this.top *= yscl; |
|
251 this.bottom *= yscl; |
|
252 return this; |
|
253 }, |
|
254 |
|
255 map: function map(f) { |
|
256 this.left = f.call(this, this.left); |
|
257 this.top = f.call(this, this.top); |
|
258 this.right = f.call(this, this.right); |
|
259 this.bottom = f.call(this, this.bottom); |
|
260 return this; |
|
261 }, |
|
262 |
|
263 /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */ |
|
264 translateInside: function translateInside(other) { |
|
265 let offsetX = (this.left <= other.left ? other.left - this.left : |
|
266 (this.right > other.right ? other.right - this.right : 0)); |
|
267 let offsetY = (this.top <= other.top ? other.top - this.top : |
|
268 (this.bottom > other.bottom ? other.bottom - this.bottom : 0)); |
|
269 return this.translate(offsetX, offsetY); |
|
270 }, |
|
271 |
|
272 /** Subtract other area from this. Returns array of rects whose union is this-other. */ |
|
273 subtract: function subtract(other) { |
|
274 let r = new Rect(0, 0, 0, 0); |
|
275 let result = []; |
|
276 other = other.intersect(this); |
|
277 if (other.isEmpty()) |
|
278 return [this.clone()]; |
|
279 |
|
280 // left strip |
|
281 r.setBounds(this.left, this.top, other.left, this.bottom); |
|
282 if (!r.isEmpty()) |
|
283 result.push(r.clone()); |
|
284 // inside strip |
|
285 r.setBounds(other.left, this.top, other.right, other.top); |
|
286 if (!r.isEmpty()) |
|
287 result.push(r.clone()); |
|
288 r.setBounds(other.left, other.bottom, other.right, this.bottom); |
|
289 if (!r.isEmpty()) |
|
290 result.push(r.clone()); |
|
291 // right strip |
|
292 r.setBounds(other.right, this.top, this.right, this.bottom); |
|
293 if (!r.isEmpty()) |
|
294 result.push(r.clone()); |
|
295 |
|
296 return result; |
|
297 }, |
|
298 |
|
299 /** |
|
300 * Blends two rectangles together. |
|
301 * @param rect Rectangle to blend this one with |
|
302 * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect). |
|
303 * @return New blended rectangle. |
|
304 */ |
|
305 blend: function blend(rect, scalar) { |
|
306 return new Rect( |
|
307 this.left + (rect.left - this.left ) * scalar, |
|
308 this.top + (rect.top - this.top ) * scalar, |
|
309 this.width + (rect.width - this.width ) * scalar, |
|
310 this.height + (rect.height - this.height) * scalar); |
|
311 }, |
|
312 |
|
313 /** |
|
314 * Grows or shrinks the rectangle while keeping the center point. |
|
315 * Accepts single multipler, or separate for both axes. |
|
316 */ |
|
317 inflate: function inflate(xscl, yscl) { |
|
318 let xAdj = (this.width * xscl - this.width) / 2; |
|
319 let s = (arguments.length > 1) ? yscl : xscl; |
|
320 let yAdj = (this.height * s - this.height) / 2; |
|
321 this.left -= xAdj; |
|
322 this.right += xAdj; |
|
323 this.top -= yAdj; |
|
324 this.bottom += yAdj; |
|
325 return this; |
|
326 } |
|
327 }; |