|
1 |
|
2 (function(window, document, Math, undef) { |
|
3 |
|
4 var nop = function(){}; |
|
5 |
|
6 var debug = (function() { |
|
7 if ("console" in window) { |
|
8 return function(msg) { |
|
9 window.console.log('Processing.js: ' + msg); |
|
10 }; |
|
11 } |
|
12 return nop(); |
|
13 }()); |
|
14 |
|
15 var ajax = function(url) { |
|
16 var xhr = new XMLHttpRequest(); |
|
17 xhr.open("GET", url, false); |
|
18 if (xhr.overrideMimeType) { |
|
19 xhr.overrideMimeType("text/plain"); |
|
20 } |
|
21 xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); |
|
22 xhr.send(null); |
|
23 // failed request? |
|
24 if (xhr.status !== 200 && xhr.status !== 0) { throw ("XMLHttpRequest failed, status code " + xhr.status); } |
|
25 return xhr.responseText; |
|
26 }; |
|
27 |
|
28 var isDOMPresent = ("document" in this) && !("fake" in this.document); |
|
29 |
|
30 // document.head polyfill for the benefit of Firefox 3.6 |
|
31 document.head = document.head || document.getElementsByTagName('head')[0]; |
|
32 |
|
33 // Typed Arrays: fallback to WebGL arrays or Native JS arrays if unavailable |
|
34 function setupTypedArray(name, fallback) { |
|
35 // Check if TypedArray exists, and use if so. |
|
36 if (name in window) { |
|
37 return window[name]; |
|
38 } |
|
39 |
|
40 // Check if WebGLArray exists |
|
41 if (typeof window[fallback] === "function") { |
|
42 return window[fallback]; |
|
43 } |
|
44 |
|
45 // Use Native JS array |
|
46 return function(obj) { |
|
47 if (obj instanceof Array) { |
|
48 return obj; |
|
49 } |
|
50 if (typeof obj === "number") { |
|
51 var arr = []; |
|
52 arr.length = obj; |
|
53 return arr; |
|
54 } |
|
55 }; |
|
56 } |
|
57 |
|
58 var Float32Array = setupTypedArray("Float32Array", "WebGLFloatArray"), |
|
59 Int32Array = setupTypedArray("Int32Array", "WebGLIntArray"), |
|
60 Uint16Array = setupTypedArray("Uint16Array", "WebGLUnsignedShortArray"), |
|
61 Uint8Array = setupTypedArray("Uint8Array", "WebGLUnsignedByteArray"); |
|
62 |
|
63 /* Browsers fixes end */ |
|
64 |
|
65 /** |
|
66 * NOTE: in releases we replace symbolic PConstants.* names with their values. |
|
67 * Using PConstants.* in code below is fine. See tools/rewrite-pconstants.js. |
|
68 */ |
|
69 var PConstants = { |
|
70 X: 0, |
|
71 Y: 1, |
|
72 Z: 2, |
|
73 |
|
74 R: 3, |
|
75 G: 4, |
|
76 B: 5, |
|
77 A: 6, |
|
78 |
|
79 U: 7, |
|
80 V: 8, |
|
81 |
|
82 NX: 9, |
|
83 NY: 10, |
|
84 NZ: 11, |
|
85 |
|
86 EDGE: 12, |
|
87 |
|
88 // Stroke |
|
89 SR: 13, |
|
90 SG: 14, |
|
91 SB: 15, |
|
92 SA: 16, |
|
93 |
|
94 SW: 17, |
|
95 |
|
96 // Transformations (2D and 3D) |
|
97 TX: 18, |
|
98 TY: 19, |
|
99 TZ: 20, |
|
100 |
|
101 VX: 21, |
|
102 VY: 22, |
|
103 VZ: 23, |
|
104 VW: 24, |
|
105 |
|
106 // Material properties |
|
107 AR: 25, |
|
108 AG: 26, |
|
109 AB: 27, |
|
110 |
|
111 DR: 3, |
|
112 DG: 4, |
|
113 DB: 5, |
|
114 DA: 6, |
|
115 |
|
116 SPR: 28, |
|
117 SPG: 29, |
|
118 SPB: 30, |
|
119 |
|
120 SHINE: 31, |
|
121 |
|
122 ER: 32, |
|
123 EG: 33, |
|
124 EB: 34, |
|
125 |
|
126 BEEN_LIT: 35, |
|
127 |
|
128 VERTEX_FIELD_COUNT: 36, |
|
129 |
|
130 // Renderers |
|
131 P2D: 1, |
|
132 JAVA2D: 1, |
|
133 WEBGL: 2, |
|
134 P3D: 2, |
|
135 OPENGL: 2, |
|
136 PDF: 0, |
|
137 DXF: 0, |
|
138 |
|
139 // Platform IDs |
|
140 OTHER: 0, |
|
141 WINDOWS: 1, |
|
142 MAXOSX: 2, |
|
143 LINUX: 3, |
|
144 |
|
145 EPSILON: 0.0001, |
|
146 |
|
147 MAX_FLOAT: 3.4028235e+38, |
|
148 MIN_FLOAT: -3.4028235e+38, |
|
149 MAX_INT: 2147483647, |
|
150 MIN_INT: -2147483648, |
|
151 |
|
152 PI: Math.PI, |
|
153 TWO_PI: 2 * Math.PI, |
|
154 HALF_PI: Math.PI / 2, |
|
155 THIRD_PI: Math.PI / 3, |
|
156 QUARTER_PI: Math.PI / 4, |
|
157 |
|
158 DEG_TO_RAD: Math.PI / 180, |
|
159 RAD_TO_DEG: 180 / Math.PI, |
|
160 |
|
161 WHITESPACE: " \t\n\r\f\u00A0", |
|
162 |
|
163 // Color modes |
|
164 RGB: 1, |
|
165 ARGB: 2, |
|
166 HSB: 3, |
|
167 ALPHA: 4, |
|
168 CMYK: 5, |
|
169 |
|
170 // Image file types |
|
171 TIFF: 0, |
|
172 TARGA: 1, |
|
173 JPEG: 2, |
|
174 GIF: 3, |
|
175 |
|
176 // Filter/convert types |
|
177 BLUR: 11, |
|
178 GRAY: 12, |
|
179 INVERT: 13, |
|
180 OPAQUE: 14, |
|
181 POSTERIZE: 15, |
|
182 THRESHOLD: 16, |
|
183 ERODE: 17, |
|
184 DILATE: 18, |
|
185 |
|
186 // Blend modes |
|
187 REPLACE: 0, |
|
188 BLEND: 1 << 0, |
|
189 ADD: 1 << 1, |
|
190 SUBTRACT: 1 << 2, |
|
191 LIGHTEST: 1 << 3, |
|
192 DARKEST: 1 << 4, |
|
193 DIFFERENCE: 1 << 5, |
|
194 EXCLUSION: 1 << 6, |
|
195 MULTIPLY: 1 << 7, |
|
196 SCREEN: 1 << 8, |
|
197 OVERLAY: 1 << 9, |
|
198 HARD_LIGHT: 1 << 10, |
|
199 SOFT_LIGHT: 1 << 11, |
|
200 DODGE: 1 << 12, |
|
201 BURN: 1 << 13, |
|
202 |
|
203 // Color component bit masks |
|
204 ALPHA_MASK: 0xff000000, |
|
205 RED_MASK: 0x00ff0000, |
|
206 GREEN_MASK: 0x0000ff00, |
|
207 BLUE_MASK: 0x000000ff, |
|
208 |
|
209 // Projection matrices |
|
210 CUSTOM: 0, |
|
211 ORTHOGRAPHIC: 2, |
|
212 PERSPECTIVE: 3, |
|
213 |
|
214 // Shapes |
|
215 POINT: 2, |
|
216 POINTS: 2, |
|
217 LINE: 4, |
|
218 LINES: 4, |
|
219 TRIANGLE: 8, |
|
220 TRIANGLES: 9, |
|
221 TRIANGLE_STRIP: 10, |
|
222 TRIANGLE_FAN: 11, |
|
223 QUAD: 16, |
|
224 QUADS: 16, |
|
225 QUAD_STRIP: 17, |
|
226 POLYGON: 20, |
|
227 PATH: 21, |
|
228 RECT: 30, |
|
229 ELLIPSE: 31, |
|
230 ARC: 32, |
|
231 SPHERE: 40, |
|
232 BOX: 41, |
|
233 |
|
234 GROUP: 0, |
|
235 PRIMITIVE: 1, |
|
236 //PATH: 21, // shared with Shape PATH |
|
237 GEOMETRY: 3, |
|
238 |
|
239 // Shape Vertex |
|
240 VERTEX: 0, |
|
241 BEZIER_VERTEX: 1, |
|
242 CURVE_VERTEX: 2, |
|
243 BREAK: 3, |
|
244 CLOSESHAPE: 4, |
|
245 |
|
246 // Shape closing modes |
|
247 OPEN: 1, |
|
248 CLOSE: 2, |
|
249 |
|
250 // Shape drawing modes |
|
251 CORNER: 0, // Draw mode convention to use (x, y) to (width, height) |
|
252 CORNERS: 1, // Draw mode convention to use (x1, y1) to (x2, y2) coordinates |
|
253 RADIUS: 2, // Draw mode from the center, and using the radius |
|
254 CENTER_RADIUS: 2, // Deprecated! Use RADIUS instead |
|
255 CENTER: 3, // Draw from the center, using second pair of values as the diameter |
|
256 DIAMETER: 3, // Synonym for the CENTER constant. Draw from the center |
|
257 CENTER_DIAMETER: 3, // Deprecated! Use DIAMETER instead |
|
258 |
|
259 // Text vertical alignment modes |
|
260 BASELINE: 0, // Default vertical alignment for text placement |
|
261 TOP: 101, // Align text to the top |
|
262 BOTTOM: 102, // Align text from the bottom, using the baseline |
|
263 |
|
264 // UV Texture coordinate modes |
|
265 NORMAL: 1, |
|
266 NORMALIZED: 1, |
|
267 IMAGE: 2, |
|
268 |
|
269 // Text placement modes |
|
270 MODEL: 4, |
|
271 SHAPE: 5, |
|
272 |
|
273 // Stroke modes |
|
274 SQUARE: 'butt', |
|
275 ROUND: 'round', |
|
276 PROJECT: 'square', |
|
277 MITER: 'miter', |
|
278 BEVEL: 'bevel', |
|
279 |
|
280 // Lighting modes |
|
281 AMBIENT: 0, |
|
282 DIRECTIONAL: 1, |
|
283 //POINT: 2, Shared with Shape constant |
|
284 SPOT: 3, |
|
285 |
|
286 // Key constants |
|
287 |
|
288 // Both key and keyCode will be equal to these values |
|
289 BACKSPACE: 8, |
|
290 TAB: 9, |
|
291 ENTER: 10, |
|
292 RETURN: 13, |
|
293 ESC: 27, |
|
294 DELETE: 127, |
|
295 CODED: 0xffff, |
|
296 |
|
297 // p.key will be CODED and p.keyCode will be this value |
|
298 SHIFT: 16, |
|
299 CONTROL: 17, |
|
300 ALT: 18, |
|
301 CAPSLK: 20, |
|
302 PGUP: 33, |
|
303 PGDN: 34, |
|
304 END: 35, |
|
305 HOME: 36, |
|
306 LEFT: 37, |
|
307 UP: 38, |
|
308 RIGHT: 39, |
|
309 DOWN: 40, |
|
310 F1: 112, |
|
311 F2: 113, |
|
312 F3: 114, |
|
313 F4: 115, |
|
314 F5: 116, |
|
315 F6: 117, |
|
316 F7: 118, |
|
317 F8: 119, |
|
318 F9: 120, |
|
319 F10: 121, |
|
320 F11: 122, |
|
321 F12: 123, |
|
322 NUMLK: 144, |
|
323 META: 157, |
|
324 INSERT: 155, |
|
325 |
|
326 // Cursor types |
|
327 ARROW: 'default', |
|
328 CROSS: 'crosshair', |
|
329 HAND: 'pointer', |
|
330 MOVE: 'move', |
|
331 TEXT: 'text', |
|
332 WAIT: 'wait', |
|
333 NOCURSOR: "url('data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='), auto", |
|
334 |
|
335 // Hints |
|
336 DISABLE_OPENGL_2X_SMOOTH: 1, |
|
337 ENABLE_OPENGL_2X_SMOOTH: -1, |
|
338 ENABLE_OPENGL_4X_SMOOTH: 2, |
|
339 ENABLE_NATIVE_FONTS: 3, |
|
340 DISABLE_DEPTH_TEST: 4, |
|
341 ENABLE_DEPTH_TEST: -4, |
|
342 ENABLE_DEPTH_SORT: 5, |
|
343 DISABLE_DEPTH_SORT: -5, |
|
344 DISABLE_OPENGL_ERROR_REPORT: 6, |
|
345 ENABLE_OPENGL_ERROR_REPORT: -6, |
|
346 ENABLE_ACCURATE_TEXTURES: 7, |
|
347 DISABLE_ACCURATE_TEXTURES: -7, |
|
348 HINT_COUNT: 10, |
|
349 |
|
350 // PJS defined constants |
|
351 SINCOS_LENGTH: 720, // every half degree |
|
352 PRECISIONB: 15, // fixed point precision is limited to 15 bits!! |
|
353 PRECISIONF: 1 << 15, |
|
354 PREC_MAXVAL: (1 << 15) - 1, |
|
355 PREC_ALPHA_SHIFT: 24 - 15, |
|
356 PREC_RED_SHIFT: 16 - 15, |
|
357 NORMAL_MODE_AUTO: 0, |
|
358 NORMAL_MODE_SHAPE: 1, |
|
359 NORMAL_MODE_VERTEX: 2, |
|
360 MAX_LIGHTS: 8 |
|
361 }; |
|
362 |
|
363 /** |
|
364 * Returns Java hashCode() result for the object. If the object has the "hashCode" function, |
|
365 * it preforms the call of this function. Otherwise it uses/creates the "$id" property, |
|
366 * which is used as the hashCode. |
|
367 * |
|
368 * @param {Object} obj The object. |
|
369 * @returns {int} The object's hash code. |
|
370 */ |
|
371 function virtHashCode(obj) { |
|
372 if (typeof(obj) === "string") { |
|
373 var hash = 0; |
|
374 for (var i = 0; i < obj.length; ++i) { |
|
375 hash = (hash * 31 + obj.charCodeAt(i)) & 0xFFFFFFFF; |
|
376 } |
|
377 return hash; |
|
378 } |
|
379 if (typeof(obj) !== "object") { |
|
380 return obj & 0xFFFFFFFF; |
|
381 } |
|
382 if (obj.hashCode instanceof Function) { |
|
383 return obj.hashCode(); |
|
384 } |
|
385 if (obj.$id === undef) { |
|
386 obj.$id = ((Math.floor(Math.random() * 0x10000) - 0x8000) << 16) | Math.floor(Math.random() * 0x10000); |
|
387 } |
|
388 return obj.$id; |
|
389 } |
|
390 |
|
391 /** |
|
392 * Returns Java equals() result for two objects. If the first object |
|
393 * has the "equals" function, it preforms the call of this function. |
|
394 * Otherwise the method uses the JavaScript === operator. |
|
395 * |
|
396 * @param {Object} obj The first object. |
|
397 * @param {Object} other The second object. |
|
398 * |
|
399 * @returns {boolean} true if the objects are equal. |
|
400 */ |
|
401 function virtEquals(obj, other) { |
|
402 if (obj === null || other === null) { |
|
403 return (obj === null) && (other === null); |
|
404 } |
|
405 if (typeof (obj) === "string") { |
|
406 return obj === other; |
|
407 } |
|
408 if (typeof(obj) !== "object") { |
|
409 return obj === other; |
|
410 } |
|
411 if (obj.equals instanceof Function) { |
|
412 return obj.equals(other); |
|
413 } |
|
414 return obj === other; |
|
415 } |
|
416 |
|
417 /** |
|
418 * A ObjectIterator is an iterator wrapper for objects. If passed object contains |
|
419 * the iterator method, the object instance will be replaced by the result returned by |
|
420 * this method call. If passed object is an array, the ObjectIterator instance iterates |
|
421 * through its items. |
|
422 * |
|
423 * @param {Object} obj The object to be iterated. |
|
424 */ |
|
425 var ObjectIterator = function(obj) { |
|
426 if (obj.iterator instanceof Function) { |
|
427 return obj.iterator(); |
|
428 } |
|
429 if (obj instanceof Array) { |
|
430 // iterate through array items |
|
431 var index = -1; |
|
432 this.hasNext = function() { |
|
433 return ++index < obj.length; |
|
434 }; |
|
435 this.next = function() { |
|
436 return obj[index]; |
|
437 }; |
|
438 } else { |
|
439 throw "Unable to iterate: " + obj; |
|
440 } |
|
441 }; |
|
442 |
|
443 /** |
|
444 * An ArrayList stores a variable number of objects. |
|
445 * |
|
446 * @param {int} initialCapacity optional defines the initial capacity of the list, it's empty by default |
|
447 * |
|
448 * @returns {ArrayList} new ArrayList object |
|
449 */ |
|
450 var ArrayList = (function() { |
|
451 function Iterator(array) { |
|
452 var index = 0; |
|
453 this.hasNext = function() { |
|
454 return index < array.length; |
|
455 }; |
|
456 |
|
457 this.next = function() { |
|
458 return array[index++]; |
|
459 }; |
|
460 |
|
461 this.remove = function() { |
|
462 array.splice(index, 1); |
|
463 }; |
|
464 } |
|
465 |
|
466 function ArrayList() { |
|
467 var array; |
|
468 if (arguments.length === 0) { |
|
469 array = []; |
|
470 } else if (arguments.length > 0 && typeof arguments[0] !== 'number') { |
|
471 array = arguments[0].toArray(); |
|
472 } else { |
|
473 array = []; |
|
474 array.length = 0 | arguments[0]; |
|
475 } |
|
476 |
|
477 /** |
|
478 * @member ArrayList |
|
479 * ArrayList.get() Returns the element at the specified position in this list. |
|
480 * |
|
481 * @param {int} i index of element to return |
|
482 * |
|
483 * @returns {Object} the element at the specified position in this list. |
|
484 */ |
|
485 this.get = function(i) { |
|
486 return array[i]; |
|
487 }; |
|
488 /** |
|
489 * @member ArrayList |
|
490 * ArrayList.contains() Returns true if this list contains the specified element. |
|
491 * |
|
492 * @param {Object} item element whose presence in this List is to be tested. |
|
493 * |
|
494 * @returns {boolean} true if the specified element is present; false otherwise. |
|
495 */ |
|
496 this.contains = function(item) { |
|
497 return this.indexOf(item)>-1; |
|
498 }; |
|
499 /** |
|
500 * @member ArrayList |
|
501 * ArrayList.indexOf() Returns the position this element takes in the list, or -1 if the element is not found. |
|
502 * |
|
503 * @param {Object} item element whose position in this List is to be tested. |
|
504 * |
|
505 * @returns {int} the list position that the first match for this element holds in the list, or -1 if it is not in the list. |
|
506 */ |
|
507 this.indexOf = function(item) { |
|
508 for (var i = 0, len = array.length; i < len; ++i) { |
|
509 if (virtEquals(item, array[i])) { |
|
510 return i; |
|
511 } |
|
512 } |
|
513 return -1; |
|
514 }; |
|
515 /** |
|
516 * @member ArrayList |
|
517 * ArrayList.add() Adds the specified element to this list. |
|
518 * |
|
519 * @param {int} index optional index at which the specified element is to be inserted |
|
520 * @param {Object} object element to be added to the list |
|
521 */ |
|
522 this.add = function() { |
|
523 if (arguments.length === 1) { |
|
524 array.push(arguments[0]); // for add(Object) |
|
525 } else if (arguments.length === 2) { |
|
526 var arg0 = arguments[0]; |
|
527 if (typeof arg0 === 'number') { |
|
528 if (arg0 >= 0 && arg0 <= array.length) { |
|
529 array.splice(arg0, 0, arguments[1]); // for add(i, Object) |
|
530 } else { |
|
531 throw(arg0 + " is not a valid index"); |
|
532 } |
|
533 } else { |
|
534 throw(typeof arg0 + " is not a number"); |
|
535 } |
|
536 } else { |
|
537 throw("Please use the proper number of parameters."); |
|
538 } |
|
539 }; |
|
540 /** |
|
541 * @member ArrayList |
|
542 * ArrayList.addAll(collection) appends all of the elements in the specified |
|
543 * Collection to the end of this list, in the order that they are returned by |
|
544 * the specified Collection's Iterator. |
|
545 * |
|
546 * When called as addAll(index, collection) the elements are inserted into |
|
547 * this list at the position indicated by index. |
|
548 * |
|
549 * @param {index} Optional; specifies the position the colletion should be inserted at |
|
550 * @param {collection} Any iterable object (ArrayList, HashMap.keySet(), etc.) |
|
551 * @throws out of bounds error for negative index, or index greater than list size. |
|
552 */ |
|
553 this.addAll = function(arg1, arg2) { |
|
554 // addAll(int, Collection) |
|
555 var it; |
|
556 if (typeof arg1 === "number") { |
|
557 if (arg1 < 0 || arg1 > array.length) { |
|
558 throw("Index out of bounds for addAll: " + arg1 + " greater or equal than " + array.length); |
|
559 } |
|
560 it = new ObjectIterator(arg2); |
|
561 while (it.hasNext()) { |
|
562 array.splice(arg1++, 0, it.next()); |
|
563 } |
|
564 } |
|
565 // addAll(Collection) |
|
566 else { |
|
567 it = new ObjectIterator(arg1); |
|
568 while (it.hasNext()) { |
|
569 array.push(it.next()); |
|
570 } |
|
571 } |
|
572 }; |
|
573 /** |
|
574 * @member ArrayList |
|
575 * ArrayList.set() Replaces the element at the specified position in this list with the specified element. |
|
576 * |
|
577 * @param {int} index index of element to replace |
|
578 * @param {Object} object element to be stored at the specified position |
|
579 */ |
|
580 this.set = function() { |
|
581 if (arguments.length === 2) { |
|
582 var arg0 = arguments[0]; |
|
583 if (typeof arg0 === 'number') { |
|
584 if (arg0 >= 0 && arg0 < array.length) { |
|
585 array.splice(arg0, 1, arguments[1]); |
|
586 } else { |
|
587 throw(arg0 + " is not a valid index."); |
|
588 } |
|
589 } else { |
|
590 throw(typeof arg0 + " is not a number"); |
|
591 } |
|
592 } else { |
|
593 throw("Please use the proper number of parameters."); |
|
594 } |
|
595 }; |
|
596 |
|
597 /** |
|
598 * @member ArrayList |
|
599 * ArrayList.size() Returns the number of elements in this list. |
|
600 * |
|
601 * @returns {int} the number of elements in this list |
|
602 */ |
|
603 this.size = function() { |
|
604 return array.length; |
|
605 }; |
|
606 |
|
607 /** |
|
608 * @member ArrayList |
|
609 * ArrayList.clear() Removes all of the elements from this list. The list will be empty after this call returns. |
|
610 */ |
|
611 this.clear = function() { |
|
612 array.length = 0; |
|
613 }; |
|
614 |
|
615 /** |
|
616 * @member ArrayList |
|
617 * ArrayList.remove() Removes an element either based on index, if the argument is a number, or |
|
618 * by equality check, if the argument is an object. |
|
619 * |
|
620 * @param {int|Object} item either the index of the element to be removed, or the element itself. |
|
621 * |
|
622 * @returns {Object|boolean} If removal is by index, the element that was removed, or null if nothing was removed. If removal is by object, true if removal occurred, otherwise false. |
|
623 */ |
|
624 this.remove = function(item) { |
|
625 if (typeof item === 'number') { |
|
626 return array.splice(item, 1)[0]; |
|
627 } |
|
628 item = this.indexOf(item); |
|
629 if (item > -1) { |
|
630 array.splice(item, 1); |
|
631 return true; |
|
632 } |
|
633 return false; |
|
634 }; |
|
635 |
|
636 /** |
|
637 * @member ArrayList |
|
638 * ArrayList.isEmpty() Tests if this list has no elements. |
|
639 * |
|
640 * @returns {boolean} true if this list has no elements; false otherwise |
|
641 */ |
|
642 this.isEmpty = function() { |
|
643 return !array.length; |
|
644 }; |
|
645 |
|
646 /** |
|
647 * @member ArrayList |
|
648 * ArrayList.clone() Returns a shallow copy of this ArrayList instance. (The elements themselves are not copied.) |
|
649 * |
|
650 * @returns {ArrayList} a clone of this ArrayList instance |
|
651 */ |
|
652 this.clone = function() { |
|
653 return new ArrayList(this); |
|
654 }; |
|
655 |
|
656 /** |
|
657 * @member ArrayList |
|
658 * ArrayList.toArray() Returns an array containing all of the elements in this list in the correct order. |
|
659 * |
|
660 * @returns {Object[]} Returns an array containing all of the elements in this list in the correct order |
|
661 */ |
|
662 this.toArray = function() { |
|
663 return array.slice(0); |
|
664 }; |
|
665 |
|
666 this.iterator = function() { |
|
667 return new Iterator(array); |
|
668 }; |
|
669 } |
|
670 |
|
671 return ArrayList; |
|
672 }()); |
|
673 |
|
674 /** |
|
675 * A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only |
|
676 * instead of accessing elements with a numeric index, a String is used. (If you are familiar with |
|
677 * associative arrays from other languages, this is the same idea.) |
|
678 * |
|
679 * @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default |
|
680 * @param {float} loadFactor the load factor for the map, the default is 0.75 |
|
681 * @param {Map} m gives the new HashMap the same mappings as this Map |
|
682 */ |
|
683 var HashMap = (function() { |
|
684 /** |
|
685 * @member HashMap |
|
686 * A HashMap stores a collection of objects, each referenced by a key. This is similar to an Array, only |
|
687 * instead of accessing elements with a numeric index, a String is used. (If you are familiar with |
|
688 * associative arrays from other languages, this is the same idea.) |
|
689 * |
|
690 * @param {int} initialCapacity defines the initial capacity of the map, it's 16 by default |
|
691 * @param {float} loadFactor the load factor for the map, the default is 0.75 |
|
692 * @param {Map} m gives the new HashMap the same mappings as this Map |
|
693 */ |
|
694 function HashMap() { |
|
695 if (arguments.length === 1 && arguments[0] instanceof HashMap) { |
|
696 return arguments[0].clone(); |
|
697 } |
|
698 |
|
699 var initialCapacity = arguments.length > 0 ? arguments[0] : 16; |
|
700 var loadFactor = arguments.length > 1 ? arguments[1] : 0.75; |
|
701 var buckets = []; |
|
702 buckets.length = initialCapacity; |
|
703 var count = 0; |
|
704 var hashMap = this; |
|
705 |
|
706 function getBucketIndex(key) { |
|
707 var index = virtHashCode(key) % buckets.length; |
|
708 return index < 0 ? buckets.length + index : index; |
|
709 } |
|
710 function ensureLoad() { |
|
711 if (count <= loadFactor * buckets.length) { |
|
712 return; |
|
713 } |
|
714 var allEntries = []; |
|
715 for (var i = 0; i < buckets.length; ++i) { |
|
716 if (buckets[i] !== undef) { |
|
717 allEntries = allEntries.concat(buckets[i]); |
|
718 } |
|
719 } |
|
720 var newBucketsLength = buckets.length * 2; |
|
721 buckets = []; |
|
722 buckets.length = newBucketsLength; |
|
723 for (var j = 0; j < allEntries.length; ++j) { |
|
724 var index = getBucketIndex(allEntries[j].key); |
|
725 var bucket = buckets[index]; |
|
726 if (bucket === undef) { |
|
727 buckets[index] = bucket = []; |
|
728 } |
|
729 bucket.push(allEntries[j]); |
|
730 } |
|
731 } |
|
732 |
|
733 function Iterator(conversion, removeItem) { |
|
734 var bucketIndex = 0; |
|
735 var itemIndex = -1; |
|
736 var endOfBuckets = false; |
|
737 |
|
738 function findNext() { |
|
739 while (!endOfBuckets) { |
|
740 ++itemIndex; |
|
741 if (bucketIndex >= buckets.length) { |
|
742 endOfBuckets = true; |
|
743 } else if (buckets[bucketIndex] === undef || itemIndex >= buckets[bucketIndex].length) { |
|
744 itemIndex = -1; |
|
745 ++bucketIndex; |
|
746 } else { |
|
747 return; |
|
748 } |
|
749 } |
|
750 } |
|
751 |
|
752 /* |
|
753 * @member Iterator |
|
754 * Checks if the Iterator has more items |
|
755 */ |
|
756 this.hasNext = function() { |
|
757 return !endOfBuckets; |
|
758 }; |
|
759 |
|
760 /* |
|
761 * @member Iterator |
|
762 * Return the next Item |
|
763 */ |
|
764 this.next = function() { |
|
765 var result = conversion(buckets[bucketIndex][itemIndex]); |
|
766 findNext(); |
|
767 return result; |
|
768 }; |
|
769 |
|
770 /* |
|
771 * @member Iterator |
|
772 * Remove the current item |
|
773 */ |
|
774 this.remove = function() { |
|
775 removeItem(this.next()); |
|
776 --itemIndex; |
|
777 }; |
|
778 |
|
779 findNext(); |
|
780 } |
|
781 |
|
782 function Set(conversion, isIn, removeItem) { |
|
783 this.clear = function() { |
|
784 hashMap.clear(); |
|
785 }; |
|
786 |
|
787 this.contains = function(o) { |
|
788 return isIn(o); |
|
789 }; |
|
790 |
|
791 this.containsAll = function(o) { |
|
792 var it = o.iterator(); |
|
793 while (it.hasNext()) { |
|
794 if (!this.contains(it.next())) { |
|
795 return false; |
|
796 } |
|
797 } |
|
798 return true; |
|
799 }; |
|
800 |
|
801 this.isEmpty = function() { |
|
802 return hashMap.isEmpty(); |
|
803 }; |
|
804 |
|
805 this.iterator = function() { |
|
806 return new Iterator(conversion, removeItem); |
|
807 }; |
|
808 |
|
809 this.remove = function(o) { |
|
810 if (this.contains(o)) { |
|
811 removeItem(o); |
|
812 return true; |
|
813 } |
|
814 return false; |
|
815 }; |
|
816 |
|
817 this.removeAll = function(c) { |
|
818 var it = c.iterator(); |
|
819 var changed = false; |
|
820 while (it.hasNext()) { |
|
821 var item = it.next(); |
|
822 if (this.contains(item)) { |
|
823 removeItem(item); |
|
824 changed = true; |
|
825 } |
|
826 } |
|
827 return true; |
|
828 }; |
|
829 |
|
830 this.retainAll = function(c) { |
|
831 var it = this.iterator(); |
|
832 var toRemove = []; |
|
833 while (it.hasNext()) { |
|
834 var entry = it.next(); |
|
835 if (!c.contains(entry)) { |
|
836 toRemove.push(entry); |
|
837 } |
|
838 } |
|
839 for (var i = 0; i < toRemove.length; ++i) { |
|
840 removeItem(toRemove[i]); |
|
841 } |
|
842 return toRemove.length > 0; |
|
843 }; |
|
844 |
|
845 this.size = function() { |
|
846 return hashMap.size(); |
|
847 }; |
|
848 |
|
849 this.toArray = function() { |
|
850 var result = []; |
|
851 var it = this.iterator(); |
|
852 while (it.hasNext()) { |
|
853 result.push(it.next()); |
|
854 } |
|
855 return result; |
|
856 }; |
|
857 } |
|
858 |
|
859 function Entry(pair) { |
|
860 this._isIn = function(map) { |
|
861 return map === hashMap && (pair.removed === undef); |
|
862 }; |
|
863 |
|
864 this.equals = function(o) { |
|
865 return virtEquals(pair.key, o.getKey()); |
|
866 }; |
|
867 |
|
868 this.getKey = function() { |
|
869 return pair.key; |
|
870 }; |
|
871 |
|
872 this.getValue = function() { |
|
873 return pair.value; |
|
874 }; |
|
875 |
|
876 this.hashCode = function(o) { |
|
877 return virtHashCode(pair.key); |
|
878 }; |
|
879 |
|
880 this.setValue = function(value) { |
|
881 var old = pair.value; |
|
882 pair.value = value; |
|
883 return old; |
|
884 }; |
|
885 } |
|
886 |
|
887 this.clear = function() { |
|
888 count = 0; |
|
889 buckets = []; |
|
890 buckets.length = initialCapacity; |
|
891 }; |
|
892 |
|
893 this.clone = function() { |
|
894 var map = new HashMap(); |
|
895 map.putAll(this); |
|
896 return map; |
|
897 }; |
|
898 |
|
899 this.containsKey = function(key) { |
|
900 var index = getBucketIndex(key); |
|
901 var bucket = buckets[index]; |
|
902 if (bucket === undef) { |
|
903 return false; |
|
904 } |
|
905 for (var i = 0; i < bucket.length; ++i) { |
|
906 if (virtEquals(bucket[i].key, key)) { |
|
907 return true; |
|
908 } |
|
909 } |
|
910 return false; |
|
911 }; |
|
912 |
|
913 this.containsValue = function(value) { |
|
914 for (var i = 0; i < buckets.length; ++i) { |
|
915 var bucket = buckets[i]; |
|
916 if (bucket === undef) { |
|
917 continue; |
|
918 } |
|
919 for (var j = 0; j < bucket.length; ++j) { |
|
920 if (virtEquals(bucket[j].value, value)) { |
|
921 return true; |
|
922 } |
|
923 } |
|
924 } |
|
925 return false; |
|
926 }; |
|
927 |
|
928 this.entrySet = function() { |
|
929 return new Set( |
|
930 |
|
931 function(pair) { |
|
932 return new Entry(pair); |
|
933 }, |
|
934 |
|
935 function(pair) { |
|
936 return (pair instanceof Entry) && pair._isIn(hashMap); |
|
937 }, |
|
938 |
|
939 function(pair) { |
|
940 return hashMap.remove(pair.getKey()); |
|
941 }); |
|
942 }; |
|
943 |
|
944 this.get = function(key) { |
|
945 var index = getBucketIndex(key); |
|
946 var bucket = buckets[index]; |
|
947 if (bucket === undef) { |
|
948 return null; |
|
949 } |
|
950 for (var i = 0; i < bucket.length; ++i) { |
|
951 if (virtEquals(bucket[i].key, key)) { |
|
952 return bucket[i].value; |
|
953 } |
|
954 } |
|
955 return null; |
|
956 }; |
|
957 |
|
958 this.isEmpty = function() { |
|
959 return count === 0; |
|
960 }; |
|
961 |
|
962 this.keySet = function() { |
|
963 return new Set( |
|
964 // get key from pair |
|
965 function(pair) { |
|
966 return pair.key; |
|
967 }, |
|
968 // is-in test |
|
969 function(key) { |
|
970 return hashMap.containsKey(key); |
|
971 }, |
|
972 // remove from hashmap by key |
|
973 function(key) { |
|
974 return hashMap.remove(key); |
|
975 } |
|
976 ); |
|
977 }; |
|
978 |
|
979 this.values = function() { |
|
980 return new Set( |
|
981 // get value from pair |
|
982 function(pair) { |
|
983 return pair.value; |
|
984 }, |
|
985 // is-in test |
|
986 function(value) { |
|
987 return hashMap.containsValue(value); |
|
988 }, |
|
989 // remove from hashmap by value |
|
990 function(value) { |
|
991 return hashMap.removeByValue(value); |
|
992 } |
|
993 ); |
|
994 }; |
|
995 |
|
996 this.put = function(key, value) { |
|
997 var index = getBucketIndex(key); |
|
998 var bucket = buckets[index]; |
|
999 if (bucket === undef) { |
|
1000 ++count; |
|
1001 buckets[index] = [{ |
|
1002 key: key, |
|
1003 value: value |
|
1004 }]; |
|
1005 ensureLoad(); |
|
1006 return null; |
|
1007 } |
|
1008 for (var i = 0; i < bucket.length; ++i) { |
|
1009 if (virtEquals(bucket[i].key, key)) { |
|
1010 var previous = bucket[i].value; |
|
1011 bucket[i].value = value; |
|
1012 return previous; |
|
1013 } |
|
1014 } |
|
1015 ++count; |
|
1016 bucket.push({ |
|
1017 key: key, |
|
1018 value: value |
|
1019 }); |
|
1020 ensureLoad(); |
|
1021 return null; |
|
1022 }; |
|
1023 |
|
1024 this.putAll = function(m) { |
|
1025 var it = m.entrySet().iterator(); |
|
1026 while (it.hasNext()) { |
|
1027 var entry = it.next(); |
|
1028 this.put(entry.getKey(), entry.getValue()); |
|
1029 } |
|
1030 }; |
|
1031 |
|
1032 this.remove = function(key) { |
|
1033 var index = getBucketIndex(key); |
|
1034 var bucket = buckets[index]; |
|
1035 if (bucket === undef) { |
|
1036 return null; |
|
1037 } |
|
1038 for (var i = 0; i < bucket.length; ++i) { |
|
1039 if (virtEquals(bucket[i].key, key)) { |
|
1040 --count; |
|
1041 var previous = bucket[i].value; |
|
1042 bucket[i].removed = true; |
|
1043 if (bucket.length > 1) { |
|
1044 bucket.splice(i, 1); |
|
1045 } else { |
|
1046 buckets[index] = undef; |
|
1047 } |
|
1048 return previous; |
|
1049 } |
|
1050 } |
|
1051 return null; |
|
1052 }; |
|
1053 |
|
1054 this.removeByValue = function(value) { |
|
1055 var bucket, i, ilen, pair; |
|
1056 for (bucket in buckets) { |
|
1057 if (buckets.hasOwnProperty(bucket)) { |
|
1058 for (i = 0, ilen = buckets[bucket].length; i < ilen; i++) { |
|
1059 pair = buckets[bucket][i]; |
|
1060 // removal on values is based on identity, not equality |
|
1061 if (pair.value === value) { |
|
1062 buckets[bucket].splice(i, 1); |
|
1063 return true; |
|
1064 } |
|
1065 } |
|
1066 } |
|
1067 } |
|
1068 return false; |
|
1069 }; |
|
1070 |
|
1071 this.size = function() { |
|
1072 return count; |
|
1073 }; |
|
1074 } |
|
1075 |
|
1076 return HashMap; |
|
1077 }()); |
|
1078 |
|
1079 var PVector = (function() { |
|
1080 function PVector(x, y, z) { |
|
1081 this.x = x || 0; |
|
1082 this.y = y || 0; |
|
1083 this.z = z || 0; |
|
1084 } |
|
1085 |
|
1086 PVector.dist = function(v1, v2) { |
|
1087 return v1.dist(v2); |
|
1088 }; |
|
1089 |
|
1090 PVector.dot = function(v1, v2) { |
|
1091 return v1.dot(v2); |
|
1092 }; |
|
1093 |
|
1094 PVector.cross = function(v1, v2) { |
|
1095 return v1.cross(v2); |
|
1096 }; |
|
1097 |
|
1098 PVector.angleBetween = function(v1, v2) { |
|
1099 return Math.acos(v1.dot(v2) / (v1.mag() * v2.mag())); |
|
1100 }; |
|
1101 |
|
1102 // Common vector operations for PVector |
|
1103 PVector.prototype = { |
|
1104 set: function(v, y, z) { |
|
1105 if (arguments.length === 1) { |
|
1106 this.set(v.x || v[0] || 0, |
|
1107 v.y || v[1] || 0, |
|
1108 v.z || v[2] || 0); |
|
1109 } else { |
|
1110 this.x = v; |
|
1111 this.y = y; |
|
1112 this.z = z; |
|
1113 } |
|
1114 }, |
|
1115 get: function() { |
|
1116 return new PVector(this.x, this.y, this.z); |
|
1117 }, |
|
1118 mag: function() { |
|
1119 var x = this.x, |
|
1120 y = this.y, |
|
1121 z = this.z; |
|
1122 return Math.sqrt(x * x + y * y + z * z); |
|
1123 }, |
|
1124 add: function(v, y, z) { |
|
1125 if (arguments.length === 1) { |
|
1126 this.x += v.x; |
|
1127 this.y += v.y; |
|
1128 this.z += v.z; |
|
1129 } else { |
|
1130 this.x += v; |
|
1131 this.y += y; |
|
1132 this.z += z; |
|
1133 } |
|
1134 }, |
|
1135 sub: function(v, y, z) { |
|
1136 if (arguments.length === 1) { |
|
1137 this.x -= v.x; |
|
1138 this.y -= v.y; |
|
1139 this.z -= v.z; |
|
1140 } else { |
|
1141 this.x -= v; |
|
1142 this.y -= y; |
|
1143 this.z -= z; |
|
1144 } |
|
1145 }, |
|
1146 mult: function(v) { |
|
1147 if (typeof v === 'number') { |
|
1148 this.x *= v; |
|
1149 this.y *= v; |
|
1150 this.z *= v; |
|
1151 } else { |
|
1152 this.x *= v.x; |
|
1153 this.y *= v.y; |
|
1154 this.z *= v.z; |
|
1155 } |
|
1156 }, |
|
1157 div: function(v) { |
|
1158 if (typeof v === 'number') { |
|
1159 this.x /= v; |
|
1160 this.y /= v; |
|
1161 this.z /= v; |
|
1162 } else { |
|
1163 this.x /= v.x; |
|
1164 this.y /= v.y; |
|
1165 this.z /= v.z; |
|
1166 } |
|
1167 }, |
|
1168 dist: function(v) { |
|
1169 var dx = this.x - v.x, |
|
1170 dy = this.y - v.y, |
|
1171 dz = this.z - v.z; |
|
1172 return Math.sqrt(dx * dx + dy * dy + dz * dz); |
|
1173 }, |
|
1174 dot: function(v, y, z) { |
|
1175 if (arguments.length === 1) { |
|
1176 return (this.x * v.x + this.y * v.y + this.z * v.z); |
|
1177 } |
|
1178 return (this.x * v + this.y * y + this.z * z); |
|
1179 }, |
|
1180 cross: function(v) { |
|
1181 var x = this.x, |
|
1182 y = this.y, |
|
1183 z = this.z; |
|
1184 return new PVector(y * v.z - v.y * z, |
|
1185 z * v.x - v.z * x, |
|
1186 x * v.y - v.x * y); |
|
1187 }, |
|
1188 normalize: function() { |
|
1189 var m = this.mag(); |
|
1190 if (m > 0) { |
|
1191 this.div(m); |
|
1192 } |
|
1193 }, |
|
1194 limit: function(high) { |
|
1195 if (this.mag() > high) { |
|
1196 this.normalize(); |
|
1197 this.mult(high); |
|
1198 } |
|
1199 }, |
|
1200 heading2D: function() { |
|
1201 return (-Math.atan2(-this.y, this.x)); |
|
1202 }, |
|
1203 toString: function() { |
|
1204 return "[" + this.x + ", " + this.y + ", " + this.z + "]"; |
|
1205 }, |
|
1206 array: function() { |
|
1207 return [this.x, this.y, this.z]; |
|
1208 } |
|
1209 }; |
|
1210 |
|
1211 function createPVectorMethod(method) { |
|
1212 return function(v1, v2) { |
|
1213 var v = v1.get(); |
|
1214 v[method](v2); |
|
1215 return v; |
|
1216 }; |
|
1217 } |
|
1218 |
|
1219 for (var method in PVector.prototype) { |
|
1220 if (PVector.prototype.hasOwnProperty(method) && !PVector.hasOwnProperty(method)) { |
|
1221 PVector[method] = createPVectorMethod(method); |
|
1222 } |
|
1223 } |
|
1224 |
|
1225 return PVector; |
|
1226 }()); |
|
1227 |
|
1228 // Building defaultScope. Changing of the prototype protects |
|
1229 // internal Processing code from the changes in defaultScope |
|
1230 function DefaultScope() {} |
|
1231 DefaultScope.prototype = PConstants; |
|
1232 |
|
1233 var defaultScope = new DefaultScope(); |
|
1234 defaultScope.ArrayList = ArrayList; |
|
1235 defaultScope.HashMap = HashMap; |
|
1236 defaultScope.PVector = PVector; |
|
1237 defaultScope.ObjectIterator = ObjectIterator; |
|
1238 defaultScope.PConstants = PConstants; |
|
1239 //defaultScope.PImage = PImage; // TODO |
|
1240 //defaultScope.PShape = PShape; // TODO |
|
1241 //defaultScope.PShapeSVG = PShapeSVG; // TODO |
|
1242 |
|
1243 //////////////////////////////////////////////////////////////////////////// |
|
1244 // Class inheritance helper methods |
|
1245 //////////////////////////////////////////////////////////////////////////// |
|
1246 |
|
1247 defaultScope.defineProperty = function(obj, name, desc) { |
|
1248 if("defineProperty" in Object) { |
|
1249 Object.defineProperty(obj, name, desc); |
|
1250 } else { |
|
1251 if (desc.hasOwnProperty("get")) { |
|
1252 obj.__defineGetter__(name, desc.get); |
|
1253 } |
|
1254 if (desc.hasOwnProperty("set")) { |
|
1255 obj.__defineSetter__(name, desc.set); |
|
1256 } |
|
1257 } |
|
1258 }; |
|
1259 |
|
1260 function extendClass(subClass, baseClass) { |
|
1261 function extendGetterSetter(propertyName) { |
|
1262 defaultScope.defineProperty(subClass, propertyName, { |
|
1263 get: function() { |
|
1264 return baseClass[propertyName]; |
|
1265 }, |
|
1266 set: function(v) { |
|
1267 baseClass[propertyName]=v; |
|
1268 }, |
|
1269 enumerable: true |
|
1270 }); |
|
1271 } |
|
1272 |
|
1273 var properties = []; |
|
1274 for (var propertyName in baseClass) { |
|
1275 if (typeof baseClass[propertyName] === 'function') { |
|
1276 // Overriding all non-overriden functions |
|
1277 if (!subClass.hasOwnProperty(propertyName)) { |
|
1278 subClass[propertyName] = baseClass[propertyName]; |
|
1279 } |
|
1280 } else if(propertyName.charAt(0) !== "$" && !(propertyName in subClass)) { |
|
1281 // Delaying the properties extension due to the IE9 bug (see #918). |
|
1282 properties.push(propertyName); |
|
1283 } |
|
1284 } |
|
1285 while (properties.length > 0) { |
|
1286 extendGetterSetter(properties.shift()); |
|
1287 } |
|
1288 } |
|
1289 |
|
1290 defaultScope.extendClassChain = function(base) { |
|
1291 var path = [base]; |
|
1292 for (var self = base.$upcast; self; self = self.$upcast) { |
|
1293 extendClass(self, base); |
|
1294 path.push(self); |
|
1295 base = self; |
|
1296 } |
|
1297 while (path.length > 0) { |
|
1298 path.pop().$self=base; |
|
1299 } |
|
1300 }; |
|
1301 |
|
1302 defaultScope.extendStaticMembers = function(derived, base) { |
|
1303 extendClass(derived, base); |
|
1304 }; |
|
1305 |
|
1306 defaultScope.extendInterfaceMembers = function(derived, base) { |
|
1307 extendClass(derived, base); |
|
1308 }; |
|
1309 |
|
1310 defaultScope.addMethod = function(object, name, fn, superAccessor) { |
|
1311 if (object[name]) { |
|
1312 var args = fn.length, |
|
1313 oldfn = object[name]; |
|
1314 |
|
1315 object[name] = function() { |
|
1316 if (arguments.length === args) { |
|
1317 return fn.apply(this, arguments); |
|
1318 } |
|
1319 return oldfn.apply(this, arguments); |
|
1320 }; |
|
1321 } else { |
|
1322 object[name] = fn; |
|
1323 } |
|
1324 }; |
|
1325 |
|
1326 defaultScope.createJavaArray = function(type, bounds) { |
|
1327 var result = null; |
|
1328 if (typeof bounds[0] === 'number') { |
|
1329 var itemsCount = 0 | bounds[0]; |
|
1330 if (bounds.length <= 1) { |
|
1331 result = []; |
|
1332 result.length = itemsCount; |
|
1333 for (var i = 0; i < itemsCount; ++i) { |
|
1334 result[i] = 0; |
|
1335 } |
|
1336 } else { |
|
1337 result = []; |
|
1338 var newBounds = bounds.slice(1); |
|
1339 for (var j = 0; j < itemsCount; ++j) { |
|
1340 result.push(defaultScope.createJavaArray(type, newBounds)); |
|
1341 } |
|
1342 } |
|
1343 } |
|
1344 return result; |
|
1345 }; |
|
1346 |
|
1347 var colors = { |
|
1348 aliceblue: "#f0f8ff", |
|
1349 antiquewhite: "#faebd7", |
|
1350 aqua: "#00ffff", |
|
1351 aquamarine: "#7fffd4", |
|
1352 azure: "#f0ffff", |
|
1353 beige: "#f5f5dc", |
|
1354 bisque: "#ffe4c4", |
|
1355 black: "#000000", |
|
1356 blanchedalmond: "#ffebcd", |
|
1357 blue: "#0000ff", |
|
1358 blueviolet: "#8a2be2", |
|
1359 brown: "#a52a2a", |
|
1360 burlywood: "#deb887", |
|
1361 cadetblue: "#5f9ea0", |
|
1362 chartreuse: "#7fff00", |
|
1363 chocolate: "#d2691e", |
|
1364 coral: "#ff7f50", |
|
1365 cornflowerblue: "#6495ed", |
|
1366 cornsilk: "#fff8dc", |
|
1367 crimson: "#dc143c", |
|
1368 cyan: "#00ffff", |
|
1369 darkblue: "#00008b", |
|
1370 darkcyan: "#008b8b", |
|
1371 darkgoldenrod: "#b8860b", |
|
1372 darkgray: "#a9a9a9", |
|
1373 darkgreen: "#006400", |
|
1374 darkkhaki: "#bdb76b", |
|
1375 darkmagenta: "#8b008b", |
|
1376 darkolivegreen: "#556b2f", |
|
1377 darkorange: "#ff8c00", |
|
1378 darkorchid: "#9932cc", |
|
1379 darkred: "#8b0000", |
|
1380 darksalmon: "#e9967a", |
|
1381 darkseagreen: "#8fbc8f", |
|
1382 darkslateblue: "#483d8b", |
|
1383 darkslategray: "#2f4f4f", |
|
1384 darkturquoise: "#00ced1", |
|
1385 darkviolet: "#9400d3", |
|
1386 deeppink: "#ff1493", |
|
1387 deepskyblue: "#00bfff", |
|
1388 dimgray: "#696969", |
|
1389 dodgerblue: "#1e90ff", |
|
1390 firebrick: "#b22222", |
|
1391 floralwhite: "#fffaf0", |
|
1392 forestgreen: "#228b22", |
|
1393 fuchsia: "#ff00ff", |
|
1394 gainsboro: "#dcdcdc", |
|
1395 ghostwhite: "#f8f8ff", |
|
1396 gold: "#ffd700", |
|
1397 goldenrod: "#daa520", |
|
1398 gray: "#808080", |
|
1399 green: "#008000", |
|
1400 greenyellow: "#adff2f", |
|
1401 honeydew: "#f0fff0", |
|
1402 hotpink: "#ff69b4", |
|
1403 indianred: "#cd5c5c", |
|
1404 indigo: "#4b0082", |
|
1405 ivory: "#fffff0", |
|
1406 khaki: "#f0e68c", |
|
1407 lavender: "#e6e6fa", |
|
1408 lavenderblush: "#fff0f5", |
|
1409 lawngreen: "#7cfc00", |
|
1410 lemonchiffon: "#fffacd", |
|
1411 lightblue: "#add8e6", |
|
1412 lightcoral: "#f08080", |
|
1413 lightcyan: "#e0ffff", |
|
1414 lightgoldenrodyellow: "#fafad2", |
|
1415 lightgrey: "#d3d3d3", |
|
1416 lightgreen: "#90ee90", |
|
1417 lightpink: "#ffb6c1", |
|
1418 lightsalmon: "#ffa07a", |
|
1419 lightseagreen: "#20b2aa", |
|
1420 lightskyblue: "#87cefa", |
|
1421 lightslategray: "#778899", |
|
1422 lightsteelblue: "#b0c4de", |
|
1423 lightyellow: "#ffffe0", |
|
1424 lime: "#00ff00", |
|
1425 limegreen: "#32cd32", |
|
1426 linen: "#faf0e6", |
|
1427 magenta: "#ff00ff", |
|
1428 maroon: "#800000", |
|
1429 mediumaquamarine: "#66cdaa", |
|
1430 mediumblue: "#0000cd", |
|
1431 mediumorchid: "#ba55d3", |
|
1432 mediumpurple: "#9370d8", |
|
1433 mediumseagreen: "#3cb371", |
|
1434 mediumslateblue: "#7b68ee", |
|
1435 mediumspringgreen: "#00fa9a", |
|
1436 mediumturquoise: "#48d1cc", |
|
1437 mediumvioletred: "#c71585", |
|
1438 midnightblue: "#191970", |
|
1439 mintcream: "#f5fffa", |
|
1440 mistyrose: "#ffe4e1", |
|
1441 moccasin: "#ffe4b5", |
|
1442 navajowhite: "#ffdead", |
|
1443 navy: "#000080", |
|
1444 oldlace: "#fdf5e6", |
|
1445 olive: "#808000", |
|
1446 olivedrab: "#6b8e23", |
|
1447 orange: "#ffa500", |
|
1448 orangered: "#ff4500", |
|
1449 orchid: "#da70d6", |
|
1450 palegoldenrod: "#eee8aa", |
|
1451 palegreen: "#98fb98", |
|
1452 paleturquoise: "#afeeee", |
|
1453 palevioletred: "#d87093", |
|
1454 papayawhip: "#ffefd5", |
|
1455 peachpuff: "#ffdab9", |
|
1456 peru: "#cd853f", |
|
1457 pink: "#ffc0cb", |
|
1458 plum: "#dda0dd", |
|
1459 powderblue: "#b0e0e6", |
|
1460 purple: "#800080", |
|
1461 red: "#ff0000", |
|
1462 rosybrown: "#bc8f8f", |
|
1463 royalblue: "#4169e1", |
|
1464 saddlebrown: "#8b4513", |
|
1465 salmon: "#fa8072", |
|
1466 sandybrown: "#f4a460", |
|
1467 seagreen: "#2e8b57", |
|
1468 seashell: "#fff5ee", |
|
1469 sienna: "#a0522d", |
|
1470 silver: "#c0c0c0", |
|
1471 skyblue: "#87ceeb", |
|
1472 slateblue: "#6a5acd", |
|
1473 slategray: "#708090", |
|
1474 snow: "#fffafa", |
|
1475 springgreen: "#00ff7f", |
|
1476 steelblue: "#4682b4", |
|
1477 tan: "#d2b48c", |
|
1478 teal: "#008080", |
|
1479 thistle: "#d8bfd8", |
|
1480 tomato: "#ff6347", |
|
1481 turquoise: "#40e0d0", |
|
1482 violet: "#ee82ee", |
|
1483 wheat: "#f5deb3", |
|
1484 white: "#ffffff", |
|
1485 whitesmoke: "#f5f5f5", |
|
1486 yellow: "#ffff00", |
|
1487 yellowgreen: "#9acd32" |
|
1488 }; |
|
1489 |
|
1490 // Unsupported Processing File and I/O operations. |
|
1491 (function(Processing) { |
|
1492 var unsupportedP5 = ("open() createOutput() createInput() BufferedReader selectFolder() " + |
|
1493 "dataPath() createWriter() selectOutput() beginRecord() " + |
|
1494 "saveStream() endRecord() selectInput() saveBytes() createReader() " + |
|
1495 "beginRaw() endRaw() PrintWriter delay()").split(" "), |
|
1496 count = unsupportedP5.length, |
|
1497 prettyName, |
|
1498 p5Name; |
|
1499 |
|
1500 function createUnsupportedFunc(n) { |
|
1501 return function() { |
|
1502 throw "Processing.js does not support " + n + "."; |
|
1503 }; |
|
1504 } |
|
1505 |
|
1506 while (count--) { |
|
1507 prettyName = unsupportedP5[count]; |
|
1508 p5Name = prettyName.replace("()", ""); |
|
1509 |
|
1510 Processing[p5Name] = createUnsupportedFunc(prettyName); |
|
1511 } |
|
1512 }(defaultScope)); |
|
1513 |
|
1514 // screenWidth and screenHeight are shared by all instances. |
|
1515 // and return the width/height of the browser's viewport. |
|
1516 defaultScope.defineProperty(defaultScope, 'screenWidth', |
|
1517 { get: function() { return window.innerWidth; } }); |
|
1518 |
|
1519 defaultScope.defineProperty(defaultScope, 'screenHeight', |
|
1520 { get: function() { return window.innerHeight; } }); |
|
1521 |
|
1522 // Manage multiple Processing instances |
|
1523 var processingInstances = []; |
|
1524 var processingInstanceIds = {}; |
|
1525 |
|
1526 var removeInstance = function(id) { |
|
1527 processingInstances.splice(processingInstanceIds[id], 1); |
|
1528 delete processingInstanceIds[id]; |
|
1529 }; |
|
1530 |
|
1531 var addInstance = function(processing) { |
|
1532 if (processing.externals.canvas.id === undef || !processing.externals.canvas.id.length) { |
|
1533 processing.externals.canvas.id = "__processing" + processingInstances.length; |
|
1534 } |
|
1535 processingInstanceIds[processing.externals.canvas.id] = processingInstances.length; |
|
1536 processingInstances.push(processing); |
|
1537 }; |
|
1538 |
|
1539 |
|
1540 //////////////////////////////////////////////////////////////////////////// |
|
1541 // PFONT.JS START |
|
1542 //////////////////////////////////////////////////////////////////////////// |
|
1543 |
|
1544 /** |
|
1545 * [internal function] computeFontMetrics() calculates various metrics for text |
|
1546 * placement. Currently this function computes the ascent, descent and leading |
|
1547 * (from "lead", used for vertical space) values for the currently active font. |
|
1548 */ |
|
1549 function computeFontMetrics(pfont) { |
|
1550 var emQuad = 250, |
|
1551 correctionFactor = pfont.size / emQuad, |
|
1552 canvas = document.createElement("canvas"); |
|
1553 canvas.width = 2*emQuad; |
|
1554 canvas.height = 2*emQuad; |
|
1555 canvas.style.opacity = 0; |
|
1556 var cfmFont = pfont.getCSSDefinition(emQuad+"px", "normal"), |
|
1557 ctx = canvas.getContext("2d"); |
|
1558 ctx.font = cfmFont; |
|
1559 pfont.context2d = ctx; |
|
1560 |
|
1561 // Size the canvas using a string with common max-ascent and max-descent letters. |
|
1562 // Changing the canvas dimensions resets the context, so we must reset the font. |
|
1563 var protrusions = "dbflkhyjqpg"; |
|
1564 canvas.width = ctx.measureText(protrusions).width; |
|
1565 ctx.font = cfmFont; |
|
1566 |
|
1567 // for text lead values, we meaure a multiline text container. |
|
1568 var leadDiv = document.createElement("div"); |
|
1569 leadDiv.style.position = "absolute"; |
|
1570 leadDiv.style.opacity = 0; |
|
1571 leadDiv.style.fontFamily = '"' + pfont.name + '"'; |
|
1572 leadDiv.style.fontSize = emQuad + "px"; |
|
1573 leadDiv.innerHTML = protrusions + "<br/>" + protrusions; |
|
1574 document.body.appendChild(leadDiv); |
|
1575 |
|
1576 var w = canvas.width, |
|
1577 h = canvas.height, |
|
1578 baseline = h/2; |
|
1579 |
|
1580 // Set all canvas pixeldata values to 255, with all the content |
|
1581 // data being 0. This lets us scan for data[i] != 255. |
|
1582 ctx.fillStyle = "white"; |
|
1583 ctx.fillRect(0, 0, w, h); |
|
1584 ctx.fillStyle = "black"; |
|
1585 ctx.fillText(protrusions, 0, baseline); |
|
1586 var pixelData = ctx.getImageData(0, 0, w, h).data; |
|
1587 |
|
1588 // canvas pixel data is w*4 by h*4, because R, G, B and A are separate, |
|
1589 // consecutive values in the array, rather than stored as 32 bit ints. |
|
1590 var i = 0, |
|
1591 w4 = w * 4, |
|
1592 len = pixelData.length; |
|
1593 |
|
1594 // Finding the ascent uses a normal, forward scanline |
|
1595 while (++i < len && pixelData[i] === 255) { |
|
1596 nop(); |
|
1597 } |
|
1598 var ascent = Math.round(i / w4); |
|
1599 |
|
1600 // Finding the descent uses a reverse scanline |
|
1601 i = len - 1; |
|
1602 while (--i > 0 && pixelData[i] === 255) { |
|
1603 nop(); |
|
1604 } |
|
1605 var descent = Math.round(i / w4); |
|
1606 |
|
1607 // set font metrics |
|
1608 pfont.ascent = correctionFactor * (baseline - ascent); |
|
1609 pfont.descent = correctionFactor * (descent - baseline); |
|
1610 |
|
1611 // Then we try to get the real value from the browser |
|
1612 if (document.defaultView.getComputedStyle) { |
|
1613 var leadDivHeight = document.defaultView.getComputedStyle(leadDiv,null).getPropertyValue("height"); |
|
1614 leadDivHeight = correctionFactor * leadDivHeight.replace("px",""); |
|
1615 if (leadDivHeight >= pfont.size * 2) { |
|
1616 pfont.leading = Math.round(leadDivHeight/2); |
|
1617 } |
|
1618 } |
|
1619 document.body.removeChild(leadDiv); |
|
1620 } |
|
1621 |
|
1622 // Defines system (non-SVG) font. |
|
1623 function PFont(name, size) { |
|
1624 // according to the P5 API, new PFont() is legal (albeit completely useless) |
|
1625 if (name === undef) { |
|
1626 name = ""; |
|
1627 } |
|
1628 this.name = name; |
|
1629 if (size === undef) { |
|
1630 size = 0; |
|
1631 } |
|
1632 this.size = size; |
|
1633 this.glyph = false; |
|
1634 this.ascent = 0; |
|
1635 this.descent = 0; |
|
1636 // For leading, the "safe" value uses the standard TEX ratio |
|
1637 this.leading = 1.2 * size; |
|
1638 |
|
1639 // Note that an italic, bold font must used "... Bold Italic" |
|
1640 // in P5. "... Italic Bold" is treated as normal/normal. |
|
1641 var illegalIndicator = name.indexOf(" Italic Bold"); |
|
1642 if (illegalIndicator !== -1) { |
|
1643 name = name.substring(0, illegalIndicator); |
|
1644 } |
|
1645 |
|
1646 // determine font style |
|
1647 this.style = "normal"; |
|
1648 var italicsIndicator = name.indexOf(" Italic"); |
|
1649 if (italicsIndicator !== -1) { |
|
1650 name = name.substring(0, italicsIndicator); |
|
1651 this.style = "italic"; |
|
1652 } |
|
1653 |
|
1654 // determine font weight |
|
1655 this.weight = "normal"; |
|
1656 var boldIndicator = name.indexOf(" Bold"); |
|
1657 if (boldIndicator !== -1) { |
|
1658 name = name.substring(0, boldIndicator); |
|
1659 this.weight = "bold"; |
|
1660 } |
|
1661 |
|
1662 // determine font-family name |
|
1663 this.family = "sans-serif"; |
|
1664 if (name !== undef) { |
|
1665 switch(name) { |
|
1666 case "sans-serif": |
|
1667 case "serif": |
|
1668 case "monospace": |
|
1669 case "fantasy": |
|
1670 case "cursive": |
|
1671 this.family = name; |
|
1672 break; |
|
1673 default: |
|
1674 this.family = '"' + name + '", sans-serif'; |
|
1675 break; |
|
1676 } |
|
1677 } |
|
1678 // Calculate the ascent/descent/leading value based on |
|
1679 // how the browser renders this font. |
|
1680 this.context2d = null; |
|
1681 computeFontMetrics(this); |
|
1682 this.css = this.getCSSDefinition(); |
|
1683 this.context2d.font = this.css; |
|
1684 } |
|
1685 |
|
1686 /** |
|
1687 * This function generates the CSS "font" string for this PFont |
|
1688 */ |
|
1689 PFont.prototype.getCSSDefinition = function(fontSize, lineHeight) { |
|
1690 if(fontSize===undef) { |
|
1691 fontSize = this.size + "px"; |
|
1692 } |
|
1693 if(lineHeight===undef) { |
|
1694 lineHeight = this.leading + "px"; |
|
1695 } |
|
1696 // CSS "font" definition: font-style font-variant font-weight font-size/line-height font-family |
|
1697 var components = [this.style, "normal", this.weight, fontSize + "/" + lineHeight, this.family]; |
|
1698 return components.join(" "); |
|
1699 }; |
|
1700 |
|
1701 /** |
|
1702 * We cannot rely on there being a 2d context available, |
|
1703 * because we support OPENGL sketches, and canvas3d has |
|
1704 * no "measureText" function in the API. |
|
1705 */ |
|
1706 PFont.prototype.measureTextWidth = function(string) { |
|
1707 return this.context2d.measureText(string).width; |
|
1708 }; |
|
1709 |
|
1710 /** |
|
1711 * Global "loaded fonts" list, internal to PFont |
|
1712 */ |
|
1713 PFont.PFontCache = {}; |
|
1714 |
|
1715 /** |
|
1716 * This function acts as single access point for getting and caching |
|
1717 * fonts across all sketches handled by an instance of Processing.js |
|
1718 */ |
|
1719 PFont.get = function(fontName, fontSize) { |
|
1720 var cache = PFont.PFontCache; |
|
1721 var idx = fontName+"/"+fontSize; |
|
1722 if (!cache[idx]) { |
|
1723 cache[idx] = new PFont(fontName, fontSize); |
|
1724 } |
|
1725 return cache[idx]; |
|
1726 }; |
|
1727 |
|
1728 /** |
|
1729 * Lists all standard fonts. Due to browser limitations, this list is |
|
1730 * not the system font list, like in P5, but the CSS "genre" list. |
|
1731 */ |
|
1732 PFont.list = function() { |
|
1733 return ["sans-serif", "serif", "monospace", "fantasy", "cursive"]; |
|
1734 }; |
|
1735 |
|
1736 /** |
|
1737 * Loading external fonts through @font-face rules is handled by PFont, |
|
1738 * to ensure fonts loaded in this way are globally available. |
|
1739 */ |
|
1740 PFont.preloading = { |
|
1741 // template element used to compare font sizes |
|
1742 template: {}, |
|
1743 // indicates whether or not the reference tiny font has been loaded |
|
1744 initialized: false, |
|
1745 // load the reference tiny font via a css @font-face rule |
|
1746 initialize: function() { |
|
1747 var generateTinyFont = function() { |
|
1748 var encoded = "#E3KAI2wAgT1MvMg7Eo3VmNtYX7ABi3CxnbHlm" + |
|
1749 "7Abw3kaGVhZ7ACs3OGhoZWE7A53CRobXR47AY3" + |
|
1750 "AGbG9jYQ7G03Bm1heH7ABC3CBuYW1l7Ae3AgcG" + |
|
1751 "9zd7AI3AE#B3AQ2kgTY18PPPUACwAg3ALSRoo3" + |
|
1752 "#yld0xg32QAB77#E777773B#E3C#I#Q77773E#" + |
|
1753 "Q7777777772CMAIw7AB77732B#M#Q3wAB#g3B#" + |
|
1754 "E#E2BB//82BB////w#B7#gAEg3E77x2B32B#E#" + |
|
1755 "Q#MTcBAQ32gAe#M#QQJ#E32M#QQJ#I#g32Q77#"; |
|
1756 var expand = function(input) { |
|
1757 return "AAAAAAAA".substr(~~input ? 7-input : 6); |
|
1758 }; |
|
1759 return encoded.replace(/[#237]/g, expand); |
|
1760 }; |
|
1761 var fontface = document.createElement("style"); |
|
1762 fontface.setAttribute("type","text/css"); |
|
1763 fontface.innerHTML = "@font-face {\n" + |
|
1764 ' font-family: "PjsEmptyFont";' + "\n" + |
|
1765 " src: url('data:application/x-font-ttf;base64,"+generateTinyFont()+"')\n" + |
|
1766 " format('truetype');\n" + |
|
1767 "}"; |
|
1768 document.head.appendChild(fontface); |
|
1769 |
|
1770 // set up the template element |
|
1771 var element = document.createElement("span"); |
|
1772 element.style.cssText = 'position: absolute; top: 0; left: 0; opacity: 0; font-family: "PjsEmptyFont", fantasy;'; |
|
1773 element.innerHTML = "AAAAAAAA"; |
|
1774 document.body.appendChild(element); |
|
1775 this.template = element; |
|
1776 |
|
1777 this.initialized = true; |
|
1778 }, |
|
1779 // Shorthand function to get the computed width for an element. |
|
1780 getElementWidth: function(element) { |
|
1781 return document.defaultView.getComputedStyle(element,"").getPropertyValue("width"); |
|
1782 }, |
|
1783 // time taken so far in attempting to load a font |
|
1784 timeAttempted: 0, |
|
1785 // returns false if no fonts are pending load, or true otherwise. |
|
1786 pending: function(intervallength) { |
|
1787 if (!this.initialized) { |
|
1788 this.initialize(); |
|
1789 } |
|
1790 var element, |
|
1791 computedWidthFont, |
|
1792 computedWidthRef = this.getElementWidth(this.template); |
|
1793 for (var i = 0; i < this.fontList.length; i++) { |
|
1794 // compares size of text in pixels. if equal, custom font is not yet loaded |
|
1795 element = this.fontList[i]; |
|
1796 computedWidthFont = this.getElementWidth(element); |
|
1797 if (this.timeAttempted < 4000 && computedWidthFont === computedWidthRef) { |
|
1798 this.timeAttempted += intervallength; |
|
1799 return true; |
|
1800 } else { |
|
1801 document.body.removeChild(element); |
|
1802 this.fontList.splice(i--, 1); |
|
1803 this.timeAttempted = 0; |
|
1804 } |
|
1805 } |
|
1806 // if there are no more fonts to load, pending is false |
|
1807 if (this.fontList.length === 0) { |
|
1808 return false; |
|
1809 } |
|
1810 // We should have already returned before getting here. |
|
1811 // But, if we do get here, length!=0 so fonts are pending. |
|
1812 return true; |
|
1813 }, |
|
1814 // fontList contains elements to compare font sizes against a template |
|
1815 fontList: [], |
|
1816 // addedList contains the fontnames of all the fonts loaded via @font-face |
|
1817 addedList: {}, |
|
1818 // adds a font to the font cache |
|
1819 // creates an element using the font, to start loading the font, |
|
1820 // and compare against a default font to see if the custom font is loaded |
|
1821 add: function(fontSrc) { |
|
1822 if (!this.initialized) { |
|
1823 this.initialize(); |
|
1824 } |
|
1825 // fontSrc can be a string or a javascript object |
|
1826 // acceptable fonts are .ttf, .otf, and data uri |
|
1827 var fontName = (typeof fontSrc === 'object' ? fontSrc.fontFace : fontSrc), |
|
1828 fontUrl = (typeof fontSrc === 'object' ? fontSrc.url : fontSrc); |
|
1829 |
|
1830 // check whether we already created the @font-face rule for this font |
|
1831 if (this.addedList[fontName]) { |
|
1832 return; |
|
1833 } |
|
1834 |
|
1835 // if we didn't, create the @font-face rule |
|
1836 var style = document.createElement("style"); |
|
1837 style.setAttribute("type","text/css"); |
|
1838 style.innerHTML = "@font-face{\n font-family: '" + fontName + "';\n src: url('" + fontUrl + "');\n}\n"; |
|
1839 document.head.appendChild(style); |
|
1840 this.addedList[fontName] = true; |
|
1841 |
|
1842 // also create the element to load and compare the new font |
|
1843 var element = document.createElement("span"); |
|
1844 element.style.cssText = "position: absolute; top: 0; left: 0; opacity: 0;"; |
|
1845 element.style.fontFamily = '"' + fontName + '", "PjsEmptyFont", fantasy'; |
|
1846 element.innerHTML = "AAAAAAAA"; |
|
1847 document.body.appendChild(element); |
|
1848 this.fontList.push(element); |
|
1849 } |
|
1850 }; |
|
1851 |
|
1852 |
|
1853 // add to the default scope |
|
1854 defaultScope.PFont = PFont; |
|
1855 |
|
1856 |
|
1857 //////////////////////////////////////////////////////////////////////////// |
|
1858 // PFONT.JS END |
|
1859 //////////////////////////////////////////////////////////////////////////// |
|
1860 |
|
1861 |
|
1862 var Processing = this.Processing = function(aCanvas, aCode) { |
|
1863 // Previously we allowed calling Processing as a func instead of ctor, but no longer. |
|
1864 if (!(this instanceof Processing)) { |
|
1865 throw("called Processing constructor as if it were a function: missing 'new'."); |
|
1866 } |
|
1867 |
|
1868 var curElement, |
|
1869 pgraphicsMode = (aCanvas === undef && aCode === undef); |
|
1870 |
|
1871 if (pgraphicsMode) { |
|
1872 curElement = document.createElement("canvas"); |
|
1873 } else { |
|
1874 // We'll take a canvas element or a string for a canvas element's id |
|
1875 curElement = typeof aCanvas === "string" ? document.getElementById(aCanvas) : aCanvas; |
|
1876 } |
|
1877 |
|
1878 if (!(curElement instanceof HTMLCanvasElement)) { |
|
1879 throw("called Processing constructor without passing canvas element reference or id."); |
|
1880 } |
|
1881 |
|
1882 function unimplemented(s) { |
|
1883 Processing.debug('Unimplemented - ' + s); |
|
1884 } |
|
1885 |
|
1886 // When something new is added to "p." it must also be added to the "names" array. |
|
1887 // The names array contains the names of everything that is inside "p." |
|
1888 var p = this; |
|
1889 |
|
1890 // PJS specific (non-p5) methods and properties to externalize |
|
1891 p.externals = { |
|
1892 canvas: curElement, |
|
1893 context: undef, |
|
1894 sketch: undef |
|
1895 }; |
|
1896 |
|
1897 p.name = 'Processing.js Instance'; // Set Processing defaults / environment variables |
|
1898 p.use3DContext = false; // default '2d' canvas context |
|
1899 |
|
1900 /** |
|
1901 * Confirms if a Processing program is "focused", meaning that it is |
|
1902 * active and will accept input from mouse or keyboard. This variable |
|
1903 * is "true" if it is focused and "false" if not. This variable is |
|
1904 * often used when you want to warn people they need to click on the |
|
1905 * browser before it will work. |
|
1906 */ |
|
1907 p.focused = false; |
|
1908 p.breakShape = false; |
|
1909 |
|
1910 // Glyph path storage for textFonts |
|
1911 p.glyphTable = {}; |
|
1912 |
|
1913 // Global vars for tracking mouse position |
|
1914 p.pmouseX = 0; |
|
1915 p.pmouseY = 0; |
|
1916 p.mouseX = 0; |
|
1917 p.mouseY = 0; |
|
1918 p.mouseButton = 0; |
|
1919 p.mouseScroll = 0; |
|
1920 |
|
1921 // Undefined event handlers to be replaced by user when needed |
|
1922 p.mouseClicked = undef; |
|
1923 p.mouseDragged = undef; |
|
1924 p.mouseMoved = undef; |
|
1925 p.mousePressed = undef; |
|
1926 p.mouseReleased = undef; |
|
1927 p.mouseScrolled = undef; |
|
1928 p.mouseOver = undef; |
|
1929 p.mouseOut = undef; |
|
1930 p.touchStart = undef; |
|
1931 p.touchEnd = undef; |
|
1932 p.touchMove = undef; |
|
1933 p.touchCancel = undef; |
|
1934 p.key = undef; |
|
1935 p.keyCode = undef; |
|
1936 p.keyPressed = nop; // needed to remove function checks |
|
1937 p.keyReleased = nop; |
|
1938 p.keyTyped = nop; |
|
1939 p.draw = undef; |
|
1940 p.setup = undef; |
|
1941 |
|
1942 // Remapped vars |
|
1943 p.__mousePressed = false; |
|
1944 p.__keyPressed = false; |
|
1945 p.__frameRate = 60; |
|
1946 |
|
1947 // The current animation frame |
|
1948 p.frameCount = 0; |
|
1949 |
|
1950 // The height/width of the canvas |
|
1951 p.width = 100; |
|
1952 p.height = 100; |
|
1953 |
|
1954 // "Private" variables used to maintain state |
|
1955 var curContext, |
|
1956 curSketch, |
|
1957 drawing, // hold a Drawing2D or Drawing3D object |
|
1958 online = true, |
|
1959 doFill = true, |
|
1960 fillStyle = [1.0, 1.0, 1.0, 1.0], |
|
1961 currentFillColor = 0xFFFFFFFF, |
|
1962 isFillDirty = true, |
|
1963 doStroke = true, |
|
1964 strokeStyle = [0.0, 0.0, 0.0, 1.0], |
|
1965 currentStrokeColor = 0xFF000000, |
|
1966 isStrokeDirty = true, |
|
1967 lineWidth = 1, |
|
1968 loopStarted = false, |
|
1969 renderSmooth = false, |
|
1970 doLoop = true, |
|
1971 looping = 0, |
|
1972 curRectMode = PConstants.CORNER, |
|
1973 curEllipseMode = PConstants.CENTER, |
|
1974 normalX = 0, |
|
1975 normalY = 0, |
|
1976 normalZ = 0, |
|
1977 normalMode = PConstants.NORMAL_MODE_AUTO, |
|
1978 curFrameRate = 60, |
|
1979 curMsPerFrame = 1000/curFrameRate, |
|
1980 curCursor = PConstants.ARROW, |
|
1981 oldCursor = curElement.style.cursor, |
|
1982 curShape = PConstants.POLYGON, |
|
1983 curShapeCount = 0, |
|
1984 curvePoints = [], |
|
1985 curTightness = 0, |
|
1986 curveDet = 20, |
|
1987 curveInited = false, |
|
1988 backgroundObj = -3355444, // rgb(204, 204, 204) is the default gray background colour |
|
1989 bezDetail = 20, |
|
1990 colorModeA = 255, |
|
1991 colorModeX = 255, |
|
1992 colorModeY = 255, |
|
1993 colorModeZ = 255, |
|
1994 pathOpen = false, |
|
1995 mouseDragging = false, |
|
1996 pmouseXLastFrame = 0, |
|
1997 pmouseYLastFrame = 0, |
|
1998 curColorMode = PConstants.RGB, |
|
1999 curTint = null, |
|
2000 curTint3d = null, |
|
2001 getLoaded = false, |
|
2002 start = Date.now(), |
|
2003 timeSinceLastFPS = start, |
|
2004 framesSinceLastFPS = 0, |
|
2005 textcanvas, |
|
2006 curveBasisMatrix, |
|
2007 curveToBezierMatrix, |
|
2008 curveDrawMatrix, |
|
2009 bezierDrawMatrix, |
|
2010 bezierBasisInverse, |
|
2011 bezierBasisMatrix, |
|
2012 curContextCache = { attributes: {}, locations: {} }, |
|
2013 // Shaders |
|
2014 programObject3D, |
|
2015 programObject2D, |
|
2016 programObjectUnlitShape, |
|
2017 boxBuffer, |
|
2018 boxNormBuffer, |
|
2019 boxOutlineBuffer, |
|
2020 rectBuffer, |
|
2021 rectNormBuffer, |
|
2022 sphereBuffer, |
|
2023 lineBuffer, |
|
2024 fillBuffer, |
|
2025 fillColorBuffer, |
|
2026 strokeColorBuffer, |
|
2027 pointBuffer, |
|
2028 shapeTexVBO, |
|
2029 canTex, // texture for createGraphics |
|
2030 textTex, // texture for 3d tex |
|
2031 curTexture = {width:0,height:0}, |
|
2032 curTextureMode = PConstants.IMAGE, |
|
2033 usingTexture = false, |
|
2034 textBuffer, |
|
2035 textureBuffer, |
|
2036 indexBuffer, |
|
2037 // Text alignment |
|
2038 horizontalTextAlignment = PConstants.LEFT, |
|
2039 verticalTextAlignment = PConstants.BASELINE, |
|
2040 textMode = PConstants.MODEL, |
|
2041 // Font state |
|
2042 curFontName = "Arial", |
|
2043 curTextSize = 12, |
|
2044 curTextAscent = 9, |
|
2045 curTextDescent = 2, |
|
2046 curTextLeading = 14, |
|
2047 curTextFont = PFont.get(curFontName, curTextSize), |
|
2048 // Pixels cache |
|
2049 originalContext, |
|
2050 proxyContext = null, |
|
2051 isContextReplaced = false, |
|
2052 setPixelsCached, |
|
2053 maxPixelsCached = 1000, |
|
2054 pressedKeysMap = [], |
|
2055 lastPressedKeyCode = null, |
|
2056 codedKeys = [ PConstants.SHIFT, PConstants.CONTROL, PConstants.ALT, PConstants.CAPSLK, PConstants.PGUP, PConstants.PGDN, |
|
2057 PConstants.END, PConstants.HOME, PConstants.LEFT, PConstants.UP, PConstants.RIGHT, PConstants.DOWN, PConstants.NUMLK, |
|
2058 PConstants.INSERT, PConstants.F1, PConstants.F2, PConstants.F3, PConstants.F4, PConstants.F5, PConstants.F6, PConstants.F7, |
|
2059 PConstants.F8, PConstants.F9, PConstants.F10, PConstants.F11, PConstants.F12, PConstants.META ]; |
|
2060 |
|
2061 // Get padding and border style widths for mouse offsets |
|
2062 var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop; |
|
2063 |
|
2064 if (document.defaultView && document.defaultView.getComputedStyle) { |
|
2065 stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingLeft'], 10) || 0; |
|
2066 stylePaddingTop = parseInt(document.defaultView.getComputedStyle(curElement, null)['paddingTop'], 10) || 0; |
|
2067 styleBorderLeft = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderLeftWidth'], 10) || 0; |
|
2068 styleBorderTop = parseInt(document.defaultView.getComputedStyle(curElement, null)['borderTopWidth'], 10) || 0; |
|
2069 } |
|
2070 |
|
2071 // User can only have MAX_LIGHTS lights |
|
2072 var lightCount = 0; |
|
2073 |
|
2074 //sphere stuff |
|
2075 var sphereDetailV = 0, |
|
2076 sphereDetailU = 0, |
|
2077 sphereX = [], |
|
2078 sphereY = [], |
|
2079 sphereZ = [], |
|
2080 sinLUT = new Float32Array(PConstants.SINCOS_LENGTH), |
|
2081 cosLUT = new Float32Array(PConstants.SINCOS_LENGTH), |
|
2082 sphereVerts, |
|
2083 sphereNorms; |
|
2084 |
|
2085 // Camera defaults and settings |
|
2086 var cam, |
|
2087 cameraInv, |
|
2088 modelView, |
|
2089 modelViewInv, |
|
2090 userMatrixStack, |
|
2091 userReverseMatrixStack, |
|
2092 inverseCopy, |
|
2093 projection, |
|
2094 manipulatingCamera = false, |
|
2095 frustumMode = false, |
|
2096 cameraFOV = 60 * (Math.PI / 180), |
|
2097 cameraX = p.width / 2, |
|
2098 cameraY = p.height / 2, |
|
2099 cameraZ = cameraY / Math.tan(cameraFOV / 2), |
|
2100 cameraNear = cameraZ / 10, |
|
2101 cameraFar = cameraZ * 10, |
|
2102 cameraAspect = p.width / p.height; |
|
2103 |
|
2104 var vertArray = [], |
|
2105 curveVertArray = [], |
|
2106 curveVertCount = 0, |
|
2107 isCurve = false, |
|
2108 isBezier = false, |
|
2109 firstVert = true; |
|
2110 |
|
2111 //PShape stuff |
|
2112 var curShapeMode = PConstants.CORNER; |
|
2113 |
|
2114 // Stores states for pushStyle() and popStyle(). |
|
2115 var styleArray = []; |
|
2116 |
|
2117 // Vertices are specified in a counter-clockwise order |
|
2118 // triangles are in this order: back, front, right, bottom, left, top |
|
2119 var boxVerts = new Float32Array([ |
|
2120 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, |
|
2121 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, |
|
2122 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, |
|
2123 0.5, -0.5, -0.5, 0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, |
|
2124 -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, |
|
2125 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5]); |
|
2126 |
|
2127 var boxOutlineVerts = new Float32Array([ |
|
2128 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, -0.5, 0.5, -0.5, -0.5, |
|
2129 -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, |
|
2130 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, |
|
2131 -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, |
|
2132 0.5, -0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5, -0.5, -0.5, -0.5, -0.5, |
|
2133 -0.5, -0.5, -0.5, -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, -0.5, 0.5]); |
|
2134 |
|
2135 var boxNorms = new Float32Array([ |
|
2136 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, |
|
2137 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, |
|
2138 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, |
|
2139 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, |
|
2140 -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, |
|
2141 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]); |
|
2142 |
|
2143 // These verts are used for the fill and stroke using TRIANGLE_FAN and LINE_LOOP |
|
2144 var rectVerts = new Float32Array([0,0,0, 0,1,0, 1,1,0, 1,0,0]); |
|
2145 |
|
2146 var rectNorms = new Float32Array([0,0,1, 0,0,1, 0,0,1, 0,0,1]); |
|
2147 |
|
2148 |
|
2149 // Shader for points and lines in begin/endShape |
|
2150 var vShaderSrcUnlitShape = |
|
2151 "varying vec4 frontColor;" + |
|
2152 |
|
2153 "attribute vec3 aVertex;" + |
|
2154 "attribute vec4 aColor;" + |
|
2155 |
|
2156 "uniform mat4 uView;" + |
|
2157 "uniform mat4 uProjection;" + |
|
2158 "uniform float pointSize;" + |
|
2159 |
|
2160 "void main(void) {" + |
|
2161 " frontColor = aColor;" + |
|
2162 " gl_PointSize = pointSize;" + |
|
2163 " gl_Position = uProjection * uView * vec4(aVertex, 1.0);" + |
|
2164 "}"; |
|
2165 |
|
2166 var fShaderSrcUnlitShape = |
|
2167 "#ifdef GL_ES\n" + |
|
2168 "precision highp float;\n" + |
|
2169 "#endif\n" + |
|
2170 |
|
2171 "varying vec4 frontColor;" + |
|
2172 |
|
2173 "void main(void){" + |
|
2174 " gl_FragColor = frontColor;" + |
|
2175 "}"; |
|
2176 |
|
2177 // Shader for rect, text, box outlines, sphere outlines, point() and line() |
|
2178 var vertexShaderSource2D = |
|
2179 "varying vec4 frontColor;" + |
|
2180 |
|
2181 "attribute vec3 Vertex;" + |
|
2182 "attribute vec2 aTextureCoord;" + |
|
2183 "uniform vec4 color;" + |
|
2184 |
|
2185 "uniform mat4 model;" + |
|
2186 "uniform mat4 view;" + |
|
2187 "uniform mat4 projection;" + |
|
2188 "uniform float pointSize;" + |
|
2189 "varying vec2 vTextureCoord;"+ |
|
2190 |
|
2191 "void main(void) {" + |
|
2192 " gl_PointSize = pointSize;" + |
|
2193 " frontColor = color;" + |
|
2194 " gl_Position = projection * view * model * vec4(Vertex, 1.0);" + |
|
2195 " vTextureCoord = aTextureCoord;" + |
|
2196 "}"; |
|
2197 |
|
2198 var fragmentShaderSource2D = |
|
2199 "#ifdef GL_ES\n" + |
|
2200 "precision highp float;\n" + |
|
2201 "#endif\n" + |
|
2202 |
|
2203 "varying vec4 frontColor;" + |
|
2204 "varying vec2 vTextureCoord;"+ |
|
2205 |
|
2206 "uniform sampler2D uSampler;"+ |
|
2207 "uniform int picktype;"+ |
|
2208 |
|
2209 "void main(void){" + |
|
2210 " if(picktype == 0){"+ |
|
2211 " gl_FragColor = frontColor;" + |
|
2212 " }" + |
|
2213 " else if(picktype == 1){"+ |
|
2214 " float alpha = texture2D(uSampler, vTextureCoord).a;"+ |
|
2215 " gl_FragColor = vec4(frontColor.rgb*alpha, alpha);\n"+ |
|
2216 " }"+ |
|
2217 "}"; |
|
2218 |
|
2219 var webglMaxTempsWorkaround = /Windows/.test(navigator.userAgent); |
|
2220 |
|
2221 // Vertex shader for boxes and spheres |
|
2222 var vertexShaderSource3D = |
|
2223 "varying vec4 frontColor;" + |
|
2224 |
|
2225 "attribute vec3 Vertex;" + |
|
2226 "attribute vec3 Normal;" + |
|
2227 "attribute vec4 aColor;" + |
|
2228 "attribute vec2 aTexture;" + |
|
2229 "varying vec2 vTexture;" + |
|
2230 |
|
2231 "uniform vec4 color;" + |
|
2232 |
|
2233 "uniform bool usingMat;" + |
|
2234 "uniform vec3 specular;" + |
|
2235 "uniform vec3 mat_emissive;" + |
|
2236 "uniform vec3 mat_ambient;" + |
|
2237 "uniform vec3 mat_specular;" + |
|
2238 "uniform float shininess;" + |
|
2239 |
|
2240 "uniform mat4 model;" + |
|
2241 "uniform mat4 view;" + |
|
2242 "uniform mat4 projection;" + |
|
2243 "uniform mat4 normalTransform;" + |
|
2244 |
|
2245 "uniform int lightCount;" + |
|
2246 "uniform vec3 falloff;" + |
|
2247 |
|
2248 // careful changing the order of these fields. Some cards |
|
2249 // have issues with memory alignment |
|
2250 "struct Light {" + |
|
2251 " int type;" + |
|
2252 " vec3 color;" + |
|
2253 " vec3 position;" + |
|
2254 " vec3 direction;" + |
|
2255 " float angle;" + |
|
2256 " vec3 halfVector;" + |
|
2257 " float concentration;" + |
|
2258 "};" + |
|
2259 |
|
2260 // nVidia cards have issues with arrays of structures |
|
2261 // so instead we create 8 instances of Light |
|
2262 "uniform Light lights0;" + |
|
2263 "uniform Light lights1;" + |
|
2264 "uniform Light lights2;" + |
|
2265 "uniform Light lights3;" + |
|
2266 "uniform Light lights4;" + |
|
2267 "uniform Light lights5;" + |
|
2268 "uniform Light lights6;" + |
|
2269 "uniform Light lights7;" + |
|
2270 |
|
2271 // GLSL does not support switch |
|
2272 "Light getLight(int index){" + |
|
2273 " if(index == 0) return lights0;" + |
|
2274 " if(index == 1) return lights1;" + |
|
2275 " if(index == 2) return lights2;" + |
|
2276 " if(index == 3) return lights3;" + |
|
2277 " if(index == 4) return lights4;" + |
|
2278 " if(index == 5) return lights5;" + |
|
2279 " if(index == 6) return lights6;" + |
|
2280 // Do not use a conditional for the last return statement |
|
2281 // because some video cards will fail and complain that |
|
2282 // "not all paths return" |
|
2283 " return lights7;" + |
|
2284 "}" + |
|
2285 |
|
2286 "void AmbientLight( inout vec3 totalAmbient, in vec3 ecPos, in Light light ) {" + |
|
2287 // Get the vector from the light to the vertex |
|
2288 // Get the distance from the current vector to the light position |
|
2289 " float d = length( light.position - ecPos );" + |
|
2290 " float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" + |
|
2291 " totalAmbient += light.color * attenuation;" + |
|
2292 "}" + |
|
2293 |
|
2294 "void DirectionalLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + |
|
2295 " float powerfactor = 0.0;" + |
|
2296 " float nDotVP = max(0.0, dot( vertNormal, normalize(-light.position) ));" + |
|
2297 " float nDotVH = max(0.0, dot( vertNormal, normalize(-light.position-normalize(ecPos) )));" + |
|
2298 |
|
2299 " if( nDotVP != 0.0 ){" + |
|
2300 " powerfactor = pow( nDotVH, shininess );" + |
|
2301 " }" + |
|
2302 |
|
2303 " col += light.color * nDotVP;" + |
|
2304 " spec += specular * powerfactor;" + |
|
2305 "}" + |
|
2306 |
|
2307 "void PointLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + |
|
2308 " float powerfactor;" + |
|
2309 |
|
2310 // Get the vector from the light to the vertex |
|
2311 " vec3 VP = light.position - ecPos;" + |
|
2312 |
|
2313 // Get the distance from the current vector to the light position |
|
2314 " float d = length( VP ); " + |
|
2315 |
|
2316 // Normalize the light ray so it can be used in the dot product operation. |
|
2317 " VP = normalize( VP );" + |
|
2318 |
|
2319 " float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ));" + |
|
2320 |
|
2321 " float nDotVP = max( 0.0, dot( vertNormal, VP ));" + |
|
2322 " vec3 halfVector = normalize( VP - normalize(ecPos) );" + |
|
2323 " float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" + |
|
2324 |
|
2325 " if( nDotVP == 0.0) {" + |
|
2326 " powerfactor = 0.0;" + |
|
2327 " }" + |
|
2328 " else{" + |
|
2329 " powerfactor = pow( nDotHV, shininess );" + |
|
2330 " }" + |
|
2331 |
|
2332 " spec += specular * powerfactor * attenuation;" + |
|
2333 " col += light.color * nDotVP * attenuation;" + |
|
2334 "}" + |
|
2335 |
|
2336 /* |
|
2337 */ |
|
2338 "void SpotLight( inout vec3 col, inout vec3 spec, in vec3 vertNormal, in vec3 ecPos, in Light light ) {" + |
|
2339 " float spotAttenuation;" + |
|
2340 " float powerfactor;" + |
|
2341 |
|
2342 // calculate the vector from the current vertex to the light. |
|
2343 " vec3 VP = light.position - ecPos; " + |
|
2344 " vec3 ldir = normalize( -light.direction );" + |
|
2345 |
|
2346 // get the distance from the spotlight and the vertex |
|
2347 " float d = length( VP );" + |
|
2348 " VP = normalize( VP );" + |
|
2349 |
|
2350 " float attenuation = 1.0 / ( falloff[0] + ( falloff[1] * d ) + ( falloff[2] * d * d ) );" + |
|
2351 |
|
2352 // dot product of the vector from vertex to light and light direction. |
|
2353 " float spotDot = dot( VP, ldir );" + |
|
2354 |
|
2355 // if the vertex falls inside the cone |
|
2356 (webglMaxTempsWorkaround ? // Windows reports max temps error if light.angle is used |
|
2357 " spotAttenuation = 1.0; " : |
|
2358 " if( spotDot > cos( light.angle ) ) {" + |
|
2359 " spotAttenuation = pow( spotDot, light.concentration );" + |
|
2360 " }" + |
|
2361 " else{" + |
|
2362 " spotAttenuation = 0.0;" + |
|
2363 " }" + |
|
2364 " attenuation *= spotAttenuation;" + |
|
2365 "") + |
|
2366 |
|
2367 " float nDotVP = max( 0.0, dot( vertNormal, VP ));" + |
|
2368 " vec3 halfVector = normalize( VP - normalize(ecPos) );" + |
|
2369 " float nDotHV = max( 0.0, dot( vertNormal, halfVector ));" + |
|
2370 |
|
2371 " if( nDotVP == 0.0 ) {" + |
|
2372 " powerfactor = 0.0;" + |
|
2373 " }" + |
|
2374 " else {" + |
|
2375 " powerfactor = pow( nDotHV, shininess );" + |
|
2376 " }" + |
|
2377 |
|
2378 " spec += specular * powerfactor * attenuation;" + |
|
2379 " col += light.color * nDotVP * attenuation;" + |
|
2380 "}" + |
|
2381 |
|
2382 "void main(void) {" + |
|
2383 " vec3 finalAmbient = vec3( 0.0, 0.0, 0.0 );" + |
|
2384 " vec3 finalDiffuse = vec3( 0.0, 0.0, 0.0 );" + |
|
2385 " vec3 finalSpecular = vec3( 0.0, 0.0, 0.0 );" + |
|
2386 |
|
2387 " vec4 col = color;" + |
|
2388 |
|
2389 " if(color[0] == -1.0){" + |
|
2390 " col = aColor;" + |
|
2391 " }" + |
|
2392 |
|
2393 // We use the sphere vertices as the normals when we create the sphere buffer. |
|
2394 // But this only works if the sphere vertices are unit length, so we |
|
2395 // have to normalize the normals here. Since this is only required for spheres |
|
2396 // we could consider placing this in a conditional later on. |
|
2397 " vec3 norm = normalize(vec3( normalTransform * vec4( Normal, 0.0 ) ));" + |
|
2398 |
|
2399 " vec4 ecPos4 = view * model * vec4(Vertex,1.0);" + |
|
2400 " vec3 ecPos = (vec3(ecPos4))/ecPos4.w;" + |
|
2401 |
|
2402 // If there were no lights this draw call, just use the |
|
2403 // assigned fill color of the shape and the specular value |
|
2404 " if( lightCount == 0 ) {" + |
|
2405 " frontColor = col + vec4(mat_specular,1.0);" + |
|
2406 " }" + |
|
2407 " else {" + |
|
2408 // WebGL forces us to iterate over a constant value |
|
2409 // so we can't iterate using lightCount |
|
2410 " for( int i = 0; i < 8; i++ ) {" + |
|
2411 " Light l = getLight(i);" + |
|
2412 |
|
2413 // We can stop iterating if we know we have gone past |
|
2414 // the number of lights which are on |
|
2415 " if( i >= lightCount ){" + |
|
2416 " break;" + |
|
2417 " }" + |
|
2418 |
|
2419 " if( l.type == 0 ) {" + |
|
2420 " AmbientLight( finalAmbient, ecPos, l );" + |
|
2421 " }" + |
|
2422 " else if( l.type == 1 ) {" + |
|
2423 " DirectionalLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + |
|
2424 " }" + |
|
2425 " else if( l.type == 2 ) {" + |
|
2426 " PointLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + |
|
2427 " }" + |
|
2428 " else {" + |
|
2429 " SpotLight( finalDiffuse, finalSpecular, norm, ecPos, l );" + |
|
2430 " }" + |
|
2431 " }" + |
|
2432 |
|
2433 " if( usingMat == false ) {" + |
|
2434 " frontColor = vec4(" + |
|
2435 " vec3(col) * finalAmbient +" + |
|
2436 " vec3(col) * finalDiffuse +" + |
|
2437 " vec3(col) * finalSpecular," + |
|
2438 " col[3] );" + |
|
2439 " }" + |
|
2440 " else{" + |
|
2441 " frontColor = vec4( " + |
|
2442 " mat_emissive + " + |
|
2443 " (vec3(col) * mat_ambient * finalAmbient) + " + |
|
2444 " (vec3(col) * finalDiffuse) + " + |
|
2445 " (mat_specular * finalSpecular), " + |
|
2446 " col[3] );" + |
|
2447 " }" + |
|
2448 " }" + |
|
2449 |
|
2450 " vTexture.xy = aTexture.xy;" + |
|
2451 " gl_Position = projection * view * model * vec4( Vertex, 1.0 );" + |
|
2452 "}"; |
|
2453 |
|
2454 var fragmentShaderSource3D = |
|
2455 "#ifdef GL_ES\n" + |
|
2456 "precision highp float;\n" + |
|
2457 "#endif\n" + |
|
2458 |
|
2459 "varying vec4 frontColor;" + |
|
2460 |
|
2461 "uniform sampler2D sampler;" + |
|
2462 "uniform bool usingTexture;" + |
|
2463 "varying vec2 vTexture;" + |
|
2464 |
|
2465 // In Processing, when a texture is used, the fill color is ignored |
|
2466 // vec4(1.0,1.0,1.0,0.5) |
|
2467 "void main(void){" + |
|
2468 " if(usingTexture){" + |
|
2469 " gl_FragColor = vec4(texture2D(sampler, vTexture.xy)) * frontColor;" + |
|
2470 " }"+ |
|
2471 " else{" + |
|
2472 " gl_FragColor = frontColor;" + |
|
2473 " }" + |
|
2474 "}"; |
|
2475 |
|
2476 //////////////////////////////////////////////////////////////////////////// |
|
2477 // 3D Functions |
|
2478 //////////////////////////////////////////////////////////////////////////// |
|
2479 |
|
2480 /* |
|
2481 * Sets a uniform variable in a program object to a particular |
|
2482 * value. Before calling this function, ensure the correct |
|
2483 * program object has been installed as part of the current |
|
2484 * rendering state by calling useProgram. |
|
2485 * |
|
2486 * On some systems, if the variable exists in the shader but isn't used, |
|
2487 * the compiler will optimize it out and this function will fail. |
|
2488 * |
|
2489 * @param {WebGLProgram} programObj program object returned from |
|
2490 * createProgramObject |
|
2491 * @param {String} varName the name of the variable in the shader |
|
2492 * @param {float | Array} varValue either a scalar value or an Array |
|
2493 * |
|
2494 * @returns none |
|
2495 * |
|
2496 * @see uniformi |
|
2497 * @see uniformMatrix |
|
2498 */ |
|
2499 function uniformf(cacheId, programObj, varName, varValue) { |
|
2500 var varLocation = curContextCache.locations[cacheId]; |
|
2501 if(varLocation === undef) { |
|
2502 varLocation = curContext.getUniformLocation(programObj, varName); |
|
2503 curContextCache.locations[cacheId] = varLocation; |
|
2504 } |
|
2505 // the variable won't be found if it was optimized out. |
|
2506 if (varLocation !== null) { |
|
2507 if (varValue.length === 4) { |
|
2508 curContext.uniform4fv(varLocation, varValue); |
|
2509 } else if (varValue.length === 3) { |
|
2510 curContext.uniform3fv(varLocation, varValue); |
|
2511 } else if (varValue.length === 2) { |
|
2512 curContext.uniform2fv(varLocation, varValue); |
|
2513 } else { |
|
2514 curContext.uniform1f(varLocation, varValue); |
|
2515 } |
|
2516 } |
|
2517 } |
|
2518 |
|
2519 /** |
|
2520 * Sets a uniform int or int array in a program object to a particular |
|
2521 * value. Before calling this function, ensure the correct |
|
2522 * program object has been installed as part of the current |
|
2523 * rendering state. |
|
2524 * |
|
2525 * On some systems, if the variable exists in the shader but isn't used, |
|
2526 * the compiler will optimize it out and this function will fail. |
|
2527 * |
|
2528 * @param {WebGLProgram} programObj program object returned from |
|
2529 * createProgramObject |
|
2530 * @param {String} varName the name of the variable in the shader |
|
2531 * @param {int | Array} varValue either a scalar value or an Array |
|
2532 * |
|
2533 * @returns none |
|
2534 * |
|
2535 * @see uniformf |
|
2536 * @see uniformMatrix |
|
2537 */ |
|
2538 function uniformi(cacheId, programObj, varName, varValue) { |
|
2539 var varLocation = curContextCache.locations[cacheId]; |
|
2540 if(varLocation === undef) { |
|
2541 varLocation = curContext.getUniformLocation(programObj, varName); |
|
2542 curContextCache.locations[cacheId] = varLocation; |
|
2543 } |
|
2544 // the variable won't be found if it was optimized out. |
|
2545 if (varLocation !== null) { |
|
2546 if (varValue.length === 4) { |
|
2547 curContext.uniform4iv(varLocation, varValue); |
|
2548 } else if (varValue.length === 3) { |
|
2549 curContext.uniform3iv(varLocation, varValue); |
|
2550 } else if (varValue.length === 2) { |
|
2551 curContext.uniform2iv(varLocation, varValue); |
|
2552 } else { |
|
2553 curContext.uniform1i(varLocation, varValue); |
|
2554 } |
|
2555 } |
|
2556 } |
|
2557 |
|
2558 /** |
|
2559 * Sets the value of a uniform matrix variable in a program |
|
2560 * object. Before calling this function, ensure the correct |
|
2561 * program object has been installed as part of the current |
|
2562 * rendering state. |
|
2563 * |
|
2564 * On some systems, if the variable exists in the shader but |
|
2565 * isn't used, the compiler will optimize it out and this |
|
2566 * function will fail. |
|
2567 * |
|
2568 * @param {WebGLProgram} programObj program object returned from |
|
2569 * createProgramObject |
|
2570 * @param {String} varName the name of the variable in the shader |
|
2571 * @param {boolean} transpose must be false |
|
2572 * @param {Array} matrix an array of 4, 9 or 16 values |
|
2573 * |
|
2574 * @returns none |
|
2575 * |
|
2576 * @see uniformi |
|
2577 * @see uniformf |
|
2578 */ |
|
2579 function uniformMatrix(cacheId, programObj, varName, transpose, matrix) { |
|
2580 var varLocation = curContextCache.locations[cacheId]; |
|
2581 if(varLocation === undef) { |
|
2582 varLocation = curContext.getUniformLocation(programObj, varName); |
|
2583 curContextCache.locations[cacheId] = varLocation; |
|
2584 } |
|
2585 // the variable won't be found if it was optimized out. |
|
2586 if (varLocation !== -1) { |
|
2587 if (matrix.length === 16) { |
|
2588 curContext.uniformMatrix4fv(varLocation, transpose, matrix); |
|
2589 } else if (matrix.length === 9) { |
|
2590 curContext.uniformMatrix3fv(varLocation, transpose, matrix); |
|
2591 } else { |
|
2592 curContext.uniformMatrix2fv(varLocation, transpose, matrix); |
|
2593 } |
|
2594 } |
|
2595 } |
|
2596 |
|
2597 /** |
|
2598 * Binds the VBO, sets the vertex attribute data for the program |
|
2599 * object and enables the attribute. |
|
2600 * |
|
2601 * On some systems, if the attribute exists in the shader but |
|
2602 * isn't used, the compiler will optimize it out and this |
|
2603 * function will fail. |
|
2604 * |
|
2605 * @param {WebGLProgram} programObj program object returned from |
|
2606 * createProgramObject |
|
2607 * @param {String} varName the name of the variable in the shader |
|
2608 * @param {int} size the number of components per vertex attribute |
|
2609 * @param {WebGLBuffer} VBO Vertex Buffer Object |
|
2610 * |
|
2611 * @returns none |
|
2612 * |
|
2613 * @see disableVertexAttribPointer |
|
2614 */ |
|
2615 function vertexAttribPointer(cacheId, programObj, varName, size, VBO) { |
|
2616 var varLocation = curContextCache.attributes[cacheId]; |
|
2617 if(varLocation === undef) { |
|
2618 varLocation = curContext.getAttribLocation(programObj, varName); |
|
2619 curContextCache.attributes[cacheId] = varLocation; |
|
2620 } |
|
2621 if (varLocation !== -1) { |
|
2622 curContext.bindBuffer(curContext.ARRAY_BUFFER, VBO); |
|
2623 curContext.vertexAttribPointer(varLocation, size, curContext.FLOAT, false, 0, 0); |
|
2624 curContext.enableVertexAttribArray(varLocation); |
|
2625 } |
|
2626 } |
|
2627 |
|
2628 /** |
|
2629 * Disables a program object attribute from being sent to WebGL. |
|
2630 * |
|
2631 * @param {WebGLProgram} programObj program object returned from |
|
2632 * createProgramObject |
|
2633 * @param {String} varName name of the attribute |
|
2634 * |
|
2635 * @returns none |
|
2636 * |
|
2637 * @see vertexAttribPointer |
|
2638 */ |
|
2639 function disableVertexAttribPointer(cacheId, programObj, varName){ |
|
2640 var varLocation = curContextCache.attributes[cacheId]; |
|
2641 if(varLocation === undef) { |
|
2642 varLocation = curContext.getAttribLocation(programObj, varName); |
|
2643 curContextCache.attributes[cacheId] = varLocation; |
|
2644 } |
|
2645 if (varLocation !== -1) { |
|
2646 curContext.disableVertexAttribArray(varLocation); |
|
2647 } |
|
2648 } |
|
2649 |
|
2650 /** |
|
2651 * Creates a WebGL program object. |
|
2652 * |
|
2653 * @param {String} vetexShaderSource |
|
2654 * @param {String} fragmentShaderSource |
|
2655 * |
|
2656 * @returns {WebGLProgram} A program object |
|
2657 */ |
|
2658 var createProgramObject = function(curContext, vetexShaderSource, fragmentShaderSource) { |
|
2659 var vertexShaderObject = curContext.createShader(curContext.VERTEX_SHADER); |
|
2660 curContext.shaderSource(vertexShaderObject, vetexShaderSource); |
|
2661 curContext.compileShader(vertexShaderObject); |
|
2662 if (!curContext.getShaderParameter(vertexShaderObject, curContext.COMPILE_STATUS)) { |
|
2663 throw curContext.getShaderInfoLog(vertexShaderObject); |
|
2664 } |
|
2665 |
|
2666 var fragmentShaderObject = curContext.createShader(curContext.FRAGMENT_SHADER); |
|
2667 curContext.shaderSource(fragmentShaderObject, fragmentShaderSource); |
|
2668 curContext.compileShader(fragmentShaderObject); |
|
2669 if (!curContext.getShaderParameter(fragmentShaderObject, curContext.COMPILE_STATUS)) { |
|
2670 throw curContext.getShaderInfoLog(fragmentShaderObject); |
|
2671 } |
|
2672 |
|
2673 var programObject = curContext.createProgram(); |
|
2674 curContext.attachShader(programObject, vertexShaderObject); |
|
2675 curContext.attachShader(programObject, fragmentShaderObject); |
|
2676 curContext.linkProgram(programObject); |
|
2677 if (!curContext.getProgramParameter(programObject, curContext.LINK_STATUS)) { |
|
2678 throw "Error linking shaders."; |
|
2679 } |
|
2680 |
|
2681 return programObject; |
|
2682 }; |
|
2683 |
|
2684 //////////////////////////////////////////////////////////////////////////// |
|
2685 // 2D/3D drawing handling |
|
2686 //////////////////////////////////////////////////////////////////////////// |
|
2687 var imageModeCorner = function(x, y, w, h, whAreSizes) { |
|
2688 return { |
|
2689 x: x, |
|
2690 y: y, |
|
2691 w: w, |
|
2692 h: h |
|
2693 }; |
|
2694 }; |
|
2695 var imageModeConvert = imageModeCorner; |
|
2696 |
|
2697 var imageModeCorners = function(x, y, w, h, whAreSizes) { |
|
2698 return { |
|
2699 x: x, |
|
2700 y: y, |
|
2701 w: whAreSizes ? w : w - x, |
|
2702 h: whAreSizes ? h : h - y |
|
2703 }; |
|
2704 }; |
|
2705 |
|
2706 var imageModeCenter = function(x, y, w, h, whAreSizes) { |
|
2707 return { |
|
2708 x: x - w / 2, |
|
2709 y: y - h / 2, |
|
2710 w: w, |
|
2711 h: h |
|
2712 }; |
|
2713 }; |
|
2714 |
|
2715 // Objects for shared, 2D and 3D contexts |
|
2716 var DrawingShared = function(){}; |
|
2717 var Drawing2D = function(){}; |
|
2718 var Drawing3D = function(){}; |
|
2719 var DrawingPre = function(){}; |
|
2720 |
|
2721 // Setup the prototype chain |
|
2722 Drawing2D.prototype = new DrawingShared(); |
|
2723 Drawing2D.prototype.constructor = Drawing2D; |
|
2724 Drawing3D.prototype = new DrawingShared(); |
|
2725 Drawing3D.prototype.constructor = Drawing3D; |
|
2726 DrawingPre.prototype = new DrawingShared(); |
|
2727 DrawingPre.prototype.constructor = DrawingPre; |
|
2728 |
|
2729 // A no-op function for when the user calls 3D functions from a 2D sketch |
|
2730 // We can change this to a throw or console.error() later if we want |
|
2731 DrawingShared.prototype.a3DOnlyFunction = nop; |
|
2732 |
|
2733 //////////////////////////////////////////////////////////////////////////// |
|
2734 // Char handling |
|
2735 //////////////////////////////////////////////////////////////////////////// |
|
2736 var charMap = {}; |
|
2737 |
|
2738 var Char = p.Character = function(chr) { |
|
2739 if (typeof chr === 'string' && chr.length === 1) { |
|
2740 this.code = chr.charCodeAt(0); |
|
2741 } else if (typeof chr === 'number') { |
|
2742 this.code = chr; |
|
2743 } else if (chr instanceof Char) { |
|
2744 this.code = chr; |
|
2745 } else { |
|
2746 this.code = NaN; |
|
2747 } |
|
2748 |
|
2749 return (charMap[this.code] === undef) ? charMap[this.code] = this : charMap[this.code]; |
|
2750 }; |
|
2751 |
|
2752 Char.prototype.toString = function() { |
|
2753 return String.fromCharCode(this.code); |
|
2754 }; |
|
2755 |
|
2756 Char.prototype.valueOf = function() { |
|
2757 return this.code; |
|
2758 }; |
|
2759 |
|
2760 /** |
|
2761 * Datatype for storing shapes. Processing can currently load and display SVG (Scalable Vector Graphics) shapes. |
|
2762 * Before a shape is used, it must be loaded with the <b>loadShape()</b> function. The <b>shape()</b> function is used to draw the shape to the display window. |
|
2763 * The <b>PShape</b> object contain a group of methods, linked below, that can operate on the shape data. |
|
2764 * <br><br>The <b>loadShape()</b> method supports SVG files created with Inkscape and Adobe Illustrator. |
|
2765 * It is not a full SVG implementation, but offers some straightforward support for handling vector data. |
|
2766 * |
|
2767 * @param {int} family the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY |
|
2768 * |
|
2769 * @see #shape() |
|
2770 * @see #loadShape() |
|
2771 * @see #shapeMode() |
|
2772 */ |
|
2773 var PShape = p.PShape = function(family) { |
|
2774 this.family = family || PConstants.GROUP; |
|
2775 this.visible = true; |
|
2776 this.style = true; |
|
2777 this.children = []; |
|
2778 this.nameTable = []; |
|
2779 this.params = []; |
|
2780 this.name = ""; |
|
2781 this.image = null; //type PImage |
|
2782 this.matrix = null; |
|
2783 this.kind = null; |
|
2784 this.close = null; |
|
2785 this.width = null; |
|
2786 this.height = null; |
|
2787 this.parent = null; |
|
2788 }; |
|
2789 /** |
|
2790 * PShape methods |
|
2791 * missing: findChild(), apply(), contains(), findChild(), getPrimitive(), getParams(), getVertex() , getVertexCount(), |
|
2792 * getVertexCode() , getVertexCodes() , getVertexCodeCount(), getVertexX(), getVertexY(), getVertexZ() |
|
2793 */ |
|
2794 PShape.prototype = { |
|
2795 /** |
|
2796 * @member PShape |
|
2797 * The isVisible() function returns a boolean value "true" if the image is set to be visible, "false" if not. This is modified with the <b>setVisible()</b> parameter. |
|
2798 * <br><br>The visibility of a shape is usually controlled by whatever program created the SVG file. |
|
2799 * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. |
|
2800 * |
|
2801 * @return {boolean} returns "true" if the image is set to be visible, "false" if not |
|
2802 */ |
|
2803 isVisible: function(){ |
|
2804 return this.visible; |
|
2805 }, |
|
2806 /** |
|
2807 * @member PShape |
|
2808 * The setVisible() function sets the shape to be visible or invisible. This is determined by the value of the <b>visible</b> parameter. |
|
2809 * <br><br>The visibility of a shape is usually controlled by whatever program created the SVG file. |
|
2810 * For instance, this parameter is controlled by showing or hiding the shape in the layers palette in Adobe Illustrator. |
|
2811 * |
|
2812 * @param {boolean} visible "false" makes the shape invisible and "true" makes it visible |
|
2813 */ |
|
2814 setVisible: function (visible){ |
|
2815 this.visible = visible; |
|
2816 }, |
|
2817 /** |
|
2818 * @member PShape |
|
2819 * The disableStyle() function disables the shape's style data and uses Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. |
|
2820 * Overrides this shape's style information and uses PGraphics styles and colors. Identical to ignoreStyles(true). Also disables styles for all child shapes. |
|
2821 */ |
|
2822 disableStyle: function(){ |
|
2823 this.style = false; |
|
2824 for(var i = 0, j=this.children.length; i<j; i++) { |
|
2825 this.children[i].disableStyle(); |
|
2826 } |
|
2827 }, |
|
2828 /** |
|
2829 * @member PShape |
|
2830 * The enableStyle() function enables the shape's style data and ignores Processing's current styles. Styles include attributes such as colors, stroke weight, and stroke joints. |
|
2831 */ |
|
2832 enableStyle: function(){ |
|
2833 this.style = true; |
|
2834 for(var i = 0, j=this.children.length; i<j; i++) { |
|
2835 this.children[i].enableStyle(); |
|
2836 } |
|
2837 }, |
|
2838 /** |
|
2839 * @member PShape |
|
2840 * The getFamily function returns the shape type |
|
2841 * |
|
2842 * @return {int} the shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY |
|
2843 */ |
|
2844 getFamily: function(){ |
|
2845 return this.family; |
|
2846 }, |
|
2847 /** |
|
2848 * @member PShape |
|
2849 * The getWidth() function gets the width of the drawing area (not necessarily the shape boundary). |
|
2850 */ |
|
2851 getWidth: function(){ |
|
2852 return this.width; |
|
2853 }, |
|
2854 /** |
|
2855 * @member PShape |
|
2856 * The getHeight() function gets the height of the drawing area (not necessarily the shape boundary). |
|
2857 */ |
|
2858 getHeight: function(){ |
|
2859 return this.height; |
|
2860 }, |
|
2861 /** |
|
2862 * @member PShape |
|
2863 * The setName() function sets the name of the shape |
|
2864 * |
|
2865 * @param {String} name the name of the shape |
|
2866 */ |
|
2867 setName: function(name){ |
|
2868 this.name = name; |
|
2869 }, |
|
2870 /** |
|
2871 * @member PShape |
|
2872 * The getName() function returns the name of the shape |
|
2873 * |
|
2874 * @return {String} the name of the shape |
|
2875 */ |
|
2876 getName: function(){ |
|
2877 return this.name; |
|
2878 }, |
|
2879 /** |
|
2880 * @member PShape |
|
2881 * Called by the following (the shape() command adds the g) |
|
2882 * PShape s = loadShapes("blah.svg"); |
|
2883 * shape(s); |
|
2884 */ |
|
2885 draw: function(){ |
|
2886 if (this.visible) { |
|
2887 this.pre(); |
|
2888 this.drawImpl(); |
|
2889 this.post(); |
|
2890 } |
|
2891 }, |
|
2892 /** |
|
2893 * @member PShape |
|
2894 * the drawImpl() function draws the SVG document. |
|
2895 */ |
|
2896 drawImpl: function(){ |
|
2897 if (this.family === PConstants.GROUP) { |
|
2898 this.drawGroup(); |
|
2899 } else if (this.family === PConstants.PRIMITIVE) { |
|
2900 this.drawPrimitive(); |
|
2901 } else if (this.family === PConstants.GEOMETRY) { |
|
2902 this.drawGeometry(); |
|
2903 } else if (this.family === PConstants.PATH) { |
|
2904 this.drawPath(); |
|
2905 } |
|
2906 }, |
|
2907 /** |
|
2908 * @member PShape |
|
2909 * The drawPath() function draws the <path> part of the SVG document. |
|
2910 */ |
|
2911 drawPath: function(){ |
|
2912 var i, j; |
|
2913 if (this.vertices.length === 0) { return; } |
|
2914 p.beginShape(); |
|
2915 if (this.vertexCodes.length === 0) { // each point is a simple vertex |
|
2916 if (this.vertices[0].length === 2) { // drawing 2D vertices |
|
2917 for (i = 0, j = this.vertices.length; i < j; i++) { |
|
2918 p.vertex(this.vertices[i][0], this.vertices[i][1]); |
|
2919 } |
|
2920 } else { // drawing 3D vertices |
|
2921 for (i = 0, j = this.vertices.length; i < j; i++) { |
|
2922 p.vertex(this.vertices[i][0], |
|
2923 this.vertices[i][1], |
|
2924 this.vertices[i][2]); |
|
2925 } |
|
2926 } |
|
2927 } else { // coded set of vertices |
|
2928 var index = 0; |
|
2929 if (this.vertices[0].length === 2) { // drawing a 2D path |
|
2930 for (i = 0, j = this.vertexCodes.length; i < j; i++) { |
|
2931 if (this.vertexCodes[i] === PConstants.VERTEX) { |
|
2932 p.vertex(this.vertices[index][0], this.vertices[index][1]); |
|
2933 if ( this.vertices[index]["moveTo"] === true) { |
|
2934 vertArray[vertArray.length-1]["moveTo"] = true; |
|
2935 } else if ( this.vertices[index]["moveTo"] === false) { |
|
2936 vertArray[vertArray.length-1]["moveTo"] = false; |
|
2937 } |
|
2938 p.breakShape = false; |
|
2939 index++; |
|
2940 } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { |
|
2941 p.bezierVertex(this.vertices[index+0][0], |
|
2942 this.vertices[index+0][1], |
|
2943 this.vertices[index+1][0], |
|
2944 this.vertices[index+1][1], |
|
2945 this.vertices[index+2][0], |
|
2946 this.vertices[index+2][1]); |
|
2947 index += 3; |
|
2948 } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { |
|
2949 p.curveVertex(this.vertices[index][0], |
|
2950 this.vertices[index][1]); |
|
2951 index++; |
|
2952 } else if (this.vertexCodes[i] === PConstants.BREAK) { |
|
2953 p.breakShape = true; |
|
2954 } |
|
2955 } |
|
2956 } else { // drawing a 3D path |
|
2957 for (i = 0, j = this.vertexCodes.length; i < j; i++) { |
|
2958 if (this.vertexCodes[i] === PConstants.VERTEX) { |
|
2959 p.vertex(this.vertices[index][0], |
|
2960 this.vertices[index][1], |
|
2961 this.vertices[index][2]); |
|
2962 if (this.vertices[index]["moveTo"] === true) { |
|
2963 vertArray[vertArray.length-1]["moveTo"] = true; |
|
2964 } else if (this.vertices[index]["moveTo"] === false) { |
|
2965 vertArray[vertArray.length-1]["moveTo"] = false; |
|
2966 } |
|
2967 p.breakShape = false; |
|
2968 } else if (this.vertexCodes[i] === PConstants.BEZIER_VERTEX) { |
|
2969 p.bezierVertex(this.vertices[index+0][0], |
|
2970 this.vertices[index+0][1], |
|
2971 this.vertices[index+0][2], |
|
2972 this.vertices[index+1][0], |
|
2973 this.vertices[index+1][1], |
|
2974 this.vertices[index+1][2], |
|
2975 this.vertices[index+2][0], |
|
2976 this.vertices[index+2][1], |
|
2977 this.vertices[index+2][2]); |
|
2978 index += 3; |
|
2979 } else if (this.vertexCodes[i] === PConstants.CURVE_VERTEX) { |
|
2980 p.curveVertex(this.vertices[index][0], |
|
2981 this.vertices[index][1], |
|
2982 this.vertices[index][2]); |
|
2983 index++; |
|
2984 } else if (this.vertexCodes[i] === PConstants.BREAK) { |
|
2985 p.breakShape = true; |
|
2986 } |
|
2987 } |
|
2988 } |
|
2989 } |
|
2990 p.endShape(this.close ? PConstants.CLOSE : PConstants.OPEN); |
|
2991 }, |
|
2992 /** |
|
2993 * @member PShape |
|
2994 * The drawGeometry() function draws the geometry part of the SVG document. |
|
2995 */ |
|
2996 drawGeometry: function() { |
|
2997 var i, j; |
|
2998 p.beginShape(this.kind); |
|
2999 if (this.style) { |
|
3000 for (i = 0, j = this.vertices.length; i < j; i++) { |
|
3001 p.vertex(this.vertices[i]); |
|
3002 } |
|
3003 } else { |
|
3004 for (i = 0, j = this.vertices.length; i < j; i++) { |
|
3005 var vert = this.vertices[i]; |
|
3006 if (vert[2] === 0) { |
|
3007 p.vertex(vert[0], vert[1]); |
|
3008 } else { |
|
3009 p.vertex(vert[0], vert[1], vert[2]); |
|
3010 } |
|
3011 } |
|
3012 } |
|
3013 p.endShape(); |
|
3014 }, |
|
3015 /** |
|
3016 * @member PShape |
|
3017 * The drawGroup() function draws the <g> part of the SVG document. |
|
3018 */ |
|
3019 drawGroup: function() { |
|
3020 for (var i = 0, j = this.children.length; i < j; i++) { |
|
3021 this.children[i].draw(); |
|
3022 } |
|
3023 }, |
|
3024 /** |
|
3025 * @member PShape |
|
3026 * The drawPrimitive() function draws SVG document shape elements. These can be point, line, triangle, quad, rect, ellipse, arc, box, or sphere. |
|
3027 */ |
|
3028 drawPrimitive: function() { |
|
3029 if (this.kind === PConstants.POINT) { |
|
3030 p.point(this.params[0], this.params[1]); |
|
3031 } else if (this.kind === PConstants.LINE) { |
|
3032 if (this.params.length === 4) { // 2D |
|
3033 p.line(this.params[0], this.params[1], |
|
3034 this.params[2], this.params[3]); |
|
3035 } else { // 3D |
|
3036 p.line(this.params[0], this.params[1], this.params[2], |
|
3037 this.params[3], this.params[4], this.params[5]); |
|
3038 } |
|
3039 } else if (this.kind === PConstants.TRIANGLE) { |
|
3040 p.triangle(this.params[0], this.params[1], |
|
3041 this.params[2], this.params[3], |
|
3042 this.params[4], this.params[5]); |
|
3043 } else if (this.kind === PConstants.QUAD) { |
|
3044 p.quad(this.params[0], this.params[1], |
|
3045 this.params[2], this.params[3], |
|
3046 this.params[4], this.params[5], |
|
3047 this.params[6], this.params[7]); |
|
3048 } else if (this.kind === PConstants.RECT) { |
|
3049 if (this.image !== null) { |
|
3050 p.imageMode(PConstants.CORNER); |
|
3051 p.image(this.image, |
|
3052 this.params[0], |
|
3053 this.params[1], |
|
3054 this.params[2], |
|
3055 this.params[3]); |
|
3056 } else { |
|
3057 p.rectMode(PConstants.CORNER); |
|
3058 p.rect(this.params[0], |
|
3059 this.params[1], |
|
3060 this.params[2], |
|
3061 this.params[3]); |
|
3062 } |
|
3063 } else if (this.kind === PConstants.ELLIPSE) { |
|
3064 p.ellipseMode(PConstants.CORNER); |
|
3065 p.ellipse(this.params[0], |
|
3066 this.params[1], |
|
3067 this.params[2], |
|
3068 this.params[3]); |
|
3069 } else if (this.kind === PConstants.ARC) { |
|
3070 p.ellipseMode(PConstants.CORNER); |
|
3071 p.arc(this.params[0], |
|
3072 this.params[1], |
|
3073 this.params[2], |
|
3074 this.params[3], |
|
3075 this.params[4], |
|
3076 this.params[5]); |
|
3077 } else if (this.kind === PConstants.BOX) { |
|
3078 if (this.params.length === 1) { |
|
3079 p.box(this.params[0]); |
|
3080 } else { |
|
3081 p.box(this.params[0], this.params[1], this.params[2]); |
|
3082 } |
|
3083 } else if (this.kind === PConstants.SPHERE) { |
|
3084 p.sphere(this.params[0]); |
|
3085 } |
|
3086 }, |
|
3087 /** |
|
3088 * @member PShape |
|
3089 * The pre() function performs the preparations before the SVG is drawn. This includes doing transformations and storing previous styles. |
|
3090 */ |
|
3091 pre: function() { |
|
3092 if (this.matrix) { |
|
3093 p.pushMatrix(); |
|
3094 curContext.transform(this.matrix.elements[0], |
|
3095 this.matrix.elements[3], |
|
3096 this.matrix.elements[1], |
|
3097 this.matrix.elements[4], |
|
3098 this.matrix.elements[2], |
|
3099 this.matrix.elements[5]); |
|
3100 //p.applyMatrix(this.matrix.elements[0],this.matrix.elements[0]); |
|
3101 } |
|
3102 if (this.style) { |
|
3103 p.pushStyle(); |
|
3104 this.styles(); |
|
3105 } |
|
3106 }, |
|
3107 /** |
|
3108 * @member PShape |
|
3109 * The post() function performs the necessary actions after the SVG is drawn. This includes removing transformations and removing added styles. |
|
3110 */ |
|
3111 post: function() { |
|
3112 if (this.matrix) { |
|
3113 p.popMatrix(); |
|
3114 } |
|
3115 if (this.style) { |
|
3116 p.popStyle(); |
|
3117 } |
|
3118 }, |
|
3119 /** |
|
3120 * @member PShape |
|
3121 * The styles() function changes the Processing's current styles |
|
3122 */ |
|
3123 styles: function() { |
|
3124 if (this.stroke) { |
|
3125 p.stroke(this.strokeColor); |
|
3126 p.strokeWeight(this.strokeWeight); |
|
3127 p.strokeCap(this.strokeCap); |
|
3128 p.strokeJoin(this.strokeJoin); |
|
3129 } else { |
|
3130 p.noStroke(); |
|
3131 } |
|
3132 |
|
3133 if (this.fill) { |
|
3134 p.fill(this.fillColor); |
|
3135 |
|
3136 } else { |
|
3137 p.noFill(); |
|
3138 } |
|
3139 }, |
|
3140 /** |
|
3141 * @member PShape |
|
3142 * The getChild() function extracts a child shape from a parent shape. Specify the name of the shape with the <b>target</b> parameter or the |
|
3143 * layer position of the shape to get with the <b>index</b> parameter. |
|
3144 * The shape is returned as a <b>PShape</b> object, or <b>null</b> is returned if there is an error. |
|
3145 * |
|
3146 * @param {String} target the name of the shape to get |
|
3147 * @param {int} index the layer position of the shape to get |
|
3148 * |
|
3149 * @return {PShape} returns a child element of a shape as a PShape object or null if there is an error |
|
3150 */ |
|
3151 getChild: function(child) { |
|
3152 var i, j; |
|
3153 if (typeof child === 'number') { |
|
3154 return this.children[child]; |
|
3155 } |
|
3156 var found; |
|
3157 if(child === "" || this.name === child){ |
|
3158 return this; |
|
3159 } |
|
3160 if(this.nameTable.length > 0) { |
|
3161 for(i = 0, j = this.nameTable.length; i < j || found; i++) { |
|
3162 if(this.nameTable[i].getName === child) { |
|
3163 found = this.nameTable[i]; |
|
3164 break; |
|
3165 } |
|
3166 } |
|
3167 if (found) { return found; } |
|
3168 } |
|
3169 for(i = 0, j = this.children.length; i < j; i++) { |
|
3170 found = this.children[i].getChild(child); |
|
3171 if(found) { return found; } |
|
3172 } |
|
3173 return null; |
|
3174 }, |
|
3175 /** |
|
3176 * @member PShape |
|
3177 * The getChildCount() returns the number of children |
|
3178 * |
|
3179 * @return {int} returns a count of children |
|
3180 */ |
|
3181 getChildCount: function () { |
|
3182 return this.children.length; |
|
3183 }, |
|
3184 /** |
|
3185 * @member PShape |
|
3186 * The addChild() adds a child to the PShape. |
|
3187 * |
|
3188 * @param {PShape} child the child to add |
|
3189 */ |
|
3190 addChild: function( child ) { |
|
3191 this.children.push(child); |
|
3192 child.parent = this; |
|
3193 if (child.getName() !== null) { |
|
3194 this.addName(child.getName(), child); |
|
3195 } |
|
3196 }, |
|
3197 /** |
|
3198 * @member PShape |
|
3199 * The addName() functions adds a shape to the name lookup table. |
|
3200 * |
|
3201 * @param {String} name the name to be added |
|
3202 * @param {PShape} shape the shape |
|
3203 */ |
|
3204 addName: function(name, shape) { |
|
3205 if (this.parent !== null) { |
|
3206 this.parent.addName( name, shape ); |
|
3207 } else { |
|
3208 this.nameTable.push( [name, shape] ); |
|
3209 } |
|
3210 }, |
|
3211 /** |
|
3212 * @member PShape |
|
3213 * The translate() function specifies an amount to displace the shape. The <b>x</b> parameter specifies left/right translation, the <b>y</b> parameter specifies up/down translation, and the <b>z</b> parameter specifies translations toward/away from the screen. |
|
3214 * Subsequent calls to the method accumulates the effect. For example, calling <b>translate(50, 0)</b> and then <b>translate(20, 0)</b> is the same as <b>translate(70, 0)</b>. |
|
3215 * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. |
|
3216 * <br><br>Using this method with the <b>z</b> parameter requires using the P3D or OPENGL parameter in combination with size. |
|
3217 * |
|
3218 * @param {int|float} x left/right translation |
|
3219 * @param {int|float} y up/down translation |
|
3220 * @param {int|float} z forward/back translation |
|
3221 * |
|
3222 * @see PMatrix2D#translate |
|
3223 * @see PMatrix3D#translate |
|
3224 */ |
|
3225 translate: function() { |
|
3226 if(arguments.length === 2) |
|
3227 { |
|
3228 this.checkMatrix(2); |
|
3229 this.matrix.translate(arguments[0], arguments[1]); |
|
3230 } else { |
|
3231 this.checkMatrix(3); |
|
3232 this.matrix.translate(arguments[0], arguments[1], 0); |
|
3233 } |
|
3234 }, |
|
3235 /** |
|
3236 * @member PShape |
|
3237 * The checkMatrix() function makes sure that the shape's matrix is 1) not null, and 2) has a matrix |
|
3238 * that can handle <em>at least</em> the specified number of dimensions. |
|
3239 * |
|
3240 * @param {int} dimensions the specified number of dimensions |
|
3241 */ |
|
3242 checkMatrix: function(dimensions) { |
|
3243 if(this.matrix === null) { |
|
3244 if(dimensions === 2) { |
|
3245 this.matrix = new p.PMatrix2D(); |
|
3246 } else { |
|
3247 this.matrix = new p.PMatrix3D(); |
|
3248 } |
|
3249 }else if(dimensions === 3 && this.matrix instanceof p.PMatrix2D) { |
|
3250 this.matrix = new p.PMatrix3D(); |
|
3251 } |
|
3252 }, |
|
3253 /** |
|
3254 * @member PShape |
|
3255 * The rotateX() function rotates a shape around the x-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. |
|
3256 * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. |
|
3257 * Subsequent calls to the method accumulates the effect. For example, calling <b>rotateX(HALF_PI)</b> and then <b>rotateX(HALF_PI)</b> is the same as <b>rotateX(PI)</b>. |
|
3258 * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. |
|
3259 * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> method as shown in the example above. |
|
3260 * |
|
3261 * @param {float}angle angle of rotation specified in radians |
|
3262 * |
|
3263 * @see PMatrix3D#rotateX |
|
3264 */ |
|
3265 rotateX: function(angle) { |
|
3266 this.rotate(angle, 1, 0, 0); |
|
3267 }, |
|
3268 /** |
|
3269 * @member PShape |
|
3270 * The rotateY() function rotates a shape around the y-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. |
|
3271 * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. |
|
3272 * Subsequent calls to the method accumulates the effect. For example, calling <b>rotateY(HALF_PI)</b> and then <b>rotateY(HALF_PI)</b> is the same as <b>rotateY(PI)</b>. |
|
3273 * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. |
|
3274 * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> method as shown in the example above. |
|
3275 * |
|
3276 * @param {float}angle angle of rotation specified in radians |
|
3277 * |
|
3278 * @see PMatrix3D#rotateY |
|
3279 */ |
|
3280 rotateY: function(angle) { |
|
3281 this.rotate(angle, 0, 1, 0); |
|
3282 }, |
|
3283 /** |
|
3284 * @member PShape |
|
3285 * The rotateZ() function rotates a shape around the z-axis the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. |
|
3286 * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. |
|
3287 * Subsequent calls to the method accumulates the effect. For example, calling <b>rotateZ(HALF_PI)</b> and then <b>rotateZ(HALF_PI)</b> is the same as <b>rotateZ(PI)</b>. |
|
3288 * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. |
|
3289 * <br><br>This method requires a 3D renderer. You need to pass P3D or OPENGL as a third parameter into the <b>size()</b> method as shown in the example above. |
|
3290 * |
|
3291 * @param {float}angle angle of rotation specified in radians |
|
3292 * |
|
3293 * @see PMatrix3D#rotateZ |
|
3294 */ |
|
3295 rotateZ: function(angle) { |
|
3296 this.rotate(angle, 0, 0, 1); |
|
3297 }, |
|
3298 /** |
|
3299 * @member PShape |
|
3300 * The rotate() function rotates a shape the amount specified by the <b>angle</b> parameter. Angles should be specified in radians (values from 0 to TWO_PI) or converted to radians with the <b>radians()</b> method. |
|
3301 * <br><br>Shapes are always rotated around the upper-left corner of their bounding box. Positive numbers rotate objects in a clockwise direction. |
|
3302 * Transformations apply to everything that happens after and subsequent calls to the method accumulates the effect. |
|
3303 * For example, calling <b>rotate(HALF_PI)</b> and then <b>rotate(HALF_PI)</b> is the same as <b>rotate(PI)</b>. |
|
3304 * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. |
|
3305 * If optional parameters x,y,z are supplied, the rotate is about the point (x, y, z). |
|
3306 * |
|
3307 * @param {float}angle angle of rotation specified in radians |
|
3308 * @param {float}x x-coordinate of the point |
|
3309 * @param {float}y y-coordinate of the point |
|
3310 * @param {float}z z-coordinate of the point |
|
3311 * @see PMatrix2D#rotate |
|
3312 * @see PMatrix3D#rotate |
|
3313 */ |
|
3314 rotate: function() { |
|
3315 if(arguments.length === 1){ |
|
3316 this.checkMatrix(2); |
|
3317 this.matrix.rotate(arguments[0]); |
|
3318 } else { |
|
3319 this.checkMatrix(3); |
|
3320 this.matrix.rotate(arguments[0], |
|
3321 arguments[1], |
|
3322 arguments[2], |
|
3323 arguments[3]); |
|
3324 } |
|
3325 }, |
|
3326 /** |
|
3327 * @member PShape |
|
3328 * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. Shapes always scale from the relative origin of their bounding box. |
|
3329 * Scale values are specified as decimal percentages. For example, the method call <b>scale(2.0)</b> increases the dimension of a shape by 200%. |
|
3330 * Subsequent calls to the method multiply the effect. For example, calling <b>scale(2.0)</b> and then <b>scale(1.5)</b> is the same as <b>scale(3.0)</b>. |
|
3331 * This transformation is applied directly to the shape, it's not refreshed each time <b>draw()</b> is run. |
|
3332 * <br><br>Using this fuction with the <b>z</b> parameter requires passing P3D or OPENGL into the size() parameter. |
|
3333 * |
|
3334 * @param {float}s percentage to scale the object |
|
3335 * @param {float}x percentage to scale the object in the x-axis |
|
3336 * @param {float}y percentage to scale the object in the y-axis |
|
3337 * @param {float}z percentage to scale the object in the z-axis |
|
3338 * |
|
3339 * @see PMatrix2D#scale |
|
3340 * @see PMatrix3D#scale |
|
3341 */ |
|
3342 scale: function() { |
|
3343 if(arguments.length === 2) { |
|
3344 this.checkMatrix(2); |
|
3345 this.matrix.scale(arguments[0], arguments[1]); |
|
3346 } else if (arguments.length === 3) { |
|
3347 this.checkMatrix(2); |
|
3348 this.matrix.scale(arguments[0], arguments[1], arguments[2]); |
|
3349 } else { |
|
3350 this.checkMatrix(2); |
|
3351 this.matrix.scale(arguments[0]); |
|
3352 } |
|
3353 }, |
|
3354 /** |
|
3355 * @member PShape |
|
3356 * The resetMatrix() function resets the matrix |
|
3357 * |
|
3358 * @see PMatrix2D#reset |
|
3359 * @see PMatrix3D#reset |
|
3360 */ |
|
3361 resetMatrix: function() { |
|
3362 this.checkMatrix(2); |
|
3363 this.matrix.reset(); |
|
3364 }, |
|
3365 /** |
|
3366 * @member PShape |
|
3367 * The applyMatrix() function multiplies this matrix by another matrix of type PMatrix3D or PMatrix2D. |
|
3368 * Individual elements can also be provided |
|
3369 * |
|
3370 * @param {PMatrix3D|PMatrix2D} matrix the matrix to multiply by |
|
3371 * |
|
3372 * @see PMatrix2D#apply |
|
3373 * @see PMatrix3D#apply |
|
3374 */ |
|
3375 applyMatrix: function(matrix) { |
|
3376 if (arguments.length === 1) { |
|
3377 this.applyMatrix(matrix.elements[0], |
|
3378 matrix.elements[1], 0, |
|
3379 matrix.elements[2], |
|
3380 matrix.elements[3], |
|
3381 matrix.elements[4], 0, |
|
3382 matrix.elements[5], |
|
3383 0, 0, 1, 0, |
|
3384 0, 0, 0, 1); |
|
3385 } else if (arguments.length === 6) { |
|
3386 this.checkMatrix(2); |
|
3387 this.matrix.apply(arguments[0], arguments[1], arguments[2], 0, |
|
3388 arguments[3], arguments[4], arguments[5], 0, |
|
3389 0, 0, 1, 0, |
|
3390 0, 0, 0, 1); |
|
3391 |
|
3392 } else if (arguments.length === 16) { |
|
3393 this.checkMatrix(3); |
|
3394 this.matrix.apply(arguments[0], |
|
3395 arguments[1], |
|
3396 arguments[2], |
|
3397 arguments[3], |
|
3398 arguments[4], |
|
3399 arguments[5], |
|
3400 arguments[6], |
|
3401 arguments[7], |
|
3402 arguments[8], |
|
3403 arguments[9], |
|
3404 arguments[10], |
|
3405 arguments[11], |
|
3406 arguments[12], |
|
3407 arguments[13], |
|
3408 arguments[14], |
|
3409 arguments[15]); |
|
3410 } |
|
3411 } |
|
3412 }; |
|
3413 |
|
3414 /** |
|
3415 * SVG stands for Scalable Vector Graphics, a portable graphics format. It is |
|
3416 * a vector format so it allows for infinite resolution and relatively small |
|
3417 * file sizes. Most modern media software can view SVG files, including Adobe |
|
3418 * products, Firefox, etc. Illustrator and Inkscape can edit SVG files. |
|
3419 * |
|
3420 * @param {PApplet} parent typically use "this" |
|
3421 * @param {String} filename name of the SVG file to load |
|
3422 * @param {XMLElement} xml an XMLElement element |
|
3423 * @param {PShapeSVG} parent the parent PShapeSVG |
|
3424 * |
|
3425 * @see PShape |
|
3426 */ |
|
3427 var PShapeSVG = p.PShapeSVG = function() { |
|
3428 p.PShape.call( this ); // PShape is the base class. |
|
3429 if (arguments.length === 1) { //xml element coming in |
|
3430 this.element = arguments[0] ;//new p.XMLElement(null, arguments[0]); |
|
3431 // set values to their defaults according to the SVG spec |
|
3432 this.vertexCodes = []; |
|
3433 this.vertices = []; |
|
3434 this.opacity = 1; |
|
3435 |
|
3436 this.stroke = false; |
|
3437 this.strokeColor = PConstants.ALPHA_MASK; |
|
3438 this.strokeWeight = 1; |
|
3439 this.strokeCap = PConstants.SQUARE; // BUTT in svg spec |
|
3440 this.strokeJoin = PConstants.MITER; |
|
3441 this.strokeGradient = null; |
|
3442 this.strokeGradientPaint = null; |
|
3443 this.strokeName = null; |
|
3444 this.strokeOpacity = 1; |
|
3445 |
|
3446 this.fill = true; |
|
3447 this.fillColor = PConstants.ALPHA_MASK; |
|
3448 this.fillGradient = null; |
|
3449 this.fillGradientPaint = null; |
|
3450 this.fillName = null; |
|
3451 this.fillOpacity = 1; |
|
3452 |
|
3453 if (this.element.getName() !== "svg") { |
|
3454 throw("root is not <svg>, it's <" + this.element.getName() + ">"); |
|
3455 } |
|
3456 } |
|
3457 else if (arguments.length === 2) { |
|
3458 if (typeof arguments[1] === 'string') { |
|
3459 if (arguments[1].indexOf(".svg") > -1) { //its a filename |
|
3460 this.element = new p.XMLElement(null, arguments[1]); |
|
3461 // set values to their defaults according to the SVG spec |
|
3462 this.vertexCodes = []; |
|
3463 this.vertices = []; |
|
3464 this.opacity = 1; |
|
3465 |
|
3466 this.stroke = false; |
|
3467 this.strokeColor = PConstants.ALPHA_MASK; |
|
3468 this.strokeWeight = 1; |
|
3469 this.strokeCap = PConstants.SQUARE; // BUTT in svg spec |
|
3470 this.strokeJoin = PConstants.MITER; |
|
3471 this.strokeGradient = ""; |
|
3472 this.strokeGradientPaint = ""; |
|
3473 this.strokeName = ""; |
|
3474 this.strokeOpacity = 1; |
|
3475 |
|
3476 this.fill = true; |
|
3477 this.fillColor = PConstants.ALPHA_MASK; |
|
3478 this.fillGradient = null; |
|
3479 this.fillGradientPaint = null; |
|
3480 this.fillOpacity = 1; |
|
3481 |
|
3482 } |
|
3483 } else { // XMLElement |
|
3484 if (arguments[0]) { // PShapeSVG |
|
3485 this.element = arguments[1]; |
|
3486 this.vertexCodes = arguments[0].vertexCodes.slice(); |
|
3487 this.vertices = arguments[0].vertices.slice(); |
|
3488 |
|
3489 this.stroke = arguments[0].stroke; |
|
3490 this.strokeColor = arguments[0].strokeColor; |
|
3491 this.strokeWeight = arguments[0].strokeWeight; |
|
3492 this.strokeCap = arguments[0].strokeCap; |
|
3493 this.strokeJoin = arguments[0].strokeJoin; |
|
3494 this.strokeGradient = arguments[0].strokeGradient; |
|
3495 this.strokeGradientPaint = arguments[0].strokeGradientPaint; |
|
3496 this.strokeName = arguments[0].strokeName; |
|
3497 |
|
3498 this.fill = arguments[0].fill; |
|
3499 this.fillColor = arguments[0].fillColor; |
|
3500 this.fillGradient = arguments[0].fillGradient; |
|
3501 this.fillGradientPaint = arguments[0].fillGradientPaint; |
|
3502 this.fillName = arguments[0].fillName; |
|
3503 this.strokeOpacity = arguments[0].strokeOpacity; |
|
3504 this.fillOpacity = arguments[0].fillOpacity; |
|
3505 this.opacity = arguments[0].opacity; |
|
3506 } |
|
3507 } |
|
3508 } |
|
3509 |
|
3510 this.name = this.element.getStringAttribute("id"); |
|
3511 var displayStr = this.element.getStringAttribute("display", "inline"); |
|
3512 this.visible = displayStr !== "none"; |
|
3513 var str = this.element.getAttribute("transform"); |
|
3514 if (str) { |
|
3515 this.matrix = this.parseMatrix(str); |
|
3516 } |
|
3517 // not proper parsing of the viewBox, but will cover us for cases where |
|
3518 // the width and height of the object is not specified |
|
3519 var viewBoxStr = this.element.getStringAttribute("viewBox"); |
|
3520 if ( viewBoxStr !== null ) { |
|
3521 var viewBox = viewBoxStr.split(" "); |
|
3522 this.width = viewBox[2]; |
|
3523 this.height = viewBox[3]; |
|
3524 } |
|
3525 |
|
3526 // TODO if viewbox is not same as width/height, then use it to scale |
|
3527 // the original objects. for now, viewbox only used when width/height |
|
3528 // are empty values (which by the spec means w/h of "100%" |
|
3529 var unitWidth = this.element.getStringAttribute("width"); |
|
3530 var unitHeight = this.element.getStringAttribute("height"); |
|
3531 if (unitWidth !== null) { |
|
3532 this.width = this.parseUnitSize(unitWidth); |
|
3533 this.height = this.parseUnitSize(unitHeight); |
|
3534 } else { |
|
3535 if ((this.width === 0) || (this.height === 0)) { |
|
3536 // For the spec, the default is 100% and 100%. For purposes |
|
3537 // here, insert a dummy value because this is prolly just a |
|
3538 // font or something for which the w/h doesn't matter. |
|
3539 this.width = 1; |
|
3540 this.height = 1; |
|
3541 |
|
3542 //show warning |
|
3543 throw("The width and/or height is not " + |
|
3544 "readable in the <svg> tag of this file."); |
|
3545 } |
|
3546 } |
|
3547 this.parseColors(this.element); |
|
3548 this.parseChildren(this.element); |
|
3549 |
|
3550 }; |
|
3551 /** |
|
3552 * PShapeSVG methods |
|
3553 * missing: getChild(), print(), parseStyleAttributes(), styles() - deals with strokeGradient and fillGradient |
|
3554 */ |
|
3555 PShapeSVG.prototype = new PShape(); |
|
3556 /** |
|
3557 * @member PShapeSVG |
|
3558 * The parseMatrix() function parses the specified SVG matrix into a PMatrix2D. Note that PMatrix2D |
|
3559 * is rotated relative to the SVG definition, so parameters are rearranged |
|
3560 * here. More about the transformation matrices in |
|
3561 * <a href="http://www.w3.org/TR/SVG/coords.html#TransformAttribute">this section</a> |
|
3562 * of the SVG documentation. |
|
3563 * |
|
3564 * @param {String} str text of the matrix param. |
|
3565 * |
|
3566 * @return {PMatrix2D} a PMatrix2D |
|
3567 */ |
|
3568 PShapeSVG.prototype.parseMatrix = (function() { |
|
3569 function getCoords(s) { |
|
3570 var m = []; |
|
3571 s.replace(/\((.*?)\)/, (function() { |
|
3572 return function(all, params) { |
|
3573 // get the coordinates that can be separated by spaces or a comma |
|
3574 m = params.replace(/,+/g, " ").split(/\s+/); |
|
3575 }; |
|
3576 }())); |
|
3577 return m; |
|
3578 } |
|
3579 |
|
3580 return function(str) { |
|
3581 this.checkMatrix(2); |
|
3582 var pieces = []; |
|
3583 str.replace(/\s*(\w+)\((.*?)\)/g, function(all) { |
|
3584 // get a list of transform definitions |
|
3585 pieces.push(p.trim(all)); |
|
3586 }); |
|
3587 if (pieces.length === 0) { |
|
3588 return null; |
|
3589 } |
|
3590 |
|
3591 for (var i = 0, j = pieces.length; i < j; i++) { |
|
3592 var m = getCoords(pieces[i]); |
|
3593 |
|
3594 if (pieces[i].indexOf("matrix") !== -1) { |
|
3595 this.matrix.set(m[0], m[2], m[4], m[1], m[3], m[5]); |
|
3596 } else if (pieces[i].indexOf("translate") !== -1) { |
|
3597 var tx = m[0]; |
|
3598 var ty = (m.length === 2) ? m[1] : 0; |
|
3599 this.matrix.translate(tx,ty); |
|
3600 } else if (pieces[i].indexOf("scale") !== -1) { |
|
3601 var sx = m[0]; |
|
3602 var sy = (m.length === 2) ? m[1] : m[0]; |
|
3603 this.matrix.scale(sx,sy); |
|
3604 } else if (pieces[i].indexOf("rotate") !== -1) { |
|
3605 var angle = m[0]; |
|
3606 if (m.length === 1) { |
|
3607 this.matrix.rotate(p.radians(angle)); |
|
3608 } else if (m.length === 3) { |
|
3609 this.matrix.translate(m[1], m[2]); |
|
3610 this.matrix.rotate(p.radians(m[0])); |
|
3611 this.matrix.translate(-m[1], -m[2]); |
|
3612 } |
|
3613 } else if (pieces[i].indexOf("skewX") !== -1) { |
|
3614 this.matrix.skewX(parseFloat(m[0])); |
|
3615 } else if (pieces[i].indexOf("skewY") !== -1) { |
|
3616 this.matrix.skewY(m[0]); |
|
3617 } |
|
3618 } |
|
3619 return this.matrix; |
|
3620 }; |
|
3621 }()); |
|
3622 |
|
3623 /** |
|
3624 * @member PShapeSVG |
|
3625 * The parseChildren() function parses the specified XMLElement |
|
3626 * |
|
3627 * @param {XMLElement}element the XMLElement to parse |
|
3628 */ |
|
3629 PShapeSVG.prototype.parseChildren = function(element) { |
|
3630 var newelement = element.getChildren(); |
|
3631 var children = new p.PShape(); |
|
3632 for (var i = 0, j = newelement.length; i < j; i++) { |
|
3633 var kid = this.parseChild(newelement[i]); |
|
3634 if (kid) { |
|
3635 children.addChild(kid); |
|
3636 } |
|
3637 } |
|
3638 this.children.push(children); |
|
3639 }; |
|
3640 /** |
|
3641 * @member PShapeSVG |
|
3642 * The getName() function returns the name |
|
3643 * |
|
3644 * @return {String} the name |
|
3645 */ |
|
3646 PShapeSVG.prototype.getName = function() { |
|
3647 return this.name; |
|
3648 }; |
|
3649 /** |
|
3650 * @member PShapeSVG |
|
3651 * The parseChild() function parses a child XML element. |
|
3652 * |
|
3653 * @param {XMLElement} elem the element to parse |
|
3654 * |
|
3655 * @return {PShape} the newly created PShape |
|
3656 */ |
|
3657 PShapeSVG.prototype.parseChild = function( elem ) { |
|
3658 var name = elem.getName(); |
|
3659 var shape; |
|
3660 if (name === "g") { |
|
3661 shape = new PShapeSVG(this, elem); |
|
3662 } else if (name === "defs") { |
|
3663 // generally this will contain gradient info, so may |
|
3664 // as well just throw it into a group element for parsing |
|
3665 shape = new PShapeSVG(this, elem); |
|
3666 } else if (name === "line") { |
|
3667 shape = new PShapeSVG(this, elem); |
|
3668 shape.parseLine(); |
|
3669 } else if (name === "circle") { |
|
3670 shape = new PShapeSVG(this, elem); |
|
3671 shape.parseEllipse(true); |
|
3672 } else if (name === "ellipse") { |
|
3673 shape = new PShapeSVG(this, elem); |
|
3674 shape.parseEllipse(false); |
|
3675 } else if (name === "rect") { |
|
3676 shape = new PShapeSVG(this, elem); |
|
3677 shape.parseRect(); |
|
3678 } else if (name === "polygon") { |
|
3679 shape = new PShapeSVG(this, elem); |
|
3680 shape.parsePoly(true); |
|
3681 } else if (name === "polyline") { |
|
3682 shape = new PShapeSVG(this, elem); |
|
3683 shape.parsePoly(false); |
|
3684 } else if (name === "path") { |
|
3685 shape = new PShapeSVG(this, elem); |
|
3686 shape.parsePath(); |
|
3687 } else if (name === "radialGradient") { |
|
3688 //return new RadialGradient(this, elem); |
|
3689 unimplemented('PShapeSVG.prototype.parseChild, name = radialGradient'); |
|
3690 } else if (name === "linearGradient") { |
|
3691 //return new LinearGradient(this, elem); |
|
3692 unimplemented('PShapeSVG.prototype.parseChild, name = linearGradient'); |
|
3693 } else if (name === "text") { |
|
3694 unimplemented('PShapeSVG.prototype.parseChild, name = text'); |
|
3695 } else if (name === "filter") { |
|
3696 unimplemented('PShapeSVG.prototype.parseChild, name = filter'); |
|
3697 } else if (name === "mask") { |
|
3698 unimplemented('PShapeSVG.prototype.parseChild, name = mask'); |
|
3699 } else { |
|
3700 // ignoring |
|
3701 nop(); |
|
3702 } |
|
3703 return shape; |
|
3704 }; |
|
3705 /** |
|
3706 * @member PShapeSVG |
|
3707 * The parsePath() function parses the <path> element of the svg file |
|
3708 * A path is defined by including a path element which contains a d="(path data)" attribute, where the d attribute contains |
|
3709 * the moveto, line, curve (both cubic and quadratic Beziers), arc and closepath instructions. |
|
3710 **/ |
|
3711 PShapeSVG.prototype.parsePath = function() { |
|
3712 this.family = PConstants.PATH; |
|
3713 this.kind = 0; |
|
3714 var pathDataChars = []; |
|
3715 var c; |
|
3716 //change multiple spaces and commas to single space |
|
3717 var pathData = p.trim(this.element.getStringAttribute("d") |
|
3718 .replace(/[\s,]+/g,' ')); |
|
3719 if (pathData === null) { |
|
3720 return; |
|
3721 } |
|
3722 pathData = p.__toCharArray(pathData); |
|
3723 var cx = 0, |
|
3724 cy = 0, |
|
3725 ctrlX = 0, |
|
3726 ctrlY = 0, |
|
3727 ctrlX1 = 0, |
|
3728 ctrlX2 = 0, |
|
3729 ctrlY1 = 0, |
|
3730 ctrlY2 = 0, |
|
3731 endX = 0, |
|
3732 endY = 0, |
|
3733 ppx = 0, |
|
3734 ppy = 0, |
|
3735 px = 0, |
|
3736 py = 0, |
|
3737 i = 0, |
|
3738 valOf = 0; |
|
3739 var str = ""; |
|
3740 var tmpArray =[]; |
|
3741 var flag = false; |
|
3742 var lastInstruction; |
|
3743 var command; |
|
3744 var j, k; |
|
3745 while (i< pathData.length) { |
|
3746 valOf = pathData[i].valueOf(); |
|
3747 if ((valOf >= 65 && valOf <= 90) || (valOf >= 97 && valOf <= 122)) { |
|
3748 // if it's a letter |
|
3749 // populate the tmpArray with coordinates |
|
3750 j = i; |
|
3751 i++; |
|
3752 if (i < pathData.length) { // don't go over boundary of array |
|
3753 tmpArray = []; |
|
3754 valOf = pathData[i].valueOf(); |
|
3755 while (!((valOf >= 65 && valOf <= 90) || |
|
3756 (valOf >= 97 && valOf <= 100) || |
|
3757 (valOf >= 102 && valOf <= 122)) |
|
3758 && flag === false) { // if its NOT a letter |
|
3759 if (valOf === 32) { //if its a space and the str isn't empty |
|
3760 // sometimes you get a space after the letter |
|
3761 if (str !== "") { |
|
3762 tmpArray.push(parseFloat(str)); |
|
3763 str = ""; |
|
3764 } |
|
3765 i++; |
|
3766 } else if (valOf === 45) { //if it's a - |
|
3767 // allow for 'e' notation in numbers, e.g. 2.10e-9 |
|
3768 if (pathData[i-1].valueOf() === 101) { |
|
3769 str += pathData[i].toString(); |
|
3770 i++; |
|
3771 } else { |
|
3772 // sometimes no space separator after (ex: 104.535-16.322) |
|
3773 if (str !== "") { |
|
3774 tmpArray.push(parseFloat(str)); |
|
3775 } |
|
3776 str = pathData[i].toString(); |
|
3777 i++; |
|
3778 } |
|
3779 } else { |
|
3780 str += pathData[i].toString(); |
|
3781 i++; |
|
3782 } |
|
3783 if (i === pathData.length) { // don't go over boundary of array |
|
3784 flag = true; |
|
3785 } else { |
|
3786 valOf = pathData[i].valueOf(); |
|
3787 } |
|
3788 } |
|
3789 } |
|
3790 if (str !== "") { |
|
3791 tmpArray.push(parseFloat(str)); |
|
3792 str = ""; |
|
3793 } |
|
3794 command = pathData[j]; |
|
3795 valOf = command.valueOf(); |
|
3796 if (valOf === 77) { // M - move to (absolute) |
|
3797 if (tmpArray.length >= 2 && tmpArray.length % 2 ===0) { |
|
3798 // need one+ pairs of co-ordinates |
|
3799 cx = tmpArray[0]; |
|
3800 cy = tmpArray[1]; |
|
3801 this.parsePathMoveto(cx, cy); |
|
3802 if (tmpArray.length > 2) { |
|
3803 for (j = 2, k = tmpArray.length; j < k; j+=2) { |
|
3804 // absolute line to |
|
3805 cx = tmpArray[j]; |
|
3806 cy = tmpArray[j+1]; |
|
3807 this.parsePathLineto(cx,cy); |
|
3808 } |
|
3809 } |
|
3810 } |
|
3811 } else if (valOf === 109) { // m - move to (relative) |
|
3812 if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { |
|
3813 // need one+ pairs of co-ordinates |
|
3814 cx += tmpArray[0]; |
|
3815 cy += tmpArray[1]; |
|
3816 this.parsePathMoveto(cx,cy); |
|
3817 if (tmpArray.length > 2) { |
|
3818 for (j = 2, k = tmpArray.length; j < k; j+=2) { |
|
3819 // relative line to |
|
3820 cx += tmpArray[j]; |
|
3821 cy += tmpArray[j + 1]; |
|
3822 this.parsePathLineto(cx,cy); |
|
3823 } |
|
3824 } |
|
3825 } |
|
3826 } else if (valOf === 76) { // L - lineto (absolute) |
|
3827 if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { |
|
3828 // need one+ pairs of co-ordinates |
|
3829 for (j = 0, k = tmpArray.length; j < k; j+=2) { |
|
3830 cx = tmpArray[j]; |
|
3831 cy = tmpArray[j + 1]; |
|
3832 this.parsePathLineto(cx,cy); |
|
3833 } |
|
3834 } |
|
3835 } else if (valOf === 108) { // l - lineto (relative) |
|
3836 if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { |
|
3837 // need one+ pairs of co-ordinates |
|
3838 for (j = 0, k = tmpArray.length; j < k; j+=2) { |
|
3839 cx += tmpArray[j]; |
|
3840 cy += tmpArray[j+1]; |
|
3841 this.parsePathLineto(cx,cy); |
|
3842 } |
|
3843 } |
|
3844 } else if (valOf === 72) { // H - horizontal lineto (absolute) |
|
3845 for (j = 0, k = tmpArray.length; j < k; j++) { |
|
3846 // multiple x co-ordinates can be provided |
|
3847 cx = tmpArray[j]; |
|
3848 this.parsePathLineto(cx, cy); |
|
3849 } |
|
3850 } else if (valOf === 104) { // h - horizontal lineto (relative) |
|
3851 for (j = 0, k = tmpArray.length; j < k; j++) { |
|
3852 // multiple x co-ordinates can be provided |
|
3853 cx += tmpArray[j]; |
|
3854 this.parsePathLineto(cx, cy); |
|
3855 } |
|
3856 } else if (valOf === 86) { // V - vertical lineto (absolute) |
|
3857 for (j = 0, k = tmpArray.length; j < k; j++) { |
|
3858 // multiple y co-ordinates can be provided |
|
3859 cy = tmpArray[j]; |
|
3860 this.parsePathLineto(cx, cy); |
|
3861 } |
|
3862 } else if (valOf === 118) { // v - vertical lineto (relative) |
|
3863 for (j = 0, k = tmpArray.length; j < k; j++) { |
|
3864 // multiple y co-ordinates can be provided |
|
3865 cy += tmpArray[j]; |
|
3866 this.parsePathLineto(cx, cy); |
|
3867 } |
|
3868 } else if (valOf === 67) { // C - curve to (absolute) |
|
3869 if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { |
|
3870 // need one+ multiples of 6 co-ordinates |
|
3871 for (j = 0, k = tmpArray.length; j < k; j+=6) { |
|
3872 ctrlX1 = tmpArray[j]; |
|
3873 ctrlY1 = tmpArray[j + 1]; |
|
3874 ctrlX2 = tmpArray[j + 2]; |
|
3875 ctrlY2 = tmpArray[j + 3]; |
|
3876 endX = tmpArray[j + 4]; |
|
3877 endY = tmpArray[j + 5]; |
|
3878 this.parsePathCurveto(ctrlX1, |
|
3879 ctrlY1, |
|
3880 ctrlX2, |
|
3881 ctrlY2, |
|
3882 endX, |
|
3883 endY); |
|
3884 cx = endX; |
|
3885 cy = endY; |
|
3886 } |
|
3887 } |
|
3888 } else if (valOf === 99) { // c - curve to (relative) |
|
3889 if (tmpArray.length >= 6 && tmpArray.length % 6 === 0) { |
|
3890 // need one+ multiples of 6 co-ordinates |
|
3891 for (j = 0, k = tmpArray.length; j < k; j+=6) { |
|
3892 ctrlX1 = cx + tmpArray[j]; |
|
3893 ctrlY1 = cy + tmpArray[j + 1]; |
|
3894 ctrlX2 = cx + tmpArray[j + 2]; |
|
3895 ctrlY2 = cy + tmpArray[j + 3]; |
|
3896 endX = cx + tmpArray[j + 4]; |
|
3897 endY = cy + tmpArray[j + 5]; |
|
3898 this.parsePathCurveto(ctrlX1, |
|
3899 ctrlY1, |
|
3900 ctrlX2, |
|
3901 ctrlY2, |
|
3902 endX, |
|
3903 endY); |
|
3904 cx = endX; |
|
3905 cy = endY; |
|
3906 } |
|
3907 } |
|
3908 } else if (valOf === 83) { // S - curve to shorthand (absolute) |
|
3909 if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { |
|
3910 // need one+ multiples of 4 co-ordinates |
|
3911 for (j = 0, k = tmpArray.length; j < k; j+=4) { |
|
3912 if (lastInstruction.toLowerCase() === "c" || |
|
3913 lastInstruction.toLowerCase() === "s") { |
|
3914 ppx = this.vertices[ this.vertices.length-2 ][0]; |
|
3915 ppy = this.vertices[ this.vertices.length-2 ][1]; |
|
3916 px = this.vertices[ this.vertices.length-1 ][0]; |
|
3917 py = this.vertices[ this.vertices.length-1 ][1]; |
|
3918 ctrlX1 = px + (px - ppx); |
|
3919 ctrlY1 = py + (py - ppy); |
|
3920 } else { |
|
3921 //If there is no previous curve, |
|
3922 //the current point will be used as the first control point. |
|
3923 ctrlX1 = this.vertices[this.vertices.length-1][0]; |
|
3924 ctrlY1 = this.vertices[this.vertices.length-1][1]; |
|
3925 } |
|
3926 ctrlX2 = tmpArray[j]; |
|
3927 ctrlY2 = tmpArray[j + 1]; |
|
3928 endX = tmpArray[j + 2]; |
|
3929 endY = tmpArray[j + 3]; |
|
3930 this.parsePathCurveto(ctrlX1, |
|
3931 ctrlY1, |
|
3932 ctrlX2, |
|
3933 ctrlY2, |
|
3934 endX, |
|
3935 endY); |
|
3936 cx = endX; |
|
3937 cy = endY; |
|
3938 } |
|
3939 } |
|
3940 } else if (valOf === 115) { // s - curve to shorthand (relative) |
|
3941 if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { |
|
3942 // need one+ multiples of 4 co-ordinates |
|
3943 for (j = 0, k = tmpArray.length; j < k; j+=4) { |
|
3944 if (lastInstruction.toLowerCase() === "c" || |
|
3945 lastInstruction.toLowerCase() === "s") { |
|
3946 ppx = this.vertices[this.vertices.length-2][0]; |
|
3947 ppy = this.vertices[this.vertices.length-2][1]; |
|
3948 px = this.vertices[this.vertices.length-1][0]; |
|
3949 py = this.vertices[this.vertices.length-1][1]; |
|
3950 ctrlX1 = px + (px - ppx); |
|
3951 ctrlY1 = py + (py - ppy); |
|
3952 } else { |
|
3953 //If there is no previous curve, |
|
3954 //the current point will be used as the first control point. |
|
3955 ctrlX1 = this.vertices[this.vertices.length-1][0]; |
|
3956 ctrlY1 = this.vertices[this.vertices.length-1][1]; |
|
3957 } |
|
3958 ctrlX2 = cx + tmpArray[j]; |
|
3959 ctrlY2 = cy + tmpArray[j + 1]; |
|
3960 endX = cx + tmpArray[j + 2]; |
|
3961 endY = cy + tmpArray[j + 3]; |
|
3962 this.parsePathCurveto(ctrlX1, |
|
3963 ctrlY1, |
|
3964 ctrlX2, |
|
3965 ctrlY2, |
|
3966 endX, |
|
3967 endY); |
|
3968 cx = endX; |
|
3969 cy = endY; |
|
3970 } |
|
3971 } |
|
3972 } else if (valOf === 81) { // Q - quadratic curve to (absolute) |
|
3973 if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { |
|
3974 // need one+ multiples of 4 co-ordinates |
|
3975 for (j = 0, k = tmpArray.length; j < k; j+=4) { |
|
3976 ctrlX = tmpArray[j]; |
|
3977 ctrlY = tmpArray[j + 1]; |
|
3978 endX = tmpArray[j + 2]; |
|
3979 endY = tmpArray[j + 3]; |
|
3980 this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
|
3981 cx = endX; |
|
3982 cy = endY; |
|
3983 } |
|
3984 } |
|
3985 } else if (valOf === 113) { // q - quadratic curve to (relative) |
|
3986 if (tmpArray.length >= 4 && tmpArray.length % 4 === 0) { |
|
3987 // need one+ multiples of 4 co-ordinates |
|
3988 for (j = 0, k = tmpArray.length; j < k; j+=4) { |
|
3989 ctrlX = cx + tmpArray[j]; |
|
3990 ctrlY = cy + tmpArray[j + 1]; |
|
3991 endX = cx + tmpArray[j + 2]; |
|
3992 endY = cy + tmpArray[j + 3]; |
|
3993 this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
|
3994 cx = endX; |
|
3995 cy = endY; |
|
3996 } |
|
3997 } |
|
3998 } else if (valOf === 84) { |
|
3999 // T - quadratic curve to shorthand (absolute) |
|
4000 if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { |
|
4001 // need one+ pairs of co-ordinates |
|
4002 for (j = 0, k = tmpArray.length; j < k; j+=2) { |
|
4003 if (lastInstruction.toLowerCase() === "q" || |
|
4004 lastInstruction.toLowerCase() === "t") { |
|
4005 ppx = this.vertices[this.vertices.length-2][0]; |
|
4006 ppy = this.vertices[this.vertices.length-2][1]; |
|
4007 px = this.vertices[this.vertices.length-1][0]; |
|
4008 py = this.vertices[this.vertices.length-1][1]; |
|
4009 ctrlX = px + (px - ppx); |
|
4010 ctrlY = py + (py - ppy); |
|
4011 } else { |
|
4012 // If there is no previous command or if the previous command |
|
4013 // was not a Q, q, T or t, assume the control point is |
|
4014 // coincident with the current point. |
|
4015 ctrlX = cx; |
|
4016 ctrlY = cy; |
|
4017 } |
|
4018 endX = tmpArray[j]; |
|
4019 endY = tmpArray[j + 1]; |
|
4020 this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
|
4021 cx = endX; |
|
4022 cy = endY; |
|
4023 } |
|
4024 } |
|
4025 } else if (valOf === 116) { |
|
4026 // t - quadratic curve to shorthand (relative) |
|
4027 if (tmpArray.length >= 2 && tmpArray.length % 2 === 0) { |
|
4028 // need one+ pairs of co-ordinates |
|
4029 for (j = 0, k = tmpArray.length; j < k; j+=2) { |
|
4030 if (lastInstruction.toLowerCase() === "q" || |
|
4031 lastInstruction.toLowerCase() === "t") { |
|
4032 ppx = this.vertices[this.vertices.length-2][0]; |
|
4033 ppy = this.vertices[this.vertices.length-2][1]; |
|
4034 px = this.vertices[this.vertices.length-1][0]; |
|
4035 py = this.vertices[this.vertices.length-1][1]; |
|
4036 ctrlX = px + (px - ppx); |
|
4037 ctrlY = py + (py - ppy); |
|
4038 } else { |
|
4039 // If there is no previous command or if the previous command |
|
4040 // was not a Q, q, T or t, assume the control point is |
|
4041 // coincident with the current point. |
|
4042 ctrlX = cx; |
|
4043 ctrlY = cy; |
|
4044 } |
|
4045 endX = cx + tmpArray[j]; |
|
4046 endY = cy + tmpArray[j + 1]; |
|
4047 this.parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
|
4048 cx = endX; |
|
4049 cy = endY; |
|
4050 } |
|
4051 } |
|
4052 } else if (valOf === 90 || valOf === 122) { // Z or z (these do the same thing) |
|
4053 this.close = true; |
|
4054 } |
|
4055 lastInstruction = command.toString(); |
|
4056 } else { i++;} |
|
4057 } |
|
4058 }; |
|
4059 /** |
|
4060 * @member PShapeSVG |
|
4061 * PShapeSVG.parsePath() helper function |
|
4062 * |
|
4063 * @see PShapeSVG#parsePath |
|
4064 */ |
|
4065 PShapeSVG.prototype.parsePathQuadto = function(x1, y1, cx, cy, x2, y2) { |
|
4066 if (this.vertices.length > 0) { |
|
4067 this.parsePathCode(PConstants.BEZIER_VERTEX); |
|
4068 // x1/y1 already covered by last moveto, lineto, or curveto |
|
4069 this.parsePathVertex(x1 + ((cx-x1)*2/3), y1 + ((cy-y1)*2/3)); |
|
4070 this.parsePathVertex(x2 + ((cx-x2)*2/3), y2 + ((cy-y2)*2/3)); |
|
4071 this.parsePathVertex(x2, y2); |
|
4072 } else { |
|
4073 throw ("Path must start with M/m"); |
|
4074 } |
|
4075 }; |
|
4076 /** |
|
4077 * @member PShapeSVG |
|
4078 * PShapeSVG.parsePath() helper function |
|
4079 * |
|
4080 * @see PShapeSVG#parsePath |
|
4081 */ |
|
4082 PShapeSVG.prototype.parsePathCurveto = function(x1, y1, x2, y2, x3, y3) { |
|
4083 if (this.vertices.length > 0) { |
|
4084 this.parsePathCode(PConstants.BEZIER_VERTEX ); |
|
4085 this.parsePathVertex(x1, y1); |
|
4086 this.parsePathVertex(x2, y2); |
|
4087 this.parsePathVertex(x3, y3); |
|
4088 } else { |
|
4089 throw ("Path must start with M/m"); |
|
4090 } |
|
4091 }; |
|
4092 /** |
|
4093 * @member PShapeSVG |
|
4094 * PShapeSVG.parsePath() helper function |
|
4095 * |
|
4096 * @see PShapeSVG#parsePath |
|
4097 */ |
|
4098 PShapeSVG.prototype.parsePathLineto = function(px, py) { |
|
4099 if (this.vertices.length > 0) { |
|
4100 this.parsePathCode(PConstants.VERTEX); |
|
4101 this.parsePathVertex(px, py); |
|
4102 // add property to distinguish between curContext.moveTo |
|
4103 // or curContext.lineTo |
|
4104 this.vertices[this.vertices.length-1]["moveTo"] = false; |
|
4105 } else { |
|
4106 throw ("Path must start with M/m"); |
|
4107 } |
|
4108 }; |
|
4109 |
|
4110 PShapeSVG.prototype.parsePathMoveto = function(px, py) { |
|
4111 if (this.vertices.length > 0) { |
|
4112 this.parsePathCode(PConstants.BREAK); |
|
4113 } |
|
4114 this.parsePathCode(PConstants.VERTEX); |
|
4115 this.parsePathVertex(px, py); |
|
4116 // add property to distinguish between curContext.moveTo |
|
4117 // or curContext.lineTo |
|
4118 this.vertices[this.vertices.length-1]["moveTo"] = true; |
|
4119 }; |
|
4120 /** |
|
4121 * @member PShapeSVG |
|
4122 * PShapeSVG.parsePath() helper function |
|
4123 * |
|
4124 * @see PShapeSVG#parsePath |
|
4125 */ |
|
4126 PShapeSVG.prototype.parsePathVertex = function(x, y) { |
|
4127 var verts = []; |
|
4128 verts[0] = x; |
|
4129 verts[1] = y; |
|
4130 this.vertices.push(verts); |
|
4131 }; |
|
4132 /** |
|
4133 * @member PShapeSVG |
|
4134 * PShapeSVG.parsePath() helper function |
|
4135 * |
|
4136 * @see PShapeSVG#parsePath |
|
4137 */ |
|
4138 PShapeSVG.prototype.parsePathCode = function(what) { |
|
4139 this.vertexCodes.push(what); |
|
4140 }; |
|
4141 /** |
|
4142 * @member PShapeSVG |
|
4143 * The parsePoly() function parses a polyline or polygon from an SVG file. |
|
4144 * |
|
4145 * @param {boolean}val true if shape is closed (polygon), false if not (polyline) |
|
4146 */ |
|
4147 PShapeSVG.prototype.parsePoly = function(val) { |
|
4148 this.family = PConstants.PATH; |
|
4149 this.close = val; |
|
4150 var pointsAttr = p.trim(this.element.getStringAttribute("points") |
|
4151 .replace(/[,\s]+/g,' ')); |
|
4152 if (pointsAttr !== null) { |
|
4153 //split into array |
|
4154 var pointsBuffer = pointsAttr.split(" "); |
|
4155 if (pointsBuffer.length % 2 === 0) { |
|
4156 for (var i = 0, j = pointsBuffer.length; i < j; i++) { |
|
4157 var verts = []; |
|
4158 verts[0] = pointsBuffer[i]; |
|
4159 verts[1] = pointsBuffer[++i]; |
|
4160 this.vertices.push(verts); |
|
4161 } |
|
4162 } else { |
|
4163 throw("Error parsing polygon points: odd number of coordinates provided"); |
|
4164 } |
|
4165 } |
|
4166 }; |
|
4167 /** |
|
4168 * @member PShapeSVG |
|
4169 * The parseRect() function parses a rect from an SVG file. |
|
4170 */ |
|
4171 PShapeSVG.prototype.parseRect = function() { |
|
4172 this.kind = PConstants.RECT; |
|
4173 this.family = PConstants.PRIMITIVE; |
|
4174 this.params = []; |
|
4175 this.params[0] = this.element.getFloatAttribute("x"); |
|
4176 this.params[1] = this.element.getFloatAttribute("y"); |
|
4177 this.params[2] = this.element.getFloatAttribute("width"); |
|
4178 this.params[3] = this.element.getFloatAttribute("height"); |
|
4179 if (this.params[2] < 0 || this.params[3] < 0) { |
|
4180 throw("svg error: negative width or height found while parsing <rect>"); |
|
4181 } |
|
4182 }; |
|
4183 /** |
|
4184 * @member PShapeSVG |
|
4185 * The parseEllipse() function handles parsing ellipse and circle tags. |
|
4186 * |
|
4187 * @param {boolean}val true if this is a circle and not an ellipse |
|
4188 */ |
|
4189 PShapeSVG.prototype.parseEllipse = function(val) { |
|
4190 this.kind = PConstants.ELLIPSE; |
|
4191 this.family = PConstants.PRIMITIVE; |
|
4192 this.params = []; |
|
4193 |
|
4194 this.params[0] = this.element.getFloatAttribute("cx") | 0 ; |
|
4195 this.params[1] = this.element.getFloatAttribute("cy") | 0; |
|
4196 |
|
4197 var rx, ry; |
|
4198 if (val) { |
|
4199 rx = ry = this.element.getFloatAttribute("r"); |
|
4200 if (rx < 0) { |
|
4201 throw("svg error: negative radius found while parsing <circle>"); |
|
4202 } |
|
4203 } else { |
|
4204 rx = this.element.getFloatAttribute("rx"); |
|
4205 ry = this.element.getFloatAttribute("ry"); |
|
4206 if (rx < 0 || ry < 0) { |
|
4207 throw("svg error: negative x-axis radius or y-axis radius found while parsing <ellipse>"); |
|
4208 } |
|
4209 } |
|
4210 this.params[0] -= rx; |
|
4211 this.params[1] -= ry; |
|
4212 |
|
4213 this.params[2] = rx*2; |
|
4214 this.params[3] = ry*2; |
|
4215 }; |
|
4216 /** |
|
4217 * @member PShapeSVG |
|
4218 * The parseLine() function handles parsing line tags. |
|
4219 * |
|
4220 * @param {boolean}val true if this is a circle and not an ellipse |
|
4221 */ |
|
4222 PShapeSVG.prototype.parseLine = function() { |
|
4223 this.kind = PConstants.LINE; |
|
4224 this.family = PConstants.PRIMITIVE; |
|
4225 this.params = []; |
|
4226 this.params[0] = this.element.getFloatAttribute("x1"); |
|
4227 this.params[1] = this.element.getFloatAttribute("y1"); |
|
4228 this.params[2] = this.element.getFloatAttribute("x2"); |
|
4229 this.params[3] = this.element.getFloatAttribute("y2"); |
|
4230 }; |
|
4231 /** |
|
4232 * @member PShapeSVG |
|
4233 * The parseColors() function handles parsing the opacity, strijem stroke-width, stroke-linejoin,stroke-linecap, fill, and style attributes |
|
4234 * |
|
4235 * @param {XMLElement}element the element of which attributes to parse |
|
4236 */ |
|
4237 PShapeSVG.prototype.parseColors = function(element) { |
|
4238 if (element.hasAttribute("opacity")) { |
|
4239 this.setOpacity(element.getAttribute("opacity")); |
|
4240 } |
|
4241 if (element.hasAttribute("stroke")) { |
|
4242 this.setStroke(element.getAttribute("stroke")); |
|
4243 } |
|
4244 if (element.hasAttribute("stroke-width")) { |
|
4245 // if NaN (i.e. if it's 'inherit') then default |
|
4246 // back to the inherit setting |
|
4247 this.setStrokeWeight(element.getAttribute("stroke-width")); |
|
4248 } |
|
4249 if (element.hasAttribute("stroke-linejoin") ) { |
|
4250 this.setStrokeJoin(element.getAttribute("stroke-linejoin")); |
|
4251 } |
|
4252 if (element.hasAttribute("stroke-linecap")) { |
|
4253 this.setStrokeCap(element.getStringAttribute("stroke-linecap")); |
|
4254 } |
|
4255 // fill defaults to black (though stroke defaults to "none") |
|
4256 // http://www.w3.org/TR/SVG/painting.html#FillProperties |
|
4257 if (element.hasAttribute("fill")) { |
|
4258 this.setFill(element.getStringAttribute("fill")); |
|
4259 } |
|
4260 if (element.hasAttribute("style")) { |
|
4261 var styleText = element.getStringAttribute("style"); |
|
4262 var styleTokens = styleText.toString().split( ";" ); |
|
4263 |
|
4264 for (var i = 0, j = styleTokens.length; i < j; i++) { |
|
4265 var tokens = p.trim(styleTokens[i].split( ":" )); |
|
4266 if (tokens[0] === "fill") { |
|
4267 this.setFill(tokens[1]); |
|
4268 } else if (tokens[0] === "fill-opacity") { |
|
4269 this.setFillOpacity(tokens[1]); |
|
4270 } else if (tokens[0] === "stroke") { |
|
4271 this.setStroke(tokens[1]); |
|
4272 } else if (tokens[0] === "stroke-width") { |
|
4273 this.setStrokeWeight(tokens[1]); |
|
4274 } else if (tokens[0] === "stroke-linecap") { |
|
4275 this.setStrokeCap(tokens[1]); |
|
4276 } else if (tokens[0] === "stroke-linejoin") { |
|
4277 this.setStrokeJoin(tokens[1]); |
|
4278 } else if (tokens[0] === "stroke-opacity") { |
|
4279 this.setStrokeOpacity(tokens[1]); |
|
4280 } else if (tokens[0] === "opacity") { |
|
4281 this.setOpacity(tokens[1]); |
|
4282 } // Other attributes are not yet implemented |
|
4283 } |
|
4284 } |
|
4285 }; |
|
4286 /** |
|
4287 * @member PShapeSVG |
|
4288 * PShapeSVG.parseColors() helper function |
|
4289 * |
|
4290 * @param {String} opacityText the value of fillOpacity |
|
4291 * |
|
4292 * @see PShapeSVG#parseColors |
|
4293 */ |
|
4294 PShapeSVG.prototype.setFillOpacity = function(opacityText) { |
|
4295 this.fillOpacity = parseFloat(opacityText); |
|
4296 this.fillColor = this.fillOpacity * 255 << 24 | |
|
4297 this.fillColor & 0xFFFFFF; |
|
4298 }; |
|
4299 /** |
|
4300 * @member PShapeSVG |
|
4301 * PShapeSVG.parseColors() helper function |
|
4302 * |
|
4303 * @param {String} fillText the value of fill |
|
4304 * |
|
4305 * @see PShapeSVG#parseColors |
|
4306 */ |
|
4307 PShapeSVG.prototype.setFill = function (fillText) { |
|
4308 var opacityMask = this.fillColor & 0xFF000000; |
|
4309 if (fillText === "none") { |
|
4310 this.fill = false; |
|
4311 } else if (fillText.indexOf("#") === 0) { |
|
4312 this.fill = true; |
|
4313 if (fillText.length === 4) { |
|
4314 // convert #00F to #0000FF |
|
4315 fillText = fillText.replace(/#(.)(.)(.)/,"#$1$1$2$2$3$3"); |
|
4316 } |
|
4317 this.fillColor = opacityMask | |
|
4318 (parseInt(fillText.substring(1), 16 )) & |
|
4319 0xFFFFFF; |
|
4320 } else if (fillText.indexOf("rgb") === 0) { |
|
4321 this.fill = true; |
|
4322 this.fillColor = opacityMask | this.parseRGB(fillText); |
|
4323 } else if (fillText.indexOf("url(#") === 0) { |
|
4324 this.fillName = fillText.substring(5, fillText.length - 1 ); |
|
4325 } else if (colors[fillText]) { |
|
4326 this.fill = true; |
|
4327 this.fillColor = opacityMask | |
|
4328 (parseInt(colors[fillText].substring(1), 16)) & |
|
4329 0xFFFFFF; |
|
4330 } |
|
4331 }; |
|
4332 /** |
|
4333 * @member PShapeSVG |
|
4334 * PShapeSVG.parseColors() helper function |
|
4335 * |
|
4336 * @param {String} opacity the value of opacity |
|
4337 * |
|
4338 * @see PShapeSVG#parseColors |
|
4339 */ |
|
4340 PShapeSVG.prototype.setOpacity = function(opacity) { |
|
4341 this.strokeColor = parseFloat(opacity) * 255 << 24 | |
|
4342 this.strokeColor & 0xFFFFFF; |
|
4343 this.fillColor = parseFloat(opacity) * 255 << 24 | |
|
4344 this.fillColor & 0xFFFFFF; |
|
4345 }; |
|
4346 /** |
|
4347 * @member PShapeSVG |
|
4348 * PShapeSVG.parseColors() helper function |
|
4349 * |
|
4350 * @param {String} strokeText the value to set stroke to |
|
4351 * |
|
4352 * @see PShapeSVG#parseColors |
|
4353 */ |
|
4354 PShapeSVG.prototype.setStroke = function(strokeText) { |
|
4355 var opacityMask = this.strokeColor & 0xFF000000; |
|
4356 if (strokeText === "none") { |
|
4357 this.stroke = false; |
|
4358 } else if (strokeText.charAt( 0 ) === "#") { |
|
4359 this.stroke = true; |
|
4360 if (strokeText.length === 4) { |
|
4361 // convert #00F to #0000FF |
|
4362 strokeText = strokeText.replace(/#(.)(.)(.)/,"#$1$1$2$2$3$3"); |
|
4363 } |
|
4364 this.strokeColor = opacityMask | |
|
4365 (parseInt( strokeText.substring( 1 ), 16 )) & |
|
4366 0xFFFFFF; |
|
4367 } else if (strokeText.indexOf( "rgb" ) === 0 ) { |
|
4368 this.stroke = true; |
|
4369 this.strokeColor = opacityMask | this.parseRGB(strokeText); |
|
4370 } else if (strokeText.indexOf( "url(#" ) === 0) { |
|
4371 this.strokeName = strokeText.substring(5, strokeText.length - 1); |
|
4372 } else if (colors[strokeText]) { |
|
4373 this.stroke = true; |
|
4374 this.strokeColor = opacityMask | |
|
4375 (parseInt(colors[strokeText].substring(1), 16)) & |
|
4376 0xFFFFFF; |
|
4377 } |
|
4378 }; |
|
4379 /** |
|
4380 * @member PShapeSVG |
|
4381 * PShapeSVG.parseColors() helper function |
|
4382 * |
|
4383 * @param {String} weight the value to set strokeWeight to |
|
4384 * |
|
4385 * @see PShapeSVG#parseColors |
|
4386 */ |
|
4387 PShapeSVG.prototype.setStrokeWeight = function(weight) { |
|
4388 this.strokeWeight = this.parseUnitSize(weight); |
|
4389 }; |
|
4390 /** |
|
4391 * @member PShapeSVG |
|
4392 * PShapeSVG.parseColors() helper function |
|
4393 * |
|
4394 * @param {String} linejoin the value to set strokeJoin to |
|
4395 * |
|
4396 * @see PShapeSVG#parseColors |
|
4397 */ |
|
4398 PShapeSVG.prototype.setStrokeJoin = function(linejoin) { |
|
4399 if (linejoin === "miter") { |
|
4400 this.strokeJoin = PConstants.MITER; |
|
4401 |
|
4402 } else if (linejoin === "round") { |
|
4403 this.strokeJoin = PConstants.ROUND; |
|
4404 |
|
4405 } else if (linejoin === "bevel") { |
|
4406 this.strokeJoin = PConstants.BEVEL; |
|
4407 } |
|
4408 }; |
|
4409 /** |
|
4410 * @member PShapeSVG |
|
4411 * PShapeSVG.parseColors() helper function |
|
4412 * |
|
4413 * @param {String} linecap the value to set strokeCap to |
|
4414 * |
|
4415 * @see PShapeSVG#parseColors |
|
4416 */ |
|
4417 PShapeSVG.prototype.setStrokeCap = function (linecap) { |
|
4418 if (linecap === "butt") { |
|
4419 this.strokeCap = PConstants.SQUARE; |
|
4420 |
|
4421 } else if (linecap === "round") { |
|
4422 this.strokeCap = PConstants.ROUND; |
|
4423 |
|
4424 } else if (linecap === "square") { |
|
4425 this.strokeCap = PConstants.PROJECT; |
|
4426 } |
|
4427 }; |
|
4428 /** |
|
4429 * @member PShapeSVG |
|
4430 * PShapeSVG.parseColors() helper function |
|
4431 * |
|
4432 * @param {String} opacityText the value to set stroke opacity to |
|
4433 * |
|
4434 * @see PShapeSVG#parseColors |
|
4435 */ |
|
4436 PShapeSVG.prototype.setStrokeOpacity = function (opacityText) { |
|
4437 this.strokeOpacity = parseFloat(opacityText); |
|
4438 this.strokeColor = this.strokeOpacity * 255 << 24 | |
|
4439 this.strokeColor & |
|
4440 0xFFFFFF; |
|
4441 }; |
|
4442 /** |
|
4443 * @member PShapeSVG |
|
4444 * The parseRGB() function parses an rbg() color string and returns a color int |
|
4445 * |
|
4446 * @param {String} color the color to parse in rbg() format |
|
4447 * |
|
4448 * @return {int} the equivalent color int |
|
4449 */ |
|
4450 PShapeSVG.prototype.parseRGB = function(color) { |
|
4451 var sub = color.substring(color.indexOf('(') + 1, color.indexOf(')')); |
|
4452 var values = sub.split(", "); |
|
4453 return (values[0] << 16) | (values[1] << 8) | (values[2]); |
|
4454 }; |
|
4455 /** |
|
4456 * @member PShapeSVG |
|
4457 * The parseUnitSize() function parse a size that may have a suffix for its units. |
|
4458 * Ignoring cases where this could also be a percentage. |
|
4459 * The <A HREF="http://www.w3.org/TR/SVG/coords.html#Units">units</A> spec: |
|
4460 * <UL> |
|
4461 * <LI>"1pt" equals "1.25px" (and therefore 1.25 user units) |
|
4462 * <LI>"1pc" equals "15px" (and therefore 15 user units) |
|
4463 * <LI>"1mm" would be "3.543307px" (3.543307 user units) |
|
4464 * <LI>"1cm" equals "35.43307px" (and therefore 35.43307 user units) |
|
4465 * <LI>"1in" equals "90px" (and therefore 90 user units) |
|
4466 * </UL> |
|
4467 */ |
|
4468 PShapeSVG.prototype.parseUnitSize = function (text) { |
|
4469 var len = text.length - 2; |
|
4470 if (len < 0) { return text; } |
|
4471 if (text.indexOf("pt") === len) { |
|
4472 return parseFloat(text.substring(0, len)) * 1.25; |
|
4473 } |
|
4474 if (text.indexOf("pc") === len) { |
|
4475 return parseFloat( text.substring( 0, len)) * 15; |
|
4476 } |
|
4477 if (text.indexOf("mm") === len) { |
|
4478 return parseFloat( text.substring(0, len)) * 3.543307; |
|
4479 } |
|
4480 if (text.indexOf("cm") === len) { |
|
4481 return parseFloat(text.substring(0, len)) * 35.43307; |
|
4482 } |
|
4483 if (text.indexOf("in") === len) { |
|
4484 return parseFloat(text.substring(0, len)) * 90; |
|
4485 } |
|
4486 if (text.indexOf("px") === len) { |
|
4487 return parseFloat(text.substring(0, len)); |
|
4488 } |
|
4489 return parseFloat(text); |
|
4490 }; |
|
4491 /** |
|
4492 * The shape() function displays shapes to the screen. |
|
4493 * Processing currently works with SVG shapes only. |
|
4494 * The <b>shape</b> parameter specifies the shape to display and the <b>x</b> |
|
4495 * and <b>y</b> parameters define the location of the shape from its |
|
4496 * upper-left corner. |
|
4497 * The shape is displayed at its original size unless the <b>width</b> |
|
4498 * and <b>height</b> parameters specify a different size. |
|
4499 * The <b>shapeMode()</b> function changes the way the parameters work. |
|
4500 * A call to <b>shapeMode(CORNERS)</b>, for example, will change the width |
|
4501 * and height parameters to define the x and y values of the opposite corner |
|
4502 * of the shape. |
|
4503 * <br><br> |
|
4504 * Note complex shapes may draw awkwardly with P2D, P3D, and OPENGL. Those |
|
4505 * renderers do not yet support shapes that have holes or complicated breaks. |
|
4506 * |
|
4507 * @param {PShape} shape the shape to display |
|
4508 * @param {int|float} x x-coordinate of the shape |
|
4509 * @param {int|float} y y-coordinate of the shape |
|
4510 * @param {int|float} width width to display the shape |
|
4511 * @param {int|float} height height to display the shape |
|
4512 * |
|
4513 * @see PShape |
|
4514 * @see loadShape() |
|
4515 * @see shapeMode() |
|
4516 */ |
|
4517 p.shape = function(shape, x, y, width, height) { |
|
4518 if (arguments.length >= 1 && arguments[0] !== null) { |
|
4519 if (shape.isVisible()) { |
|
4520 p.pushMatrix(); |
|
4521 if (curShapeMode === PConstants.CENTER) { |
|
4522 if (arguments.length === 5) { |
|
4523 p.translate(x - width/2, y - height/2); |
|
4524 p.scale(width / shape.getWidth(), height / shape.getHeight()); |
|
4525 } else if (arguments.length === 3) { |
|
4526 p.translate(x - shape.getWidth()/2, - shape.getHeight()/2); |
|
4527 } else { |
|
4528 p.translate(-shape.getWidth()/2, -shape.getHeight()/2); |
|
4529 } |
|
4530 } else if (curShapeMode === PConstants.CORNER) { |
|
4531 if (arguments.length === 5) { |
|
4532 p.translate(x, y); |
|
4533 p.scale(width / shape.getWidth(), height / shape.getHeight()); |
|
4534 } else if (arguments.length === 3) { |
|
4535 p.translate(x, y); |
|
4536 } |
|
4537 } else if (curShapeMode === PConstants.CORNERS) { |
|
4538 if (arguments.length === 5) { |
|
4539 width -= x; |
|
4540 height -= y; |
|
4541 p.translate(x, y); |
|
4542 p.scale(width / shape.getWidth(), height / shape.getHeight()); |
|
4543 } else if (arguments.length === 3) { |
|
4544 p.translate(x, y); |
|
4545 } |
|
4546 } |
|
4547 shape.draw(); |
|
4548 if ((arguments.length === 1 && curShapeMode === PConstants.CENTER ) || arguments.length > 1) { |
|
4549 p.popMatrix(); |
|
4550 } |
|
4551 } |
|
4552 } |
|
4553 }; |
|
4554 |
|
4555 /** |
|
4556 * The shapeMode() function modifies the location from which shapes draw. |
|
4557 * The default mode is <b>shapeMode(CORNER)</b>, which specifies the |
|
4558 * location to be the upper left corner of the shape and uses the third |
|
4559 * and fourth parameters of <b>shape()</b> to specify the width and height. |
|
4560 * The syntax <b>shapeMode(CORNERS)</b> uses the first and second parameters |
|
4561 * of <b>shape()</b> to set the location of one corner and uses the third |
|
4562 * and fourth parameters to set the opposite corner. |
|
4563 * The syntax <b>shapeMode(CENTER)</b> draws the shape from its center point |
|
4564 * and uses the third and forth parameters of <b>shape()</b> to specify the |
|
4565 * width and height. |
|
4566 * The parameter must be written in "ALL CAPS" because Processing syntax |
|
4567 * is case sensitive. |
|
4568 * |
|
4569 * @param {int} mode One of CORNER, CORNERS, CENTER |
|
4570 * |
|
4571 * @see shape() |
|
4572 * @see rectMode() |
|
4573 */ |
|
4574 p.shapeMode = function (mode) { |
|
4575 curShapeMode = mode; |
|
4576 }; |
|
4577 |
|
4578 /** |
|
4579 * The loadShape() function loads vector shapes into a variable of type PShape. Currently, only SVG files may be loaded. |
|
4580 * In most cases, <b>loadShape()</b> should be used inside <b>setup()</b> because loading shapes inside <b>draw()</b> will reduce the speed of a sketch. |
|
4581 * |
|
4582 * @param {String} filename an SVG file |
|
4583 * |
|
4584 * @return {PShape} a object of type PShape or null |
|
4585 * @see PShape |
|
4586 * @see PApplet#shape() |
|
4587 * @see PApplet#shapeMode() |
|
4588 */ |
|
4589 p.loadShape = function (filename) { |
|
4590 if (arguments.length === 1) { |
|
4591 if (filename.indexOf(".svg") > -1) { |
|
4592 return new PShapeSVG(null, filename); |
|
4593 } |
|
4594 } |
|
4595 return null; |
|
4596 }; |
|
4597 |
|
4598 /** |
|
4599 * XMLAttribute is an attribute of a XML element. This is an internal class |
|
4600 * |
|
4601 * @param {String} fname the full name of the attribute |
|
4602 * @param {String} n the short name of the attribute |
|
4603 * @param {String} namespace the namespace URI of the attribute |
|
4604 * @param {String} v the value of the attribute |
|
4605 * @param {String }t the type of the attribute |
|
4606 * |
|
4607 * @see XMLElement |
|
4608 */ |
|
4609 var XMLAttribute = function(fname, n, nameSpace, v, t){ |
|
4610 this.fullName = fname || ""; |
|
4611 this.name = n || ""; |
|
4612 this.namespace = nameSpace || ""; |
|
4613 this.value = v; |
|
4614 this.type = t; |
|
4615 }; |
|
4616 /** |
|
4617 * XMLAttribute methods |
|
4618 */ |
|
4619 XMLAttribute.prototype = { |
|
4620 /** |
|
4621 * @member XMLAttribute |
|
4622 * The getName() function returns the short name of the attribute |
|
4623 * |
|
4624 * @return {String} the short name of the attribute |
|
4625 */ |
|
4626 getName: function() { |
|
4627 return this.name; |
|
4628 }, |
|
4629 /** |
|
4630 * @member XMLAttribute |
|
4631 * The getFullName() function returns the full name of the attribute |
|
4632 * |
|
4633 * @return {String} the full name of the attribute |
|
4634 */ |
|
4635 getFullName: function() { |
|
4636 return this.fullName; |
|
4637 }, |
|
4638 /** |
|
4639 * @member XMLAttribute |
|
4640 * The getNamespace() function returns the namespace of the attribute |
|
4641 * |
|
4642 * @return {String} the namespace of the attribute |
|
4643 */ |
|
4644 getNamespace: function() { |
|
4645 return this.namespace; |
|
4646 }, |
|
4647 /** |
|
4648 * @member XMLAttribute |
|
4649 * The getValue() function returns the value of the attribute |
|
4650 * |
|
4651 * @return {String} the value of the attribute |
|
4652 */ |
|
4653 getValue: function() { |
|
4654 return this.value; |
|
4655 }, |
|
4656 /** |
|
4657 * @member XMLAttribute |
|
4658 * The getValue() function returns the type of the attribute |
|
4659 * |
|
4660 * @return {String} the type of the attribute |
|
4661 */ |
|
4662 getType: function() { |
|
4663 return this.type; |
|
4664 }, |
|
4665 /** |
|
4666 * @member XMLAttribute |
|
4667 * The setValue() function sets the value of the attribute |
|
4668 * |
|
4669 * @param {String} newval the new value |
|
4670 */ |
|
4671 setValue: function(newval) { |
|
4672 this.value = newval; |
|
4673 } |
|
4674 }; |
|
4675 |
|
4676 /** |
|
4677 * XMLElement is a representation of an XML object. The object is able to parse XML code |
|
4678 * |
|
4679 * @param {PApplet} parent typically use "this" |
|
4680 * @param {String} filename name of the XML/SVG file to load |
|
4681 * @param {String} xml the xml/svg string |
|
4682 * @param {String} fullname the full name of the element |
|
4683 * @param {String} namespace the namespace of the URI |
|
4684 * @param {String} systemID the system ID of the XML data where the element starts |
|
4685 * @param {Integer }lineNr the line in the XML data where the element starts |
|
4686 */ |
|
4687 var XMLElement = p.XMLElement = function() { |
|
4688 this.attributes = []; |
|
4689 this.children = []; |
|
4690 this.fullName = null; |
|
4691 this.name = null; |
|
4692 this.namespace = ""; |
|
4693 this.content = null; |
|
4694 this.parent = null; |
|
4695 this.lineNr = ""; |
|
4696 this.systemID = ""; |
|
4697 this.type = "ELEMENT"; |
|
4698 |
|
4699 if (arguments.length === 4) { |
|
4700 this.fullName = arguments[0] || ""; |
|
4701 if (arguments[1]) { |
|
4702 this.name = arguments[1]; |
|
4703 } else { |
|
4704 var index = this.fullName.indexOf(':'); |
|
4705 if (index >= 0) { |
|
4706 this.name = this.fullName.substring(index + 1); |
|
4707 } else { |
|
4708 this.name = this.fullName; |
|
4709 } |
|
4710 } |
|
4711 this.namespace = arguments[1]; |
|
4712 this.lineNr = arguments[3]; |
|
4713 this.systemID = arguments[2]; |
|
4714 } |
|
4715 else if ((arguments.length === 2 && arguments[1].indexOf(".") > -1) ) { |
|
4716 // filename or svg xml element |
|
4717 this.parse(arguments[arguments.length -1]); |
|
4718 } else if (arguments.length === 1 && typeof arguments[0] === "string"){ |
|
4719 this.parse(arguments[0]); |
|
4720 } |
|
4721 }; |
|
4722 /** |
|
4723 * XMLElement methods |
|
4724 * missing: enumerateAttributeNames(), enumerateChildren(), |
|
4725 * NOTE: parse does not work when a url is passed in |
|
4726 */ |
|
4727 XMLElement.prototype = { |
|
4728 /** |
|
4729 * @member XMLElement |
|
4730 * The parse() function retrieves the file via ajax() and uses DOMParser() |
|
4731 * parseFromString method to make an XML document |
|
4732 * @addon |
|
4733 * |
|
4734 * @param {String} filename name of the XML/SVG file to load |
|
4735 * |
|
4736 * @throws ExceptionType Error loading document |
|
4737 * |
|
4738 * @see XMLElement#parseChildrenRecursive |
|
4739 */ |
|
4740 parse: function(textstring) { |
|
4741 var xmlDoc; |
|
4742 try { |
|
4743 var extension = textstring.substring(textstring.length-4); |
|
4744 if (extension === ".xml" || extension === ".svg") { |
|
4745 textstring = ajax(textstring); |
|
4746 } |
|
4747 xmlDoc = new DOMParser().parseFromString(textstring, "text/xml"); |
|
4748 var elements = xmlDoc.documentElement; |
|
4749 if (elements) { |
|
4750 this.parseChildrenRecursive(null, elements); |
|
4751 } else { |
|
4752 throw ("Error loading document"); |
|
4753 } |
|
4754 return this; |
|
4755 } catch(e) { |
|
4756 throw(e); |
|
4757 } |
|
4758 }, |
|
4759 /** |
|
4760 * @member XMLElement |
|
4761 * Internal helper function for parse(). |
|
4762 * Loops through the |
|
4763 * @addon |
|
4764 * |
|
4765 * @param {XMLElement} parent the parent node |
|
4766 * @param {XML document childNodes} elementpath the remaining nodes that need parsing |
|
4767 * |
|
4768 * @return {XMLElement} the new element and its children elements |
|
4769 */ |
|
4770 parseChildrenRecursive: function (parent , elementpath){ |
|
4771 var xmlelement, |
|
4772 xmlattribute, |
|
4773 tmpattrib, |
|
4774 l, m, |
|
4775 child; |
|
4776 if (!parent) { // this element is the root element |
|
4777 this.fullName = elementpath.localName; |
|
4778 this.name = elementpath.nodeName; |
|
4779 xmlelement = this; |
|
4780 } else { // this element has a parent |
|
4781 xmlelement = new XMLElement(elementpath.localName, elementpath.nodeName, "", ""); |
|
4782 xmlelement.parent = parent; |
|
4783 } |
|
4784 |
|
4785 // if this is a text node, return a PCData element, instead of an XML element. |
|
4786 if(elementpath.nodeType === 3 && elementpath.textContent !== "") { |
|
4787 return this.createPCDataElement(elementpath.textContent); |
|
4788 } |
|
4789 |
|
4790 // bind all attributes |
|
4791 for (l = 0, m = elementpath.attributes.length; l < m; l++) { |
|
4792 tmpattrib = elementpath.attributes[l]; |
|
4793 xmlattribute = new XMLAttribute(tmpattrib.getname, |
|
4794 tmpattrib.nodeName, |
|
4795 tmpattrib.namespaceURI, |
|
4796 tmpattrib.nodeValue, |
|
4797 tmpattrib.nodeType); |
|
4798 xmlelement.attributes.push(xmlattribute); |
|
4799 } |
|
4800 |
|
4801 // bind all children |
|
4802 for (l = 0, m = elementpath.childNodes.length; l < m; l++) { |
|
4803 var node = elementpath.childNodes[l]; |
|
4804 if (node.nodeType === 1 || node.nodeType === 3) { // ELEMENT_NODE or TEXT_NODE |
|
4805 child = xmlelement.parseChildrenRecursive(xmlelement, node); |
|
4806 if (child !== null) { |
|
4807 xmlelement.children.push(child); |
|
4808 } |
|
4809 } |
|
4810 } |
|
4811 |
|
4812 return xmlelement; |
|
4813 }, |
|
4814 /** |
|
4815 * @member XMLElement |
|
4816 * The createElement() function Creates an empty element |
|
4817 * |
|
4818 * @param {String} fullName the full name of the element |
|
4819 * @param {String} namespace the namespace URI |
|
4820 * @param {String} systemID the system ID of the XML data where the element starts |
|
4821 * @param {int} lineNr the line in the XML data where the element starts |
|
4822 */ |
|
4823 createElement: function () { |
|
4824 if (arguments.length === 2) { |
|
4825 return new XMLElement(arguments[0], arguments[1], null, null); |
|
4826 } |
|
4827 return new XMLElement(arguments[0], arguments[1], arguments[2], arguments[3]); |
|
4828 }, |
|
4829 /** |
|
4830 * @member XMLElement |
|
4831 * The createPCDataElement() function creates an element to be used for #PCDATA content. |
|
4832 * Because Processing discards whitespace TEXT nodes, this method will not build an element |
|
4833 * if the passed content is empty after trimming for whitespace. |
|
4834 * |
|
4835 * @return {XMLElement} new "test" XMLElement, or null if content consists only of whitespace |
|
4836 */ |
|
4837 createPCDataElement: function (content) { |
|
4838 if(content.replace(/^\s+$/g,"") === "") { |
|
4839 return null; |
|
4840 } |
|
4841 var pcdata = new XMLElement(); |
|
4842 pcdata.content = content; |
|
4843 pcdata.type = "TEXT"; |
|
4844 return pcdata; |
|
4845 }, |
|
4846 /** |
|
4847 * @member XMLElement |
|
4848 * The hasAttribute() function returns whether an attribute exists |
|
4849 * |
|
4850 * @param {String} name name of the attribute |
|
4851 * @param {String} namespace the namespace URI of the attribute |
|
4852 * |
|
4853 * @return {boolean} true if the attribute exists |
|
4854 */ |
|
4855 hasAttribute: function () { |
|
4856 if (arguments.length === 1) { |
|
4857 return this.getAttribute(arguments[0]) !== null; |
|
4858 } |
|
4859 if (arguments.length === 2) { |
|
4860 return this.getAttribute(arguments[0],arguments[1]) !== null; |
|
4861 } |
|
4862 }, |
|
4863 /** |
|
4864 * @member XMLElement |
|
4865 * The equals() function checks to see if the XMLElement being passed in equals another XMLElement |
|
4866 * |
|
4867 * @param {XMLElement} rawElement the element to compare to |
|
4868 * |
|
4869 * @return {boolean} true if the element equals another element |
|
4870 */ |
|
4871 equals: function(other) { |
|
4872 if (!(other instanceof XMLElement)) { |
|
4873 return false; |
|
4874 } |
|
4875 var i, j; |
|
4876 if (this.name !== other.getLocalName()) { return false; } |
|
4877 if (this.attributes.length !== other.getAttributeCount()) { return false; } |
|
4878 // attributes may be ordered differently |
|
4879 if (this.attributes.length !== other.attributes.length) { return false; } |
|
4880 var attr_name, attr_ns, attr_value, attr_type, attr_other; |
|
4881 for (i = 0, j = this.attributes.length; i < j; i++) { |
|
4882 attr_name = this.attributes[i].getName(); |
|
4883 attr_ns = this.attributes[i].getNamespace(); |
|
4884 attr_other = other.findAttribute(attr_name, attr_ns); |
|
4885 if (attr_other === null) { return false; } |
|
4886 if (this.attributes[i].getValue() !== attr_other.getValue()) { return false; } |
|
4887 if (this.attributes[i].getType() !== attr_other.getType()) { return false; } |
|
4888 } |
|
4889 // children must be ordered identically |
|
4890 if (this.children.length !== other.getChildCount()) { return false; } |
|
4891 if (this.children.length>0) { |
|
4892 var child1, child2; |
|
4893 for (i = 0, j = this.children.length; i < j; i++) { |
|
4894 child1 = this.getChild(i); |
|
4895 child2 = other.getChild(i); |
|
4896 if (!child1.equals(child2)) { return false; } |
|
4897 } |
|
4898 return true; |
|
4899 } |
|
4900 return (this.content === other.content); |
|
4901 }, |
|
4902 /** |
|
4903 * @member XMLElement |
|
4904 * The getContent() function returns the content of an element. If there is no such content, null is returned |
|
4905 * |
|
4906 * @return {String} the (possibly null) content |
|
4907 */ |
|
4908 getContent: function(){ |
|
4909 if (this.type === "TEXT") { |
|
4910 return this.content; |
|
4911 } |
|
4912 var children = this.children; |
|
4913 if (children.length === 1 && children[0].type === "TEXT") { |
|
4914 return children[0].content; |
|
4915 } |
|
4916 return null; |
|
4917 }, |
|
4918 /** |
|
4919 * @member XMLElement |
|
4920 * The getAttribute() function returns the value of an attribute |
|
4921 * |
|
4922 * @param {String} name the non-null full name of the attribute |
|
4923 * @param {String} namespace the namespace URI, which may be null |
|
4924 * @param {String} defaultValue the default value of the attribute |
|
4925 * |
|
4926 * @return {String} the value, or defaultValue if the attribute does not exist |
|
4927 */ |
|
4928 getAttribute: function (){ |
|
4929 var attribute; |
|
4930 if( arguments.length === 2 ){ |
|
4931 attribute = this.findAttribute(arguments[0]); |
|
4932 if (attribute) { |
|
4933 return attribute.getValue(); |
|
4934 } |
|
4935 return arguments[1]; |
|
4936 } else if (arguments.length === 1) { |
|
4937 attribute = this.findAttribute(arguments[0]); |
|
4938 if (attribute) { |
|
4939 return attribute.getValue(); |
|
4940 } |
|
4941 return null; |
|
4942 } else if (arguments.length === 3) { |
|
4943 attribute = this.findAttribute(arguments[0],arguments[1]); |
|
4944 if (attribute) { |
|
4945 return attribute.getValue(); |
|
4946 } |
|
4947 return arguments[2]; |
|
4948 } |
|
4949 }, |
|
4950 /** |
|
4951 * @member XMLElement |
|
4952 * The getStringAttribute() function returns the string attribute of the element |
|
4953 * If the <b>defaultValue</b> parameter is used and the attribute doesn't exist, the <b>defaultValue</b> value is returned. |
|
4954 * When calling the function without the <b>defaultValue</b> parameter, if the attribute doesn't exist, the value 0 is returned. |
|
4955 * |
|
4956 * @param name the name of the attribute |
|
4957 * @param defaultValue value returned if the attribute is not found |
|
4958 * |
|
4959 * @return {String} the value, or defaultValue if the attribute does not exist |
|
4960 */ |
|
4961 getStringAttribute: function() { |
|
4962 if (arguments.length === 1) { |
|
4963 return this.getAttribute(arguments[0]); |
|
4964 } |
|
4965 if (arguments.length === 2){ |
|
4966 return this.getAttribute(arguments[0], arguments[1]); |
|
4967 } |
|
4968 return this.getAttribute(arguments[0], arguments[1],arguments[2]); |
|
4969 }, |
|
4970 /** |
|
4971 * Processing 1.5 XML API wrapper for the generic String |
|
4972 * attribute getter. This may only take one argument. |
|
4973 */ |
|
4974 getString: function(attributeName) { |
|
4975 return this.getStringAttribute(attributeName); |
|
4976 }, |
|
4977 /** |
|
4978 * @member XMLElement |
|
4979 * The getFloatAttribute() function returns the float attribute of the element. |
|
4980 * If the <b>defaultValue</b> parameter is used and the attribute doesn't exist, the <b>defaultValue</b> value is returned. |
|
4981 * When calling the function without the <b>defaultValue</b> parameter, if the attribute doesn't exist, the value 0 is returned. |
|
4982 * |
|
4983 * @param name the name of the attribute |
|
4984 * @param defaultValue value returned if the attribute is not found |
|
4985 * |
|
4986 * @return {float} the value, or defaultValue if the attribute does not exist |
|
4987 */ |
|
4988 getFloatAttribute: function() { |
|
4989 if (arguments.length === 1 ) { |
|
4990 return parseFloat(this.getAttribute(arguments[0], 0)); |
|
4991 } |
|
4992 if (arguments.length === 2 ){ |
|
4993 return this.getAttribute(arguments[0], arguments[1]); |
|
4994 } |
|
4995 return this.getAttribute(arguments[0], arguments[1],arguments[2]); |
|
4996 }, |
|
4997 /** |
|
4998 * Processing 1.5 XML API wrapper for the generic float |
|
4999 * attribute getter. This may only take one argument. |
|
5000 */ |
|
5001 getFloat: function(attributeName) { |
|
5002 return this.getFloatAttribute(attributeName); |
|
5003 }, |
|
5004 /** |
|
5005 * @member XMLElement |
|
5006 * The getIntAttribute() function returns the integer attribute of the element. |
|
5007 * If the <b>defaultValue</b> parameter is used and the attribute doesn't exist, the <b>defaultValue</b> value is returned. |
|
5008 * When calling the function without the <b>defaultValue</b> parameter, if the attribute doesn't exist, the value 0 is returned. |
|
5009 * |
|
5010 * @param name the name of the attribute |
|
5011 * @param defaultValue value returned if the attribute is not found |
|
5012 * |
|
5013 * @return {int} the value, or defaultValue if the attribute does not exist |
|
5014 */ |
|
5015 getIntAttribute: function () { |
|
5016 if (arguments.length === 1) { |
|
5017 return this.getAttribute( arguments[0], 0 ); |
|
5018 } |
|
5019 if (arguments.length === 2) { |
|
5020 return this.getAttribute(arguments[0], arguments[1]); |
|
5021 } |
|
5022 return this.getAttribute(arguments[0], arguments[1],arguments[2]); |
|
5023 }, |
|
5024 /** |
|
5025 * Processing 1.5 XML API wrapper for the generic int |
|
5026 * attribute getter. This may only take one argument. |
|
5027 */ |
|
5028 getInt: function(attributeName) { |
|
5029 return this.getIntAttribute(attributeName); |
|
5030 }, |
|
5031 /** |
|
5032 * @member XMLElement |
|
5033 * The hasChildren() function returns whether the element has children. |
|
5034 * |
|
5035 * @return {boolean} true if the element has children. |
|
5036 */ |
|
5037 hasChildren: function () { |
|
5038 return this.children.length > 0 ; |
|
5039 }, |
|
5040 /** |
|
5041 * @member XMLElement |
|
5042 * The addChild() function adds a child element |
|
5043 * |
|
5044 * @param {XMLElement} child the non-null child to add. |
|
5045 */ |
|
5046 addChild: function (child) { |
|
5047 if (child !== null) { |
|
5048 child.parent = this; |
|
5049 this.children.push(child); |
|
5050 } |
|
5051 }, |
|
5052 /** |
|
5053 * @member XMLElement |
|
5054 * The insertChild() function inserts a child element at the index provided |
|
5055 * |
|
5056 * @param {XMLElement} child the non-null child to add. |
|
5057 * @param {int} index where to put the child. |
|
5058 */ |
|
5059 insertChild: function (child, index) { |
|
5060 if (child) { |
|
5061 if ((child.getLocalName() === null) && (! this.hasChildren())) { |
|
5062 var lastChild = this.children[this.children.length -1]; |
|
5063 if (lastChild.getLocalName() === null) { |
|
5064 lastChild.setContent(lastChild.getContent() + child.getContent()); |
|
5065 return; |
|
5066 } |
|
5067 } |
|
5068 child.parent = this; |
|
5069 this.children.splice(index,0,child); |
|
5070 } |
|
5071 }, |
|
5072 /** |
|
5073 * @member XMLElement |
|
5074 * The getChild() returns the child XMLElement as specified by the <b>index</b> parameter. |
|
5075 * The value of the <b>index</b> parameter must be less than the total number of children to avoid going out of the array storing the child elements. |
|
5076 * When the <b>path</b> parameter is specified, then it will return all children that match that path. The path is a series of elements and sub-elements, separated by slashes. |
|
5077 * |
|
5078 * @param {int} index where to put the child. |
|
5079 * @param {String} path path to a particular element |
|
5080 * |
|
5081 * @return {XMLElement} the element |
|
5082 */ |
|
5083 getChild: function (){ |
|
5084 if (typeof arguments[0] === "number") { |
|
5085 return this.children[arguments[0]]; |
|
5086 } |
|
5087 if (arguments[0].indexOf('/') !== -1) { // path was given |
|
5088 this.getChildRecursive(arguments[0].split("/"), 0); |
|
5089 return null; |
|
5090 } |
|
5091 var kid, kidName; |
|
5092 for (var i = 0, j = this.getChildCount(); i < j; i++) { |
|
5093 kid = this.getChild(i); |
|
5094 kidName = kid.getName(); |
|
5095 if (kidName !== null && kidName === arguments[0]) { |
|
5096 return kid; |
|
5097 } |
|
5098 } |
|
5099 return null; |
|
5100 }, |
|
5101 /** |
|
5102 * @member XMLElement |
|
5103 * The getChildren() returns all of the children as an XMLElement array. |
|
5104 * When the <b>path</b> parameter is specified, then it will return all children that match that path. |
|
5105 * The path is a series of elements and sub-elements, separated by slashes. |
|
5106 * |
|
5107 * @param {String} path element name or path/to/element |
|
5108 * |
|
5109 * @return {XMLElement} array of child elements that match |
|
5110 * |
|
5111 * @see XMLElement#getChildCount() |
|
5112 * @see XMLElement#getChild() |
|
5113 */ |
|
5114 getChildren: function(){ |
|
5115 if (arguments.length === 1) { |
|
5116 if (typeof arguments[0] === "number") { |
|
5117 return this.getChild( arguments[0]); |
|
5118 } |
|
5119 if (arguments[0].indexOf('/') !== -1) { // path was given |
|
5120 return this.getChildrenRecursive( arguments[0].split("/"), 0); |
|
5121 } |
|
5122 var matches = []; |
|
5123 var kid, kidName; |
|
5124 for (var i = 0, j = this.getChildCount(); i < j; i++) { |
|
5125 kid = this.getChild(i); |
|
5126 kidName = kid.getName(); |
|
5127 if (kidName !== null && kidName === arguments[0]) { |
|
5128 matches.push(kid); |
|
5129 } |
|
5130 } |
|
5131 return matches; |
|
5132 } |
|
5133 return this.children; |
|
5134 }, |
|
5135 /** |
|
5136 * @member XMLElement |
|
5137 * The getChildCount() returns the number of children for the element. |
|
5138 * |
|
5139 * @return {int} the count |
|
5140 * |
|
5141 * @see XMLElement#getChild() |
|
5142 * @see XMLElement#getChildren() |
|
5143 */ |
|
5144 getChildCount: function(){ |
|
5145 return this.children.length; |
|
5146 }, |
|
5147 /** |
|
5148 * @member XMLElement |
|
5149 * Internal helper function for getChild(). |
|
5150 * |
|
5151 * @param {String[]} items result of splitting the query on slashes |
|
5152 * @param {int} offset where in the items[] array we're currently looking |
|
5153 * |
|
5154 * @return {XMLElement} matching element or null if no match |
|
5155 */ |
|
5156 getChildRecursive: function (items, offset) { |
|
5157 var kid, kidName; |
|
5158 for(var i = 0, j = this.getChildCount(); i < j; i++) { |
|
5159 kid = this.getChild(i); |
|
5160 kidName = kid.getName(); |
|
5161 if (kidName !== null && kidName === items[offset]) { |
|
5162 if (offset === items.length-1) { |
|
5163 return kid; |
|
5164 } |
|
5165 offset += 1; |
|
5166 return kid.getChildRecursive(items, offset); |
|
5167 } |
|
5168 } |
|
5169 return null; |
|
5170 }, |
|
5171 /** |
|
5172 * @member XMLElement |
|
5173 * Internal helper function for getChildren(). |
|
5174 * |
|
5175 * @param {String[]} items result of splitting the query on slashes |
|
5176 * @param {int} offset where in the items[] array we're currently looking |
|
5177 * |
|
5178 * @return {XMLElement[]} matching elements or empty array if no match |
|
5179 */ |
|
5180 getChildrenRecursive: function (items, offset) { |
|
5181 if (offset === items.length-1) { |
|
5182 return this.getChildren(items[offset]); |
|
5183 } |
|
5184 var matches = this.getChildren(items[offset]); |
|
5185 var kidMatches = []; |
|
5186 for (var i = 0; i < matches.length; i++) { |
|
5187 kidMatches = kidMatches.concat(matches[i].getChildrenRecursive(items, offset+1)); |
|
5188 } |
|
5189 return kidMatches; |
|
5190 }, |
|
5191 /** |
|
5192 * @member XMLElement |
|
5193 * The isLeaf() function returns whether the element is a leaf element. |
|
5194 * |
|
5195 * @return {boolean} true if the element has no children. |
|
5196 */ |
|
5197 isLeaf: function(){ |
|
5198 return !this.hasChildren(); |
|
5199 }, |
|
5200 /** |
|
5201 * @member XMLElement |
|
5202 * The listChildren() function put the names of all children into an array. Same as looping through |
|
5203 * each child and calling getName() on each XMLElement. |
|
5204 * |
|
5205 * @return {String[]} a list of element names. |
|
5206 */ |
|
5207 listChildren: function() { |
|
5208 var arr = []; |
|
5209 for (var i = 0, j = this.children.length; i < j; i++) { |
|
5210 arr.push( this.getChild(i).getName()); |
|
5211 } |
|
5212 return arr; |
|
5213 }, |
|
5214 /** |
|
5215 * @member XMLElement |
|
5216 * The removeAttribute() function removes an attribute |
|
5217 * |
|
5218 * @param {String} name the non-null name of the attribute. |
|
5219 * @param {String} namespace the namespace URI of the attribute, which may be null. |
|
5220 */ |
|
5221 removeAttribute: function (name , namespace) { |
|
5222 this.namespace = namespace || ""; |
|
5223 for (var i = 0, j = this.attributes.length; i < j; i++) { |
|
5224 if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) { |
|
5225 this.attributes.splice(i, 1); |
|
5226 break; |
|
5227 } |
|
5228 } |
|
5229 }, |
|
5230 /** |
|
5231 * @member XMLElement |
|
5232 * The removeChild() removes a child element. |
|
5233 * |
|
5234 * @param {XMLElement} child the the non-null child to be renoved |
|
5235 */ |
|
5236 removeChild: function(child) { |
|
5237 if (child) { |
|
5238 for (var i = 0, j = this.children.length; i < j; i++) { |
|
5239 if (this.children[i].equals(child)) { |
|
5240 this.children.splice(i, 1); |
|
5241 break; |
|
5242 } |
|
5243 } |
|
5244 } |
|
5245 }, |
|
5246 /** |
|
5247 * @member XMLElement |
|
5248 * The removeChildAtIndex() removes the child located at a certain index |
|
5249 * |
|
5250 * @param {int} index the index of the child, where the first child has index 0 |
|
5251 */ |
|
5252 removeChildAtIndex: function(index) { |
|
5253 if (this.children.length > index) { //make sure its not outofbounds |
|
5254 this.children.splice(index, 1); |
|
5255 } |
|
5256 }, |
|
5257 /** |
|
5258 * @member XMLElement |
|
5259 * The findAttribute() function searches an attribute |
|
5260 * |
|
5261 * @param {String} name fullName the non-null full name of the attribute |
|
5262 * @param {String} namespace the name space, which may be null |
|
5263 * |
|
5264 * @return {XMLAttribute} the attribute, or null if the attribute does not exist. |
|
5265 */ |
|
5266 findAttribute: function (name, namespace) { |
|
5267 this.namespace = namespace || ""; |
|
5268 for (var i = 0, j = this.attributes.length; i < j; i++) { |
|
5269 if (this.attributes[i].getName() === name && this.attributes[i].getNamespace() === this.namespace) { |
|
5270 return this.attributes[i]; |
|
5271 } |
|
5272 } |
|
5273 return null; |
|
5274 }, |
|
5275 /** |
|
5276 * @member XMLElement |
|
5277 * The setAttribute() function sets an attribute. |
|
5278 * |
|
5279 * @param {String} name the non-null full name of the attribute |
|
5280 * @param {String} namespace the non-null value of the attribute |
|
5281 */ |
|
5282 setAttribute: function() { |
|
5283 var attr; |
|
5284 if (arguments.length === 3) { |
|
5285 var index = arguments[0].indexOf(':'); |
|
5286 var name = arguments[0].substring(index + 1); |
|
5287 attr = this.findAttribute(name, arguments[1]); |
|
5288 if (attr) { |
|
5289 attr.setValue(arguments[2]); |
|
5290 } else { |
|
5291 attr = new XMLAttribute(arguments[0], name, arguments[1], arguments[2], "CDATA"); |
|
5292 this.attributes.push(attr); |
|
5293 } |
|
5294 } else { |
|
5295 attr = this.findAttribute(arguments[0]); |
|
5296 if (attr) { |
|
5297 attr.setValue(arguments[1]); |
|
5298 } else { |
|
5299 attr = new XMLAttribute(arguments[0], arguments[0], null, arguments[1], "CDATA"); |
|
5300 this.attributes.push(attr); |
|
5301 } |
|
5302 } |
|
5303 }, |
|
5304 /** |
|
5305 * Processing 1.5 XML API wrapper for the generic String |
|
5306 * attribute setter. This must take two arguments. |
|
5307 */ |
|
5308 setString: function(attribute, value) { |
|
5309 this.setAttribute(attribute, value); |
|
5310 }, |
|
5311 /** |
|
5312 * Processing 1.5 XML API wrapper for the generic int |
|
5313 * attribute setter. This must take two arguments. |
|
5314 */ |
|
5315 setInt: function(attribute, value) { |
|
5316 this.setAttribute(attribute, value); |
|
5317 }, |
|
5318 /** |
|
5319 * Processing 1.5 XML API wrapper for the generic float |
|
5320 * attribute setter. This must take two arguments. |
|
5321 */ |
|
5322 setFloat: function(attribute, value) { |
|
5323 this.setAttribute(attribute, value); |
|
5324 }, |
|
5325 /** |
|
5326 * @member XMLElement |
|
5327 * The setContent() function sets the #PCDATA content. It is an error to call this method with a |
|
5328 * non-null value if there are child objects. |
|
5329 * |
|
5330 * @param {String} content the (possibly null) content |
|
5331 */ |
|
5332 setContent: function(content) { |
|
5333 if (this.children.length>0) { |
|
5334 Processing.debug("Tried to set content for XMLElement with children"); } |
|
5335 this.content = content; |
|
5336 }, |
|
5337 /** |
|
5338 * @member XMLElement |
|
5339 * The setName() function sets the full name. This method also sets the short name and clears the |
|
5340 * namespace URI. |
|
5341 * |
|
5342 * @param {String} name the non-null name |
|
5343 * @param {String} namespace the namespace URI, which may be null. |
|
5344 */ |
|
5345 setName: function() { |
|
5346 if (arguments.length === 1) { |
|
5347 this.name = arguments[0]; |
|
5348 this.fullName = arguments[0]; |
|
5349 this.namespace = null; |
|
5350 } else { |
|
5351 var index = arguments[0].indexOf(':'); |
|
5352 if ((arguments[1] === null) || (index < 0)) { |
|
5353 this.name = arguments[0]; |
|
5354 } else { |
|
5355 this.name = arguments[0].substring(index + 1); |
|
5356 } |
|
5357 this.fullName = arguments[0]; |
|
5358 this.namespace = arguments[1]; |
|
5359 } |
|
5360 }, |
|
5361 /** |
|
5362 * @member XMLElement |
|
5363 * The getName() function returns the full name (i.e. the name including an eventual namespace |
|
5364 * prefix) of the element. |
|
5365 * |
|
5366 * @return {String} the name, or null if the element only contains #PCDATA. |
|
5367 */ |
|
5368 getName: function() { |
|
5369 return this.fullName; |
|
5370 }, |
|
5371 /** |
|
5372 * @member XMLElement |
|
5373 * The getLocalName() function returns the local name (i.e. the name excluding an eventual namespace |
|
5374 * prefix) of the element. |
|
5375 * |
|
5376 * @return {String} the name, or null if the element only contains #PCDATA. |
|
5377 */ |
|
5378 getLocalName: function() { |
|
5379 return this.name; |
|
5380 }, |
|
5381 /** |
|
5382 * @member XMLElement |
|
5383 * The getAttributeCount() function returns the number of attributes for the node |
|
5384 * that this XMLElement represents. |
|
5385 * |
|
5386 * @return {int} the number of attributes in this XMLElement |
|
5387 */ |
|
5388 getAttributeCount: function() { |
|
5389 return this.attributes.length; |
|
5390 }, |
|
5391 /** |
|
5392 * @member XMLElement |
|
5393 * The toString() function returns the XML definition of an XMLElement. |
|
5394 * |
|
5395 * @return {String} the XML definition of this XMLElement |
|
5396 */ |
|
5397 toString: function() { |
|
5398 // shortcut for text nodes |
|
5399 if(this.type==="TEXT") { return this.content; } |
|
5400 |
|
5401 // real XMLElements |
|
5402 var tagstring = (this.namespace !== "" && this.namespace !== this.name ? this.namespace + ":" : "") + this.name; |
|
5403 var xmlstring = "<" + tagstring; |
|
5404 var a,c; |
|
5405 |
|
5406 // serialize the attributes to XML string |
|
5407 for (a = 0; a<this.attributes.length; a++) { |
|
5408 var attr = this.attributes[a]; |
|
5409 xmlstring += " " + attr.getName() + "=" + '"' + attr.getValue() + '"'; |
|
5410 } |
|
5411 |
|
5412 // serialize all children to XML string |
|
5413 if (this.children.length === 0) { |
|
5414 if (this.content==="") { |
|
5415 xmlstring += "/>"; |
|
5416 } else { |
|
5417 xmlstring += ">" + this.content + "</"+tagstring+">"; |
|
5418 } |
|
5419 } else { |
|
5420 xmlstring += ">"; |
|
5421 for (c = 0; c<this.children.length; c++) { |
|
5422 xmlstring += this.children[c].toString(); |
|
5423 } |
|
5424 xmlstring += "</" + tagstring + ">"; |
|
5425 } |
|
5426 return xmlstring; |
|
5427 } |
|
5428 }; |
|
5429 |
|
5430 /** |
|
5431 * static Processing 1.5 XML API wrapper for the |
|
5432 * parse method. This may only take one argument. |
|
5433 */ |
|
5434 XMLElement.parse = function(xmlstring) { |
|
5435 var element = new XMLElement(); |
|
5436 element.parse(xmlstring); |
|
5437 return element; |
|
5438 }; |
|
5439 |
|
5440 //////////////////////////////////////////////////////////////////////////// |
|
5441 // 2D Matrix |
|
5442 //////////////////////////////////////////////////////////////////////////// |
|
5443 /** |
|
5444 * Helper function for printMatrix(). Finds the largest scalar |
|
5445 * in the matrix, then number of digits left of the decimal. |
|
5446 * Call from PMatrix2D and PMatrix3D's print() function. |
|
5447 */ |
|
5448 var printMatrixHelper = function(elements) { |
|
5449 var big = 0; |
|
5450 for (var i = 0; i < elements.length; i++) { |
|
5451 if (i !== 0) { |
|
5452 big = Math.max(big, Math.abs(elements[i])); |
|
5453 } else { |
|
5454 big = Math.abs(elements[i]); |
|
5455 } |
|
5456 } |
|
5457 |
|
5458 var digits = (big + "").indexOf("."); |
|
5459 if (digits === 0) { |
|
5460 digits = 1; |
|
5461 } else if (digits === -1) { |
|
5462 digits = (big + "").length; |
|
5463 } |
|
5464 |
|
5465 return digits; |
|
5466 }; |
|
5467 /** |
|
5468 * PMatrix2D is a 3x2 affine matrix implementation. The constructor accepts another PMatrix2D or a list of six float elements. |
|
5469 * If no parameters are provided the matrix is set to the identity matrix. |
|
5470 * |
|
5471 * @param {PMatrix2D} matrix the initial matrix to set to |
|
5472 * @param {float} m00 the first element of the matrix |
|
5473 * @param {float} m01 the second element of the matrix |
|
5474 * @param {float} m02 the third element of the matrix |
|
5475 * @param {float} m10 the fourth element of the matrix |
|
5476 * @param {float} m11 the fifth element of the matrix |
|
5477 * @param {float} m12 the sixth element of the matrix |
|
5478 */ |
|
5479 var PMatrix2D = p.PMatrix2D = function() { |
|
5480 if (arguments.length === 0) { |
|
5481 this.reset(); |
|
5482 } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { |
|
5483 this.set(arguments[0].array()); |
|
5484 } else if (arguments.length === 6) { |
|
5485 this.set(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); |
|
5486 } |
|
5487 }; |
|
5488 /** |
|
5489 * PMatrix2D methods |
|
5490 */ |
|
5491 PMatrix2D.prototype = { |
|
5492 /** |
|
5493 * @member PMatrix2D |
|
5494 * The set() function sets the matrix elements. The function accepts either another PMatrix2D, an array of elements, or a list of six floats. |
|
5495 * |
|
5496 * @param {PMatrix2D} matrix the matrix to set this matrix to |
|
5497 * @param {float[]} elements an array of elements to set this matrix to |
|
5498 * @param {float} m00 the first element of the matrix |
|
5499 * @param {float} m01 the third element of the matrix |
|
5500 * @param {float} m10 the fourth element of the matrix |
|
5501 * @param {float} m11 the fith element of the matrix |
|
5502 * @param {float} m12 the sixth element of the matrix |
|
5503 */ |
|
5504 set: function() { |
|
5505 if (arguments.length === 6) { |
|
5506 var a = arguments; |
|
5507 this.set([a[0], a[1], a[2], |
|
5508 a[3], a[4], a[5]]); |
|
5509 } else if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { |
|
5510 this.elements = arguments[0].array(); |
|
5511 } else if (arguments.length === 1 && arguments[0] instanceof Array) { |
|
5512 this.elements = arguments[0].slice(); |
|
5513 } |
|
5514 }, |
|
5515 /** |
|
5516 * @member PMatrix2D |
|
5517 * The get() function returns a copy of this PMatrix2D. |
|
5518 * |
|
5519 * @return {PMatrix2D} a copy of this PMatrix2D |
|
5520 */ |
|
5521 get: function() { |
|
5522 var outgoing = new PMatrix2D(); |
|
5523 outgoing.set(this.elements); |
|
5524 return outgoing; |
|
5525 }, |
|
5526 /** |
|
5527 * @member PMatrix2D |
|
5528 * The reset() function sets this PMatrix2D to the identity matrix. |
|
5529 */ |
|
5530 reset: function() { |
|
5531 this.set([1, 0, 0, 0, 1, 0]); |
|
5532 }, |
|
5533 /** |
|
5534 * @member PMatrix2D |
|
5535 * The array() function returns a copy of the element values. |
|
5536 * @addon |
|
5537 * |
|
5538 * @return {float[]} returns a copy of the element values |
|
5539 */ |
|
5540 array: function array() { |
|
5541 return this.elements.slice(); |
|
5542 }, |
|
5543 /** |
|
5544 * @member PMatrix2D |
|
5545 * The translate() function translates this matrix by moving the current coordinates to the location specified by tx and ty. |
|
5546 * |
|
5547 * @param {float} tx the x-axis coordinate to move to |
|
5548 * @param {float} ty the y-axis coordinate to move to |
|
5549 */ |
|
5550 translate: function(tx, ty) { |
|
5551 this.elements[2] = tx * this.elements[0] + ty * this.elements[1] + this.elements[2]; |
|
5552 this.elements[5] = tx * this.elements[3] + ty * this.elements[4] + this.elements[5]; |
|
5553 }, |
|
5554 /** |
|
5555 * @member PMatrix2D |
|
5556 * The invTranslate() function translates this matrix by moving the current coordinates to the negative location specified by tx and ty. |
|
5557 * |
|
5558 * @param {float} tx the x-axis coordinate to move to |
|
5559 * @param {float} ty the y-axis coordinate to move to |
|
5560 */ |
|
5561 invTranslate: function(tx, ty) { |
|
5562 this.translate(-tx, -ty); |
|
5563 }, |
|
5564 /** |
|
5565 * @member PMatrix2D |
|
5566 * The transpose() function is not used in processingjs. |
|
5567 */ |
|
5568 transpose: function() { |
|
5569 // Does nothing in Processing. |
|
5570 }, |
|
5571 /** |
|
5572 * @member PMatrix2D |
|
5573 * The mult() function multiplied this matrix. |
|
5574 * If two array elements are passed in the function will multiply a two element vector against this matrix. |
|
5575 * If target is null or not length four, a new float array will be returned. |
|
5576 * The values for vec and target can be the same (though that's less efficient). |
|
5577 * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. |
|
5578 * |
|
5579 * @param {PVector} source, target the PVectors used to multiply this matrix |
|
5580 * @param {float[]} source, target the arrays used to multiply this matrix |
|
5581 * |
|
5582 * @return {PVector|float[]} returns a PVector or an array representing the new matrix |
|
5583 */ |
|
5584 mult: function(source, target) { |
|
5585 var x, y; |
|
5586 if (source instanceof PVector) { |
|
5587 x = source.x; |
|
5588 y = source.y; |
|
5589 if (!target) { |
|
5590 target = new PVector(); |
|
5591 } |
|
5592 } else if (source instanceof Array) { |
|
5593 x = source[0]; |
|
5594 y = source[1]; |
|
5595 if (!target) { |
|
5596 target = []; |
|
5597 } |
|
5598 } |
|
5599 if (target instanceof Array) { |
|
5600 target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2]; |
|
5601 target[1] = this.elements[3] * x + this.elements[4] * y + this.elements[5]; |
|
5602 } else if (target instanceof PVector) { |
|
5603 target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2]; |
|
5604 target.y = this.elements[3] * x + this.elements[4] * y + this.elements[5]; |
|
5605 target.z = 0; |
|
5606 } |
|
5607 return target; |
|
5608 }, |
|
5609 /** |
|
5610 * @member PMatrix2D |
|
5611 * The multX() function calculates the x component of a vector from a transformation. |
|
5612 * |
|
5613 * @param {float} x the x component of the vector being transformed |
|
5614 * @param {float} y the y component of the vector being transformed |
|
5615 * |
|
5616 * @return {float} returnes the result of the calculation |
|
5617 */ |
|
5618 multX: function(x, y) { |
|
5619 return (x * this.elements[0] + y * this.elements[1] + this.elements[2]); |
|
5620 }, |
|
5621 /** |
|
5622 * @member PMatrix2D |
|
5623 * The multY() function calculates the y component of a vector from a transformation. |
|
5624 * |
|
5625 * @param {float} x the x component of the vector being transformed |
|
5626 * @param {float} y the y component of the vector being transformed |
|
5627 * |
|
5628 * @return {float} returnes the result of the calculation |
|
5629 */ |
|
5630 multY: function(x, y) { |
|
5631 return (x * this.elements[3] + y * this.elements[4] + this.elements[5]); |
|
5632 }, |
|
5633 /** |
|
5634 * @member PMatrix2D |
|
5635 * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. |
|
5636 * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. |
|
5637 * |
|
5638 * @param {float} angle angle of skew specified in radians |
|
5639 */ |
|
5640 skewX: function(angle) { |
|
5641 this.apply(1, 0, 1, angle, 0, 0); |
|
5642 }, |
|
5643 /** |
|
5644 * @member PMatrix2D |
|
5645 * The skewY() function skews the matrix along the y-axis the amount specified by the angle parameter. |
|
5646 * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. |
|
5647 * |
|
5648 * @param {float} angle angle of skew specified in radians |
|
5649 */ |
|
5650 skewY: function(angle) { |
|
5651 this.apply(1, 0, 1, 0, angle, 0); |
|
5652 }, |
|
5653 /** |
|
5654 * @member PMatrix2D |
|
5655 * The determinant() function calvculates the determinant of this matrix. |
|
5656 * |
|
5657 * @return {float} the determinant of the matrix |
|
5658 */ |
|
5659 determinant: function() { |
|
5660 return (this.elements[0] * this.elements[4] - this.elements[1] * this.elements[3]); |
|
5661 }, |
|
5662 /** |
|
5663 * @member PMatrix2D |
|
5664 * The invert() function inverts this matrix |
|
5665 * |
|
5666 * @return {boolean} true if successful |
|
5667 */ |
|
5668 invert: function() { |
|
5669 var d = this.determinant(); |
|
5670 if (Math.abs( d ) > PConstants.MIN_INT) { |
|
5671 var old00 = this.elements[0]; |
|
5672 var old01 = this.elements[1]; |
|
5673 var old02 = this.elements[2]; |
|
5674 var old10 = this.elements[3]; |
|
5675 var old11 = this.elements[4]; |
|
5676 var old12 = this.elements[5]; |
|
5677 this.elements[0] = old11 / d; |
|
5678 this.elements[3] = -old10 / d; |
|
5679 this.elements[1] = -old01 / d; |
|
5680 this.elements[4] = old00 / d; |
|
5681 this.elements[2] = (old01 * old12 - old11 * old02) / d; |
|
5682 this.elements[5] = (old10 * old02 - old00 * old12) / d; |
|
5683 return true; |
|
5684 } |
|
5685 return false; |
|
5686 }, |
|
5687 /** |
|
5688 * @member PMatrix2D |
|
5689 * The scale() function increases or decreases the size of a shape by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. |
|
5690 * This is equivalent to a two parameter call. |
|
5691 * |
|
5692 * @param {float} sx the amount to scale on the x-axis |
|
5693 * @param {float} sy the amount to scale on the y-axis |
|
5694 */ |
|
5695 scale: function(sx, sy) { |
|
5696 if (sx && !sy) { |
|
5697 sy = sx; |
|
5698 } |
|
5699 if (sx && sy) { |
|
5700 this.elements[0] *= sx; |
|
5701 this.elements[1] *= sy; |
|
5702 this.elements[3] *= sx; |
|
5703 this.elements[4] *= sy; |
|
5704 } |
|
5705 }, |
|
5706 /** |
|
5707 * @member PMatrix2D |
|
5708 * The invScale() function decreases or increases the size of a shape by contracting and expanding vertices. When only one parameter is specified scale will occur in all dimensions. |
|
5709 * This is equivalent to a two parameter call. |
|
5710 * |
|
5711 * @param {float} sx the amount to scale on the x-axis |
|
5712 * @param {float} sy the amount to scale on the y-axis |
|
5713 */ |
|
5714 invScale: function(sx, sy) { |
|
5715 if (sx && !sy) { |
|
5716 sy = sx; |
|
5717 } |
|
5718 this.scale(1 / sx, 1 / sy); |
|
5719 }, |
|
5720 /** |
|
5721 * @member PMatrix2D |
|
5722 * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix2D or a list of floats can be passed in. |
|
5723 * |
|
5724 * @param {PMatrix2D} matrix the matrix to apply this matrix to |
|
5725 * @param {float} m00 the first element of the matrix |
|
5726 * @param {float} m01 the third element of the matrix |
|
5727 * @param {float} m10 the fourth element of the matrix |
|
5728 * @param {float} m11 the fith element of the matrix |
|
5729 * @param {float} m12 the sixth element of the matrix |
|
5730 */ |
|
5731 apply: function() { |
|
5732 var source; |
|
5733 if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { |
|
5734 source = arguments[0].array(); |
|
5735 } else if (arguments.length === 6) { |
|
5736 source = Array.prototype.slice.call(arguments); |
|
5737 } else if (arguments.length === 1 && arguments[0] instanceof Array) { |
|
5738 source = arguments[0]; |
|
5739 } |
|
5740 |
|
5741 var result = [0, 0, this.elements[2], |
|
5742 0, 0, this.elements[5]]; |
|
5743 var e = 0; |
|
5744 for (var row = 0; row < 2; row++) { |
|
5745 for (var col = 0; col < 3; col++, e++) { |
|
5746 result[e] += this.elements[row * 3 + 0] * source[col + 0] + |
|
5747 this.elements[row * 3 + 1] * source[col + 3]; |
|
5748 } |
|
5749 } |
|
5750 this.elements = result.slice(); |
|
5751 }, |
|
5752 /** |
|
5753 * @member PMatrix2D |
|
5754 * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix2D or elements of a matrix can be passed in. |
|
5755 * |
|
5756 * @param {PMatrix2D} matrix the matrix to apply this matrix to |
|
5757 * @param {float} m00 the first element of the matrix |
|
5758 * @param {float} m01 the third element of the matrix |
|
5759 * @param {float} m10 the fourth element of the matrix |
|
5760 * @param {float} m11 the fith element of the matrix |
|
5761 * @param {float} m12 the sixth element of the matrix |
|
5762 */ |
|
5763 preApply: function() { |
|
5764 var source; |
|
5765 if (arguments.length === 1 && arguments[0] instanceof PMatrix2D) { |
|
5766 source = arguments[0].array(); |
|
5767 } else if (arguments.length === 6) { |
|
5768 source = Array.prototype.slice.call(arguments); |
|
5769 } else if (arguments.length === 1 && arguments[0] instanceof Array) { |
|
5770 source = arguments[0]; |
|
5771 } |
|
5772 var result = [0, 0, source[2], |
|
5773 0, 0, source[5]]; |
|
5774 result[2] = source[2] + this.elements[2] * source[0] + this.elements[5] * source[1]; |
|
5775 result[5] = source[5] + this.elements[2] * source[3] + this.elements[5] * source[4]; |
|
5776 result[0] = this.elements[0] * source[0] + this.elements[3] * source[1]; |
|
5777 result[3] = this.elements[0] * source[3] + this.elements[3] * source[4]; |
|
5778 result[1] = this.elements[1] * source[0] + this.elements[4] * source[1]; |
|
5779 result[4] = this.elements[1] * source[3] + this.elements[4] * source[4]; |
|
5780 this.elements = result.slice(); |
|
5781 }, |
|
5782 /** |
|
5783 * @member PMatrix2D |
|
5784 * The rotate() function rotates the matrix. |
|
5785 * |
|
5786 * @param {float} angle the angle of rotation in radiants |
|
5787 */ |
|
5788 rotate: function(angle) { |
|
5789 var c = Math.cos(angle); |
|
5790 var s = Math.sin(angle); |
|
5791 var temp1 = this.elements[0]; |
|
5792 var temp2 = this.elements[1]; |
|
5793 this.elements[0] = c * temp1 + s * temp2; |
|
5794 this.elements[1] = -s * temp1 + c * temp2; |
|
5795 temp1 = this.elements[3]; |
|
5796 temp2 = this.elements[4]; |
|
5797 this.elements[3] = c * temp1 + s * temp2; |
|
5798 this.elements[4] = -s * temp1 + c * temp2; |
|
5799 }, |
|
5800 /** |
|
5801 * @member PMatrix2D |
|
5802 * The rotateZ() function rotates the matrix. |
|
5803 * |
|
5804 * @param {float} angle the angle of rotation in radiants |
|
5805 */ |
|
5806 rotateZ: function(angle) { |
|
5807 this.rotate(angle); |
|
5808 }, |
|
5809 /** |
|
5810 * @member PMatrix2D |
|
5811 * The invRotateZ() function rotates the matrix in opposite direction. |
|
5812 * |
|
5813 * @param {float} angle the angle of rotation in radiants |
|
5814 */ |
|
5815 invRotateZ: function(angle) { |
|
5816 this.rotateZ(angle - Math.PI); |
|
5817 }, |
|
5818 /** |
|
5819 * @member PMatrix2D |
|
5820 * The print() function prints out the elements of this matrix |
|
5821 */ |
|
5822 print: function() { |
|
5823 var digits = printMatrixHelper(this.elements); |
|
5824 var output = "" + p.nfs(this.elements[0], digits, 4) + " " + |
|
5825 p.nfs(this.elements[1], digits, 4) + " " + |
|
5826 p.nfs(this.elements[2], digits, 4) + "\n" + |
|
5827 p.nfs(this.elements[3], digits, 4) + " " + |
|
5828 p.nfs(this.elements[4], digits, 4) + " " + |
|
5829 p.nfs(this.elements[5], digits, 4) + "\n\n"; |
|
5830 p.println(output); |
|
5831 } |
|
5832 }; |
|
5833 |
|
5834 /** |
|
5835 * PMatrix3D is a 4x4 matrix implementation. The constructor accepts another PMatrix3D or a list of six or sixteen float elements. |
|
5836 * If no parameters are provided the matrix is set to the identity matrix. |
|
5837 */ |
|
5838 var PMatrix3D = p.PMatrix3D = function() { |
|
5839 // When a matrix is created, it is set to an identity matrix |
|
5840 this.reset(); |
|
5841 }; |
|
5842 /** |
|
5843 * PMatrix3D methods |
|
5844 */ |
|
5845 PMatrix3D.prototype = { |
|
5846 /** |
|
5847 * @member PMatrix2D |
|
5848 * The set() function sets the matrix elements. The function accepts either another PMatrix3D, an array of elements, or a list of six or sixteen floats. |
|
5849 * |
|
5850 * @param {PMatrix3D} matrix the initial matrix to set to |
|
5851 * @param {float[]} elements an array of elements to set this matrix to |
|
5852 * @param {float} m00 the first element of the matrix |
|
5853 * @param {float} m01 the second element of the matrix |
|
5854 * @param {float} m02 the third element of the matrix |
|
5855 * @param {float} m03 the fourth element of the matrix |
|
5856 * @param {float} m10 the fifth element of the matrix |
|
5857 * @param {float} m11 the sixth element of the matrix |
|
5858 * @param {float} m12 the seventh element of the matrix |
|
5859 * @param {float} m13 the eight element of the matrix |
|
5860 * @param {float} m20 the nineth element of the matrix |
|
5861 * @param {float} m21 the tenth element of the matrix |
|
5862 * @param {float} m22 the eleventh element of the matrix |
|
5863 * @param {float} m23 the twelveth element of the matrix |
|
5864 * @param {float} m30 the thirteenth element of the matrix |
|
5865 * @param {float} m31 the fourtheenth element of the matrix |
|
5866 * @param {float} m32 the fivetheenth element of the matrix |
|
5867 * @param {float} m33 the sixteenth element of the matrix |
|
5868 */ |
|
5869 set: function() { |
|
5870 if (arguments.length === 16) { |
|
5871 this.elements = Array.prototype.slice.call(arguments); |
|
5872 } else if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { |
|
5873 this.elements = arguments[0].array(); |
|
5874 } else if (arguments.length === 1 && arguments[0] instanceof Array) { |
|
5875 this.elements = arguments[0].slice(); |
|
5876 } |
|
5877 }, |
|
5878 /** |
|
5879 * @member PMatrix3D |
|
5880 * The get() function returns a copy of this PMatrix3D. |
|
5881 * |
|
5882 * @return {PMatrix3D} a copy of this PMatrix3D |
|
5883 */ |
|
5884 get: function() { |
|
5885 var outgoing = new PMatrix3D(); |
|
5886 outgoing.set(this.elements); |
|
5887 return outgoing; |
|
5888 }, |
|
5889 /** |
|
5890 * @member PMatrix3D |
|
5891 * The reset() function sets this PMatrix3D to the identity matrix. |
|
5892 */ |
|
5893 reset: function() { |
|
5894 this.elements = [1,0,0,0, |
|
5895 0,1,0,0, |
|
5896 0,0,1,0, |
|
5897 0,0,0,1]; |
|
5898 }, |
|
5899 /** |
|
5900 * @member PMatrix3D |
|
5901 * The array() function returns a copy of the element values. |
|
5902 * @addon |
|
5903 * |
|
5904 * @return {float[]} returns a copy of the element values |
|
5905 */ |
|
5906 array: function array() { |
|
5907 return this.elements.slice(); |
|
5908 }, |
|
5909 /** |
|
5910 * @member PMatrix3D |
|
5911 * The translate() function translates this matrix by moving the current coordinates to the location specified by tx, ty, and tz. |
|
5912 * |
|
5913 * @param {float} tx the x-axis coordinate to move to |
|
5914 * @param {float} ty the y-axis coordinate to move to |
|
5915 * @param {float} tz the z-axis coordinate to move to |
|
5916 */ |
|
5917 translate: function(tx, ty, tz) { |
|
5918 if (tz === undef) { |
|
5919 tz = 0; |
|
5920 } |
|
5921 |
|
5922 this.elements[3] += tx * this.elements[0] + ty * this.elements[1] + tz * this.elements[2]; |
|
5923 this.elements[7] += tx * this.elements[4] + ty * this.elements[5] + tz * this.elements[6]; |
|
5924 this.elements[11] += tx * this.elements[8] + ty * this.elements[9] + tz * this.elements[10]; |
|
5925 this.elements[15] += tx * this.elements[12] + ty * this.elements[13] + tz * this.elements[14]; |
|
5926 }, |
|
5927 /** |
|
5928 * @member PMatrix3D |
|
5929 * The transpose() function transpose this matrix. |
|
5930 */ |
|
5931 transpose: function() { |
|
5932 var temp = this.elements[4]; |
|
5933 this.elements[4] = this.elements[1]; |
|
5934 this.elements[1] = temp; |
|
5935 |
|
5936 temp = this.elements[8]; |
|
5937 this.elements[8] = this.elements[2]; |
|
5938 this.elements[2] = temp; |
|
5939 |
|
5940 temp = this.elements[6]; |
|
5941 this.elements[6] = this.elements[9]; |
|
5942 this.elements[9] = temp; |
|
5943 |
|
5944 temp = this.elements[3]; |
|
5945 this.elements[3] = this.elements[12]; |
|
5946 this.elements[12] = temp; |
|
5947 |
|
5948 temp = this.elements[7]; |
|
5949 this.elements[7] = this.elements[13]; |
|
5950 this.elements[13] = temp; |
|
5951 |
|
5952 temp = this.elements[11]; |
|
5953 this.elements[11] = this.elements[14]; |
|
5954 this.elements[14] = temp; |
|
5955 }, |
|
5956 /** |
|
5957 * @member PMatrix3D |
|
5958 * The mult() function multiplied this matrix. |
|
5959 * If two array elements are passed in the function will multiply a two element vector against this matrix. |
|
5960 * If target is null or not length four, a new float array will be returned. |
|
5961 * The values for vec and target can be the same (though that's less efficient). |
|
5962 * If two PVectors are passed in the function multiply the x and y coordinates of a PVector against this matrix. |
|
5963 * |
|
5964 * @param {PVector} source, target the PVectors used to multiply this matrix |
|
5965 * @param {float[]} source, target the arrays used to multiply this matrix |
|
5966 * |
|
5967 * @return {PVector|float[]} returns a PVector or an array representing the new matrix |
|
5968 */ |
|
5969 mult: function(source, target) { |
|
5970 var x, y, z, w; |
|
5971 if (source instanceof PVector) { |
|
5972 x = source.x; |
|
5973 y = source.y; |
|
5974 z = source.z; |
|
5975 w = 1; |
|
5976 if (!target) { |
|
5977 target = new PVector(); |
|
5978 } |
|
5979 } else if (source instanceof Array) { |
|
5980 x = source[0]; |
|
5981 y = source[1]; |
|
5982 z = source[2]; |
|
5983 w = source[3] || 1; |
|
5984 |
|
5985 if ( !target || (target.length !== 3 && target.length !== 4) ) { |
|
5986 target = [0, 0, 0]; |
|
5987 } |
|
5988 } |
|
5989 |
|
5990 if (target instanceof Array) { |
|
5991 if (target.length === 3) { |
|
5992 target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; |
|
5993 target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; |
|
5994 target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; |
|
5995 } else if (target.length === 4) { |
|
5996 target[0] = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; |
|
5997 target[1] = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; |
|
5998 target[2] = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; |
|
5999 target[3] = this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; |
|
6000 } |
|
6001 } |
|
6002 if (target instanceof PVector) { |
|
6003 target.x = this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; |
|
6004 target.y = this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; |
|
6005 target.z = this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; |
|
6006 } |
|
6007 return target; |
|
6008 }, |
|
6009 /** |
|
6010 * @member PMatrix3D |
|
6011 * The preApply() function applies another matrix to the left of this one. Note that either a PMatrix3D or elements of a matrix can be passed in. |
|
6012 * |
|
6013 * @param {PMatrix3D} matrix the matrix to apply this matrix to |
|
6014 * @param {float} m00 the first element of the matrix |
|
6015 * @param {float} m01 the second element of the matrix |
|
6016 * @param {float} m02 the third element of the matrix |
|
6017 * @param {float} m03 the fourth element of the matrix |
|
6018 * @param {float} m10 the fifth element of the matrix |
|
6019 * @param {float} m11 the sixth element of the matrix |
|
6020 * @param {float} m12 the seventh element of the matrix |
|
6021 * @param {float} m13 the eight element of the matrix |
|
6022 * @param {float} m20 the nineth element of the matrix |
|
6023 * @param {float} m21 the tenth element of the matrix |
|
6024 * @param {float} m22 the eleventh element of the matrix |
|
6025 * @param {float} m23 the twelveth element of the matrix |
|
6026 * @param {float} m30 the thirteenth element of the matrix |
|
6027 * @param {float} m31 the fourtheenth element of the matrix |
|
6028 * @param {float} m32 the fivetheenth element of the matrix |
|
6029 * @param {float} m33 the sixteenth element of the matrix |
|
6030 */ |
|
6031 preApply: function() { |
|
6032 var source; |
|
6033 if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { |
|
6034 source = arguments[0].array(); |
|
6035 } else if (arguments.length === 16) { |
|
6036 source = Array.prototype.slice.call(arguments); |
|
6037 } else if (arguments.length === 1 && arguments[0] instanceof Array) { |
|
6038 source = arguments[0]; |
|
6039 } |
|
6040 |
|
6041 var result = [0, 0, 0, 0, |
|
6042 0, 0, 0, 0, |
|
6043 0, 0, 0, 0, |
|
6044 0, 0, 0, 0]; |
|
6045 var e = 0; |
|
6046 for (var row = 0; row < 4; row++) { |
|
6047 for (var col = 0; col < 4; col++, e++) { |
|
6048 result[e] += this.elements[col + 0] * source[row * 4 + 0] + this.elements[col + 4] * |
|
6049 source[row * 4 + 1] + this.elements[col + 8] * source[row * 4 + 2] + |
|
6050 this.elements[col + 12] * source[row * 4 + 3]; |
|
6051 } |
|
6052 } |
|
6053 this.elements = result.slice(); |
|
6054 }, |
|
6055 /** |
|
6056 * @member PMatrix3D |
|
6057 * The apply() function multiplies the current matrix by the one specified through the parameters. Note that either a PMatrix3D or a list of floats can be passed in. |
|
6058 * |
|
6059 * @param {PMatrix3D} matrix the matrix to apply this matrix to |
|
6060 * @param {float} m00 the first element of the matrix |
|
6061 * @param {float} m01 the second element of the matrix |
|
6062 * @param {float} m02 the third element of the matrix |
|
6063 * @param {float} m03 the fourth element of the matrix |
|
6064 * @param {float} m10 the fifth element of the matrix |
|
6065 * @param {float} m11 the sixth element of the matrix |
|
6066 * @param {float} m12 the seventh element of the matrix |
|
6067 * @param {float} m13 the eight element of the matrix |
|
6068 * @param {float} m20 the nineth element of the matrix |
|
6069 * @param {float} m21 the tenth element of the matrix |
|
6070 * @param {float} m22 the eleventh element of the matrix |
|
6071 * @param {float} m23 the twelveth element of the matrix |
|
6072 * @param {float} m30 the thirteenth element of the matrix |
|
6073 * @param {float} m31 the fourtheenth element of the matrix |
|
6074 * @param {float} m32 the fivetheenth element of the matrix |
|
6075 * @param {float} m33 the sixteenth element of the matrix |
|
6076 */ |
|
6077 apply: function() { |
|
6078 var source; |
|
6079 if (arguments.length === 1 && arguments[0] instanceof PMatrix3D) { |
|
6080 source = arguments[0].array(); |
|
6081 } else if (arguments.length === 16) { |
|
6082 source = Array.prototype.slice.call(arguments); |
|
6083 } else if (arguments.length === 1 && arguments[0] instanceof Array) { |
|
6084 source = arguments[0]; |
|
6085 } |
|
6086 |
|
6087 var result = [0, 0, 0, 0, |
|
6088 0, 0, 0, 0, |
|
6089 0, 0, 0, 0, |
|
6090 0, 0, 0, 0]; |
|
6091 var e = 0; |
|
6092 for (var row = 0; row < 4; row++) { |
|
6093 for (var col = 0; col < 4; col++, e++) { |
|
6094 result[e] += this.elements[row * 4 + 0] * source[col + 0] + this.elements[row * 4 + 1] * |
|
6095 source[col + 4] + this.elements[row * 4 + 2] * source[col + 8] + |
|
6096 this.elements[row * 4 + 3] * source[col + 12]; |
|
6097 } |
|
6098 } |
|
6099 this.elements = result.slice(); |
|
6100 }, |
|
6101 /** |
|
6102 * @member PMatrix3D |
|
6103 * The rotate() function rotates the matrix. |
|
6104 * |
|
6105 * @param {float} angle the angle of rotation in radiants |
|
6106 */ |
|
6107 rotate: function(angle, v0, v1, v2) { |
|
6108 if (!v1) { |
|
6109 this.rotateZ(angle); |
|
6110 } else { |
|
6111 // TODO should make sure this vector is normalized |
|
6112 var c = p.cos(angle); |
|
6113 var s = p.sin(angle); |
|
6114 var t = 1.0 - c; |
|
6115 |
|
6116 this.apply((t * v0 * v0) + c, |
|
6117 (t * v0 * v1) - (s * v2), |
|
6118 (t * v0 * v2) + (s * v1), |
|
6119 0, |
|
6120 (t * v0 * v1) + (s * v2), |
|
6121 (t * v1 * v1) + c, |
|
6122 (t * v1 * v2) - (s * v0), |
|
6123 0, |
|
6124 (t * v0 * v2) - (s * v1), |
|
6125 (t * v1 * v2) + (s * v0), |
|
6126 (t * v2 * v2) + c, |
|
6127 0, |
|
6128 0, 0, 0, 1); |
|
6129 } |
|
6130 }, |
|
6131 /** |
|
6132 * @member PMatrix3D |
|
6133 * The invApply() function applies the inverted matrix to this matrix. |
|
6134 * |
|
6135 * @param {float} m00 the first element of the matrix |
|
6136 * @param {float} m01 the second element of the matrix |
|
6137 * @param {float} m02 the third element of the matrix |
|
6138 * @param {float} m03 the fourth element of the matrix |
|
6139 * @param {float} m10 the fifth element of the matrix |
|
6140 * @param {float} m11 the sixth element of the matrix |
|
6141 * @param {float} m12 the seventh element of the matrix |
|
6142 * @param {float} m13 the eight element of the matrix |
|
6143 * @param {float} m20 the nineth element of the matrix |
|
6144 * @param {float} m21 the tenth element of the matrix |
|
6145 * @param {float} m22 the eleventh element of the matrix |
|
6146 * @param {float} m23 the twelveth element of the matrix |
|
6147 * @param {float} m30 the thirteenth element of the matrix |
|
6148 * @param {float} m31 the fourtheenth element of the matrix |
|
6149 * @param {float} m32 the fivetheenth element of the matrix |
|
6150 * @param {float} m33 the sixteenth element of the matrix |
|
6151 * |
|
6152 * @return {boolean} returns true if the operation was successful. |
|
6153 */ |
|
6154 invApply: function() { |
|
6155 if (inverseCopy === undef) { |
|
6156 inverseCopy = new PMatrix3D(); |
|
6157 } |
|
6158 var a = arguments; |
|
6159 inverseCopy.set(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], |
|
6160 a[9], a[10], a[11], a[12], a[13], a[14], a[15]); |
|
6161 |
|
6162 if (!inverseCopy.invert()) { |
|
6163 return false; |
|
6164 } |
|
6165 this.preApply(inverseCopy); |
|
6166 return true; |
|
6167 }, |
|
6168 /** |
|
6169 * @member PMatrix3D |
|
6170 * The rotateZ() function rotates the matrix. |
|
6171 * |
|
6172 * @param {float} angle the angle of rotation in radiants |
|
6173 */ |
|
6174 rotateX: function(angle) { |
|
6175 var c = p.cos(angle); |
|
6176 var s = p.sin(angle); |
|
6177 this.apply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); |
|
6178 }, |
|
6179 /** |
|
6180 * @member PMatrix3D |
|
6181 * The rotateY() function rotates the matrix. |
|
6182 * |
|
6183 * @param {float} angle the angle of rotation in radiants |
|
6184 */ |
|
6185 rotateY: function(angle) { |
|
6186 var c = p.cos(angle); |
|
6187 var s = p.sin(angle); |
|
6188 this.apply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); |
|
6189 }, |
|
6190 /** |
|
6191 * @member PMatrix3D |
|
6192 * The rotateZ() function rotates the matrix. |
|
6193 * |
|
6194 * @param {float} angle the angle of rotation in radiants |
|
6195 */ |
|
6196 rotateZ: function(angle) { |
|
6197 var c = Math.cos(angle); |
|
6198 var s = Math.sin(angle); |
|
6199 this.apply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); |
|
6200 }, |
|
6201 /** |
|
6202 * @member PMatrix3D |
|
6203 * The scale() function increases or decreases the size of a matrix by expanding and contracting vertices. When only one parameter is specified scale will occur in all dimensions. |
|
6204 * This is equivalent to a three parameter call. |
|
6205 * |
|
6206 * @param {float} sx the amount to scale on the x-axis |
|
6207 * @param {float} sy the amount to scale on the y-axis |
|
6208 * @param {float} sz the amount to scale on the z-axis |
|
6209 */ |
|
6210 scale: function(sx, sy, sz) { |
|
6211 if (sx && !sy && !sz) { |
|
6212 sy = sz = sx; |
|
6213 } else if (sx && sy && !sz) { |
|
6214 sz = 1; |
|
6215 } |
|
6216 |
|
6217 if (sx && sy && sz) { |
|
6218 this.elements[0] *= sx; |
|
6219 this.elements[1] *= sy; |
|
6220 this.elements[2] *= sz; |
|
6221 this.elements[4] *= sx; |
|
6222 this.elements[5] *= sy; |
|
6223 this.elements[6] *= sz; |
|
6224 this.elements[8] *= sx; |
|
6225 this.elements[9] *= sy; |
|
6226 this.elements[10] *= sz; |
|
6227 this.elements[12] *= sx; |
|
6228 this.elements[13] *= sy; |
|
6229 this.elements[14] *= sz; |
|
6230 } |
|
6231 }, |
|
6232 /** |
|
6233 * @member PMatrix3D |
|
6234 * The skewX() function skews the matrix along the x-axis the amount specified by the angle parameter. |
|
6235 * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. |
|
6236 * |
|
6237 * @param {float} angle angle of skew specified in radians |
|
6238 */ |
|
6239 skewX: function(angle) { |
|
6240 var t = Math.tan(angle); |
|
6241 this.apply(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); |
|
6242 }, |
|
6243 /** |
|
6244 * @member PMatrix3D |
|
6245 * The skewY() function skews the matrix along the y-axis the amount specified by the angle parameter. |
|
6246 * Angles should be specified in radians (values from 0 to PI*2) or converted to radians with the <b>radians()</b> function. |
|
6247 * |
|
6248 * @param {float} angle angle of skew specified in radians |
|
6249 */ |
|
6250 skewY: function(angle) { |
|
6251 var t = Math.tan(angle); |
|
6252 this.apply(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); |
|
6253 }, |
|
6254 multX: function(x, y, z, w) { |
|
6255 if (!z) { |
|
6256 return this.elements[0] * x + this.elements[1] * y + this.elements[3]; |
|
6257 } |
|
6258 if (!w) { |
|
6259 return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3]; |
|
6260 } |
|
6261 return this.elements[0] * x + this.elements[1] * y + this.elements[2] * z + this.elements[3] * w; |
|
6262 }, |
|
6263 multY: function(x, y, z, w) { |
|
6264 if (!z) { |
|
6265 return this.elements[4] * x + this.elements[5] * y + this.elements[7]; |
|
6266 } |
|
6267 if (!w) { |
|
6268 return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7]; |
|
6269 } |
|
6270 return this.elements[4] * x + this.elements[5] * y + this.elements[6] * z + this.elements[7] * w; |
|
6271 }, |
|
6272 multZ: function(x, y, z, w) { |
|
6273 if (!w) { |
|
6274 return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11]; |
|
6275 } |
|
6276 return this.elements[8] * x + this.elements[9] * y + this.elements[10] * z + this.elements[11] * w; |
|
6277 }, |
|
6278 multW: function(x, y, z, w) { |
|
6279 if (!w) { |
|
6280 return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15]; |
|
6281 } |
|
6282 return this.elements[12] * x + this.elements[13] * y + this.elements[14] * z + this.elements[15] * w; |
|
6283 }, |
|
6284 /** |
|
6285 * @member PMatrix3D |
|
6286 * The invert() function inverts this matrix |
|
6287 * |
|
6288 * @return {boolean} true if successful |
|
6289 */ |
|
6290 invert: function() { |
|
6291 var fA0 = this.elements[0] * this.elements[5] - this.elements[1] * this.elements[4]; |
|
6292 var fA1 = this.elements[0] * this.elements[6] - this.elements[2] * this.elements[4]; |
|
6293 var fA2 = this.elements[0] * this.elements[7] - this.elements[3] * this.elements[4]; |
|
6294 var fA3 = this.elements[1] * this.elements[6] - this.elements[2] * this.elements[5]; |
|
6295 var fA4 = this.elements[1] * this.elements[7] - this.elements[3] * this.elements[5]; |
|
6296 var fA5 = this.elements[2] * this.elements[7] - this.elements[3] * this.elements[6]; |
|
6297 var fB0 = this.elements[8] * this.elements[13] - this.elements[9] * this.elements[12]; |
|
6298 var fB1 = this.elements[8] * this.elements[14] - this.elements[10] * this.elements[12]; |
|
6299 var fB2 = this.elements[8] * this.elements[15] - this.elements[11] * this.elements[12]; |
|
6300 var fB3 = this.elements[9] * this.elements[14] - this.elements[10] * this.elements[13]; |
|
6301 var fB4 = this.elements[9] * this.elements[15] - this.elements[11] * this.elements[13]; |
|
6302 var fB5 = this.elements[10] * this.elements[15] - this.elements[11] * this.elements[14]; |
|
6303 |
|
6304 // Determinant |
|
6305 var fDet = fA0 * fB5 - fA1 * fB4 + fA2 * fB3 + fA3 * fB2 - fA4 * fB1 + fA5 * fB0; |
|
6306 |
|
6307 // Account for a very small value |
|
6308 // return false if not successful. |
|
6309 if (Math.abs(fDet) <= 1e-9) { |
|
6310 return false; |
|
6311 } |
|
6312 |
|
6313 var kInv = []; |
|
6314 kInv[0] = +this.elements[5] * fB5 - this.elements[6] * fB4 + this.elements[7] * fB3; |
|
6315 kInv[4] = -this.elements[4] * fB5 + this.elements[6] * fB2 - this.elements[7] * fB1; |
|
6316 kInv[8] = +this.elements[4] * fB4 - this.elements[5] * fB2 + this.elements[7] * fB0; |
|
6317 kInv[12] = -this.elements[4] * fB3 + this.elements[5] * fB1 - this.elements[6] * fB0; |
|
6318 kInv[1] = -this.elements[1] * fB5 + this.elements[2] * fB4 - this.elements[3] * fB3; |
|
6319 kInv[5] = +this.elements[0] * fB5 - this.elements[2] * fB2 + this.elements[3] * fB1; |
|
6320 kInv[9] = -this.elements[0] * fB4 + this.elements[1] * fB2 - this.elements[3] * fB0; |
|
6321 kInv[13] = +this.elements[0] * fB3 - this.elements[1] * fB1 + this.elements[2] * fB0; |
|
6322 kInv[2] = +this.elements[13] * fA5 - this.elements[14] * fA4 + this.elements[15] * fA3; |
|
6323 kInv[6] = -this.elements[12] * fA5 + this.elements[14] * fA2 - this.elements[15] * fA1; |
|
6324 kInv[10] = +this.elements[12] * fA4 - this.elements[13] * fA2 + this.elements[15] * fA0; |
|
6325 kInv[14] = -this.elements[12] * fA3 + this.elements[13] * fA1 - this.elements[14] * fA0; |
|
6326 kInv[3] = -this.elements[9] * fA5 + this.elements[10] * fA4 - this.elements[11] * fA3; |
|
6327 kInv[7] = +this.elements[8] * fA5 - this.elements[10] * fA2 + this.elements[11] * fA1; |
|
6328 kInv[11] = -this.elements[8] * fA4 + this.elements[9] * fA2 - this.elements[11] * fA0; |
|
6329 kInv[15] = +this.elements[8] * fA3 - this.elements[9] * fA1 + this.elements[10] * fA0; |
|
6330 |
|
6331 // Inverse using Determinant |
|
6332 var fInvDet = 1.0 / fDet; |
|
6333 kInv[0] *= fInvDet; |
|
6334 kInv[1] *= fInvDet; |
|
6335 kInv[2] *= fInvDet; |
|
6336 kInv[3] *= fInvDet; |
|
6337 kInv[4] *= fInvDet; |
|
6338 kInv[5] *= fInvDet; |
|
6339 kInv[6] *= fInvDet; |
|
6340 kInv[7] *= fInvDet; |
|
6341 kInv[8] *= fInvDet; |
|
6342 kInv[9] *= fInvDet; |
|
6343 kInv[10] *= fInvDet; |
|
6344 kInv[11] *= fInvDet; |
|
6345 kInv[12] *= fInvDet; |
|
6346 kInv[13] *= fInvDet; |
|
6347 kInv[14] *= fInvDet; |
|
6348 kInv[15] *= fInvDet; |
|
6349 |
|
6350 this.elements = kInv.slice(); |
|
6351 return true; |
|
6352 }, |
|
6353 toString: function() { |
|
6354 var str = ""; |
|
6355 for (var i = 0; i < 15; i++) { |
|
6356 str += this.elements[i] + ", "; |
|
6357 } |
|
6358 str += this.elements[15]; |
|
6359 return str; |
|
6360 }, |
|
6361 /** |
|
6362 * @member PMatrix3D |
|
6363 * The print() function prints out the elements of this matrix |
|
6364 */ |
|
6365 print: function() { |
|
6366 var digits = printMatrixHelper(this.elements); |
|
6367 |
|
6368 var output = "" + p.nfs(this.elements[0], digits, 4) + " " + p.nfs(this.elements[1], digits, 4) + |
|
6369 " " + p.nfs(this.elements[2], digits, 4) + " " + p.nfs(this.elements[3], digits, 4) + |
|
6370 "\n" + p.nfs(this.elements[4], digits, 4) + " " + p.nfs(this.elements[5], digits, 4) + |
|
6371 " " + p.nfs(this.elements[6], digits, 4) + " " + p.nfs(this.elements[7], digits, 4) + |
|
6372 "\n" + p.nfs(this.elements[8], digits, 4) + " " + p.nfs(this.elements[9], digits, 4) + |
|
6373 " " + p.nfs(this.elements[10], digits, 4) + " " + p.nfs(this.elements[11], digits, 4) + |
|
6374 "\n" + p.nfs(this.elements[12], digits, 4) + " " + p.nfs(this.elements[13], digits, 4) + |
|
6375 " " + p.nfs(this.elements[14], digits, 4) + " " + p.nfs(this.elements[15], digits, 4) + "\n\n"; |
|
6376 p.println(output); |
|
6377 }, |
|
6378 invTranslate: function(tx, ty, tz) { |
|
6379 this.preApply(1, 0, 0, -tx, 0, 1, 0, -ty, 0, 0, 1, -tz, 0, 0, 0, 1); |
|
6380 }, |
|
6381 invRotateX: function(angle) { |
|
6382 var c = Math.cos(-angle); |
|
6383 var s = Math.sin(-angle); |
|
6384 this.preApply([1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1]); |
|
6385 }, |
|
6386 invRotateY: function(angle) { |
|
6387 var c = Math.cos(-angle); |
|
6388 var s = Math.sin(-angle); |
|
6389 this.preApply([c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1]); |
|
6390 }, |
|
6391 invRotateZ: function(angle) { |
|
6392 var c = Math.cos(-angle); |
|
6393 var s = Math.sin(-angle); |
|
6394 this.preApply([c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); |
|
6395 }, |
|
6396 invScale: function(x, y, z) { |
|
6397 this.preApply([1 / x, 0, 0, 0, 0, 1 / y, 0, 0, 0, 0, 1 / z, 0, 0, 0, 0, 1]); |
|
6398 } |
|
6399 }; |
|
6400 |
|
6401 /** |
|
6402 * @private |
|
6403 * The matrix stack stores the transformations and translations that occur within the space. |
|
6404 */ |
|
6405 var PMatrixStack = p.PMatrixStack = function() { |
|
6406 this.matrixStack = []; |
|
6407 }; |
|
6408 |
|
6409 /** |
|
6410 * @member PMatrixStack |
|
6411 * load pushes the matrix given in the function into the stack |
|
6412 * |
|
6413 * @param {Object | Array} matrix the matrix to be pushed into the stack |
|
6414 */ |
|
6415 PMatrixStack.prototype.load = function() { |
|
6416 var tmpMatrix = drawing.$newPMatrix(); |
|
6417 |
|
6418 if (arguments.length === 1) { |
|
6419 tmpMatrix.set(arguments[0]); |
|
6420 } else { |
|
6421 tmpMatrix.set(arguments); |
|
6422 } |
|
6423 this.matrixStack.push(tmpMatrix); |
|
6424 }; |
|
6425 |
|
6426 Drawing2D.prototype.$newPMatrix = function() { |
|
6427 return new PMatrix2D(); |
|
6428 }; |
|
6429 |
|
6430 Drawing3D.prototype.$newPMatrix = function() { |
|
6431 return new PMatrix3D(); |
|
6432 }; |
|
6433 |
|
6434 /** |
|
6435 * @member PMatrixStack |
|
6436 * push adds a duplicate of the top of the stack onto the stack - uses the peek function |
|
6437 */ |
|
6438 PMatrixStack.prototype.push = function() { |
|
6439 this.matrixStack.push(this.peek()); |
|
6440 }; |
|
6441 |
|
6442 /** |
|
6443 * @member PMatrixStack |
|
6444 * pop removes returns the matrix at the top of the stack |
|
6445 * |
|
6446 * @returns {Object} the matrix at the top of the stack |
|
6447 */ |
|
6448 PMatrixStack.prototype.pop = function() { |
|
6449 return this.matrixStack.pop(); |
|
6450 }; |
|
6451 |
|
6452 /** |
|
6453 * @member PMatrixStack |
|
6454 * peek returns but doesn't remove the matrix at the top of the stack |
|
6455 * |
|
6456 * @returns {Object} the matrix at the top of the stack |
|
6457 */ |
|
6458 PMatrixStack.prototype.peek = function() { |
|
6459 var tmpMatrix = drawing.$newPMatrix(); |
|
6460 |
|
6461 tmpMatrix.set(this.matrixStack[this.matrixStack.length - 1]); |
|
6462 return tmpMatrix; |
|
6463 }; |
|
6464 |
|
6465 /** |
|
6466 * @member PMatrixStack |
|
6467 * this function multiplies the matrix at the top of the stack with the matrix given as a parameter |
|
6468 * |
|
6469 * @param {Object | Array} matrix the matrix to be multiplied into the stack |
|
6470 */ |
|
6471 PMatrixStack.prototype.mult = function(matrix) { |
|
6472 this.matrixStack[this.matrixStack.length - 1].apply(matrix); |
|
6473 }; |
|
6474 |
|
6475 //////////////////////////////////////////////////////////////////////////// |
|
6476 // Array handling |
|
6477 //////////////////////////////////////////////////////////////////////////// |
|
6478 |
|
6479 /** |
|
6480 * The split() function breaks a string into pieces using a character or string |
|
6481 * as the divider. The delim parameter specifies the character or characters that |
|
6482 * mark the boundaries between each piece. A String[] array is returned that contains |
|
6483 * each of the pieces. |
|
6484 * If the result is a set of numbers, you can convert the String[] array to to a float[] |
|
6485 * or int[] array using the datatype conversion functions int() and float() (see example above). |
|
6486 * The splitTokens() function works in a similar fashion, except that it splits using a range |
|
6487 * of characters instead of a specific character or sequence. |
|
6488 * |
|
6489 * @param {String} str the String to be split |
|
6490 * @param {String} delim the character or String used to separate the data |
|
6491 * |
|
6492 * @returns {string[]} The new string array |
|
6493 * |
|
6494 * @see splitTokens |
|
6495 * @see join |
|
6496 * @see trim |
|
6497 */ |
|
6498 p.split = function(str, delim) { |
|
6499 return str.split(delim); |
|
6500 }; |
|
6501 |
|
6502 /** |
|
6503 * The splitTokens() function splits a String at one or many character "tokens." The tokens |
|
6504 * parameter specifies the character or characters to be used as a boundary. |
|
6505 * If no tokens character is specified, any whitespace character is used to split. |
|
6506 * Whitespace characters include tab (\t), line feed (\n), carriage return (\r), form |
|
6507 * feed (\f), and space. To convert a String to an array of integers or floats, use the |
|
6508 * datatype conversion functions int() and float() to convert the array of Strings. |
|
6509 * |
|
6510 * @param {String} str the String to be split |
|
6511 * @param {Char[]} tokens list of individual characters that will be used as separators |
|
6512 * |
|
6513 * @returns {string[]} The new string array |
|
6514 * |
|
6515 * @see split |
|
6516 * @see join |
|
6517 * @see trim |
|
6518 */ |
|
6519 p.splitTokens = function(str, tokens) { |
|
6520 if (arguments.length === 1) { |
|
6521 tokens = "\n\t\r\f "; |
|
6522 } |
|
6523 |
|
6524 tokens = "[" + tokens + "]"; |
|
6525 |
|
6526 var ary = []; |
|
6527 var index = 0; |
|
6528 var pos = str.search(tokens); |
|
6529 |
|
6530 while (pos >= 0) { |
|
6531 if (pos === 0) { |
|
6532 str = str.substring(1); |
|
6533 } else { |
|
6534 ary[index] = str.substring(0, pos); |
|
6535 index++; |
|
6536 str = str.substring(pos); |
|
6537 } |
|
6538 pos = str.search(tokens); |
|
6539 } |
|
6540 |
|
6541 if (str.length > 0) { |
|
6542 ary[index] = str; |
|
6543 } |
|
6544 |
|
6545 if (ary.length === 0) { |
|
6546 ary = undef; |
|
6547 } |
|
6548 |
|
6549 return ary; |
|
6550 }; |
|
6551 |
|
6552 /** |
|
6553 * Expands an array by one element and adds data to the new position. The datatype of |
|
6554 * the element parameter must be the same as the datatype of the array. |
|
6555 * When using an array of objects, the data returned from the function must be cast to |
|
6556 * the object array's data type. For example: SomeClass[] items = (SomeClass[]) |
|
6557 * append(originalArray, element). |
|
6558 * |
|
6559 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], |
|
6560 * byte[], char[], int[], float[], or String[], or an array of objects |
|
6561 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} element new data for the array |
|
6562 * |
|
6563 * @returns Array (the same datatype as the input) |
|
6564 * |
|
6565 * @see shorten |
|
6566 * @see expand |
|
6567 */ |
|
6568 p.append = function(array, element) { |
|
6569 array[array.length] = element; |
|
6570 return array; |
|
6571 }; |
|
6572 |
|
6573 /** |
|
6574 * Concatenates two arrays. For example, concatenating the array { 1, 2, 3 } and the |
|
6575 * array { 4, 5, 6 } yields { 1, 2, 3, 4, 5, 6 }. Both parameters must be arrays of the |
|
6576 * same datatype. |
|
6577 * When using an array of objects, the data returned from the function must be cast to the |
|
6578 * object array's data type. For example: SomeClass[] items = (SomeClass[]) concat(array1, array2). |
|
6579 * |
|
6580 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array1 boolean[], |
|
6581 * byte[], char[], int[], float[], String[], or an array of objects |
|
6582 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array2 boolean[], |
|
6583 * byte[], char[], int[], float[], String[], or an array of objects |
|
6584 * |
|
6585 * @returns Array (the same datatype as the input) |
|
6586 * |
|
6587 * @see splice |
|
6588 */ |
|
6589 p.concat = function(array1, array2) { |
|
6590 return array1.concat(array2); |
|
6591 }; |
|
6592 |
|
6593 /** |
|
6594 * Sorts an array of numbers from smallest to largest and puts an array of |
|
6595 * words in alphabetical order. The original array is not modified, a |
|
6596 * re-ordered array is returned. The count parameter states the number of |
|
6597 * elements to sort. For example if there are 12 elements in an array and |
|
6598 * if count is the value 5, only the first five elements on the array will |
|
6599 * be sorted. Alphabetical ordering is case insensitive. |
|
6600 * |
|
6601 * @param {String[] | int[] | float[]} array Array of elements to sort |
|
6602 * @param {int} numElem Number of elements to sort |
|
6603 * |
|
6604 * @returns {String[] | int[] | float[]} Array (same datatype as the input) |
|
6605 * |
|
6606 * @see reverse |
|
6607 */ |
|
6608 p.sort = function(array, numElem) { |
|
6609 var ret = []; |
|
6610 |
|
6611 // depending on the type used (int, float) or string |
|
6612 // we'll need to use a different compare function |
|
6613 if (array.length > 0) { |
|
6614 // copy since we need to return another array |
|
6615 var elemsToCopy = numElem > 0 ? numElem : array.length; |
|
6616 for (var i = 0; i < elemsToCopy; i++) { |
|
6617 ret.push(array[i]); |
|
6618 } |
|
6619 if (typeof array[0] === "string") { |
|
6620 ret.sort(); |
|
6621 } |
|
6622 // int or float |
|
6623 else { |
|
6624 ret.sort(function(a, b) { |
|
6625 return a - b; |
|
6626 }); |
|
6627 } |
|
6628 |
|
6629 // copy on the rest of the elements that were not sorted in case the user |
|
6630 // only wanted a subset of an array to be sorted. |
|
6631 if (numElem > 0) { |
|
6632 for (var j = ret.length; j < array.length; j++) { |
|
6633 ret.push(array[j]); |
|
6634 } |
|
6635 } |
|
6636 } |
|
6637 return ret; |
|
6638 }; |
|
6639 |
|
6640 /** |
|
6641 * Inserts a value or array of values into an existing array. The first two parameters must |
|
6642 * be of the same datatype. The array parameter defines the array which will be modified |
|
6643 * and the second parameter defines the data which will be inserted. When using an array |
|
6644 * of objects, the data returned from the function must be cast to the object array's data |
|
6645 * type. For example: SomeClass[] items = (SomeClass[]) splice(array1, array2, index). |
|
6646 * |
|
6647 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], |
|
6648 * byte[], char[], int[], float[], String[], or an array of objects |
|
6649 * @param {boolean|byte|char|int|float|String|boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} |
|
6650 * value boolean, byte, char, int, float, String, boolean[], byte[], char[], int[], |
|
6651 * float[], String[], or other Object: value or an array of objects to be spliced in |
|
6652 * @param {int} index position in the array from which to insert data |
|
6653 * |
|
6654 * @returns Array (the same datatype as the input) |
|
6655 * |
|
6656 * @see contract |
|
6657 * @see subset |
|
6658 */ |
|
6659 p.splice = function(array, value, index) { |
|
6660 |
|
6661 // Trying to splice an empty array into "array" in P5 won't do |
|
6662 // anything, just return the original. |
|
6663 if(value.length === 0) |
|
6664 { |
|
6665 return array; |
|
6666 } |
|
6667 |
|
6668 // If the second argument was an array, we'll need to iterate over all |
|
6669 // the "value" elements and add one by one because |
|
6670 // array.splice(index, 0, value); |
|
6671 // would create a multi-dimensional array which isn't what we want. |
|
6672 if(value instanceof Array) { |
|
6673 for(var i = 0, j = index; i < value.length; j++,i++) { |
|
6674 array.splice(j, 0, value[i]); |
|
6675 } |
|
6676 } else { |
|
6677 array.splice(index, 0, value); |
|
6678 } |
|
6679 |
|
6680 return array; |
|
6681 }; |
|
6682 |
|
6683 /** |
|
6684 * Extracts an array of elements from an existing array. The array parameter defines the |
|
6685 * array from which the elements will be copied and the offset and length parameters determine |
|
6686 * which elements to extract. If no length is given, elements will be extracted from the offset |
|
6687 * to the end of the array. When specifying the offset remember the first array element is 0. |
|
6688 * This function does not change the source array. |
|
6689 * When using an array of objects, the data returned from the function must be cast to the |
|
6690 * object array's data type. |
|
6691 * |
|
6692 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array boolean[], |
|
6693 * byte[], char[], int[], float[], String[], or an array of objects |
|
6694 * @param {int} offset position to begin |
|
6695 * @param {int} length number of values to extract |
|
6696 * |
|
6697 * @returns Array (the same datatype as the input) |
|
6698 * |
|
6699 * @see splice |
|
6700 */ |
|
6701 p.subset = function(array, offset, length) { |
|
6702 var end = (length !== undef) ? offset + length : array.length; |
|
6703 return array.slice(offset, end); |
|
6704 }; |
|
6705 |
|
6706 /** |
|
6707 * Combines an array of Strings into one String, each separated by the character(s) used for |
|
6708 * the separator parameter. To join arrays of ints or floats, it's necessary to first convert |
|
6709 * them to strings using nf() or nfs(). |
|
6710 * |
|
6711 * @param {Array} array array of Strings |
|
6712 * @param {char|String} separator char or String to be placed between each item |
|
6713 * |
|
6714 * @returns {String} The combined string |
|
6715 * |
|
6716 * @see split |
|
6717 * @see trim |
|
6718 * @see nf |
|
6719 * @see nfs |
|
6720 */ |
|
6721 p.join = function(array, seperator) { |
|
6722 return array.join(seperator); |
|
6723 }; |
|
6724 |
|
6725 /** |
|
6726 * Decreases an array by one element and returns the shortened array. When using an |
|
6727 * array of objects, the data returned from the function must be cast to the object array's |
|
6728 * data type. For example: SomeClass[] items = (SomeClass[]) shorten(originalArray). |
|
6729 * |
|
6730 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} array |
|
6731 * boolean[], byte[], char[], int[], float[], or String[], or an array of objects |
|
6732 * |
|
6733 * @returns Array (the same datatype as the input) |
|
6734 * |
|
6735 * @see append |
|
6736 * @see expand |
|
6737 */ |
|
6738 p.shorten = function(ary) { |
|
6739 var newary = []; |
|
6740 |
|
6741 // copy array into new array |
|
6742 var len = ary.length; |
|
6743 for (var i = 0; i < len; i++) { |
|
6744 newary[i] = ary[i]; |
|
6745 } |
|
6746 newary.pop(); |
|
6747 |
|
6748 return newary; |
|
6749 }; |
|
6750 |
|
6751 /** |
|
6752 * Increases the size of an array. By default, this function doubles the size of the array, |
|
6753 * but the optional newSize parameter provides precise control over the increase in size. |
|
6754 * When using an array of objects, the data returned from the function must be cast to the |
|
6755 * object array's data type. For example: SomeClass[] items = (SomeClass[]) expand(originalArray). |
|
6756 * |
|
6757 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]|array of objects} ary |
|
6758 * boolean[], byte[], char[], int[], float[], String[], or an array of objects |
|
6759 * @param {int} newSize positive int: new size for the array |
|
6760 * |
|
6761 * @returns Array (the same datatype as the input) |
|
6762 * |
|
6763 * @see contract |
|
6764 */ |
|
6765 p.expand = function(ary, targetSize) { |
|
6766 var temp = ary.slice(0), |
|
6767 newSize = targetSize || ary.length * 2; |
|
6768 temp.length = newSize; |
|
6769 return temp; |
|
6770 }; |
|
6771 |
|
6772 /** |
|
6773 * Copies an array (or part of an array) to another array. The src array is copied to the |
|
6774 * dst array, beginning at the position specified by srcPos and into the position specified |
|
6775 * by dstPos. The number of elements to copy is determined by length. The simplified version |
|
6776 * with two arguments copies an entire array to another of the same size. It is equivalent |
|
6777 * to "arrayCopy(src, 0, dst, 0, src.length)". This function is far more efficient for copying |
|
6778 * array data than iterating through a for and copying each element. |
|
6779 * |
|
6780 * @param {Array} src an array of any data type: the source array |
|
6781 * @param {Array} dest an array of any data type (as long as it's the same as src): the destination array |
|
6782 * @param {int} srcPos starting position in the source array |
|
6783 * @param {int} destPos starting position in the destination array |
|
6784 * @param {int} length number of array elements to be copied |
|
6785 * |
|
6786 * @returns none |
|
6787 */ |
|
6788 p.arrayCopy = function() { // src, srcPos, dest, destPos, length) { |
|
6789 var src, srcPos = 0, dest, destPos = 0, length; |
|
6790 |
|
6791 if (arguments.length === 2) { |
|
6792 // recall itself and copy src to dest from start index 0 to 0 of src.length |
|
6793 src = arguments[0]; |
|
6794 dest = arguments[1]; |
|
6795 length = src.length; |
|
6796 } else if (arguments.length === 3) { |
|
6797 // recall itself and copy src to dest from start index 0 to 0 of length |
|
6798 src = arguments[0]; |
|
6799 dest = arguments[1]; |
|
6800 length = arguments[2]; |
|
6801 } else if (arguments.length === 5) { |
|
6802 src = arguments[0]; |
|
6803 srcPos = arguments[1]; |
|
6804 dest = arguments[2]; |
|
6805 destPos = arguments[3]; |
|
6806 length = arguments[4]; |
|
6807 } |
|
6808 |
|
6809 // copy src to dest from index srcPos to index destPos of length recursivly on objects |
|
6810 for (var i = srcPos, j = destPos; i < length + srcPos; i++, j++) { |
|
6811 if (dest[j] !== undef) { |
|
6812 dest[j] = src[i]; |
|
6813 } else { |
|
6814 throw "array index out of bounds exception"; |
|
6815 } |
|
6816 } |
|
6817 }; |
|
6818 |
|
6819 /** |
|
6820 * Reverses the order of an array. |
|
6821 * |
|
6822 * @param {boolean[]|byte[]|char[]|int[]|float[]|String[]} array |
|
6823 * boolean[], byte[], char[], int[], float[], or String[] |
|
6824 * |
|
6825 * @returns Array (the same datatype as the input) |
|
6826 * |
|
6827 * @see sort |
|
6828 */ |
|
6829 p.reverse = function(array) { |
|
6830 return array.reverse(); |
|
6831 }; |
|
6832 |
|
6833 |
|
6834 //////////////////////////////////////////////////////////////////////////// |
|
6835 // Color functions |
|
6836 //////////////////////////////////////////////////////////////////////////// |
|
6837 |
|
6838 // helper functions for internal blending modes |
|
6839 p.mix = function(a, b, f) { |
|
6840 return a + (((b - a) * f) >> 8); |
|
6841 }; |
|
6842 |
|
6843 p.peg = function(n) { |
|
6844 return (n < 0) ? 0 : ((n > 255) ? 255 : n); |
|
6845 }; |
|
6846 |
|
6847 // blending modes |
|
6848 /** |
|
6849 * These are internal blending modes used for BlendColor() |
|
6850 * |
|
6851 * @param {Color} c1 First Color to blend |
|
6852 * @param {Color} c2 Second Color to blend |
|
6853 * |
|
6854 * @returns {Color} The blended Color |
|
6855 * |
|
6856 * @see BlendColor |
|
6857 * @see Blend |
|
6858 */ |
|
6859 p.modes = (function() { |
|
6860 var ALPHA_MASK = PConstants.ALPHA_MASK, |
|
6861 RED_MASK = PConstants.RED_MASK, |
|
6862 GREEN_MASK = PConstants.GREEN_MASK, |
|
6863 BLUE_MASK = PConstants.BLUE_MASK, |
|
6864 min = Math.min, |
|
6865 max = Math.max; |
|
6866 |
|
6867 function applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb) { |
|
6868 var a = min(((c1 & 0xff000000) >>> 24) + f, 0xff) << 24; |
|
6869 |
|
6870 var r = (ar + (((cr - ar) * f) >> 8)); |
|
6871 r = ((r < 0) ? 0 : ((r > 255) ? 255 : r)) << 16; |
|
6872 |
|
6873 var g = (ag + (((cg - ag) * f) >> 8)); |
|
6874 g = ((g < 0) ? 0 : ((g > 255) ? 255 : g)) << 8; |
|
6875 |
|
6876 var b = ab + (((cb - ab) * f) >> 8); |
|
6877 b = (b < 0) ? 0 : ((b > 255) ? 255 : b); |
|
6878 |
|
6879 return (a | r | g | b); |
|
6880 } |
|
6881 |
|
6882 return { |
|
6883 replace: function(c1, c2) { |
|
6884 return c2; |
|
6885 }, |
|
6886 blend: function(c1, c2) { |
|
6887 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6888 ar = (c1 & RED_MASK), |
|
6889 ag = (c1 & GREEN_MASK), |
|
6890 ab = (c1 & BLUE_MASK), |
|
6891 br = (c2 & RED_MASK), |
|
6892 bg = (c2 & GREEN_MASK), |
|
6893 bb = (c2 & BLUE_MASK); |
|
6894 |
|
6895 return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | |
|
6896 (ar + (((br - ar) * f) >> 8)) & RED_MASK | |
|
6897 (ag + (((bg - ag) * f) >> 8)) & GREEN_MASK | |
|
6898 (ab + (((bb - ab) * f) >> 8)) & BLUE_MASK); |
|
6899 }, |
|
6900 add: function(c1, c2) { |
|
6901 var f = (c2 & ALPHA_MASK) >>> 24; |
|
6902 return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | |
|
6903 min(((c1 & RED_MASK) + ((c2 & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | |
|
6904 min(((c1 & GREEN_MASK) + ((c2 & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | |
|
6905 min((c1 & BLUE_MASK) + (((c2 & BLUE_MASK) * f) >> 8), BLUE_MASK)); |
|
6906 }, |
|
6907 subtract: function(c1, c2) { |
|
6908 var f = (c2 & ALPHA_MASK) >>> 24; |
|
6909 return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | |
|
6910 max(((c1 & RED_MASK) - ((c2 & RED_MASK) >> 8) * f), GREEN_MASK) & RED_MASK | |
|
6911 max(((c1 & GREEN_MASK) - ((c2 & GREEN_MASK) >> 8) * f), BLUE_MASK) & GREEN_MASK | |
|
6912 max((c1 & BLUE_MASK) - (((c2 & BLUE_MASK) * f) >> 8), 0)); |
|
6913 }, |
|
6914 lightest: function(c1, c2) { |
|
6915 var f = (c2 & ALPHA_MASK) >>> 24; |
|
6916 return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | |
|
6917 max(c1 & RED_MASK, ((c2 & RED_MASK) >> 8) * f) & RED_MASK | |
|
6918 max(c1 & GREEN_MASK, ((c2 & GREEN_MASK) >> 8) * f) & GREEN_MASK | |
|
6919 max(c1 & BLUE_MASK, ((c2 & BLUE_MASK) * f) >> 8)); |
|
6920 }, |
|
6921 darkest: function(c1, c2) { |
|
6922 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6923 ar = (c1 & RED_MASK), |
|
6924 ag = (c1 & GREEN_MASK), |
|
6925 ab = (c1 & BLUE_MASK), |
|
6926 br = min(c1 & RED_MASK, ((c2 & RED_MASK) >> 8) * f), |
|
6927 bg = min(c1 & GREEN_MASK, ((c2 & GREEN_MASK) >> 8) * f), |
|
6928 bb = min(c1 & BLUE_MASK, ((c2 & BLUE_MASK) * f) >> 8); |
|
6929 |
|
6930 return (min(((c1 & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | |
|
6931 (ar + (((br - ar) * f) >> 8)) & RED_MASK | |
|
6932 (ag + (((bg - ag) * f) >> 8)) & GREEN_MASK | |
|
6933 (ab + (((bb - ab) * f) >> 8)) & BLUE_MASK); |
|
6934 }, |
|
6935 difference: function(c1, c2) { |
|
6936 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6937 ar = (c1 & RED_MASK) >> 16, |
|
6938 ag = (c1 & GREEN_MASK) >> 8, |
|
6939 ab = (c1 & BLUE_MASK), |
|
6940 br = (c2 & RED_MASK) >> 16, |
|
6941 bg = (c2 & GREEN_MASK) >> 8, |
|
6942 bb = (c2 & BLUE_MASK), |
|
6943 cr = (ar > br) ? (ar - br) : (br - ar), |
|
6944 cg = (ag > bg) ? (ag - bg) : (bg - ag), |
|
6945 cb = (ab > bb) ? (ab - bb) : (bb - ab); |
|
6946 |
|
6947 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
6948 }, |
|
6949 exclusion: function(c1, c2) { |
|
6950 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6951 ar = (c1 & RED_MASK) >> 16, |
|
6952 ag = (c1 & GREEN_MASK) >> 8, |
|
6953 ab = (c1 & BLUE_MASK), |
|
6954 br = (c2 & RED_MASK) >> 16, |
|
6955 bg = (c2 & GREEN_MASK) >> 8, |
|
6956 bb = (c2 & BLUE_MASK), |
|
6957 cr = ar + br - ((ar * br) >> 7), |
|
6958 cg = ag + bg - ((ag * bg) >> 7), |
|
6959 cb = ab + bb - ((ab * bb) >> 7); |
|
6960 |
|
6961 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
6962 }, |
|
6963 multiply: function(c1, c2) { |
|
6964 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6965 ar = (c1 & RED_MASK) >> 16, |
|
6966 ag = (c1 & GREEN_MASK) >> 8, |
|
6967 ab = (c1 & BLUE_MASK), |
|
6968 br = (c2 & RED_MASK) >> 16, |
|
6969 bg = (c2 & GREEN_MASK) >> 8, |
|
6970 bb = (c2 & BLUE_MASK), |
|
6971 cr = (ar * br) >> 8, |
|
6972 cg = (ag * bg) >> 8, |
|
6973 cb = (ab * bb) >> 8; |
|
6974 |
|
6975 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
6976 }, |
|
6977 screen: function(c1, c2) { |
|
6978 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6979 ar = (c1 & RED_MASK) >> 16, |
|
6980 ag = (c1 & GREEN_MASK) >> 8, |
|
6981 ab = (c1 & BLUE_MASK), |
|
6982 br = (c2 & RED_MASK) >> 16, |
|
6983 bg = (c2 & GREEN_MASK) >> 8, |
|
6984 bb = (c2 & BLUE_MASK), |
|
6985 cr = 255 - (((255 - ar) * (255 - br)) >> 8), |
|
6986 cg = 255 - (((255 - ag) * (255 - bg)) >> 8), |
|
6987 cb = 255 - (((255 - ab) * (255 - bb)) >> 8); |
|
6988 |
|
6989 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
6990 }, |
|
6991 hard_light: function(c1, c2) { |
|
6992 var f = (c2 & ALPHA_MASK) >>> 24, |
|
6993 ar = (c1 & RED_MASK) >> 16, |
|
6994 ag = (c1 & GREEN_MASK) >> 8, |
|
6995 ab = (c1 & BLUE_MASK), |
|
6996 br = (c2 & RED_MASK) >> 16, |
|
6997 bg = (c2 & GREEN_MASK) >> 8, |
|
6998 bb = (c2 & BLUE_MASK), |
|
6999 cr = (br < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)), |
|
7000 cg = (bg < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)), |
|
7001 cb = (bb < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7)); |
|
7002 |
|
7003 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
7004 }, |
|
7005 soft_light: function(c1, c2) { |
|
7006 var f = (c2 & ALPHA_MASK) >>> 24, |
|
7007 ar = (c1 & RED_MASK) >> 16, |
|
7008 ag = (c1 & GREEN_MASK) >> 8, |
|
7009 ab = (c1 & BLUE_MASK), |
|
7010 br = (c2 & RED_MASK) >> 16, |
|
7011 bg = (c2 & GREEN_MASK) >> 8, |
|
7012 bb = (c2 & BLUE_MASK), |
|
7013 cr = ((ar * br) >> 7) + ((ar * ar) >> 8) - ((ar * ar * br) >> 15), |
|
7014 cg = ((ag * bg) >> 7) + ((ag * ag) >> 8) - ((ag * ag * bg) >> 15), |
|
7015 cb = ((ab * bb) >> 7) + ((ab * ab) >> 8) - ((ab * ab * bb) >> 15); |
|
7016 |
|
7017 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
7018 }, |
|
7019 overlay: function(c1, c2) { |
|
7020 var f = (c2 & ALPHA_MASK) >>> 24, |
|
7021 ar = (c1 & RED_MASK) >> 16, |
|
7022 ag = (c1 & GREEN_MASK) >> 8, |
|
7023 ab = (c1 & BLUE_MASK), |
|
7024 br = (c2 & RED_MASK) >> 16, |
|
7025 bg = (c2 & GREEN_MASK) >> 8, |
|
7026 bb = (c2 & BLUE_MASK), |
|
7027 cr = (ar < 128) ? ((ar * br) >> 7) : (255 - (((255 - ar) * (255 - br)) >> 7)), |
|
7028 cg = (ag < 128) ? ((ag * bg) >> 7) : (255 - (((255 - ag) * (255 - bg)) >> 7)), |
|
7029 cb = (ab < 128) ? ((ab * bb) >> 7) : (255 - (((255 - ab) * (255 - bb)) >> 7)); |
|
7030 |
|
7031 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
7032 }, |
|
7033 dodge: function(c1, c2) { |
|
7034 var f = (c2 & ALPHA_MASK) >>> 24, |
|
7035 ar = (c1 & RED_MASK) >> 16, |
|
7036 ag = (c1 & GREEN_MASK) >> 8, |
|
7037 ab = (c1 & BLUE_MASK), |
|
7038 br = (c2 & RED_MASK) >> 16, |
|
7039 bg = (c2 & GREEN_MASK) >> 8, |
|
7040 bb = (c2 & BLUE_MASK); |
|
7041 |
|
7042 var cr = 255; |
|
7043 if (br !== 255) { |
|
7044 cr = (ar << 8) / (255 - br); |
|
7045 cr = (cr < 0) ? 0 : ((cr > 255) ? 255 : cr); |
|
7046 } |
|
7047 |
|
7048 var cg = 255; |
|
7049 if (bg !== 255) { |
|
7050 cg = (ag << 8) / (255 - bg); |
|
7051 cg = (cg < 0) ? 0 : ((cg > 255) ? 255 : cg); |
|
7052 } |
|
7053 |
|
7054 var cb = 255; |
|
7055 if (bb !== 255) { |
|
7056 cb = (ab << 8) / (255 - bb); |
|
7057 cb = (cb < 0) ? 0 : ((cb > 255) ? 255 : cb); |
|
7058 } |
|
7059 |
|
7060 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
7061 }, |
|
7062 burn: function(c1, c2) { |
|
7063 var f = (c2 & ALPHA_MASK) >>> 24, |
|
7064 ar = (c1 & RED_MASK) >> 16, |
|
7065 ag = (c1 & GREEN_MASK) >> 8, |
|
7066 ab = (c1 & BLUE_MASK), |
|
7067 br = (c2 & RED_MASK) >> 16, |
|
7068 bg = (c2 & GREEN_MASK) >> 8, |
|
7069 bb = (c2 & BLUE_MASK); |
|
7070 |
|
7071 var cr = 0; |
|
7072 if (br !== 0) { |
|
7073 cr = ((255 - ar) << 8) / br; |
|
7074 cr = 255 - ((cr < 0) ? 0 : ((cr > 255) ? 255 : cr)); |
|
7075 } |
|
7076 |
|
7077 var cg = 0; |
|
7078 if (bg !== 0) { |
|
7079 cg = ((255 - ag) << 8) / bg; |
|
7080 cg = 255 - ((cg < 0) ? 0 : ((cg > 255) ? 255 : cg)); |
|
7081 } |
|
7082 |
|
7083 var cb = 0; |
|
7084 if (bb !== 0) { |
|
7085 cb = ((255 - ab) << 8) / bb; |
|
7086 cb = 255 - ((cb < 0) ? 0 : ((cb > 255) ? 255 : cb)); |
|
7087 } |
|
7088 |
|
7089 return applyMode(c1, f, ar, ag, ab, br, bg, bb, cr, cg, cb); |
|
7090 } |
|
7091 }; |
|
7092 }()); |
|
7093 |
|
7094 function color$4(aValue1, aValue2, aValue3, aValue4) { |
|
7095 var r, g, b, a; |
|
7096 |
|
7097 if (curColorMode === PConstants.HSB) { |
|
7098 var rgb = p.color.toRGB(aValue1, aValue2, aValue3); |
|
7099 r = rgb[0]; |
|
7100 g = rgb[1]; |
|
7101 b = rgb[2]; |
|
7102 } else { |
|
7103 r = Math.round(255 * (aValue1 / colorModeX)); |
|
7104 g = Math.round(255 * (aValue2 / colorModeY)); |
|
7105 b = Math.round(255 * (aValue3 / colorModeZ)); |
|
7106 } |
|
7107 |
|
7108 a = Math.round(255 * (aValue4 / colorModeA)); |
|
7109 |
|
7110 // Limit values less than 0 and greater than 255 |
|
7111 r = (r < 0) ? 0 : r; |
|
7112 g = (g < 0) ? 0 : g; |
|
7113 b = (b < 0) ? 0 : b; |
|
7114 a = (a < 0) ? 0 : a; |
|
7115 r = (r > 255) ? 255 : r; |
|
7116 g = (g > 255) ? 255 : g; |
|
7117 b = (b > 255) ? 255 : b; |
|
7118 a = (a > 255) ? 255 : a; |
|
7119 |
|
7120 // Create color int |
|
7121 return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; |
|
7122 } |
|
7123 |
|
7124 function color$2(aValue1, aValue2) { |
|
7125 var a; |
|
7126 |
|
7127 // Color int and alpha |
|
7128 if (aValue1 & PConstants.ALPHA_MASK) { |
|
7129 a = Math.round(255 * (aValue2 / colorModeA)); |
|
7130 // Limit values less than 0 and greater than 255 |
|
7131 a = (a > 255) ? 255 : a; |
|
7132 a = (a < 0) ? 0 : a; |
|
7133 |
|
7134 return aValue1 - (aValue1 & PConstants.ALPHA_MASK) + ((a << 24) & PConstants.ALPHA_MASK); |
|
7135 } |
|
7136 // Grayscale and alpha |
|
7137 if (curColorMode === PConstants.RGB) { |
|
7138 return color$4(aValue1, aValue1, aValue1, aValue2); |
|
7139 } |
|
7140 if (curColorMode === PConstants.HSB) { |
|
7141 return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, aValue2); |
|
7142 } |
|
7143 } |
|
7144 |
|
7145 function color$1(aValue1) { |
|
7146 // Grayscale |
|
7147 if (aValue1 <= colorModeX && aValue1 >= 0) { |
|
7148 if (curColorMode === PConstants.RGB) { |
|
7149 return color$4(aValue1, aValue1, aValue1, colorModeA); |
|
7150 } |
|
7151 if (curColorMode === PConstants.HSB) { |
|
7152 return color$4(0, 0, (aValue1 / colorModeX) * colorModeZ, colorModeA); |
|
7153 } |
|
7154 } |
|
7155 // Color int |
|
7156 if (aValue1) { |
|
7157 if (aValue1 > 2147483647) { |
|
7158 // Java Overflow |
|
7159 aValue1 -= 4294967296; |
|
7160 } |
|
7161 return aValue1; |
|
7162 } |
|
7163 } |
|
7164 |
|
7165 /** |
|
7166 * Creates colors for storing in variables of the color datatype. The parameters are |
|
7167 * interpreted as RGB or HSB values depending on the current colorMode(). The default |
|
7168 * mode is RGB values from 0 to 255 and therefore, the function call color(255, 204, 0) |
|
7169 * will return a bright yellow color. More about how colors are stored can be found in |
|
7170 * the reference for the color datatype. |
|
7171 * |
|
7172 * @param {int|float} aValue1 red or hue or grey values relative to the current color range. |
|
7173 * Also can be color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) |
|
7174 * @param {int|float} aValue2 green or saturation values relative to the current color range |
|
7175 * @param {int|float} aValue3 blue or brightness values relative to the current color range |
|
7176 * @param {int|float} aValue4 relative to current color range. Represents alpha |
|
7177 * |
|
7178 * @returns {color} the color |
|
7179 * |
|
7180 * @see colorMode |
|
7181 */ |
|
7182 p.color = function(aValue1, aValue2, aValue3, aValue4) { |
|
7183 |
|
7184 // 4 arguments: (R, G, B, A) or (H, S, B, A) |
|
7185 if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef && aValue4 !== undef) { |
|
7186 return color$4(aValue1, aValue2, aValue3, aValue4); |
|
7187 } |
|
7188 |
|
7189 // 3 arguments: (R, G, B) or (H, S, B) |
|
7190 if (aValue1 !== undef && aValue2 !== undef && aValue3 !== undef) { |
|
7191 return color$4(aValue1, aValue2, aValue3, colorModeA); |
|
7192 } |
|
7193 |
|
7194 // 2 arguments: (Color, A) or (Grayscale, A) |
|
7195 if (aValue1 !== undef && aValue2 !== undef) { |
|
7196 return color$2(aValue1, aValue2); |
|
7197 } |
|
7198 |
|
7199 // 1 argument: (Grayscale) or (Color) |
|
7200 if (typeof aValue1 === "number") { |
|
7201 return color$1(aValue1); |
|
7202 } |
|
7203 |
|
7204 // Default |
|
7205 return color$4(colorModeX, colorModeY, colorModeZ, colorModeA); |
|
7206 }; |
|
7207 |
|
7208 // Ease of use function to extract the colour bits into a string |
|
7209 p.color.toString = function(colorInt) { |
|
7210 return "rgba(" + ((colorInt & PConstants.RED_MASK) >>> 16) + "," + ((colorInt & PConstants.GREEN_MASK) >>> 8) + |
|
7211 "," + ((colorInt & PConstants.BLUE_MASK)) + "," + ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255 + ")"; |
|
7212 }; |
|
7213 |
|
7214 // Easy of use function to pack rgba values into a single bit-shifted color int. |
|
7215 p.color.toInt = function(r, g, b, a) { |
|
7216 return (a << 24) & PConstants.ALPHA_MASK | (r << 16) & PConstants.RED_MASK | (g << 8) & PConstants.GREEN_MASK | b & PConstants.BLUE_MASK; |
|
7217 }; |
|
7218 |
|
7219 // Creates a simple array in [R, G, B, A] format, [255, 255, 255, 255] |
|
7220 p.color.toArray = function(colorInt) { |
|
7221 return [(colorInt & PConstants.RED_MASK) >>> 16, (colorInt & PConstants.GREEN_MASK) >>> 8, |
|
7222 colorInt & PConstants.BLUE_MASK, (colorInt & PConstants.ALPHA_MASK) >>> 24]; |
|
7223 }; |
|
7224 |
|
7225 // Creates a WebGL color array in [R, G, B, A] format. WebGL wants the color ranges between 0 and 1, [1, 1, 1, 1] |
|
7226 p.color.toGLArray = function(colorInt) { |
|
7227 return [((colorInt & PConstants.RED_MASK) >>> 16) / 255, ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255, |
|
7228 (colorInt & PConstants.BLUE_MASK) / 255, ((colorInt & PConstants.ALPHA_MASK) >>> 24) / 255]; |
|
7229 }; |
|
7230 |
|
7231 // HSB conversion function from Mootools, MIT Licensed |
|
7232 p.color.toRGB = function(h, s, b) { |
|
7233 // Limit values greater than range |
|
7234 h = (h > colorModeX) ? colorModeX : h; |
|
7235 s = (s > colorModeY) ? colorModeY : s; |
|
7236 b = (b > colorModeZ) ? colorModeZ : b; |
|
7237 |
|
7238 h = (h / colorModeX) * 360; |
|
7239 s = (s / colorModeY) * 100; |
|
7240 b = (b / colorModeZ) * 100; |
|
7241 |
|
7242 var br = Math.round(b / 100 * 255); |
|
7243 |
|
7244 if (s === 0) { // Grayscale |
|
7245 return [br, br, br]; |
|
7246 } |
|
7247 var hue = h % 360; |
|
7248 var f = hue % 60; |
|
7249 var p = Math.round((b * (100 - s)) / 10000 * 255); |
|
7250 var q = Math.round((b * (6000 - s * f)) / 600000 * 255); |
|
7251 var t = Math.round((b * (6000 - s * (60 - f))) / 600000 * 255); |
|
7252 switch (Math.floor(hue / 60)) { |
|
7253 case 0: |
|
7254 return [br, t, p]; |
|
7255 case 1: |
|
7256 return [q, br, p]; |
|
7257 case 2: |
|
7258 return [p, br, t]; |
|
7259 case 3: |
|
7260 return [p, q, br]; |
|
7261 case 4: |
|
7262 return [t, p, br]; |
|
7263 case 5: |
|
7264 return [br, p, q]; |
|
7265 } |
|
7266 }; |
|
7267 |
|
7268 function colorToHSB(colorInt) { |
|
7269 var red, green, blue; |
|
7270 |
|
7271 red = ((colorInt & PConstants.RED_MASK) >>> 16) / 255; |
|
7272 green = ((colorInt & PConstants.GREEN_MASK) >>> 8) / 255; |
|
7273 blue = (colorInt & PConstants.BLUE_MASK) / 255; |
|
7274 |
|
7275 var max = p.max(p.max(red,green), blue), |
|
7276 min = p.min(p.min(red,green), blue), |
|
7277 hue, saturation; |
|
7278 |
|
7279 if (min === max) { |
|
7280 return [0, 0, max*colorModeZ]; |
|
7281 } |
|
7282 saturation = (max - min) / max; |
|
7283 |
|
7284 if (red === max) { |
|
7285 hue = (green - blue) / (max - min); |
|
7286 } else if (green === max) { |
|
7287 hue = 2 + ((blue - red) / (max - min)); |
|
7288 } else { |
|
7289 hue = 4 + ((red - green) / (max - min)); |
|
7290 } |
|
7291 |
|
7292 hue /= 6; |
|
7293 |
|
7294 if (hue < 0) { |
|
7295 hue += 1; |
|
7296 } else if (hue > 1) { |
|
7297 hue -= 1; |
|
7298 } |
|
7299 return [hue*colorModeX, saturation*colorModeY, max*colorModeZ]; |
|
7300 } |
|
7301 |
|
7302 /** |
|
7303 * Extracts the brightness value from a color. |
|
7304 * |
|
7305 * @param {color} colInt any value of the color datatype |
|
7306 * |
|
7307 * @returns {float} The brightness color value. |
|
7308 * |
|
7309 * @see red |
|
7310 * @see green |
|
7311 * @see blue |
|
7312 * @see hue |
|
7313 * @see saturation |
|
7314 */ |
|
7315 p.brightness = function(colInt){ |
|
7316 return colorToHSB(colInt)[2]; |
|
7317 }; |
|
7318 |
|
7319 /** |
|
7320 * Extracts the saturation value from a color. |
|
7321 * |
|
7322 * @param {color} colInt any value of the color datatype |
|
7323 * |
|
7324 * @returns {float} The saturation color value. |
|
7325 * |
|
7326 * @see red |
|
7327 * @see green |
|
7328 * @see blue |
|
7329 * @see hue |
|
7330 * @see brightness |
|
7331 */ |
|
7332 p.saturation = function(colInt){ |
|
7333 return colorToHSB(colInt)[1]; |
|
7334 }; |
|
7335 |
|
7336 /** |
|
7337 * Extracts the hue value from a color. |
|
7338 * |
|
7339 * @param {color} colInt any value of the color datatype |
|
7340 * |
|
7341 * @returns {float} The hue color value. |
|
7342 * |
|
7343 * @see red |
|
7344 * @see green |
|
7345 * @see blue |
|
7346 * @see saturation |
|
7347 * @see brightness |
|
7348 */ |
|
7349 p.hue = function(colInt){ |
|
7350 return colorToHSB(colInt)[0]; |
|
7351 }; |
|
7352 |
|
7353 /** |
|
7354 * Extracts the red value from a color, scaled to match current colorMode(). |
|
7355 * This value is always returned as a float so be careful not to assign it to an int value. |
|
7356 * |
|
7357 * @param {color} aColor any value of the color datatype |
|
7358 * |
|
7359 * @returns {float} The red color value. |
|
7360 * |
|
7361 * @see green |
|
7362 * @see blue |
|
7363 * @see alpha |
|
7364 * @see >> right shift |
|
7365 * @see hue |
|
7366 * @see saturation |
|
7367 * @see brightness |
|
7368 */ |
|
7369 p.red = function(aColor) { |
|
7370 return ((aColor & PConstants.RED_MASK) >>> 16) / 255 * colorModeX; |
|
7371 }; |
|
7372 |
|
7373 /** |
|
7374 * Extracts the green value from a color, scaled to match current colorMode(). |
|
7375 * This value is always returned as a float so be careful not to assign it to an int value. |
|
7376 * |
|
7377 * @param {color} aColor any value of the color datatype |
|
7378 * |
|
7379 * @returns {float} The green color value. |
|
7380 * |
|
7381 * @see red |
|
7382 * @see blue |
|
7383 * @see alpha |
|
7384 * @see >> right shift |
|
7385 * @see hue |
|
7386 * @see saturation |
|
7387 * @see brightness |
|
7388 */ |
|
7389 p.green = function(aColor) { |
|
7390 return ((aColor & PConstants.GREEN_MASK) >>> 8) / 255 * colorModeY; |
|
7391 }; |
|
7392 |
|
7393 /** |
|
7394 * Extracts the blue value from a color, scaled to match current colorMode(). |
|
7395 * This value is always returned as a float so be careful not to assign it to an int value. |
|
7396 * |
|
7397 * @param {color} aColor any value of the color datatype |
|
7398 * |
|
7399 * @returns {float} The blue color value. |
|
7400 * |
|
7401 * @see red |
|
7402 * @see green |
|
7403 * @see alpha |
|
7404 * @see >> right shift |
|
7405 * @see hue |
|
7406 * @see saturation |
|
7407 * @see brightness |
|
7408 */ |
|
7409 p.blue = function(aColor) { |
|
7410 return (aColor & PConstants.BLUE_MASK) / 255 * colorModeZ; |
|
7411 }; |
|
7412 |
|
7413 /** |
|
7414 * Extracts the alpha value from a color, scaled to match current colorMode(). |
|
7415 * This value is always returned as a float so be careful not to assign it to an int value. |
|
7416 * |
|
7417 * @param {color} aColor any value of the color datatype |
|
7418 * |
|
7419 * @returns {float} The alpha color value. |
|
7420 * |
|
7421 * @see red |
|
7422 * @see green |
|
7423 * @see blue |
|
7424 * @see >> right shift |
|
7425 * @see hue |
|
7426 * @see saturation |
|
7427 * @see brightness |
|
7428 */ |
|
7429 p.alpha = function(aColor) { |
|
7430 return ((aColor & PConstants.ALPHA_MASK) >>> 24) / 255 * colorModeA; |
|
7431 }; |
|
7432 |
|
7433 /** |
|
7434 * Calculates a color or colors between two colors at a specific increment. |
|
7435 * The amt parameter is the amount to interpolate between the two values where 0.0 |
|
7436 * equal to the first point, 0.1 is very near the first point, 0.5 is half-way in between, etc. |
|
7437 * |
|
7438 * @param {color} c1 interpolate from this color |
|
7439 * @param {color} c2 interpolate to this color |
|
7440 * @param {float} amt between 0.0 and 1.0 |
|
7441 * |
|
7442 * @returns {float} The blended color. |
|
7443 * |
|
7444 * @see blendColor |
|
7445 * @see color |
|
7446 */ |
|
7447 p.lerpColor = function(c1, c2, amt) { |
|
7448 var r, g, b, a, r1, g1, b1, a1, r2, g2, b2, a2; |
|
7449 var hsb1, hsb2, rgb, h, s; |
|
7450 var colorBits1 = p.color(c1); |
|
7451 var colorBits2 = p.color(c2); |
|
7452 |
|
7453 if (curColorMode === PConstants.HSB) { |
|
7454 // Special processing for HSB mode. |
|
7455 // Get HSB and Alpha values for Color 1 and 2 |
|
7456 hsb1 = colorToHSB(colorBits1); |
|
7457 a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; |
|
7458 hsb2 = colorToHSB(colorBits2); |
|
7459 a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; |
|
7460 |
|
7461 // Return lerp value for each channel, for HSB components |
|
7462 h = p.lerp(hsb1[0], hsb2[0], amt); |
|
7463 s = p.lerp(hsb1[1], hsb2[1], amt); |
|
7464 b = p.lerp(hsb1[2], hsb2[2], amt); |
|
7465 rgb = p.color.toRGB(h, s, b); |
|
7466 // ... and for Alpha-range |
|
7467 a = p.lerp(a1, a2, amt) * colorModeA; |
|
7468 |
|
7469 return (a << 24) & PConstants.ALPHA_MASK | |
|
7470 (rgb[0] << 16) & PConstants.RED_MASK | |
|
7471 (rgb[1] << 8) & PConstants.GREEN_MASK | |
|
7472 rgb[2] & PConstants.BLUE_MASK; |
|
7473 } |
|
7474 |
|
7475 // Get RGBA values for Color 1 to floats |
|
7476 r1 = (colorBits1 & PConstants.RED_MASK) >>> 16; |
|
7477 g1 = (colorBits1 & PConstants.GREEN_MASK) >>> 8; |
|
7478 b1 = (colorBits1 & PConstants.BLUE_MASK); |
|
7479 a1 = ((colorBits1 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; |
|
7480 |
|
7481 // Get RGBA values for Color 2 to floats |
|
7482 r2 = (colorBits2 & PConstants.RED_MASK) >>> 16; |
|
7483 g2 = (colorBits2 & PConstants.GREEN_MASK) >>> 8; |
|
7484 b2 = (colorBits2 & PConstants.BLUE_MASK); |
|
7485 a2 = ((colorBits2 & PConstants.ALPHA_MASK) >>> 24) / colorModeA; |
|
7486 |
|
7487 // Return lerp value for each channel, INT for color, Float for Alpha-range |
|
7488 r = p.lerp(r1, r2, amt) | 0; |
|
7489 g = p.lerp(g1, g2, amt) | 0; |
|
7490 b = p.lerp(b1, b2, amt) | 0; |
|
7491 a = p.lerp(a1, a2, amt) * colorModeA; |
|
7492 |
|
7493 return (a << 24) & PConstants.ALPHA_MASK | |
|
7494 (r << 16) & PConstants.RED_MASK | |
|
7495 (g << 8) & PConstants.GREEN_MASK | |
|
7496 b & PConstants.BLUE_MASK; |
|
7497 }; |
|
7498 |
|
7499 /** |
|
7500 * Changes the way Processing interprets color data. By default, fill(), stroke(), and background() |
|
7501 * colors are set by values between 0 and 255 using the RGB color model. It is possible to change the |
|
7502 * numerical range used for specifying colors and to switch color systems. For example, calling colorMode(RGB, 1.0) |
|
7503 * will specify that values are specified between 0 and 1. The limits for defining colors are altered by setting the |
|
7504 * parameters range1, range2, range3, and range 4. |
|
7505 * |
|
7506 * @param {MODE} mode Either RGB or HSB, corresponding to Red/Green/Blue and Hue/Saturation/Brightness |
|
7507 * @param {int|float} range range for all color elements |
|
7508 * @param {int|float} range1 range for the red or hue depending on the current color mode |
|
7509 * @param {int|float} range2 range for the green or saturation depending on the current color mode |
|
7510 * @param {int|float} range3 range for the blue or brightness depending on the current color mode |
|
7511 * @param {int|float} range4 range for the alpha |
|
7512 * |
|
7513 * @returns none |
|
7514 * |
|
7515 * @see background |
|
7516 * @see fill |
|
7517 * @see stroke |
|
7518 */ |
|
7519 p.colorMode = function() { // mode, range1, range2, range3, range4 |
|
7520 curColorMode = arguments[0]; |
|
7521 if (arguments.length > 1) { |
|
7522 colorModeX = arguments[1]; |
|
7523 colorModeY = arguments[2] || arguments[1]; |
|
7524 colorModeZ = arguments[3] || arguments[1]; |
|
7525 colorModeA = arguments[4] || arguments[1]; |
|
7526 } |
|
7527 }; |
|
7528 |
|
7529 /** |
|
7530 * Blends two color values together based on the blending mode given as the MODE parameter. |
|
7531 * The possible modes are described in the reference for the blend() function. |
|
7532 * |
|
7533 * @param {color} c1 color: the first color to blend |
|
7534 * @param {color} c2 color: the second color to blend |
|
7535 * @param {MODE} MODE Either BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, |
|
7536 * SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, or BURN |
|
7537 * |
|
7538 * @returns {float} The blended color. |
|
7539 * |
|
7540 * @see blend |
|
7541 * @see color |
|
7542 */ |
|
7543 p.blendColor = function(c1, c2, mode) { |
|
7544 if (mode === PConstants.REPLACE) { |
|
7545 return p.modes.replace(c1, c2); |
|
7546 } else if (mode === PConstants.BLEND) { |
|
7547 return p.modes.blend(c1, c2); |
|
7548 } else if (mode === PConstants.ADD) { |
|
7549 return p.modes.add(c1, c2); |
|
7550 } else if (mode === PConstants.SUBTRACT) { |
|
7551 return p.modes.subtract(c1, c2); |
|
7552 } else if (mode === PConstants.LIGHTEST) { |
|
7553 return p.modes.lightest(c1, c2); |
|
7554 } else if (mode === PConstants.DARKEST) { |
|
7555 return p.modes.darkest(c1, c2); |
|
7556 } else if (mode === PConstants.DIFFERENCE) { |
|
7557 return p.modes.difference(c1, c2); |
|
7558 } else if (mode === PConstants.EXCLUSION) { |
|
7559 return p.modes.exclusion(c1, c2); |
|
7560 } else if (mode === PConstants.MULTIPLY) { |
|
7561 return p.modes.multiply(c1, c2); |
|
7562 } else if (mode === PConstants.SCREEN) { |
|
7563 return p.modes.screen(c1, c2); |
|
7564 } else if (mode === PConstants.HARD_LIGHT) { |
|
7565 return p.modes.hard_light(c1, c2); |
|
7566 } else if (mode === PConstants.SOFT_LIGHT) { |
|
7567 return p.modes.soft_light(c1, c2); |
|
7568 } else if (mode === PConstants.OVERLAY) { |
|
7569 return p.modes.overlay(c1, c2); |
|
7570 } else if (mode === PConstants.DODGE) { |
|
7571 return p.modes.dodge(c1, c2); |
|
7572 } else if (mode === PConstants.BURN) { |
|
7573 return p.modes.burn(c1, c2); |
|
7574 } |
|
7575 }; |
|
7576 |
|
7577 //////////////////////////////////////////////////////////////////////////// |
|
7578 // Canvas-Matrix manipulation |
|
7579 //////////////////////////////////////////////////////////////////////////// |
|
7580 |
|
7581 function saveContext() { |
|
7582 curContext.save(); |
|
7583 } |
|
7584 |
|
7585 function restoreContext() { |
|
7586 curContext.restore(); |
|
7587 isStrokeDirty = true; |
|
7588 isFillDirty = true; |
|
7589 } |
|
7590 |
|
7591 /** |
|
7592 * Prints the current matrix to the text window. |
|
7593 * |
|
7594 * @returns none |
|
7595 * |
|
7596 * @see pushMatrix |
|
7597 * @see popMatrix |
|
7598 * @see resetMatrix |
|
7599 * @see applyMatrix |
|
7600 */ |
|
7601 p.printMatrix = function() { |
|
7602 modelView.print(); |
|
7603 }; |
|
7604 |
|
7605 /** |
|
7606 * Specifies an amount to displace objects within the display window. The x parameter specifies left/right translation, |
|
7607 * the y parameter specifies up/down translation, and the z parameter specifies translations toward/away from the screen. |
|
7608 * Using this function with the z parameter requires using the P3D or OPENGL parameter in combination with size as shown |
|
7609 * in the above example. Transformations apply to everything that happens after and subsequent calls to the function |
|
7610 * accumulates the effect. For example, calling translate(50, 0) and then translate(20, 0) is the same as translate(70, 0). |
|
7611 * If translate() is called within draw(), the transformation is reset when the loop begins again. |
|
7612 * This function can be further controlled by the pushMatrix() and popMatrix(). |
|
7613 * |
|
7614 * @param {int|float} x left/right translation |
|
7615 * @param {int|float} y up/down translation |
|
7616 * @param {int|float} z forward/back translation |
|
7617 * |
|
7618 * @returns none |
|
7619 * |
|
7620 * @see pushMatrix |
|
7621 * @see popMatrix |
|
7622 * @see scale |
|
7623 * @see rotate |
|
7624 * @see rotateX |
|
7625 * @see rotateY |
|
7626 * @see rotateZ |
|
7627 */ |
|
7628 Drawing2D.prototype.translate = function(x, y) { |
|
7629 modelView.translate(x, y); |
|
7630 modelViewInv.invTranslate(x, y); |
|
7631 curContext.translate(x, y); |
|
7632 }; |
|
7633 |
|
7634 Drawing3D.prototype.translate = function(x, y, z) { |
|
7635 modelView.translate(x, y, z); |
|
7636 modelViewInv.invTranslate(x, y, z); |
|
7637 }; |
|
7638 |
|
7639 /** |
|
7640 * Increases or decreases the size of a shape by expanding and contracting vertices. Objects always scale from their |
|
7641 * relative origin to the coordinate system. Scale values are specified as decimal percentages. For example, the |
|
7642 * function call scale(2.0) increases the dimension of a shape by 200%. Transformations apply to everything that |
|
7643 * happens after and subsequent calls to the function multiply the effect. For example, calling scale(2.0) and |
|
7644 * then scale(1.5) is the same as scale(3.0). If scale() is called within draw(), the transformation is reset when |
|
7645 * the loop begins again. Using this fuction with the z parameter requires passing P3D or OPENGL into the size() |
|
7646 * parameter as shown in the example above. This function can be further controlled by pushMatrix() and popMatrix(). |
|
7647 * |
|
7648 * @param {int|float} size percentage to scale the object |
|
7649 * @param {int|float} x percentage to scale the object in the x-axis |
|
7650 * @param {int|float} y percentage to scale the object in the y-axis |
|
7651 * @param {int|float} z percentage to scale the object in the z-axis |
|
7652 * |
|
7653 * @returns none |
|
7654 * |
|
7655 * @see pushMatrix |
|
7656 * @see popMatrix |
|
7657 * @see translate |
|
7658 * @see rotate |
|
7659 * @see rotateX |
|
7660 * @see rotateY |
|
7661 * @see rotateZ |
|
7662 */ |
|
7663 Drawing2D.prototype.scale = function(x, y) { |
|
7664 modelView.scale(x, y); |
|
7665 modelViewInv.invScale(x, y); |
|
7666 curContext.scale(x, y || x); |
|
7667 }; |
|
7668 |
|
7669 Drawing3D.prototype.scale = function(x, y, z) { |
|
7670 modelView.scale(x, y, z); |
|
7671 modelViewInv.invScale(x, y, z); |
|
7672 }; |
|
7673 |
|
7674 /** |
|
7675 * Pushes the current transformation matrix onto the matrix stack. Understanding pushMatrix() and popMatrix() |
|
7676 * requires understanding the concept of a matrix stack. The pushMatrix() function saves the current coordinate |
|
7677 * system to the stack and popMatrix() restores the prior coordinate system. pushMatrix() and popMatrix() are |
|
7678 * used in conjuction with the other transformation methods and may be embedded to control the scope of |
|
7679 * the transformations. |
|
7680 * |
|
7681 * @returns none |
|
7682 * |
|
7683 * @see popMatrix |
|
7684 * @see translate |
|
7685 * @see rotate |
|
7686 * @see rotateX |
|
7687 * @see rotateY |
|
7688 * @see rotateZ |
|
7689 */ |
|
7690 Drawing2D.prototype.pushMatrix = function() { |
|
7691 userMatrixStack.load(modelView); |
|
7692 userReverseMatrixStack.load(modelViewInv); |
|
7693 saveContext(); |
|
7694 }; |
|
7695 |
|
7696 Drawing3D.prototype.pushMatrix = function() { |
|
7697 userMatrixStack.load(modelView); |
|
7698 userReverseMatrixStack.load(modelViewInv); |
|
7699 }; |
|
7700 |
|
7701 /** |
|
7702 * Pops the current transformation matrix off the matrix stack. Understanding pushing and popping requires |
|
7703 * understanding the concept of a matrix stack. The pushMatrix() function saves the current coordinate system to |
|
7704 * the stack and popMatrix() restores the prior coordinate system. pushMatrix() and popMatrix() are used in |
|
7705 * conjuction with the other transformation methods and may be embedded to control the scope of the transformations. |
|
7706 * |
|
7707 * @returns none |
|
7708 * |
|
7709 * @see popMatrix |
|
7710 * @see pushMatrix |
|
7711 */ |
|
7712 Drawing2D.prototype.popMatrix = function() { |
|
7713 modelView.set(userMatrixStack.pop()); |
|
7714 modelViewInv.set(userReverseMatrixStack.pop()); |
|
7715 restoreContext(); |
|
7716 }; |
|
7717 |
|
7718 Drawing3D.prototype.popMatrix = function() { |
|
7719 modelView.set(userMatrixStack.pop()); |
|
7720 modelViewInv.set(userReverseMatrixStack.pop()); |
|
7721 }; |
|
7722 |
|
7723 /** |
|
7724 * Replaces the current matrix with the identity matrix. The equivalent function in OpenGL is glLoadIdentity(). |
|
7725 * |
|
7726 * @returns none |
|
7727 * |
|
7728 * @see popMatrix |
|
7729 * @see pushMatrix |
|
7730 * @see applyMatrix |
|
7731 * @see printMatrix |
|
7732 */ |
|
7733 Drawing2D.prototype.resetMatrix = function() { |
|
7734 modelView.reset(); |
|
7735 modelViewInv.reset(); |
|
7736 curContext.setTransform(1,0,0,1,0,0); |
|
7737 }; |
|
7738 |
|
7739 Drawing3D.prototype.resetMatrix = function() { |
|
7740 modelView.reset(); |
|
7741 modelViewInv.reset(); |
|
7742 }; |
|
7743 |
|
7744 /** |
|
7745 * Multiplies the current matrix by the one specified through the parameters. This is very slow because it will |
|
7746 * try to calculate the inverse of the transform, so avoid it whenever possible. The equivalent function |
|
7747 * in OpenGL is glMultMatrix(). |
|
7748 * |
|
7749 * @param {int|float} n00-n15 numbers which define the 4x4 matrix to be multiplied |
|
7750 * |
|
7751 * @returns none |
|
7752 * |
|
7753 * @see popMatrix |
|
7754 * @see pushMatrix |
|
7755 * @see resetMatrix |
|
7756 * @see printMatrix |
|
7757 */ |
|
7758 DrawingShared.prototype.applyMatrix = function() { |
|
7759 var a = arguments; |
|
7760 modelView.apply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); |
|
7761 modelViewInv.invApply(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); |
|
7762 }; |
|
7763 |
|
7764 Drawing2D.prototype.applyMatrix = function() { |
|
7765 var a = arguments; |
|
7766 for (var cnt = a.length; cnt < 16; cnt++) { |
|
7767 a[cnt] = 0; |
|
7768 } |
|
7769 a[10] = a[15] = 1; |
|
7770 DrawingShared.prototype.applyMatrix.apply(this, a); |
|
7771 }; |
|
7772 |
|
7773 /** |
|
7774 * Rotates a shape around the x-axis the amount specified by the angle parameter. Angles should be |
|
7775 * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. |
|
7776 * Objects are always rotated around their relative position to the origin and positive numbers |
|
7777 * rotate objects in a counterclockwise direction. Transformations apply to everything that happens |
|
7778 * after and subsequent calls to the function accumulates the effect. For example, calling rotateX(PI/2) |
|
7779 * and then rotateX(PI/2) is the same as rotateX(PI). If rotateX() is called within the draw(), the |
|
7780 * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL |
|
7781 * into the size() parameter as shown in the example above. |
|
7782 * |
|
7783 * @param {int|float} angleInRadians angle of rotation specified in radians |
|
7784 * |
|
7785 * @returns none |
|
7786 * |
|
7787 * @see rotateY |
|
7788 * @see rotateZ |
|
7789 * @see rotate |
|
7790 * @see translate |
|
7791 * @see scale |
|
7792 * @see popMatrix |
|
7793 * @see pushMatrix |
|
7794 */ |
|
7795 p.rotateX = function(angleInRadians) { |
|
7796 modelView.rotateX(angleInRadians); |
|
7797 modelViewInv.invRotateX(angleInRadians); |
|
7798 }; |
|
7799 |
|
7800 /** |
|
7801 * Rotates a shape around the z-axis the amount specified by the angle parameter. Angles should be |
|
7802 * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. |
|
7803 * Objects are always rotated around their relative position to the origin and positive numbers |
|
7804 * rotate objects in a counterclockwise direction. Transformations apply to everything that happens |
|
7805 * after and subsequent calls to the function accumulates the effect. For example, calling rotateZ(PI/2) |
|
7806 * and then rotateZ(PI/2) is the same as rotateZ(PI). If rotateZ() is called within the draw(), the |
|
7807 * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL |
|
7808 * into the size() parameter as shown in the example above. |
|
7809 * |
|
7810 * @param {int|float} angleInRadians angle of rotation specified in radians |
|
7811 * |
|
7812 * @returns none |
|
7813 * |
|
7814 * @see rotateX |
|
7815 * @see rotateY |
|
7816 * @see rotate |
|
7817 * @see translate |
|
7818 * @see scale |
|
7819 * @see popMatrix |
|
7820 * @see pushMatrix |
|
7821 */ |
|
7822 Drawing2D.prototype.rotateZ = function() { |
|
7823 throw "rotateZ() is not supported in 2D mode. Use rotate(float) instead."; |
|
7824 }; |
|
7825 |
|
7826 Drawing3D.prototype.rotateZ = function(angleInRadians) { |
|
7827 modelView.rotateZ(angleInRadians); |
|
7828 modelViewInv.invRotateZ(angleInRadians); |
|
7829 }; |
|
7830 |
|
7831 /** |
|
7832 * Rotates a shape around the y-axis the amount specified by the angle parameter. Angles should be |
|
7833 * specified in radians (values from 0 to PI*2) or converted to radians with the radians() function. |
|
7834 * Objects are always rotated around their relative position to the origin and positive numbers |
|
7835 * rotate objects in a counterclockwise direction. Transformations apply to everything that happens |
|
7836 * after and subsequent calls to the function accumulates the effect. For example, calling rotateY(PI/2) |
|
7837 * and then rotateY(PI/2) is the same as rotateY(PI). If rotateY() is called within the draw(), the |
|
7838 * transformation is reset when the loop begins again. This function requires passing P3D or OPENGL |
|
7839 * into the size() parameter as shown in the example above. |
|
7840 * |
|
7841 * @param {int|float} angleInRadians angle of rotation specified in radians |
|
7842 * |
|
7843 * @returns none |
|
7844 * |
|
7845 * @see rotateX |
|
7846 * @see rotateZ |
|
7847 * @see rotate |
|
7848 * @see translate |
|
7849 * @see scale |
|
7850 * @see popMatrix |
|
7851 * @see pushMatrix |
|
7852 */ |
|
7853 p.rotateY = function(angleInRadians) { |
|
7854 modelView.rotateY(angleInRadians); |
|
7855 modelViewInv.invRotateY(angleInRadians); |
|
7856 }; |
|
7857 |
|
7858 /** |
|
7859 * Rotates a shape the amount specified by the angle parameter. Angles should be specified in radians |
|
7860 * (values from 0 to TWO_PI) or converted to radians with the radians() function. Objects are always |
|
7861 * rotated around their relative position to the origin and positive numbers rotate objects in a |
|
7862 * clockwise direction. Transformations apply to everything that happens after and subsequent calls |
|
7863 * to the function accumulates the effect. For example, calling rotate(HALF_PI) and then rotate(HALF_PI) |
|
7864 * is the same as rotate(PI). All tranformations are reset when draw() begins again. Technically, |
|
7865 * rotate() multiplies the current transformation matrix by a rotation matrix. This function can be |
|
7866 * further controlled by the pushMatrix() and popMatrix(). |
|
7867 * |
|
7868 * @param {int|float} angleInRadians angle of rotation specified in radians |
|
7869 * |
|
7870 * @returns none |
|
7871 * |
|
7872 * @see rotateX |
|
7873 * @see rotateY |
|
7874 * @see rotateZ |
|
7875 * @see rotate |
|
7876 * @see translate |
|
7877 * @see scale |
|
7878 * @see popMatrix |
|
7879 * @see pushMatrix |
|
7880 */ |
|
7881 Drawing2D.prototype.rotate = function(angleInRadians) { |
|
7882 modelView.rotateZ(angleInRadians); |
|
7883 modelViewInv.invRotateZ(angleInRadians); |
|
7884 curContext.rotate(angleInRadians); |
|
7885 }; |
|
7886 |
|
7887 Drawing3D.prototype.rotate = function(angleInRadians) { |
|
7888 p.rotateZ(angleInRadians); |
|
7889 }; |
|
7890 |
|
7891 /** |
|
7892 * The pushStyle() function saves the current style settings and popStyle() restores the prior settings. |
|
7893 * Note that these functions are always used together. They allow you to change the style settings and later |
|
7894 * return to what you had. When a new style is started with pushStyle(), it builds on the current style information. |
|
7895 * The pushStyle() and popStyle() functions can be embedded to provide more control (see the second example |
|
7896 * above for a demonstration.) |
|
7897 * The style information controlled by the following functions are included in the style: fill(), stroke(), tint(), |
|
7898 * strokeWeight(), strokeCap(), strokeJoin(), imageMode(), rectMode(), ellipseMode(), shapeMode(), colorMode(), |
|
7899 * textAlign(), textFont(), textMode(), textSize(), textLeading(), emissive(), specular(), shininess(), ambient() |
|
7900 * |
|
7901 * @returns none |
|
7902 * |
|
7903 * @see popStyle |
|
7904 */ |
|
7905 p.pushStyle = function() { |
|
7906 // Save the canvas state. |
|
7907 saveContext(); |
|
7908 |
|
7909 p.pushMatrix(); |
|
7910 |
|
7911 var newState = { |
|
7912 'doFill': doFill, |
|
7913 'currentFillColor': currentFillColor, |
|
7914 'doStroke': doStroke, |
|
7915 'currentStrokeColor': currentStrokeColor, |
|
7916 'curTint': curTint, |
|
7917 'curRectMode': curRectMode, |
|
7918 'curColorMode': curColorMode, |
|
7919 'colorModeX': colorModeX, |
|
7920 'colorModeZ': colorModeZ, |
|
7921 'colorModeY': colorModeY, |
|
7922 'colorModeA': colorModeA, |
|
7923 'curTextFont': curTextFont, |
|
7924 'horizontalTextAlignment': horizontalTextAlignment, |
|
7925 'verticalTextAlignment': verticalTextAlignment, |
|
7926 'textMode': textMode, |
|
7927 'curFontName': curFontName, |
|
7928 'curTextSize': curTextSize, |
|
7929 'curTextAscent': curTextAscent, |
|
7930 'curTextDescent': curTextDescent, |
|
7931 'curTextLeading': curTextLeading |
|
7932 }; |
|
7933 |
|
7934 styleArray.push(newState); |
|
7935 }; |
|
7936 |
|
7937 /** |
|
7938 * The pushStyle() function saves the current style settings and popStyle() restores the prior settings; these |
|
7939 * functions are always used together. They allow you to change the style settings and later return to what you had. |
|
7940 * When a new style is started with pushStyle(), it builds on the current style information. The pushStyle() and |
|
7941 * popStyle() functions can be embedded to provide more control (see the second example above for a demonstration.) |
|
7942 * |
|
7943 * @returns none |
|
7944 * |
|
7945 * @see pushStyle |
|
7946 */ |
|
7947 p.popStyle = function() { |
|
7948 var oldState = styleArray.pop(); |
|
7949 |
|
7950 if (oldState) { |
|
7951 restoreContext(); |
|
7952 |
|
7953 p.popMatrix(); |
|
7954 |
|
7955 doFill = oldState.doFill; |
|
7956 currentFillColor = oldState.currentFillColor; |
|
7957 doStroke = oldState.doStroke; |
|
7958 currentStrokeColor = oldState.currentStrokeColor; |
|
7959 curTint = oldState.curTint; |
|
7960 curRectMode = oldState.curRectmode; |
|
7961 curColorMode = oldState.curColorMode; |
|
7962 colorModeX = oldState.colorModeX; |
|
7963 colorModeZ = oldState.colorModeZ; |
|
7964 colorModeY = oldState.colorModeY; |
|
7965 colorModeA = oldState.colorModeA; |
|
7966 curTextFont = oldState.curTextFont; |
|
7967 curFontName = oldState.curFontName; |
|
7968 curTextSize = oldState.curTextSize; |
|
7969 horizontalTextAlignment = oldState.horizontalTextAlignment; |
|
7970 verticalTextAlignment = oldState.verticalTextAlignment; |
|
7971 textMode = oldState.textMode; |
|
7972 curTextAscent = oldState.curTextAscent; |
|
7973 curTextDescent = oldState.curTextDescent; |
|
7974 curTextLeading = oldState.curTextLeading; |
|
7975 } else { |
|
7976 throw "Too many popStyle() without enough pushStyle()"; |
|
7977 } |
|
7978 }; |
|
7979 |
|
7980 //////////////////////////////////////////////////////////////////////////// |
|
7981 // Time based functions |
|
7982 //////////////////////////////////////////////////////////////////////////// |
|
7983 |
|
7984 /** |
|
7985 * Processing communicates with the clock on your computer. |
|
7986 * The year() function returns the current year as an integer (2003, 2004, 2005, etc). |
|
7987 * |
|
7988 * @returns {float} The current year. |
|
7989 * |
|
7990 * @see millis |
|
7991 * @see second |
|
7992 * @see minute |
|
7993 * @see hour |
|
7994 * @see day |
|
7995 * @see month |
|
7996 */ |
|
7997 p.year = function() { |
|
7998 return new Date().getFullYear(); |
|
7999 }; |
|
8000 /** |
|
8001 * Processing communicates with the clock on your computer. |
|
8002 * The month() function returns the current month as a value from 1 - 12. |
|
8003 * |
|
8004 * @returns {float} The current month. |
|
8005 * |
|
8006 * @see millis |
|
8007 * @see second |
|
8008 * @see minute |
|
8009 * @see hour |
|
8010 * @see day |
|
8011 * @see year |
|
8012 */ |
|
8013 p.month = function() { |
|
8014 return new Date().getMonth() + 1; |
|
8015 }; |
|
8016 /** |
|
8017 * Processing communicates with the clock on your computer. |
|
8018 * The day() function returns the current day as a value from 1 - 31. |
|
8019 * |
|
8020 * @returns {float} The current day. |
|
8021 * |
|
8022 * @see millis |
|
8023 * @see second |
|
8024 * @see minute |
|
8025 * @see hour |
|
8026 * @see month |
|
8027 * @see year |
|
8028 */ |
|
8029 p.day = function() { |
|
8030 return new Date().getDate(); |
|
8031 }; |
|
8032 /** |
|
8033 * Processing communicates with the clock on your computer. |
|
8034 * The hour() function returns the current hour as a value from 0 - 23. |
|
8035 * |
|
8036 * @returns {float} The current hour. |
|
8037 * |
|
8038 * @see millis |
|
8039 * @see second |
|
8040 * @see minute |
|
8041 * @see month |
|
8042 * @see day |
|
8043 * @see year |
|
8044 */ |
|
8045 p.hour = function() { |
|
8046 return new Date().getHours(); |
|
8047 }; |
|
8048 /** |
|
8049 * Processing communicates with the clock on your computer. |
|
8050 * The minute() function returns the current minute as a value from 0 - 59. |
|
8051 * |
|
8052 * @returns {float} The current minute. |
|
8053 * |
|
8054 * @see millis |
|
8055 * @see second |
|
8056 * @see month |
|
8057 * @see hour |
|
8058 * @see day |
|
8059 * @see year |
|
8060 */ |
|
8061 p.minute = function() { |
|
8062 return new Date().getMinutes(); |
|
8063 }; |
|
8064 /** |
|
8065 * Processing communicates with the clock on your computer. |
|
8066 * The second() function returns the current second as a value from 0 - 59. |
|
8067 * |
|
8068 * @returns {float} The current minute. |
|
8069 * |
|
8070 * @see millis |
|
8071 * @see month |
|
8072 * @see minute |
|
8073 * @see hour |
|
8074 * @see day |
|
8075 * @see year |
|
8076 */ |
|
8077 p.second = function() { |
|
8078 return new Date().getSeconds(); |
|
8079 }; |
|
8080 /** |
|
8081 * Returns the number of milliseconds (thousandths of a second) since starting a sketch. |
|
8082 * This information is often used for timing animation sequences. |
|
8083 * |
|
8084 * @returns {long} The number of milliseconds since starting the sketch. |
|
8085 * |
|
8086 * @see month |
|
8087 * @see second |
|
8088 * @see minute |
|
8089 * @see hour |
|
8090 * @see day |
|
8091 * @see year |
|
8092 */ |
|
8093 p.millis = function() { |
|
8094 return Date.now() - start; |
|
8095 }; |
|
8096 |
|
8097 /** |
|
8098 * Executes the code within draw() one time. This functions allows the program to update |
|
8099 * the display window only when necessary, for example when an event registered by |
|
8100 * mousePressed() or keyPressed() occurs. |
|
8101 * In structuring a program, it only makes sense to call redraw() within events such as |
|
8102 * mousePressed(). This is because redraw() does not run draw() immediately (it only sets |
|
8103 * a flag that indicates an update is needed). |
|
8104 * Calling redraw() within draw() has no effect because draw() is continuously called anyway. |
|
8105 * |
|
8106 * @returns none |
|
8107 * |
|
8108 * @see noLoop |
|
8109 * @see loop |
|
8110 */ |
|
8111 function redrawHelper() { |
|
8112 var sec = (Date.now() - timeSinceLastFPS) / 1000; |
|
8113 framesSinceLastFPS++; |
|
8114 var fps = framesSinceLastFPS / sec; |
|
8115 |
|
8116 // recalculate FPS every half second for better accuracy. |
|
8117 if (sec > 0.5) { |
|
8118 timeSinceLastFPS = Date.now(); |
|
8119 framesSinceLastFPS = 0; |
|
8120 p.__frameRate = fps; |
|
8121 } |
|
8122 |
|
8123 p.frameCount++; |
|
8124 } |
|
8125 |
|
8126 Drawing2D.prototype.redraw = function() { |
|
8127 redrawHelper(); |
|
8128 |
|
8129 curContext.lineWidth = lineWidth; |
|
8130 var pmouseXLastEvent = p.pmouseX, |
|
8131 pmouseYLastEvent = p.pmouseY; |
|
8132 p.pmouseX = pmouseXLastFrame; |
|
8133 p.pmouseY = pmouseYLastFrame; |
|
8134 |
|
8135 saveContext(); |
|
8136 p.draw(); |
|
8137 restoreContext(); |
|
8138 |
|
8139 pmouseXLastFrame = p.mouseX; |
|
8140 pmouseYLastFrame = p.mouseY; |
|
8141 p.pmouseX = pmouseXLastEvent; |
|
8142 p.pmouseY = pmouseYLastEvent; |
|
8143 }; |
|
8144 |
|
8145 Drawing3D.prototype.redraw = function() { |
|
8146 redrawHelper(); |
|
8147 |
|
8148 var pmouseXLastEvent = p.pmouseX, |
|
8149 pmouseYLastEvent = p.pmouseY; |
|
8150 p.pmouseX = pmouseXLastFrame; |
|
8151 p.pmouseY = pmouseYLastFrame; |
|
8152 // even if the color buffer isn't cleared with background(), |
|
8153 // the depth buffer needs to be cleared regardless. |
|
8154 curContext.clear(curContext.DEPTH_BUFFER_BIT); |
|
8155 curContextCache = { attributes: {}, locations: {} }; |
|
8156 // Delete all the lighting states and the materials the |
|
8157 // user set in the last draw() call. |
|
8158 p.noLights(); |
|
8159 p.lightFalloff(1, 0, 0); |
|
8160 p.shininess(1); |
|
8161 p.ambient(255, 255, 255); |
|
8162 p.specular(0, 0, 0); |
|
8163 p.emissive(0, 0, 0); |
|
8164 p.camera(); |
|
8165 p.draw(); |
|
8166 |
|
8167 pmouseXLastFrame = p.mouseX; |
|
8168 pmouseYLastFrame = p.mouseY; |
|
8169 p.pmouseX = pmouseXLastEvent; |
|
8170 p.pmouseY = pmouseYLastEvent; |
|
8171 }; |
|
8172 |
|
8173 /** |
|
8174 * Stops Processing from continuously executing the code within draw(). If loop() is |
|
8175 * called, the code in draw() begin to run continuously again. If using noLoop() in |
|
8176 * setup(), it should be the last line inside the block. |
|
8177 * When noLoop() is used, it's not possible to manipulate or access the screen inside event |
|
8178 * handling functions such as mousePressed() or keyPressed(). Instead, use those functions |
|
8179 * to call redraw() or loop(), which will run draw(), which can update the screen properly. |
|
8180 * This means that when noLoop() has been called, no drawing can happen, and functions like |
|
8181 * saveFrame() or loadPixels() may not be used. |
|
8182 * Note that if the sketch is resized, redraw() will be called to update the sketch, even |
|
8183 * after noLoop() has been specified. Otherwise, the sketch would enter an odd state until |
|
8184 * loop() was called. |
|
8185 * |
|
8186 * @returns none |
|
8187 * |
|
8188 * @see redraw |
|
8189 * @see draw |
|
8190 * @see loop |
|
8191 */ |
|
8192 p.noLoop = function() { |
|
8193 doLoop = false; |
|
8194 loopStarted = false; |
|
8195 clearInterval(looping); |
|
8196 curSketch.onPause(); |
|
8197 }; |
|
8198 |
|
8199 /** |
|
8200 * Causes Processing to continuously execute the code within draw(). If noLoop() is called, |
|
8201 * the code in draw() stops executing. |
|
8202 * |
|
8203 * @returns none |
|
8204 * |
|
8205 * @see noLoop |
|
8206 */ |
|
8207 p.loop = function() { |
|
8208 if (loopStarted) { |
|
8209 return; |
|
8210 } |
|
8211 |
|
8212 timeSinceLastFPS = Date.now(); |
|
8213 framesSinceLastFPS = 0; |
|
8214 |
|
8215 looping = window.setInterval(function() { |
|
8216 try { |
|
8217 curSketch.onFrameStart(); |
|
8218 p.redraw(); |
|
8219 curSketch.onFrameEnd(); |
|
8220 } catch(e_loop) { |
|
8221 window.clearInterval(looping); |
|
8222 throw e_loop; |
|
8223 } |
|
8224 }, curMsPerFrame); |
|
8225 doLoop = true; |
|
8226 loopStarted = true; |
|
8227 curSketch.onLoop(); |
|
8228 }; |
|
8229 |
|
8230 /** |
|
8231 * Specifies the number of frames to be displayed every second. If the processor is not |
|
8232 * fast enough to maintain the specified rate, it will not be achieved. For example, the |
|
8233 * function call frameRate(30) will attempt to refresh 30 times a second. It is recommended |
|
8234 * to set the frame rate within setup(). The default rate is 60 frames per second. |
|
8235 * |
|
8236 * @param {int} aRate number of frames per second. |
|
8237 * |
|
8238 * @returns none |
|
8239 * |
|
8240 * @see delay |
|
8241 */ |
|
8242 p.frameRate = function(aRate) { |
|
8243 curFrameRate = aRate; |
|
8244 curMsPerFrame = 1000 / curFrameRate; |
|
8245 |
|
8246 // clear and reset interval |
|
8247 if (doLoop) { |
|
8248 p.noLoop(); |
|
8249 p.loop(); |
|
8250 } |
|
8251 }; |
|
8252 |
|
8253 //////////////////////////////////////////////////////////////////////////// |
|
8254 // JavaScript event binding and releasing |
|
8255 //////////////////////////////////////////////////////////////////////////// |
|
8256 |
|
8257 var eventHandlers = []; |
|
8258 |
|
8259 function attachEventHandler(elem, type, fn) { |
|
8260 if (elem.addEventListener) { |
|
8261 elem.addEventListener(type, fn, false); |
|
8262 } else { |
|
8263 elem.attachEvent("on" + type, fn); |
|
8264 } |
|
8265 eventHandlers.push({elem: elem, type: type, fn: fn}); |
|
8266 } |
|
8267 |
|
8268 function detachEventHandler(eventHandler) { |
|
8269 var elem = eventHandler.elem, |
|
8270 type = eventHandler.type, |
|
8271 fn = eventHandler.fn; |
|
8272 if (elem.removeEventListener) { |
|
8273 elem.removeEventListener(type, fn, false); |
|
8274 } else if (elem.detachEvent) { |
|
8275 elem.detachEvent("on" + type, fn); |
|
8276 } |
|
8277 } |
|
8278 |
|
8279 /** |
|
8280 * Quits/stops/exits the program. Programs without a draw() function exit automatically |
|
8281 * after the last line has run, but programs with draw() run continuously until the |
|
8282 * program is manually stopped or exit() is run. |
|
8283 * Rather than terminating immediately, exit() will cause the sketch to exit after draw() |
|
8284 * has completed (or after setup() completes if called during the setup() method). |
|
8285 * |
|
8286 * @returns none |
|
8287 */ |
|
8288 p.exit = function() { |
|
8289 window.clearInterval(looping); |
|
8290 |
|
8291 removeInstance(p.externals.canvas.id); |
|
8292 |
|
8293 // Step through the libraries to detach them |
|
8294 for (var lib in Processing.lib) { |
|
8295 if (Processing.lib.hasOwnProperty(lib)) { |
|
8296 if (Processing.lib[lib].hasOwnProperty("detach")) { |
|
8297 Processing.lib[lib].detach(p); |
|
8298 } |
|
8299 } |
|
8300 } |
|
8301 |
|
8302 var i = eventHandlers.length; |
|
8303 while (i--) { |
|
8304 detachEventHandler(eventHandlers[i]); |
|
8305 } |
|
8306 curSketch.onExit(); |
|
8307 }; |
|
8308 |
|
8309 //////////////////////////////////////////////////////////////////////////// |
|
8310 // MISC functions |
|
8311 //////////////////////////////////////////////////////////////////////////// |
|
8312 |
|
8313 /** |
|
8314 * Sets the cursor to a predefined symbol, an image, or turns it on if already hidden. |
|
8315 * If you are trying to set an image as the cursor, it is recommended to make the size |
|
8316 * 16x16 or 32x32 pixels. It is not possible to load an image as the cursor if you are |
|
8317 * exporting your program for the Web. The values for parameters x and y must be less |
|
8318 * than the dimensions of the image. |
|
8319 * |
|
8320 * @param {MODE} MODE either ARROW, CROSS, HAND, MOVE, TEXT, WAIT |
|
8321 * @param {PImage} image any variable of type PImage |
|
8322 * @param {int} x the horizonal active spot of the cursor |
|
8323 * @param {int} y the vertical active spot of the cursor |
|
8324 * |
|
8325 * @returns none |
|
8326 * |
|
8327 * @see noCursor |
|
8328 */ |
|
8329 p.cursor = function() { |
|
8330 if (arguments.length > 1 || (arguments.length === 1 && arguments[0] instanceof p.PImage)) { |
|
8331 var image = arguments[0], |
|
8332 x, y; |
|
8333 if (arguments.length >= 3) { |
|
8334 x = arguments[1]; |
|
8335 y = arguments[2]; |
|
8336 if (x < 0 || y < 0 || y >= image.height || x >= image.width) { |
|
8337 throw "x and y must be non-negative and less than the dimensions of the image"; |
|
8338 } |
|
8339 } else { |
|
8340 x = image.width >>> 1; |
|
8341 y = image.height >>> 1; |
|
8342 } |
|
8343 |
|
8344 // see https://developer.mozilla.org/en/Using_URL_values_for_the_cursor_property |
|
8345 var imageDataURL = image.toDataURL(); |
|
8346 var style = "url(\"" + imageDataURL + "\") " + x + " " + y + ", default"; |
|
8347 curCursor = curElement.style.cursor = style; |
|
8348 } else if (arguments.length === 1) { |
|
8349 var mode = arguments[0]; |
|
8350 curCursor = curElement.style.cursor = mode; |
|
8351 } else { |
|
8352 curCursor = curElement.style.cursor = oldCursor; |
|
8353 } |
|
8354 }; |
|
8355 |
|
8356 /** |
|
8357 * Hides the cursor from view. |
|
8358 * |
|
8359 * @returns none |
|
8360 * |
|
8361 * @see cursor |
|
8362 */ |
|
8363 p.noCursor = function() { |
|
8364 curCursor = curElement.style.cursor = PConstants.NOCURSOR; |
|
8365 }; |
|
8366 |
|
8367 /** |
|
8368 * Links to a webpage either in the same window or in a new window. The complete URL |
|
8369 * must be specified. |
|
8370 * |
|
8371 * @param {String} href complete url as a String in quotes |
|
8372 * @param {String} target name of the window to load the URL as a string in quotes |
|
8373 * |
|
8374 * @returns none |
|
8375 */ |
|
8376 p.link = function(href, target) { |
|
8377 if (target !== undef) { |
|
8378 window.open(href, target); |
|
8379 } else { |
|
8380 window.location = href; |
|
8381 } |
|
8382 }; |
|
8383 |
|
8384 // PGraphics methods |
|
8385 // These functions exist only for compatibility with P5 |
|
8386 p.beginDraw = nop; |
|
8387 p.endDraw = nop; |
|
8388 |
|
8389 /** |
|
8390 * This function takes content from a canvas and turns it into an ImageData object to be used with a PImage |
|
8391 * |
|
8392 * @returns {ImageData} ImageData object to attach to a PImage (1D array of pixel data) |
|
8393 * |
|
8394 * @see PImage |
|
8395 */ |
|
8396 Drawing2D.prototype.toImageData = function(x, y, w, h) { |
|
8397 x = x !== undef ? x : 0; |
|
8398 y = y !== undef ? y : 0; |
|
8399 w = w !== undef ? w : p.width; |
|
8400 h = h !== undef ? h : p.height; |
|
8401 return curContext.getImageData(x, y, w, h); |
|
8402 }; |
|
8403 |
|
8404 Drawing3D.prototype.toImageData = function(x, y, w, h) { |
|
8405 x = x !== undef ? x : 0; |
|
8406 y = y !== undef ? y : 0; |
|
8407 w = w !== undef ? w : p.width; |
|
8408 h = h !== undef ? h : p.height; |
|
8409 var c = document.createElement("canvas"), |
|
8410 ctx = c.getContext("2d"), |
|
8411 obj = ctx.createImageData(w, h), |
|
8412 uBuff = new Uint8Array(w * h * 4); |
|
8413 curContext.readPixels(x, y, w, h, curContext.RGBA, curContext.UNSIGNED_BYTE, uBuff); |
|
8414 for (var i=0, ul=uBuff.length, obj_data=obj.data; i < ul; i++) { |
|
8415 obj_data[i] = uBuff[(h - 1 - Math.floor(i / 4 / w)) * w * 4 + (i % (w * 4))]; |
|
8416 } |
|
8417 return obj; |
|
8418 }; |
|
8419 |
|
8420 /** |
|
8421 * Displays message in the browser's status area. This is the text area in the lower |
|
8422 * left corner of the browser. The status() function will only work when the |
|
8423 * Processing program is running in a web browser. |
|
8424 * |
|
8425 * @param {String} text any valid String |
|
8426 * |
|
8427 * @returns none |
|
8428 */ |
|
8429 p.status = function(text) { |
|
8430 window.status = text; |
|
8431 }; |
|
8432 |
|
8433 //////////////////////////////////////////////////////////////////////////// |
|
8434 // Binary Functions |
|
8435 //////////////////////////////////////////////////////////////////////////// |
|
8436 |
|
8437 /** |
|
8438 * Converts a byte, char, int, or color to a String containing the equivalent binary |
|
8439 * notation. For example color(0, 102, 153, 255) will convert to the String |
|
8440 * "11111111000000000110011010011001". This function can help make your geeky debugging |
|
8441 * sessions much happier. |
|
8442 * |
|
8443 * @param {byte|char|int|color} num byte, char, int, color: value to convert |
|
8444 * @param {int} numBits number of digits to return |
|
8445 * |
|
8446 * @returns {String} |
|
8447 * |
|
8448 * @see unhex |
|
8449 * @see hex |
|
8450 * @see unbinary |
|
8451 */ |
|
8452 p.binary = function(num, numBits) { |
|
8453 var bit; |
|
8454 if (numBits > 0) { |
|
8455 bit = numBits; |
|
8456 } else if(num instanceof Char) { |
|
8457 bit = 16; |
|
8458 num |= 0; // making it int |
|
8459 } else { |
|
8460 // autodetect, skipping zeros |
|
8461 bit = 32; |
|
8462 while (bit > 1 && !((num >>> (bit - 1)) & 1)) { |
|
8463 bit--; |
|
8464 } |
|
8465 } |
|
8466 var result = ""; |
|
8467 while (bit > 0) { |
|
8468 result += ((num >>> (--bit)) & 1) ? "1" : "0"; |
|
8469 } |
|
8470 return result; |
|
8471 }; |
|
8472 |
|
8473 /** |
|
8474 * Converts a String representation of a binary number to its equivalent integer value. |
|
8475 * For example, unbinary("00001000") will return 8. |
|
8476 * |
|
8477 * @param {String} binaryString String |
|
8478 * |
|
8479 * @returns {Int} |
|
8480 * |
|
8481 * @see hex |
|
8482 * @see binary |
|
8483 * @see unbinary |
|
8484 */ |
|
8485 p.unbinary = function(binaryString) { |
|
8486 var i = binaryString.length - 1, mask = 1, result = 0; |
|
8487 while (i >= 0) { |
|
8488 var ch = binaryString[i--]; |
|
8489 if (ch !== '0' && ch !== '1') { |
|
8490 throw "the value passed into unbinary was not an 8 bit binary number"; |
|
8491 } |
|
8492 if (ch === '1') { |
|
8493 result += mask; |
|
8494 } |
|
8495 mask <<= 1; |
|
8496 } |
|
8497 return result; |
|
8498 }; |
|
8499 |
|
8500 /** |
|
8501 * Number-to-String formatting function. Prepends "plus" or "minus" depending |
|
8502 * on whether the value is positive or negative, respectively, after padding |
|
8503 * the value with zeroes on the left and right, the number of zeroes used dictated |
|
8504 * by the values 'leftDigits' and 'rightDigits'. 'value' cannot be an array. |
|
8505 * |
|
8506 * @param {int|float} value the number to format |
|
8507 * @param {String} plus the prefix for positive numbers |
|
8508 * @param {String} minus the prefix for negative numbers |
|
8509 * @param {int} left number of digits to the left of the decimal point |
|
8510 * @param {int} right number of digits to the right of the decimal point |
|
8511 * @param {String} group string delimited for groups, such as the comma in "1,000" |
|
8512 * |
|
8513 * @returns {String or String[]} |
|
8514 * |
|
8515 * @see nfCore |
|
8516 */ |
|
8517 function nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group) { |
|
8518 var sign = (value < 0) ? minus : plus; |
|
8519 var autoDetectDecimals = rightDigits === 0; |
|
8520 var rightDigitsOfDefault = (rightDigits === undef || rightDigits < 0) ? 0 : rightDigits; |
|
8521 |
|
8522 var absValue = Math.abs(value); |
|
8523 if (autoDetectDecimals) { |
|
8524 rightDigitsOfDefault = 1; |
|
8525 absValue *= 10; |
|
8526 while (Math.abs(Math.round(absValue) - absValue) > 1e-6 && rightDigitsOfDefault < 7) { |
|
8527 ++rightDigitsOfDefault; |
|
8528 absValue *= 10; |
|
8529 } |
|
8530 } else if (rightDigitsOfDefault !== 0) { |
|
8531 absValue *= Math.pow(10, rightDigitsOfDefault); |
|
8532 } |
|
8533 |
|
8534 // Using Java's default rounding policy HALF_EVEN. This policy is based |
|
8535 // on the idea that 0.5 values round to the nearest even number, and |
|
8536 // everything else is rounded normally. |
|
8537 var number, doubled = absValue * 2; |
|
8538 if (Math.floor(absValue) === absValue) { |
|
8539 number = absValue; |
|
8540 } else if (Math.floor(doubled) === doubled) { |
|
8541 var floored = Math.floor(absValue); |
|
8542 number = floored + (floored % 2); |
|
8543 } else { |
|
8544 number = Math.round(absValue); |
|
8545 } |
|
8546 |
|
8547 var buffer = ""; |
|
8548 var totalDigits = leftDigits + rightDigitsOfDefault; |
|
8549 while (totalDigits > 0 || number > 0) { |
|
8550 totalDigits--; |
|
8551 buffer = "" + (number % 10) + buffer; |
|
8552 number = Math.floor(number / 10); |
|
8553 } |
|
8554 if (group !== undef) { |
|
8555 var i = buffer.length - 3 - rightDigitsOfDefault; |
|
8556 while(i > 0) { |
|
8557 buffer = buffer.substring(0,i) + group + buffer.substring(i); |
|
8558 i-=3; |
|
8559 } |
|
8560 } |
|
8561 if (rightDigitsOfDefault > 0) { |
|
8562 return sign + buffer.substring(0, buffer.length - rightDigitsOfDefault) + |
|
8563 "." + buffer.substring(buffer.length - rightDigitsOfDefault, buffer.length); |
|
8564 } |
|
8565 return sign + buffer; |
|
8566 } |
|
8567 |
|
8568 /** |
|
8569 * Number-to-String formatting function. Prepends "plus" or "minus" depending |
|
8570 * on whether the value is positive or negative, respectively, after padding |
|
8571 * the value with zeroes on the left and right, the number of zeroes used dictated |
|
8572 * by the values 'leftDigits' and 'rightDigits'. 'value' can be an array; |
|
8573 * if the input is an array, each value in it is formatted separately, and |
|
8574 * an array with formatted values is returned. |
|
8575 * |
|
8576 * @param {int|int[]|float|float[]} value the number(s) to format |
|
8577 * @param {String} plus the prefix for positive numbers |
|
8578 * @param {String} minus the prefix for negative numbers |
|
8579 * @param {int} left number of digits to the left of the decimal point |
|
8580 * @param {int} right number of digits to the right of the decimal point |
|
8581 * @param {String} group string delimited for groups, such as the comma in "1,000" |
|
8582 * |
|
8583 * @returns {String or String[]} |
|
8584 * |
|
8585 * @see nfCoreScalar |
|
8586 */ |
|
8587 function nfCore(value, plus, minus, leftDigits, rightDigits, group) { |
|
8588 if (value instanceof Array) { |
|
8589 var arr = []; |
|
8590 for (var i = 0, len = value.length; i < len; i++) { |
|
8591 arr.push(nfCoreScalar(value[i], plus, minus, leftDigits, rightDigits, group)); |
|
8592 } |
|
8593 return arr; |
|
8594 } |
|
8595 return nfCoreScalar(value, plus, minus, leftDigits, rightDigits, group); |
|
8596 } |
|
8597 |
|
8598 /** |
|
8599 * Utility function for formatting numbers into strings. There are two versions, one for |
|
8600 * formatting floats and one for formatting ints. The values for the digits, left, and |
|
8601 * right parameters should always be positive integers. |
|
8602 * As shown in the above example, nf() is used to add zeros to the left and/or right |
|
8603 * of a number. This is typically for aligning a list of numbers. To remove digits from |
|
8604 * a floating-point number, use the int(), ceil(), floor(), or round() functions. |
|
8605 * |
|
8606 * @param {int|int[]|float|float[]} value the number(s) to format |
|
8607 * @param {int} left number of digits to the left of the decimal point |
|
8608 * @param {int} right number of digits to the right of the decimal point |
|
8609 * |
|
8610 * @returns {String or String[]} |
|
8611 * |
|
8612 * @see nfs |
|
8613 * @see nfp |
|
8614 * @see nfc |
|
8615 */ |
|
8616 p.nf = function(value, leftDigits, rightDigits) { return nfCore(value, "", "-", leftDigits, rightDigits); }; |
|
8617 |
|
8618 /** |
|
8619 * Utility function for formatting numbers into strings. Similar to nf() but leaves a blank space in front |
|
8620 * of positive numbers so they align with negative numbers in spite of the minus symbol. There are two |
|
8621 * versions, one for formatting floats and one for formatting ints. The values for the digits, left, |
|
8622 * and right parameters should always be positive integers. |
|
8623 * |
|
8624 * @param {int|int[]|float|float[]} value the number(s) to format |
|
8625 * @param {int} left number of digits to the left of the decimal point |
|
8626 * @param {int} right number of digits to the right of the decimal point |
|
8627 * |
|
8628 * @returns {String or String[]} |
|
8629 * |
|
8630 * @see nf |
|
8631 * @see nfp |
|
8632 * @see nfc |
|
8633 */ |
|
8634 p.nfs = function(value, leftDigits, rightDigits) { return nfCore(value, " ", "-", leftDigits, rightDigits); }; |
|
8635 |
|
8636 /** |
|
8637 * Utility function for formatting numbers into strings. Similar to nf() but puts a "+" in front of |
|
8638 * positive numbers and a "-" in front of negative numbers. There are two versions, one for formatting |
|
8639 * floats and one for formatting ints. The values for the digits, left, and right parameters should |
|
8640 * always be positive integers. |
|
8641 * |
|
8642 * @param {int|int[]|float|float[]} value the number(s) to format |
|
8643 * @param {int} left number of digits to the left of the decimal point |
|
8644 * @param {int} right number of digits to the right of the decimal point |
|
8645 * |
|
8646 * @returns {String or String[]} |
|
8647 * |
|
8648 * @see nfs |
|
8649 * @see nf |
|
8650 * @see nfc |
|
8651 */ |
|
8652 p.nfp = function(value, leftDigits, rightDigits) { return nfCore(value, "+", "-", leftDigits, rightDigits); }; |
|
8653 |
|
8654 /** |
|
8655 * Utility function for formatting numbers into strings and placing appropriate commas to mark |
|
8656 * units of 1000. There are two versions, one for formatting ints and one for formatting an array |
|
8657 * of ints. The value for the digits parameter should always be a positive integer. |
|
8658 * |
|
8659 * @param {int|int[]|float|float[]} value the number(s) to format |
|
8660 * @param {int} left number of digits to the left of the decimal point |
|
8661 * @param {int} right number of digits to the right of the decimal point |
|
8662 * |
|
8663 * @returns {String or String[]} |
|
8664 * |
|
8665 * @see nf |
|
8666 * @see nfs |
|
8667 * @see nfp |
|
8668 */ |
|
8669 p.nfc = function(value, leftDigits, rightDigits) { return nfCore(value, "", "-", leftDigits, rightDigits, ","); }; |
|
8670 |
|
8671 var decimalToHex = function(d, padding) { |
|
8672 //if there is no padding value added, default padding to 8 else go into while statement. |
|
8673 padding = (padding === undef || padding === null) ? padding = 8 : padding; |
|
8674 if (d < 0) { |
|
8675 d = 0xFFFFFFFF + d + 1; |
|
8676 } |
|
8677 var hex = Number(d).toString(16).toUpperCase(); |
|
8678 while (hex.length < padding) { |
|
8679 hex = "0" + hex; |
|
8680 } |
|
8681 if (hex.length >= padding) { |
|
8682 hex = hex.substring(hex.length - padding, hex.length); |
|
8683 } |
|
8684 return hex; |
|
8685 }; |
|
8686 |
|
8687 // note: since we cannot keep track of byte, int types by default the returned string is 8 chars long |
|
8688 // if no 2nd argument is passed. closest compromise we can use to match java implementation Feb 5 2010 |
|
8689 // also the char parser has issues with chars that are not digits or letters IE: !@#$%^&* |
|
8690 /** |
|
8691 * Converts a byte, char, int, or color to a String containing the equivalent hexadecimal notation. |
|
8692 * For example color(0, 102, 153, 255) will convert to the String "FF006699". This function can help |
|
8693 * make your geeky debugging sessions much happier. |
|
8694 * |
|
8695 * @param {byte|char|int|Color} value the value to turn into a hex string |
|
8696 * @param {int} digits the number of digits to return |
|
8697 * |
|
8698 * @returns {String} |
|
8699 * |
|
8700 * @see unhex |
|
8701 * @see binary |
|
8702 * @see unbinary |
|
8703 */ |
|
8704 p.hex = function(value, len) { |
|
8705 if (arguments.length === 1) { |
|
8706 if (value instanceof Char) { |
|
8707 len = 4; |
|
8708 } else { // int or byte, indistinguishable at the moment, default to 8 |
|
8709 len = 8; |
|
8710 } |
|
8711 } |
|
8712 return decimalToHex(value, len); |
|
8713 }; |
|
8714 |
|
8715 function unhexScalar(hex) { |
|
8716 var value = parseInt("0x" + hex, 16); |
|
8717 |
|
8718 // correct for int overflow java expectation |
|
8719 if (value > 2147483647) { |
|
8720 value -= 4294967296; |
|
8721 } |
|
8722 return value; |
|
8723 } |
|
8724 |
|
8725 /** |
|
8726 * Converts a String representation of a hexadecimal number to its equivalent integer value. |
|
8727 * |
|
8728 * @param {String} hex the hex string to convert to an int |
|
8729 * |
|
8730 * @returns {int} |
|
8731 * |
|
8732 * @see hex |
|
8733 * @see binary |
|
8734 * @see unbinary |
|
8735 */ |
|
8736 p.unhex = function(hex) { |
|
8737 if (hex instanceof Array) { |
|
8738 var arr = []; |
|
8739 for (var i = 0; i < hex.length; i++) { |
|
8740 arr.push(unhexScalar(hex[i])); |
|
8741 } |
|
8742 return arr; |
|
8743 } |
|
8744 return unhexScalar(hex); |
|
8745 }; |
|
8746 |
|
8747 // Load a file or URL into strings |
|
8748 /** |
|
8749 * Reads the contents of a file or url and creates a String array of its individual lines. |
|
8750 * The filename parameter can also be a URL to a file found online. If the file is not available or an error occurs, |
|
8751 * null will be returned and an error message will be printed to the console. The error message does not halt |
|
8752 * the program. |
|
8753 * |
|
8754 * @param {String} filename name of the file or url to load |
|
8755 * |
|
8756 * @returns {String[]} |
|
8757 * |
|
8758 * @see loadBytes |
|
8759 * @see saveStrings |
|
8760 * @see saveBytes |
|
8761 */ |
|
8762 p.loadStrings = function(filename) { |
|
8763 if (localStorage[filename]) { |
|
8764 return localStorage[filename].split("\n"); |
|
8765 } |
|
8766 |
|
8767 var filecontent = ajax(filename); |
|
8768 if(typeof filecontent !== "string" || filecontent === "") { |
|
8769 return []; |
|
8770 } |
|
8771 |
|
8772 // deal with the fact that Windows uses \r\n, Unix uses \n, |
|
8773 // Mac uses \r, and we actually expect \n |
|
8774 filecontent = filecontent.replace(/(\r\n?)/g,"\n").replace(/\n$/,""); |
|
8775 |
|
8776 return filecontent.split("\n"); |
|
8777 }; |
|
8778 |
|
8779 // Writes an array of strings to a file, one line per string |
|
8780 /** |
|
8781 * Writes an array of strings to a file, one line per string. This file is saved to the localStorage. |
|
8782 * |
|
8783 * @param {String} filename name of the file to save to localStorage |
|
8784 * @param {String[]} strings string array to be written |
|
8785 * |
|
8786 * @see loadBytes |
|
8787 * @see loadStrings |
|
8788 * @see saveBytes |
|
8789 */ |
|
8790 p.saveStrings = function(filename, strings) { |
|
8791 localStorage[filename] = strings.join('\n'); |
|
8792 }; |
|
8793 |
|
8794 /** |
|
8795 * Reads the contents of a file or url and places it in a byte array. If a file is specified, it must be located in the localStorage. |
|
8796 * The filename parameter can also be a URL to a file found online. |
|
8797 * |
|
8798 * @param {String} filename name of a file in the localStorage or a URL. |
|
8799 * |
|
8800 * @returns {byte[]} |
|
8801 * |
|
8802 * @see loadStrings |
|
8803 * @see saveStrings |
|
8804 * @see saveBytes |
|
8805 */ |
|
8806 p.loadBytes = function(url) { |
|
8807 var string = ajax(url); |
|
8808 var ret = []; |
|
8809 |
|
8810 for (var i = 0; i < string.length; i++) { |
|
8811 ret.push(string.charCodeAt(i)); |
|
8812 } |
|
8813 |
|
8814 return ret; |
|
8815 }; |
|
8816 |
|
8817 /** |
|
8818 * Removes the first argument from the arguments set -- shifts. |
|
8819 * |
|
8820 * @param {Arguments} args The Arguments object. |
|
8821 * |
|
8822 * @return {Object[]} Returns an array of arguments except first one. |
|
8823 * |
|
8824 * @see #match |
|
8825 */ |
|
8826 function removeFirstArgument(args) { |
|
8827 return Array.prototype.slice.call(args, 1); |
|
8828 } |
|
8829 |
|
8830 //////////////////////////////////////////////////////////////////////////// |
|
8831 // String Functions |
|
8832 //////////////////////////////////////////////////////////////////////////// |
|
8833 /** |
|
8834 * The matchAll() function is identical to match(), except that it returns an array of all matches in |
|
8835 * the specified String, rather than just the first. |
|
8836 * |
|
8837 * @param {String} aString the String to search inside |
|
8838 * @param {String} aRegExp the regexp to be used for matching |
|
8839 * |
|
8840 * @return {String[]} returns an array of matches |
|
8841 * |
|
8842 * @see #match |
|
8843 */ |
|
8844 p.matchAll = function(aString, aRegExp) { |
|
8845 var results = [], |
|
8846 latest; |
|
8847 var regexp = new RegExp(aRegExp, "g"); |
|
8848 while ((latest = regexp.exec(aString)) !== null) { |
|
8849 results.push(latest); |
|
8850 if (latest[0].length === 0) { |
|
8851 ++regexp.lastIndex; |
|
8852 } |
|
8853 } |
|
8854 return results.length > 0 ? results : null; |
|
8855 }; |
|
8856 /** |
|
8857 * The contains(string) function returns true if the string passed in the parameter |
|
8858 * is a substring of this string. It returns false if the string passed |
|
8859 * in the parameter is not a substring of this string. |
|
8860 * |
|
8861 * @param {String} The string to look for in the current string |
|
8862 * |
|
8863 * @return {boolean} returns true if this string contains |
|
8864 * the string passed as parameter. returns false, otherwise. |
|
8865 * |
|
8866 */ |
|
8867 p.__contains = function (subject, subStr) { |
|
8868 if (typeof subject !== "string") { |
|
8869 return subject.contains.apply(subject, removeFirstArgument(arguments)); |
|
8870 } |
|
8871 //Parameter is not null AND |
|
8872 //The type of the parameter is the same as this object (string) |
|
8873 //The javascript function that finds a substring returns 0 or higher |
|
8874 return ( |
|
8875 (subject !== null) && |
|
8876 (subStr !== null) && |
|
8877 (typeof subStr === "string") && |
|
8878 (subject.indexOf(subStr) > -1) |
|
8879 ); |
|
8880 }; |
|
8881 /** |
|
8882 * The __replaceAll() function searches all matches between a substring (or regular expression) and a string, |
|
8883 * and replaces the matched substring with a new substring |
|
8884 * |
|
8885 * @param {String} subject a substring |
|
8886 * @param {String} regex a substring or a regular expression |
|
8887 * @param {String} replace the string to replace the found value |
|
8888 * |
|
8889 * @return {String} returns result |
|
8890 * |
|
8891 * @see #match |
|
8892 */ |
|
8893 p.__replaceAll = function(subject, regex, replacement) { |
|
8894 if (typeof subject !== "string") { |
|
8895 return subject.replaceAll.apply(subject, removeFirstArgument(arguments)); |
|
8896 } |
|
8897 |
|
8898 return subject.replace(new RegExp(regex, "g"), replacement); |
|
8899 }; |
|
8900 /** |
|
8901 * The __replaceFirst() function searches first matche between a substring (or regular expression) and a string, |
|
8902 * and replaces the matched substring with a new substring |
|
8903 * |
|
8904 * @param {String} subject a substring |
|
8905 * @param {String} regex a substring or a regular expression |
|
8906 * @param {String} replace the string to replace the found value |
|
8907 * |
|
8908 * @return {String} returns result |
|
8909 * |
|
8910 * @see #match |
|
8911 */ |
|
8912 p.__replaceFirst = function(subject, regex, replacement) { |
|
8913 if (typeof subject !== "string") { |
|
8914 return subject.replaceFirst.apply(subject, removeFirstArgument(arguments)); |
|
8915 } |
|
8916 |
|
8917 return subject.replace(new RegExp(regex, ""), replacement); |
|
8918 }; |
|
8919 /** |
|
8920 * The __replace() function searches all matches between a substring and a string, |
|
8921 * and replaces the matched substring with a new substring |
|
8922 * |
|
8923 * @param {String} subject a substring |
|
8924 * @param {String} what a substring to find |
|
8925 * @param {String} replacement the string to replace the found value |
|
8926 * |
|
8927 * @return {String} returns result |
|
8928 */ |
|
8929 p.__replace = function(subject, what, replacement) { |
|
8930 if (typeof subject !== "string") { |
|
8931 return subject.replace.apply(subject, removeFirstArgument(arguments)); |
|
8932 } |
|
8933 if (what instanceof RegExp) { |
|
8934 return subject.replace(what, replacement); |
|
8935 } |
|
8936 |
|
8937 if (typeof what !== "string") { |
|
8938 what = what.toString(); |
|
8939 } |
|
8940 if (what === "") { |
|
8941 return subject; |
|
8942 } |
|
8943 |
|
8944 var i = subject.indexOf(what); |
|
8945 if (i < 0) { |
|
8946 return subject; |
|
8947 } |
|
8948 |
|
8949 var j = 0, result = ""; |
|
8950 do { |
|
8951 result += subject.substring(j, i) + replacement; |
|
8952 j = i + what.length; |
|
8953 } while ( (i = subject.indexOf(what, j)) >= 0); |
|
8954 return result + subject.substring(j); |
|
8955 }; |
|
8956 /** |
|
8957 * The __equals() function compares two strings (or objects) to see if they are the same. |
|
8958 * This method is necessary because it's not possible to compare strings using the equality operator (==). |
|
8959 * Returns true if the strings are the same and false if they are not. |
|
8960 * |
|
8961 * @param {String} subject a string used for comparison |
|
8962 * @param {String} other a string used for comparison with |
|
8963 * |
|
8964 * @return {boolean} true is the strings are the same false otherwise |
|
8965 */ |
|
8966 p.__equals = function(subject, other) { |
|
8967 if (subject.equals instanceof Function) { |
|
8968 return subject.equals.apply(subject, removeFirstArgument(arguments)); |
|
8969 } |
|
8970 |
|
8971 // TODO use virtEquals for HashMap here |
|
8972 return subject.valueOf() === other.valueOf(); |
|
8973 }; |
|
8974 /** |
|
8975 * The __equalsIgnoreCase() function compares two strings to see if they are the same. |
|
8976 * Returns true if the strings are the same, either when forced to all lower case or |
|
8977 * all upper case. |
|
8978 * |
|
8979 * @param {String} subject a string used for comparison |
|
8980 * @param {String} other a string used for comparison with |
|
8981 * |
|
8982 * @return {boolean} true is the strings are the same, ignoring case. false otherwise |
|
8983 */ |
|
8984 p.__equalsIgnoreCase = function(subject, other) { |
|
8985 if (typeof subject !== "string") { |
|
8986 return subject.equalsIgnoreCase.apply(subject, removeFirstArgument(arguments)); |
|
8987 } |
|
8988 |
|
8989 return subject.toLowerCase() === other.toLowerCase(); |
|
8990 }; |
|
8991 /** |
|
8992 * The __toCharArray() function splits the string into a char array. |
|
8993 * |
|
8994 * @param {String} subject The string |
|
8995 * |
|
8996 * @return {Char[]} a char array |
|
8997 */ |
|
8998 p.__toCharArray = function(subject) { |
|
8999 if (typeof subject !== "string") { |
|
9000 return subject.toCharArray.apply(subject, removeFirstArgument(arguments)); |
|
9001 } |
|
9002 |
|
9003 var chars = []; |
|
9004 for (var i = 0, len = subject.length; i < len; ++i) { |
|
9005 chars[i] = new Char(subject.charAt(i)); |
|
9006 } |
|
9007 return chars; |
|
9008 }; |
|
9009 /** |
|
9010 * The __split() function splits a string using the regex delimiter |
|
9011 * specified. If limit is specified, the resultant array will have number |
|
9012 * of elements equal to or less than the limit. |
|
9013 * |
|
9014 * @param {String} subject string to be split |
|
9015 * @param {String} regexp regex string used to split the subject |
|
9016 * @param {int} limit max number of tokens to be returned |
|
9017 * |
|
9018 * @return {String[]} an array of tokens from the split string |
|
9019 */ |
|
9020 p.__split = function(subject, regex, limit) { |
|
9021 if (typeof subject !== "string") { |
|
9022 return subject.split.apply(subject, removeFirstArgument(arguments)); |
|
9023 } |
|
9024 |
|
9025 var pattern = new RegExp(regex); |
|
9026 |
|
9027 // If limit is not specified, use JavaScript's built-in String.split. |
|
9028 if ((limit === undef) || (limit < 1)) { |
|
9029 return subject.split(pattern); |
|
9030 } |
|
9031 |
|
9032 // If limit is specified, JavaScript's built-in String.split has a |
|
9033 // different behaviour than Java's. A Java-compatible implementation is |
|
9034 // provided here. |
|
9035 var result = [], currSubject = subject, pos; |
|
9036 while (((pos = currSubject.search(pattern)) !== -1) |
|
9037 && (result.length < (limit - 1))) { |
|
9038 var match = pattern.exec(currSubject).toString(); |
|
9039 result.push(currSubject.substring(0, pos)); |
|
9040 currSubject = currSubject.substring(pos + match.length); |
|
9041 } |
|
9042 if ((pos !== -1) || (currSubject !== "")) { |
|
9043 result.push(currSubject); |
|
9044 } |
|
9045 return result; |
|
9046 }; |
|
9047 /** |
|
9048 * The codePointAt() function returns the unicode value of the character at a given index of a string. |
|
9049 * |
|
9050 * @param {int} idx the index of the character |
|
9051 * |
|
9052 * @return {String} code the String containing the unicode value of the character |
|
9053 */ |
|
9054 p.__codePointAt = function(subject, idx) { |
|
9055 var code = subject.charCodeAt(idx), |
|
9056 hi, |
|
9057 low; |
|
9058 if (0xD800 <= code && code <= 0xDBFF) { |
|
9059 hi = code; |
|
9060 low = subject.charCodeAt(idx + 1); |
|
9061 return ((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000; |
|
9062 } |
|
9063 return code; |
|
9064 }; |
|
9065 /** |
|
9066 * The match() function matches a string with a regular expression, and returns the match as an |
|
9067 * array. The first index is the matching expression, and array elements |
|
9068 * [1] and higher represent each of the groups (sequences found in parens). |
|
9069 * |
|
9070 * @param {String} str the String to be searched |
|
9071 * @param {String} regexp the regexp to be used for matching |
|
9072 * |
|
9073 * @return {String[]} an array of matching strings |
|
9074 */ |
|
9075 p.match = function(str, regexp) { |
|
9076 return str.match(regexp); |
|
9077 }; |
|
9078 /** |
|
9079 * The startsWith() function tests if a string starts with the specified prefix. If the prefix |
|
9080 * is the empty String or equal to the subject String, startsWith() will also return true. |
|
9081 * |
|
9082 * @param {String} prefix the String used to compare against the start of the subject String. |
|
9083 * @param {int} toffset (optional) an offset into the subject String where searching should begin. |
|
9084 * |
|
9085 * @return {boolean} true if the subject String starts with the prefix. |
|
9086 */ |
|
9087 p.__startsWith = function(subject, prefix, toffset) { |
|
9088 if (typeof subject !== "string") { |
|
9089 return subject.startsWith.apply(subject, removeFirstArgument(arguments)); |
|
9090 } |
|
9091 |
|
9092 toffset = toffset || 0; |
|
9093 if (toffset < 0 || toffset > subject.length) { |
|
9094 return false; |
|
9095 } |
|
9096 return (prefix === '' || prefix === subject) ? true : (subject.indexOf(prefix) === toffset); |
|
9097 }; |
|
9098 /** |
|
9099 * The endsWith() function tests if a string ends with the specified suffix. If the suffix |
|
9100 * is the empty String, endsWith() will also return true. |
|
9101 * |
|
9102 * @param {String} suffix the String used to compare against the end of the subject String. |
|
9103 * |
|
9104 * @return {boolean} true if the subject String starts with the prefix. |
|
9105 */ |
|
9106 p.__endsWith = function(subject, suffix) { |
|
9107 if (typeof subject !== "string") { |
|
9108 return subject.endsWith.apply(subject, removeFirstArgument(arguments)); |
|
9109 } |
|
9110 |
|
9111 var suffixLen = suffix ? suffix.length : 0; |
|
9112 return (suffix === '' || suffix === subject) ? true : |
|
9113 (subject.indexOf(suffix) === subject.length - suffixLen); |
|
9114 }; |
|
9115 |
|
9116 //////////////////////////////////////////////////////////////////////////// |
|
9117 // Other java specific functions |
|
9118 //////////////////////////////////////////////////////////////////////////// |
|
9119 |
|
9120 /** |
|
9121 * The returns hash code of the. |
|
9122 * |
|
9123 * @param {Object} subject The string |
|
9124 * |
|
9125 * @return {int} a hash code |
|
9126 */ |
|
9127 p.__hashCode = function(subject) { |
|
9128 if (subject.hashCode instanceof Function) { |
|
9129 return subject.hashCode.apply(subject, removeFirstArgument(arguments)); |
|
9130 } |
|
9131 return virtHashCode(subject); |
|
9132 }; |
|
9133 /** |
|
9134 * The __printStackTrace() prints stack trace to the console. |
|
9135 * |
|
9136 * @param {Exception} subject The error |
|
9137 */ |
|
9138 p.__printStackTrace = function(subject) { |
|
9139 p.println("Exception: " + subject.toString() ); |
|
9140 }; |
|
9141 |
|
9142 var logBuffer = []; |
|
9143 |
|
9144 /** |
|
9145 * The println() function writes to the console area of the Processing environment. |
|
9146 * Each call to this function creates a new line of output. Individual elements can be separated with quotes ("") and joined with the string concatenation operator (+). |
|
9147 * |
|
9148 * @param {String} message the string to write to the console |
|
9149 * |
|
9150 * @see #join |
|
9151 * @see #print |
|
9152 */ |
|
9153 p.println = function(message) { |
|
9154 var bufferLen = logBuffer.length; |
|
9155 if (bufferLen) { |
|
9156 Processing.logger.log(logBuffer.join("")); |
|
9157 logBuffer.length = 0; // clear log buffer |
|
9158 } |
|
9159 |
|
9160 if (arguments.length === 0 && bufferLen === 0) { |
|
9161 Processing.logger.log(""); |
|
9162 } else if (arguments.length !== 0) { |
|
9163 Processing.logger.log(message); |
|
9164 } |
|
9165 }; |
|
9166 /** |
|
9167 * The print() function writes to the console area of the Processing environment. |
|
9168 * |
|
9169 * @param {String} message the string to write to the console |
|
9170 * |
|
9171 * @see #join |
|
9172 */ |
|
9173 p.print = function(message) { |
|
9174 logBuffer.push(message); |
|
9175 }; |
|
9176 |
|
9177 // Alphanumeric chars arguments automatically converted to numbers when |
|
9178 // passed in, and will come out as numbers. |
|
9179 p.str = function(val) { |
|
9180 if (val instanceof Array) { |
|
9181 var arr = []; |
|
9182 for (var i = 0; i < val.length; i++) { |
|
9183 arr.push(val[i].toString() + ""); |
|
9184 } |
|
9185 return arr; |
|
9186 } |
|
9187 return (val.toString() + ""); |
|
9188 }; |
|
9189 /** |
|
9190 * Remove whitespace characters from the beginning and ending |
|
9191 * of a String or a String array. Works like String.trim() but includes the |
|
9192 * unicode nbsp character as well. If an array is passed in the function will return a new array not effecting the array passed in. |
|
9193 * |
|
9194 * @param {String} str the string to trim |
|
9195 * @param {String[]} str the string array to trim |
|
9196 * |
|
9197 * @return {String|String[]} retrurns a string or an array will removed whitespaces |
|
9198 */ |
|
9199 p.trim = function(str) { |
|
9200 if (str instanceof Array) { |
|
9201 var arr = []; |
|
9202 for (var i = 0; i < str.length; i++) { |
|
9203 arr.push(str[i].replace(/^\s*/, '').replace(/\s*$/, '').replace(/\r*$/, '')); |
|
9204 } |
|
9205 return arr; |
|
9206 } |
|
9207 return str.replace(/^\s*/, '').replace(/\s*$/, '').replace(/\r*$/, ''); |
|
9208 }; |
|
9209 |
|
9210 // Conversion |
|
9211 function booleanScalar(val) { |
|
9212 if (typeof val === 'number') { |
|
9213 return val !== 0; |
|
9214 } |
|
9215 if (typeof val === 'boolean') { |
|
9216 return val; |
|
9217 } |
|
9218 if (typeof val === 'string') { |
|
9219 return val.toLowerCase() === 'true'; |
|
9220 } |
|
9221 if (val instanceof Char) { |
|
9222 // 1, T or t |
|
9223 return val.code === 49 || val.code === 84 || val.code === 116; |
|
9224 } |
|
9225 } |
|
9226 |
|
9227 /** |
|
9228 * Converts the passed parameter to the function to its boolean value. |
|
9229 * It will return an array of booleans if an array is passed in. |
|
9230 * |
|
9231 * @param {int, byte, string} val the parameter to be converted to boolean |
|
9232 * @param {int[], byte[], string[]} val the array to be converted to boolean[] |
|
9233 * |
|
9234 * @return {boolean|boolean[]} returns a boolean or an array of booleans |
|
9235 */ |
|
9236 p.parseBoolean = function (val) { |
|
9237 if (val instanceof Array) { |
|
9238 var ret = []; |
|
9239 for (var i = 0; i < val.length; i++) { |
|
9240 ret.push(booleanScalar(val[i])); |
|
9241 } |
|
9242 return ret; |
|
9243 } |
|
9244 return booleanScalar(val); |
|
9245 }; |
|
9246 |
|
9247 /** |
|
9248 * Converts the passed parameter to the function to its byte value. |
|
9249 * A byte is a number between -128 and 127. |
|
9250 * It will return an array of bytes if an array is passed in. |
|
9251 * |
|
9252 * @param {int, char} what the parameter to be conveted to byte |
|
9253 * @param {int[], char[]} what the array to be converted to byte[] |
|
9254 * |
|
9255 * @return {byte|byte[]} returns a byte or an array of bytes |
|
9256 */ |
|
9257 p.parseByte = function(what) { |
|
9258 if (what instanceof Array) { |
|
9259 var bytes = []; |
|
9260 for (var i = 0; i < what.length; i++) { |
|
9261 bytes.push((0 - (what[i] & 0x80)) | (what[i] & 0x7F)); |
|
9262 } |
|
9263 return bytes; |
|
9264 } |
|
9265 return (0 - (what & 0x80)) | (what & 0x7F); |
|
9266 }; |
|
9267 |
|
9268 /** |
|
9269 * Converts the passed parameter to the function to its char value. |
|
9270 * It will return an array of chars if an array is passed in. |
|
9271 * |
|
9272 * @param {int, byte} key the parameter to be conveted to char |
|
9273 * @param {int[], byte[]} key the array to be converted to char[] |
|
9274 * |
|
9275 * @return {char|char[]} returns a char or an array of chars |
|
9276 */ |
|
9277 p.parseChar = function(key) { |
|
9278 if (typeof key === "number") { |
|
9279 return new Char(String.fromCharCode(key & 0xFFFF)); |
|
9280 } |
|
9281 if (key instanceof Array) { |
|
9282 var ret = []; |
|
9283 for (var i = 0; i < key.length; i++) { |
|
9284 ret.push(new Char(String.fromCharCode(key[i] & 0xFFFF))); |
|
9285 } |
|
9286 return ret; |
|
9287 } |
|
9288 throw "char() may receive only one argument of type int, byte, int[], or byte[]."; |
|
9289 }; |
|
9290 |
|
9291 // Processing doc claims good argument types are: int, char, byte, boolean, |
|
9292 // String, int[], char[], byte[], boolean[], String[]. |
|
9293 // floats should not work. However, floats with only zeroes right of the |
|
9294 // decimal will work because JS converts those to int. |
|
9295 function floatScalar(val) { |
|
9296 if (typeof val === 'number') { |
|
9297 return val; |
|
9298 } |
|
9299 if (typeof val === 'boolean') { |
|
9300 return val ? 1 : 0; |
|
9301 } |
|
9302 if (typeof val === 'string') { |
|
9303 return parseFloat(val); |
|
9304 } |
|
9305 if (val instanceof Char) { |
|
9306 return val.code; |
|
9307 } |
|
9308 } |
|
9309 |
|
9310 /** |
|
9311 * Converts the passed parameter to the function to its float value. |
|
9312 * It will return an array of floats if an array is passed in. |
|
9313 * |
|
9314 * @param {int, char, boolean, string} val the parameter to be conveted to float |
|
9315 * @param {int[], char[], boolean[], string[]} val the array to be converted to float[] |
|
9316 * |
|
9317 * @return {float|float[]} returns a float or an array of floats |
|
9318 */ |
|
9319 p.parseFloat = function(val) { |
|
9320 if (val instanceof Array) { |
|
9321 var ret = []; |
|
9322 for (var i = 0; i < val.length; i++) { |
|
9323 ret.push(floatScalar(val[i])); |
|
9324 } |
|
9325 return ret; |
|
9326 } |
|
9327 return floatScalar(val); |
|
9328 }; |
|
9329 |
|
9330 function intScalar(val, radix) { |
|
9331 if (typeof val === 'number') { |
|
9332 return val & 0xFFFFFFFF; |
|
9333 } |
|
9334 if (typeof val === 'boolean') { |
|
9335 return val ? 1 : 0; |
|
9336 } |
|
9337 if (typeof val === 'string') { |
|
9338 var number = parseInt(val, radix || 10); // Default to decimal radix. |
|
9339 return number & 0xFFFFFFFF; |
|
9340 } |
|
9341 if (val instanceof Char) { |
|
9342 return val.code; |
|
9343 } |
|
9344 } |
|
9345 |
|
9346 /** |
|
9347 * Converts the passed parameter to the function to its int value. |
|
9348 * It will return an array of ints if an array is passed in. |
|
9349 * |
|
9350 * @param {string, char, boolean, float} val the parameter to be conveted to int |
|
9351 * @param {string[], char[], boolean[], float[]} val the array to be converted to int[] |
|
9352 * @param {int} radix optional the radix of the number (for js compatibility) |
|
9353 * |
|
9354 * @return {int|int[]} returns a int or an array of ints |
|
9355 */ |
|
9356 p.parseInt = function(val, radix) { |
|
9357 if (val instanceof Array) { |
|
9358 var ret = []; |
|
9359 for (var i = 0; i < val.length; i++) { |
|
9360 if (typeof val[i] === 'string' && !/^\s*[+\-]?\d+\s*$/.test(val[i])) { |
|
9361 ret.push(0); |
|
9362 } else { |
|
9363 ret.push(intScalar(val[i], radix)); |
|
9364 } |
|
9365 } |
|
9366 return ret; |
|
9367 } |
|
9368 return intScalar(val, radix); |
|
9369 }; |
|
9370 |
|
9371 p.__int_cast = function(val) { |
|
9372 return 0|val; |
|
9373 }; |
|
9374 |
|
9375 p.__instanceof = function(obj, type) { |
|
9376 if (typeof type !== "function") { |
|
9377 throw "Function is expected as type argument for instanceof operator"; |
|
9378 } |
|
9379 |
|
9380 if (typeof obj === "string") { |
|
9381 // special case for strings |
|
9382 return type === Object || type === String; |
|
9383 } |
|
9384 |
|
9385 if (obj instanceof type) { |
|
9386 // fast check if obj is already of type instance |
|
9387 return true; |
|
9388 } |
|
9389 |
|
9390 if (typeof obj !== "object" || obj === null) { |
|
9391 return false; // not an object or null |
|
9392 } |
|
9393 |
|
9394 var objType = obj.constructor; |
|
9395 if (type.$isInterface) { |
|
9396 // expecting the interface |
|
9397 // queueing interfaces from type and its base classes |
|
9398 var interfaces = []; |
|
9399 while (objType) { |
|
9400 if (objType.$interfaces) { |
|
9401 interfaces = interfaces.concat(objType.$interfaces); |
|
9402 } |
|
9403 objType = objType.$base; |
|
9404 } |
|
9405 while (interfaces.length > 0) { |
|
9406 var i = interfaces.shift(); |
|
9407 if (i === type) { |
|
9408 return true; |
|
9409 } |
|
9410 // wide search in base interfaces |
|
9411 if (i.$interfaces) { |
|
9412 interfaces = interfaces.concat(i.$interfaces); |
|
9413 } |
|
9414 } |
|
9415 return false; |
|
9416 } |
|
9417 |
|
9418 while (objType.hasOwnProperty("$base")) { |
|
9419 objType = objType.$base; |
|
9420 if (objType === type) { |
|
9421 return true; // object was found |
|
9422 } |
|
9423 } |
|
9424 |
|
9425 return false; |
|
9426 }; |
|
9427 |
|
9428 //////////////////////////////////////////////////////////////////////////// |
|
9429 // Math functions |
|
9430 //////////////////////////////////////////////////////////////////////////// |
|
9431 |
|
9432 // Calculation |
|
9433 /** |
|
9434 * Calculates the absolute value (magnitude) of a number. The absolute value of a number is always positive. |
|
9435 * |
|
9436 * @param {int|float} value int or float |
|
9437 * |
|
9438 * @returns {int|float} |
|
9439 */ |
|
9440 p.abs = Math.abs; |
|
9441 |
|
9442 /** |
|
9443 * Calculates the closest int value that is greater than or equal to the value of the parameter. |
|
9444 * For example, ceil(9.03) returns the value 10. |
|
9445 * |
|
9446 * @param {float} value float |
|
9447 * |
|
9448 * @returns {int} |
|
9449 * |
|
9450 * @see floor |
|
9451 * @see round |
|
9452 */ |
|
9453 p.ceil = Math.ceil; |
|
9454 |
|
9455 /** |
|
9456 * Constrains a value to not exceed a maximum and minimum value. |
|
9457 * |
|
9458 * @param {int|float} value the value to constrain |
|
9459 * @param {int|float} value minimum limit |
|
9460 * @param {int|float} value maximum limit |
|
9461 * |
|
9462 * @returns {int|float} |
|
9463 * |
|
9464 * @see max |
|
9465 * @see min |
|
9466 */ |
|
9467 p.constrain = function(aNumber, aMin, aMax) { |
|
9468 return aNumber > aMax ? aMax : aNumber < aMin ? aMin : aNumber; |
|
9469 }; |
|
9470 |
|
9471 /** |
|
9472 * Calculates the distance between two points. |
|
9473 * |
|
9474 * @param {int|float} x1 int or float: x-coordinate of the first point |
|
9475 * @param {int|float} y1 int or float: y-coordinate of the first point |
|
9476 * @param {int|float} z1 int or float: z-coordinate of the first point |
|
9477 * @param {int|float} x2 int or float: x-coordinate of the second point |
|
9478 * @param {int|float} y2 int or float: y-coordinate of the second point |
|
9479 * @param {int|float} z2 int or float: z-coordinate of the second point |
|
9480 * |
|
9481 * @returns {float} |
|
9482 */ |
|
9483 p.dist = function() { |
|
9484 var dx, dy, dz; |
|
9485 if (arguments.length === 4) { |
|
9486 dx = arguments[0] - arguments[2]; |
|
9487 dy = arguments[1] - arguments[3]; |
|
9488 return Math.sqrt(dx * dx + dy * dy); |
|
9489 } |
|
9490 if (arguments.length === 6) { |
|
9491 dx = arguments[0] - arguments[3]; |
|
9492 dy = arguments[1] - arguments[4]; |
|
9493 dz = arguments[2] - arguments[5]; |
|
9494 return Math.sqrt(dx * dx + dy * dy + dz * dz); |
|
9495 } |
|
9496 }; |
|
9497 |
|
9498 /** |
|
9499 * Returns Euler's number e (2.71828...) raised to the power of the value parameter. |
|
9500 * |
|
9501 * @param {int|float} value int or float: the exponent to raise e to |
|
9502 * |
|
9503 * @returns {float} |
|
9504 */ |
|
9505 p.exp = Math.exp; |
|
9506 |
|
9507 /** |
|
9508 * Calculates the closest int value that is less than or equal to the value of the parameter. |
|
9509 * |
|
9510 * @param {int|float} value the value to floor |
|
9511 * |
|
9512 * @returns {int|float} |
|
9513 * |
|
9514 * @see ceil |
|
9515 * @see round |
|
9516 */ |
|
9517 p.floor = Math.floor; |
|
9518 |
|
9519 /** |
|
9520 * Calculates a number between two numbers at a specific increment. The amt parameter is the |
|
9521 * amount to interpolate between the two values where 0.0 equal to the first point, 0.1 is very |
|
9522 * near the first point, 0.5 is half-way in between, etc. The lerp function is convenient for |
|
9523 * creating motion along a straight path and for drawing dotted lines. |
|
9524 * |
|
9525 * @param {int|float} value1 float or int: first value |
|
9526 * @param {int|float} value2 float or int: second value |
|
9527 * @param {int|float} amt float: between 0.0 and 1.0 |
|
9528 * |
|
9529 * @returns {float} |
|
9530 * |
|
9531 * @see curvePoint |
|
9532 * @see bezierPoint |
|
9533 */ |
|
9534 p.lerp = function(value1, value2, amt) { |
|
9535 return ((value2 - value1) * amt) + value1; |
|
9536 }; |
|
9537 |
|
9538 /** |
|
9539 * Calculates the natural logarithm (the base-e logarithm) of a number. This function |
|
9540 * expects the values greater than 0.0. |
|
9541 * |
|
9542 * @param {int|float} value int or float: number must be greater then 0.0 |
|
9543 * |
|
9544 * @returns {float} |
|
9545 */ |
|
9546 p.log = Math.log; |
|
9547 |
|
9548 /** |
|
9549 * Calculates the magnitude (or length) of a vector. A vector is a direction in space commonly |
|
9550 * used in computer graphics and linear algebra. Because it has no "start" position, the magnitude |
|
9551 * of a vector can be thought of as the distance from coordinate (0,0) to its (x,y) value. |
|
9552 * Therefore, mag() is a shortcut for writing "dist(0, 0, x, y)". |
|
9553 * |
|
9554 * @param {int|float} a float or int: first value |
|
9555 * @param {int|float} b float or int: second value |
|
9556 * @param {int|float} c float or int: third value |
|
9557 * |
|
9558 * @returns {float} |
|
9559 * |
|
9560 * @see dist |
|
9561 */ |
|
9562 p.mag = function(a, b, c) { |
|
9563 if (c) { |
|
9564 return Math.sqrt(a * a + b * b + c * c); |
|
9565 } |
|
9566 |
|
9567 return Math.sqrt(a * a + b * b); |
|
9568 }; |
|
9569 |
|
9570 /** |
|
9571 * Re-maps a number from one range to another. In the example above, the number '25' is converted from |
|
9572 * a value in the range 0..100 into a value that ranges from the left edge (0) to the right edge (width) of the screen. |
|
9573 * Numbers outside the range are not clamped to 0 and 1, because out-of-range values are often intentional and useful. |
|
9574 * |
|
9575 * @param {float} value The incoming value to be converted |
|
9576 * @param {float} istart Lower bound of the value's current range |
|
9577 * @param {float} istop Upper bound of the value's current range |
|
9578 * @param {float} ostart Lower bound of the value's target range |
|
9579 * @param {float} ostop Upper bound of the value's target range |
|
9580 * |
|
9581 * @returns {float} |
|
9582 * |
|
9583 * @see norm |
|
9584 * @see lerp |
|
9585 */ |
|
9586 p.map = function(value, istart, istop, ostart, ostop) { |
|
9587 return ostart + (ostop - ostart) * ((value - istart) / (istop - istart)); |
|
9588 }; |
|
9589 |
|
9590 /** |
|
9591 * Determines the largest value in a sequence of numbers. |
|
9592 * |
|
9593 * @param {int|float} value1 int or float |
|
9594 * @param {int|float} value2 int or float |
|
9595 * @param {int|float} value3 int or float |
|
9596 * @param {int|float} array int or float array |
|
9597 * |
|
9598 * @returns {int|float} |
|
9599 * |
|
9600 * @see min |
|
9601 */ |
|
9602 p.max = function() { |
|
9603 if (arguments.length === 2) { |
|
9604 return arguments[0] < arguments[1] ? arguments[1] : arguments[0]; |
|
9605 } |
|
9606 var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used |
|
9607 if (! ("length" in numbers && numbers.length > 0)) { |
|
9608 throw "Non-empty array is expected"; |
|
9609 } |
|
9610 var max = numbers[0], |
|
9611 count = numbers.length; |
|
9612 for (var i = 1; i < count; ++i) { |
|
9613 if (max < numbers[i]) { |
|
9614 max = numbers[i]; |
|
9615 } |
|
9616 } |
|
9617 return max; |
|
9618 }; |
|
9619 |
|
9620 /** |
|
9621 * Determines the smallest value in a sequence of numbers. |
|
9622 * |
|
9623 * @param {int|float} value1 int or float |
|
9624 * @param {int|float} value2 int or float |
|
9625 * @param {int|float} value3 int or float |
|
9626 * @param {int|float} array int or float array |
|
9627 * |
|
9628 * @returns {int|float} |
|
9629 * |
|
9630 * @see max |
|
9631 */ |
|
9632 p.min = function() { |
|
9633 if (arguments.length === 2) { |
|
9634 return arguments[0] < arguments[1] ? arguments[0] : arguments[1]; |
|
9635 } |
|
9636 var numbers = arguments.length === 1 ? arguments[0] : arguments; // if single argument, array is used |
|
9637 if (! ("length" in numbers && numbers.length > 0)) { |
|
9638 throw "Non-empty array is expected"; |
|
9639 } |
|
9640 var min = numbers[0], |
|
9641 count = numbers.length; |
|
9642 for (var i = 1; i < count; ++i) { |
|
9643 if (min > numbers[i]) { |
|
9644 min = numbers[i]; |
|
9645 } |
|
9646 } |
|
9647 return min; |
|
9648 }; |
|
9649 |
|
9650 /** |
|
9651 * Normalizes a number from another range into a value between 0 and 1. |
|
9652 * Identical to map(value, low, high, 0, 1); |
|
9653 * Numbers outside the range are not clamped to 0 and 1, because out-of-range |
|
9654 * values are often intentional and useful. |
|
9655 * |
|
9656 * @param {float} aNumber The incoming value to be converted |
|
9657 * @param {float} low Lower bound of the value's current range |
|
9658 * @param {float} high Upper bound of the value's current range |
|
9659 * |
|
9660 * @returns {float} |
|
9661 * |
|
9662 * @see map |
|
9663 * @see lerp |
|
9664 */ |
|
9665 p.norm = function(aNumber, low, high) { |
|
9666 return (aNumber - low) / (high - low); |
|
9667 }; |
|
9668 |
|
9669 /** |
|
9670 * Facilitates exponential expressions. The pow() function is an efficient way of |
|
9671 * multiplying numbers by themselves (or their reciprocal) in large quantities. |
|
9672 * For example, pow(3, 5) is equivalent to the expression 3*3*3*3*3 and pow(3, -5) |
|
9673 * is equivalent to 1 / 3*3*3*3*3. |
|
9674 * |
|
9675 * @param {int|float} num base of the exponential expression |
|
9676 * @param {int|float} exponent power of which to raise the base |
|
9677 * |
|
9678 * @returns {float} |
|
9679 * |
|
9680 * @see sqrt |
|
9681 */ |
|
9682 p.pow = Math.pow; |
|
9683 |
|
9684 /** |
|
9685 * Calculates the integer closest to the value parameter. For example, round(9.2) returns the value 9. |
|
9686 * |
|
9687 * @param {float} value number to round |
|
9688 * |
|
9689 * @returns {int} |
|
9690 * |
|
9691 * @see floor |
|
9692 * @see ceil |
|
9693 */ |
|
9694 p.round = Math.round; |
|
9695 |
|
9696 /** |
|
9697 * Squares a number (multiplies a number by itself). The result is always a positive number, |
|
9698 * as multiplying two negative numbers always yields a positive result. For example, -1 * -1 = 1. |
|
9699 * |
|
9700 * @param {float} value int or float |
|
9701 * |
|
9702 * @returns {float} |
|
9703 * |
|
9704 * @see sqrt |
|
9705 */ |
|
9706 p.sq = function(aNumber) { |
|
9707 return aNumber * aNumber; |
|
9708 }; |
|
9709 |
|
9710 /** |
|
9711 * Calculates the square root of a number. The square root of a number is always positive, |
|
9712 * even though there may be a valid negative root. The square root s of number a is such |
|
9713 * that s*s = a. It is the opposite of squaring. |
|
9714 * |
|
9715 * @param {float} value int or float, non negative |
|
9716 * |
|
9717 * @returns {float} |
|
9718 * |
|
9719 * @see pow |
|
9720 * @see sq |
|
9721 */ |
|
9722 p.sqrt = Math.sqrt; |
|
9723 |
|
9724 // Trigonometry |
|
9725 /** |
|
9726 * The inverse of cos(), returns the arc cosine of a value. This function expects the |
|
9727 * values in the range of -1 to 1 and values are returned in the range 0 to PI (3.1415927). |
|
9728 * |
|
9729 * @param {float} value the value whose arc cosine is to be returned |
|
9730 * |
|
9731 * @returns {float} |
|
9732 * |
|
9733 * @see cos |
|
9734 * @see asin |
|
9735 * @see atan |
|
9736 */ |
|
9737 p.acos = Math.acos; |
|
9738 |
|
9739 /** |
|
9740 * The inverse of sin(), returns the arc sine of a value. This function expects the values |
|
9741 * in the range of -1 to 1 and values are returned in the range -PI/2 to PI/2. |
|
9742 * |
|
9743 * @param {float} value the value whose arc sine is to be returned |
|
9744 * |
|
9745 * @returns {float} |
|
9746 * |
|
9747 * @see sin |
|
9748 * @see acos |
|
9749 * @see atan |
|
9750 */ |
|
9751 p.asin = Math.asin; |
|
9752 |
|
9753 /** |
|
9754 * The inverse of tan(), returns the arc tangent of a value. This function expects the values |
|
9755 * in the range of -Infinity to Infinity (exclusive) and values are returned in the range -PI/2 to PI/2 . |
|
9756 * |
|
9757 * @param {float} value -Infinity to Infinity (exclusive) |
|
9758 * |
|
9759 * @returns {float} |
|
9760 * |
|
9761 * @see tan |
|
9762 * @see asin |
|
9763 * @see acos |
|
9764 */ |
|
9765 p.atan = Math.atan; |
|
9766 |
|
9767 /** |
|
9768 * Calculates the angle (in radians) from a specified point to the coordinate origin as measured from |
|
9769 * the positive x-axis. Values are returned as a float in the range from PI to -PI. The atan2() function |
|
9770 * is most often used for orienting geometry to the position of the cursor. Note: The y-coordinate of the |
|
9771 * point is the first parameter and the x-coordinate is the second due the the structure of calculating the tangent. |
|
9772 * |
|
9773 * @param {float} y y-coordinate of the point |
|
9774 * @param {float} x x-coordinate of the point |
|
9775 * |
|
9776 * @returns {float} |
|
9777 * |
|
9778 * @see tan |
|
9779 */ |
|
9780 p.atan2 = Math.atan2; |
|
9781 |
|
9782 /** |
|
9783 * Calculates the cosine of an angle. This function expects the values of the angle parameter to be provided |
|
9784 * in radians (values from 0 to PI*2). Values are returned in the range -1 to 1. |
|
9785 * |
|
9786 * @param {float} value an angle in radians |
|
9787 * |
|
9788 * @returns {float} |
|
9789 * |
|
9790 * @see tan |
|
9791 * @see sin |
|
9792 */ |
|
9793 p.cos = Math.cos; |
|
9794 |
|
9795 /** |
|
9796 * Converts a radian measurement to its corresponding value in degrees. Radians and degrees are two ways of |
|
9797 * measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, |
|
9798 * 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians. |
|
9799 * |
|
9800 * @param {int|float} value an angle in radians |
|
9801 * |
|
9802 * @returns {float} |
|
9803 * |
|
9804 * @see radians |
|
9805 */ |
|
9806 p.degrees = function(aAngle) { |
|
9807 return (aAngle * 180) / Math.PI; |
|
9808 }; |
|
9809 |
|
9810 /** |
|
9811 * Converts a degree measurement to its corresponding value in radians. Radians and degrees are two ways of |
|
9812 * measuring the same thing. There are 360 degrees in a circle and 2*PI radians in a circle. For example, |
|
9813 * 90 degrees = PI/2 = 1.5707964. All trigonometric methods in Processing require their parameters to be specified in radians. |
|
9814 * |
|
9815 * @param {int|float} value an angle in radians |
|
9816 * |
|
9817 * @returns {float} |
|
9818 * |
|
9819 * @see degrees |
|
9820 */ |
|
9821 p.radians = function(aAngle) { |
|
9822 return (aAngle / 180) * Math.PI; |
|
9823 }; |
|
9824 |
|
9825 /** |
|
9826 * Calculates the sine of an angle. This function expects the values of the angle parameter to be provided in |
|
9827 * radians (values from 0 to 6.28). Values are returned in the range -1 to 1. |
|
9828 * |
|
9829 * @param {float} value an angle in radians |
|
9830 * |
|
9831 * @returns {float} |
|
9832 * |
|
9833 * @see cos |
|
9834 * @see radians |
|
9835 */ |
|
9836 p.sin = Math.sin; |
|
9837 |
|
9838 /** |
|
9839 * Calculates the ratio of the sine and cosine of an angle. This function expects the values of the angle |
|
9840 * parameter to be provided in radians (values from 0 to PI*2). Values are returned in the range infinity to -infinity. |
|
9841 * |
|
9842 * @param {float} value an angle in radians |
|
9843 * |
|
9844 * @returns {float} |
|
9845 * |
|
9846 * @see cos |
|
9847 * @see sin |
|
9848 * @see radians |
|
9849 */ |
|
9850 p.tan = Math.tan; |
|
9851 |
|
9852 var currentRandom = Math.random; |
|
9853 |
|
9854 /** |
|
9855 * Generates random numbers. Each time the random() function is called, it returns an unexpected value within |
|
9856 * the specified range. If one parameter is passed to the function it will return a float between zero and the |
|
9857 * value of the high parameter. The function call random(5) returns values between 0 and 5 (starting at zero, |
|
9858 * up to but not including 5). If two parameters are passed, it will return a float with a value between the |
|
9859 * parameters. The function call random(-5, 10.2) returns values starting at -5 up to (but not including) 10.2. |
|
9860 * To convert a floating-point random number to an integer, use the int() function. |
|
9861 * |
|
9862 * @param {int|float} value1 if one parameter is used, the top end to random from, if two params the low end |
|
9863 * @param {int|float} value2 the top end of the random range |
|
9864 * |
|
9865 * @returns {float} |
|
9866 * |
|
9867 * @see randomSeed |
|
9868 * @see noise |
|
9869 */ |
|
9870 p.random = function() { |
|
9871 if(arguments.length === 0) { |
|
9872 return currentRandom(); |
|
9873 } |
|
9874 if(arguments.length === 1) { |
|
9875 return currentRandom() * arguments[0]; |
|
9876 } |
|
9877 var aMin = arguments[0], aMax = arguments[1]; |
|
9878 return currentRandom() * (aMax - aMin) + aMin; |
|
9879 }; |
|
9880 |
|
9881 // Pseudo-random generator |
|
9882 function Marsaglia(i1, i2) { |
|
9883 // from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c |
|
9884 var z=i1 || 362436069, w= i2 || 521288629; |
|
9885 var nextInt = function() { |
|
9886 z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF; |
|
9887 w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF; |
|
9888 return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF; |
|
9889 }; |
|
9890 |
|
9891 this.nextDouble = function() { |
|
9892 var i = nextInt() / 4294967296; |
|
9893 return i < 0 ? 1 + i : i; |
|
9894 }; |
|
9895 this.nextInt = nextInt; |
|
9896 } |
|
9897 Marsaglia.createRandomized = function() { |
|
9898 var now = new Date(); |
|
9899 return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF); |
|
9900 }; |
|
9901 |
|
9902 /** |
|
9903 * Sets the seed value for random(). By default, random() produces different results each time the |
|
9904 * program is run. Set the value parameter to a constant to return the same pseudo-random numbers |
|
9905 * each time the software is run. |
|
9906 * |
|
9907 * @param {int|float} seed int |
|
9908 * |
|
9909 * @see random |
|
9910 * @see noise |
|
9911 * @see noiseSeed |
|
9912 */ |
|
9913 p.randomSeed = function(seed) { |
|
9914 currentRandom = (new Marsaglia(seed)).nextDouble; |
|
9915 }; |
|
9916 |
|
9917 // Random |
|
9918 // We have two random()'s in the code... what does this do ? and which one is current ? |
|
9919 p.Random = function(seed) { |
|
9920 var haveNextNextGaussian = false, nextNextGaussian, random; |
|
9921 |
|
9922 this.nextGaussian = function() { |
|
9923 if (haveNextNextGaussian) { |
|
9924 haveNextNextGaussian = false; |
|
9925 return nextNextGaussian; |
|
9926 } |
|
9927 var v1, v2, s; |
|
9928 do { |
|
9929 v1 = 2 * random() - 1; // between -1.0 and 1.0 |
|
9930 v2 = 2 * random() - 1; // between -1.0 and 1.0 |
|
9931 s = v1 * v1 + v2 * v2; |
|
9932 } |
|
9933 while (s >= 1 || s === 0); |
|
9934 |
|
9935 var multiplier = Math.sqrt(-2 * Math.log(s) / s); |
|
9936 nextNextGaussian = v2 * multiplier; |
|
9937 haveNextNextGaussian = true; |
|
9938 |
|
9939 return v1 * multiplier; |
|
9940 }; |
|
9941 |
|
9942 // by default use standard random, otherwise seeded |
|
9943 random = (seed === undef) ? Math.random : (new Marsaglia(seed)).nextDouble; |
|
9944 }; |
|
9945 |
|
9946 // Noise functions and helpers |
|
9947 function PerlinNoise(seed) { |
|
9948 var rnd = seed !== undef ? new Marsaglia(seed) : Marsaglia.createRandomized(); |
|
9949 var i, j; |
|
9950 // http://www.noisemachine.com/talk1/17b.html |
|
9951 // http://mrl.nyu.edu/~perlin/noise/ |
|
9952 // generate permutation |
|
9953 var perm = new Uint8Array(512); |
|
9954 for(i=0;i<256;++i) { perm[i] = i; } |
|
9955 for(i=0;i<256;++i) { var t = perm[j = rnd.nextInt() & 0xFF]; perm[j] = perm[i]; perm[i] = t; } |
|
9956 // copy to avoid taking mod in perm[0]; |
|
9957 for(i=0;i<256;++i) { perm[i + 256] = perm[i]; } |
|
9958 |
|
9959 function grad3d(i,x,y,z) { |
|
9960 var h = i & 15; // convert into 12 gradient directions |
|
9961 var u = h<8 ? x : y, |
|
9962 v = h<4 ? y : h===12||h===14 ? x : z; |
|
9963 return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v); |
|
9964 } |
|
9965 |
|
9966 function grad2d(i,x,y) { |
|
9967 var v = (i & 1) === 0 ? x : y; |
|
9968 return (i&2) === 0 ? -v : v; |
|
9969 } |
|
9970 |
|
9971 function grad1d(i,x) { |
|
9972 return (i&1) === 0 ? -x : x; |
|
9973 } |
|
9974 |
|
9975 function lerp(t,a,b) { return a + t * (b - a); } |
|
9976 |
|
9977 this.noise3d = function(x, y, z) { |
|
9978 var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255; |
|
9979 x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); |
|
9980 var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z; |
|
9981 var p0 = perm[X]+Y, p00 = perm[p0] + Z, p01 = perm[p0 + 1] + Z, |
|
9982 p1 = perm[X + 1] + Y, p10 = perm[p1] + Z, p11 = perm[p1 + 1] + Z; |
|
9983 return lerp(fz, |
|
9984 lerp(fy, lerp(fx, grad3d(perm[p00], x, y, z), grad3d(perm[p10], x-1, y, z)), |
|
9985 lerp(fx, grad3d(perm[p01], x, y-1, z), grad3d(perm[p11], x-1, y-1,z))), |
|
9986 lerp(fy, lerp(fx, grad3d(perm[p00 + 1], x, y, z-1), grad3d(perm[p10 + 1], x-1, y, z-1)), |
|
9987 lerp(fx, grad3d(perm[p01 + 1], x, y-1, z-1), grad3d(perm[p11 + 1], x-1, y-1,z-1)))); |
|
9988 }; |
|
9989 |
|
9990 this.noise2d = function(x, y) { |
|
9991 var X = Math.floor(x)&255, Y = Math.floor(y)&255; |
|
9992 x -= Math.floor(x); y -= Math.floor(y); |
|
9993 var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y; |
|
9994 var p0 = perm[X]+Y, p1 = perm[X + 1] + Y; |
|
9995 return lerp(fy, |
|
9996 lerp(fx, grad2d(perm[p0], x, y), grad2d(perm[p1], x-1, y)), |
|
9997 lerp(fx, grad2d(perm[p0 + 1], x, y-1), grad2d(perm[p1 + 1], x-1, y-1))); |
|
9998 }; |
|
9999 |
|
10000 this.noise1d = function(x) { |
|
10001 var X = Math.floor(x)&255; |
|
10002 x -= Math.floor(x); |
|
10003 var fx = (3-2*x)*x*x; |
|
10004 return lerp(fx, grad1d(perm[X], x), grad1d(perm[X+1], x-1)); |
|
10005 }; |
|
10006 } |
|
10007 |
|
10008 // processing defaults |
|
10009 var noiseProfile = { generator: undef, octaves: 4, fallout: 0.5, seed: undef}; |
|
10010 |
|
10011 /** |
|
10012 * Returns the Perlin noise value at specified coordinates. Perlin noise is a random sequence |
|
10013 * generator producing a more natural ordered, harmonic succession of numbers compared to the |
|
10014 * standard random() function. It was invented by Ken Perlin in the 1980s and been used since |
|
10015 * in graphical applications to produce procedural textures, natural motion, shapes, terrains etc. |
|
10016 * The main difference to the random() function is that Perlin noise is defined in an infinite |
|
10017 * n-dimensional space where each pair of coordinates corresponds to a fixed semi-random value |
|
10018 * (fixed only for the lifespan of the program). The resulting value will always be between 0.0 |
|
10019 * and 1.0. Processing can compute 1D, 2D and 3D noise, depending on the number of coordinates |
|
10020 * given. The noise value can be animated by moving through the noise space as demonstrated in |
|
10021 * the example above. The 2nd and 3rd dimension can also be interpreted as time. |
|
10022 * The actual noise is structured similar to an audio signal, in respect to the function's use |
|
10023 * of frequencies. Similar to the concept of harmonics in physics, perlin noise is computed over |
|
10024 * several octaves which are added together for the final result. |
|
10025 * Another way to adjust the character of the resulting sequence is the scale of the input |
|
10026 * coordinates. As the function works within an infinite space the value of the coordinates |
|
10027 * doesn't matter as such, only the distance between successive coordinates does (eg. when using |
|
10028 * noise() within a loop). As a general rule the smaller the difference between coordinates, the |
|
10029 * smoother the resulting noise sequence will be. Steps of 0.005-0.03 work best for most applications, |
|
10030 * but this will differ depending on use. |
|
10031 * |
|
10032 * @param {float} x x coordinate in noise space |
|
10033 * @param {float} y y coordinate in noise space |
|
10034 * @param {float} z z coordinate in noise space |
|
10035 * |
|
10036 * @returns {float} |
|
10037 * |
|
10038 * @see random |
|
10039 * @see noiseDetail |
|
10040 */ |
|
10041 p.noise = function(x, y, z) { |
|
10042 if(noiseProfile.generator === undef) { |
|
10043 // caching |
|
10044 noiseProfile.generator = new PerlinNoise(noiseProfile.seed); |
|
10045 } |
|
10046 var generator = noiseProfile.generator; |
|
10047 var effect = 1, k = 1, sum = 0; |
|
10048 for(var i=0; i<noiseProfile.octaves; ++i) { |
|
10049 effect *= noiseProfile.fallout; |
|
10050 switch (arguments.length) { |
|
10051 case 1: |
|
10052 sum += effect * (1 + generator.noise1d(k*x))/2; break; |
|
10053 case 2: |
|
10054 sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break; |
|
10055 case 3: |
|
10056 sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break; |
|
10057 } |
|
10058 k *= 2; |
|
10059 } |
|
10060 return sum; |
|
10061 }; |
|
10062 |
|
10063 /** |
|
10064 * Adjusts the character and level of detail produced by the Perlin noise function. |
|
10065 * Similar to harmonics in physics, noise is computed over several octaves. Lower octaves |
|
10066 * contribute more to the output signal and as such define the overal intensity of the noise, |
|
10067 * whereas higher octaves create finer grained details in the noise sequence. By default, |
|
10068 * noise is computed over 4 octaves with each octave contributing exactly half than its |
|
10069 * predecessor, starting at 50% strength for the 1st octave. This falloff amount can be |
|
10070 * changed by adding an additional function parameter. Eg. a falloff factor of 0.75 means |
|
10071 * each octave will now have 75% impact (25% less) of the previous lower octave. Any value |
|
10072 * between 0.0 and 1.0 is valid, however note that values greater than 0.5 might result in |
|
10073 * greater than 1.0 values returned by noise(). By changing these parameters, the signal |
|
10074 * created by the noise() function can be adapted to fit very specific needs and characteristics. |
|
10075 * |
|
10076 * @param {int} octaves number of octaves to be used by the noise() function |
|
10077 * @param {float} falloff falloff factor for each octave |
|
10078 * |
|
10079 * @see noise |
|
10080 */ |
|
10081 p.noiseDetail = function(octaves, fallout) { |
|
10082 noiseProfile.octaves = octaves; |
|
10083 if(fallout !== undef) { |
|
10084 noiseProfile.fallout = fallout; |
|
10085 } |
|
10086 }; |
|
10087 |
|
10088 /** |
|
10089 * Sets the seed value for noise(). By default, noise() produces different results each |
|
10090 * time the program is run. Set the value parameter to a constant to return the same |
|
10091 * pseudo-random numbers each time the software is run. |
|
10092 * |
|
10093 * @param {int} seed int |
|
10094 * |
|
10095 * @returns {float} |
|
10096 * |
|
10097 * @see random |
|
10098 * @see radomSeed |
|
10099 * @see noise |
|
10100 * @see noiseDetail |
|
10101 */ |
|
10102 p.noiseSeed = function(seed) { |
|
10103 noiseProfile.seed = seed; |
|
10104 noiseProfile.generator = undef; |
|
10105 }; |
|
10106 |
|
10107 /** |
|
10108 * Defines the dimension of the display window in units of pixels. The size() function must |
|
10109 * be the first line in setup(). If size() is not called, the default size of the window is |
|
10110 * 100x100 pixels. The system variables width and height are set by the parameters passed to |
|
10111 * the size() function. |
|
10112 * |
|
10113 * @param {int} aWidth width of the display window in units of pixels |
|
10114 * @param {int} aHeight height of the display window in units of pixels |
|
10115 * @param {MODE} aMode Either P2D, P3D, JAVA2D, or OPENGL |
|
10116 * |
|
10117 * @see createGraphics |
|
10118 * @see screen |
|
10119 */ |
|
10120 DrawingShared.prototype.size = function(aWidth, aHeight, aMode) { |
|
10121 if (doStroke) { |
|
10122 p.stroke(0); |
|
10123 } |
|
10124 |
|
10125 if (doFill) { |
|
10126 p.fill(255); |
|
10127 } |
|
10128 |
|
10129 // The default 2d context has already been created in the p.init() stage if |
|
10130 // a 3d context was not specified. This is so that a 2d context will be |
|
10131 // available if size() was not called. |
|
10132 var savedProperties = { |
|
10133 fillStyle: curContext.fillStyle, |
|
10134 strokeStyle: curContext.strokeStyle, |
|
10135 lineCap: curContext.lineCap, |
|
10136 lineJoin: curContext.lineJoin |
|
10137 }; |
|
10138 // remove the style width and height properties to ensure that the canvas gets set to |
|
10139 // aWidth and aHeight coming in |
|
10140 if (curElement.style.length > 0 ) { |
|
10141 curElement.style.removeProperty("width"); |
|
10142 curElement.style.removeProperty("height"); |
|
10143 } |
|
10144 |
|
10145 curElement.width = p.width = aWidth || 100; |
|
10146 curElement.height = p.height = aHeight || 100; |
|
10147 |
|
10148 for (var prop in savedProperties) { |
|
10149 if (savedProperties.hasOwnProperty(prop)) { |
|
10150 curContext[prop] = savedProperties[prop]; |
|
10151 } |
|
10152 } |
|
10153 |
|
10154 // make sure to set the default font the first time round. |
|
10155 p.textFont(curTextFont); |
|
10156 |
|
10157 // Set the background to whatever it was called last as if background() was called before size() |
|
10158 // If background() hasn't been called before, set background() to a light gray |
|
10159 p.background(); |
|
10160 |
|
10161 // set 5% for pixels to cache (or 1000) |
|
10162 maxPixelsCached = Math.max(1000, aWidth * aHeight * 0.05); |
|
10163 |
|
10164 // Externalize the context |
|
10165 p.externals.context = curContext; |
|
10166 |
|
10167 for (var i = 0; i < PConstants.SINCOS_LENGTH; i++) { |
|
10168 sinLUT[i] = p.sin(i * (PConstants.PI / 180) * 0.5); |
|
10169 cosLUT[i] = p.cos(i * (PConstants.PI / 180) * 0.5); |
|
10170 } |
|
10171 }; |
|
10172 |
|
10173 Drawing2D.prototype.size = function(aWidth, aHeight, aMode) { |
|
10174 if (curContext === undef) { |
|
10175 // size() was called without p.init() default context, i.e. p.createGraphics() |
|
10176 curContext = curElement.getContext("2d"); |
|
10177 userMatrixStack = new PMatrixStack(); |
|
10178 userReverseMatrixStack = new PMatrixStack(); |
|
10179 modelView = new PMatrix2D(); |
|
10180 modelViewInv = new PMatrix2D(); |
|
10181 } |
|
10182 |
|
10183 DrawingShared.prototype.size.apply(this, arguments); |
|
10184 }; |
|
10185 |
|
10186 Drawing3D.prototype.size = (function() { |
|
10187 var size3DCalled = false; |
|
10188 |
|
10189 return function size(aWidth, aHeight, aMode) { |
|
10190 if (size3DCalled) { |
|
10191 throw "Multiple calls to size() for 3D renders are not allowed."; |
|
10192 } |
|
10193 size3DCalled = true; |
|
10194 |
|
10195 function getGLContext(canvas) { |
|
10196 var ctxNames = ['experimental-webgl', 'webgl', 'webkit-3d'], |
|
10197 gl; |
|
10198 |
|
10199 for (var i=0, l=ctxNames.length; i<l; i++) { |
|
10200 gl = canvas.getContext(ctxNames[i], {antialias: false}); |
|
10201 if (gl) { |
|
10202 break; |
|
10203 } |
|
10204 } |
|
10205 |
|
10206 return gl; |
|
10207 } |
|
10208 |
|
10209 // get the 3D rendering context |
|
10210 try { |
|
10211 // If the HTML <canvas> dimensions differ from the |
|
10212 // dimensions specified in the size() call in the sketch, for |
|
10213 // 3D sketches, browsers will either not render or render the |
|
10214 // scene incorrectly. To fix this, we need to adjust the |
|
10215 // width and height attributes of the canvas. |
|
10216 curElement.width = p.width = aWidth || 100; |
|
10217 curElement.height = p.height = aHeight || 100; |
|
10218 curContext = getGLContext(curElement); |
|
10219 canTex = curContext.createTexture(); // texture |
|
10220 textTex = curContext.createTexture(); // texture |
|
10221 } catch(e_size) { |
|
10222 Processing.debug(e_size); |
|
10223 } |
|
10224 |
|
10225 if (!curContext) { |
|
10226 throw "WebGL context is not supported on this browser."; |
|
10227 } |
|
10228 |
|
10229 // Set defaults |
|
10230 curContext.viewport(0, 0, curElement.width, curElement.height); |
|
10231 curContext.enable(curContext.DEPTH_TEST); |
|
10232 curContext.enable(curContext.BLEND); |
|
10233 curContext.blendFunc(curContext.SRC_ALPHA, curContext.ONE_MINUS_SRC_ALPHA); |
|
10234 |
|
10235 // Create the program objects to render 2D (points, lines) and |
|
10236 // 3D (spheres, boxes) shapes. Because 2D shapes are not lit, |
|
10237 // lighting calculations could be ommitted from that program object. |
|
10238 programObject2D = createProgramObject(curContext, vertexShaderSource2D, fragmentShaderSource2D); |
|
10239 |
|
10240 programObjectUnlitShape = createProgramObject(curContext, vShaderSrcUnlitShape, fShaderSrcUnlitShape); |
|
10241 |
|
10242 // Set the default point and line width for the 2D and unlit shapes. |
|
10243 p.strokeWeight(1.0); |
|
10244 |
|
10245 // Now that the programs have been compiled, we can set the default |
|
10246 // states for the lights. |
|
10247 programObject3D = createProgramObject(curContext, vertexShaderSource3D, fragmentShaderSource3D); |
|
10248 curContext.useProgram(programObject3D); |
|
10249 |
|
10250 // assume we aren't using textures by default |
|
10251 uniformi("usingTexture3d", programObject3D, "usingTexture", usingTexture); |
|
10252 // assume that we arn't tinting by default |
|
10253 p.lightFalloff(1, 0, 0); |
|
10254 p.shininess(1); |
|
10255 p.ambient(255, 255, 255); |
|
10256 p.specular(0, 0, 0); |
|
10257 p.emissive(0, 0, 0); |
|
10258 |
|
10259 // Create buffers for 3D primitives |
|
10260 boxBuffer = curContext.createBuffer(); |
|
10261 curContext.bindBuffer(curContext.ARRAY_BUFFER, boxBuffer); |
|
10262 curContext.bufferData(curContext.ARRAY_BUFFER, boxVerts, curContext.STATIC_DRAW); |
|
10263 |
|
10264 boxNormBuffer = curContext.createBuffer(); |
|
10265 curContext.bindBuffer(curContext.ARRAY_BUFFER, boxNormBuffer); |
|
10266 curContext.bufferData(curContext.ARRAY_BUFFER, boxNorms, curContext.STATIC_DRAW); |
|
10267 |
|
10268 boxOutlineBuffer = curContext.createBuffer(); |
|
10269 curContext.bindBuffer(curContext.ARRAY_BUFFER, boxOutlineBuffer); |
|
10270 curContext.bufferData(curContext.ARRAY_BUFFER, boxOutlineVerts, curContext.STATIC_DRAW); |
|
10271 |
|
10272 // used to draw the rectangle and the outline |
|
10273 rectBuffer = curContext.createBuffer(); |
|
10274 curContext.bindBuffer(curContext.ARRAY_BUFFER, rectBuffer); |
|
10275 curContext.bufferData(curContext.ARRAY_BUFFER, rectVerts, curContext.STATIC_DRAW); |
|
10276 |
|
10277 rectNormBuffer = curContext.createBuffer(); |
|
10278 curContext.bindBuffer(curContext.ARRAY_BUFFER, rectNormBuffer); |
|
10279 curContext.bufferData(curContext.ARRAY_BUFFER, rectNorms, curContext.STATIC_DRAW); |
|
10280 |
|
10281 // The sphere vertices are specified dynamically since the user |
|
10282 // can change the level of detail. Everytime the user does that |
|
10283 // using sphereDetail(), the new vertices are calculated. |
|
10284 sphereBuffer = curContext.createBuffer(); |
|
10285 |
|
10286 lineBuffer = curContext.createBuffer(); |
|
10287 |
|
10288 // Shape buffers |
|
10289 fillBuffer = curContext.createBuffer(); |
|
10290 fillColorBuffer = curContext.createBuffer(); |
|
10291 strokeColorBuffer = curContext.createBuffer(); |
|
10292 shapeTexVBO = curContext.createBuffer(); |
|
10293 |
|
10294 pointBuffer = curContext.createBuffer(); |
|
10295 curContext.bindBuffer(curContext.ARRAY_BUFFER, pointBuffer); |
|
10296 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0, 0, 0]), curContext.STATIC_DRAW); |
|
10297 |
|
10298 textBuffer = curContext.createBuffer(); |
|
10299 curContext.bindBuffer(curContext.ARRAY_BUFFER, textBuffer ); |
|
10300 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([1,1,0,-1,1,0,-1,-1,0,1,-1,0]), curContext.STATIC_DRAW); |
|
10301 |
|
10302 textureBuffer = curContext.createBuffer(); |
|
10303 curContext.bindBuffer(curContext.ARRAY_BUFFER, textureBuffer); |
|
10304 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array([0,0,1,0,1,1,0,1]), curContext.STATIC_DRAW); |
|
10305 |
|
10306 indexBuffer = curContext.createBuffer(); |
|
10307 curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer); |
|
10308 curContext.bufferData(curContext.ELEMENT_ARRAY_BUFFER, new Uint16Array([0,1,2,2,3,0]), curContext.STATIC_DRAW); |
|
10309 |
|
10310 cam = new PMatrix3D(); |
|
10311 cameraInv = new PMatrix3D(); |
|
10312 modelView = new PMatrix3D(); |
|
10313 modelViewInv = new PMatrix3D(); |
|
10314 projection = new PMatrix3D(); |
|
10315 p.camera(); |
|
10316 p.perspective(); |
|
10317 |
|
10318 userMatrixStack = new PMatrixStack(); |
|
10319 userReverseMatrixStack = new PMatrixStack(); |
|
10320 // used by both curve and bezier, so just init here |
|
10321 curveBasisMatrix = new PMatrix3D(); |
|
10322 curveToBezierMatrix = new PMatrix3D(); |
|
10323 curveDrawMatrix = new PMatrix3D(); |
|
10324 bezierDrawMatrix = new PMatrix3D(); |
|
10325 bezierBasisInverse = new PMatrix3D(); |
|
10326 bezierBasisMatrix = new PMatrix3D(); |
|
10327 bezierBasisMatrix.set(-1, 3, -3, 1, 3, -6, 3, 0, -3, 3, 0, 0, 1, 0, 0, 0); |
|
10328 |
|
10329 DrawingShared.prototype.size.apply(this, arguments); |
|
10330 }; |
|
10331 }()); |
|
10332 |
|
10333 //////////////////////////////////////////////////////////////////////////// |
|
10334 // Lights |
|
10335 //////////////////////////////////////////////////////////////////////////// |
|
10336 |
|
10337 /** |
|
10338 * Adds an ambient light. Ambient light doesn't come from a specific direction, |
|
10339 * the rays have light have bounced around so much that objects are evenly lit |
|
10340 * from all sides. Ambient lights are almost always used in combination with |
|
10341 * other types of lights. Lights need to be included in the <b>draw()</b> to |
|
10342 * remain persistent in a looping program. Placing them in the <b>setup()</b> |
|
10343 * of a looping program will cause them to only have an effect the first time |
|
10344 * through the loop. The effect of the parameters is determined by the current |
|
10345 * color mode. |
|
10346 * |
|
10347 * @param {int | float} r red or hue value |
|
10348 * @param {int | float} g green or hue value |
|
10349 * @param {int | float} b blue or hue value |
|
10350 * |
|
10351 * @param {int | float} x x position of light (used for falloff) |
|
10352 * @param {int | float} y y position of light (used for falloff) |
|
10353 * @param {int | float} z z position of light (used for falloff) |
|
10354 * |
|
10355 * @returns none |
|
10356 * |
|
10357 * @see lights |
|
10358 * @see directionalLight |
|
10359 * @see pointLight |
|
10360 * @see spotLight |
|
10361 */ |
|
10362 Drawing2D.prototype.ambientLight = DrawingShared.prototype.a3DOnlyFunction; |
|
10363 |
|
10364 Drawing3D.prototype.ambientLight = function(r, g, b, x, y, z) { |
|
10365 if (lightCount === PConstants.MAX_LIGHTS) { |
|
10366 throw "can only create " + PConstants.MAX_LIGHTS + " lights"; |
|
10367 } |
|
10368 |
|
10369 var pos = new PVector(x, y, z); |
|
10370 var view = new PMatrix3D(); |
|
10371 view.scale(1, -1, 1); |
|
10372 view.apply(modelView.array()); |
|
10373 view.mult(pos, pos); |
|
10374 |
|
10375 // Instead of calling p.color, we do the calculations ourselves to |
|
10376 // reduce property lookups. |
|
10377 var col = color$4(r, g, b, 0); |
|
10378 var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, |
|
10379 ((col & PConstants.GREEN_MASK) >>> 8) / 255, |
|
10380 (col & PConstants.BLUE_MASK) / 255 ]; |
|
10381 |
|
10382 curContext.useProgram(programObject3D); |
|
10383 uniformf("lights.color.3d." + lightCount, programObject3D, "lights" + lightCount + ".color", normalizedCol); |
|
10384 uniformf("lights.position.3d." + lightCount, programObject3D, "lights" + lightCount + ".position", pos.array()); |
|
10385 uniformi("lights.type.3d." + lightCount, programObject3D, "lights" + lightCount + ".type", 0); |
|
10386 uniformi("lightCount3d", programObject3D, "lightCount", ++lightCount); |
|
10387 }; |
|
10388 |
|
10389 /** |
|
10390 * Adds a directional light. Directional light comes from one direction and |
|
10391 * is stronger when hitting a surface squarely and weaker if it hits at a |
|
10392 * gentle angle. After hitting a surface, a directional lights scatters in |
|
10393 * all directions. Lights need to be included in the <b>draw()</b> to remain |
|
10394 * persistent in a looping program. Placing them in the <b>setup()</b> of a |
|
10395 * looping program will cause them to only have an effect the first time |
|
10396 * through the loop. The affect of the <br>r</b>, <br>g</b>, and <br>b</b> |
|
10397 * parameters is determined by the current color mode. The <b>nx</b>, |
|
10398 * <b>ny</b>, and <b>nz</b> parameters specify the direction the light is |
|
10399 * facing. For example, setting <b>ny</b> to -1 will cause the geometry to be |
|
10400 * lit from below (the light is facing directly upward). |
|
10401 * |
|
10402 * @param {int | float} r red or hue value |
|
10403 * @param {int | float} g green or hue value |
|
10404 * @param {int | float} b blue or hue value |
|
10405 * |
|
10406 * @param {int | float} nx direction along the x axis |
|
10407 * @param {int | float} ny direction along the y axis |
|
10408 * @param {int | float} nz direction along the z axis |
|
10409 * |
|
10410 * @returns none |
|
10411 * |
|
10412 * @see lights |
|
10413 * @see ambientLight |
|
10414 * @see pointLight |
|
10415 * @see spotLight |
|
10416 */ |
|
10417 Drawing2D.prototype.directionalLight = DrawingShared.prototype.a3DOnlyFunction; |
|
10418 |
|
10419 Drawing3D.prototype.directionalLight = function(r, g, b, nx, ny, nz) { |
|
10420 if (lightCount === PConstants.MAX_LIGHTS) { |
|
10421 throw "can only create " + PConstants.MAX_LIGHTS + " lights"; |
|
10422 } |
|
10423 |
|
10424 curContext.useProgram(programObject3D); |
|
10425 |
|
10426 var mvm = new PMatrix3D(); |
|
10427 mvm.scale(1, -1, 1); |
|
10428 mvm.apply(modelView.array()); |
|
10429 mvm = mvm.array(); |
|
10430 |
|
10431 // We need to multiply the direction by the model view matrix, but |
|
10432 // the mult function checks the w component of the vector, if it isn't |
|
10433 // present, it uses 1, so we manually multiply. |
|
10434 var dir = [ |
|
10435 mvm[0] * nx + mvm[4] * ny + mvm[8] * nz, |
|
10436 mvm[1] * nx + mvm[5] * ny + mvm[9] * nz, |
|
10437 mvm[2] * nx + mvm[6] * ny + mvm[10] * nz |
|
10438 ]; |
|
10439 |
|
10440 // Instead of calling p.color, we do the calculations ourselves to |
|
10441 // reduce property lookups. |
|
10442 var col = color$4(r, g, b, 0); |
|
10443 var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, |
|
10444 ((col & PConstants.GREEN_MASK) >>> 8) / 255, |
|
10445 (col & PConstants.BLUE_MASK) / 255 ]; |
|
10446 |
|
10447 uniformf("lights.color.3d." + lightCount, programObject3D, "lights" + lightCount + ".color", normalizedCol); |
|
10448 uniformf("lights.position.3d." + lightCount, programObject3D, "lights" + lightCount + ".position", dir); |
|
10449 uniformi("lights.type.3d." + lightCount, programObject3D, "lights" + lightCount + ".type", 1); |
|
10450 uniformi("lightCount3d", programObject3D, "lightCount", ++lightCount); |
|
10451 }; |
|
10452 |
|
10453 /** |
|
10454 * Sets the falloff rates for point lights, spot lights, and ambient lights. |
|
10455 * The parameters are used to determine the falloff with the following equation: |
|
10456 * |
|
10457 * d = distance from light position to vertex position |
|
10458 * falloff = 1 / (CONSTANT + d * LINEAR + (d*d) * QUADRATIC) |
|
10459 * |
|
10460 * Like <b>fill()</b>, it affects only the elements which are created after it in the |
|
10461 * code. The default value if <b>LightFalloff(1.0, 0.0, 0.0)</b>. Thinking about an |
|
10462 * ambient light with a falloff can be tricky. It is used, for example, if you |
|
10463 * wanted a region of your scene to be lit ambiently one color and another region |
|
10464 * to be lit ambiently by another color, you would use an ambient light with location |
|
10465 * and falloff. You can think of it as a point light that doesn't care which direction |
|
10466 * a surface is facing. |
|
10467 * |
|
10468 * @param {int | float} constant constant value for determining falloff |
|
10469 * @param {int | float} linear linear value for determining falloff |
|
10470 * @param {int | float} quadratic quadratic value for determining falloff |
|
10471 * |
|
10472 * @returns none |
|
10473 * |
|
10474 * @see lights |
|
10475 * @see ambientLight |
|
10476 * @see pointLight |
|
10477 * @see spotLight |
|
10478 * @see lightSpecular |
|
10479 */ |
|
10480 Drawing2D.prototype.lightFalloff = DrawingShared.prototype.a3DOnlyFunction; |
|
10481 |
|
10482 Drawing3D.prototype.lightFalloff = function(constant, linear, quadratic) { |
|
10483 curContext.useProgram(programObject3D); |
|
10484 uniformf("falloff3d", programObject3D, "falloff", [constant, linear, quadratic]); |
|
10485 }; |
|
10486 |
|
10487 /** |
|
10488 * Sets the specular color for lights. Like <b>fill()</b>, it affects only the |
|
10489 * elements which are created after it in the code. Specular refers to light |
|
10490 * which bounces off a surface in a perferred direction (rather than bouncing |
|
10491 * in all directions like a diffuse light) and is used for creating highlights. |
|
10492 * The specular quality of a light interacts with the specular material qualities |
|
10493 * set through the <b>specular()</b> and <b>shininess()</b> functions. |
|
10494 * |
|
10495 * @param {int | float} r red or hue value |
|
10496 * @param {int | float} g green or hue value |
|
10497 * @param {int | float} b blue or hue value |
|
10498 * |
|
10499 * @returns none |
|
10500 * |
|
10501 * @see lights |
|
10502 * @see ambientLight |
|
10503 * @see pointLight |
|
10504 * @see spotLight |
|
10505 */ |
|
10506 Drawing2D.prototype.lightSpecular = DrawingShared.prototype.a3DOnlyFunction; |
|
10507 |
|
10508 Drawing3D.prototype.lightSpecular = function(r, g, b) { |
|
10509 |
|
10510 // Instead of calling p.color, we do the calculations ourselves to |
|
10511 // reduce property lookups. |
|
10512 var col = color$4(r, g, b, 0); |
|
10513 var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, |
|
10514 ((col & PConstants.GREEN_MASK) >>> 8) / 255, |
|
10515 (col & PConstants.BLUE_MASK) / 255 ]; |
|
10516 |
|
10517 curContext.useProgram(programObject3D); |
|
10518 uniformf("specular3d", programObject3D, "specular", normalizedCol); |
|
10519 }; |
|
10520 |
|
10521 /** |
|
10522 * Sets the default ambient light, directional light, falloff, and specular |
|
10523 * values. The defaults are ambientLight(128, 128, 128) and |
|
10524 * directionalLight(128, 128, 128, 0, 0, -1), lightFalloff(1, 0, 0), and |
|
10525 * lightSpecular(0, 0, 0). Lights need to be included in the draw() to remain |
|
10526 * persistent in a looping program. Placing them in the setup() of a looping |
|
10527 * program will cause them to only have an effect the first time through the |
|
10528 * loop. |
|
10529 * |
|
10530 * @returns none |
|
10531 * |
|
10532 * @see ambientLight |
|
10533 * @see directionalLight |
|
10534 * @see pointLight |
|
10535 * @see spotLight |
|
10536 * @see noLights |
|
10537 * |
|
10538 */ |
|
10539 p.lights = function() { |
|
10540 p.ambientLight(128, 128, 128); |
|
10541 p.directionalLight(128, 128, 128, 0, 0, -1); |
|
10542 p.lightFalloff(1, 0, 0); |
|
10543 p.lightSpecular(0, 0, 0); |
|
10544 }; |
|
10545 |
|
10546 /** |
|
10547 * Adds a point light. Lights need to be included in the <b>draw()</b> to remain |
|
10548 * persistent in a looping program. Placing them in the <b>setup()</b> of a |
|
10549 * looping program will cause them to only have an effect the first time through |
|
10550 * the loop. The affect of the <b>r</b>, <b>g</b>, and <b>b</b> parameters |
|
10551 * is determined by the current color mode. The <b>x</b>, <b>y</b>, and <b>z</b> |
|
10552 * parameters set the position of the light. |
|
10553 * |
|
10554 * @param {int | float} r red or hue value |
|
10555 * @param {int | float} g green or hue value |
|
10556 * @param {int | float} b blue or hue value |
|
10557 * @param {int | float} x x coordinate of the light |
|
10558 * @param {int | float} y y coordinate of the light |
|
10559 * @param {int | float} z z coordinate of the light |
|
10560 * |
|
10561 * @returns none |
|
10562 * |
|
10563 * @see lights |
|
10564 * @see directionalLight |
|
10565 * @see ambientLight |
|
10566 * @see spotLight |
|
10567 */ |
|
10568 Drawing2D.prototype.pointLight = DrawingShared.prototype.a3DOnlyFunction; |
|
10569 |
|
10570 Drawing3D.prototype.pointLight = function(r, g, b, x, y, z) { |
|
10571 if (lightCount === PConstants.MAX_LIGHTS) { |
|
10572 throw "can only create " + PConstants.MAX_LIGHTS + " lights"; |
|
10573 } |
|
10574 |
|
10575 // Place the point in view space once instead of once per vertex |
|
10576 // in the shader. |
|
10577 var pos = new PVector(x, y, z); |
|
10578 var view = new PMatrix3D(); |
|
10579 view.scale(1, -1, 1); |
|
10580 view.apply(modelView.array()); |
|
10581 view.mult(pos, pos); |
|
10582 |
|
10583 // Instead of calling p.color, we do the calculations ourselves to |
|
10584 // reduce property lookups. |
|
10585 var col = color$4(r, g, b, 0); |
|
10586 var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, |
|
10587 ((col & PConstants.GREEN_MASK) >>> 8) / 255, |
|
10588 (col & PConstants.BLUE_MASK) / 255 ]; |
|
10589 |
|
10590 curContext.useProgram(programObject3D); |
|
10591 uniformf("lights.color.3d." + lightCount, programObject3D, "lights" + lightCount + ".color", normalizedCol); |
|
10592 uniformf("lights.position.3d." + lightCount, programObject3D, "lights" + lightCount + ".position", pos.array()); |
|
10593 uniformi("lights.type.3d." + lightCount, programObject3D, "lights" + lightCount + ".type", 2); |
|
10594 uniformi("lightCount3d", programObject3D, "lightCount", ++lightCount); |
|
10595 }; |
|
10596 |
|
10597 /** |
|
10598 * Disable all lighting. Lighting is turned off by default and enabled with |
|
10599 * the lights() method. This function can be used to disable lighting so |
|
10600 * that 2D geometry (which does not require lighting) can be drawn after a |
|
10601 * set of lighted 3D geometry. |
|
10602 * |
|
10603 * @returns none |
|
10604 * |
|
10605 * @see lights |
|
10606 */ |
|
10607 Drawing2D.prototype.noLights = DrawingShared.prototype.a3DOnlyFunction; |
|
10608 |
|
10609 Drawing3D.prototype.noLights = function() { |
|
10610 lightCount = 0; |
|
10611 curContext.useProgram(programObject3D); |
|
10612 uniformi("lightCount3d", programObject3D, "lightCount", lightCount); |
|
10613 }; |
|
10614 |
|
10615 /** |
|
10616 * Adds a spot light. Lights need to be included in the <b>draw()</b> to |
|
10617 * remain persistent in a looping program. Placing them in the <b>setup()</b> |
|
10618 * of a looping program will cause them to only have an effect the first time |
|
10619 * through the loop. The affect of the <b>r</b>, <b>g</b>, and <b>b</b> parameters |
|
10620 * is determined by the current color mode. The <b>x</b>, <b>y</b>, and <b>z</b> |
|
10621 * parameters specify the position of the light and <b>nx</b>, <b>ny</b>, <b>nz</b> |
|
10622 * specify the direction or light. The angle parameter affects <b>angle</b> of the |
|
10623 * spotlight cone. |
|
10624 * |
|
10625 * @param {int | float} r red or hue value |
|
10626 * @param {int | float} g green or hue value |
|
10627 * @param {int | float} b blue or hue value |
|
10628 * @param {int | float} x coordinate of the light |
|
10629 * @param {int | float} y coordinate of the light |
|
10630 * @param {int | float} z coordinate of the light |
|
10631 * @param {int | float} nx direction along the x axis |
|
10632 * @param {int | float} ny direction along the y axis |
|
10633 * @param {int | float} nz direction along the z axis |
|
10634 * @param {float} angle angle of the spotlight cone |
|
10635 * @param {float} concentration exponent determining the center bias of the cone |
|
10636 * |
|
10637 * @returns none |
|
10638 * |
|
10639 * @see lights |
|
10640 * @see directionalLight |
|
10641 * @see ambientLight |
|
10642 * @see pointLight |
|
10643 */ |
|
10644 Drawing2D.prototype.spotLight = DrawingShared.prototype.a3DOnlyFunction; |
|
10645 |
|
10646 Drawing3D.prototype.spotLight = function(r, g, b, x, y, z, nx, ny, nz, angle, concentration) { |
|
10647 if (lightCount === PConstants.MAX_LIGHTS) { |
|
10648 throw "can only create " + PConstants.MAX_LIGHTS + " lights"; |
|
10649 } |
|
10650 |
|
10651 curContext.useProgram(programObject3D); |
|
10652 |
|
10653 // multiply the position and direction by the model view matrix |
|
10654 // once per object rather than once per vertex. |
|
10655 var pos = new PVector(x, y, z); |
|
10656 var mvm = new PMatrix3D(); |
|
10657 mvm.scale(1, -1, 1); |
|
10658 mvm.apply(modelView.array()); |
|
10659 mvm.mult(pos, pos); |
|
10660 |
|
10661 // Convert to array since we need to directly access the elements. |
|
10662 mvm = mvm.array(); |
|
10663 |
|
10664 // We need to multiply the direction by the model view matrix, but |
|
10665 // the mult function checks the w component of the vector, if it isn't |
|
10666 // present, it uses 1, so we use a very small value as a work around. |
|
10667 var dir = [ |
|
10668 mvm[0] * nx + mvm[4] * ny + mvm[8] * nz, |
|
10669 mvm[1] * nx + mvm[5] * ny + mvm[9] * nz, |
|
10670 mvm[2] * nx + mvm[6] * ny + mvm[10] * nz |
|
10671 ]; |
|
10672 |
|
10673 // Instead of calling p.color, we do the calculations ourselves to |
|
10674 // reduce property lookups. |
|
10675 var col = color$4(r, g, b, 0); |
|
10676 var normalizedCol = [ ((col & PConstants.RED_MASK) >>> 16) / 255, |
|
10677 ((col & PConstants.GREEN_MASK) >>> 8) / 255, |
|
10678 (col & PConstants.BLUE_MASK) / 255 ]; |
|
10679 |
|
10680 uniformf("lights.color.3d." + lightCount, programObject3D, "lights" + lightCount + ".color", normalizedCol); |
|
10681 uniformf("lights.position.3d." + lightCount, programObject3D, "lights" + lightCount + ".position", pos.array()); |
|
10682 uniformf("lights.direction.3d." + lightCount, programObject3D, "lights" + lightCount + ".direction", dir); |
|
10683 uniformf("lights.concentration.3d." + lightCount, programObject3D, "lights" + lightCount + ".concentration", concentration); |
|
10684 uniformf("lights.angle.3d." + lightCount, programObject3D, "lights" + lightCount + ".angle", angle); |
|
10685 uniformi("lights.type.3d." + lightCount, programObject3D, "lights" + lightCount + ".type", 3); |
|
10686 uniformi("lightCount3d", programObject3D, "lightCount", ++lightCount); |
|
10687 }; |
|
10688 |
|
10689 //////////////////////////////////////////////////////////////////////////// |
|
10690 // Camera functions |
|
10691 //////////////////////////////////////////////////////////////////////////// |
|
10692 |
|
10693 /** |
|
10694 * The <b>beginCamera()</b> and <b>endCamera()</b> functions enable advanced customization of the camera space. |
|
10695 * The functions are useful if you want to more control over camera movement, however for most users, the <b>camera()</b> |
|
10696 * function will be sufficient.<br /><br />The camera functions will replace any transformations (such as <b>rotate()</b> |
|
10697 * or <b>translate()</b>) that occur before them in <b>draw()</b>, but they will not automatically replace the camera |
|
10698 * transform itself. For this reason, camera functions should be placed at the beginning of <b>draw()</b> (so that |
|
10699 * transformations happen afterwards), and the <b>camera()</b> function can be used after <b>beginCamera()</b> if |
|
10700 * you want to reset the camera before applying transformations.<br /><br />This function sets the matrix mode to the |
|
10701 * camera matrix so calls such as <b>translate()</b>, <b>rotate()</b>, applyMatrix() and resetMatrix() affect the camera. |
|
10702 * <b>beginCamera()</b> should always be used with a following <b>endCamera()</b> and pairs of <b>beginCamera()</b> and |
|
10703 * <b>endCamera()</b> cannot be nested. |
|
10704 * |
|
10705 * @see camera |
|
10706 * @see endCamera |
|
10707 * @see applyMatrix |
|
10708 * @see resetMatrix |
|
10709 * @see translate |
|
10710 * @see rotate |
|
10711 * @see scale |
|
10712 */ |
|
10713 Drawing2D.prototype.beginCamera = function() { |
|
10714 throw ("beginCamera() is not available in 2D mode"); |
|
10715 }; |
|
10716 |
|
10717 Drawing3D.prototype.beginCamera = function() { |
|
10718 if (manipulatingCamera) { |
|
10719 throw ("You cannot call beginCamera() again before calling endCamera()"); |
|
10720 } |
|
10721 manipulatingCamera = true; |
|
10722 modelView = cameraInv; |
|
10723 modelViewInv = cam; |
|
10724 }; |
|
10725 |
|
10726 /** |
|
10727 * The <b>beginCamera()</b> and <b>endCamera()</b> functions enable advanced customization of the camera space. |
|
10728 * Please see the reference for <b>beginCamera()</b> for a description of how the functions are used. |
|
10729 * |
|
10730 * @see beginCamera |
|
10731 */ |
|
10732 Drawing2D.prototype.endCamera = function() { |
|
10733 throw ("endCamera() is not available in 2D mode"); |
|
10734 }; |
|
10735 |
|
10736 Drawing3D.prototype.endCamera = function() { |
|
10737 if (!manipulatingCamera) { |
|
10738 throw ("You cannot call endCamera() before calling beginCamera()"); |
|
10739 } |
|
10740 modelView.set(cam); |
|
10741 modelViewInv.set(cameraInv); |
|
10742 manipulatingCamera = false; |
|
10743 }; |
|
10744 |
|
10745 /** |
|
10746 * Sets the position of the camera through setting the eye position, the center of the scene, and which axis is facing |
|
10747 * upward. Moving the eye position and the direction it is pointing (the center of the scene) allows the images to be |
|
10748 * seen from different angles. The version without any parameters sets the camera to the default position, pointing to |
|
10749 * the center of the display window with the Y axis as up. The default values are camera(width/2.0, height/2.0, |
|
10750 * (height/2.0) / tan(PI*60.0 / 360.0), width/2.0, height/2.0, 0, 0, 1, 0). This function is similar to gluLookAt() |
|
10751 * in OpenGL, but it first clears the current camera settings. |
|
10752 * |
|
10753 * @param {float} eyeX x-coordinate for the eye |
|
10754 * @param {float} eyeY y-coordinate for the eye |
|
10755 * @param {float} eyeZ z-coordinate for the eye |
|
10756 * @param {float} centerX x-coordinate for the center of the scene |
|
10757 * @param {float} centerY y-coordinate for the center of the scene |
|
10758 * @param {float} centerZ z-coordinate for the center of the scene |
|
10759 * @param {float} upX usually 0.0, 1.0, -1.0 |
|
10760 * @param {float} upY usually 0.0, 1.0, -1.0 |
|
10761 * @param {float} upZ usually 0.0, 1.0, -1.0 |
|
10762 * |
|
10763 * @see beginCamera |
|
10764 * @see endCamera |
|
10765 * @see frustum |
|
10766 */ |
|
10767 p.camera = function(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ) { |
|
10768 if (eyeX === undef) { |
|
10769 // Workaround if createGraphics is used. |
|
10770 cameraX = p.width / 2; |
|
10771 cameraY = p.height / 2; |
|
10772 cameraZ = cameraY / Math.tan(cameraFOV / 2); |
|
10773 eyeX = cameraX; |
|
10774 eyeY = cameraY; |
|
10775 eyeZ = cameraZ; |
|
10776 centerX = cameraX; |
|
10777 centerY = cameraY; |
|
10778 centerZ = 0; |
|
10779 upX = 0; |
|
10780 upY = 1; |
|
10781 upZ = 0; |
|
10782 } |
|
10783 |
|
10784 var z = new PVector(eyeX - centerX, eyeY - centerY, eyeZ - centerZ); |
|
10785 var y = new PVector(upX, upY, upZ); |
|
10786 z.normalize(); |
|
10787 var x = PVector.cross(y, z); |
|
10788 y = PVector.cross(z, x); |
|
10789 x.normalize(); |
|
10790 y.normalize(); |
|
10791 |
|
10792 var xX = x.x, |
|
10793 xY = x.y, |
|
10794 xZ = x.z; |
|
10795 |
|
10796 var yX = y.x, |
|
10797 yY = y.y, |
|
10798 yZ = y.z; |
|
10799 |
|
10800 var zX = z.x, |
|
10801 zY = z.y, |
|
10802 zZ = z.z; |
|
10803 |
|
10804 cam.set(xX, xY, xZ, 0, yX, yY, yZ, 0, zX, zY, zZ, 0, 0, 0, 0, 1); |
|
10805 |
|
10806 cam.translate(-eyeX, -eyeY, -eyeZ); |
|
10807 |
|
10808 cameraInv.reset(); |
|
10809 cameraInv.invApply(xX, xY, xZ, 0, yX, yY, yZ, 0, zX, zY, zZ, 0, 0, 0, 0, 1); |
|
10810 |
|
10811 cameraInv.translate(eyeX, eyeY, eyeZ); |
|
10812 |
|
10813 modelView.set(cam); |
|
10814 modelViewInv.set(cameraInv); |
|
10815 }; |
|
10816 |
|
10817 /** |
|
10818 * Sets a perspective projection applying foreshortening, making distant objects appear smaller than closer ones. The |
|
10819 * parameters define a viewing volume with the shape of truncated pyramid. Objects near to the front of the volume appear |
|
10820 * their actual size, while farther objects appear smaller. This projection simulates the perspective of the world more |
|
10821 * accurately than orthographic projection. The version of perspective without parameters sets the default perspective and |
|
10822 * the version with four parameters allows the programmer to set the area precisely. The default values are: |
|
10823 * perspective(PI/3.0, width/height, cameraZ/10.0, cameraZ*10.0) where cameraZ is ((height/2.0) / tan(PI*60.0/360.0)); |
|
10824 * |
|
10825 * @param {float} fov field-of-view angle (in radians) for vertical direction |
|
10826 * @param {float} aspect ratio of width to height |
|
10827 * @param {float} zNear z-position of nearest clipping plane |
|
10828 * @param {float} zFar z-positions of farthest clipping plane |
|
10829 */ |
|
10830 p.perspective = function(fov, aspect, near, far) { |
|
10831 if (arguments.length === 0) { |
|
10832 //in case canvas is resized |
|
10833 cameraY = curElement.height / 2; |
|
10834 cameraZ = cameraY / Math.tan(cameraFOV / 2); |
|
10835 cameraNear = cameraZ / 10; |
|
10836 cameraFar = cameraZ * 10; |
|
10837 cameraAspect = p.width / p.height; |
|
10838 fov = cameraFOV; |
|
10839 aspect = cameraAspect; |
|
10840 near = cameraNear; |
|
10841 far = cameraFar; |
|
10842 } |
|
10843 |
|
10844 var yMax, yMin, xMax, xMin; |
|
10845 yMax = near * Math.tan(fov / 2); |
|
10846 yMin = -yMax; |
|
10847 xMax = yMax * aspect; |
|
10848 xMin = yMin * aspect; |
|
10849 p.frustum(xMin, xMax, yMin, yMax, near, far); |
|
10850 }; |
|
10851 |
|
10852 /** |
|
10853 * Sets a perspective matrix defined through the parameters. Works like glFrustum, except it wipes out the current |
|
10854 * perspective matrix rather than muliplying itself with it. |
|
10855 * |
|
10856 * @param {float} left left coordinate of the clipping plane |
|
10857 * @param {float} right right coordinate of the clipping plane |
|
10858 * @param {float} bottom bottom coordinate of the clipping plane |
|
10859 * @param {float} top top coordinate of the clipping plane |
|
10860 * @param {float} near near coordinate of the clipping plane |
|
10861 * @param {float} far far coordinate of the clipping plane |
|
10862 * |
|
10863 * @see beginCamera |
|
10864 * @see camera |
|
10865 * @see endCamera |
|
10866 * @see perspective |
|
10867 */ |
|
10868 Drawing2D.prototype.frustum = function() { |
|
10869 throw("Processing.js: frustum() is not supported in 2D mode"); |
|
10870 }; |
|
10871 |
|
10872 Drawing3D.prototype.frustum = function(left, right, bottom, top, near, far) { |
|
10873 frustumMode = true; |
|
10874 projection = new PMatrix3D(); |
|
10875 projection.set((2 * near) / (right - left), 0, (right + left) / (right - left), |
|
10876 0, 0, (2 * near) / (top - bottom), (top + bottom) / (top - bottom), |
|
10877 0, 0, 0, -(far + near) / (far - near), -(2 * far * near) / (far - near), |
|
10878 0, 0, -1, 0); |
|
10879 var proj = new PMatrix3D(); |
|
10880 proj.set(projection); |
|
10881 proj.transpose(); |
|
10882 curContext.useProgram(programObject2D); |
|
10883 uniformMatrix("projection2d", programObject2D, "projection", false, proj.array()); |
|
10884 curContext.useProgram(programObject3D); |
|
10885 uniformMatrix("projection3d", programObject3D, "projection", false, proj.array()); |
|
10886 curContext.useProgram(programObjectUnlitShape); |
|
10887 uniformMatrix("uProjectionUS", programObjectUnlitShape, "uProjection", false, proj.array()); |
|
10888 }; |
|
10889 |
|
10890 /** |
|
10891 * Sets an orthographic projection and defines a parallel clipping volume. All objects with the same dimension appear |
|
10892 * the same size, regardless of whether they are near or far from the camera. The parameters to this function specify |
|
10893 * the clipping volume where left and right are the minimum and maximum x values, top and bottom are the minimum and |
|
10894 * maximum y values, and near and far are the minimum and maximum z values. If no parameters are given, the default |
|
10895 * is used: ortho(0, width, 0, height, -10, 10). |
|
10896 * |
|
10897 * @param {float} left left plane of the clipping volume |
|
10898 * @param {float} right right plane of the clipping volume |
|
10899 * @param {float} bottom bottom plane of the clipping volume |
|
10900 * @param {float} top top plane of the clipping volume |
|
10901 * @param {float} near maximum distance from the origin to the viewer |
|
10902 * @param {float} far maximum distance from the origin away from the viewer |
|
10903 */ |
|
10904 p.ortho = function(left, right, bottom, top, near, far) { |
|
10905 if (arguments.length === 0) { |
|
10906 left = 0; |
|
10907 right = p.width; |
|
10908 bottom = 0; |
|
10909 top = p.height; |
|
10910 near = -10; |
|
10911 far = 10; |
|
10912 } |
|
10913 |
|
10914 var x = 2 / (right - left); |
|
10915 var y = 2 / (top - bottom); |
|
10916 var z = -2 / (far - near); |
|
10917 |
|
10918 var tx = -(right + left) / (right - left); |
|
10919 var ty = -(top + bottom) / (top - bottom); |
|
10920 var tz = -(far + near) / (far - near); |
|
10921 |
|
10922 projection = new PMatrix3D(); |
|
10923 projection.set(x, 0, 0, tx, 0, y, 0, ty, 0, 0, z, tz, 0, 0, 0, 1); |
|
10924 |
|
10925 var proj = new PMatrix3D(); |
|
10926 proj.set(projection); |
|
10927 proj.transpose(); |
|
10928 curContext.useProgram(programObject2D); |
|
10929 uniformMatrix("projection2d", programObject2D, "projection", false, proj.array()); |
|
10930 curContext.useProgram(programObject3D); |
|
10931 uniformMatrix("projection3d", programObject3D, "projection", false, proj.array()); |
|
10932 curContext.useProgram(programObjectUnlitShape); |
|
10933 uniformMatrix("uProjectionUS", programObjectUnlitShape, "uProjection", false, proj.array()); |
|
10934 frustumMode = false; |
|
10935 }; |
|
10936 /** |
|
10937 * The printProjection() prints the current projection matrix to the text window. |
|
10938 */ |
|
10939 p.printProjection = function() { |
|
10940 projection.print(); |
|
10941 }; |
|
10942 /** |
|
10943 * The printCamera() function prints the current camera matrix. |
|
10944 */ |
|
10945 p.printCamera = function() { |
|
10946 cam.print(); |
|
10947 }; |
|
10948 |
|
10949 //////////////////////////////////////////////////////////////////////////// |
|
10950 // Shapes |
|
10951 //////////////////////////////////////////////////////////////////////////// |
|
10952 /** |
|
10953 * The box() function renders a box. A box is an extruded rectangle. A box with equal dimension on all sides is a cube. |
|
10954 * Calling this function with only one parameter will create a cube. |
|
10955 * |
|
10956 * @param {int|float} w dimension of the box in the x-dimension |
|
10957 * @param {int|float} h dimension of the box in the y-dimension |
|
10958 * @param {int|float} d dimension of the box in the z-dimension |
|
10959 */ |
|
10960 Drawing2D.prototype.box = DrawingShared.prototype.a3DOnlyFunction; |
|
10961 |
|
10962 Drawing3D.prototype.box = function(w, h, d) { |
|
10963 // user can uniformly scale the box by |
|
10964 // passing in only one argument. |
|
10965 if (!h || !d) { |
|
10966 h = d = w; |
|
10967 } |
|
10968 |
|
10969 // Modeling transformation |
|
10970 var model = new PMatrix3D(); |
|
10971 model.scale(w, h, d); |
|
10972 |
|
10973 // viewing transformation needs to have Y flipped |
|
10974 // becuase that's what Processing does. |
|
10975 var view = new PMatrix3D(); |
|
10976 view.scale(1, -1, 1); |
|
10977 view.apply(modelView.array()); |
|
10978 view.transpose(); |
|
10979 |
|
10980 if (doFill) { |
|
10981 curContext.useProgram(programObject3D); |
|
10982 uniformMatrix("model3d", programObject3D, "model", false, model.array()); |
|
10983 uniformMatrix("view3d", programObject3D, "view", false, view.array()); |
|
10984 // fix stitching problems. (lines get occluded by triangles |
|
10985 // since they share the same depth values). This is not entirely |
|
10986 // working, but it's a start for drawing the outline. So |
|
10987 // developers can start playing around with styles. |
|
10988 curContext.enable(curContext.POLYGON_OFFSET_FILL); |
|
10989 curContext.polygonOffset(1, 1); |
|
10990 uniformf("color3d", programObject3D, "color", fillStyle); |
|
10991 |
|
10992 // Calculating the normal matrix can be expensive, so only |
|
10993 // do it if it's necessary |
|
10994 if(lightCount > 0){ |
|
10995 // Create the normal transformation matrix |
|
10996 var v = new PMatrix3D(); |
|
10997 v.set(view); |
|
10998 |
|
10999 var m = new PMatrix3D(); |
|
11000 m.set(model); |
|
11001 |
|
11002 v.mult(m); |
|
11003 |
|
11004 var normalMatrix = new PMatrix3D(); |
|
11005 normalMatrix.set(v); |
|
11006 normalMatrix.invert(); |
|
11007 normalMatrix.transpose(); |
|
11008 |
|
11009 uniformMatrix("normalTransform3d", programObject3D, "normalTransform", false, normalMatrix.array()); |
|
11010 vertexAttribPointer("normal3d", programObject3D, "Normal", 3, boxNormBuffer); |
|
11011 } |
|
11012 else{ |
|
11013 disableVertexAttribPointer("normal3d", programObject3D, "Normal"); |
|
11014 } |
|
11015 |
|
11016 vertexAttribPointer("vertex3d", programObject3D, "Vertex", 3, boxBuffer); |
|
11017 |
|
11018 // Turn off per vertex colors |
|
11019 disableVertexAttribPointer("aColor3d", programObject3D, "aColor"); |
|
11020 disableVertexAttribPointer("aTexture3d", programObject3D, "aTexture"); |
|
11021 |
|
11022 curContext.drawArrays(curContext.TRIANGLES, 0, boxVerts.length / 3); |
|
11023 curContext.disable(curContext.POLYGON_OFFSET_FILL); |
|
11024 } |
|
11025 |
|
11026 if (lineWidth > 0 && doStroke) { |
|
11027 curContext.useProgram(programObject2D); |
|
11028 uniformMatrix("model2d", programObject2D, "model", false, model.array()); |
|
11029 uniformMatrix("view2d", programObject2D, "view", false, view.array()); |
|
11030 uniformf("color2d", programObject2D, "color", strokeStyle); |
|
11031 uniformi("picktype2d", programObject2D, "picktype", 0); |
|
11032 vertexAttribPointer("vertex2d", programObject2D, "Vertex", 3, boxOutlineBuffer); |
|
11033 disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); |
|
11034 curContext.drawArrays(curContext.LINES, 0, boxOutlineVerts.length / 3); |
|
11035 } |
|
11036 }; |
|
11037 |
|
11038 /** |
|
11039 * The initSphere() function is a helper function used by <b>sphereDetail()</b> |
|
11040 * This function creates and stores sphere vertices every time the user changes sphere detail. |
|
11041 * |
|
11042 * @see #sphereDetail |
|
11043 */ |
|
11044 var initSphere = function() { |
|
11045 var i; |
|
11046 sphereVerts = []; |
|
11047 |
|
11048 for (i = 0; i < sphereDetailU; i++) { |
|
11049 sphereVerts.push(0); |
|
11050 sphereVerts.push(-1); |
|
11051 sphereVerts.push(0); |
|
11052 sphereVerts.push(sphereX[i]); |
|
11053 sphereVerts.push(sphereY[i]); |
|
11054 sphereVerts.push(sphereZ[i]); |
|
11055 } |
|
11056 sphereVerts.push(0); |
|
11057 sphereVerts.push(-1); |
|
11058 sphereVerts.push(0); |
|
11059 sphereVerts.push(sphereX[0]); |
|
11060 sphereVerts.push(sphereY[0]); |
|
11061 sphereVerts.push(sphereZ[0]); |
|
11062 |
|
11063 var v1, v11, v2; |
|
11064 |
|
11065 // middle rings |
|
11066 var voff = 0; |
|
11067 for (i = 2; i < sphereDetailV; i++) { |
|
11068 v1 = v11 = voff; |
|
11069 voff += sphereDetailU; |
|
11070 v2 = voff; |
|
11071 for (var j = 0; j < sphereDetailU; j++) { |
|
11072 sphereVerts.push(sphereX[v1]); |
|
11073 sphereVerts.push(sphereY[v1]); |
|
11074 sphereVerts.push(sphereZ[v1++]); |
|
11075 sphereVerts.push(sphereX[v2]); |
|
11076 sphereVerts.push(sphereY[v2]); |
|
11077 sphereVerts.push(sphereZ[v2++]); |
|
11078 } |
|
11079 |
|
11080 // close each ring |
|
11081 v1 = v11; |
|
11082 v2 = voff; |
|
11083 |
|
11084 sphereVerts.push(sphereX[v1]); |
|
11085 sphereVerts.push(sphereY[v1]); |
|
11086 sphereVerts.push(sphereZ[v1]); |
|
11087 sphereVerts.push(sphereX[v2]); |
|
11088 sphereVerts.push(sphereY[v2]); |
|
11089 sphereVerts.push(sphereZ[v2]); |
|
11090 } |
|
11091 |
|
11092 // add the northern cap |
|
11093 for (i = 0; i < sphereDetailU; i++) { |
|
11094 v2 = voff + i; |
|
11095 |
|
11096 sphereVerts.push(sphereX[v2]); |
|
11097 sphereVerts.push(sphereY[v2]); |
|
11098 sphereVerts.push(sphereZ[v2]); |
|
11099 sphereVerts.push(0); |
|
11100 sphereVerts.push(1); |
|
11101 sphereVerts.push(0); |
|
11102 } |
|
11103 |
|
11104 sphereVerts.push(sphereX[voff]); |
|
11105 sphereVerts.push(sphereY[voff]); |
|
11106 sphereVerts.push(sphereZ[voff]); |
|
11107 sphereVerts.push(0); |
|
11108 sphereVerts.push(1); |
|
11109 sphereVerts.push(0); |
|
11110 |
|
11111 //set the buffer data |
|
11112 curContext.bindBuffer(curContext.ARRAY_BUFFER, sphereBuffer); |
|
11113 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(sphereVerts), curContext.STATIC_DRAW); |
|
11114 }; |
|
11115 |
|
11116 /** |
|
11117 * The sphereDetail() function controls the detail used to render a sphere by adjusting the number of |
|
11118 * vertices of the sphere mesh. The default resolution is 30, which creates |
|
11119 * a fairly detailed sphere definition with vertices every 360/30 = 12 |
|
11120 * degrees. If you're going to render a great number of spheres per frame, |
|
11121 * it is advised to reduce the level of detail using this function. |
|
11122 * The setting stays active until <b>sphereDetail()</b> is called again with |
|
11123 * a new parameter and so should <i>not</i> be called prior to every |
|
11124 * <b>sphere()</b> statement, unless you wish to render spheres with |
|
11125 * different settings, e.g. using less detail for smaller spheres or ones |
|
11126 * further away from the camera. To control the detail of the horizontal |
|
11127 * and vertical resolution independently, use the version of the functions |
|
11128 * with two parameters. Calling this function with one parameter sets the number of segments |
|
11129 *(minimum of 3) used per full circle revolution. This is equivalent to calling the function with |
|
11130 * two identical values. |
|
11131 * |
|
11132 * @param {int} ures number of segments used horizontally (longitudinally) per full circle revolution |
|
11133 * @param {int} vres number of segments used vertically (latitudinally) from top to bottom |
|
11134 * |
|
11135 * @see #sphere() |
|
11136 */ |
|
11137 p.sphereDetail = function(ures, vres) { |
|
11138 var i; |
|
11139 |
|
11140 if (arguments.length === 1) { |
|
11141 ures = vres = arguments[0]; |
|
11142 } |
|
11143 |
|
11144 if (ures < 3) { |
|
11145 ures = 3; |
|
11146 } // force a minimum res |
|
11147 if (vres < 2) { |
|
11148 vres = 2; |
|
11149 } // force a minimum res |
|
11150 // if it hasn't changed do nothing |
|
11151 if ((ures === sphereDetailU) && (vres === sphereDetailV)) { |
|
11152 return; |
|
11153 } |
|
11154 |
|
11155 var delta = PConstants.SINCOS_LENGTH / ures; |
|
11156 var cx = new Float32Array(ures); |
|
11157 var cz = new Float32Array(ures); |
|
11158 // calc unit circle in XZ plane |
|
11159 for (i = 0; i < ures; i++) { |
|
11160 cx[i] = cosLUT[((i * delta) % PConstants.SINCOS_LENGTH) | 0]; |
|
11161 cz[i] = sinLUT[((i * delta) % PConstants.SINCOS_LENGTH) | 0]; |
|
11162 } |
|
11163 |
|
11164 // computing vertexlist |
|
11165 // vertexlist starts at south pole |
|
11166 var vertCount = ures * (vres - 1) + 2; |
|
11167 var currVert = 0; |
|
11168 |
|
11169 // re-init arrays to store vertices |
|
11170 sphereX = new Float32Array(vertCount); |
|
11171 sphereY = new Float32Array(vertCount); |
|
11172 sphereZ = new Float32Array(vertCount); |
|
11173 |
|
11174 var angle_step = (PConstants.SINCOS_LENGTH * 0.5) / vres; |
|
11175 var angle = angle_step; |
|
11176 |
|
11177 // step along Y axis |
|
11178 for (i = 1; i < vres; i++) { |
|
11179 var curradius = sinLUT[(angle % PConstants.SINCOS_LENGTH) | 0]; |
|
11180 var currY = -cosLUT[(angle % PConstants.SINCOS_LENGTH) | 0]; |
|
11181 for (var j = 0; j < ures; j++) { |
|
11182 sphereX[currVert] = cx[j] * curradius; |
|
11183 sphereY[currVert] = currY; |
|
11184 sphereZ[currVert++] = cz[j] * curradius; |
|
11185 } |
|
11186 angle += angle_step; |
|
11187 } |
|
11188 sphereDetailU = ures; |
|
11189 sphereDetailV = vres; |
|
11190 |
|
11191 // make the sphere verts and norms |
|
11192 initSphere(); |
|
11193 }; |
|
11194 |
|
11195 /** |
|
11196 * The sphere() function draws a sphere with radius r centered at coordinate 0, 0, 0. |
|
11197 * A sphere is a hollow ball made from tessellated triangles. |
|
11198 * |
|
11199 * @param {int|float} r the radius of the sphere |
|
11200 */ |
|
11201 Drawing2D.prototype.sphere = DrawingShared.prototype.a3DOnlyFunction; |
|
11202 |
|
11203 Drawing3D.prototype.sphere = function() { |
|
11204 var sRad = arguments[0]; |
|
11205 |
|
11206 if ((sphereDetailU < 3) || (sphereDetailV < 2)) { |
|
11207 p.sphereDetail(30); |
|
11208 } |
|
11209 |
|
11210 // Modeling transformation |
|
11211 var model = new PMatrix3D(); |
|
11212 model.scale(sRad, sRad, sRad); |
|
11213 |
|
11214 // viewing transformation needs to have Y flipped |
|
11215 // becuase that's what Processing does. |
|
11216 var view = new PMatrix3D(); |
|
11217 view.scale(1, -1, 1); |
|
11218 view.apply(modelView.array()); |
|
11219 view.transpose(); |
|
11220 |
|
11221 if (doFill) { |
|
11222 // Calculating the normal matrix can be expensive, so only |
|
11223 // do it if it's necessary |
|
11224 if(lightCount > 0){ |
|
11225 // Create a normal transformation matrix |
|
11226 var v = new PMatrix3D(); |
|
11227 v.set(view); |
|
11228 |
|
11229 var m = new PMatrix3D(); |
|
11230 m.set(model); |
|
11231 |
|
11232 v.mult(m); |
|
11233 |
|
11234 var normalMatrix = new PMatrix3D(); |
|
11235 normalMatrix.set(v); |
|
11236 normalMatrix.invert(); |
|
11237 normalMatrix.transpose(); |
|
11238 |
|
11239 uniformMatrix("normalTransform3d", programObject3D, "normalTransform", false, normalMatrix.array()); |
|
11240 vertexAttribPointer("normal3d", programObject3D, "Normal", 3, sphereBuffer); |
|
11241 } |
|
11242 else{ |
|
11243 disableVertexAttribPointer("normal3d", programObject3D, "Normal"); |
|
11244 } |
|
11245 |
|
11246 curContext.useProgram(programObject3D); |
|
11247 disableVertexAttribPointer("aTexture3d", programObject3D, "aTexture"); |
|
11248 |
|
11249 uniformMatrix("model3d", programObject3D, "model", false, model.array()); |
|
11250 uniformMatrix("view3d", programObject3D, "view", false, view.array()); |
|
11251 vertexAttribPointer("vertex3d", programObject3D, "Vertex", 3, sphereBuffer); |
|
11252 |
|
11253 // Turn off per vertex colors |
|
11254 disableVertexAttribPointer("aColor3d", programObject3D, "aColor"); |
|
11255 |
|
11256 // fix stitching problems. (lines get occluded by triangles |
|
11257 // since they share the same depth values). This is not entirely |
|
11258 // working, but it's a start for drawing the outline. So |
|
11259 // developers can start playing around with styles. |
|
11260 curContext.enable(curContext.POLYGON_OFFSET_FILL); |
|
11261 curContext.polygonOffset(1, 1); |
|
11262 uniformf("color3d", programObject3D, "color", fillStyle); |
|
11263 curContext.drawArrays(curContext.TRIANGLE_STRIP, 0, sphereVerts.length / 3); |
|
11264 curContext.disable(curContext.POLYGON_OFFSET_FILL); |
|
11265 } |
|
11266 |
|
11267 if (lineWidth > 0 && doStroke) { |
|
11268 curContext.useProgram(programObject2D); |
|
11269 uniformMatrix("model2d", programObject2D, "model", false, model.array()); |
|
11270 uniformMatrix("view2d", programObject2D, "view", false, view.array()); |
|
11271 vertexAttribPointer("vertex2d", programObject2D, "Vertex", 3, sphereBuffer); |
|
11272 disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); |
|
11273 uniformf("color2d", programObject2D, "color", strokeStyle); |
|
11274 uniformi("picktype2d", programObject2D, "picktype", 0); |
|
11275 curContext.drawArrays(curContext.LINE_STRIP, 0, sphereVerts.length / 3); |
|
11276 } |
|
11277 }; |
|
11278 |
|
11279 //////////////////////////////////////////////////////////////////////////// |
|
11280 // Coordinates |
|
11281 //////////////////////////////////////////////////////////////////////////// |
|
11282 |
|
11283 /** |
|
11284 * Returns the three-dimensional X, Y, Z position in model space. This returns |
|
11285 * the X value for a given coordinate based on the current set of transformations |
|
11286 * (scale, rotate, translate, etc.) The X value can be used to place an object |
|
11287 * in space relative to the location of the original point once the transformations |
|
11288 * are no longer in use.<br /> |
|
11289 * <br /> |
|
11290 * |
|
11291 * @param {int | float} x 3D x coordinate to be mapped |
|
11292 * @param {int | float} y 3D y coordinate to be mapped |
|
11293 * @param {int | float} z 3D z coordinate to be mapped |
|
11294 * |
|
11295 * @returns {float} |
|
11296 * |
|
11297 * @see modelY |
|
11298 * @see modelZ |
|
11299 */ |
|
11300 p.modelX = function(x, y, z) { |
|
11301 var mv = modelView.array(); |
|
11302 var ci = cameraInv.array(); |
|
11303 |
|
11304 var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; |
|
11305 var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; |
|
11306 var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; |
|
11307 var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15]; |
|
11308 |
|
11309 var ox = ci[0] * ax + ci[1] * ay + ci[2] * az + ci[3] * aw; |
|
11310 var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw; |
|
11311 |
|
11312 return (ow !== 0) ? ox / ow : ox; |
|
11313 }; |
|
11314 |
|
11315 /** |
|
11316 * Returns the three-dimensional X, Y, Z position in model space. This returns |
|
11317 * the Y value for a given coordinate based on the current set of transformations |
|
11318 * (scale, rotate, translate, etc.) The Y value can be used to place an object in |
|
11319 * space relative to the location of the original point once the transformations |
|
11320 * are no longer in use.<br /> |
|
11321 * <br /> |
|
11322 * |
|
11323 * @param {int | float} x 3D x coordinate to be mapped |
|
11324 * @param {int | float} y 3D y coordinate to be mapped |
|
11325 * @param {int | float} z 3D z coordinate to be mapped |
|
11326 * |
|
11327 * @returns {float} |
|
11328 * |
|
11329 * @see modelX |
|
11330 * @see modelZ |
|
11331 */ |
|
11332 p.modelY = function(x, y, z) { |
|
11333 var mv = modelView.array(); |
|
11334 var ci = cameraInv.array(); |
|
11335 |
|
11336 var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; |
|
11337 var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; |
|
11338 var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; |
|
11339 var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15]; |
|
11340 |
|
11341 var oy = ci[4] * ax + ci[5] * ay + ci[6] * az + ci[7] * aw; |
|
11342 var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw; |
|
11343 |
|
11344 return (ow !== 0) ? oy / ow : oy; |
|
11345 }; |
|
11346 |
|
11347 /** |
|
11348 * Returns the three-dimensional X, Y, Z position in model space. This returns |
|
11349 * the Z value for a given coordinate based on the current set of transformations |
|
11350 * (scale, rotate, translate, etc.) The Z value can be used to place an object in |
|
11351 * space relative to the location of the original point once the transformations |
|
11352 * are no longer in use. |
|
11353 * |
|
11354 * @param {int | float} x 3D x coordinate to be mapped |
|
11355 * @param {int | float} y 3D y coordinate to be mapped |
|
11356 * @param {int | float} z 3D z coordinate to be mapped |
|
11357 * |
|
11358 * @returns {float} |
|
11359 * |
|
11360 * @see modelX |
|
11361 * @see modelY |
|
11362 */ |
|
11363 p.modelZ = function(x, y, z) { |
|
11364 var mv = modelView.array(); |
|
11365 var ci = cameraInv.array(); |
|
11366 |
|
11367 var ax = mv[0] * x + mv[1] * y + mv[2] * z + mv[3]; |
|
11368 var ay = mv[4] * x + mv[5] * y + mv[6] * z + mv[7]; |
|
11369 var az = mv[8] * x + mv[9] * y + mv[10] * z + mv[11]; |
|
11370 var aw = mv[12] * x + mv[13] * y + mv[14] * z + mv[15]; |
|
11371 |
|
11372 var oz = ci[8] * ax + ci[9] * ay + ci[10] * az + ci[11] * aw; |
|
11373 var ow = ci[12] * ax + ci[13] * ay + ci[14] * az + ci[15] * aw; |
|
11374 |
|
11375 return (ow !== 0) ? oz / ow : oz; |
|
11376 }; |
|
11377 |
|
11378 //////////////////////////////////////////////////////////////////////////// |
|
11379 // Material Properties |
|
11380 //////////////////////////////////////////////////////////////////////////// |
|
11381 |
|
11382 /** |
|
11383 * Sets the ambient reflectance for shapes drawn to the screen. This is |
|
11384 * combined with the ambient light component of environment. The color |
|
11385 * components set through the parameters define the reflectance. For example in |
|
11386 * the default color mode, setting v1=255, v2=126, v3=0, would cause all the |
|
11387 * red light to reflect and half of the green light to reflect. Used in combination |
|
11388 * with <b>emissive()</b>, <b>specular()</b>, and <b>shininess()</b> in setting |
|
11389 * the materal properties of shapes. |
|
11390 * |
|
11391 * @param {int | float} gray |
|
11392 * |
|
11393 * @returns none |
|
11394 * |
|
11395 * @see emissive |
|
11396 * @see specular |
|
11397 * @see shininess |
|
11398 */ |
|
11399 Drawing2D.prototype.ambient = DrawingShared.prototype.a3DOnlyFunction; |
|
11400 |
|
11401 Drawing3D.prototype.ambient = function(v1, v2, v3) { |
|
11402 curContext.useProgram(programObject3D); |
|
11403 uniformi("usingMat3d", programObject3D, "usingMat", true); |
|
11404 var col = p.color(v1, v2, v3); |
|
11405 uniformf("mat_ambient3d", programObject3D, "mat_ambient", p.color.toGLArray(col).slice(0, 3)); |
|
11406 }; |
|
11407 |
|
11408 /** |
|
11409 * Sets the emissive color of the material used for drawing shapes |
|
11410 * drawn to the screen. Used in combination with ambient(), specular(), |
|
11411 * and shininess() in setting the material properties of shapes. |
|
11412 * |
|
11413 * Can be called in the following ways: |
|
11414 * |
|
11415 * emissive(gray) |
|
11416 * @param {int | float} gray number specifying value between white and black |
|
11417 * |
|
11418 * emissive(color) |
|
11419 * @param {color} color any value of the color datatype |
|
11420 * |
|
11421 * emissive(v1, v2, v3) |
|
11422 * @param {int | float} v1 red or hue value |
|
11423 * @param {int | float} v2 green or saturation value |
|
11424 * @param {int | float} v3 blue or brightness value |
|
11425 * |
|
11426 * @returns none |
|
11427 * |
|
11428 * @see ambient |
|
11429 * @see specular |
|
11430 * @see shininess |
|
11431 */ |
|
11432 Drawing2D.prototype.emissive = DrawingShared.prototype.a3DOnlyFunction; |
|
11433 |
|
11434 Drawing3D.prototype.emissive = function(v1, v2, v3) { |
|
11435 curContext.useProgram(programObject3D); |
|
11436 uniformi("usingMat3d", programObject3D, "usingMat", true); |
|
11437 var col = p.color(v1, v2, v3); |
|
11438 uniformf("mat_emissive3d", programObject3D, "mat_emissive", p.color.toGLArray(col).slice(0, 3)); |
|
11439 }; |
|
11440 |
|
11441 /** |
|
11442 * Sets the amount of gloss in the surface of shapes. Used in combination with |
|
11443 * <b>ambient()</b>, <b>specular()</b>, and <b>emissive()</b> in setting the |
|
11444 * material properties of shapes. |
|
11445 * |
|
11446 * @param {float} shine degree of shininess |
|
11447 * |
|
11448 * @returns none |
|
11449 */ |
|
11450 Drawing2D.prototype.shininess = DrawingShared.prototype.a3DOnlyFunction; |
|
11451 |
|
11452 Drawing3D.prototype.shininess = function(shine) { |
|
11453 curContext.useProgram(programObject3D); |
|
11454 uniformi("usingMat3d", programObject3D, "usingMat", true); |
|
11455 uniformf("shininess3d", programObject3D, "shininess", shine); |
|
11456 }; |
|
11457 |
|
11458 /** |
|
11459 * Sets the specular color of the materials used for shapes drawn to the screen, |
|
11460 * which sets the color of hightlights. Specular refers to light which bounces |
|
11461 * off a surface in a perferred direction (rather than bouncing in all directions |
|
11462 * like a diffuse light). Used in combination with emissive(), ambient(), and |
|
11463 * shininess() in setting the material properties of shapes. |
|
11464 * |
|
11465 * Can be called in the following ways: |
|
11466 * |
|
11467 * specular(gray) |
|
11468 * @param {int | float} gray number specifying value between white and black |
|
11469 * |
|
11470 * specular(gray, alpha) |
|
11471 * @param {int | float} gray number specifying value between white and black |
|
11472 * @param {int | float} alpha opacity |
|
11473 * |
|
11474 * specular(color) |
|
11475 * @param {color} color any value of the color datatype |
|
11476 * |
|
11477 * specular(v1, v2, v3) |
|
11478 * @param {int | float} v1 red or hue value |
|
11479 * @param {int | float} v2 green or saturation value |
|
11480 * @param {int | float} v3 blue or brightness value |
|
11481 * |
|
11482 * specular(v1, v2, v3, alpha) |
|
11483 * @param {int | float} v1 red or hue value |
|
11484 * @param {int | float} v2 green or saturation value |
|
11485 * @param {int | float} v3 blue or brightness value |
|
11486 * @param {int | float} alpha opacity |
|
11487 * |
|
11488 * @returns none |
|
11489 * |
|
11490 * @see ambient |
|
11491 * @see emissive |
|
11492 * @see shininess |
|
11493 */ |
|
11494 Drawing2D.prototype.specular = DrawingShared.prototype.a3DOnlyFunction; |
|
11495 |
|
11496 Drawing3D.prototype.specular = function(v1, v2, v3) { |
|
11497 curContext.useProgram(programObject3D); |
|
11498 uniformi("usingMat3d", programObject3D, "usingMat", true); |
|
11499 var col = p.color(v1, v2, v3); |
|
11500 uniformf("mat_specular3d", programObject3D, "mat_specular", p.color.toGLArray(col).slice(0, 3)); |
|
11501 }; |
|
11502 |
|
11503 //////////////////////////////////////////////////////////////////////////// |
|
11504 // Coordinates |
|
11505 //////////////////////////////////////////////////////////////////////////// |
|
11506 |
|
11507 /** |
|
11508 * Takes a three-dimensional X, Y, Z position and returns the X value for |
|
11509 * where it will appear on a (two-dimensional) screen. |
|
11510 * |
|
11511 * @param {int | float} x 3D x coordinate to be mapped |
|
11512 * @param {int | float} y 3D y coordinate to be mapped |
|
11513 * @param {int | float} z 3D z optional coordinate to be mapped |
|
11514 * |
|
11515 * @returns {float} |
|
11516 * |
|
11517 * @see screenY |
|
11518 * @see screenZ |
|
11519 */ |
|
11520 p.screenX = function( x, y, z ) { |
|
11521 var mv = modelView.array(); |
|
11522 if( mv.length === 16 ) |
|
11523 { |
|
11524 var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; |
|
11525 var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; |
|
11526 var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; |
|
11527 var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15]; |
|
11528 |
|
11529 var pj = projection.array(); |
|
11530 |
|
11531 var ox = pj[ 0]*ax + pj[ 1]*ay + pj[ 2]*az + pj[ 3]*aw; |
|
11532 var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw; |
|
11533 |
|
11534 if ( ow !== 0 ){ |
|
11535 ox /= ow; |
|
11536 } |
|
11537 return p.width * ( 1 + ox ) / 2.0; |
|
11538 } |
|
11539 // We assume that we're in 2D |
|
11540 return modelView.multX(x, y); |
|
11541 }; |
|
11542 |
|
11543 /** |
|
11544 * Takes a three-dimensional X, Y, Z position and returns the Y value for |
|
11545 * where it will appear on a (two-dimensional) screen. |
|
11546 * |
|
11547 * @param {int | float} x 3D x coordinate to be mapped |
|
11548 * @param {int | float} y 3D y coordinate to be mapped |
|
11549 * @param {int | float} z 3D z optional coordinate to be mapped |
|
11550 * |
|
11551 * @returns {float} |
|
11552 * |
|
11553 * @see screenX |
|
11554 * @see screenZ |
|
11555 */ |
|
11556 p.screenY = function screenY( x, y, z ) { |
|
11557 var mv = modelView.array(); |
|
11558 if( mv.length === 16 ) { |
|
11559 var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; |
|
11560 var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; |
|
11561 var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; |
|
11562 var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15]; |
|
11563 |
|
11564 var pj = projection.array(); |
|
11565 |
|
11566 var oy = pj[ 4]*ax + pj[ 5]*ay + pj[ 6]*az + pj[ 7]*aw; |
|
11567 var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw; |
|
11568 |
|
11569 if ( ow !== 0 ){ |
|
11570 oy /= ow; |
|
11571 } |
|
11572 return p.height * ( 1 + oy ) / 2.0; |
|
11573 } |
|
11574 // We assume that we're in 2D |
|
11575 return modelView.multY(x, y); |
|
11576 }; |
|
11577 |
|
11578 /** |
|
11579 * Takes a three-dimensional X, Y, Z position and returns the Z value for |
|
11580 * where it will appear on a (two-dimensional) screen. |
|
11581 * |
|
11582 * @param {int | float} x 3D x coordinate to be mapped |
|
11583 * @param {int | float} y 3D y coordinate to be mapped |
|
11584 * @param {int | float} z 3D z coordinate to be mapped |
|
11585 * |
|
11586 * @returns {float} |
|
11587 * |
|
11588 * @see screenX |
|
11589 * @see screenY |
|
11590 */ |
|
11591 p.screenZ = function screenZ( x, y, z ) { |
|
11592 var mv = modelView.array(); |
|
11593 if( mv.length !== 16 ) { |
|
11594 return 0; |
|
11595 } |
|
11596 |
|
11597 var pj = projection.array(); |
|
11598 |
|
11599 var ax = mv[ 0]*x + mv[ 1]*y + mv[ 2]*z + mv[ 3]; |
|
11600 var ay = mv[ 4]*x + mv[ 5]*y + mv[ 6]*z + mv[ 7]; |
|
11601 var az = mv[ 8]*x + mv[ 9]*y + mv[10]*z + mv[11]; |
|
11602 var aw = mv[12]*x + mv[13]*y + mv[14]*z + mv[15]; |
|
11603 |
|
11604 var oz = pj[ 8]*ax + pj[ 9]*ay + pj[10]*az + pj[11]*aw; |
|
11605 var ow = pj[12]*ax + pj[13]*ay + pj[14]*az + pj[15]*aw; |
|
11606 |
|
11607 if ( ow !== 0 ) { |
|
11608 oz /= ow; |
|
11609 } |
|
11610 return ( oz + 1 ) / 2.0; |
|
11611 }; |
|
11612 |
|
11613 //////////////////////////////////////////////////////////////////////////// |
|
11614 // Style functions |
|
11615 //////////////////////////////////////////////////////////////////////////// |
|
11616 /** |
|
11617 * The fill() function sets the color used to fill shapes. For example, if you run <b>fill(204, 102, 0)</b>, all subsequent shapes will be filled with orange. |
|
11618 * This color is either specified in terms of the RGB or HSB color depending on the current <b>colorMode()</b> |
|
11619 *(the default color space is RGB, with each value in the range from 0 to 255). |
|
11620 * <br><br>When using hexadecimal notation to specify a color, use "#" or "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). |
|
11621 * The # syntax uses six digits to specify a color (the way colors are specified in HTML and CSS). When using the hexadecimal notation starting with "0x", |
|
11622 * the hexadecimal value must be specified with eight characters; the first two characters define the alpha component and the remainder the red, green, and blue components. |
|
11623 * <br><br>The value for the parameter "gray" must be less than or equal to the current maximum value as specified by <b>colorMode()</b>. The default maximum value is 255. |
|
11624 * <br><br>To change the color of an image (or a texture), use tint(). |
|
11625 * |
|
11626 * @param {int|float} gray number specifying value between white and black |
|
11627 * @param {int|float} value1 red or hue value |
|
11628 * @param {int|float} value2 green or saturation value |
|
11629 * @param {int|float} value3 blue or brightness value |
|
11630 * @param {int|float} alpha opacity of the fill |
|
11631 * @param {Color} color any value of the color datatype |
|
11632 * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) |
|
11633 * |
|
11634 * @see #noFill() |
|
11635 * @see #stroke() |
|
11636 * @see #tint() |
|
11637 * @see #background() |
|
11638 * @see #colorMode() |
|
11639 */ |
|
11640 DrawingShared.prototype.fill = function() { |
|
11641 var color = p.color(arguments[0], arguments[1], arguments[2], arguments[3]); |
|
11642 if(color === currentFillColor && doFill) { |
|
11643 return; |
|
11644 } |
|
11645 doFill = true; |
|
11646 currentFillColor = color; |
|
11647 }; |
|
11648 |
|
11649 Drawing2D.prototype.fill = function() { |
|
11650 DrawingShared.prototype.fill.apply(this, arguments); |
|
11651 isFillDirty = true; |
|
11652 }; |
|
11653 |
|
11654 Drawing3D.prototype.fill = function() { |
|
11655 DrawingShared.prototype.fill.apply(this, arguments); |
|
11656 fillStyle = p.color.toGLArray(currentFillColor); |
|
11657 }; |
|
11658 |
|
11659 function executeContextFill() { |
|
11660 if(doFill) { |
|
11661 if(isFillDirty) { |
|
11662 curContext.fillStyle = p.color.toString(currentFillColor); |
|
11663 isFillDirty = false; |
|
11664 } |
|
11665 curContext.fill(); |
|
11666 } |
|
11667 } |
|
11668 |
|
11669 /** |
|
11670 * The noFill() function disables filling geometry. If both <b>noStroke()</b> and <b>noFill()</b> |
|
11671 * are called, no shapes will be drawn to the screen. |
|
11672 * |
|
11673 * @see #fill() |
|
11674 * |
|
11675 */ |
|
11676 p.noFill = function() { |
|
11677 doFill = false; |
|
11678 }; |
|
11679 |
|
11680 /** |
|
11681 * The stroke() function sets the color used to draw lines and borders around shapes. This color |
|
11682 * is either specified in terms of the RGB or HSB color depending on the |
|
11683 * current <b>colorMode()</b> (the default color space is RGB, with each |
|
11684 * value in the range from 0 to 255). |
|
11685 * <br><br>When using hexadecimal notation to specify a color, use "#" or |
|
11686 * "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six |
|
11687 * digits to specify a color (the way colors are specified in HTML and CSS). |
|
11688 * When using the hexadecimal notation starting with "0x", the hexadecimal |
|
11689 * value must be specified with eight characters; the first two characters |
|
11690 * define the alpha component and the remainder the red, green, and blue |
|
11691 * components. |
|
11692 * <br><br>The value for the parameter "gray" must be less than or equal |
|
11693 * to the current maximum value as specified by <b>colorMode()</b>. |
|
11694 * The default maximum value is 255. |
|
11695 * |
|
11696 * @param {int|float} gray number specifying value between white and black |
|
11697 * @param {int|float} value1 red or hue value |
|
11698 * @param {int|float} value2 green or saturation value |
|
11699 * @param {int|float} value3 blue or brightness value |
|
11700 * @param {int|float} alpha opacity of the stroke |
|
11701 * @param {Color} color any value of the color datatype |
|
11702 * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) |
|
11703 * |
|
11704 * @see #fill() |
|
11705 * @see #noStroke() |
|
11706 * @see #tint() |
|
11707 * @see #background() |
|
11708 * @see #colorMode() |
|
11709 */ |
|
11710 DrawingShared.prototype.stroke = function() { |
|
11711 var color = p.color(arguments[0], arguments[1], arguments[2], arguments[3]); |
|
11712 if(color === currentStrokeColor && doStroke) { |
|
11713 return; |
|
11714 } |
|
11715 doStroke = true; |
|
11716 currentStrokeColor = color; |
|
11717 }; |
|
11718 |
|
11719 Drawing2D.prototype.stroke = function() { |
|
11720 DrawingShared.prototype.stroke.apply(this, arguments); |
|
11721 isStrokeDirty = true; |
|
11722 }; |
|
11723 |
|
11724 Drawing3D.prototype.stroke = function() { |
|
11725 DrawingShared.prototype.stroke.apply(this, arguments); |
|
11726 strokeStyle = p.color.toGLArray(currentStrokeColor); |
|
11727 }; |
|
11728 |
|
11729 function executeContextStroke() { |
|
11730 if(doStroke) { |
|
11731 if(isStrokeDirty) { |
|
11732 curContext.strokeStyle = p.color.toString(currentStrokeColor); |
|
11733 isStrokeDirty = false; |
|
11734 } |
|
11735 curContext.stroke(); |
|
11736 } |
|
11737 } |
|
11738 |
|
11739 /** |
|
11740 * The noStroke() function disables drawing the stroke (outline). If both <b>noStroke()</b> and |
|
11741 * <b>noFill()</b> are called, no shapes will be drawn to the screen. |
|
11742 * |
|
11743 * @see #stroke() |
|
11744 */ |
|
11745 p.noStroke = function() { |
|
11746 doStroke = false; |
|
11747 }; |
|
11748 |
|
11749 /** |
|
11750 * The strokeWeight() function sets the width of the stroke used for lines, points, and the border around shapes. |
|
11751 * All widths are set in units of pixels. |
|
11752 * |
|
11753 * @param {int|float} w the weight (in pixels) of the stroke |
|
11754 */ |
|
11755 DrawingShared.prototype.strokeWeight = function(w) { |
|
11756 lineWidth = w; |
|
11757 }; |
|
11758 |
|
11759 Drawing2D.prototype.strokeWeight = function(w) { |
|
11760 DrawingShared.prototype.strokeWeight.apply(this, arguments); |
|
11761 curContext.lineWidth = w; |
|
11762 }; |
|
11763 |
|
11764 Drawing3D.prototype.strokeWeight = function(w) { |
|
11765 DrawingShared.prototype.strokeWeight.apply(this, arguments); |
|
11766 |
|
11767 // Processing groups the weight of points and lines under this one function, |
|
11768 // but for WebGL, we need to set a uniform for points and call a function for line. |
|
11769 |
|
11770 curContext.useProgram(programObject2D); |
|
11771 uniformf("pointSize2d", programObject2D, "pointSize", w); |
|
11772 |
|
11773 curContext.useProgram(programObjectUnlitShape); |
|
11774 uniformf("pointSizeUnlitShape", programObjectUnlitShape, "pointSize", w); |
|
11775 |
|
11776 curContext.lineWidth(w); |
|
11777 }; |
|
11778 |
|
11779 /** |
|
11780 * The strokeCap() function sets the style for rendering line endings. These ends are either squared, extended, or rounded and |
|
11781 * specified with the corresponding parameters SQUARE, PROJECT, and ROUND. The default cap is ROUND. |
|
11782 * This function is not available with the P2D, P3D, or OPENGL renderers |
|
11783 * |
|
11784 * @param {int} value Either SQUARE, PROJECT, or ROUND |
|
11785 */ |
|
11786 p.strokeCap = function(value) { |
|
11787 drawing.$ensureContext().lineCap = value; |
|
11788 }; |
|
11789 |
|
11790 /** |
|
11791 * The strokeJoin() function sets the style of the joints which connect line segments. |
|
11792 * These joints are either mitered, beveled, or rounded and specified with the corresponding parameters MITER, BEVEL, and ROUND. The default joint is MITER. |
|
11793 * This function is not available with the P2D, P3D, or OPENGL renderers |
|
11794 * |
|
11795 * @param {int} value Either SQUARE, PROJECT, or ROUND |
|
11796 */ |
|
11797 p.strokeJoin = function(value) { |
|
11798 drawing.$ensureContext().lineJoin = value; |
|
11799 }; |
|
11800 |
|
11801 /** |
|
11802 * The smooth() function draws all geometry with smooth (anti-aliased) edges. This will slow down the frame rate of the application, |
|
11803 * but will enhance the visual refinement. <br/><br/> |
|
11804 * Note that smooth() will also improve image quality of resized images, and noSmooth() will disable image (and font) smoothing altogether. |
|
11805 * |
|
11806 * @see #noSmooth() |
|
11807 * @see #hint() |
|
11808 * @see #size() |
|
11809 */ |
|
11810 |
|
11811 Drawing2D.prototype.smooth = function() { |
|
11812 renderSmooth = true; |
|
11813 var style = curElement.style; |
|
11814 style.setProperty("image-rendering", "optimizeQuality", "important"); |
|
11815 style.setProperty("-ms-interpolation-mode", "bicubic", "important"); |
|
11816 if (curContext.hasOwnProperty("mozImageSmoothingEnabled")) { |
|
11817 curContext.mozImageSmoothingEnabled = true; |
|
11818 } |
|
11819 }; |
|
11820 |
|
11821 Drawing3D.prototype.smooth = nop; |
|
11822 |
|
11823 /** |
|
11824 * The noSmooth() function draws all geometry with jagged (aliased) edges. |
|
11825 * |
|
11826 * @see #smooth() |
|
11827 */ |
|
11828 |
|
11829 Drawing2D.prototype.noSmooth = function() { |
|
11830 renderSmooth = false; |
|
11831 var style = curElement.style; |
|
11832 style.setProperty("image-rendering", "optimizeSpeed", "important"); |
|
11833 style.setProperty("image-rendering", "-moz-crisp-edges", "important"); |
|
11834 style.setProperty("image-rendering", "-webkit-optimize-contrast", "important"); |
|
11835 style.setProperty("image-rendering", "optimize-contrast", "important"); |
|
11836 style.setProperty("-ms-interpolation-mode", "nearest-neighbor", "important"); |
|
11837 if (curContext.hasOwnProperty("mozImageSmoothingEnabled")) { |
|
11838 curContext.mozImageSmoothingEnabled = false; |
|
11839 } |
|
11840 }; |
|
11841 |
|
11842 Drawing3D.prototype.noSmooth = nop; |
|
11843 |
|
11844 //////////////////////////////////////////////////////////////////////////// |
|
11845 // Vector drawing functions |
|
11846 //////////////////////////////////////////////////////////////////////////// |
|
11847 /** |
|
11848 * The point() function draws a point, a coordinate in space at the dimension of one pixel. |
|
11849 * The first parameter is the horizontal value for the point, the second |
|
11850 * value is the vertical value for the point, and the optional third value |
|
11851 * is the depth value. Drawing this shape in 3D using the <b>z</b> |
|
11852 * parameter requires the P3D or OPENGL parameter in combination with |
|
11853 * size as shown in the above example. |
|
11854 * |
|
11855 * @param {int|float} x x-coordinate of the point |
|
11856 * @param {int|float} y y-coordinate of the point |
|
11857 * @param {int|float} z z-coordinate of the point |
|
11858 * |
|
11859 * @see #beginShape() |
|
11860 */ |
|
11861 Drawing2D.prototype.point = function(x, y) { |
|
11862 if (!doStroke) { |
|
11863 return; |
|
11864 } |
|
11865 |
|
11866 x = Math.round(x); |
|
11867 y = Math.round(y); |
|
11868 curContext.fillStyle = p.color.toString(currentStrokeColor); |
|
11869 isFillDirty = true; |
|
11870 // Draw a circle for any point larger than 1px |
|
11871 if (lineWidth > 1) { |
|
11872 curContext.beginPath(); |
|
11873 curContext.arc(x, y, lineWidth / 2, 0, PConstants.TWO_PI, false); |
|
11874 curContext.fill(); |
|
11875 } else { |
|
11876 curContext.fillRect(x, y, 1, 1); |
|
11877 } |
|
11878 }; |
|
11879 |
|
11880 Drawing3D.prototype.point = function(x, y, z) { |
|
11881 var model = new PMatrix3D(); |
|
11882 |
|
11883 // move point to position |
|
11884 model.translate(x, y, z || 0); |
|
11885 model.transpose(); |
|
11886 |
|
11887 var view = new PMatrix3D(); |
|
11888 view.scale(1, -1, 1); |
|
11889 view.apply(modelView.array()); |
|
11890 view.transpose(); |
|
11891 |
|
11892 curContext.useProgram(programObject2D); |
|
11893 uniformMatrix("model2d", programObject2D, "model", false, model.array()); |
|
11894 uniformMatrix("view2d", programObject2D, "view", false, view.array()); |
|
11895 |
|
11896 if (lineWidth > 0 && doStroke) { |
|
11897 // this will be replaced with the new bit shifting color code |
|
11898 uniformf("color2d", programObject2D, "color", strokeStyle); |
|
11899 uniformi("picktype2d", programObject2D, "picktype", 0); |
|
11900 vertexAttribPointer("vertex2d", programObject2D, "Vertex", 3, pointBuffer); |
|
11901 disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); |
|
11902 curContext.drawArrays(curContext.POINTS, 0, 1); |
|
11903 } |
|
11904 }; |
|
11905 |
|
11906 /** |
|
11907 * Using the <b>beginShape()</b> and <b>endShape()</b> functions allow creating more complex forms. |
|
11908 * <b>beginShape()</b> begins recording vertices for a shape and <b>endShape()</b> stops recording. |
|
11909 * The value of the <b>MODE</b> parameter tells it which types of shapes to create from the provided vertices. |
|
11910 * With no mode specified, the shape can be any irregular polygon. After calling the <b>beginShape()</b> function, |
|
11911 * a series of <b>vertex()</b> commands must follow. To stop drawing the shape, call <b>endShape()</b>. |
|
11912 * The <b>vertex()</b> function with two parameters specifies a position in 2D and the <b>vertex()</b> |
|
11913 * function with three parameters specifies a position in 3D. Each shape will be outlined with the current |
|
11914 * stroke color and filled with the fill color. |
|
11915 * |
|
11916 * @param {int} MODE either POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, and QUAD_STRIP. |
|
11917 * |
|
11918 * @see endShape |
|
11919 * @see vertex |
|
11920 * @see curveVertex |
|
11921 * @see bezierVertex |
|
11922 */ |
|
11923 p.beginShape = function(type) { |
|
11924 curShape = type; |
|
11925 curvePoints = []; |
|
11926 vertArray = []; |
|
11927 }; |
|
11928 |
|
11929 /** |
|
11930 * All shapes are constructed by connecting a series of vertices. <b>vertex()</b> is used to specify the vertex |
|
11931 * coordinates for points, lines, triangles, quads, and polygons and is used exclusively within the <b>beginShape()</b> |
|
11932 * and <b>endShape()</b> function. <br /><br />Drawing a vertex in 3D using the <b>z</b> parameter requires the P3D or |
|
11933 * OPENGL parameter in combination with size as shown in the above example.<br /><br />This function is also used to map a |
|
11934 * texture onto the geometry. The <b>texture()</b> function declares the texture to apply to the geometry and the <b>u</b> |
|
11935 * and <b>v</b> coordinates set define the mapping of this texture to the form. By default, the coordinates used for |
|
11936 * <b>u</b> and <b>v</b> are specified in relation to the image's size in pixels, but this relation can be changed with |
|
11937 * <b>textureMode()</b>. |
|
11938 * |
|
11939 * @param {int | float} x x-coordinate of the vertex |
|
11940 * @param {int | float} y y-coordinate of the vertex |
|
11941 * @param {int | float} z z-coordinate of the vertex |
|
11942 * @param {int | float} u horizontal coordinate for the texture mapping |
|
11943 * @param {int | float} v vertical coordinate for the texture mapping |
|
11944 * |
|
11945 * @see beginShape |
|
11946 * @see endShape |
|
11947 * @see bezierVertex |
|
11948 * @see curveVertex |
|
11949 * @see texture |
|
11950 */ |
|
11951 |
|
11952 Drawing2D.prototype.vertex = function(x, y, u, v) { |
|
11953 var vert = []; |
|
11954 |
|
11955 if (firstVert) { firstVert = false; } |
|
11956 vert["isVert"] = true; |
|
11957 |
|
11958 vert[0] = x; |
|
11959 vert[1] = y; |
|
11960 vert[2] = 0; |
|
11961 vert[3] = u; |
|
11962 vert[4] = v; |
|
11963 |
|
11964 // fill and stroke color |
|
11965 vert[5] = currentFillColor; |
|
11966 vert[6] = currentStrokeColor; |
|
11967 |
|
11968 vertArray.push(vert); |
|
11969 }; |
|
11970 |
|
11971 Drawing3D.prototype.vertex = function(x, y, z, u, v) { |
|
11972 var vert = []; |
|
11973 |
|
11974 if (firstVert) { firstVert = false; } |
|
11975 vert["isVert"] = true; |
|
11976 |
|
11977 if (v === undef && usingTexture) { |
|
11978 v = u; |
|
11979 u = z; |
|
11980 z = 0; |
|
11981 } |
|
11982 |
|
11983 // Convert u and v to normalized coordinates |
|
11984 if (u !== undef && v !== undef) { |
|
11985 if (curTextureMode === PConstants.IMAGE) { |
|
11986 u /= curTexture.width; |
|
11987 v /= curTexture.height; |
|
11988 } |
|
11989 u = u > 1 ? 1 : u; |
|
11990 u = u < 0 ? 0 : u; |
|
11991 v = v > 1 ? 1 : v; |
|
11992 v = v < 0 ? 0 : v; |
|
11993 } |
|
11994 |
|
11995 vert[0] = x; |
|
11996 vert[1] = y; |
|
11997 vert[2] = z || 0; |
|
11998 vert[3] = u || 0; |
|
11999 vert[4] = v || 0; |
|
12000 |
|
12001 // fill rgba |
|
12002 vert[5] = fillStyle[0]; |
|
12003 vert[6] = fillStyle[1]; |
|
12004 vert[7] = fillStyle[2]; |
|
12005 vert[8] = fillStyle[3]; |
|
12006 // stroke rgba |
|
12007 vert[9] = strokeStyle[0]; |
|
12008 vert[10] = strokeStyle[1]; |
|
12009 vert[11] = strokeStyle[2]; |
|
12010 vert[12] = strokeStyle[3]; |
|
12011 //normals |
|
12012 vert[13] = normalX; |
|
12013 vert[14] = normalY; |
|
12014 vert[15] = normalZ; |
|
12015 |
|
12016 vertArray.push(vert); |
|
12017 }; |
|
12018 |
|
12019 /** |
|
12020 * @private |
|
12021 * Renders 3D points created from calls to vertex and beginShape/endShape |
|
12022 * |
|
12023 * @param {Array} vArray an array of vertex coordinate |
|
12024 * @param {Array} cArray an array of colours used for the vertices |
|
12025 * |
|
12026 * @see beginShape |
|
12027 * @see endShape |
|
12028 * @see vertex |
|
12029 */ |
|
12030 var point3D = function(vArray, cArray){ |
|
12031 var view = new PMatrix3D(); |
|
12032 view.scale(1, -1, 1); |
|
12033 view.apply(modelView.array()); |
|
12034 view.transpose(); |
|
12035 |
|
12036 curContext.useProgram(programObjectUnlitShape); |
|
12037 |
|
12038 uniformMatrix("uViewUS", programObjectUnlitShape, "uView", false, view.array()); |
|
12039 |
|
12040 vertexAttribPointer("aVertexUS", programObjectUnlitShape, "aVertex", 3, pointBuffer); |
|
12041 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW); |
|
12042 |
|
12043 vertexAttribPointer("aColorUS", programObjectUnlitShape, "aColor", 4, fillColorBuffer); |
|
12044 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW); |
|
12045 |
|
12046 curContext.drawArrays(curContext.POINTS, 0, vArray.length/3); |
|
12047 }; |
|
12048 |
|
12049 /** |
|
12050 * @private |
|
12051 * Renders 3D lines created from calls to beginShape/vertex/endShape - based on the mode specified LINES, LINE_LOOP, etc. |
|
12052 * |
|
12053 * @param {Array} vArray an array of vertex coordinate |
|
12054 * @param {String} mode either LINES, LINE_LOOP, or LINE_STRIP |
|
12055 * @param {Array} cArray an array of colours used for the vertices |
|
12056 * |
|
12057 * @see beginShape |
|
12058 * @see endShape |
|
12059 * @see vertex |
|
12060 */ |
|
12061 var line3D = function(vArray, mode, cArray){ |
|
12062 var ctxMode; |
|
12063 if (mode === "LINES"){ |
|
12064 ctxMode = curContext.LINES; |
|
12065 } |
|
12066 else if(mode === "LINE_LOOP"){ |
|
12067 ctxMode = curContext.LINE_LOOP; |
|
12068 } |
|
12069 else{ |
|
12070 ctxMode = curContext.LINE_STRIP; |
|
12071 } |
|
12072 |
|
12073 var view = new PMatrix3D(); |
|
12074 view.scale(1, -1, 1); |
|
12075 view.apply(modelView.array()); |
|
12076 view.transpose(); |
|
12077 |
|
12078 curContext.useProgram(programObjectUnlitShape); |
|
12079 uniformMatrix("uViewUS", programObjectUnlitShape, "uView", false, view.array()); |
|
12080 vertexAttribPointer("aVertexUS", programObjectUnlitShape, "aVertex", 3, lineBuffer); |
|
12081 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW); |
|
12082 vertexAttribPointer("aColorUS", programObjectUnlitShape, "aColor", 4, strokeColorBuffer); |
|
12083 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW); |
|
12084 curContext.drawArrays(ctxMode, 0, vArray.length/3); |
|
12085 }; |
|
12086 |
|
12087 /** |
|
12088 * @private |
|
12089 * Render filled shapes created from calls to beginShape/vertex/endShape - based on the mode specified TRIANGLES, etc. |
|
12090 * |
|
12091 * @param {Array} vArray an array of vertex coordinate |
|
12092 * @param {String} mode either LINES, LINE_LOOP, or LINE_STRIP |
|
12093 * @param {Array} cArray an array of colours used for the vertices |
|
12094 * @param {Array} tArray an array of u,v coordinates for textures |
|
12095 * |
|
12096 * @see beginShape |
|
12097 * @see endShape |
|
12098 * @see vertex |
|
12099 */ |
|
12100 var fill3D = function(vArray, mode, cArray, tArray){ |
|
12101 var ctxMode; |
|
12102 if (mode === "TRIANGLES") { |
|
12103 ctxMode = curContext.TRIANGLES; |
|
12104 } else if(mode === "TRIANGLE_FAN") { |
|
12105 ctxMode = curContext.TRIANGLE_FAN; |
|
12106 } else { |
|
12107 ctxMode = curContext.TRIANGLE_STRIP; |
|
12108 } |
|
12109 |
|
12110 var view = new PMatrix3D(); |
|
12111 view.scale(1, -1, 1); |
|
12112 view.apply(modelView.array()); |
|
12113 view.transpose(); |
|
12114 |
|
12115 curContext.useProgram( programObject3D ); |
|
12116 uniformMatrix("model3d", programObject3D, "model", false, [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1] ); |
|
12117 uniformMatrix("view3d", programObject3D, "view", false, view.array() ); |
|
12118 curContext.enable( curContext.POLYGON_OFFSET_FILL ); |
|
12119 curContext.polygonOffset( 1, 1 ); |
|
12120 uniformf("color3d", programObject3D, "color", [-1,0,0,0]); |
|
12121 vertexAttribPointer("vertex3d", programObject3D, "Vertex", 3, fillBuffer); |
|
12122 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(vArray), curContext.STREAM_DRAW); |
|
12123 |
|
12124 // if we are using a texture and a tint, then overwrite the |
|
12125 // contents of the color buffer with the current tint |
|
12126 if (usingTexture && curTint !== null){ |
|
12127 curTint3d(cArray); |
|
12128 } |
|
12129 |
|
12130 vertexAttribPointer("aColor3d", programObject3D, "aColor", 4, fillColorBuffer); |
|
12131 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(cArray), curContext.STREAM_DRAW); |
|
12132 |
|
12133 // No support for lights....yet |
|
12134 disableVertexAttribPointer("normal3d", programObject3D, "Normal"); |
|
12135 |
|
12136 if (usingTexture) { |
|
12137 uniformi("usingTexture3d", programObject3D, "usingTexture", usingTexture); |
|
12138 vertexAttribPointer("aTexture3d", programObject3D, "aTexture", 2, shapeTexVBO); |
|
12139 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(tArray), curContext.STREAM_DRAW); |
|
12140 } |
|
12141 |
|
12142 curContext.drawArrays( ctxMode, 0, vArray.length/3 ); |
|
12143 curContext.disable( curContext.POLYGON_OFFSET_FILL ); |
|
12144 }; |
|
12145 |
|
12146 /** |
|
12147 * this series of three operations is used a lot in Drawing2D.prototype.endShape |
|
12148 * and has been split off as its own function, to tighten the code and allow for |
|
12149 * fewer bugs. |
|
12150 */ |
|
12151 function fillStrokeClose() { |
|
12152 executeContextFill(); |
|
12153 executeContextStroke(); |
|
12154 curContext.closePath(); |
|
12155 } |
|
12156 |
|
12157 /** |
|
12158 * The endShape() function is the companion to beginShape() and may only be called after beginShape(). |
|
12159 * When endshape() is called, all of image data defined since the previous call to beginShape() is written |
|
12160 * into the image buffer. |
|
12161 * |
|
12162 * @param {int} MODE Use CLOSE to close the shape |
|
12163 * |
|
12164 * @see beginShape |
|
12165 */ |
|
12166 Drawing2D.prototype.endShape = function(mode) { |
|
12167 // Duplicated in Drawing3D; too many variables used |
|
12168 if (vertArray.length === 0) { return; } |
|
12169 |
|
12170 var closeShape = mode === PConstants.CLOSE; |
|
12171 |
|
12172 // if the shape is closed, the first element is also the last element |
|
12173 if (closeShape) { |
|
12174 vertArray.push(vertArray[0]); |
|
12175 } |
|
12176 |
|
12177 var lineVertArray = []; |
|
12178 var fillVertArray = []; |
|
12179 var colorVertArray = []; |
|
12180 var strokeVertArray = []; |
|
12181 var texVertArray = []; |
|
12182 var cachedVertArray; |
|
12183 |
|
12184 firstVert = true; |
|
12185 var i, j, k; |
|
12186 var vertArrayLength = vertArray.length; |
|
12187 |
|
12188 for (i = 0; i < vertArrayLength; i++) { |
|
12189 cachedVertArray = vertArray[i]; |
|
12190 for (j = 0; j < 3; j++) { |
|
12191 fillVertArray.push(cachedVertArray[j]); |
|
12192 } |
|
12193 } |
|
12194 |
|
12195 // 5,6,7,8 |
|
12196 // R,G,B,A - fill colour |
|
12197 for (i = 0; i < vertArrayLength; i++) { |
|
12198 cachedVertArray = vertArray[i]; |
|
12199 for (j = 5; j < 9; j++) { |
|
12200 colorVertArray.push(cachedVertArray[j]); |
|
12201 } |
|
12202 } |
|
12203 |
|
12204 // 9,10,11,12 |
|
12205 // R, G, B, A - stroke colour |
|
12206 for (i = 0; i < vertArrayLength; i++) { |
|
12207 cachedVertArray = vertArray[i]; |
|
12208 for (j = 9; j < 13; j++) { |
|
12209 strokeVertArray.push(cachedVertArray[j]); |
|
12210 } |
|
12211 } |
|
12212 |
|
12213 // texture u,v |
|
12214 for (i = 0; i < vertArrayLength; i++) { |
|
12215 cachedVertArray = vertArray[i]; |
|
12216 texVertArray.push(cachedVertArray[3]); |
|
12217 texVertArray.push(cachedVertArray[4]); |
|
12218 } |
|
12219 |
|
12220 // curveVertex |
|
12221 if ( isCurve && (curShape === PConstants.POLYGON || curShape === undef) ) { |
|
12222 if (vertArrayLength > 3) { |
|
12223 var b = [], |
|
12224 s = 1 - curTightness; |
|
12225 curContext.beginPath(); |
|
12226 curContext.moveTo(vertArray[1][0], vertArray[1][1]); |
|
12227 /* |
|
12228 * Matrix to convert from Catmull-Rom to cubic Bezier |
|
12229 * where t = curTightness |
|
12230 * |0 1 0 0 | |
|
12231 * |(t-1)/6 1 (1-t)/6 0 | |
|
12232 * |0 (1-t)/6 1 (t-1)/6 | |
|
12233 * |0 0 0 0 | |
|
12234 */ |
|
12235 for (i = 1; (i+2) < vertArrayLength; i++) { |
|
12236 cachedVertArray = vertArray[i]; |
|
12237 b[0] = [cachedVertArray[0], cachedVertArray[1]]; |
|
12238 b[1] = [cachedVertArray[0] + (s * vertArray[i+1][0] - s * vertArray[i-1][0]) / 6, |
|
12239 cachedVertArray[1] + (s * vertArray[i+1][1] - s * vertArray[i-1][1]) / 6]; |
|
12240 b[2] = [vertArray[i+1][0] + (s * vertArray[i][0] - s * vertArray[i+2][0]) / 6, |
|
12241 vertArray[i+1][1] + (s * vertArray[i][1] - s * vertArray[i+2][1]) / 6]; |
|
12242 b[3] = [vertArray[i+1][0], vertArray[i+1][1]]; |
|
12243 curContext.bezierCurveTo(b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]); |
|
12244 } |
|
12245 fillStrokeClose(); |
|
12246 } |
|
12247 } |
|
12248 |
|
12249 // bezierVertex |
|
12250 else if ( isBezier && (curShape === PConstants.POLYGON || curShape === undef) ) { |
|
12251 curContext.beginPath(); |
|
12252 for (i = 0; i < vertArrayLength; i++) { |
|
12253 cachedVertArray = vertArray[i]; |
|
12254 if (vertArray[i]["isVert"]) { //if it is a vertex move to the position |
|
12255 if (vertArray[i]["moveTo"]) { |
|
12256 curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); |
|
12257 } else { |
|
12258 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12259 } |
|
12260 } else { //otherwise continue drawing bezier |
|
12261 curContext.bezierCurveTo(vertArray[i][0], vertArray[i][1], vertArray[i][2], vertArray[i][3], vertArray[i][4], vertArray[i][5]); |
|
12262 } |
|
12263 } |
|
12264 fillStrokeClose(); |
|
12265 } |
|
12266 |
|
12267 // render the vertices provided |
|
12268 else { |
|
12269 if (curShape === PConstants.POINTS) { |
|
12270 for (i = 0; i < vertArrayLength; i++) { |
|
12271 cachedVertArray = vertArray[i]; |
|
12272 if (doStroke) { |
|
12273 p.stroke(cachedVertArray[6]); |
|
12274 } |
|
12275 p.point(cachedVertArray[0], cachedVertArray[1]); |
|
12276 } |
|
12277 } else if (curShape === PConstants.LINES) { |
|
12278 for (i = 0; (i + 1) < vertArrayLength; i+=2) { |
|
12279 cachedVertArray = vertArray[i]; |
|
12280 if (doStroke) { |
|
12281 p.stroke(vertArray[i+1][6]); |
|
12282 } |
|
12283 p.line(cachedVertArray[0], cachedVertArray[1], vertArray[i+1][0], vertArray[i+1][1]); |
|
12284 } |
|
12285 } else if (curShape === PConstants.TRIANGLES) { |
|
12286 for (i = 0; (i + 2) < vertArrayLength; i+=3) { |
|
12287 cachedVertArray = vertArray[i]; |
|
12288 curContext.beginPath(); |
|
12289 curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); |
|
12290 curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); |
|
12291 curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]); |
|
12292 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12293 |
|
12294 if (doFill) { |
|
12295 p.fill(vertArray[i+2][5]); |
|
12296 executeContextFill(); |
|
12297 } |
|
12298 if (doStroke) { |
|
12299 p.stroke(vertArray[i+2][6]); |
|
12300 executeContextStroke(); |
|
12301 } |
|
12302 |
|
12303 curContext.closePath(); |
|
12304 } |
|
12305 } else if (curShape === PConstants.TRIANGLE_STRIP) { |
|
12306 for (i = 0; (i+1) < vertArrayLength; i++) { |
|
12307 cachedVertArray = vertArray[i]; |
|
12308 curContext.beginPath(); |
|
12309 curContext.moveTo(vertArray[i+1][0], vertArray[i+1][1]); |
|
12310 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12311 |
|
12312 if (doStroke) { |
|
12313 p.stroke(vertArray[i+1][6]); |
|
12314 } |
|
12315 if (doFill) { |
|
12316 p.fill(vertArray[i+1][5]); |
|
12317 } |
|
12318 |
|
12319 if (i + 2 < vertArrayLength) { |
|
12320 curContext.lineTo(vertArray[i+2][0], vertArray[i+2][1]); |
|
12321 if (doStroke) { |
|
12322 p.stroke(vertArray[i+2][6]); |
|
12323 } |
|
12324 if (doFill) { |
|
12325 p.fill(vertArray[i+2][5]); |
|
12326 } |
|
12327 } |
|
12328 fillStrokeClose(); |
|
12329 } |
|
12330 } else if (curShape === PConstants.TRIANGLE_FAN) { |
|
12331 if (vertArrayLength > 2) { |
|
12332 curContext.beginPath(); |
|
12333 curContext.moveTo(vertArray[0][0], vertArray[0][1]); |
|
12334 curContext.lineTo(vertArray[1][0], vertArray[1][1]); |
|
12335 curContext.lineTo(vertArray[2][0], vertArray[2][1]); |
|
12336 |
|
12337 if (doFill) { |
|
12338 p.fill(vertArray[2][5]); |
|
12339 executeContextFill(); |
|
12340 } |
|
12341 if (doStroke) { |
|
12342 p.stroke(vertArray[2][6]); |
|
12343 executeContextStroke(); |
|
12344 } |
|
12345 |
|
12346 curContext.closePath(); |
|
12347 for (i = 3; i < vertArrayLength; i++) { |
|
12348 cachedVertArray = vertArray[i]; |
|
12349 curContext.beginPath(); |
|
12350 curContext.moveTo(vertArray[0][0], vertArray[0][1]); |
|
12351 curContext.lineTo(vertArray[i-1][0], vertArray[i-1][1]); |
|
12352 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12353 |
|
12354 if (doFill) { |
|
12355 p.fill(cachedVertArray[5]); |
|
12356 executeContextFill(); |
|
12357 } |
|
12358 if (doStroke) { |
|
12359 p.stroke(cachedVertArray[6]); |
|
12360 executeContextStroke(); |
|
12361 } |
|
12362 |
|
12363 curContext.closePath(); |
|
12364 } |
|
12365 } |
|
12366 } else if (curShape === PConstants.QUADS) { |
|
12367 for (i = 0; (i + 3) < vertArrayLength; i+=4) { |
|
12368 cachedVertArray = vertArray[i]; |
|
12369 curContext.beginPath(); |
|
12370 curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); |
|
12371 for (j = 1; j < 4; j++) { |
|
12372 curContext.lineTo(vertArray[i+j][0], vertArray[i+j][1]); |
|
12373 } |
|
12374 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12375 |
|
12376 if (doFill) { |
|
12377 p.fill(vertArray[i+3][5]); |
|
12378 executeContextFill(); |
|
12379 } |
|
12380 if (doStroke) { |
|
12381 p.stroke(vertArray[i+3][6]); |
|
12382 executeContextStroke(); |
|
12383 } |
|
12384 |
|
12385 curContext.closePath(); |
|
12386 } |
|
12387 } else if (curShape === PConstants.QUAD_STRIP) { |
|
12388 if (vertArrayLength > 3) { |
|
12389 for (i = 0; (i+1) < vertArrayLength; i+=2) { |
|
12390 cachedVertArray = vertArray[i]; |
|
12391 curContext.beginPath(); |
|
12392 if (i+3 < vertArrayLength) { |
|
12393 curContext.moveTo(vertArray[i+2][0], vertArray[i+2][1]); |
|
12394 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12395 curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); |
|
12396 curContext.lineTo(vertArray[i+3][0], vertArray[i+3][1]); |
|
12397 |
|
12398 if (doFill) { |
|
12399 p.fill(vertArray[i+3][5]); |
|
12400 } |
|
12401 if (doStroke) { |
|
12402 p.stroke(vertArray[i+3][6]); |
|
12403 } |
|
12404 } else { |
|
12405 curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); |
|
12406 curContext.lineTo(vertArray[i+1][0], vertArray[i+1][1]); |
|
12407 } |
|
12408 fillStrokeClose(); |
|
12409 } |
|
12410 } |
|
12411 } else { |
|
12412 curContext.beginPath(); |
|
12413 curContext.moveTo(vertArray[0][0], vertArray[0][1]); |
|
12414 for (i = 1; i < vertArrayLength; i++) { |
|
12415 cachedVertArray = vertArray[i]; |
|
12416 if (cachedVertArray["isVert"]) { //if it is a vertex move to the position |
|
12417 if (cachedVertArray["moveTo"]) { |
|
12418 curContext.moveTo(cachedVertArray[0], cachedVertArray[1]); |
|
12419 } else { |
|
12420 curContext.lineTo(cachedVertArray[0], cachedVertArray[1]); |
|
12421 } |
|
12422 } |
|
12423 } |
|
12424 fillStrokeClose(); |
|
12425 } |
|
12426 } |
|
12427 |
|
12428 // Reset some settings |
|
12429 isCurve = false; |
|
12430 isBezier = false; |
|
12431 curveVertArray = []; |
|
12432 curveVertCount = 0; |
|
12433 |
|
12434 // If the shape is closed, the first element was added as last element. |
|
12435 // We must remove it again to prevent the list of vertices from growing |
|
12436 // over successive calls to endShape(CLOSE) |
|
12437 if (closeShape) { |
|
12438 vertArray.pop(); |
|
12439 } |
|
12440 }; |
|
12441 |
|
12442 Drawing3D.prototype.endShape = function(mode) { |
|
12443 // Duplicated in Drawing3D; too many variables used |
|
12444 if (vertArray.length === 0) { return; } |
|
12445 |
|
12446 var closeShape = mode === PConstants.CLOSE; |
|
12447 var lineVertArray = []; |
|
12448 var fillVertArray = []; |
|
12449 var colorVertArray = []; |
|
12450 var strokeVertArray = []; |
|
12451 var texVertArray = []; |
|
12452 var cachedVertArray; |
|
12453 |
|
12454 firstVert = true; |
|
12455 var i, j, k; |
|
12456 var vertArrayLength = vertArray.length; |
|
12457 |
|
12458 for (i = 0; i < vertArrayLength; i++) { |
|
12459 cachedVertArray = vertArray[i]; |
|
12460 for (j = 0; j < 3; j++) { |
|
12461 fillVertArray.push(cachedVertArray[j]); |
|
12462 } |
|
12463 } |
|
12464 |
|
12465 // 5,6,7,8 |
|
12466 // R,G,B,A - fill colour |
|
12467 for (i = 0; i < vertArrayLength; i++) { |
|
12468 cachedVertArray = vertArray[i]; |
|
12469 for (j = 5; j < 9; j++) { |
|
12470 colorVertArray.push(cachedVertArray[j]); |
|
12471 } |
|
12472 } |
|
12473 |
|
12474 // 9,10,11,12 |
|
12475 // R, G, B, A - stroke colour |
|
12476 for (i = 0; i < vertArrayLength; i++) { |
|
12477 cachedVertArray = vertArray[i]; |
|
12478 for (j = 9; j < 13; j++) { |
|
12479 strokeVertArray.push(cachedVertArray[j]); |
|
12480 } |
|
12481 } |
|
12482 |
|
12483 // texture u,v |
|
12484 for (i = 0; i < vertArrayLength; i++) { |
|
12485 cachedVertArray = vertArray[i]; |
|
12486 texVertArray.push(cachedVertArray[3]); |
|
12487 texVertArray.push(cachedVertArray[4]); |
|
12488 } |
|
12489 |
|
12490 // if shape is closed, push the first point into the last point (including colours) |
|
12491 if (closeShape) { |
|
12492 fillVertArray.push(vertArray[0][0]); |
|
12493 fillVertArray.push(vertArray[0][1]); |
|
12494 fillVertArray.push(vertArray[0][2]); |
|
12495 |
|
12496 for (i = 5; i < 9; i++) { |
|
12497 colorVertArray.push(vertArray[0][i]); |
|
12498 } |
|
12499 |
|
12500 for (i = 9; i < 13; i++) { |
|
12501 strokeVertArray.push(vertArray[0][i]); |
|
12502 } |
|
12503 |
|
12504 texVertArray.push(vertArray[0][3]); |
|
12505 texVertArray.push(vertArray[0][4]); |
|
12506 } |
|
12507 // End duplication |
|
12508 |
|
12509 // curveVertex |
|
12510 if ( isCurve && (curShape === PConstants.POLYGON || curShape === undef) ) { |
|
12511 lineVertArray = fillVertArray; |
|
12512 if (doStroke) { |
|
12513 line3D(lineVertArray, null, strokeVertArray); |
|
12514 } |
|
12515 if (doFill) { |
|
12516 fill3D(fillVertArray, null, colorVertArray); |
|
12517 } |
|
12518 } |
|
12519 // bezierVertex |
|
12520 else if ( isBezier && (curShape === PConstants.POLYGON || curShape === undef) ) { |
|
12521 lineVertArray = fillVertArray; |
|
12522 lineVertArray.splice(lineVertArray.length - 3); |
|
12523 strokeVertArray.splice(strokeVertArray.length - 4); |
|
12524 if (doStroke) { |
|
12525 line3D(lineVertArray, null, strokeVertArray); |
|
12526 } |
|
12527 if (doFill) { |
|
12528 fill3D(fillVertArray, "TRIANGLES", colorVertArray); |
|
12529 } |
|
12530 } |
|
12531 |
|
12532 // render the vertices provided |
|
12533 else { |
|
12534 if (curShape === PConstants.POINTS) { // if POINTS was the specified parameter in beginShape |
|
12535 for (i = 0; i < vertArrayLength; i++) { // loop through and push the point location information to the array |
|
12536 cachedVertArray = vertArray[i]; |
|
12537 for (j = 0; j < 3; j++) { |
|
12538 lineVertArray.push(cachedVertArray[j]); |
|
12539 } |
|
12540 } |
|
12541 point3D(lineVertArray, strokeVertArray); // render function for points |
|
12542 } else if (curShape === PConstants.LINES) { // if LINES was the specified parameter in beginShape |
|
12543 for (i = 0; i < vertArrayLength; i++) { // loop through and push the point location information to the array |
|
12544 cachedVertArray = vertArray[i]; |
|
12545 for (j = 0; j < 3; j++) { |
|
12546 lineVertArray.push(cachedVertArray[j]); |
|
12547 } |
|
12548 } |
|
12549 for (i = 0; i < vertArrayLength; i++) { // loop through and push the color information to the array |
|
12550 cachedVertArray = vertArray[i]; |
|
12551 for (j = 5; j < 9; j++) { |
|
12552 colorVertArray.push(cachedVertArray[j]); |
|
12553 } |
|
12554 } |
|
12555 line3D(lineVertArray, "LINES", strokeVertArray); // render function for lines |
|
12556 } else if (curShape === PConstants.TRIANGLES) { // if TRIANGLES was the specified parameter in beginShape |
|
12557 if (vertArrayLength > 2) { |
|
12558 for (i = 0; (i+2) < vertArrayLength; i+=3) { // loop through the array per triangle |
|
12559 fillVertArray = []; |
|
12560 texVertArray = []; |
|
12561 lineVertArray = []; |
|
12562 colorVertArray = []; |
|
12563 strokeVertArray = []; |
|
12564 for (j = 0; j < 3; j++) { |
|
12565 for (k = 0; k < 3; k++) { // loop through and push |
|
12566 lineVertArray.push(vertArray[i+j][k]); // the line point location information |
|
12567 fillVertArray.push(vertArray[i+j][k]); // and fill point location information |
|
12568 } |
|
12569 } |
|
12570 for (j = 0; j < 3; j++) { // loop through and push the texture information |
|
12571 for (k = 3; k < 5; k++) { |
|
12572 texVertArray.push(vertArray[i+j][k]); |
|
12573 } |
|
12574 } |
|
12575 for (j = 0; j < 3; j++) { |
|
12576 for (k = 5; k < 9; k++) { // loop through and push |
|
12577 colorVertArray.push(vertArray[i+j][k]); // the colour information |
|
12578 strokeVertArray.push(vertArray[i+j][k+4]);// and the stroke information |
|
12579 } |
|
12580 } |
|
12581 if (doStroke) { |
|
12582 line3D(lineVertArray, "LINE_LOOP", strokeVertArray ); // line render function |
|
12583 } |
|
12584 if (doFill || usingTexture) { |
|
12585 fill3D(fillVertArray, "TRIANGLES", colorVertArray, texVertArray); // fill shape render function |
|
12586 } |
|
12587 } |
|
12588 } |
|
12589 } else if (curShape === PConstants.TRIANGLE_STRIP) { // if TRIANGLE_STRIP was the specified parameter in beginShape |
|
12590 if (vertArrayLength > 2) { |
|
12591 for (i = 0; (i+2) < vertArrayLength; i++) { |
|
12592 lineVertArray = []; |
|
12593 fillVertArray = []; |
|
12594 strokeVertArray = []; |
|
12595 colorVertArray = []; |
|
12596 texVertArray = []; |
|
12597 for (j = 0; j < 3; j++) { |
|
12598 for (k = 0; k < 3; k++) { |
|
12599 lineVertArray.push(vertArray[i+j][k]); |
|
12600 fillVertArray.push(vertArray[i+j][k]); |
|
12601 } |
|
12602 } |
|
12603 for (j = 0; j < 3; j++) { |
|
12604 for (k = 3; k < 5; k++) { |
|
12605 texVertArray.push(vertArray[i+j][k]); |
|
12606 } |
|
12607 } |
|
12608 for (j = 0; j < 3; j++) { |
|
12609 for (k = 5; k < 9; k++) { |
|
12610 strokeVertArray.push(vertArray[i+j][k+4]); |
|
12611 colorVertArray.push(vertArray[i+j][k]); |
|
12612 } |
|
12613 } |
|
12614 |
|
12615 if (doFill || usingTexture) { |
|
12616 fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray); |
|
12617 } |
|
12618 if (doStroke) { |
|
12619 line3D(lineVertArray, "LINE_LOOP", strokeVertArray); |
|
12620 } |
|
12621 } |
|
12622 } |
|
12623 } else if (curShape === PConstants.TRIANGLE_FAN) { |
|
12624 if (vertArrayLength > 2) { |
|
12625 for (i = 0; i < 3; i++) { |
|
12626 cachedVertArray = vertArray[i]; |
|
12627 for (j = 0; j < 3; j++) { |
|
12628 lineVertArray.push(cachedVertArray[j]); |
|
12629 } |
|
12630 } |
|
12631 for (i = 0; i < 3; i++) { |
|
12632 cachedVertArray = vertArray[i]; |
|
12633 for (j = 9; j < 13; j++) { |
|
12634 strokeVertArray.push(cachedVertArray[j]); |
|
12635 } |
|
12636 } |
|
12637 if (doStroke) { |
|
12638 line3D(lineVertArray, "LINE_LOOP", strokeVertArray); |
|
12639 } |
|
12640 |
|
12641 for (i = 2; (i+1) < vertArrayLength; i++) { |
|
12642 lineVertArray = []; |
|
12643 strokeVertArray = []; |
|
12644 lineVertArray.push(vertArray[0][0]); |
|
12645 lineVertArray.push(vertArray[0][1]); |
|
12646 lineVertArray.push(vertArray[0][2]); |
|
12647 |
|
12648 strokeVertArray.push(vertArray[0][9]); |
|
12649 strokeVertArray.push(vertArray[0][10]); |
|
12650 strokeVertArray.push(vertArray[0][11]); |
|
12651 strokeVertArray.push(vertArray[0][12]); |
|
12652 |
|
12653 for (j = 0; j < 2; j++) { |
|
12654 for (k = 0; k < 3; k++) { |
|
12655 lineVertArray.push(vertArray[i+j][k]); |
|
12656 } |
|
12657 } |
|
12658 for (j = 0; j < 2; j++) { |
|
12659 for (k = 9; k < 13; k++) { |
|
12660 strokeVertArray.push(vertArray[i+j][k]); |
|
12661 } |
|
12662 } |
|
12663 if (doStroke) { |
|
12664 line3D(lineVertArray, "LINE_STRIP",strokeVertArray); |
|
12665 } |
|
12666 } |
|
12667 if (doFill || usingTexture) { |
|
12668 fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray); |
|
12669 } |
|
12670 } |
|
12671 } else if (curShape === PConstants.QUADS) { |
|
12672 for (i = 0; (i + 3) < vertArrayLength; i+=4) { |
|
12673 lineVertArray = []; |
|
12674 for (j = 0; j < 4; j++) { |
|
12675 cachedVertArray = vertArray[i+j]; |
|
12676 for (k = 0; k < 3; k++) { |
|
12677 lineVertArray.push(cachedVertArray[k]); |
|
12678 } |
|
12679 } |
|
12680 if (doStroke) { |
|
12681 line3D(lineVertArray, "LINE_LOOP",strokeVertArray); |
|
12682 } |
|
12683 |
|
12684 if (doFill) { |
|
12685 fillVertArray = []; |
|
12686 colorVertArray = []; |
|
12687 texVertArray = []; |
|
12688 for (j = 0; j < 3; j++) { |
|
12689 fillVertArray.push(vertArray[i][j]); |
|
12690 } |
|
12691 for (j = 5; j < 9; j++) { |
|
12692 colorVertArray.push(vertArray[i][j]); |
|
12693 } |
|
12694 |
|
12695 for (j = 0; j < 3; j++) { |
|
12696 fillVertArray.push(vertArray[i+1][j]); |
|
12697 } |
|
12698 for (j = 5; j < 9; j++) { |
|
12699 colorVertArray.push(vertArray[i+1][j]); |
|
12700 } |
|
12701 |
|
12702 for (j = 0; j < 3; j++) { |
|
12703 fillVertArray.push(vertArray[i+3][j]); |
|
12704 } |
|
12705 for (j = 5; j < 9; j++) { |
|
12706 colorVertArray.push(vertArray[i+3][j]); |
|
12707 } |
|
12708 |
|
12709 for (j = 0; j < 3; j++) { |
|
12710 fillVertArray.push(vertArray[i+2][j]); |
|
12711 } |
|
12712 for (j = 5; j < 9; j++) { |
|
12713 colorVertArray.push(vertArray[i+2][j]); |
|
12714 } |
|
12715 |
|
12716 if (usingTexture) { |
|
12717 texVertArray.push(vertArray[i+0][3]); |
|
12718 texVertArray.push(vertArray[i+0][4]); |
|
12719 texVertArray.push(vertArray[i+1][3]); |
|
12720 texVertArray.push(vertArray[i+1][4]); |
|
12721 texVertArray.push(vertArray[i+3][3]); |
|
12722 texVertArray.push(vertArray[i+3][4]); |
|
12723 texVertArray.push(vertArray[i+2][3]); |
|
12724 texVertArray.push(vertArray[i+2][4]); |
|
12725 } |
|
12726 |
|
12727 fill3D(fillVertArray, "TRIANGLE_STRIP", colorVertArray, texVertArray); |
|
12728 } |
|
12729 } |
|
12730 } else if (curShape === PConstants.QUAD_STRIP) { |
|
12731 var tempArray = []; |
|
12732 if (vertArrayLength > 3) { |
|
12733 for (i = 0; i < 2; i++) { |
|
12734 cachedVertArray = vertArray[i]; |
|
12735 for (j = 0; j < 3; j++) { |
|
12736 lineVertArray.push(cachedVertArray[j]); |
|
12737 } |
|
12738 } |
|
12739 |
|
12740 for (i = 0; i < 2; i++) { |
|
12741 cachedVertArray = vertArray[i]; |
|
12742 for (j = 9; j < 13; j++) { |
|
12743 strokeVertArray.push(cachedVertArray[j]); |
|
12744 } |
|
12745 } |
|
12746 |
|
12747 line3D(lineVertArray, "LINE_STRIP", strokeVertArray); |
|
12748 if (vertArrayLength > 4 && vertArrayLength % 2 > 0) { |
|
12749 tempArray = fillVertArray.splice(fillVertArray.length - 3); |
|
12750 vertArray.pop(); |
|
12751 } |
|
12752 for (i = 0; (i+3) < vertArrayLength; i+=2) { |
|
12753 lineVertArray = []; |
|
12754 strokeVertArray = []; |
|
12755 for (j = 0; j < 3; j++) { |
|
12756 lineVertArray.push(vertArray[i+1][j]); |
|
12757 } |
|
12758 for (j = 0; j < 3; j++) { |
|
12759 lineVertArray.push(vertArray[i+3][j]); |
|
12760 } |
|
12761 for (j = 0; j < 3; j++) { |
|
12762 lineVertArray.push(vertArray[i+2][j]); |
|
12763 } |
|
12764 for (j = 0; j < 3; j++) { |
|
12765 lineVertArray.push(vertArray[i+0][j]); |
|
12766 } |
|
12767 for (j = 9; j < 13; j++) { |
|
12768 strokeVertArray.push(vertArray[i+1][j]); |
|
12769 } |
|
12770 for (j = 9; j < 13; j++) { |
|
12771 strokeVertArray.push(vertArray[i+3][j]); |
|
12772 } |
|
12773 for (j = 9; j < 13; j++) { |
|
12774 strokeVertArray.push(vertArray[i+2][j]); |
|
12775 } |
|
12776 for (j = 9; j < 13; j++) { |
|
12777 strokeVertArray.push(vertArray[i+0][j]); |
|
12778 } |
|
12779 if (doStroke) { |
|
12780 line3D(lineVertArray, "LINE_STRIP", strokeVertArray); |
|
12781 } |
|
12782 } |
|
12783 |
|
12784 if (doFill || usingTexture) { |
|
12785 fill3D(fillVertArray, "TRIANGLE_LIST", colorVertArray, texVertArray); |
|
12786 } |
|
12787 } |
|
12788 } |
|
12789 // If the user didn't specify a type (LINES, TRIANGLES, etc) |
|
12790 else { |
|
12791 // If only one vertex was specified, it must be a point |
|
12792 if (vertArrayLength === 1) { |
|
12793 for (j = 0; j < 3; j++) { |
|
12794 lineVertArray.push(vertArray[0][j]); |
|
12795 } |
|
12796 for (j = 9; j < 13; j++) { |
|
12797 strokeVertArray.push(vertArray[0][j]); |
|
12798 } |
|
12799 point3D(lineVertArray,strokeVertArray); |
|
12800 } else { |
|
12801 for (i = 0; i < vertArrayLength; i++) { |
|
12802 cachedVertArray = vertArray[i]; |
|
12803 for (j = 0; j < 3; j++) { |
|
12804 lineVertArray.push(cachedVertArray[j]); |
|
12805 } |
|
12806 for (j = 5; j < 9; j++) { |
|
12807 strokeVertArray.push(cachedVertArray[j]); |
|
12808 } |
|
12809 } |
|
12810 if (doStroke && closeShape) { |
|
12811 line3D(lineVertArray, "LINE_LOOP", strokeVertArray); |
|
12812 } else if (doStroke && !closeShape) { |
|
12813 line3D(lineVertArray, "LINE_STRIP", strokeVertArray); |
|
12814 } |
|
12815 |
|
12816 // fill is ignored if textures are used |
|
12817 if (doFill || usingTexture) { |
|
12818 fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray, texVertArray); |
|
12819 } |
|
12820 } |
|
12821 } |
|
12822 // everytime beginShape is followed by a call to |
|
12823 // texture(), texturing it turned back on. We do this to |
|
12824 // figure out if the shape should be textured or filled |
|
12825 // with a color. |
|
12826 usingTexture = false; |
|
12827 curContext.useProgram(programObject3D); |
|
12828 uniformi("usingTexture3d", programObject3D, "usingTexture", usingTexture); |
|
12829 } |
|
12830 |
|
12831 // Reset some settings |
|
12832 isCurve = false; |
|
12833 isBezier = false; |
|
12834 curveVertArray = []; |
|
12835 curveVertCount = 0; |
|
12836 }; |
|
12837 |
|
12838 /** |
|
12839 * The function splineForward() setup forward-differencing matrix to be used for speedy |
|
12840 * curve rendering. It's based on using a specific number |
|
12841 * of curve segments and just doing incremental adds for each |
|
12842 * vertex of the segment, rather than running the mathematically |
|
12843 * expensive cubic equation. This function is used by both curveDetail and bezierDetail. |
|
12844 * |
|
12845 * @param {int} segments number of curve segments to use when drawing |
|
12846 * @param {PMatrix3D} matrix target object for the new matrix |
|
12847 */ |
|
12848 var splineForward = function(segments, matrix) { |
|
12849 var f = 1.0 / segments; |
|
12850 var ff = f * f; |
|
12851 var fff = ff * f; |
|
12852 |
|
12853 matrix.set(0, 0, 0, 1, fff, ff, f, 0, 6 * fff, 2 * ff, 0, 0, 6 * fff, 0, 0, 0); |
|
12854 }; |
|
12855 |
|
12856 /** |
|
12857 * The curveInit() function set the number of segments to use when drawing a Catmull-Rom |
|
12858 * curve, and setting the s parameter, which defines how tightly |
|
12859 * the curve fits to each vertex. Catmull-Rom curves are actually |
|
12860 * a subset of this curve type where the s is set to zero. |
|
12861 * This in an internal function used by curveDetail() and curveTightness(). |
|
12862 */ |
|
12863 var curveInit = function() { |
|
12864 // allocate only if/when used to save startup time |
|
12865 if (!curveDrawMatrix) { |
|
12866 curveBasisMatrix = new PMatrix3D(); |
|
12867 curveDrawMatrix = new PMatrix3D(); |
|
12868 curveInited = true; |
|
12869 } |
|
12870 |
|
12871 var s = curTightness; |
|
12872 curveBasisMatrix.set((s - 1) / 2, (s + 3) / 2, (-3 - s) / 2, (1 - s) / 2, |
|
12873 (1 - s), (-5 - s) / 2, (s + 2), (s - 1) / 2, |
|
12874 (s - 1) / 2, 0, (1 - s) / 2, 0, 0, 1, 0, 0); |
|
12875 |
|
12876 splineForward(curveDet, curveDrawMatrix); |
|
12877 |
|
12878 if (!bezierBasisInverse) { |
|
12879 //bezierBasisInverse = bezierBasisMatrix.get(); |
|
12880 //bezierBasisInverse.invert(); |
|
12881 curveToBezierMatrix = new PMatrix3D(); |
|
12882 } |
|
12883 |
|
12884 // TODO only needed for PGraphicsJava2D? if so, move it there |
|
12885 // actually, it's generally useful for other renderers, so keep it |
|
12886 // or hide the implementation elsewhere. |
|
12887 curveToBezierMatrix.set(curveBasisMatrix); |
|
12888 curveToBezierMatrix.preApply(bezierBasisInverse); |
|
12889 |
|
12890 // multiply the basis and forward diff matrices together |
|
12891 // saves much time since this needn't be done for each curve |
|
12892 curveDrawMatrix.apply(curveBasisMatrix); |
|
12893 }; |
|
12894 |
|
12895 /** |
|
12896 * Specifies vertex coordinates for Bezier curves. Each call to <b>bezierVertex()</b> defines the position of two control |
|
12897 * points and one anchor point of a Bezier curve, adding a new segment to a line or shape. The first time |
|
12898 * <b>bezierVertex()</b> is used within a <b>beginShape()</b> call, it must be prefaced with a call to <b>vertex()</b> |
|
12899 * to set the first anchor point. This function must be used between <b>beginShape()</b> and <b>endShape()</b> and only |
|
12900 * when there is no MODE parameter specified to <b>beginShape()</b>. Using the 3D version of requires rendering with P3D |
|
12901 * or OPENGL (see the Environment reference for more information). <br /> <br /> <b>NOTE: </b> Fill does not work properly yet. |
|
12902 * |
|
12903 * @param {float | int} cx1 The x-coordinate of 1st control point |
|
12904 * @param {float | int} cy1 The y-coordinate of 1st control point |
|
12905 * @param {float | int} cz1 The z-coordinate of 1st control point |
|
12906 * @param {float | int} cx2 The x-coordinate of 2nd control point |
|
12907 * @param {float | int} cy2 The y-coordinate of 2nd control point |
|
12908 * @param {float | int} cz2 The z-coordinate of 2nd control point |
|
12909 * @param {float | int} x The x-coordinate of the anchor point |
|
12910 * @param {float | int} y The y-coordinate of the anchor point |
|
12911 * @param {float | int} z The z-coordinate of the anchor point |
|
12912 * |
|
12913 * @see curveVertex |
|
12914 * @see vertex |
|
12915 * @see bezier |
|
12916 */ |
|
12917 Drawing2D.prototype.bezierVertex = function() { |
|
12918 isBezier = true; |
|
12919 var vert = []; |
|
12920 if (firstVert) { |
|
12921 throw ("vertex() must be used at least once before calling bezierVertex()"); |
|
12922 } |
|
12923 |
|
12924 for (var i = 0; i < arguments.length; i++) { |
|
12925 vert[i] = arguments[i]; |
|
12926 } |
|
12927 vertArray.push(vert); |
|
12928 vertArray[vertArray.length -1]["isVert"] = false; |
|
12929 }; |
|
12930 |
|
12931 Drawing3D.prototype.bezierVertex = function() { |
|
12932 isBezier = true; |
|
12933 var vert = []; |
|
12934 if (firstVert) { |
|
12935 throw ("vertex() must be used at least once before calling bezierVertex()"); |
|
12936 } |
|
12937 |
|
12938 if (arguments.length === 9) { |
|
12939 if (bezierDrawMatrix === undef) { |
|
12940 bezierDrawMatrix = new PMatrix3D(); |
|
12941 } |
|
12942 // setup matrix for forward differencing to speed up drawing |
|
12943 var lastPoint = vertArray.length - 1; |
|
12944 splineForward( bezDetail, bezierDrawMatrix ); |
|
12945 bezierDrawMatrix.apply( bezierBasisMatrix ); |
|
12946 var draw = bezierDrawMatrix.array(); |
|
12947 var x1 = vertArray[lastPoint][0], |
|
12948 y1 = vertArray[lastPoint][1], |
|
12949 z1 = vertArray[lastPoint][2]; |
|
12950 var xplot1 = draw[4] * x1 + draw[5] * arguments[0] + draw[6] * arguments[3] + draw[7] * arguments[6]; |
|
12951 var xplot2 = draw[8] * x1 + draw[9] * arguments[0] + draw[10]* arguments[3] + draw[11]* arguments[6]; |
|
12952 var xplot3 = draw[12]* x1 + draw[13]* arguments[0] + draw[14]* arguments[3] + draw[15]* arguments[6]; |
|
12953 |
|
12954 var yplot1 = draw[4] * y1 + draw[5] * arguments[1] + draw[6] * arguments[4] + draw[7] * arguments[7]; |
|
12955 var yplot2 = draw[8] * y1 + draw[9] * arguments[1] + draw[10]* arguments[4] + draw[11]* arguments[7]; |
|
12956 var yplot3 = draw[12]* y1 + draw[13]* arguments[1] + draw[14]* arguments[4] + draw[15]* arguments[7]; |
|
12957 |
|
12958 var zplot1 = draw[4] * z1 + draw[5] * arguments[2] + draw[6] * arguments[5] + draw[7] * arguments[8]; |
|
12959 var zplot2 = draw[8] * z1 + draw[9] * arguments[2] + draw[10]* arguments[5] + draw[11]* arguments[8]; |
|
12960 var zplot3 = draw[12]* z1 + draw[13]* arguments[2] + draw[14]* arguments[5] + draw[15]* arguments[8]; |
|
12961 for (var j = 0; j < bezDetail; j++) { |
|
12962 x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; |
|
12963 y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; |
|
12964 z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; |
|
12965 p.vertex(x1, y1, z1); |
|
12966 } |
|
12967 p.vertex(arguments[6], arguments[7], arguments[8]); |
|
12968 } |
|
12969 }; |
|
12970 |
|
12971 /** |
|
12972 * Sets a texture to be applied to vertex points. The <b>texture()</b> function |
|
12973 * must be called between <b>beginShape()</b> and <b>endShape()</b> and before |
|
12974 * any calls to vertex(). |
|
12975 * |
|
12976 * When textures are in use, the fill color is ignored. Instead, use tint() to |
|
12977 * specify the color of the texture as it is applied to the shape. |
|
12978 * |
|
12979 * @param {PImage} pimage the texture to apply |
|
12980 * |
|
12981 * @returns none |
|
12982 * |
|
12983 * @see textureMode |
|
12984 * @see beginShape |
|
12985 * @see endShape |
|
12986 * @see vertex |
|
12987 */ |
|
12988 p.texture = function(pimage) { |
|
12989 var curContext = drawing.$ensureContext(); |
|
12990 |
|
12991 if (pimage.__texture) { |
|
12992 curContext.bindTexture(curContext.TEXTURE_2D, pimage.__texture); |
|
12993 } else if (pimage.localName === "canvas") { |
|
12994 curContext.bindTexture(curContext.TEXTURE_2D, canTex); |
|
12995 curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, pimage); |
|
12996 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); |
|
12997 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR); |
|
12998 curContext.generateMipmap(curContext.TEXTURE_2D); |
|
12999 curTexture.width = pimage.width; |
|
13000 curTexture.height = pimage.height; |
|
13001 } else { |
|
13002 var texture = curContext.createTexture(), |
|
13003 cvs = document.createElement('canvas'), |
|
13004 cvsTextureCtx = cvs.getContext('2d'), |
|
13005 pot; |
|
13006 |
|
13007 // WebGL requires power of two textures |
|
13008 if (pimage.width & (pimage.width-1) === 0) { |
|
13009 cvs.width = pimage.width; |
|
13010 } else { |
|
13011 pot = 1; |
|
13012 while (pot < pimage.width) { |
|
13013 pot *= 2; |
|
13014 } |
|
13015 cvs.width = pot; |
|
13016 } |
|
13017 |
|
13018 if (pimage.height & (pimage.height-1) === 0) { |
|
13019 cvs.height = pimage.height; |
|
13020 } else { |
|
13021 pot = 1; |
|
13022 while (pot < pimage.height) { |
|
13023 pot *= 2; |
|
13024 } |
|
13025 cvs.height = pot; |
|
13026 } |
|
13027 |
|
13028 cvsTextureCtx.drawImage(pimage.sourceImg, 0, 0, pimage.width, pimage.height, 0, 0, cvs.width, cvs.height); |
|
13029 |
|
13030 curContext.bindTexture(curContext.TEXTURE_2D, texture); |
|
13031 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR_MIPMAP_LINEAR); |
|
13032 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); |
|
13033 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE); |
|
13034 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE); |
|
13035 curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, cvs); |
|
13036 curContext.generateMipmap(curContext.TEXTURE_2D); |
|
13037 |
|
13038 pimage.__texture = texture; |
|
13039 curTexture.width = pimage.width; |
|
13040 curTexture.height = pimage.height; |
|
13041 } |
|
13042 |
|
13043 usingTexture = true; |
|
13044 curContext.useProgram(programObject3D); |
|
13045 uniformi("usingTexture3d", programObject3D, "usingTexture", usingTexture); |
|
13046 }; |
|
13047 |
|
13048 /** |
|
13049 * Sets the coordinate space for texture mapping. There are two options, IMAGE, |
|
13050 * which refers to the actual coordinates of the image, and NORMALIZED, which |
|
13051 * refers to a normalized space of values ranging from 0 to 1. The default mode |
|
13052 * is IMAGE. In IMAGE, if an image is 100 x 200 pixels, mapping the image onto |
|
13053 * the entire size of a quad would require the points (0,0) (0,100) (100,200) (0,200). |
|
13054 * The same mapping in NORMAL_SPACE is (0,0) (0,1) (1,1) (0,1). |
|
13055 * |
|
13056 * @param MODE either IMAGE or NORMALIZED |
|
13057 * |
|
13058 * @returns none |
|
13059 * |
|
13060 * @see texture |
|
13061 */ |
|
13062 p.textureMode = function(mode){ |
|
13063 curTextureMode = mode; |
|
13064 }; |
|
13065 /** |
|
13066 * The curveVertexSegment() function handle emitting a specific segment of Catmull-Rom curve. Internal helper function used by <b>curveVertex()</b>. |
|
13067 */ |
|
13068 var curveVertexSegment = function(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) { |
|
13069 var x0 = x2; |
|
13070 var y0 = y2; |
|
13071 var z0 = z2; |
|
13072 |
|
13073 var draw = curveDrawMatrix.array(); |
|
13074 |
|
13075 var xplot1 = draw[4] * x1 + draw[5] * x2 + draw[6] * x3 + draw[7] * x4; |
|
13076 var xplot2 = draw[8] * x1 + draw[9] * x2 + draw[10] * x3 + draw[11] * x4; |
|
13077 var xplot3 = draw[12] * x1 + draw[13] * x2 + draw[14] * x3 + draw[15] * x4; |
|
13078 |
|
13079 var yplot1 = draw[4] * y1 + draw[5] * y2 + draw[6] * y3 + draw[7] * y4; |
|
13080 var yplot2 = draw[8] * y1 + draw[9] * y2 + draw[10] * y3 + draw[11] * y4; |
|
13081 var yplot3 = draw[12] * y1 + draw[13] * y2 + draw[14] * y3 + draw[15] * y4; |
|
13082 |
|
13083 var zplot1 = draw[4] * z1 + draw[5] * z2 + draw[6] * z3 + draw[7] * z4; |
|
13084 var zplot2 = draw[8] * z1 + draw[9] * z2 + draw[10] * z3 + draw[11] * z4; |
|
13085 var zplot3 = draw[12] * z1 + draw[13] * z2 + draw[14] * z3 + draw[15] * z4; |
|
13086 |
|
13087 p.vertex(x0, y0, z0); |
|
13088 for (var j = 0; j < curveDet; j++) { |
|
13089 x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; |
|
13090 y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; |
|
13091 z0 += zplot1; zplot1 += zplot2; zplot2 += zplot3; |
|
13092 p.vertex(x0, y0, z0); |
|
13093 } |
|
13094 }; |
|
13095 |
|
13096 /** |
|
13097 * Specifies vertex coordinates for curves. This function may only be used between <b>beginShape()</b> and |
|
13098 * <b>endShape()</b> and only when there is no MODE parameter specified to <b>beginShape()</b>. The first and last points |
|
13099 * in a series of <b>curveVertex()</b> lines will be used to guide the beginning and end of a the curve. A minimum of four |
|
13100 * points is required to draw a tiny curve between the second and third points. Adding a fifth point with |
|
13101 * <b>curveVertex()</b> will draw the curve between the second, third, and fourth points. The <b>curveVertex()</b> function |
|
13102 * is an implementation of Catmull-Rom splines. Using the 3D version of requires rendering with P3D or OPENGL (see the |
|
13103 * Environment reference for more information). <br /> <br /><b>NOTE: </b> Fill does not work properly yet. |
|
13104 * |
|
13105 * @param {float | int} x The x-coordinate of the vertex |
|
13106 * @param {float | int} y The y-coordinate of the vertex |
|
13107 * @param {float | int} z The z-coordinate of the vertex |
|
13108 * |
|
13109 * @see curve |
|
13110 * @see beginShape |
|
13111 * @see endShape |
|
13112 * @see vertex |
|
13113 * @see bezierVertex |
|
13114 */ |
|
13115 Drawing2D.prototype.curveVertex = function(x, y) { |
|
13116 isCurve = true; |
|
13117 |
|
13118 p.vertex(x, y); |
|
13119 }; |
|
13120 |
|
13121 Drawing3D.prototype.curveVertex = function(x, y, z) { |
|
13122 isCurve = true; |
|
13123 |
|
13124 if (!curveInited) { |
|
13125 curveInit(); |
|
13126 } |
|
13127 var vert = []; |
|
13128 vert[0] = x; |
|
13129 vert[1] = y; |
|
13130 vert[2] = z; |
|
13131 curveVertArray.push(vert); |
|
13132 curveVertCount++; |
|
13133 |
|
13134 if (curveVertCount > 3) { |
|
13135 curveVertexSegment( curveVertArray[curveVertCount-4][0], |
|
13136 curveVertArray[curveVertCount-4][1], |
|
13137 curveVertArray[curveVertCount-4][2], |
|
13138 curveVertArray[curveVertCount-3][0], |
|
13139 curveVertArray[curveVertCount-3][1], |
|
13140 curveVertArray[curveVertCount-3][2], |
|
13141 curveVertArray[curveVertCount-2][0], |
|
13142 curveVertArray[curveVertCount-2][1], |
|
13143 curveVertArray[curveVertCount-2][2], |
|
13144 curveVertArray[curveVertCount-1][0], |
|
13145 curveVertArray[curveVertCount-1][1], |
|
13146 curveVertArray[curveVertCount-1][2] ); |
|
13147 } |
|
13148 }; |
|
13149 |
|
13150 /** |
|
13151 * The curve() function draws a curved line on the screen. The first and second parameters |
|
13152 * specify the beginning control point and the last two parameters specify |
|
13153 * the ending control point. The middle parameters specify the start and |
|
13154 * stop of the curve. Longer curves can be created by putting a series of |
|
13155 * <b>curve()</b> functions together or using <b>curveVertex()</b>. |
|
13156 * An additional function called <b>curveTightness()</b> provides control |
|
13157 * for the visual quality of the curve. The <b>curve()</b> function is an |
|
13158 * implementation of Catmull-Rom splines. Using the 3D version of requires |
|
13159 * rendering with P3D or OPENGL (see the Environment reference for more |
|
13160 * information). |
|
13161 * |
|
13162 * @param {int|float} x1 coordinates for the beginning control point |
|
13163 * @param {int|float} y1 coordinates for the beginning control point |
|
13164 * @param {int|float} z1 coordinates for the beginning control point |
|
13165 * @param {int|float} x2 coordinates for the first point |
|
13166 * @param {int|float} y2 coordinates for the first point |
|
13167 * @param {int|float} z2 coordinates for the first point |
|
13168 * @param {int|float} x3 coordinates for the second point |
|
13169 * @param {int|float} y3 coordinates for the second point |
|
13170 * @param {int|float} z3 coordinates for the second point |
|
13171 * @param {int|float} x4 coordinates for the ending control point |
|
13172 * @param {int|float} y4 coordinates for the ending control point |
|
13173 * @param {int|float} z4 coordinates for the ending control point |
|
13174 * |
|
13175 * @see #curveVertex() |
|
13176 * @see #curveTightness() |
|
13177 * @see #bezier() |
|
13178 */ |
|
13179 Drawing2D.prototype.curve = function() { |
|
13180 if (arguments.length === 8) { // curve(x1, y1, x2, y2, x3, y3, x4, y4) |
|
13181 p.beginShape(); |
|
13182 p.curveVertex(arguments[0], arguments[1]); |
|
13183 p.curveVertex(arguments[2], arguments[3]); |
|
13184 p.curveVertex(arguments[4], arguments[5]); |
|
13185 p.curveVertex(arguments[6], arguments[7]); |
|
13186 p.endShape(); |
|
13187 } |
|
13188 }; |
|
13189 |
|
13190 Drawing3D.prototype.curve = function() { |
|
13191 if (arguments.length === 12) { // curve( x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4); |
|
13192 p.beginShape(); |
|
13193 p.curveVertex(arguments[0], arguments[1], arguments[2]); |
|
13194 p.curveVertex(arguments[3], arguments[4], arguments[5]); |
|
13195 p.curveVertex(arguments[6], arguments[7], arguments[8]); |
|
13196 p.curveVertex(arguments[9], arguments[10], arguments[11]); |
|
13197 p.endShape(); |
|
13198 } |
|
13199 }; |
|
13200 |
|
13201 /** |
|
13202 * The curveTightness() function modifies the quality of forms created with <b>curve()</b> and |
|
13203 * <b>curveVertex()</b>. The parameter <b>squishy</b> determines how the |
|
13204 * curve fits to the vertex points. The value 0.0 is the default value for |
|
13205 * <b>squishy</b> (this value defines the curves to be Catmull-Rom splines) |
|
13206 * and the value 1.0 connects all the points with straight lines. |
|
13207 * Values within the range -5.0 and 5.0 will deform the curves but |
|
13208 * will leave them recognizable and as values increase in magnitude, |
|
13209 * they will continue to deform. |
|
13210 * |
|
13211 * @param {float} tightness amount of deformation from the original vertices |
|
13212 * |
|
13213 * @see #curve() |
|
13214 * @see #curveVertex() |
|
13215 * |
|
13216 */ |
|
13217 p.curveTightness = function(tightness) { |
|
13218 curTightness = tightness; |
|
13219 }; |
|
13220 |
|
13221 /** |
|
13222 * The curveDetail() function sets the resolution at which curves display. The default value is 20. |
|
13223 * This function is only useful when using the P3D or OPENGL renderer. |
|
13224 * |
|
13225 * @param {int} detail resolution of the curves |
|
13226 * |
|
13227 * @see curve() |
|
13228 * @see curveVertex() |
|
13229 * @see curveTightness() |
|
13230 */ |
|
13231 p.curveDetail = function(detail) { |
|
13232 curveDet = detail; |
|
13233 curveInit(); |
|
13234 }; |
|
13235 |
|
13236 /** |
|
13237 * Modifies the location from which rectangles draw. The default mode is rectMode(CORNER), which |
|
13238 * specifies the location to be the upper left corner of the shape and uses the third and fourth |
|
13239 * parameters of rect() to specify the width and height. The syntax rectMode(CORNERS) uses the |
|
13240 * first and second parameters of rect() to set the location of one corner and uses the third and |
|
13241 * fourth parameters to set the opposite corner. The syntax rectMode(CENTER) draws the image from |
|
13242 * its center point and uses the third and forth parameters of rect() to specify the image's width |
|
13243 * and height. The syntax rectMode(RADIUS) draws the image from its center point and uses the third |
|
13244 * and forth parameters of rect() to specify half of the image's width and height. The parameter must |
|
13245 * be written in ALL CAPS because Processing is a case sensitive language. Note: In version 125, the |
|
13246 * mode named CENTER_RADIUS was shortened to RADIUS. |
|
13247 * |
|
13248 * @param {MODE} MODE Either CORNER, CORNERS, CENTER, or RADIUS |
|
13249 * |
|
13250 * @see rect |
|
13251 */ |
|
13252 p.rectMode = function(aRectMode) { |
|
13253 curRectMode = aRectMode; |
|
13254 }; |
|
13255 |
|
13256 /** |
|
13257 * Modifies the location from which images draw. The default mode is imageMode(CORNER), which specifies |
|
13258 * the location to be the upper left corner and uses the fourth and fifth parameters of image() to set |
|
13259 * the image's width and height. The syntax imageMode(CORNERS) uses the second and third parameters of |
|
13260 * image() to set the location of one corner of the image and uses the fourth and fifth parameters to |
|
13261 * set the opposite corner. Use imageMode(CENTER) to draw images centered at the given x and y position. |
|
13262 * The parameter to imageMode() must be written in ALL CAPS because Processing is a case sensitive language. |
|
13263 * |
|
13264 * @param {MODE} MODE Either CORNER, CORNERS, or CENTER |
|
13265 * |
|
13266 * @see loadImage |
|
13267 * @see PImage |
|
13268 * @see image |
|
13269 * @see background |
|
13270 */ |
|
13271 p.imageMode = function(mode) { |
|
13272 switch (mode) { |
|
13273 case PConstants.CORNER: |
|
13274 imageModeConvert = imageModeCorner; |
|
13275 break; |
|
13276 case PConstants.CORNERS: |
|
13277 imageModeConvert = imageModeCorners; |
|
13278 break; |
|
13279 case PConstants.CENTER: |
|
13280 imageModeConvert = imageModeCenter; |
|
13281 break; |
|
13282 default: |
|
13283 throw "Invalid imageMode"; |
|
13284 } |
|
13285 }; |
|
13286 |
|
13287 /** |
|
13288 * The origin of the ellipse is modified by the ellipseMode() function. The default configuration is |
|
13289 * ellipseMode(CENTER), which specifies the location of the ellipse as the center of the shape. The RADIUS |
|
13290 * mode is the same, but the width and height parameters to ellipse() specify the radius of the ellipse, |
|
13291 * rather than the diameter. The CORNER mode draws the shape from the upper-left corner of its bounding box. |
|
13292 * The CORNERS mode uses the four parameters to ellipse() to set two opposing corners of the ellipse's bounding |
|
13293 * box. The parameter must be written in "ALL CAPS" because Processing is a case sensitive language. |
|
13294 * |
|
13295 * @param {MODE} MODE Either CENTER, RADIUS, CORNER, or CORNERS. |
|
13296 * |
|
13297 * @see ellipse |
|
13298 */ |
|
13299 p.ellipseMode = function(aEllipseMode) { |
|
13300 curEllipseMode = aEllipseMode; |
|
13301 }; |
|
13302 |
|
13303 /** |
|
13304 * The arc() function draws an arc in the display window. |
|
13305 * Arcs are drawn along the outer edge of an ellipse defined by the |
|
13306 * <b>x</b>, <b>y</b>, <b>width</b> and <b>height</b> parameters. |
|
13307 * The origin or the arc's ellipse may be changed with the |
|
13308 * <b>ellipseMode()</b> function. |
|
13309 * The <b>start</b> and <b>stop</b> parameters specify the angles |
|
13310 * at which to draw the arc. |
|
13311 * |
|
13312 * @param {float} a x-coordinate of the arc's ellipse |
|
13313 * @param {float} b y-coordinate of the arc's ellipse |
|
13314 * @param {float} c width of the arc's ellipse |
|
13315 * @param {float} d height of the arc's ellipse |
|
13316 * @param {float} start angle to start the arc, specified in radians |
|
13317 * @param {float} stop angle to stop the arc, specified in radians |
|
13318 * |
|
13319 * @see #ellipseMode() |
|
13320 * @see #ellipse() |
|
13321 */ |
|
13322 p.arc = function(x, y, width, height, start, stop) { |
|
13323 if (width <= 0 || stop < start) { return; } |
|
13324 |
|
13325 if (curEllipseMode === PConstants.CORNERS) { |
|
13326 width = width - x; |
|
13327 height = height - y; |
|
13328 } else if (curEllipseMode === PConstants.RADIUS) { |
|
13329 x = x - width; |
|
13330 y = y - height; |
|
13331 width = width * 2; |
|
13332 height = height * 2; |
|
13333 } else if (curEllipseMode === PConstants.CENTER) { |
|
13334 x = x - width/2; |
|
13335 y = y - height/2; |
|
13336 } |
|
13337 // make sure that we're starting at a useful point |
|
13338 while (start < 0) { |
|
13339 start += PConstants.TWO_PI; |
|
13340 stop += PConstants.TWO_PI; |
|
13341 } |
|
13342 if (stop - start > PConstants.TWO_PI) { |
|
13343 start = 0; |
|
13344 stop = PConstants.TWO_PI; |
|
13345 } |
|
13346 var hr = width / 2; |
|
13347 var vr = height / 2; |
|
13348 var centerX = x + hr; |
|
13349 var centerY = y + vr; |
|
13350 var startLUT = 0 | (-0.5 + start * p.RAD_TO_DEG * 2); |
|
13351 var stopLUT = 0 | (0.5 + stop * p.RAD_TO_DEG * 2); |
|
13352 var i, j; |
|
13353 if (doFill) { |
|
13354 // shut off stroke for a minute |
|
13355 var savedStroke = doStroke; |
|
13356 doStroke = false; |
|
13357 p.beginShape(); |
|
13358 p.vertex(centerX, centerY); |
|
13359 for (i = startLUT; i <= stopLUT; i++) { |
|
13360 j = i % PConstants.SINCOS_LENGTH; |
|
13361 p.vertex(centerX + cosLUT[j] * hr, centerY + sinLUT[j] * vr); |
|
13362 } |
|
13363 p.endShape(PConstants.CLOSE); |
|
13364 doStroke = savedStroke; |
|
13365 } |
|
13366 |
|
13367 if (doStroke) { |
|
13368 // and doesn't include the first (center) vertex. |
|
13369 var savedFill = doFill; |
|
13370 doFill = false; |
|
13371 p.beginShape(); |
|
13372 for (i = startLUT; i <= stopLUT; i++) { |
|
13373 j = i % PConstants.SINCOS_LENGTH; |
|
13374 p.vertex(centerX + cosLUT[j] * hr, centerY + sinLUT[j] * vr); |
|
13375 } |
|
13376 p.endShape(); |
|
13377 doFill = savedFill; |
|
13378 } |
|
13379 }; |
|
13380 |
|
13381 /** |
|
13382 * Draws a line (a direct path between two points) to the screen. The version of line() with four parameters |
|
13383 * draws the line in 2D. To color a line, use the stroke() function. A line cannot be filled, therefore the |
|
13384 * fill() method will not affect the color of a line. 2D lines are drawn with a width of one pixel by default, |
|
13385 * but this can be changed with the strokeWeight() function. The version with six parameters allows the line |
|
13386 * to be placed anywhere within XYZ space. Drawing this shape in 3D using the z parameter requires the P3D or |
|
13387 * OPENGL parameter in combination with size. |
|
13388 * |
|
13389 * @param {int|float} x1 x-coordinate of the first point |
|
13390 * @param {int|float} y1 y-coordinate of the first point |
|
13391 * @param {int|float} z1 z-coordinate of the first point |
|
13392 * @param {int|float} x2 x-coordinate of the second point |
|
13393 * @param {int|float} y2 y-coordinate of the second point |
|
13394 * @param {int|float} z2 z-coordinate of the second point |
|
13395 * |
|
13396 * @see strokeWeight |
|
13397 * @see strokeJoin |
|
13398 * @see strokeCap |
|
13399 * @see beginShape |
|
13400 */ |
|
13401 Drawing2D.prototype.line = function(x1, y1, x2, y2) { |
|
13402 if (!doStroke) { |
|
13403 return; |
|
13404 } |
|
13405 x1 = Math.round(x1); |
|
13406 x2 = Math.round(x2); |
|
13407 y1 = Math.round(y1); |
|
13408 y2 = Math.round(y2); |
|
13409 // A line is only defined if it has different start and end coordinates. |
|
13410 // If they are the same, we call point instead. |
|
13411 if (x1 === x2 && y1 === y2) { |
|
13412 p.point(x1, y1); |
|
13413 return; |
|
13414 } |
|
13415 |
|
13416 var swap = undef, |
|
13417 lineCap = undef, |
|
13418 drawCrisp = true, |
|
13419 currentModelView = modelView.array(), |
|
13420 identityMatrix = [1, 0, 0, 0, 1, 0]; |
|
13421 // Test if any transformations have been applied to the sketch |
|
13422 for (var i = 0; i < 6 && drawCrisp; i++) { |
|
13423 drawCrisp = currentModelView[i] === identityMatrix[i]; |
|
13424 } |
|
13425 /* Draw crisp lines if the line is vertical or horizontal with the following method |
|
13426 * If any transformations have been applied to the sketch, don't make the line crisp |
|
13427 * If the line is directed up or to the left, reverse it by swapping x1/x2 or y1/y2 |
|
13428 * Make the line 1 pixel longer to work around cross-platform canvas implementations |
|
13429 * If the lineWidth is odd, translate the line by 0.5 in the perpendicular direction |
|
13430 * Even lineWidths do not need to be translated because the canvas will draw them on pixel boundaries |
|
13431 * Change the cap to butt-end to work around cross-platform canvas implementations |
|
13432 * Reverse the translate and lineCap canvas state changes after drawing the line |
|
13433 */ |
|
13434 if (drawCrisp) { |
|
13435 if (x1 === x2) { |
|
13436 if (y1 > y2) { |
|
13437 swap = y1; |
|
13438 y1 = y2; |
|
13439 y2 = swap; |
|
13440 } |
|
13441 y2++; |
|
13442 if (lineWidth % 2 === 1) { |
|
13443 curContext.translate(0.5, 0.0); |
|
13444 } |
|
13445 } else if (y1 === y2) { |
|
13446 if (x1 > x2) { |
|
13447 swap = x1; |
|
13448 x1 = x2; |
|
13449 x2 = swap; |
|
13450 } |
|
13451 x2++; |
|
13452 if (lineWidth % 2 === 1) { |
|
13453 curContext.translate(0.0, 0.5); |
|
13454 } |
|
13455 } |
|
13456 if (lineWidth === 1) { |
|
13457 lineCap = curContext.lineCap; |
|
13458 curContext.lineCap = 'butt'; |
|
13459 } |
|
13460 } |
|
13461 curContext.beginPath(); |
|
13462 curContext.moveTo(x1 || 0, y1 || 0); |
|
13463 curContext.lineTo(x2 || 0, y2 || 0); |
|
13464 executeContextStroke(); |
|
13465 if (drawCrisp) { |
|
13466 if (x1 === x2 && lineWidth % 2 === 1) { |
|
13467 curContext.translate(-0.5, 0.0); |
|
13468 } else if (y1 === y2 && lineWidth % 2 === 1) { |
|
13469 curContext.translate(0.0, -0.5); |
|
13470 } |
|
13471 if (lineWidth === 1) { |
|
13472 curContext.lineCap = lineCap; |
|
13473 } |
|
13474 } |
|
13475 }; |
|
13476 |
|
13477 Drawing3D.prototype.line = function(x1, y1, z1, x2, y2, z2) { |
|
13478 if (y2 === undef || z2 === undef) { // 2D line called in 3D context |
|
13479 z2 = 0; |
|
13480 y2 = x2; |
|
13481 x2 = z1; |
|
13482 z1 = 0; |
|
13483 } |
|
13484 |
|
13485 // a line is only defined if it has different start and end coordinates. |
|
13486 // If they are the same, we call point instead. |
|
13487 if (x1===x2 && y1===y2 && z1===z2) { |
|
13488 p.point(x1,y1,z1); |
|
13489 return; |
|
13490 } |
|
13491 |
|
13492 var lineVerts = [x1, y1, z1, x2, y2, z2]; |
|
13493 |
|
13494 var view = new PMatrix3D(); |
|
13495 view.scale(1, -1, 1); |
|
13496 view.apply(modelView.array()); |
|
13497 view.transpose(); |
|
13498 |
|
13499 if (lineWidth > 0 && doStroke) { |
|
13500 curContext.useProgram(programObject2D); |
|
13501 |
|
13502 uniformMatrix("model2d", programObject2D, "model", false, [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]); |
|
13503 uniformMatrix("view2d", programObject2D, "view", false, view.array()); |
|
13504 |
|
13505 uniformf("color2d", programObject2D, "color", strokeStyle); |
|
13506 uniformi("picktype2d", programObject2D, "picktype", 0); |
|
13507 |
|
13508 vertexAttribPointer("vertex2d", programObject2D, "Vertex", 3, lineBuffer); |
|
13509 disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); |
|
13510 |
|
13511 curContext.bufferData(curContext.ARRAY_BUFFER, new Float32Array(lineVerts), curContext.STREAM_DRAW); |
|
13512 curContext.drawArrays(curContext.LINES, 0, 2); |
|
13513 } |
|
13514 }; |
|
13515 |
|
13516 /** |
|
13517 * Draws a Bezier curve on the screen. These curves are defined by a series of anchor and control points. The first |
|
13518 * two parameters specify the first anchor point and the last two parameters specify the other anchor point. The |
|
13519 * middle parameters specify the control points which define the shape of the curve. Bezier curves were developed |
|
13520 * by French engineer Pierre Bezier. Using the 3D version of requires rendering with P3D or OPENGL (see the |
|
13521 * Environment reference for more information). |
|
13522 * |
|
13523 * @param {int | float} x1,y1,z1 coordinates for the first anchor point |
|
13524 * @param {int | float} cx1,cy1,cz1 coordinates for the first control point |
|
13525 * @param {int | float} cx2,cy2,cz2 coordinates for the second control point |
|
13526 * @param {int | float} x2,y2,z2 coordinates for the second anchor point |
|
13527 * |
|
13528 * @see bezierVertex |
|
13529 * @see curve |
|
13530 */ |
|
13531 Drawing2D.prototype.bezier = function() { |
|
13532 if (arguments.length !== 8) { |
|
13533 throw("You must use 8 parameters for bezier() in 2D mode"); |
|
13534 } |
|
13535 |
|
13536 p.beginShape(); |
|
13537 p.vertex( arguments[0], arguments[1] ); |
|
13538 p.bezierVertex( arguments[2], arguments[3], |
|
13539 arguments[4], arguments[5], |
|
13540 arguments[6], arguments[7] ); |
|
13541 p.endShape(); |
|
13542 }; |
|
13543 |
|
13544 Drawing3D.prototype.bezier = function() { |
|
13545 if (arguments.length !== 12) { |
|
13546 throw("You must use 12 parameters for bezier() in 3D mode"); |
|
13547 } |
|
13548 |
|
13549 p.beginShape(); |
|
13550 p.vertex( arguments[0], arguments[1], arguments[2] ); |
|
13551 p.bezierVertex( arguments[3], arguments[4], arguments[5], |
|
13552 arguments[6], arguments[7], arguments[8], |
|
13553 arguments[9], arguments[10], arguments[11] ); |
|
13554 p.endShape(); |
|
13555 }; |
|
13556 |
|
13557 /** |
|
13558 * Sets the resolution at which Beziers display. The default value is 20. This function is only useful when using the P3D |
|
13559 * or OPENGL renderer as the default (JAVA2D) renderer does not use this information. |
|
13560 * |
|
13561 * @param {int} detail resolution of the curves |
|
13562 * |
|
13563 * @see curve |
|
13564 * @see curveVertex |
|
13565 * @see curveTightness |
|
13566 */ |
|
13567 p.bezierDetail = function( detail ){ |
|
13568 bezDetail = detail; |
|
13569 }; |
|
13570 |
|
13571 /** |
|
13572 * The bezierPoint() function evalutes quadratic bezier at point t for points a, b, c, d. |
|
13573 * The parameter t varies between 0 and 1. The a and d parameters are the |
|
13574 * on-curve points, b and c are the control points. To make a two-dimensional |
|
13575 * curve, call this function once with the x coordinates and a second time |
|
13576 * with the y coordinates to get the location of a bezier curve at t. |
|
13577 * |
|
13578 * @param {float} a coordinate of first point on the curve |
|
13579 * @param {float} b coordinate of first control point |
|
13580 * @param {float} c coordinate of second control point |
|
13581 * @param {float} d coordinate of second point on the curve |
|
13582 * @param {float} t value between 0 and 1 |
|
13583 * |
|
13584 * @see #bezier() |
|
13585 * @see #bezierVertex() |
|
13586 * @see #curvePoint() |
|
13587 */ |
|
13588 p.bezierPoint = function(a, b, c, d, t) { |
|
13589 return (1 - t) * (1 - t) * (1 - t) * a + 3 * (1 - t) * (1 - t) * t * b + 3 * (1 - t) * t * t * c + t * t * t * d; |
|
13590 }; |
|
13591 |
|
13592 /** |
|
13593 * The bezierTangent() function calculates the tangent of a point on a Bezier curve. There is a good |
|
13594 * definition of "tangent" at Wikipedia: <a href="http://en.wikipedia.org/wiki/Tangent" target="new">http://en.wikipedia.org/wiki/Tangent</a> |
|
13595 * |
|
13596 * @param {float} a coordinate of first point on the curve |
|
13597 * @param {float} b coordinate of first control point |
|
13598 * @param {float} c coordinate of second control point |
|
13599 * @param {float} d coordinate of second point on the curve |
|
13600 * @param {float} t value between 0 and 1 |
|
13601 * |
|
13602 * @see #bezier() |
|
13603 * @see #bezierVertex() |
|
13604 * @see #curvePoint() |
|
13605 */ |
|
13606 p.bezierTangent = function(a, b, c, d, t) { |
|
13607 return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b)); |
|
13608 }; |
|
13609 |
|
13610 /** |
|
13611 * The curvePoint() function evalutes the Catmull-Rom curve at point t for points a, b, c, d. The |
|
13612 * parameter t varies between 0 and 1, a and d are points on the curve, |
|
13613 * and b and c are the control points. This can be done once with the x |
|
13614 * coordinates and a second time with the y coordinates to get the |
|
13615 * location of a curve at t. |
|
13616 * |
|
13617 * @param {int|float} a coordinate of first point on the curve |
|
13618 * @param {int|float} b coordinate of second point on the curve |
|
13619 * @param {int|float} c coordinate of third point on the curve |
|
13620 * @param {int|float} d coordinate of fourth point on the curve |
|
13621 * @param {float} t value between 0 and 1 |
|
13622 * |
|
13623 * @see #curve() |
|
13624 * @see #curveVertex() |
|
13625 * @see #bezierPoint() |
|
13626 */ |
|
13627 p.curvePoint = function(a, b, c, d, t) { |
|
13628 return 0.5 * ((2 * b) + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t * t + (-a + 3 * b - 3 * c + d) * t * t * t); |
|
13629 }; |
|
13630 |
|
13631 /** |
|
13632 * The curveTangent() function calculates the tangent of a point on a Catmull-Rom curve. |
|
13633 * There is a good definition of "tangent" at Wikipedia: <a href="http://en.wikipedia.org/wiki/Tangent" target="new">http://en.wikipedia.org/wiki/Tangent</a>. |
|
13634 * |
|
13635 * @param {int|float} a coordinate of first point on the curve |
|
13636 * @param {int|float} b coordinate of first control point |
|
13637 * @param {int|float} c coordinate of second control point |
|
13638 * @param {int|float} d coordinate of second point on the curve |
|
13639 * @param {float} t value between 0 and 1 |
|
13640 * |
|
13641 * @see #curve() |
|
13642 * @see #curveVertex() |
|
13643 * @see #curvePoint() |
|
13644 * @see #bezierTangent() |
|
13645 */ |
|
13646 p.curveTangent = function(a, b, c, d, t) { |
|
13647 return 0.5 * ((-a + c) + 2 * (2 * a - 5 * b + 4 * c - d) * t + 3 * (-a + 3 * b - 3 * c + d) * t * t); |
|
13648 }; |
|
13649 |
|
13650 /** |
|
13651 * A triangle is a plane created by connecting three points. The first two arguments specify the first point, |
|
13652 * the middle two arguments specify the second point, and the last two arguments specify the third point. |
|
13653 * |
|
13654 * @param {int | float} x1 x-coordinate of the first point |
|
13655 * @param {int | float} y1 y-coordinate of the first point |
|
13656 * @param {int | float} x2 x-coordinate of the second point |
|
13657 * @param {int | float} y2 y-coordinate of the second point |
|
13658 * @param {int | float} x3 x-coordinate of the third point |
|
13659 * @param {int | float} y3 y-coordinate of the third point |
|
13660 */ |
|
13661 p.triangle = function(x1, y1, x2, y2, x3, y3) { |
|
13662 p.beginShape(PConstants.TRIANGLES); |
|
13663 p.vertex(x1, y1, 0); |
|
13664 p.vertex(x2, y2, 0); |
|
13665 p.vertex(x3, y3, 0); |
|
13666 p.endShape(); |
|
13667 }; |
|
13668 |
|
13669 /** |
|
13670 * A quad is a quadrilateral, a four sided polygon. It is similar to a rectangle, but the angles between its |
|
13671 * edges are not constrained to ninety degrees. The first pair of parameters (x1,y1) sets the first vertex |
|
13672 * and the subsequent pairs should proceed clockwise or counter-clockwise around the defined shape. |
|
13673 * |
|
13674 * @param {float | int} x1 x-coordinate of the first corner |
|
13675 * @param {float | int} y1 y-coordinate of the first corner |
|
13676 * @param {float | int} x2 x-coordinate of the second corner |
|
13677 * @param {float | int} y2 y-coordinate of the second corner |
|
13678 * @param {float | int} x3 x-coordinate of the third corner |
|
13679 * @param {float | int} y3 y-coordinate of the third corner |
|
13680 * @param {float | int} x4 x-coordinate of the fourth corner |
|
13681 * @param {float | int} y4 y-coordinate of the fourth corner |
|
13682 */ |
|
13683 p.quad = function(x1, y1, x2, y2, x3, y3, x4, y4) { |
|
13684 p.beginShape(PConstants.QUADS); |
|
13685 p.vertex(x1, y1, 0); |
|
13686 p.vertex(x2, y2, 0); |
|
13687 p.vertex(x3, y3, 0); |
|
13688 p.vertex(x4, y4, 0); |
|
13689 p.endShape(); |
|
13690 }; |
|
13691 |
|
13692 var roundedRect$2d = function(x, y, width, height, tl, tr, br, bl) { |
|
13693 if (bl === undef) { |
|
13694 tr = tl; |
|
13695 br = tl; |
|
13696 bl = tl; |
|
13697 } |
|
13698 var halfWidth = width / 2, |
|
13699 halfHeight = height / 2; |
|
13700 if (tl > halfWidth || tl > halfHeight) { |
|
13701 tl = Math.min(halfWidth, halfHeight); |
|
13702 } |
|
13703 if (tr > halfWidth || tr > halfHeight) { |
|
13704 tr = Math.min(halfWidth, halfHeight); |
|
13705 } |
|
13706 if (br > halfWidth || br > halfHeight) { |
|
13707 br = Math.min(halfWidth, halfHeight); |
|
13708 } |
|
13709 if (bl > halfWidth || bl > halfHeight) { |
|
13710 bl = Math.min(halfWidth, halfHeight); |
|
13711 } |
|
13712 // Translate the stroke by (0.5, 0.5) to draw a crisp border |
|
13713 if (!doFill || doStroke) { |
|
13714 curContext.translate(0.5, 0.5); |
|
13715 } |
|
13716 curContext.beginPath(); |
|
13717 curContext.moveTo(x + tl, y); |
|
13718 curContext.lineTo(x + width - tr, y); |
|
13719 curContext.quadraticCurveTo(x + width, y, x + width, y + tr); |
|
13720 curContext.lineTo(x + width, y + height - br); |
|
13721 curContext.quadraticCurveTo(x + width, y + height, x + width - br, y + height); |
|
13722 curContext.lineTo(x + bl, y + height); |
|
13723 curContext.quadraticCurveTo(x, y + height, x, y + height - bl); |
|
13724 curContext.lineTo(x, y + tl); |
|
13725 curContext.quadraticCurveTo(x, y, x + tl, y); |
|
13726 if (!doFill || doStroke) { |
|
13727 curContext.translate(-0.5, -0.5); |
|
13728 } |
|
13729 executeContextFill(); |
|
13730 executeContextStroke(); |
|
13731 }; |
|
13732 |
|
13733 /** |
|
13734 * Draws a rectangle to the screen. A rectangle is a four-sided shape with every angle at ninety |
|
13735 * degrees. The first two parameters set the location, the third sets the width, and the fourth |
|
13736 * sets the height. The origin is changed with the rectMode() function. |
|
13737 * |
|
13738 * @param {int|float} x x-coordinate of the rectangle |
|
13739 * @param {int|float} y y-coordinate of the rectangle |
|
13740 * @param {int|float} width width of the rectangle |
|
13741 * @param {int|float} height height of the rectangle |
|
13742 * |
|
13743 * @see rectMode |
|
13744 * @see quad |
|
13745 */ |
|
13746 Drawing2D.prototype.rect = function(x, y, width, height, tl, tr, br, bl) { |
|
13747 if (!width && !height) { |
|
13748 return; |
|
13749 } |
|
13750 |
|
13751 if (curRectMode === PConstants.CORNERS) { |
|
13752 width -= x; |
|
13753 height -= y; |
|
13754 } else if (curRectMode === PConstants.RADIUS) { |
|
13755 width *= 2; |
|
13756 height *= 2; |
|
13757 x -= width / 2; |
|
13758 y -= height / 2; |
|
13759 } else if (curRectMode === PConstants.CENTER) { |
|
13760 x -= width / 2; |
|
13761 y -= height / 2; |
|
13762 } |
|
13763 |
|
13764 x = Math.round(x); |
|
13765 y = Math.round(y); |
|
13766 width = Math.round(width); |
|
13767 height = Math.round(height); |
|
13768 if (tl !== undef) { |
|
13769 roundedRect$2d(x, y, width, height, tl, tr, br, bl); |
|
13770 return; |
|
13771 } |
|
13772 |
|
13773 // Translate the line by (0.5, 0.5) to draw a crisp rectangle border |
|
13774 if (doStroke && lineWidth % 2 === 1) { |
|
13775 curContext.translate(0.5, 0.5); |
|
13776 } |
|
13777 curContext.beginPath(); |
|
13778 curContext.rect(x, y, width, height); |
|
13779 executeContextFill(); |
|
13780 executeContextStroke(); |
|
13781 if (doStroke && lineWidth % 2 === 1) { |
|
13782 curContext.translate(-0.5, -0.5); |
|
13783 } |
|
13784 }; |
|
13785 |
|
13786 Drawing3D.prototype.rect = function(x, y, width, height, tl, tr, br, bl) { |
|
13787 if (tl !== undef) { |
|
13788 throw "rect() with rounded corners is not supported in 3D mode"; |
|
13789 } |
|
13790 |
|
13791 if (curRectMode === PConstants.CORNERS) { |
|
13792 width -= x; |
|
13793 height -= y; |
|
13794 } else if (curRectMode === PConstants.RADIUS) { |
|
13795 width *= 2; |
|
13796 height *= 2; |
|
13797 x -= width / 2; |
|
13798 y -= height / 2; |
|
13799 } else if (curRectMode === PConstants.CENTER) { |
|
13800 x -= width / 2; |
|
13801 y -= height / 2; |
|
13802 } |
|
13803 |
|
13804 // Modeling transformation |
|
13805 var model = new PMatrix3D(); |
|
13806 model.translate(x, y, 0); |
|
13807 model.scale(width, height, 1); |
|
13808 model.transpose(); |
|
13809 |
|
13810 // viewing transformation needs to have Y flipped |
|
13811 // becuase that's what Processing does. |
|
13812 var view = new PMatrix3D(); |
|
13813 view.scale(1, -1, 1); |
|
13814 view.apply(modelView.array()); |
|
13815 view.transpose(); |
|
13816 |
|
13817 if (lineWidth > 0 && doStroke) { |
|
13818 curContext.useProgram(programObject2D); |
|
13819 uniformMatrix("model2d", programObject2D, "model", false, model.array()); |
|
13820 uniformMatrix("view2d", programObject2D, "view", false, view.array()); |
|
13821 uniformf("color2d", programObject2D, "color", strokeStyle); |
|
13822 uniformi("picktype2d", programObject2D, "picktype", 0); |
|
13823 vertexAttribPointer("vertex2d", programObject2D, "Vertex", 3, rectBuffer); |
|
13824 disableVertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord"); |
|
13825 curContext.drawArrays(curContext.LINE_LOOP, 0, rectVerts.length / 3); |
|
13826 } |
|
13827 |
|
13828 if (doFill) { |
|
13829 curContext.useProgram(programObject3D); |
|
13830 uniformMatrix("model3d", programObject3D, "model", false, model.array()); |
|
13831 uniformMatrix("view3d", programObject3D, "view", false, view.array()); |
|
13832 |
|
13833 // fix stitching problems. (lines get occluded by triangles |
|
13834 // since they share the same depth values). This is not entirely |
|
13835 // working, but it's a start for drawing the outline. So |
|
13836 // developers can start playing around with styles. |
|
13837 curContext.enable(curContext.POLYGON_OFFSET_FILL); |
|
13838 curContext.polygonOffset(1, 1); |
|
13839 |
|
13840 uniformf("color3d", programObject3D, "color", fillStyle); |
|
13841 |
|
13842 if(lightCount > 0){ |
|
13843 var v = new PMatrix3D(); |
|
13844 v.set(view); |
|
13845 |
|
13846 var m = new PMatrix3D(); |
|
13847 m.set(model); |
|
13848 |
|
13849 v.mult(m); |
|
13850 |
|
13851 var normalMatrix = new PMatrix3D(); |
|
13852 normalMatrix.set(v); |
|
13853 normalMatrix.invert(); |
|
13854 normalMatrix.transpose(); |
|
13855 |
|
13856 uniformMatrix("normalTransform3d", programObject3D, "normalTransform", false, normalMatrix.array()); |
|
13857 vertexAttribPointer("normal3d", programObject3D, "Normal", 3, rectNormBuffer); |
|
13858 } |
|
13859 else{ |
|
13860 disableVertexAttribPointer("normal3d", programObject3D, "Normal"); |
|
13861 } |
|
13862 |
|
13863 vertexAttribPointer("vertex3d", programObject3D, "Vertex", 3, rectBuffer); |
|
13864 |
|
13865 curContext.drawArrays(curContext.TRIANGLE_FAN, 0, rectVerts.length / 3); |
|
13866 curContext.disable(curContext.POLYGON_OFFSET_FILL); |
|
13867 } |
|
13868 }; |
|
13869 |
|
13870 /** |
|
13871 * Draws an ellipse (oval) in the display window. An ellipse with an equal <b>width</b> and <b>height</b> is a circle. |
|
13872 * The first two parameters set the location, the third sets the width, and the fourth sets the height. The origin may be |
|
13873 * changed with the <b>ellipseMode()</b> function. |
|
13874 * |
|
13875 * @param {float|int} x x-coordinate of the ellipse |
|
13876 * @param {float|int} y y-coordinate of the ellipse |
|
13877 * @param {float|int} width width of the ellipse |
|
13878 * @param {float|int} height height of the ellipse |
|
13879 * |
|
13880 * @see ellipseMode |
|
13881 */ |
|
13882 Drawing2D.prototype.ellipse = function(x, y, width, height) { |
|
13883 x = x || 0; |
|
13884 y = y || 0; |
|
13885 |
|
13886 if (width <= 0 && height <= 0) { |
|
13887 return; |
|
13888 } |
|
13889 |
|
13890 if (curEllipseMode === PConstants.RADIUS) { |
|
13891 width *= 2; |
|
13892 height *= 2; |
|
13893 } else if (curEllipseMode === PConstants.CORNERS) { |
|
13894 width = width - x; |
|
13895 height = height - y; |
|
13896 x += width / 2; |
|
13897 y += height / 2; |
|
13898 } else if (curEllipseMode === PConstants.CORNER) { |
|
13899 x += width / 2; |
|
13900 y += height / 2; |
|
13901 } |
|
13902 |
|
13903 // Shortcut for drawing a 2D circle |
|
13904 if (width === height) { |
|
13905 curContext.beginPath(); |
|
13906 curContext.arc(x, y, width / 2, 0, PConstants.TWO_PI, false); |
|
13907 executeContextFill(); |
|
13908 executeContextStroke(); |
|
13909 } else { |
|
13910 var w = width / 2, |
|
13911 h = height / 2, |
|
13912 C = 0.5522847498307933, |
|
13913 c_x = C * w, |
|
13914 c_y = C * h; |
|
13915 |
|
13916 p.beginShape(); |
|
13917 p.vertex(x + w, y); |
|
13918 p.bezierVertex(x + w, y - c_y, x + c_x, y - h, x, y - h); |
|
13919 p.bezierVertex(x - c_x, y - h, x - w, y - c_y, x - w, y); |
|
13920 p.bezierVertex(x - w, y + c_y, x - c_x, y + h, x, y + h); |
|
13921 p.bezierVertex(x + c_x, y + h, x + w, y + c_y, x + w, y); |
|
13922 p.endShape(); |
|
13923 } |
|
13924 }; |
|
13925 |
|
13926 Drawing3D.prototype.ellipse = function(x, y, width, height) { |
|
13927 x = x || 0; |
|
13928 y = y || 0; |
|
13929 |
|
13930 if (width <= 0 && height <= 0) { |
|
13931 return; |
|
13932 } |
|
13933 |
|
13934 if (curEllipseMode === PConstants.RADIUS) { |
|
13935 width *= 2; |
|
13936 height *= 2; |
|
13937 } else if (curEllipseMode === PConstants.CORNERS) { |
|
13938 width = width - x; |
|
13939 height = height - y; |
|
13940 x += width / 2; |
|
13941 y += height / 2; |
|
13942 } else if (curEllipseMode === PConstants.CORNER) { |
|
13943 x += width / 2; |
|
13944 y += height / 2; |
|
13945 } |
|
13946 |
|
13947 var w = width / 2, |
|
13948 h = height / 2, |
|
13949 C = 0.5522847498307933, |
|
13950 c_x = C * w, |
|
13951 c_y = C * h; |
|
13952 |
|
13953 p.beginShape(); |
|
13954 p.vertex(x + w, y); |
|
13955 p.bezierVertex(x + w, y - c_y, 0, x + c_x, y - h, 0, x, y - h, 0); |
|
13956 p.bezierVertex(x - c_x, y - h, 0, x - w, y - c_y, 0, x - w, y, 0); |
|
13957 p.bezierVertex(x - w, y + c_y, 0, x - c_x, y + h, 0, x, y + h, 0); |
|
13958 p.bezierVertex(x + c_x, y + h, 0, x + w, y + c_y, 0, x + w, y, 0); |
|
13959 p.endShape(); |
|
13960 |
|
13961 if (doFill) { |
|
13962 //temporary workaround to not working fills for bezier -- will fix later |
|
13963 var xAv = 0, yAv = 0, i, j; |
|
13964 for (i = 0; i < vertArray.length; i++) { |
|
13965 xAv += vertArray[i][0]; |
|
13966 yAv += vertArray[i][1]; |
|
13967 } |
|
13968 xAv /= vertArray.length; |
|
13969 yAv /= vertArray.length; |
|
13970 var vert = [], |
|
13971 fillVertArray = [], |
|
13972 colorVertArray = []; |
|
13973 vert[0] = xAv; |
|
13974 vert[1] = yAv; |
|
13975 vert[2] = 0; |
|
13976 vert[3] = 0; |
|
13977 vert[4] = 0; |
|
13978 vert[5] = fillStyle[0]; |
|
13979 vert[6] = fillStyle[1]; |
|
13980 vert[7] = fillStyle[2]; |
|
13981 vert[8] = fillStyle[3]; |
|
13982 vert[9] = strokeStyle[0]; |
|
13983 vert[10] = strokeStyle[1]; |
|
13984 vert[11] = strokeStyle[2]; |
|
13985 vert[12] = strokeStyle[3]; |
|
13986 vert[13] = normalX; |
|
13987 vert[14] = normalY; |
|
13988 vert[15] = normalZ; |
|
13989 vertArray.unshift(vert); |
|
13990 for (i = 0; i < vertArray.length; i++) { |
|
13991 for (j = 0; j < 3; j++) { |
|
13992 fillVertArray.push(vertArray[i][j]); |
|
13993 } |
|
13994 for (j = 5; j < 9; j++) { |
|
13995 colorVertArray.push(vertArray[i][j]); |
|
13996 } |
|
13997 } |
|
13998 fill3D(fillVertArray, "TRIANGLE_FAN", colorVertArray); |
|
13999 } |
|
14000 }; |
|
14001 |
|
14002 /** |
|
14003 * Sets the current normal vector. This is for drawing three dimensional shapes and surfaces and |
|
14004 * specifies a vector perpendicular to the surface of the shape which determines how lighting affects |
|
14005 * it. Processing attempts to automatically assign normals to shapes, but since that's imperfect, |
|
14006 * this is a better option when you want more control. This function is identical to glNormal3f() in OpenGL. |
|
14007 * |
|
14008 * @param {float} nx x direction |
|
14009 * @param {float} ny y direction |
|
14010 * @param {float} nz z direction |
|
14011 * |
|
14012 * @see beginShape |
|
14013 * @see endShape |
|
14014 * @see lights |
|
14015 */ |
|
14016 p.normal = function(nx, ny, nz) { |
|
14017 if (arguments.length !== 3 || !(typeof nx === "number" && typeof ny === "number" && typeof nz === "number")) { |
|
14018 throw "normal() requires three numeric arguments."; |
|
14019 } |
|
14020 |
|
14021 normalX = nx; |
|
14022 normalY = ny; |
|
14023 normalZ = nz; |
|
14024 |
|
14025 if (curShape !== 0) { |
|
14026 if (normalMode === PConstants.NORMAL_MODE_AUTO) { |
|
14027 normalMode = PConstants.NORMAL_MODE_SHAPE; |
|
14028 } else if (normalMode === PConstants.NORMAL_MODE_SHAPE) { |
|
14029 normalMode = PConstants.NORMAL_MODE_VERTEX; |
|
14030 } |
|
14031 } |
|
14032 }; |
|
14033 |
|
14034 //////////////////////////////////////////////////////////////////////////// |
|
14035 // Raster drawing functions |
|
14036 //////////////////////////////////////////////////////////////////////////// |
|
14037 |
|
14038 /** |
|
14039 * Saves an image from the display window. Images are saved in TIFF, TARGA, JPEG, and PNG format |
|
14040 * depending on the extension within the filename parameter. For example, "image.tif" will have |
|
14041 * a TIFF image and "image.png" will save a PNG image. If no extension is included in the filename, |
|
14042 * the image will save in TIFF format and .tif will be added to the name. These files are saved to |
|
14043 * the sketch's folder, which may be opened by selecting "Show sketch folder" from the "Sketch" menu. |
|
14044 * It is not possible to use save() while running the program in a web browser. All images saved |
|
14045 * from the main drawing window will be opaque. To save images without a background, use createGraphics(). |
|
14046 * |
|
14047 * @param {String} filename any sequence of letters and numbers |
|
14048 * |
|
14049 * @see saveFrame |
|
14050 * @see createGraphics |
|
14051 */ |
|
14052 p.save = function(file, img) { |
|
14053 // file is unused at the moment |
|
14054 // may implement this differently in later release |
|
14055 if (img !== undef) { |
|
14056 return window.open(img.toDataURL(),"_blank"); |
|
14057 } |
|
14058 return window.open(p.externals.canvas.toDataURL(),"_blank"); |
|
14059 }; |
|
14060 |
|
14061 var saveNumber = 0; |
|
14062 |
|
14063 p.saveFrame = function(file) { |
|
14064 if(file === undef) { |
|
14065 // use default name template if parameter is not specified |
|
14066 file = "screen-####.png"; |
|
14067 } |
|
14068 // Increment changeable part: screen-0000.png, screen-0001.png, ... |
|
14069 var frameFilename = file.replace(/#+/, function(all) { |
|
14070 var s = "" + (saveNumber++); |
|
14071 while(s.length < all.length) { |
|
14072 s = "0" + s; |
|
14073 } |
|
14074 return s; |
|
14075 }); |
|
14076 p.save(frameFilename); |
|
14077 }; |
|
14078 |
|
14079 var utilityContext2d = document.createElement("canvas").getContext("2d"); |
|
14080 |
|
14081 var canvasDataCache = [undef, undef, undef]; // we need three for now |
|
14082 |
|
14083 function getCanvasData(obj, w, h) { |
|
14084 var canvasData = canvasDataCache.shift(); |
|
14085 |
|
14086 if (canvasData === undef) { |
|
14087 canvasData = {}; |
|
14088 canvasData.canvas = document.createElement("canvas"); |
|
14089 canvasData.context = canvasData.canvas.getContext('2d'); |
|
14090 } |
|
14091 |
|
14092 canvasDataCache.push(canvasData); |
|
14093 |
|
14094 var canvas = canvasData.canvas, context = canvasData.context, |
|
14095 width = w || obj.width, height = h || obj.height; |
|
14096 |
|
14097 canvas.width = width; |
|
14098 canvas.height = height; |
|
14099 |
|
14100 if (!obj) { |
|
14101 context.clearRect(0, 0, width, height); |
|
14102 } else if ("data" in obj) { // ImageData |
|
14103 context.putImageData(obj, 0, 0); |
|
14104 } else { |
|
14105 context.clearRect(0, 0, width, height); |
|
14106 context.drawImage(obj, 0, 0, width, height); |
|
14107 } |
|
14108 return canvasData; |
|
14109 } |
|
14110 |
|
14111 /** |
|
14112 * Handle the sketch code for pixels[] and pixels.length |
|
14113 * parser code converts pixels[] to getPixels() |
|
14114 * or setPixels(), .length becomes getLength() |
|
14115 */ |
|
14116 function buildPixelsObject(pImage) { |
|
14117 return { |
|
14118 |
|
14119 getLength: (function(aImg) { |
|
14120 return function() { |
|
14121 if (aImg.isRemote) { |
|
14122 throw "Image is loaded remotely. Cannot get length."; |
|
14123 } else { |
|
14124 return aImg.imageData.data.length ? aImg.imageData.data.length/4 : 0; |
|
14125 } |
|
14126 }; |
|
14127 }(pImage)), |
|
14128 |
|
14129 getPixel: (function(aImg) { |
|
14130 return function(i) { |
|
14131 var offset = i*4, |
|
14132 data = aImg.imageData.data; |
|
14133 |
|
14134 if (aImg.isRemote) { |
|
14135 throw "Image is loaded remotely. Cannot get pixels."; |
|
14136 } |
|
14137 |
|
14138 return (data[offset+3] << 24) & PConstants.ALPHA_MASK | |
|
14139 (data[offset] << 16) & PConstants.RED_MASK | |
|
14140 (data[offset+1] << 8) & PConstants.GREEN_MASK | |
|
14141 data[offset+2] & PConstants.BLUE_MASK; |
|
14142 }; |
|
14143 }(pImage)), |
|
14144 |
|
14145 setPixel: (function(aImg) { |
|
14146 return function(i, c) { |
|
14147 var offset = i*4, |
|
14148 data = aImg.imageData.data; |
|
14149 |
|
14150 if (aImg.isRemote) { |
|
14151 throw "Image is loaded remotely. Cannot set pixel."; |
|
14152 } |
|
14153 |
|
14154 data[offset+0] = (c & PConstants.RED_MASK) >>> 16; |
|
14155 data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8; |
|
14156 data[offset+2] = (c & PConstants.BLUE_MASK); |
|
14157 data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24; |
|
14158 aImg.__isDirty = true; |
|
14159 }; |
|
14160 }(pImage)), |
|
14161 |
|
14162 toArray: (function(aImg) { |
|
14163 return function() { |
|
14164 var arr = [], |
|
14165 data = aImg.imageData.data, |
|
14166 length = aImg.width * aImg.height; |
|
14167 |
|
14168 if (aImg.isRemote) { |
|
14169 throw "Image is loaded remotely. Cannot get pixels."; |
|
14170 } |
|
14171 |
|
14172 for (var i = 0, offset = 0; i < length; i++, offset += 4) { |
|
14173 arr.push( (data[offset+3] << 24) & PConstants.ALPHA_MASK | |
|
14174 (data[offset] << 16) & PConstants.RED_MASK | |
|
14175 (data[offset+1] << 8) & PConstants.GREEN_MASK | |
|
14176 data[offset+2] & PConstants.BLUE_MASK ); |
|
14177 } |
|
14178 return arr; |
|
14179 }; |
|
14180 }(pImage)), |
|
14181 |
|
14182 set: (function(aImg) { |
|
14183 return function(arr) { |
|
14184 var offset, |
|
14185 data, |
|
14186 c; |
|
14187 if (this.isRemote) { |
|
14188 throw "Image is loaded remotely. Cannot set pixels."; |
|
14189 } |
|
14190 |
|
14191 data = aImg.imageData.data; |
|
14192 for (var i = 0, aL = arr.length; i < aL; i++) { |
|
14193 c = arr[i]; |
|
14194 offset = i*4; |
|
14195 |
|
14196 data[offset+0] = (c & PConstants.RED_MASK) >>> 16; |
|
14197 data[offset+1] = (c & PConstants.GREEN_MASK) >>> 8; |
|
14198 data[offset+2] = (c & PConstants.BLUE_MASK); |
|
14199 data[offset+3] = (c & PConstants.ALPHA_MASK) >>> 24; |
|
14200 } |
|
14201 aImg.__isDirty = true; |
|
14202 }; |
|
14203 }(pImage)) |
|
14204 |
|
14205 }; |
|
14206 } |
|
14207 |
|
14208 /** |
|
14209 * Datatype for storing images. Processing can display .gif, .jpg, .tga, and .png images. Images may be |
|
14210 * displayed in 2D and 3D space. Before an image is used, it must be loaded with the loadImage() function. |
|
14211 * The PImage object contains fields for the width and height of the image, as well as an array called |
|
14212 * pixels[] which contains the values for every pixel in the image. A group of methods, described below, |
|
14213 * allow easy access to the image's pixels and alpha channel and simplify the process of compositing. |
|
14214 * Before using the pixels[] array, be sure to use the loadPixels() method on the image to make sure that the |
|
14215 * pixel data is properly loaded. To create a new image, use the createImage() function (do not use new PImage()). |
|
14216 * |
|
14217 * @param {int} width image width |
|
14218 * @param {int} height image height |
|
14219 * @param {MODE} format Either RGB, ARGB, ALPHA (grayscale alpha channel) |
|
14220 * |
|
14221 * @returns {PImage} |
|
14222 * |
|
14223 * @see loadImage |
|
14224 * @see imageMode |
|
14225 * @see createImage |
|
14226 */ |
|
14227 var PImage = function(aWidth, aHeight, aFormat) { |
|
14228 |
|
14229 // Keep track of whether or not the cached imageData has been touched. |
|
14230 this.__isDirty = false; |
|
14231 |
|
14232 if (aWidth instanceof HTMLImageElement) { |
|
14233 // convert an <img> to a PImage |
|
14234 this.fromHTMLImageData(aWidth); |
|
14235 } else if (aHeight || aFormat) { |
|
14236 this.width = aWidth || 1; |
|
14237 this.height = aHeight || 1; |
|
14238 |
|
14239 // Stuff a canvas into sourceImg so image() calls can use drawImage like an <img> |
|
14240 var canvas = this.sourceImg = document.createElement("canvas"); |
|
14241 canvas.width = this.width; |
|
14242 canvas.height = this.height; |
|
14243 |
|
14244 var imageData = this.imageData = canvas.getContext('2d').createImageData(this.width, this.height); |
|
14245 this.format = (aFormat === PConstants.ARGB || aFormat === PConstants.ALPHA) ? aFormat : PConstants.RGB; |
|
14246 if (this.format === PConstants.RGB) { |
|
14247 // Set the alpha channel of an RGB image to opaque. |
|
14248 for (var i = 3, data = this.imageData.data, len = data.length; i < len; i += 4) { |
|
14249 data[i] = 255; |
|
14250 } |
|
14251 } |
|
14252 |
|
14253 this.__isDirty = true; |
|
14254 this.updatePixels(); |
|
14255 } else { |
|
14256 this.width = 0; |
|
14257 this.height = 0; |
|
14258 this.imageData = utilityContext2d.createImageData(1, 1); |
|
14259 this.format = PConstants.ARGB; |
|
14260 } |
|
14261 |
|
14262 this.pixels = buildPixelsObject(this); |
|
14263 }; |
|
14264 PImage.prototype = { |
|
14265 |
|
14266 /** |
|
14267 * Temporary hack to deal with cross-Processing-instance created PImage. See |
|
14268 * tickets #1623 and #1644. |
|
14269 */ |
|
14270 __isPImage: true, |
|
14271 |
|
14272 /** |
|
14273 * @member PImage |
|
14274 * Updates the image with the data in its pixels[] array. Use in conjunction with loadPixels(). If |
|
14275 * you're only reading pixels from the array, there's no need to call updatePixels(). |
|
14276 * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule |
|
14277 * is that any time you want to manipulate the pixels[] array, you must first call loadPixels(), and |
|
14278 * after changes have been made, call updatePixels(). Even if the renderer may not seem to use this |
|
14279 * function in the current Processing release, this will always be subject to change. |
|
14280 * Currently, none of the renderers use the additional parameters to updatePixels(). |
|
14281 */ |
|
14282 updatePixels: function() { |
|
14283 var canvas = this.sourceImg; |
|
14284 if (canvas && canvas instanceof HTMLCanvasElement && this.__isDirty) { |
|
14285 canvas.getContext('2d').putImageData(this.imageData, 0, 0); |
|
14286 } |
|
14287 this.__isDirty = false; |
|
14288 }, |
|
14289 |
|
14290 fromHTMLImageData: function(htmlImg) { |
|
14291 // convert an <img> to a PImage |
|
14292 var canvasData = getCanvasData(htmlImg); |
|
14293 try { |
|
14294 var imageData = canvasData.context.getImageData(0, 0, htmlImg.width, htmlImg.height); |
|
14295 this.fromImageData(imageData); |
|
14296 } catch(e) { |
|
14297 if (htmlImg.width && htmlImg.height) { |
|
14298 this.isRemote = true; |
|
14299 this.width = htmlImg.width; |
|
14300 this.height = htmlImg.height; |
|
14301 } |
|
14302 } |
|
14303 this.sourceImg = htmlImg; |
|
14304 }, |
|
14305 |
|
14306 'get': function(x, y, w, h) { |
|
14307 if (!arguments.length) { |
|
14308 return p.get(this); |
|
14309 } |
|
14310 if (arguments.length === 2) { |
|
14311 return p.get(x, y, this); |
|
14312 } |
|
14313 if (arguments.length === 4) { |
|
14314 return p.get(x, y, w, h, this); |
|
14315 } |
|
14316 }, |
|
14317 |
|
14318 /** |
|
14319 * @member PImage |
|
14320 * Changes the color of any pixel or writes an image directly into the image. The x and y parameter |
|
14321 * specify the pixel or the upper-left corner of the image. The color parameter specifies the color value. |
|
14322 * Setting the color of a single pixel with set(x, y) is easy, but not as fast as putting the data |
|
14323 * directly into pixels[]. The equivalent statement to "set(x, y, #000000)" using pixels[] is |
|
14324 * "pixels[y*width+x] = #000000". Processing requires calling loadPixels() to load the display window |
|
14325 * data into the pixels[] array before getting the values and calling updatePixels() to update the window. |
|
14326 * |
|
14327 * @param {int} x x-coordinate of the pixel or upper-left corner of the image |
|
14328 * @param {int} y y-coordinate of the pixel or upper-left corner of the image |
|
14329 * @param {color} color any value of the color datatype |
|
14330 * |
|
14331 * @see get |
|
14332 * @see pixels[] |
|
14333 * @see copy |
|
14334 */ |
|
14335 'set': function(x, y, c) { |
|
14336 p.set(x, y, c, this); |
|
14337 this.__isDirty = true; |
|
14338 }, |
|
14339 |
|
14340 /** |
|
14341 * @member PImage |
|
14342 * Blends a region of pixels into the image specified by the img parameter. These copies utilize full |
|
14343 * alpha channel support and a choice of the following modes to blend the colors of source pixels (A) |
|
14344 * with the ones of pixels in the destination image (B): |
|
14345 * BLEND - linear interpolation of colours: C = A*factor + B |
|
14346 * ADD - additive blending with white clip: C = min(A*factor + B, 255) |
|
14347 * SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0) |
|
14348 * DARKEST - only the darkest colour succeeds: C = min(A*factor, B) |
|
14349 * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B) |
|
14350 * DIFFERENCE - subtract colors from underlying image. |
|
14351 * EXCLUSION - similar to DIFFERENCE, but less extreme. |
|
14352 * MULTIPLY - Multiply the colors, result will always be darker. |
|
14353 * SCREEN - Opposite multiply, uses inverse values of the colors. |
|
14354 * OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values. |
|
14355 * HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. |
|
14356 * SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh. |
|
14357 * DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop. |
|
14358 * BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop. |
|
14359 * All modes use the alpha information (highest byte) of source image pixels as the blending factor. |
|
14360 * If the source and destination regions are different sizes, the image will be automatically resized to |
|
14361 * match the destination size. If the srcImg parameter is not used, the display window is used as the source image. |
|
14362 * This function ignores imageMode(). |
|
14363 * |
|
14364 * @param {int} x X coordinate of the source's upper left corner |
|
14365 * @param {int} y Y coordinate of the source's upper left corner |
|
14366 * @param {int} width source image width |
|
14367 * @param {int} height source image height |
|
14368 * @param {int} dx X coordinate of the destinations's upper left corner |
|
14369 * @param {int} dy Y coordinate of the destinations's upper left corner |
|
14370 * @param {int} dwidth destination image width |
|
14371 * @param {int} dheight destination image height |
|
14372 * @param {PImage} srcImg an image variable referring to the source image |
|
14373 * @param {MODE} MODE Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, |
|
14374 * MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN |
|
14375 * |
|
14376 * @see alpha |
|
14377 * @see copy |
|
14378 */ |
|
14379 blend: function(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE) { |
|
14380 if (arguments.length === 9) { |
|
14381 p.blend(this, srcImg, x, y, width, height, dx, dy, dwidth, dheight, this); |
|
14382 } else if (arguments.length === 10) { |
|
14383 p.blend(srcImg, x, y, width, height, dx, dy, dwidth, dheight, MODE, this); |
|
14384 } |
|
14385 delete this.sourceImg; |
|
14386 }, |
|
14387 |
|
14388 /** |
|
14389 * @member PImage |
|
14390 * Copies a region of pixels from one image into another. If the source and destination regions |
|
14391 * aren't the same size, it will automatically resize source pixels to fit the specified target region. |
|
14392 * No alpha information is used in the process, however if the source image has an alpha channel set, |
|
14393 * it will be copied as well. This function ignores imageMode(). |
|
14394 * |
|
14395 * @param {int} sx X coordinate of the source's upper left corner |
|
14396 * @param {int} sy Y coordinate of the source's upper left corner |
|
14397 * @param {int} swidth source image width |
|
14398 * @param {int} sheight source image height |
|
14399 * @param {int} dx X coordinate of the destinations's upper left corner |
|
14400 * @param {int} dy Y coordinate of the destinations's upper left corner |
|
14401 * @param {int} dwidth destination image width |
|
14402 * @param {int} dheight destination image height |
|
14403 * @param {PImage} srcImg an image variable referring to the source image |
|
14404 * |
|
14405 * @see alpha |
|
14406 * @see blend |
|
14407 */ |
|
14408 copy: function(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight) { |
|
14409 if (arguments.length === 8) { |
|
14410 p.blend(this, srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, PConstants.REPLACE, this); |
|
14411 } else if (arguments.length === 9) { |
|
14412 p.blend(srcImg, sx, sy, swidth, sheight, dx, dy, dwidth, dheight, PConstants.REPLACE, this); |
|
14413 } |
|
14414 delete this.sourceImg; |
|
14415 }, |
|
14416 |
|
14417 /** |
|
14418 * @member PImage |
|
14419 * Filters an image as defined by one of the following modes: |
|
14420 * THRESHOLD - converts the image to black and white pixels depending if they are above or below |
|
14421 * the threshold defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). |
|
14422 * If no level is specified, 0.5 is used. |
|
14423 * GRAY - converts any colors in the image to grayscale equivalents |
|
14424 * INVERT - sets each pixel to its inverse value |
|
14425 * POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter |
|
14426 * BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. |
|
14427 * If no level parameter is used, the blur is equivalent to Guassian blur of radius 1. |
|
14428 * OPAQUE - sets the alpha channel to entirely opaque. |
|
14429 * ERODE - reduces the light areas with the amount defined by the level parameter. |
|
14430 * DILATE - increases the light areas with the amount defined by the level parameter |
|
14431 * |
|
14432 * @param {MODE} MODE Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE |
|
14433 * @param {int|float} param in the range from 0 to 1 |
|
14434 */ |
|
14435 filter: function(mode, param) { |
|
14436 if (arguments.length === 2) { |
|
14437 p.filter(mode, param, this); |
|
14438 } else if (arguments.length === 1) { |
|
14439 // no param specified, send null to show its invalid |
|
14440 p.filter(mode, null, this); |
|
14441 } |
|
14442 delete this.sourceImg; |
|
14443 }, |
|
14444 |
|
14445 /** |
|
14446 * @member PImage |
|
14447 * Saves the image into a file. Images are saved in TIFF, TARGA, JPEG, and PNG format depending on |
|
14448 * the extension within the filename parameter. For example, "image.tif" will have a TIFF image and |
|
14449 * "image.png" will save a PNG image. If no extension is included in the filename, the image will save |
|
14450 * in TIFF format and .tif will be added to the name. These files are saved to the sketch's folder, |
|
14451 * which may be opened by selecting "Show sketch folder" from the "Sketch" menu. It is not possible to |
|
14452 * use save() while running the program in a web browser. |
|
14453 * To save an image created within the code, rather than through loading, it's necessary to make the |
|
14454 * image with the createImage() function so it is aware of the location of the program and can therefore |
|
14455 * save the file to the right place. See the createImage() reference for more information. |
|
14456 * |
|
14457 * @param {String} filename a sequence of letters and numbers |
|
14458 */ |
|
14459 save: function(file){ |
|
14460 p.save(file,this); |
|
14461 }, |
|
14462 |
|
14463 /** |
|
14464 * @member PImage |
|
14465 * Resize the image to a new width and height. To make the image scale proportionally, use 0 as the |
|
14466 * value for the wide or high parameter. |
|
14467 * |
|
14468 * @param {int} wide the resized image width |
|
14469 * @param {int} high the resized image height |
|
14470 * |
|
14471 * @see get |
|
14472 */ |
|
14473 resize: function(w, h) { |
|
14474 if (this.isRemote) { // Remote images cannot access imageData |
|
14475 throw "Image is loaded remotely. Cannot resize."; |
|
14476 } |
|
14477 if (this.width !== 0 || this.height !== 0) { |
|
14478 // make aspect ratio if w or h is 0 |
|
14479 if (w === 0 && h !== 0) { |
|
14480 w = Math.floor(this.width / this.height * h); |
|
14481 } else if (h === 0 && w !== 0) { |
|
14482 h = Math.floor(this.height / this.width * w); |
|
14483 } |
|
14484 // put 'this.imageData' into a new canvas |
|
14485 var canvas = getCanvasData(this.imageData).canvas; |
|
14486 // pull imageData object out of canvas into ImageData object |
|
14487 var imageData = getCanvasData(canvas, w, h).context.getImageData(0, 0, w, h); |
|
14488 // set this as new pimage |
|
14489 this.fromImageData(imageData); |
|
14490 } |
|
14491 }, |
|
14492 |
|
14493 /** |
|
14494 * @member PImage |
|
14495 * Masks part of an image from displaying by loading another image and using it as an alpha channel. |
|
14496 * This mask image should only contain grayscale data, but only the blue color channel is used. The |
|
14497 * mask image needs to be the same size as the image to which it is applied. |
|
14498 * In addition to using a mask image, an integer array containing the alpha channel data can be |
|
14499 * specified directly. This method is useful for creating dynamically generated alpha masks. This |
|
14500 * array must be of the same length as the target image's pixels array and should contain only grayscale |
|
14501 * data of values between 0-255. |
|
14502 * |
|
14503 * @param {PImage} maskImg any PImage object used as the alpha channel for "img", needs to be same |
|
14504 * size as "img" |
|
14505 * @param {int[]} maskArray any array of Integer numbers used as the alpha channel, needs to be same |
|
14506 * length as the image's pixel array |
|
14507 */ |
|
14508 mask: function(mask) { |
|
14509 var obj = this.toImageData(), |
|
14510 i, |
|
14511 size; |
|
14512 |
|
14513 if (mask instanceof PImage || mask.__isPImage) { |
|
14514 if (mask.width === this.width && mask.height === this.height) { |
|
14515 mask = mask.toImageData(); |
|
14516 |
|
14517 for (i = 2, size = this.width * this.height * 4; i < size; i += 4) { |
|
14518 // using it as an alpha channel |
|
14519 obj.data[i + 1] = mask.data[i]; |
|
14520 // but only the blue color channel |
|
14521 } |
|
14522 } else { |
|
14523 throw "mask must have the same dimensions as PImage."; |
|
14524 } |
|
14525 } else if (mask instanceof Array) { |
|
14526 if (this.width * this.height === mask.length) { |
|
14527 for (i = 0, size = mask.length; i < size; ++i) { |
|
14528 obj.data[i * 4 + 3] = mask[i]; |
|
14529 } |
|
14530 } else { |
|
14531 throw "mask array must be the same length as PImage pixels array."; |
|
14532 } |
|
14533 } |
|
14534 |
|
14535 this.fromImageData(obj); |
|
14536 }, |
|
14537 |
|
14538 // These are intentionally left blank for PImages, we work live with pixels and draw as necessary |
|
14539 /** |
|
14540 * @member PImage |
|
14541 * Loads the pixel data for the image into its pixels[] array. This function must always be called |
|
14542 * before reading from or writing to pixels[]. |
|
14543 * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the |
|
14544 * rule is that any time you want to manipulate the pixels[] array, you must first call loadPixels(), |
|
14545 * and after changes have been made, call updatePixels(). Even if the renderer may not seem to use |
|
14546 * this function in the current Processing release, this will always be subject to change. |
|
14547 */ |
|
14548 loadPixels: nop, |
|
14549 |
|
14550 toImageData: function() { |
|
14551 if (this.isRemote) { |
|
14552 return this.sourceImg; |
|
14553 } |
|
14554 |
|
14555 if (!this.__isDirty) { |
|
14556 return this.imageData; |
|
14557 } |
|
14558 |
|
14559 var canvasData = getCanvasData(this.imageData); |
|
14560 return canvasData.context.getImageData(0, 0, this.width, this.height); |
|
14561 }, |
|
14562 |
|
14563 toDataURL: function() { |
|
14564 if (this.isRemote) { // Remote images cannot access imageData |
|
14565 throw "Image is loaded remotely. Cannot create dataURI."; |
|
14566 } |
|
14567 var canvasData = getCanvasData(this.imageData); |
|
14568 return canvasData.canvas.toDataURL(); |
|
14569 }, |
|
14570 |
|
14571 fromImageData: function(canvasImg) { |
|
14572 var w = canvasImg.width, |
|
14573 h = canvasImg.height, |
|
14574 canvas = document.createElement('canvas'), |
|
14575 ctx = canvas.getContext('2d'); |
|
14576 |
|
14577 this.width = canvas.width = w; |
|
14578 this.height = canvas.height = h; |
|
14579 |
|
14580 ctx.putImageData(canvasImg, 0, 0); |
|
14581 |
|
14582 // changed for 0.9 |
|
14583 this.format = PConstants.ARGB; |
|
14584 |
|
14585 this.imageData = canvasImg; |
|
14586 this.sourceImg = canvas; |
|
14587 } |
|
14588 }; |
|
14589 |
|
14590 p.PImage = PImage; |
|
14591 |
|
14592 /** |
|
14593 * Creates a new PImage (the datatype for storing images). This provides a fresh buffer of pixels to play |
|
14594 * with. Set the size of the buffer with the width and height parameters. The format parameter defines how |
|
14595 * the pixels are stored. See the PImage reference for more information. |
|
14596 * Be sure to include all three parameters, specifying only the width and height (but no format) will |
|
14597 * produce a strange error. |
|
14598 * Advanced users please note that createImage() should be used instead of the syntax new PImage(). |
|
14599 * |
|
14600 * @param {int} width image width |
|
14601 * @param {int} height image height |
|
14602 * @param {MODE} format Either RGB, ARGB, ALPHA (grayscale alpha channel) |
|
14603 * |
|
14604 * @returns {PImage} |
|
14605 * |
|
14606 * @see PImage |
|
14607 * @see PGraphics |
|
14608 */ |
|
14609 p.createImage = function(w, h, mode) { |
|
14610 return new PImage(w,h,mode); |
|
14611 }; |
|
14612 |
|
14613 // Loads an image for display. Type is an extension. Callback is fired on load. |
|
14614 /** |
|
14615 * Loads an image into a variable of type PImage. Four types of images ( .gif, .jpg, .tga, .png) images may |
|
14616 * be loaded. To load correctly, images must be located in the data directory of the current sketch. In most |
|
14617 * cases, load all images in setup() to preload them at the start of the program. Loading images inside draw() |
|
14618 * will reduce the speed of a program. |
|
14619 * The filename parameter can also be a URL to a file found online. For security reasons, a Processing sketch |
|
14620 * found online can only download files from the same server from which it came. Getting around this restriction |
|
14621 * requires a signed applet. |
|
14622 * The extension parameter is used to determine the image type in cases where the image filename does not end |
|
14623 * with a proper extension. Specify the extension as the second parameter to loadImage(), as shown in the |
|
14624 * third example on this page. |
|
14625 * If an image is not loaded successfully, the null value is returned and an error message will be printed to |
|
14626 * the console. The error message does not halt the program, however the null value may cause a NullPointerException |
|
14627 * if your code does not check whether the value returned from loadImage() is null. |
|
14628 * Depending on the type of error, a PImage object may still be returned, but the width and height of the image |
|
14629 * will be set to -1. This happens if bad image data is returned or cannot be decoded properly. Sometimes this happens |
|
14630 * with image URLs that produce a 403 error or that redirect to a password prompt, because loadImage() will attempt |
|
14631 * to interpret the HTML as image data. |
|
14632 * |
|
14633 * @param {String} filename name of file to load, can be .gif, .jpg, .tga, or a handful of other image |
|
14634 * types depending on your platform. |
|
14635 * @param {String} extension the type of image to load, for example "png", "gif", "jpg" |
|
14636 * |
|
14637 * @returns {PImage} |
|
14638 * |
|
14639 * @see PImage |
|
14640 * @see image |
|
14641 * @see imageMode |
|
14642 * @see background |
|
14643 */ |
|
14644 p.loadImage = function(file, type, callback) { |
|
14645 // if type is specified add it with a . to file to make the filename |
|
14646 if (type) { |
|
14647 file = file + "." + type; |
|
14648 } |
|
14649 var pimg; |
|
14650 // if image is in the preloader cache return a new PImage |
|
14651 if (curSketch.imageCache.images[file]) { |
|
14652 pimg = new PImage(curSketch.imageCache.images[file]); |
|
14653 pimg.loaded = true; |
|
14654 return pimg; |
|
14655 } |
|
14656 // else async load it |
|
14657 pimg = new PImage(); |
|
14658 var img = document.createElement('img'); |
|
14659 |
|
14660 pimg.sourceImg = img; |
|
14661 |
|
14662 img.onload = (function(aImage, aPImage, aCallback) { |
|
14663 var image = aImage; |
|
14664 var pimg = aPImage; |
|
14665 var callback = aCallback; |
|
14666 return function() { |
|
14667 // change the <img> object into a PImage now that its loaded |
|
14668 pimg.fromHTMLImageData(image); |
|
14669 pimg.loaded = true; |
|
14670 if (callback) { |
|
14671 callback(); |
|
14672 } |
|
14673 }; |
|
14674 }(img, pimg, callback)); |
|
14675 |
|
14676 img.src = file; // needs to be called after the img.onload function is declared or it wont work in opera |
|
14677 return pimg; |
|
14678 }; |
|
14679 |
|
14680 // async loading of large images, same functionality as loadImage above |
|
14681 /** |
|
14682 * This function load images on a separate thread so that your sketch does not freeze while images load during |
|
14683 * setup(). While the image is loading, its width and height will be 0. If an error occurs while loading the image, |
|
14684 * its width and height will be set to -1. You'll know when the image has loaded properly because its width and |
|
14685 * height will be greater than 0. Asynchronous image loading (particularly when downloading from a server) can |
|
14686 * dramatically improve performance. |
|
14687 * The extension parameter is used to determine the image type in cases where the image filename does not end |
|
14688 * with a proper extension. Specify the extension as the second parameter to requestImage(). |
|
14689 * |
|
14690 * @param {String} filename name of file to load, can be .gif, .jpg, .tga, or a handful of other image |
|
14691 * types depending on your platform. |
|
14692 * @param {String} extension the type of image to load, for example "png", "gif", "jpg" |
|
14693 * |
|
14694 * @returns {PImage} |
|
14695 * |
|
14696 * @see PImage |
|
14697 * @see loadImage |
|
14698 */ |
|
14699 p.requestImage = p.loadImage; |
|
14700 |
|
14701 function get$2(x,y) { |
|
14702 var data; |
|
14703 // return the color at x,y (int) of curContext |
|
14704 if (x >= p.width || x < 0 || y < 0 || y >= p.height) { |
|
14705 // x,y is outside image return transparent black |
|
14706 return 0; |
|
14707 } |
|
14708 |
|
14709 // loadPixels() has been called |
|
14710 if (isContextReplaced) { |
|
14711 var offset = ((0|x) + p.width * (0|y)) * 4; |
|
14712 data = p.imageData.data; |
|
14713 return (data[offset + 3] << 24) & PConstants.ALPHA_MASK | |
|
14714 (data[offset] << 16) & PConstants.RED_MASK | |
|
14715 (data[offset + 1] << 8) & PConstants.GREEN_MASK | |
|
14716 data[offset + 2] & PConstants.BLUE_MASK; |
|
14717 } |
|
14718 |
|
14719 // x,y is inside canvas space |
|
14720 data = p.toImageData(0|x, 0|y, 1, 1).data; |
|
14721 return (data[3] << 24) & PConstants.ALPHA_MASK | |
|
14722 (data[0] << 16) & PConstants.RED_MASK | |
|
14723 (data[1] << 8) & PConstants.GREEN_MASK | |
|
14724 data[2] & PConstants.BLUE_MASK; |
|
14725 } |
|
14726 function get$3(x,y,img) { |
|
14727 if (img.isRemote) { // Remote images cannot access imageData |
|
14728 throw "Image is loaded remotely. Cannot get x,y."; |
|
14729 } |
|
14730 // PImage.get(x,y) was called, return the color (int) at x,y of img |
|
14731 var offset = y * img.width * 4 + (x * 4), |
|
14732 data = img.imageData.data; |
|
14733 return (data[offset + 3] << 24) & PConstants.ALPHA_MASK | |
|
14734 (data[offset] << 16) & PConstants.RED_MASK | |
|
14735 (data[offset + 1] << 8) & PConstants.GREEN_MASK | |
|
14736 data[offset + 2] & PConstants.BLUE_MASK; |
|
14737 } |
|
14738 function get$4(x, y, w, h) { |
|
14739 // return a PImage of w and h from cood x,y of curContext |
|
14740 var c = new PImage(w, h, PConstants.ARGB); |
|
14741 c.fromImageData(p.toImageData(x, y, w, h)); |
|
14742 return c; |
|
14743 } |
|
14744 function get$5(x, y, w, h, img) { |
|
14745 if (img.isRemote) { // Remote images cannot access imageData |
|
14746 throw "Image is loaded remotely. Cannot get x,y,w,h."; |
|
14747 } |
|
14748 // PImage.get(x,y,w,h) was called, return x,y,w,h PImage of img |
|
14749 // offset start point needs to be *4 |
|
14750 var c = new PImage(w, h, PConstants.ARGB), cData = c.imageData.data, |
|
14751 imgWidth = img.width, imgHeight = img.height, imgData = img.imageData.data; |
|
14752 // Don't need to copy pixels from the image outside ranges. |
|
14753 var startRow = Math.max(0, -y), startColumn = Math.max(0, -x), |
|
14754 stopRow = Math.min(h, imgHeight - y), stopColumn = Math.min(w, imgWidth - x); |
|
14755 for (var i = startRow; i < stopRow; ++i) { |
|
14756 var sourceOffset = ((y + i) * imgWidth + (x + startColumn)) * 4; |
|
14757 var targetOffset = (i * w + startColumn) * 4; |
|
14758 for (var j = startColumn; j < stopColumn; ++j) { |
|
14759 cData[targetOffset++] = imgData[sourceOffset++]; |
|
14760 cData[targetOffset++] = imgData[sourceOffset++]; |
|
14761 cData[targetOffset++] = imgData[sourceOffset++]; |
|
14762 cData[targetOffset++] = imgData[sourceOffset++]; |
|
14763 } |
|
14764 } |
|
14765 c.__isDirty = true; |
|
14766 return c; |
|
14767 } |
|
14768 |
|
14769 // Gets a single pixel or block of pixels from the current Canvas Context or a PImage |
|
14770 /** |
|
14771 * Reads the color of any pixel or grabs a section of an image. If no parameters are specified, the entire |
|
14772 * image is returned. Get the value of one pixel by specifying an x,y coordinate. Get a section of the display |
|
14773 * window by specifying an additional width and height parameter. If the pixel requested is outside of the image |
|
14774 * window, black is returned. The numbers returned are scaled according to the current color ranges, but only RGB |
|
14775 * values are returned by this function. For example, even though you may have drawn a shape with colorMode(HSB), |
|
14776 * the numbers returned will be in RGB. |
|
14777 * Getting the color of a single pixel with get(x, y) is easy, but not as fast as grabbing the data directly |
|
14778 * from pixels[]. The equivalent statement to "get(x, y)" using pixels[] is "pixels[y*width+x]". Processing |
|
14779 * requires calling loadPixels() to load the display window data into the pixels[] array before getting the values. |
|
14780 * This function ignores imageMode(). |
|
14781 * |
|
14782 * @param {int} x x-coordinate of the pixel |
|
14783 * @param {int} y y-coordinate of the pixel |
|
14784 * @param {int} width width of pixel rectangle to get |
|
14785 * @param {int} height height of pixel rectangle to get |
|
14786 * |
|
14787 * @returns {Color|PImage} |
|
14788 * |
|
14789 * @see set |
|
14790 * @see pixels[] |
|
14791 * @see imageMode |
|
14792 */ |
|
14793 p.get = function(x, y, w, h, img) { |
|
14794 // for 0 2 and 4 arguments use curContext, otherwise PImage.get was called |
|
14795 if (img !== undefined) { |
|
14796 return get$5(x, y, w, h, img); |
|
14797 } |
|
14798 if (h !== undefined) { |
|
14799 return get$4(x, y, w, h); |
|
14800 } |
|
14801 if (w !== undefined) { |
|
14802 return get$3(x, y, w); |
|
14803 } |
|
14804 if (y !== undefined) { |
|
14805 return get$2(x, y); |
|
14806 } |
|
14807 if (x !== undefined) { |
|
14808 // PImage.get() was called, return a new PImage |
|
14809 return get$5(0, 0, x.width, x.height, x); |
|
14810 } |
|
14811 |
|
14812 return get$4(0, 0, p.width, p.height); |
|
14813 }; |
|
14814 |
|
14815 /** |
|
14816 * Creates and returns a new <b>PGraphics</b> object of the types P2D, P3D, and JAVA2D. Use this class if you need to draw |
|
14817 * into an off-screen graphics buffer. It's not possible to use <b>createGraphics()</b> with OPENGL, because it doesn't |
|
14818 * allow offscreen use. The DXF and PDF renderers require the filename parameter. <br /><br /> It's important to call |
|
14819 * any drawing commands between beginDraw() and endDraw() statements. This is also true for any commands that affect |
|
14820 * drawing, such as smooth() or colorMode().<br /><br /> Unlike the main drawing surface which is completely opaque, |
|
14821 * surfaces created with createGraphics() can have transparency. This makes it possible to draw into a graphics and |
|
14822 * maintain the alpha channel. |
|
14823 * |
|
14824 * @param {int} width width in pixels |
|
14825 * @param {int} height height in pixels |
|
14826 * @param {int} renderer Either P2D, P3D, JAVA2D, PDF, DXF |
|
14827 * @param {String} filename the name of the file (not supported yet) |
|
14828 */ |
|
14829 p.createGraphics = function(w, h, render) { |
|
14830 var pg = new Processing(); |
|
14831 pg.size(w, h, render); |
|
14832 return pg; |
|
14833 }; |
|
14834 |
|
14835 // pixels caching |
|
14836 function resetContext() { |
|
14837 if(isContextReplaced) { |
|
14838 curContext = originalContext; |
|
14839 isContextReplaced = false; |
|
14840 |
|
14841 p.updatePixels(); |
|
14842 } |
|
14843 } |
|
14844 function SetPixelContextWrapper() { |
|
14845 function wrapFunction(newContext, name) { |
|
14846 function wrapper() { |
|
14847 resetContext(); |
|
14848 curContext[name].apply(curContext, arguments); |
|
14849 } |
|
14850 newContext[name] = wrapper; |
|
14851 } |
|
14852 function wrapProperty(newContext, name) { |
|
14853 function getter() { |
|
14854 resetContext(); |
|
14855 return curContext[name]; |
|
14856 } |
|
14857 function setter(value) { |
|
14858 resetContext(); |
|
14859 curContext[name] = value; |
|
14860 } |
|
14861 p.defineProperty(newContext, name, { get: getter, set: setter }); |
|
14862 } |
|
14863 for(var n in curContext) { |
|
14864 if(typeof curContext[n] === 'function') { |
|
14865 wrapFunction(this, n); |
|
14866 } else { |
|
14867 wrapProperty(this, n); |
|
14868 } |
|
14869 } |
|
14870 } |
|
14871 function replaceContext() { |
|
14872 if(isContextReplaced) { |
|
14873 return; |
|
14874 } |
|
14875 p.loadPixels(); |
|
14876 if(proxyContext === null) { |
|
14877 originalContext = curContext; |
|
14878 proxyContext = new SetPixelContextWrapper(); |
|
14879 } |
|
14880 isContextReplaced = true; |
|
14881 curContext = proxyContext; |
|
14882 setPixelsCached = 0; |
|
14883 } |
|
14884 |
|
14885 function set$3(x, y, c) { |
|
14886 if (x < p.width && x >= 0 && y >= 0 && y < p.height) { |
|
14887 replaceContext(); |
|
14888 p.pixels.setPixel((0|x)+p.width*(0|y), c); |
|
14889 if(++setPixelsCached > maxPixelsCached) { |
|
14890 resetContext(); |
|
14891 } |
|
14892 } |
|
14893 } |
|
14894 function set$4(x, y, obj, img) { |
|
14895 if (img.isRemote) { // Remote images cannot access imageData |
|
14896 throw "Image is loaded remotely. Cannot set x,y."; |
|
14897 } |
|
14898 var c = p.color.toArray(obj); |
|
14899 var offset = y * img.width * 4 + (x*4); |
|
14900 var data = img.imageData.data; |
|
14901 data[offset] = c[0]; |
|
14902 data[offset+1] = c[1]; |
|
14903 data[offset+2] = c[2]; |
|
14904 data[offset+3] = c[3]; |
|
14905 } |
|
14906 |
|
14907 // Paints a pixel array into the canvas |
|
14908 /** |
|
14909 * Changes the color of any pixel or writes an image directly into the display window. The x and y parameters |
|
14910 * specify the pixel to change and the color parameter specifies the color value. The color parameter is affected |
|
14911 * by the current color mode (the default is RGB values from 0 to 255). When setting an image, the x and y |
|
14912 * parameters define the coordinates for the upper-left corner of the image. |
|
14913 * Setting the color of a single pixel with set(x, y) is easy, but not as fast as putting the data directly |
|
14914 * into pixels[]. The equivalent statement to "set(x, y, #000000)" using pixels[] is "pixels[y*width+x] = #000000". |
|
14915 * You must call loadPixels() to load the display window data into the pixels[] array before setting the values |
|
14916 * and calling updatePixels() to update the window with any changes. This function ignores imageMode(). |
|
14917 * |
|
14918 * @param {int} x x-coordinate of the pixel |
|
14919 * @param {int} y y-coordinate of the pixel |
|
14920 * @param {Color} obj any value of the color datatype |
|
14921 * @param {PImage} img any valid variable of type PImage |
|
14922 * |
|
14923 * @see get |
|
14924 * @see pixels[] |
|
14925 * @see imageMode |
|
14926 */ |
|
14927 p.set = function(x, y, obj, img) { |
|
14928 var color, oldFill; |
|
14929 if (arguments.length === 3) { |
|
14930 // called p.set(), was it with a color or a img ? |
|
14931 if (typeof obj === "number") { |
|
14932 set$3(x, y, obj); |
|
14933 } else if (obj instanceof PImage || obj.__isPImage) { |
|
14934 p.image(obj, x, y); |
|
14935 } |
|
14936 } else if (arguments.length === 4) { |
|
14937 // PImage.set(x,y,c) was called, set coordinate x,y color to c of img |
|
14938 set$4(x, y, obj, img); |
|
14939 } |
|
14940 }; |
|
14941 p.imageData = {}; |
|
14942 |
|
14943 // handle the sketch code for pixels[] |
|
14944 // parser code converts pixels[] to getPixels() or setPixels(), |
|
14945 // .length becomes getLength() |
|
14946 /** |
|
14947 * Array containing the values for all the pixels in the display window. These values are of the color datatype. |
|
14948 * This array is the size of the display window. For example, if the image is 100x100 pixels, there will be 10000 |
|
14949 * values and if the window is 200x300 pixels, there will be 60000 values. The index value defines the position |
|
14950 * of a value within the array. For example, the statment color b = pixels[230] will set the variable b to be |
|
14951 * equal to the value at that location in the array. |
|
14952 * Before accessing this array, the data must loaded with the loadPixels() function. After the array data has |
|
14953 * been modified, the updatePixels() function must be run to update the changes. |
|
14954 * |
|
14955 * @param {int} index must not exceed the size of the array |
|
14956 * |
|
14957 * @see loadPixels |
|
14958 * @see updatePixels |
|
14959 * @see get |
|
14960 * @see set |
|
14961 * @see PImage |
|
14962 */ |
|
14963 p.pixels = { |
|
14964 getLength: function() { return p.imageData.data.length ? p.imageData.data.length/4 : 0; }, |
|
14965 getPixel: function(i) { |
|
14966 var offset = i*4, data = p.imageData.data; |
|
14967 return (data[offset+3] << 24) & 0xff000000 | |
|
14968 (data[offset+0] << 16) & 0x00ff0000 | |
|
14969 (data[offset+1] << 8) & 0x0000ff00 | |
|
14970 data[offset+2] & 0x000000ff; |
|
14971 }, |
|
14972 setPixel: function(i,c) { |
|
14973 var offset = i*4, data = p.imageData.data; |
|
14974 data[offset+0] = (c & 0x00ff0000) >>> 16; // RED_MASK |
|
14975 data[offset+1] = (c & 0x0000ff00) >>> 8; // GREEN_MASK |
|
14976 data[offset+2] = (c & 0x000000ff); // BLUE_MASK |
|
14977 data[offset+3] = (c & 0xff000000) >>> 24; // ALPHA_MASK |
|
14978 }, |
|
14979 toArray: function() { |
|
14980 var arr = [], length = p.imageData.width * p.imageData.height, data = p.imageData.data; |
|
14981 for (var i = 0, offset = 0; i < length; i++, offset += 4) { |
|
14982 arr.push((data[offset+3] << 24) & 0xff000000 | |
|
14983 (data[offset+0] << 16) & 0x00ff0000 | |
|
14984 (data[offset+1] << 8) & 0x0000ff00 | |
|
14985 data[offset+2] & 0x000000ff); |
|
14986 } |
|
14987 return arr; |
|
14988 }, |
|
14989 set: function(arr) { |
|
14990 for (var i = 0, aL = arr.length; i < aL; i++) { |
|
14991 this.setPixel(i, arr[i]); |
|
14992 } |
|
14993 } |
|
14994 }; |
|
14995 |
|
14996 // Gets a 1-Dimensional pixel array from Canvas |
|
14997 /** |
|
14998 * Loads the pixel data for the display window into the pixels[] array. This function must always be called |
|
14999 * before reading from or writing to pixels[]. |
|
15000 * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule is that |
|
15001 * any time you want to manipulate the pixels[] array, you must first call loadPixels(), and after changes |
|
15002 * have been made, call updatePixels(). Even if the renderer may not seem to use this function in the current |
|
15003 * Processing release, this will always be subject to change. |
|
15004 * |
|
15005 * @see pixels[] |
|
15006 * @see updatePixels |
|
15007 */ |
|
15008 p.loadPixels = function() { |
|
15009 p.imageData = drawing.$ensureContext().getImageData(0, 0, p.width, p.height); |
|
15010 }; |
|
15011 |
|
15012 // Draws a 1-Dimensional pixel array to Canvas |
|
15013 /** |
|
15014 * Updates the display window with the data in the pixels[] array. Use in conjunction with loadPixels(). If |
|
15015 * you're only reading pixels from the array, there's no need to call updatePixels() unless there are changes. |
|
15016 * Certain renderers may or may not seem to require loadPixels() or updatePixels(). However, the rule is that |
|
15017 * any time you want to manipulate the pixels[] array, you must first call loadPixels(), and after changes |
|
15018 * have been made, call updatePixels(). Even if the renderer may not seem to use this function in the current |
|
15019 * Processing release, this will always be subject to change. |
|
15020 * Currently, none of the renderers use the additional parameters to updatePixels(), however this may be |
|
15021 * implemented in the future. |
|
15022 * |
|
15023 * @see loadPixels |
|
15024 * @see pixels[] |
|
15025 */ |
|
15026 p.updatePixels = function() { |
|
15027 if (p.imageData) { |
|
15028 drawing.$ensureContext().putImageData(p.imageData, 0, 0); |
|
15029 } |
|
15030 }; |
|
15031 |
|
15032 /** |
|
15033 * Set various hints and hacks for the renderer. This is used to handle obscure rendering features that cannot be |
|
15034 * implemented in a consistent manner across renderers. Many options will often graduate to standard features |
|
15035 * instead of hints over time. |
|
15036 * hint(ENABLE_OPENGL_4X_SMOOTH) - Enable 4x anti-aliasing for OpenGL. This can help force anti-aliasing if |
|
15037 * it has not been enabled by the user. On some graphics cards, this can also be set by the graphics driver's |
|
15038 * control panel, however not all cards make this available. This hint must be called immediately after the |
|
15039 * size() command because it resets the renderer, obliterating any settings and anything drawn (and like size(), |
|
15040 * re-running the code that came before it again). |
|
15041 * hint(DISABLE_OPENGL_2X_SMOOTH) - In Processing 1.0, Processing always enables 2x smoothing when the OpenGL |
|
15042 * renderer is used. This hint disables the default 2x smoothing and returns the smoothing behavior found in |
|
15043 * earlier releases, where smooth() and noSmooth() could be used to enable and disable smoothing, though the |
|
15044 * quality was inferior. |
|
15045 * hint(ENABLE_NATIVE_FONTS) - Use the native version fonts when they are installed, rather than the bitmapped |
|
15046 * version from a .vlw file. This is useful with the JAVA2D renderer setting, as it will improve font rendering |
|
15047 * speed. This is not enabled by default, because it can be misleading while testing because the type will look |
|
15048 * great on your machine (because you have the font installed) but lousy on others' machines if the identical |
|
15049 * font is unavailable. This option can only be set per-sketch, and must be called before any use of textFont(). |
|
15050 * hint(DISABLE_DEPTH_TEST) - Disable the zbuffer, allowing you to draw on top of everything at will. When depth |
|
15051 * testing is disabled, items will be drawn to the screen sequentially, like a painting. This hint is most often |
|
15052 * used to draw in 3D, then draw in 2D on top of it (for instance, to draw GUI controls in 2D on top of a 3D |
|
15053 * interface). Starting in release 0149, this will also clear the depth buffer. Restore the default with |
|
15054 * hint(ENABLE_DEPTH_TEST), but note that with the depth buffer cleared, any 3D drawing that happens later in |
|
15055 * draw() will ignore existing shapes on the screen. |
|
15056 * hint(ENABLE_DEPTH_SORT) - Enable primitive z-sorting of triangles and lines in P3D and OPENGL. This can slow |
|
15057 * performance considerably, and the algorithm is not yet perfect. Restore the default with hint(DISABLE_DEPTH_SORT). |
|
15058 * hint(DISABLE_OPENGL_ERROR_REPORT) - Speeds up the OPENGL renderer setting by not checking for errors while |
|
15059 * running. Undo with hint(ENABLE_OPENGL_ERROR_REPORT). |
|
15060 * As of release 0149, unhint() has been removed in favor of adding additional ENABLE/DISABLE constants to reset |
|
15061 * the default behavior. This prevents the double negatives, and also reinforces which hints can be enabled or disabled. |
|
15062 * |
|
15063 * @param {MODE} item constant: name of the hint to be enabled or disabled |
|
15064 * |
|
15065 * @see PGraphics |
|
15066 * @see createGraphics |
|
15067 * @see size |
|
15068 */ |
|
15069 p.hint = function(which) { |
|
15070 var curContext = drawing.$ensureContext(); |
|
15071 if (which === PConstants.DISABLE_DEPTH_TEST) { |
|
15072 curContext.disable(curContext.DEPTH_TEST); |
|
15073 curContext.depthMask(false); |
|
15074 curContext.clear(curContext.DEPTH_BUFFER_BIT); |
|
15075 } |
|
15076 else if (which === PConstants.ENABLE_DEPTH_TEST) { |
|
15077 curContext.enable(curContext.DEPTH_TEST); |
|
15078 curContext.depthMask(true); |
|
15079 } |
|
15080 }; |
|
15081 |
|
15082 /** |
|
15083 * The background() function sets the color used for the background of the Processing window. |
|
15084 * The default background is light gray. In the <b>draw()</b> function, the background color is used to clear the display window at the beginning of each frame. |
|
15085 * An image can also be used as the background for a sketch, however its width and height must be the same size as the sketch window. |
|
15086 * To resize an image 'b' to the size of the sketch window, use b.resize(width, height). |
|
15087 * Images used as background will ignore the current <b>tint()</b> setting. |
|
15088 * For the main drawing surface, the alpha value will be ignored. However, |
|
15089 * alpha can be used on PGraphics objects from <b>createGraphics()</b>. This is |
|
15090 * the only way to set all the pixels partially transparent, for instance. |
|
15091 * If the 'gray' parameter is passed in the function sets the background to a grayscale value, based on the |
|
15092 * current colorMode. |
|
15093 * <p> |
|
15094 * Note that background() should be called before any transformations occur, |
|
15095 * because some implementations may require the current transformation matrix |
|
15096 * to be identity before drawing. |
|
15097 * |
|
15098 * @param {int|float} gray specifies a value between white and black |
|
15099 * @param {int|float} value1 red or hue value (depending on the current color mode) |
|
15100 * @param {int|float} value2 green or saturation value (depending on the current color mode) |
|
15101 * @param {int|float} value3 blue or brightness value (depending on the current color mode) |
|
15102 * @param {int|float} alpha opacity of the background |
|
15103 * @param {Color} color any value of the color datatype |
|
15104 * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) |
|
15105 * @param {PImage} image an instance of a PImage to use as a background |
|
15106 * |
|
15107 * @see #stroke() |
|
15108 * @see #fill() |
|
15109 * @see #tint() |
|
15110 * @see #colorMode() |
|
15111 */ |
|
15112 var backgroundHelper = function(arg1, arg2, arg3, arg4) { |
|
15113 var obj; |
|
15114 |
|
15115 if (arg1 instanceof PImage || arg1.__isPImage) { |
|
15116 obj = arg1; |
|
15117 |
|
15118 if (!obj.loaded) { |
|
15119 throw "Error using image in background(): PImage not loaded."; |
|
15120 } |
|
15121 if(obj.width !== p.width || obj.height !== p.height){ |
|
15122 throw "Background image must be the same dimensions as the canvas."; |
|
15123 } |
|
15124 } else { |
|
15125 obj = p.color(arg1, arg2, arg3, arg4); |
|
15126 } |
|
15127 |
|
15128 backgroundObj = obj; |
|
15129 }; |
|
15130 |
|
15131 Drawing2D.prototype.background = function(arg1, arg2, arg3, arg4) { |
|
15132 if (arg1 !== undef) { |
|
15133 backgroundHelper(arg1, arg2, arg3, arg4); |
|
15134 } |
|
15135 |
|
15136 if (backgroundObj instanceof PImage || backgroundObj.__isPImage) { |
|
15137 saveContext(); |
|
15138 curContext.setTransform(1, 0, 0, 1, 0, 0); |
|
15139 p.image(backgroundObj, 0, 0); |
|
15140 restoreContext(); |
|
15141 } else { |
|
15142 saveContext(); |
|
15143 curContext.setTransform(1, 0, 0, 1, 0, 0); |
|
15144 |
|
15145 // If the background is transparent |
|
15146 if (p.alpha(backgroundObj) !== colorModeA) { |
|
15147 curContext.clearRect(0,0, p.width, p.height); |
|
15148 } |
|
15149 curContext.fillStyle = p.color.toString(backgroundObj); |
|
15150 curContext.fillRect(0, 0, p.width, p.height); |
|
15151 isFillDirty = true; |
|
15152 restoreContext(); |
|
15153 } |
|
15154 }; |
|
15155 |
|
15156 Drawing3D.prototype.background = function(arg1, arg2, arg3, arg4) { |
|
15157 if (arguments.length > 0) { |
|
15158 backgroundHelper(arg1, arg2, arg3, arg4); |
|
15159 } |
|
15160 |
|
15161 var c = p.color.toGLArray(backgroundObj); |
|
15162 curContext.clearColor(c[0], c[1], c[2], c[3]); |
|
15163 curContext.clear(curContext.COLOR_BUFFER_BIT | curContext.DEPTH_BUFFER_BIT); |
|
15164 |
|
15165 // An image as a background in 3D is not implemented yet |
|
15166 }; |
|
15167 |
|
15168 // Draws an image to the Canvas |
|
15169 /** |
|
15170 * Displays images to the screen. The images must be in the sketch's "data" directory to load correctly. Select "Add |
|
15171 * file..." from the "Sketch" menu to add the image. Processing currently works with GIF, JPEG, and Targa images. The |
|
15172 * color of an image may be modified with the tint() function and if a GIF has transparency, it will maintain its |
|
15173 * transparency. The img parameter specifies the image to display and the x and y parameters define the location of |
|
15174 * the image from its upper-left corner. The image is displayed at its original size unless the width and height |
|
15175 * parameters specify a different size. The imageMode() function changes the way the parameters work. A call to |
|
15176 * imageMode(CORNERS) will change the width and height parameters to define the x and y values of the opposite |
|
15177 * corner of the image. |
|
15178 * |
|
15179 * @param {PImage} img the image to display |
|
15180 * @param {int|float} x x-coordinate of the image |
|
15181 * @param {int|float} y y-coordinate of the image |
|
15182 * @param {int|float} width width to display the image |
|
15183 * @param {int|float} height height to display the image |
|
15184 * |
|
15185 * @see loadImage |
|
15186 * @see PImage |
|
15187 * @see imageMode |
|
15188 * @see tint |
|
15189 * @see background |
|
15190 * @see alpha |
|
15191 */ |
|
15192 Drawing2D.prototype.image = function(img, x, y, w, h) { |
|
15193 // Fix fractional positions |
|
15194 x = Math.round(x); |
|
15195 y = Math.round(y); |
|
15196 |
|
15197 if (img.width > 0) { |
|
15198 var wid = w || img.width; |
|
15199 var hgt = h || img.height; |
|
15200 |
|
15201 var bounds = imageModeConvert(x || 0, y || 0, w || img.width, h || img.height, arguments.length < 4); |
|
15202 var fastImage = !!img.sourceImg && curTint === null; |
|
15203 if (fastImage) { |
|
15204 var htmlElement = img.sourceImg; |
|
15205 if (img.__isDirty) { |
|
15206 img.updatePixels(); |
|
15207 } |
|
15208 // Using HTML element's width and height in case if the image was resized. |
|
15209 curContext.drawImage(htmlElement, 0, 0, |
|
15210 htmlElement.width, htmlElement.height, bounds.x, bounds.y, bounds.w, bounds.h); |
|
15211 } else { |
|
15212 var obj = img.toImageData(); |
|
15213 |
|
15214 // Tint the image |
|
15215 if (curTint !== null) { |
|
15216 curTint(obj); |
|
15217 img.__isDirty = true; |
|
15218 } |
|
15219 |
|
15220 curContext.drawImage(getCanvasData(obj).canvas, 0, 0, |
|
15221 img.width, img.height, bounds.x, bounds.y, bounds.w, bounds.h); |
|
15222 } |
|
15223 } |
|
15224 }; |
|
15225 |
|
15226 Drawing3D.prototype.image = function(img, x, y, w, h) { |
|
15227 if (img.width > 0) { |
|
15228 // Fix fractional positions |
|
15229 x = Math.round(x); |
|
15230 y = Math.round(y); |
|
15231 w = w || img.width; |
|
15232 h = h || img.height; |
|
15233 |
|
15234 p.beginShape(p.QUADS); |
|
15235 p.texture(img); |
|
15236 p.vertex(x, y, 0, 0, 0); |
|
15237 p.vertex(x, y+h, 0, 0, h); |
|
15238 p.vertex(x+w, y+h, 0, w, h); |
|
15239 p.vertex(x+w, y, 0, w, 0); |
|
15240 p.endShape(); |
|
15241 } |
|
15242 }; |
|
15243 |
|
15244 /** |
|
15245 * The tint() function sets the fill value for displaying images. Images can be tinted to |
|
15246 * specified colors or made transparent by setting the alpha. |
|
15247 * <br><br>To make an image transparent, but not change it's color, |
|
15248 * use white as the tint color and specify an alpha value. For instance, |
|
15249 * tint(255, 128) will make an image 50% transparent (unless |
|
15250 * <b>colorMode()</b> has been used). |
|
15251 * |
|
15252 * <br><br>When using hexadecimal notation to specify a color, use "#" or |
|
15253 * "0x" before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six |
|
15254 * digits to specify a color (the way colors are specified in HTML and CSS). |
|
15255 * When using the hexadecimal notation starting with "0x", the hexadecimal |
|
15256 * value must be specified with eight characters; the first two characters |
|
15257 * define the alpha component and the remainder the red, green, and blue |
|
15258 * components. |
|
15259 * <br><br>The value for the parameter "gray" must be less than or equal |
|
15260 * to the current maximum value as specified by <b>colorMode()</b>. |
|
15261 * The default maximum value is 255. |
|
15262 * <br><br>The tint() method is also used to control the coloring of |
|
15263 * textures in 3D. |
|
15264 * |
|
15265 * @param {int|float} gray any valid number |
|
15266 * @param {int|float} alpha opacity of the image |
|
15267 * @param {int|float} value1 red or hue value |
|
15268 * @param {int|float} value2 green or saturation value |
|
15269 * @param {int|float} value3 blue or brightness value |
|
15270 * @param {int|float} color any value of the color datatype |
|
15271 * @param {int} hex color value in hexadecimal notation (i.e. #FFCC00 or 0xFFFFCC00) |
|
15272 * |
|
15273 * @see #noTint() |
|
15274 * @see #image() |
|
15275 */ |
|
15276 p.tint = function(a1, a2, a3, a4) { |
|
15277 var tintColor = p.color(a1, a2, a3, a4); |
|
15278 var r = p.red(tintColor) / colorModeX; |
|
15279 var g = p.green(tintColor) / colorModeY; |
|
15280 var b = p.blue(tintColor) / colorModeZ; |
|
15281 var a = p.alpha(tintColor) / colorModeA; |
|
15282 curTint = function(obj) { |
|
15283 var data = obj.data, |
|
15284 length = 4 * obj.width * obj.height; |
|
15285 for (var i = 0; i < length;) { |
|
15286 data[i++] *= r; |
|
15287 data[i++] *= g; |
|
15288 data[i++] *= b; |
|
15289 data[i++] *= a; |
|
15290 } |
|
15291 }; |
|
15292 // for overriding the color buffer when 3d rendering |
|
15293 curTint3d = function(data){ |
|
15294 for (var i = 0; i < data.length;) { |
|
15295 data[i++] = r; |
|
15296 data[i++] = g; |
|
15297 data[i++] = b; |
|
15298 data[i++] = a; |
|
15299 } |
|
15300 }; |
|
15301 }; |
|
15302 |
|
15303 /** |
|
15304 * The noTint() function removes the current fill value for displaying images and reverts to displaying images with their original hues. |
|
15305 * |
|
15306 * @see #tint() |
|
15307 * @see #image() |
|
15308 */ |
|
15309 p.noTint = function() { |
|
15310 curTint = null; |
|
15311 curTint3d = null; |
|
15312 }; |
|
15313 |
|
15314 /** |
|
15315 * Copies a region of pixels from the display window to another area of the display window and copies a region of pixels from an |
|
15316 * image used as the srcImg parameter into the display window. If the source and destination regions aren't the same size, it will |
|
15317 * automatically resize the source pixels to fit the specified target region. No alpha information is used in the process, however |
|
15318 * if the source image has an alpha channel set, it will be copied as well. This function ignores imageMode(). |
|
15319 * |
|
15320 * @param {int} x X coordinate of the source's upper left corner |
|
15321 * @param {int} y Y coordinate of the source's upper left corner |
|
15322 * @param {int} width source image width |
|
15323 * @param {int} height source image height |
|
15324 * @param {int} dx X coordinate of the destination's upper left corner |
|
15325 * @param {int} dy Y coordinate of the destination's upper left corner |
|
15326 * @param {int} dwidth destination image width |
|
15327 * @param {int} dheight destination image height |
|
15328 * @param {PImage} srcImg image variable referring to the source image |
|
15329 * |
|
15330 * @see blend |
|
15331 * @see get |
|
15332 */ |
|
15333 p.copy = function(src, sx, sy, sw, sh, dx, dy, dw, dh) { |
|
15334 if (dh === undef) { |
|
15335 // shift everything, and introduce p |
|
15336 dh = dw; |
|
15337 dw = dy; |
|
15338 dy = dx; |
|
15339 dx = sh; |
|
15340 sh = sw; |
|
15341 sw = sy; |
|
15342 sy = sx; |
|
15343 sx = src; |
|
15344 src = p; |
|
15345 } |
|
15346 p.blend(src, sx, sy, sw, sh, dx, dy, dw, dh, PConstants.REPLACE); |
|
15347 }; |
|
15348 |
|
15349 /** |
|
15350 * Blends a region of pixels from one image into another (or in itself again) with full alpha channel support. There |
|
15351 * is a choice of the following modes to blend the source pixels (A) with the ones of pixels in the destination image (B): |
|
15352 * BLEND - linear interpolation of colours: C = A*factor + B |
|
15353 * ADD - additive blending with white clip: C = min(A*factor + B, 255) |
|
15354 * SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, 0) |
|
15355 * DARKEST - only the darkest colour succeeds: C = min(A*factor, B) |
|
15356 * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B) |
|
15357 * DIFFERENCE - subtract colors from underlying image. |
|
15358 * EXCLUSION - similar to DIFFERENCE, but less extreme. |
|
15359 * MULTIPLY - Multiply the colors, result will always be darker. |
|
15360 * SCREEN - Opposite multiply, uses inverse values of the colors. |
|
15361 * OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, and screens light values. |
|
15362 * HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower. |
|
15363 * SOFT_LIGHT - Mix of DARKEST and LIGHTEST. Works like OVERLAY, but not as harsh. |
|
15364 * DODGE - Lightens light tones and increases contrast, ignores darks. Called "Color Dodge" in Illustrator and Photoshop. |
|
15365 * BURN - Darker areas are applied, increasing contrast, ignores lights. Called "Color Burn" in Illustrator and Photoshop. |
|
15366 * All modes use the alpha information (highest byte) of source image pixels as the blending factor. If the source and |
|
15367 * destination regions are different sizes, the image will be automatically resized to match the destination size. If the |
|
15368 * srcImg parameter is not used, the display window is used as the source image. This function ignores imageMode(). |
|
15369 * |
|
15370 * @param {int} x X coordinate of the source's upper left corner |
|
15371 * @param {int} y Y coordinate of the source's upper left corner |
|
15372 * @param {int} width source image width |
|
15373 * @param {int} height source image height |
|
15374 * @param {int} dx X coordinate of the destination's upper left corner |
|
15375 * @param {int} dy Y coordinate of the destination's upper left corner |
|
15376 * @param {int} dwidth destination image width |
|
15377 * @param {int} dheight destination image height |
|
15378 * @param {PImage} srcImg image variable referring to the source image |
|
15379 * @param {PImage} MODE Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, |
|
15380 * OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN |
|
15381 * @see filter |
|
15382 */ |
|
15383 p.blend = function(src, sx, sy, sw, sh, dx, dy, dw, dh, mode, pimgdest) { |
|
15384 if (src.isRemote) { |
|
15385 throw "Image is loaded remotely. Cannot blend image."; |
|
15386 } |
|
15387 |
|
15388 if (mode === undef) { |
|
15389 // shift everything, and introduce p |
|
15390 mode = dh; |
|
15391 dh = dw; |
|
15392 dw = dy; |
|
15393 dy = dx; |
|
15394 dx = sh; |
|
15395 sh = sw; |
|
15396 sw = sy; |
|
15397 sy = sx; |
|
15398 sx = src; |
|
15399 src = p; |
|
15400 } |
|
15401 |
|
15402 var sx2 = sx + sw, |
|
15403 sy2 = sy + sh, |
|
15404 dx2 = dx + dw, |
|
15405 dy2 = dy + dh, |
|
15406 dest = pimgdest || p; |
|
15407 |
|
15408 // check if pimgdest is there and pixels, if so this was a call from pimg.blend |
|
15409 if (pimgdest === undef || mode === undef) { |
|
15410 p.loadPixels(); |
|
15411 } |
|
15412 |
|
15413 src.loadPixels(); |
|
15414 |
|
15415 if (src === p && p.intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) { |
|
15416 p.blit_resize(p.get(sx, sy, sx2 - sx, sy2 - sy), 0, 0, sx2 - sx - 1, sy2 - sy - 1, |
|
15417 dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode); |
|
15418 } else { |
|
15419 p.blit_resize(src, sx, sy, sx2, sy2, dest.imageData.data, dest.width, dest.height, dx, dy, dx2, dy2, mode); |
|
15420 } |
|
15421 |
|
15422 if (pimgdest === undef) { |
|
15423 p.updatePixels(); |
|
15424 } |
|
15425 }; |
|
15426 |
|
15427 // helper function for filter() |
|
15428 var buildBlurKernel = function(r) { |
|
15429 var radius = p.floor(r * 3.5), i, radiusi; |
|
15430 radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); |
|
15431 if (p.shared.blurRadius !== radius) { |
|
15432 p.shared.blurRadius = radius; |
|
15433 p.shared.blurKernelSize = 1 + (p.shared.blurRadius<<1); |
|
15434 p.shared.blurKernel = new Float32Array(p.shared.blurKernelSize); |
|
15435 var sharedBlurKernal = p.shared.blurKernel; |
|
15436 var sharedBlurKernelSize = p.shared.blurKernelSize; |
|
15437 var sharedBlurRadius = p.shared.blurRadius; |
|
15438 // init blurKernel |
|
15439 for (i = 0; i < sharedBlurKernelSize; i++) { |
|
15440 sharedBlurKernal[i] = 0; |
|
15441 } |
|
15442 var radiusiSquared = (radius - 1) * (radius - 1); |
|
15443 for (i = 1; i < radius; i++) { |
|
15444 sharedBlurKernal[radius + i] = sharedBlurKernal[radiusi] = radiusiSquared; |
|
15445 } |
|
15446 sharedBlurKernal[radius] = radius * radius; |
|
15447 } |
|
15448 }; |
|
15449 |
|
15450 var blurARGB = function(r, aImg) { |
|
15451 var sum, cr, cg, cb, ca, c, m; |
|
15452 var read, ri, ym, ymi, bk0; |
|
15453 var wh = aImg.pixels.getLength(); |
|
15454 var r2 = new Float32Array(wh); |
|
15455 var g2 = new Float32Array(wh); |
|
15456 var b2 = new Float32Array(wh); |
|
15457 var a2 = new Float32Array(wh); |
|
15458 var yi = 0; |
|
15459 var x, y, i, offset; |
|
15460 |
|
15461 buildBlurKernel(r); |
|
15462 |
|
15463 var aImgHeight = aImg.height; |
|
15464 var aImgWidth = aImg.width; |
|
15465 var sharedBlurKernelSize = p.shared.blurKernelSize; |
|
15466 var sharedBlurRadius = p.shared.blurRadius; |
|
15467 var sharedBlurKernal = p.shared.blurKernel; |
|
15468 var pix = aImg.imageData.data; |
|
15469 |
|
15470 for (y = 0; y < aImgHeight; y++) { |
|
15471 for (x = 0; x < aImgWidth; x++) { |
|
15472 cb = cg = cr = ca = sum = 0; |
|
15473 read = x - sharedBlurRadius; |
|
15474 if (read<0) { |
|
15475 bk0 = -read; |
|
15476 read = 0; |
|
15477 } else { |
|
15478 if (read >= aImgWidth) { |
|
15479 break; |
|
15480 } |
|
15481 bk0=0; |
|
15482 } |
|
15483 for (i = bk0; i < sharedBlurKernelSize; i++) { |
|
15484 if (read >= aImgWidth) { |
|
15485 break; |
|
15486 } |
|
15487 offset = (read + yi) *4; |
|
15488 m = sharedBlurKernal[i]; |
|
15489 ca += m * pix[offset + 3]; |
|
15490 cr += m * pix[offset]; |
|
15491 cg += m * pix[offset + 1]; |
|
15492 cb += m * pix[offset + 2]; |
|
15493 sum += m; |
|
15494 read++; |
|
15495 } |
|
15496 ri = yi + x; |
|
15497 a2[ri] = ca / sum; |
|
15498 r2[ri] = cr / sum; |
|
15499 g2[ri] = cg / sum; |
|
15500 b2[ri] = cb / sum; |
|
15501 } |
|
15502 yi += aImgWidth; |
|
15503 } |
|
15504 |
|
15505 yi = 0; |
|
15506 ym = -sharedBlurRadius; |
|
15507 ymi = ym*aImgWidth; |
|
15508 |
|
15509 for (y = 0; y < aImgHeight; y++) { |
|
15510 for (x = 0; x < aImgWidth; x++) { |
|
15511 cb = cg = cr = ca = sum = 0; |
|
15512 if (ym<0) { |
|
15513 bk0 = ri = -ym; |
|
15514 read = x; |
|
15515 } else { |
|
15516 if (ym >= aImgHeight) { |
|
15517 break; |
|
15518 } |
|
15519 bk0 = 0; |
|
15520 ri = ym; |
|
15521 read = x + ymi; |
|
15522 } |
|
15523 for (i = bk0; i < sharedBlurKernelSize; i++) { |
|
15524 if (ri >= aImgHeight) { |
|
15525 break; |
|
15526 } |
|
15527 m = sharedBlurKernal[i]; |
|
15528 ca += m * a2[read]; |
|
15529 cr += m * r2[read]; |
|
15530 cg += m * g2[read]; |
|
15531 cb += m * b2[read]; |
|
15532 sum += m; |
|
15533 ri++; |
|
15534 read += aImgWidth; |
|
15535 } |
|
15536 offset = (x + yi) *4; |
|
15537 pix[offset] = cr / sum; |
|
15538 pix[offset + 1] = cg / sum; |
|
15539 pix[offset + 2] = cb / sum; |
|
15540 pix[offset + 3] = ca / sum; |
|
15541 } |
|
15542 yi += aImgWidth; |
|
15543 ymi += aImgWidth; |
|
15544 ym++; |
|
15545 } |
|
15546 }; |
|
15547 |
|
15548 // helper funtion for ERODE and DILATE modes of filter() |
|
15549 var dilate = function(isInverted, aImg) { |
|
15550 var currIdx = 0; |
|
15551 var maxIdx = aImg.pixels.getLength(); |
|
15552 var out = new Int32Array(maxIdx); |
|
15553 var currRowIdx, maxRowIdx, colOrig, colOut, currLum; |
|
15554 var idxRight, idxLeft, idxUp, idxDown, |
|
15555 colRight, colLeft, colUp, colDown, |
|
15556 lumRight, lumLeft, lumUp, lumDown; |
|
15557 |
|
15558 if (!isInverted) { |
|
15559 // erosion (grow light areas) |
|
15560 while (currIdx<maxIdx) { |
|
15561 currRowIdx = currIdx; |
|
15562 maxRowIdx = currIdx + aImg.width; |
|
15563 while (currIdx < maxRowIdx) { |
|
15564 colOrig = colOut = aImg.pixels.getPixel(currIdx); |
|
15565 idxLeft = currIdx - 1; |
|
15566 idxRight = currIdx + 1; |
|
15567 idxUp = currIdx - aImg.width; |
|
15568 idxDown = currIdx + aImg.width; |
|
15569 if (idxLeft < currRowIdx) { |
|
15570 idxLeft = currIdx; |
|
15571 } |
|
15572 if (idxRight >= maxRowIdx) { |
|
15573 idxRight = currIdx; |
|
15574 } |
|
15575 if (idxUp < 0) { |
|
15576 idxUp = 0; |
|
15577 } |
|
15578 if (idxDown >= maxIdx) { |
|
15579 idxDown = currIdx; |
|
15580 } |
|
15581 colUp = aImg.pixels.getPixel(idxUp); |
|
15582 colLeft = aImg.pixels.getPixel(idxLeft); |
|
15583 colDown = aImg.pixels.getPixel(idxDown); |
|
15584 colRight = aImg.pixels.getPixel(idxRight); |
|
15585 |
|
15586 // compute luminance |
|
15587 currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); |
|
15588 lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); |
|
15589 lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); |
|
15590 lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); |
|
15591 lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); |
|
15592 |
|
15593 if (lumLeft > currLum) { |
|
15594 colOut = colLeft; |
|
15595 currLum = lumLeft; |
|
15596 } |
|
15597 if (lumRight > currLum) { |
|
15598 colOut = colRight; |
|
15599 currLum = lumRight; |
|
15600 } |
|
15601 if (lumUp > currLum) { |
|
15602 colOut = colUp; |
|
15603 currLum = lumUp; |
|
15604 } |
|
15605 if (lumDown > currLum) { |
|
15606 colOut = colDown; |
|
15607 currLum = lumDown; |
|
15608 } |
|
15609 out[currIdx++] = colOut; |
|
15610 } |
|
15611 } |
|
15612 } else { |
|
15613 // dilate (grow dark areas) |
|
15614 while (currIdx < maxIdx) { |
|
15615 currRowIdx = currIdx; |
|
15616 maxRowIdx = currIdx + aImg.width; |
|
15617 while (currIdx < maxRowIdx) { |
|
15618 colOrig = colOut = aImg.pixels.getPixel(currIdx); |
|
15619 idxLeft = currIdx - 1; |
|
15620 idxRight = currIdx + 1; |
|
15621 idxUp = currIdx - aImg.width; |
|
15622 idxDown = currIdx + aImg.width; |
|
15623 if (idxLeft < currRowIdx) { |
|
15624 idxLeft = currIdx; |
|
15625 } |
|
15626 if (idxRight >= maxRowIdx) { |
|
15627 idxRight = currIdx; |
|
15628 } |
|
15629 if (idxUp < 0) { |
|
15630 idxUp = 0; |
|
15631 } |
|
15632 if (idxDown >= maxIdx) { |
|
15633 idxDown = currIdx; |
|
15634 } |
|
15635 colUp = aImg.pixels.getPixel(idxUp); |
|
15636 colLeft = aImg.pixels.getPixel(idxLeft); |
|
15637 colDown = aImg.pixels.getPixel(idxDown); |
|
15638 colRight = aImg.pixels.getPixel(idxRight); |
|
15639 |
|
15640 // compute luminance |
|
15641 currLum = 77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff); |
|
15642 lumLeft = 77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff); |
|
15643 lumRight = 77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff); |
|
15644 lumUp = 77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff); |
|
15645 lumDown = 77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff); |
|
15646 |
|
15647 if (lumLeft < currLum) { |
|
15648 colOut = colLeft; |
|
15649 currLum = lumLeft; |
|
15650 } |
|
15651 if (lumRight < currLum) { |
|
15652 colOut = colRight; |
|
15653 currLum = lumRight; |
|
15654 } |
|
15655 if (lumUp < currLum) { |
|
15656 colOut = colUp; |
|
15657 currLum = lumUp; |
|
15658 } |
|
15659 if (lumDown < currLum) { |
|
15660 colOut = colDown; |
|
15661 currLum = lumDown; |
|
15662 } |
|
15663 out[currIdx++]=colOut; |
|
15664 } |
|
15665 } |
|
15666 } |
|
15667 aImg.pixels.set(out); |
|
15668 //p.arraycopy(out,0,pixels,0,maxIdx); |
|
15669 }; |
|
15670 |
|
15671 /** |
|
15672 * Filters the display window as defined by one of the following modes: |
|
15673 * THRESHOLD - converts the image to black and white pixels depending if they are above or below the threshold |
|
15674 * defined by the level parameter. The level must be between 0.0 (black) and 1.0(white). If no level is specified, 0.5 is used. |
|
15675 * GRAY - converts any colors in the image to grayscale equivalents |
|
15676 * INVERT - sets each pixel to its inverse value |
|
15677 * POSTERIZE - limits each channel of the image to the number of colors specified as the level parameter |
|
15678 * BLUR - executes a Guassian blur with the level parameter specifying the extent of the blurring. If no level parameter is |
|
15679 * used, the blur is equivalent to Guassian blur of radius 1. |
|
15680 * OPAQUE - sets the alpha channel to entirely opaque. |
|
15681 * ERODE - reduces the light areas with the amount defined by the level parameter. |
|
15682 * DILATE - increases the light areas with the amount defined by the level parameter. |
|
15683 * |
|
15684 * @param {MODE} MODE Either THRESHOLD, GRAY, INVERT, POSTERIZE, BLUR, OPAQUE, ERODE, or DILATE |
|
15685 * @param {int|float} level defines the quality of the filter |
|
15686 * |
|
15687 * @see blend |
|
15688 */ |
|
15689 p.filter = function(kind, param, aImg){ |
|
15690 var img, col, lum, i; |
|
15691 |
|
15692 if (arguments.length === 3) { |
|
15693 aImg.loadPixels(); |
|
15694 img = aImg; |
|
15695 } else { |
|
15696 p.loadPixels(); |
|
15697 img = p; |
|
15698 } |
|
15699 |
|
15700 if (param === undef) { |
|
15701 param = null; |
|
15702 } |
|
15703 if (img.isRemote) { // Remote images cannot access imageData |
|
15704 throw "Image is loaded remotely. Cannot filter image."; |
|
15705 } |
|
15706 // begin filter process |
|
15707 var imglen = img.pixels.getLength(); |
|
15708 switch (kind) { |
|
15709 case PConstants.BLUR: |
|
15710 var radius = param || 1; // if no param specified, use 1 (default for p5) |
|
15711 blurARGB(radius, img); |
|
15712 break; |
|
15713 |
|
15714 case PConstants.GRAY: |
|
15715 if (img.format === PConstants.ALPHA) { //trouble |
|
15716 // for an alpha image, convert it to an opaque grayscale |
|
15717 for (i = 0; i < imglen; i++) { |
|
15718 col = 255 - img.pixels.getPixel(i); |
|
15719 img.pixels.setPixel(i,(0xff000000 | (col << 16) | (col << 8) | col)); |
|
15720 } |
|
15721 img.format = PConstants.RGB; //trouble |
|
15722 } else { |
|
15723 for (i = 0; i < imglen; i++) { |
|
15724 col = img.pixels.getPixel(i); |
|
15725 lum = (77*(col>>16&0xff) + 151*(col>>8&0xff) + 28*(col&0xff))>>8; |
|
15726 img.pixels.setPixel(i,((col & PConstants.ALPHA_MASK) | lum<<16 | lum<<8 | lum)); |
|
15727 } |
|
15728 } |
|
15729 break; |
|
15730 |
|
15731 case PConstants.INVERT: |
|
15732 for (i = 0; i < imglen; i++) { |
|
15733 img.pixels.setPixel(i, (img.pixels.getPixel(i) ^ 0xffffff)); |
|
15734 } |
|
15735 break; |
|
15736 |
|
15737 case PConstants.POSTERIZE: |
|
15738 if (param === null) { |
|
15739 throw "Use filter(POSTERIZE, int levels) instead of filter(POSTERIZE)"; |
|
15740 } |
|
15741 var levels = p.floor(param); |
|
15742 if ((levels < 2) || (levels > 255)) { |
|
15743 throw "Levels must be between 2 and 255 for filter(POSTERIZE, levels)"; |
|
15744 } |
|
15745 var levels1 = levels - 1; |
|
15746 for (i = 0; i < imglen; i++) { |
|
15747 var rlevel = (img.pixels.getPixel(i) >> 16) & 0xff; |
|
15748 var glevel = (img.pixels.getPixel(i) >> 8) & 0xff; |
|
15749 var blevel = img.pixels.getPixel(i) & 0xff; |
|
15750 rlevel = (((rlevel * levels) >> 8) * 255) / levels1; |
|
15751 glevel = (((glevel * levels) >> 8) * 255) / levels1; |
|
15752 blevel = (((blevel * levels) >> 8) * 255) / levels1; |
|
15753 img.pixels.setPixel(i, ((0xff000000 & img.pixels.getPixel(i)) | (rlevel << 16) | (glevel << 8) | blevel)); |
|
15754 } |
|
15755 break; |
|
15756 |
|
15757 case PConstants.OPAQUE: |
|
15758 for (i = 0; i < imglen; i++) { |
|
15759 img.pixels.setPixel(i, (img.pixels.getPixel(i) | 0xff000000)); |
|
15760 } |
|
15761 img.format = PConstants.RGB; //trouble |
|
15762 break; |
|
15763 |
|
15764 case PConstants.THRESHOLD: |
|
15765 if (param === null) { |
|
15766 param = 0.5; |
|
15767 } |
|
15768 if ((param < 0) || (param > 1)) { |
|
15769 throw "Level must be between 0 and 1 for filter(THRESHOLD, level)"; |
|
15770 } |
|
15771 var thresh = p.floor(param * 255); |
|
15772 for (i = 0; i < imglen; i++) { |
|
15773 var max = p.max((img.pixels.getPixel(i) & PConstants.RED_MASK) >> 16, p.max((img.pixels.getPixel(i) & PConstants.GREEN_MASK) >> 8, (img.pixels.getPixel(i) & PConstants.BLUE_MASK))); |
|
15774 img.pixels.setPixel(i, ((img.pixels.getPixel(i) & PConstants.ALPHA_MASK) | ((max < thresh) ? 0x000000 : 0xffffff))); |
|
15775 } |
|
15776 break; |
|
15777 |
|
15778 case PConstants.ERODE: |
|
15779 dilate(true, img); |
|
15780 break; |
|
15781 |
|
15782 case PConstants.DILATE: |
|
15783 dilate(false, img); |
|
15784 break; |
|
15785 } |
|
15786 img.updatePixels(); |
|
15787 }; |
|
15788 |
|
15789 |
|
15790 // shared variables for blit_resize(), filter_new_scanline(), filter_bilinear(), filter() |
|
15791 // change this in the future to not be exposed to p |
|
15792 p.shared = { |
|
15793 fracU: 0, |
|
15794 ifU: 0, |
|
15795 fracV: 0, |
|
15796 ifV: 0, |
|
15797 u1: 0, |
|
15798 u2: 0, |
|
15799 v1: 0, |
|
15800 v2: 0, |
|
15801 sX: 0, |
|
15802 sY: 0, |
|
15803 iw: 0, |
|
15804 iw1: 0, |
|
15805 ih1: 0, |
|
15806 ul: 0, |
|
15807 ll: 0, |
|
15808 ur: 0, |
|
15809 lr: 0, |
|
15810 cUL: 0, |
|
15811 cLL: 0, |
|
15812 cUR: 0, |
|
15813 cLR: 0, |
|
15814 srcXOffset: 0, |
|
15815 srcYOffset: 0, |
|
15816 r: 0, |
|
15817 g: 0, |
|
15818 b: 0, |
|
15819 a: 0, |
|
15820 srcBuffer: null, |
|
15821 blurRadius: 0, |
|
15822 blurKernelSize: 0, |
|
15823 blurKernel: null |
|
15824 }; |
|
15825 |
|
15826 p.intersect = function(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2) { |
|
15827 var sw = sx2 - sx1 + 1; |
|
15828 var sh = sy2 - sy1 + 1; |
|
15829 var dw = dx2 - dx1 + 1; |
|
15830 var dh = dy2 - dy1 + 1; |
|
15831 if (dx1 < sx1) { |
|
15832 dw += dx1 - sx1; |
|
15833 if (dw > sw) { |
|
15834 dw = sw; |
|
15835 } |
|
15836 } else { |
|
15837 var w = sw + sx1 - dx1; |
|
15838 if (dw > w) { |
|
15839 dw = w; |
|
15840 } |
|
15841 } |
|
15842 if (dy1 < sy1) { |
|
15843 dh += dy1 - sy1; |
|
15844 if (dh > sh) { |
|
15845 dh = sh; |
|
15846 } |
|
15847 } else { |
|
15848 var h = sh + sy1 - dy1; |
|
15849 if (dh > h) { |
|
15850 dh = h; |
|
15851 } |
|
15852 } |
|
15853 return ! (dw <= 0 || dh <= 0); |
|
15854 }; |
|
15855 |
|
15856 var blendFuncs = {}; |
|
15857 blendFuncs[PConstants.BLEND] = p.modes.blend; |
|
15858 blendFuncs[PConstants.ADD] = p.modes.add; |
|
15859 blendFuncs[PConstants.SUBTRACT] = p.modes.subtract; |
|
15860 blendFuncs[PConstants.LIGHTEST] = p.modes.lightest; |
|
15861 blendFuncs[PConstants.DARKEST] = p.modes.darkest; |
|
15862 blendFuncs[PConstants.REPLACE] = p.modes.replace; |
|
15863 blendFuncs[PConstants.DIFFERENCE] = p.modes.difference; |
|
15864 blendFuncs[PConstants.EXCLUSION] = p.modes.exclusion; |
|
15865 blendFuncs[PConstants.MULTIPLY] = p.modes.multiply; |
|
15866 blendFuncs[PConstants.SCREEN] = p.modes.screen; |
|
15867 blendFuncs[PConstants.OVERLAY] = p.modes.overlay; |
|
15868 blendFuncs[PConstants.HARD_LIGHT] = p.modes.hard_light; |
|
15869 blendFuncs[PConstants.SOFT_LIGHT] = p.modes.soft_light; |
|
15870 blendFuncs[PConstants.DODGE] = p.modes.dodge; |
|
15871 blendFuncs[PConstants.BURN] = p.modes.burn; |
|
15872 |
|
15873 p.blit_resize = function(img, srcX1, srcY1, srcX2, srcY2, destPixels, |
|
15874 screenW, screenH, destX1, destY1, destX2, destY2, mode) { |
|
15875 var x, y; |
|
15876 if (srcX1 < 0) { |
|
15877 srcX1 = 0; |
|
15878 } |
|
15879 if (srcY1 < 0) { |
|
15880 srcY1 = 0; |
|
15881 } |
|
15882 if (srcX2 >= img.width) { |
|
15883 srcX2 = img.width - 1; |
|
15884 } |
|
15885 if (srcY2 >= img.height) { |
|
15886 srcY2 = img.height - 1; |
|
15887 } |
|
15888 var srcW = srcX2 - srcX1; |
|
15889 var srcH = srcY2 - srcY1; |
|
15890 var destW = destX2 - destX1; |
|
15891 var destH = destY2 - destY1; |
|
15892 |
|
15893 if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW || |
|
15894 destY1 >= screenH || srcX1 >= img.width || srcY1 >= img.height) { |
|
15895 return; |
|
15896 } |
|
15897 |
|
15898 var dx = Math.floor(srcW / destW * PConstants.PRECISIONF); |
|
15899 var dy = Math.floor(srcH / destH * PConstants.PRECISIONF); |
|
15900 |
|
15901 var pshared = p.shared; |
|
15902 |
|
15903 pshared.srcXOffset = Math.floor(destX1 < 0 ? -destX1 * dx : srcX1 * PConstants.PRECISIONF); |
|
15904 pshared.srcYOffset = Math.floor(destY1 < 0 ? -destY1 * dy : srcY1 * PConstants.PRECISIONF); |
|
15905 if (destX1 < 0) { |
|
15906 destW += destX1; |
|
15907 destX1 = 0; |
|
15908 } |
|
15909 if (destY1 < 0) { |
|
15910 destH += destY1; |
|
15911 destY1 = 0; |
|
15912 } |
|
15913 destW = Math.min(destW, screenW - destX1); |
|
15914 destH = Math.min(destH, screenH - destY1); |
|
15915 |
|
15916 var destOffset = destY1 * screenW + destX1; |
|
15917 var destColor; |
|
15918 |
|
15919 pshared.srcBuffer = img.imageData.data; |
|
15920 pshared.iw = img.width; |
|
15921 pshared.iw1 = img.width - 1; |
|
15922 pshared.ih1 = img.height - 1; |
|
15923 |
|
15924 // cache for speed |
|
15925 var filterBilinear = p.filter_bilinear, |
|
15926 filterNewScanline = p.filter_new_scanline, |
|
15927 blendFunc = blendFuncs[mode], |
|
15928 blendedColor, |
|
15929 idx, |
|
15930 cULoffset, |
|
15931 cURoffset, |
|
15932 cLLoffset, |
|
15933 cLRoffset, |
|
15934 ALPHA_MASK = PConstants.ALPHA_MASK, |
|
15935 RED_MASK = PConstants.RED_MASK, |
|
15936 GREEN_MASK = PConstants.GREEN_MASK, |
|
15937 BLUE_MASK = PConstants.BLUE_MASK, |
|
15938 PREC_MAXVAL = PConstants.PREC_MAXVAL, |
|
15939 PRECISIONB = PConstants.PRECISIONB, |
|
15940 PREC_RED_SHIFT = PConstants.PREC_RED_SHIFT, |
|
15941 PREC_ALPHA_SHIFT = PConstants.PREC_ALPHA_SHIFT, |
|
15942 srcBuffer = pshared.srcBuffer, |
|
15943 min = Math.min; |
|
15944 |
|
15945 for (y = 0; y < destH; y++) { |
|
15946 |
|
15947 pshared.sX = pshared.srcXOffset; |
|
15948 pshared.fracV = pshared.srcYOffset & PREC_MAXVAL; |
|
15949 pshared.ifV = PREC_MAXVAL - pshared.fracV; |
|
15950 pshared.v1 = (pshared.srcYOffset >> PRECISIONB) * pshared.iw; |
|
15951 pshared.v2 = min((pshared.srcYOffset >> PRECISIONB) + 1, pshared.ih1) * pshared.iw; |
|
15952 |
|
15953 for (x = 0; x < destW; x++) { |
|
15954 idx = (destOffset + x) * 4; |
|
15955 |
|
15956 destColor = (destPixels[idx + 3] << 24) & |
|
15957 ALPHA_MASK | (destPixels[idx] << 16) & |
|
15958 RED_MASK | (destPixels[idx + 1] << 8) & |
|
15959 GREEN_MASK | destPixels[idx + 2] & BLUE_MASK; |
|
15960 |
|
15961 pshared.fracU = pshared.sX & PREC_MAXVAL; |
|
15962 pshared.ifU = PREC_MAXVAL - pshared.fracU; |
|
15963 pshared.ul = (pshared.ifU * pshared.ifV) >> PRECISIONB; |
|
15964 pshared.ll = (pshared.ifU * pshared.fracV) >> PRECISIONB; |
|
15965 pshared.ur = (pshared.fracU * pshared.ifV) >> PRECISIONB; |
|
15966 pshared.lr = (pshared.fracU * pshared.fracV) >> PRECISIONB; |
|
15967 pshared.u1 = (pshared.sX >> PRECISIONB); |
|
15968 pshared.u2 = min(pshared.u1 + 1, pshared.iw1); |
|
15969 |
|
15970 cULoffset = (pshared.v1 + pshared.u1) * 4; |
|
15971 cURoffset = (pshared.v1 + pshared.u2) * 4; |
|
15972 cLLoffset = (pshared.v2 + pshared.u1) * 4; |
|
15973 cLRoffset = (pshared.v2 + pshared.u2) * 4; |
|
15974 |
|
15975 pshared.cUL = (srcBuffer[cULoffset + 3] << 24) & |
|
15976 ALPHA_MASK | (srcBuffer[cULoffset] << 16) & |
|
15977 RED_MASK | (srcBuffer[cULoffset + 1] << 8) & |
|
15978 GREEN_MASK | srcBuffer[cULoffset + 2] & BLUE_MASK; |
|
15979 |
|
15980 pshared.cUR = (srcBuffer[cURoffset + 3] << 24) & |
|
15981 ALPHA_MASK | (srcBuffer[cURoffset] << 16) & |
|
15982 RED_MASK | (srcBuffer[cURoffset + 1] << 8) & |
|
15983 GREEN_MASK | srcBuffer[cURoffset + 2] & BLUE_MASK; |
|
15984 |
|
15985 pshared.cLL = (srcBuffer[cLLoffset + 3] << 24) & |
|
15986 ALPHA_MASK | (srcBuffer[cLLoffset] << 16) & |
|
15987 RED_MASK | (srcBuffer[cLLoffset + 1] << 8) & |
|
15988 GREEN_MASK | srcBuffer[cLLoffset + 2] & BLUE_MASK; |
|
15989 |
|
15990 pshared.cLR = (srcBuffer[cLRoffset + 3] << 24) & |
|
15991 ALPHA_MASK | (srcBuffer[cLRoffset] << 16) & |
|
15992 RED_MASK | (srcBuffer[cLRoffset + 1] << 8) & |
|
15993 GREEN_MASK | srcBuffer[cLRoffset + 2] & BLUE_MASK; |
|
15994 |
|
15995 pshared.r = ((pshared.ul * ((pshared.cUL & RED_MASK) >> 16) + |
|
15996 pshared.ll * ((pshared.cLL & RED_MASK) >> 16) + |
|
15997 pshared.ur * ((pshared.cUR & RED_MASK) >> 16) + |
|
15998 pshared.lr * ((pshared.cLR & RED_MASK) >> 16)) << PREC_RED_SHIFT) & RED_MASK; |
|
15999 pshared.g = ((pshared.ul * (pshared.cUL & GREEN_MASK) + |
|
16000 pshared.ll * (pshared.cLL & GREEN_MASK) + |
|
16001 pshared.ur * (pshared.cUR & GREEN_MASK) + |
|
16002 pshared.lr * (pshared.cLR & GREEN_MASK)) >>> PRECISIONB) & GREEN_MASK; |
|
16003 pshared.b = (pshared.ul * (pshared.cUL & BLUE_MASK) + |
|
16004 pshared.ll * (pshared.cLL & BLUE_MASK) + |
|
16005 pshared.ur * (pshared.cUR & BLUE_MASK) + |
|
16006 pshared.lr * (pshared.cLR & BLUE_MASK)) >>> PRECISIONB; |
|
16007 pshared.a = ((pshared.ul * ((pshared.cUL & ALPHA_MASK) >>> 24) + |
|
16008 pshared.ll * ((pshared.cLL & ALPHA_MASK) >>> 24) + |
|
16009 pshared.ur * ((pshared.cUR & ALPHA_MASK) >>> 24) + |
|
16010 pshared.lr * ((pshared.cLR & ALPHA_MASK) >>> 24)) << PREC_ALPHA_SHIFT) & ALPHA_MASK; |
|
16011 |
|
16012 blendedColor = blendFunc(destColor, (pshared.a | pshared.r | pshared.g | pshared.b)); |
|
16013 |
|
16014 destPixels[idx] = (blendedColor & RED_MASK) >>> 16; |
|
16015 destPixels[idx + 1] = (blendedColor & GREEN_MASK) >>> 8; |
|
16016 destPixels[idx + 2] = (blendedColor & BLUE_MASK); |
|
16017 destPixels[idx + 3] = (blendedColor & ALPHA_MASK) >>> 24; |
|
16018 |
|
16019 pshared.sX += dx; |
|
16020 } |
|
16021 destOffset += screenW; |
|
16022 pshared.srcYOffset += dy; |
|
16023 } |
|
16024 }; |
|
16025 |
|
16026 //////////////////////////////////////////////////////////////////////////// |
|
16027 // Font handling |
|
16028 //////////////////////////////////////////////////////////////////////////// |
|
16029 |
|
16030 /** |
|
16031 * loadFont() Loads a font into a variable of type PFont. |
|
16032 * |
|
16033 * @param {String} name filename of the font to load |
|
16034 * @param {int|float} size option font size (used internally) |
|
16035 * |
|
16036 * @returns {PFont} new PFont object |
|
16037 * |
|
16038 * @see #PFont |
|
16039 * @see #textFont |
|
16040 * @see #text |
|
16041 * @see #createFont |
|
16042 */ |
|
16043 p.loadFont = function(name, size) { |
|
16044 if (name === undef) { |
|
16045 throw("font name required in loadFont."); |
|
16046 } |
|
16047 if (name.indexOf(".svg") === -1) { |
|
16048 if (size === undef) { |
|
16049 size = curTextFont.size; |
|
16050 } |
|
16051 return PFont.get(name, size); |
|
16052 } |
|
16053 // If the font is a glyph, calculate by SVG table |
|
16054 var font = p.loadGlyphs(name); |
|
16055 |
|
16056 return { |
|
16057 name: name, |
|
16058 css: '12px sans-serif', |
|
16059 glyph: true, |
|
16060 units_per_em: font.units_per_em, |
|
16061 horiz_adv_x: 1 / font.units_per_em * font.horiz_adv_x, |
|
16062 ascent: font.ascent, |
|
16063 descent: font.descent, |
|
16064 width: function(str) { |
|
16065 var width = 0; |
|
16066 var len = str.length; |
|
16067 for (var i = 0; i < len; i++) { |
|
16068 try { |
|
16069 width += parseFloat(p.glyphLook(p.glyphTable[name], str[i]).horiz_adv_x); |
|
16070 } |
|
16071 catch(e) { |
|
16072 Processing.debug(e); |
|
16073 } |
|
16074 } |
|
16075 return width / p.glyphTable[name].units_per_em; |
|
16076 } |
|
16077 }; |
|
16078 }; |
|
16079 |
|
16080 /** |
|
16081 * createFont() Loads a font into a variable of type PFont. |
|
16082 * Smooth and charset are ignored in Processing.js. |
|
16083 * |
|
16084 * @param {String} name filename of the font to load |
|
16085 * @param {int|float} size font size in pixels |
|
16086 * @param {boolean} smooth not used in Processing.js |
|
16087 * @param {char[]} charset not used in Processing.js |
|
16088 * |
|
16089 * @returns {PFont} new PFont object |
|
16090 * |
|
16091 * @see #PFont |
|
16092 * @see #textFont |
|
16093 * @see #text |
|
16094 * @see #loadFont |
|
16095 */ |
|
16096 p.createFont = function(name, size) { |
|
16097 // because Processing.js only deals with real fonts, |
|
16098 // createFont is simply a wrapper for loadFont/2 |
|
16099 return p.loadFont(name, size); |
|
16100 }; |
|
16101 |
|
16102 /** |
|
16103 * textFont() Sets the current font. |
|
16104 * |
|
16105 * @param {PFont} pfont the PFont to load as current text font |
|
16106 * @param {int|float} size optional font size in pixels |
|
16107 * |
|
16108 * @see #createFont |
|
16109 * @see #loadFont |
|
16110 * @see #PFont |
|
16111 * @see #text |
|
16112 */ |
|
16113 p.textFont = function(pfont, size) { |
|
16114 if (size !== undef) { |
|
16115 // If we're using an SVG glyph font, don't load from cache |
|
16116 if (!pfont.glyph) { |
|
16117 pfont = PFont.get(pfont.name, size); |
|
16118 } |
|
16119 curTextSize = size; |
|
16120 } |
|
16121 curTextFont = pfont; |
|
16122 curFontName = curTextFont.name; |
|
16123 curTextAscent = curTextFont.ascent; |
|
16124 curTextDescent = curTextFont.descent; |
|
16125 curTextLeading = curTextFont.leading; |
|
16126 var curContext = drawing.$ensureContext(); |
|
16127 curContext.font = curTextFont.css; |
|
16128 }; |
|
16129 |
|
16130 /** |
|
16131 * textSize() Sets the current font size in pixels. |
|
16132 * |
|
16133 * @param {int|float} size font size in pixels |
|
16134 * |
|
16135 * @see #textFont |
|
16136 * @see #loadFont |
|
16137 * @see #PFont |
|
16138 * @see #text |
|
16139 */ |
|
16140 p.textSize = function(size) { |
|
16141 if (size !== curTextSize) { |
|
16142 curTextFont = PFont.get(curFontName, size); |
|
16143 curTextSize = size; |
|
16144 // recache metrics |
|
16145 curTextAscent = curTextFont.ascent; |
|
16146 curTextDescent = curTextFont.descent; |
|
16147 curTextLeading = curTextFont.leading; |
|
16148 var curContext = drawing.$ensureContext(); |
|
16149 curContext.font = curTextFont.css; |
|
16150 } |
|
16151 }; |
|
16152 |
|
16153 /** |
|
16154 * textAscent() returns the maximum height a character extends above the baseline of the |
|
16155 * current font at its current size, in pixels. |
|
16156 * |
|
16157 * @returns {float} height of the current font above the baseline, at its current size, in pixels |
|
16158 * |
|
16159 * @see #textDescent |
|
16160 */ |
|
16161 p.textAscent = function() { |
|
16162 return curTextAscent; |
|
16163 }; |
|
16164 |
|
16165 /** |
|
16166 * textDescent() returns the maximum depth a character will protrude below the baseline of |
|
16167 * the current font at its current size, in pixels. |
|
16168 * |
|
16169 * @returns {float} depth of the current font below the baseline, at its current size, in pixels |
|
16170 * |
|
16171 * @see #textAscent |
|
16172 */ |
|
16173 p.textDescent = function() { |
|
16174 return curTextDescent; |
|
16175 }; |
|
16176 |
|
16177 /** |
|
16178 * textLeading() Sets the current font's leading, which is the distance |
|
16179 * from baseline to baseline over consecutive lines, with additional vertical |
|
16180 * spacing taking into account. Usually this value is 1.2 or 1.25 times the |
|
16181 * textsize, but this value can be changed to effect vertically compressed |
|
16182 * or stretched text. |
|
16183 * |
|
16184 * @param {int|float} the desired baseline-to-baseline size in pixels |
|
16185 */ |
|
16186 p.textLeading = function(leading) { |
|
16187 curTextLeading = leading; |
|
16188 }; |
|
16189 |
|
16190 /** |
|
16191 * textAlign() Sets the current alignment for drawing text. |
|
16192 * |
|
16193 * @param {int} ALIGN Horizontal alignment, either LEFT, CENTER, or RIGHT |
|
16194 * @param {int} YALIGN optional vertical alignment, either TOP, BOTTOM, CENTER, or BASELINE |
|
16195 * |
|
16196 * @see #loadFont |
|
16197 * @see #PFont |
|
16198 * @see #text |
|
16199 */ |
|
16200 p.textAlign = function(xalign, yalign) { |
|
16201 horizontalTextAlignment = xalign; |
|
16202 verticalTextAlignment = yalign || PConstants.BASELINE; |
|
16203 }; |
|
16204 |
|
16205 /** |
|
16206 * toP5String converts things with arbitrary data type into |
|
16207 * string values, for text rendering. |
|
16208 * |
|
16209 * @param {any} any object that can be converted into a string |
|
16210 * |
|
16211 * @return {String} the string representation of the input |
|
16212 */ |
|
16213 function toP5String(obj) { |
|
16214 if(obj instanceof String) { |
|
16215 return obj; |
|
16216 } |
|
16217 if(typeof obj === 'number') { |
|
16218 // check if an int |
|
16219 if(obj === (0 | obj)) { |
|
16220 return obj.toString(); |
|
16221 } |
|
16222 return p.nf(obj, 0, 3); |
|
16223 } |
|
16224 if(obj === null || obj === undef) { |
|
16225 return ""; |
|
16226 } |
|
16227 return obj.toString(); |
|
16228 } |
|
16229 |
|
16230 /** |
|
16231 * textWidth() Calculates and returns the width of any character or text string in pixels. |
|
16232 * |
|
16233 * @param {char|String} str char or String to be measured |
|
16234 * |
|
16235 * @return {float} width of char or String in pixels |
|
16236 * |
|
16237 * @see #loadFont |
|
16238 * @see #PFont |
|
16239 * @see #text |
|
16240 * @see #textFont |
|
16241 */ |
|
16242 Drawing2D.prototype.textWidth = function(str) { |
|
16243 var lines = toP5String(str).split(/\r?\n/g), width = 0; |
|
16244 var i, linesCount = lines.length; |
|
16245 |
|
16246 curContext.font = curTextFont.css; |
|
16247 for (i = 0; i < linesCount; ++i) { |
|
16248 width = Math.max(width, curTextFont.measureTextWidth(lines[i])); |
|
16249 } |
|
16250 return width | 0; |
|
16251 }; |
|
16252 |
|
16253 Drawing3D.prototype.textWidth = function(str) { |
|
16254 var lines = toP5String(str).split(/\r?\n/g), width = 0; |
|
16255 var i, linesCount = lines.length; |
|
16256 if (textcanvas === undef) { |
|
16257 textcanvas = document.createElement("canvas"); |
|
16258 } |
|
16259 |
|
16260 var textContext = textcanvas.getContext("2d"); |
|
16261 textContext.font = curTextFont.css; |
|
16262 |
|
16263 for (i = 0; i < linesCount; ++i) { |
|
16264 width = Math.max(width, textContext.measureText(lines[i]).width); |
|
16265 } |
|
16266 return width | 0; |
|
16267 }; |
|
16268 |
|
16269 // A lookup table for characters that can not be referenced by Object |
|
16270 p.glyphLook = function(font, chr) { |
|
16271 try { |
|
16272 switch (chr) { |
|
16273 case "1": |
|
16274 return font.one; |
|
16275 case "2": |
|
16276 return font.two; |
|
16277 case "3": |
|
16278 return font.three; |
|
16279 case "4": |
|
16280 return font.four; |
|
16281 case "5": |
|
16282 return font.five; |
|
16283 case "6": |
|
16284 return font.six; |
|
16285 case "7": |
|
16286 return font.seven; |
|
16287 case "8": |
|
16288 return font.eight; |
|
16289 case "9": |
|
16290 return font.nine; |
|
16291 case "0": |
|
16292 return font.zero; |
|
16293 case " ": |
|
16294 return font.space; |
|
16295 case "$": |
|
16296 return font.dollar; |
|
16297 case "!": |
|
16298 return font.exclam; |
|
16299 case '"': |
|
16300 return font.quotedbl; |
|
16301 case "#": |
|
16302 return font.numbersign; |
|
16303 case "%": |
|
16304 return font.percent; |
|
16305 case "&": |
|
16306 return font.ampersand; |
|
16307 case "'": |
|
16308 return font.quotesingle; |
|
16309 case "(": |
|
16310 return font.parenleft; |
|
16311 case ")": |
|
16312 return font.parenright; |
|
16313 case "*": |
|
16314 return font.asterisk; |
|
16315 case "+": |
|
16316 return font.plus; |
|
16317 case ",": |
|
16318 return font.comma; |
|
16319 case "-": |
|
16320 return font.hyphen; |
|
16321 case ".": |
|
16322 return font.period; |
|
16323 case "/": |
|
16324 return font.slash; |
|
16325 case "_": |
|
16326 return font.underscore; |
|
16327 case ":": |
|
16328 return font.colon; |
|
16329 case ";": |
|
16330 return font.semicolon; |
|
16331 case "<": |
|
16332 return font.less; |
|
16333 case "=": |
|
16334 return font.equal; |
|
16335 case ">": |
|
16336 return font.greater; |
|
16337 case "?": |
|
16338 return font.question; |
|
16339 case "@": |
|
16340 return font.at; |
|
16341 case "[": |
|
16342 return font.bracketleft; |
|
16343 case "\\": |
|
16344 return font.backslash; |
|
16345 case "]": |
|
16346 return font.bracketright; |
|
16347 case "^": |
|
16348 return font.asciicircum; |
|
16349 case "`": |
|
16350 return font.grave; |
|
16351 case "{": |
|
16352 return font.braceleft; |
|
16353 case "|": |
|
16354 return font.bar; |
|
16355 case "}": |
|
16356 return font.braceright; |
|
16357 case "~": |
|
16358 return font.asciitilde; |
|
16359 // If the character is not 'special', access it by object reference |
|
16360 default: |
|
16361 return font[chr]; |
|
16362 } |
|
16363 } catch(e) { |
|
16364 Processing.debug(e); |
|
16365 } |
|
16366 }; |
|
16367 |
|
16368 // Print some text to the Canvas |
|
16369 Drawing2D.prototype.text$line = function(str, x, y, z, align) { |
|
16370 var textWidth = 0, xOffset = 0; |
|
16371 // If the font is a standard Canvas font... |
|
16372 if (!curTextFont.glyph) { |
|
16373 if (str && ("fillText" in curContext)) { |
|
16374 if (isFillDirty) { |
|
16375 curContext.fillStyle = p.color.toString(currentFillColor); |
|
16376 isFillDirty = false; |
|
16377 } |
|
16378 |
|
16379 // horizontal offset/alignment |
|
16380 if(align === PConstants.RIGHT || align === PConstants.CENTER) { |
|
16381 textWidth = curTextFont.measureTextWidth(str); |
|
16382 |
|
16383 if(align === PConstants.RIGHT) { |
|
16384 xOffset = -textWidth; |
|
16385 } else { // if(align === PConstants.CENTER) |
|
16386 xOffset = -textWidth/2; |
|
16387 } |
|
16388 } |
|
16389 |
|
16390 curContext.fillText(str, x+xOffset, y); |
|
16391 } |
|
16392 } else { |
|
16393 // If the font is a Batik SVG font... |
|
16394 var font = p.glyphTable[curFontName]; |
|
16395 saveContext(); |
|
16396 curContext.translate(x, y + curTextSize); |
|
16397 |
|
16398 // horizontal offset/alignment |
|
16399 if(align === PConstants.RIGHT || align === PConstants.CENTER) { |
|
16400 textWidth = font.width(str); |
|
16401 |
|
16402 if(align === PConstants.RIGHT) { |
|
16403 xOffset = -textWidth; |
|
16404 } else { // if(align === PConstants.CENTER) |
|
16405 xOffset = -textWidth/2; |
|
16406 } |
|
16407 } |
|
16408 |
|
16409 var upem = font.units_per_em, |
|
16410 newScale = 1 / upem * curTextSize; |
|
16411 |
|
16412 curContext.scale(newScale, newScale); |
|
16413 |
|
16414 for (var i=0, len=str.length; i < len; i++) { |
|
16415 // Test character against glyph table |
|
16416 try { |
|
16417 p.glyphLook(font, str[i]).draw(); |
|
16418 } catch(e) { |
|
16419 Processing.debug(e); |
|
16420 } |
|
16421 } |
|
16422 restoreContext(); |
|
16423 } |
|
16424 }; |
|
16425 |
|
16426 Drawing3D.prototype.text$line = function(str, x, y, z, align) { |
|
16427 // handle case for 3d text |
|
16428 if (textcanvas === undef) { |
|
16429 textcanvas = document.createElement("canvas"); |
|
16430 } |
|
16431 var oldContext = curContext; |
|
16432 curContext = textcanvas.getContext("2d"); |
|
16433 curContext.font = curTextFont.css; |
|
16434 var textWidth = curTextFont.measureTextWidth(str); |
|
16435 textcanvas.width = textWidth; |
|
16436 textcanvas.height = curTextSize; |
|
16437 curContext = textcanvas.getContext("2d"); // refreshes curContext |
|
16438 curContext.font = curTextFont.css; |
|
16439 curContext.textBaseline="top"; |
|
16440 |
|
16441 // paint on 2D canvas |
|
16442 Drawing2D.prototype.text$line(str,0,0,0,PConstants.LEFT); |
|
16443 |
|
16444 // use it as a texture |
|
16445 var aspect = textcanvas.width/textcanvas.height; |
|
16446 curContext = oldContext; |
|
16447 |
|
16448 curContext.bindTexture(curContext.TEXTURE_2D, textTex); |
|
16449 curContext.texImage2D(curContext.TEXTURE_2D, 0, curContext.RGBA, curContext.RGBA, curContext.UNSIGNED_BYTE, textcanvas); |
|
16450 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MAG_FILTER, curContext.LINEAR); |
|
16451 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_MIN_FILTER, curContext.LINEAR); |
|
16452 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_T, curContext.CLAMP_TO_EDGE); |
|
16453 curContext.texParameteri(curContext.TEXTURE_2D, curContext.TEXTURE_WRAP_S, curContext.CLAMP_TO_EDGE); |
|
16454 // If we don't have a power of two texture, we can't mipmap it. |
|
16455 // curContext.generateMipmap(curContext.TEXTURE_2D); |
|
16456 |
|
16457 // horizontal offset/alignment |
|
16458 var xOffset = 0; |
|
16459 if (align === PConstants.RIGHT) { |
|
16460 xOffset = -textWidth; |
|
16461 } else if(align === PConstants.CENTER) { |
|
16462 xOffset = -textWidth/2; |
|
16463 } |
|
16464 var model = new PMatrix3D(); |
|
16465 var scalefactor = curTextSize * 0.5; |
|
16466 model.translate(x+xOffset-scalefactor/2, y-scalefactor, z); |
|
16467 model.scale(-aspect*scalefactor, -scalefactor, scalefactor); |
|
16468 model.translate(-1, -1, -1); |
|
16469 model.transpose(); |
|
16470 |
|
16471 var view = new PMatrix3D(); |
|
16472 view.scale(1, -1, 1); |
|
16473 view.apply(modelView.array()); |
|
16474 view.transpose(); |
|
16475 |
|
16476 curContext.useProgram(programObject2D); |
|
16477 vertexAttribPointer("vertex2d", programObject2D, "Vertex", 3, textBuffer); |
|
16478 vertexAttribPointer("aTextureCoord2d", programObject2D, "aTextureCoord", 2, textureBuffer); |
|
16479 uniformi("uSampler2d", programObject2D, "uSampler", [0]); |
|
16480 uniformi("picktype2d", programObject2D, "picktype", 1); |
|
16481 uniformMatrix("model2d", programObject2D, "model", false, model.array()); |
|
16482 uniformMatrix("view2d", programObject2D, "view", false, view.array()); |
|
16483 uniformf("color2d", programObject2D, "color", fillStyle); |
|
16484 curContext.bindBuffer(curContext.ELEMENT_ARRAY_BUFFER, indexBuffer); |
|
16485 curContext.drawElements(curContext.TRIANGLES, 6, curContext.UNSIGNED_SHORT, 0); |
|
16486 }; |
|
16487 |
|
16488 |
|
16489 /** |
|
16490 * unbounded text function (z is an optional argument) |
|
16491 */ |
|
16492 function text$4(str, x, y, z) { |
|
16493 var lines, linesCount; |
|
16494 if(str.indexOf('\n') < 0) { |
|
16495 lines = [str]; |
|
16496 linesCount = 1; |
|
16497 } else { |
|
16498 lines = str.split(/\r?\n/g); |
|
16499 linesCount = lines.length; |
|
16500 } |
|
16501 // handle text line-by-line |
|
16502 |
|
16503 var yOffset = 0; |
|
16504 if(verticalTextAlignment === PConstants.TOP) { |
|
16505 yOffset = curTextAscent + curTextDescent; |
|
16506 } else if(verticalTextAlignment === PConstants.CENTER) { |
|
16507 yOffset = curTextAscent/2 - (linesCount-1)*curTextLeading/2; |
|
16508 } else if(verticalTextAlignment === PConstants.BOTTOM) { |
|
16509 yOffset = -(curTextDescent + (linesCount-1)*curTextLeading); |
|
16510 } |
|
16511 |
|
16512 for(var i=0;i<linesCount;++i) { |
|
16513 var line = lines[i]; |
|
16514 drawing.text$line(line, x, y + yOffset, z, horizontalTextAlignment); |
|
16515 yOffset += curTextLeading; |
|
16516 } |
|
16517 } |
|
16518 |
|
16519 |
|
16520 /** |
|
16521 * box-bounded text function (z is an optional argument) |
|
16522 */ |
|
16523 function text$6(str, x, y, width, height, z) { |
|
16524 // 'fail' on 0-valued dimensions |
|
16525 if (str.length === 0 || width === 0 || height === 0) { |
|
16526 return; |
|
16527 } |
|
16528 // also 'fail' if the text height is larger than the bounding height |
|
16529 if(curTextSize > height) { |
|
16530 return; |
|
16531 } |
|
16532 |
|
16533 var spaceMark = -1; |
|
16534 var start = 0; |
|
16535 var lineWidth = 0; |
|
16536 var drawCommands = []; |
|
16537 |
|
16538 // run through text, character-by-character |
|
16539 for (var charPos=0, len=str.length; charPos < len; charPos++) |
|
16540 { |
|
16541 var currentChar = str[charPos]; |
|
16542 var spaceChar = (currentChar === " "); |
|
16543 var letterWidth = curTextFont.measureTextWidth(currentChar); |
|
16544 |
|
16545 // if we aren't looking at a newline, and the text still fits, keep processing |
|
16546 if (currentChar !== "\n" && (lineWidth + letterWidth <= width)) { |
|
16547 if (spaceChar) { spaceMark = charPos; } |
|
16548 lineWidth += letterWidth; |
|
16549 } |
|
16550 |
|
16551 // if we're looking at a newline, or the text no longer fits, push the section that fit into the drawcommand list |
|
16552 else |
|
16553 { |
|
16554 if (spaceMark + 1 === start) { |
|
16555 if(charPos>0) { |
|
16556 // Whole line without spaces so far. |
|
16557 spaceMark = charPos; |
|
16558 } else { |
|
16559 // 'fail', because the line can't even fit the first character |
|
16560 return; |
|
16561 } |
|
16562 } |
|
16563 |
|
16564 if (currentChar === "\n") { |
|
16565 drawCommands.push({text:str.substring(start, charPos), width: lineWidth}); |
|
16566 start = charPos + 1; |
|
16567 } else { |
|
16568 // current is not a newline, which means the line doesn't fit in box. push text. |
|
16569 // In Processing 1.5.1, the space is also pushed, so we push up to spaceMark+1, |
|
16570 // rather than up to spaceMark, as was the case for Processing 1.5 and earlier. |
|
16571 drawCommands.push({text:str.substring(start, spaceMark+1), width: lineWidth}); |
|
16572 start = spaceMark + 1; |
|
16573 } |
|
16574 |
|
16575 // newline + return |
|
16576 lineWidth = 0; |
|
16577 charPos = start - 1; |
|
16578 } |
|
16579 } |
|
16580 |
|
16581 // push the remaining text |
|
16582 if (start < len) { |
|
16583 drawCommands.push({text:str.substring(start), width: lineWidth}); |
|
16584 } |
|
16585 |
|
16586 // resolve horizontal alignment |
|
16587 var xOffset = 1, |
|
16588 yOffset = curTextAscent; |
|
16589 if (horizontalTextAlignment === PConstants.CENTER) { |
|
16590 xOffset = width/2; |
|
16591 } else if (horizontalTextAlignment === PConstants.RIGHT) { |
|
16592 xOffset = width; |
|
16593 } |
|
16594 |
|
16595 // resolve vertical alignment |
|
16596 var linesCount = drawCommands.length, |
|
16597 visibleLines = Math.min(linesCount, Math.floor(height/curTextLeading)); |
|
16598 if(verticalTextAlignment === PConstants.TOP) { |
|
16599 yOffset = curTextAscent + curTextDescent; |
|
16600 } else if(verticalTextAlignment === PConstants.CENTER) { |
|
16601 yOffset = (height/2) - curTextLeading * (visibleLines/2 - 1); |
|
16602 } else if(verticalTextAlignment === PConstants.BOTTOM) { |
|
16603 yOffset = curTextDescent + curTextLeading; |
|
16604 } |
|
16605 |
|
16606 var command, |
|
16607 drawCommand, |
|
16608 leading; |
|
16609 for (command = 0; command < linesCount; command++) { |
|
16610 leading = command * curTextLeading; |
|
16611 // stop if not enough space for one more line draw |
|
16612 if (yOffset + leading > height - curTextDescent) { |
|
16613 break; |
|
16614 } |
|
16615 drawCommand = drawCommands[command]; |
|
16616 drawing.text$line(drawCommand.text, x + xOffset, y + yOffset + leading, z, horizontalTextAlignment); |
|
16617 } |
|
16618 } |
|
16619 |
|
16620 /** |
|
16621 * text() Draws text to the screen. |
|
16622 * |
|
16623 * @param {String|char|int|float} data the alphanumeric symbols to be displayed |
|
16624 * @param {int|float} x x-coordinate of text |
|
16625 * @param {int|float} y y-coordinate of text |
|
16626 * @param {int|float} z optional z-coordinate of text |
|
16627 * @param {String} stringdata optional letters to be displayed |
|
16628 * @param {int|float} width optional width of text box |
|
16629 * @param {int|float} height optional height of text box |
|
16630 * |
|
16631 * @see #textAlign |
|
16632 * @see #textMode |
|
16633 * @see #loadFont |
|
16634 * @see #PFont |
|
16635 * @see #textFont |
|
16636 */ |
|
16637 p.text = function() { |
|
16638 if (textMode === PConstants.SHAPE) { |
|
16639 // TODO: requires beginRaw function |
|
16640 return; |
|
16641 } |
|
16642 if (arguments.length === 3) { // for text( str, x, y) |
|
16643 text$4(toP5String(arguments[0]), arguments[1], arguments[2], 0); |
|
16644 } else if (arguments.length === 4) { // for text( str, x, y, z) |
|
16645 text$4(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3]); |
|
16646 } else if (arguments.length === 5) { // for text( str, x, y , width, height) |
|
16647 text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], 0); |
|
16648 } else if (arguments.length === 6) { // for text( stringdata, x, y , width, height, z) |
|
16649 text$6(toP5String(arguments[0]), arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); |
|
16650 } |
|
16651 }; |
|
16652 |
|
16653 /** |
|
16654 * Sets the way text draws to the screen. In the default configuration (the MODEL mode), it's possible to rotate, |
|
16655 * scale, and place letters in two and three dimensional space. <br /><br /> Changing to SCREEN mode draws letters |
|
16656 * directly to the front of the window and greatly increases rendering quality and speed when used with the P2D and |
|
16657 * P3D renderers. textMode(SCREEN) with OPENGL and JAVA2D (the default) renderers will generally be slower, though |
|
16658 * pixel accurate with P2D and P3D. With textMode(SCREEN), the letters draw at the actual size of the font (in pixels) |
|
16659 * and therefore calls to <b>textSize()</b> will not affect the size of the letters. To create a font at the size you |
|
16660 * desire, use the "Create font..." option in the Tools menu, or use the createFont() function. When using textMode(SCREEN), |
|
16661 * any z-coordinate passed to a text() command will be ignored, because your computer screen is...flat! |
|
16662 * |
|
16663 * @param {int} MODE Either MODEL, SCREEN or SHAPE (not yet supported) |
|
16664 * |
|
16665 * @see loadFont |
|
16666 * @see PFont |
|
16667 * @see text |
|
16668 * @see textFont |
|
16669 * @see createFont |
|
16670 */ |
|
16671 p.textMode = function(mode){ |
|
16672 textMode = mode; |
|
16673 }; |
|
16674 |
|
16675 // Load Batik SVG Fonts and parse to pre-def objects for quick rendering |
|
16676 p.loadGlyphs = function(url) { |
|
16677 var x, y, cx, cy, nx, ny, d, a, lastCom, lenC, horiz_adv_x, getXY = '[0-9\\-]+', path; |
|
16678 |
|
16679 // Return arrays of SVG commands and coords |
|
16680 // get this to use p.matchAll() - will need to work around the lack of null return |
|
16681 var regex = function(needle, hay) { |
|
16682 var i = 0, |
|
16683 results = [], |
|
16684 latest, regexp = new RegExp(needle, "g"); |
|
16685 latest = results[i] = regexp.exec(hay); |
|
16686 while (latest) { |
|
16687 i++; |
|
16688 latest = results[i] = regexp.exec(hay); |
|
16689 } |
|
16690 return results; |
|
16691 }; |
|
16692 |
|
16693 var buildPath = function(d) { |
|
16694 var c = regex("[A-Za-z][0-9\\- ]+|Z", d); |
|
16695 var beforePathDraw = function() { |
|
16696 saveContext(); |
|
16697 return drawing.$ensureContext(); |
|
16698 }; |
|
16699 var afterPathDraw = function() { |
|
16700 executeContextFill(); |
|
16701 executeContextStroke(); |
|
16702 restoreContext(); |
|
16703 }; |
|
16704 |
|
16705 // Begin storing path object |
|
16706 path = "return {draw:function(){var curContext=beforePathDraw();curContext.beginPath();"; |
|
16707 |
|
16708 x = 0; |
|
16709 y = 0; |
|
16710 cx = 0; |
|
16711 cy = 0; |
|
16712 nx = 0; |
|
16713 ny = 0; |
|
16714 d = 0; |
|
16715 a = 0; |
|
16716 lastCom = ""; |
|
16717 lenC = c.length - 1; |
|
16718 |
|
16719 // Loop through SVG commands translating to canvas eqivs functions in path object |
|
16720 for (var j = 0; j < lenC; j++) { |
|
16721 var com = c[j][0], xy = regex(getXY, com); |
|
16722 |
|
16723 switch (com[0]) { |
|
16724 case "M": |
|
16725 //curContext.moveTo(x,-y); |
|
16726 x = parseFloat(xy[0][0]); |
|
16727 y = parseFloat(xy[1][0]); |
|
16728 path += "curContext.moveTo(" + x + "," + (-y) + ");"; |
|
16729 break; |
|
16730 |
|
16731 case "L": |
|
16732 //curContext.lineTo(x,-y); |
|
16733 x = parseFloat(xy[0][0]); |
|
16734 y = parseFloat(xy[1][0]); |
|
16735 path += "curContext.lineTo(" + x + "," + (-y) + ");"; |
|
16736 break; |
|
16737 |
|
16738 case "H": |
|
16739 //curContext.lineTo(x,-y) |
|
16740 x = parseFloat(xy[0][0]); |
|
16741 path += "curContext.lineTo(" + x + "," + (-y) + ");"; |
|
16742 break; |
|
16743 |
|
16744 case "V": |
|
16745 //curContext.lineTo(x,-y); |
|
16746 y = parseFloat(xy[0][0]); |
|
16747 path += "curContext.lineTo(" + x + "," + (-y) + ");"; |
|
16748 break; |
|
16749 |
|
16750 case "T": |
|
16751 //curContext.quadraticCurveTo(cx,-cy,nx,-ny); |
|
16752 nx = parseFloat(xy[0][0]); |
|
16753 ny = parseFloat(xy[1][0]); |
|
16754 |
|
16755 if (lastCom === "Q" || lastCom === "T") { |
|
16756 d = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(cy - y, 2)); |
|
16757 a = Math.PI + Math.atan2(cx - x, cy - y); |
|
16758 cx = x + (Math.sin(a) * (d)); |
|
16759 cy = y + (Math.cos(a) * (d)); |
|
16760 } else { |
|
16761 cx = x; |
|
16762 cy = y; |
|
16763 } |
|
16764 |
|
16765 path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");"; |
|
16766 x = nx; |
|
16767 y = ny; |
|
16768 break; |
|
16769 |
|
16770 case "Q": |
|
16771 //curContext.quadraticCurveTo(cx,-cy,nx,-ny); |
|
16772 cx = parseFloat(xy[0][0]); |
|
16773 cy = parseFloat(xy[1][0]); |
|
16774 nx = parseFloat(xy[2][0]); |
|
16775 ny = parseFloat(xy[3][0]); |
|
16776 path += "curContext.quadraticCurveTo(" + cx + "," + (-cy) + "," + nx + "," + (-ny) + ");"; |
|
16777 x = nx; |
|
16778 y = ny; |
|
16779 break; |
|
16780 |
|
16781 case "Z": |
|
16782 //curContext.closePath(); |
|
16783 path += "curContext.closePath();"; |
|
16784 break; |
|
16785 } |
|
16786 lastCom = com[0]; |
|
16787 } |
|
16788 |
|
16789 path += "afterPathDraw();"; |
|
16790 path += "curContext.translate(" + horiz_adv_x + ",0);"; |
|
16791 path += "}}"; |
|
16792 |
|
16793 return ((new Function("beforePathDraw", "afterPathDraw", path))(beforePathDraw, afterPathDraw)); |
|
16794 }; |
|
16795 |
|
16796 // Parse SVG font-file into block of Canvas commands |
|
16797 var parseSVGFont = function(svg) { |
|
16798 // Store font attributes |
|
16799 var font = svg.getElementsByTagName("font"); |
|
16800 p.glyphTable[url].horiz_adv_x = font[0].getAttribute("horiz-adv-x"); |
|
16801 |
|
16802 var font_face = svg.getElementsByTagName("font-face")[0]; |
|
16803 p.glyphTable[url].units_per_em = parseFloat(font_face.getAttribute("units-per-em")); |
|
16804 p.glyphTable[url].ascent = parseFloat(font_face.getAttribute("ascent")); |
|
16805 p.glyphTable[url].descent = parseFloat(font_face.getAttribute("descent")); |
|
16806 |
|
16807 var glyph = svg.getElementsByTagName("glyph"), |
|
16808 len = glyph.length; |
|
16809 |
|
16810 // Loop through each glyph in the SVG |
|
16811 for (var i = 0; i < len; i++) { |
|
16812 // Store attributes for this glyph |
|
16813 var unicode = glyph[i].getAttribute("unicode"); |
|
16814 var name = glyph[i].getAttribute("glyph-name"); |
|
16815 horiz_adv_x = glyph[i].getAttribute("horiz-adv-x"); |
|
16816 if (horiz_adv_x === null) { |
|
16817 horiz_adv_x = p.glyphTable[url].horiz_adv_x; |
|
16818 } |
|
16819 d = glyph[i].getAttribute("d"); |
|
16820 // Split path commands in glpyh |
|
16821 if (d !== undef) { |
|
16822 path = buildPath(d); |
|
16823 // Store glyph data to table object |
|
16824 p.glyphTable[url][name] = { |
|
16825 name: name, |
|
16826 unicode: unicode, |
|
16827 horiz_adv_x: horiz_adv_x, |
|
16828 draw: path.draw |
|
16829 }; |
|
16830 } |
|
16831 } // finished adding glyphs to table |
|
16832 }; |
|
16833 |
|
16834 // Load and parse Batik SVG font as XML into a Processing Glyph object |
|
16835 var loadXML = function() { |
|
16836 var xmlDoc; |
|
16837 |
|
16838 try { |
|
16839 xmlDoc = document.implementation.createDocument("", "", null); |
|
16840 } |
|
16841 catch(e_fx_op) { |
|
16842 Processing.debug(e_fx_op.message); |
|
16843 return; |
|
16844 } |
|
16845 |
|
16846 try { |
|
16847 xmlDoc.async = false; |
|
16848 xmlDoc.load(url); |
|
16849 parseSVGFont(xmlDoc.getElementsByTagName("svg")[0]); |
|
16850 } |
|
16851 catch(e_sf_ch) { |
|
16852 // Google Chrome, Safari etc. |
|
16853 Processing.debug(e_sf_ch); |
|
16854 try { |
|
16855 var xmlhttp = new window.XMLHttpRequest(); |
|
16856 xmlhttp.open("GET", url, false); |
|
16857 xmlhttp.send(null); |
|
16858 parseSVGFont(xmlhttp.responseXML.documentElement); |
|
16859 } |
|
16860 catch(e) { |
|
16861 Processing.debug(e_sf_ch); |
|
16862 } |
|
16863 } |
|
16864 }; |
|
16865 |
|
16866 // Create a new object in glyphTable to store this font |
|
16867 p.glyphTable[url] = {}; |
|
16868 |
|
16869 // Begin loading the Batik SVG font... |
|
16870 loadXML(url); |
|
16871 |
|
16872 // Return the loaded font for attribute grabbing |
|
16873 return p.glyphTable[url]; |
|
16874 }; |
|
16875 |
|
16876 /** |
|
16877 * Gets the sketch parameter value. The parameter can be defined as the canvas attribute with |
|
16878 * the "data-processing-" prefix or provided in the pjs directive (e.g. param-test="52"). |
|
16879 * The function tries the canvas attributes, then the pjs directive content. |
|
16880 * |
|
16881 * @param {String} name The name of the param to read. |
|
16882 * |
|
16883 * @returns {String} The parameter value, or null if parameter is not defined. |
|
16884 */ |
|
16885 p.param = function(name) { |
|
16886 // trying attribute that was specified in CANVAS |
|
16887 var attributeName = "data-processing-" + name; |
|
16888 if (curElement.hasAttribute(attributeName)) { |
|
16889 return curElement.getAttribute(attributeName); |
|
16890 } |
|
16891 // trying child PARAM elements of the CANVAS |
|
16892 for (var i = 0, len = curElement.childNodes.length; i < len; ++i) { |
|
16893 var item = curElement.childNodes.item(i); |
|
16894 if (item.nodeType !== 1 || item.tagName.toLowerCase() !== "param") { |
|
16895 continue; |
|
16896 } |
|
16897 if (item.getAttribute("name") === name) { |
|
16898 return item.getAttribute("value"); |
|
16899 } |
|
16900 } |
|
16901 // fallback to default params |
|
16902 if (curSketch.params.hasOwnProperty(name)) { |
|
16903 return curSketch.params[name]; |
|
16904 } |
|
16905 return null; |
|
16906 }; |
|
16907 |
|
16908 //////////////////////////////////////////////////////////////////////////// |
|
16909 // 2D/3D methods wiring utils |
|
16910 //////////////////////////////////////////////////////////////////////////// |
|
16911 function wireDimensionalFunctions(mode) { |
|
16912 // Drawing2D/Drawing3D |
|
16913 if (mode === '3D') { |
|
16914 drawing = new Drawing3D(); |
|
16915 } else if (mode === '2D') { |
|
16916 drawing = new Drawing2D(); |
|
16917 } else { |
|
16918 drawing = new DrawingPre(); |
|
16919 } |
|
16920 |
|
16921 // Wire up functions (Use DrawingPre properties names) |
|
16922 for (var i in DrawingPre.prototype) { |
|
16923 if (DrawingPre.prototype.hasOwnProperty(i) && i.indexOf("$") < 0) { |
|
16924 p[i] = drawing[i]; |
|
16925 } |
|
16926 } |
|
16927 |
|
16928 // Run initialization |
|
16929 drawing.$init(); |
|
16930 } |
|
16931 |
|
16932 function createDrawingPreFunction(name) { |
|
16933 return function() { |
|
16934 wireDimensionalFunctions("2D"); |
|
16935 return drawing[name].apply(this, arguments); |
|
16936 }; |
|
16937 } |
|
16938 DrawingPre.prototype.translate = createDrawingPreFunction("translate"); |
|
16939 DrawingPre.prototype.scale = createDrawingPreFunction("scale"); |
|
16940 DrawingPre.prototype.pushMatrix = createDrawingPreFunction("pushMatrix"); |
|
16941 DrawingPre.prototype.popMatrix = createDrawingPreFunction("popMatrix"); |
|
16942 DrawingPre.prototype.resetMatrix = createDrawingPreFunction("resetMatrix"); |
|
16943 DrawingPre.prototype.applyMatrix = createDrawingPreFunction("applyMatrix"); |
|
16944 DrawingPre.prototype.rotate = createDrawingPreFunction("rotate"); |
|
16945 DrawingPre.prototype.rotateZ = createDrawingPreFunction("rotateZ"); |
|
16946 DrawingPre.prototype.redraw = createDrawingPreFunction("redraw"); |
|
16947 DrawingPre.prototype.toImageData = createDrawingPreFunction("toImageData"); |
|
16948 DrawingPre.prototype.ambientLight = createDrawingPreFunction("ambientLight"); |
|
16949 DrawingPre.prototype.directionalLight = createDrawingPreFunction("directionalLight"); |
|
16950 DrawingPre.prototype.lightFalloff = createDrawingPreFunction("lightFalloff"); |
|
16951 DrawingPre.prototype.lightSpecular = createDrawingPreFunction("lightSpecular"); |
|
16952 DrawingPre.prototype.pointLight = createDrawingPreFunction("pointLight"); |
|
16953 DrawingPre.prototype.noLights = createDrawingPreFunction("noLights"); |
|
16954 DrawingPre.prototype.spotLight = createDrawingPreFunction("spotLight"); |
|
16955 DrawingPre.prototype.beginCamera = createDrawingPreFunction("beginCamera"); |
|
16956 DrawingPre.prototype.endCamera = createDrawingPreFunction("endCamera"); |
|
16957 DrawingPre.prototype.frustum = createDrawingPreFunction("frustum"); |
|
16958 DrawingPre.prototype.box = createDrawingPreFunction("box"); |
|
16959 DrawingPre.prototype.sphere = createDrawingPreFunction("sphere"); |
|
16960 DrawingPre.prototype.ambient = createDrawingPreFunction("ambient"); |
|
16961 DrawingPre.prototype.emissive = createDrawingPreFunction("emissive"); |
|
16962 DrawingPre.prototype.shininess = createDrawingPreFunction("shininess"); |
|
16963 DrawingPre.prototype.specular = createDrawingPreFunction("specular"); |
|
16964 DrawingPre.prototype.fill = createDrawingPreFunction("fill"); |
|
16965 DrawingPre.prototype.stroke = createDrawingPreFunction("stroke"); |
|
16966 DrawingPre.prototype.strokeWeight = createDrawingPreFunction("strokeWeight"); |
|
16967 DrawingPre.prototype.smooth = createDrawingPreFunction("smooth"); |
|
16968 DrawingPre.prototype.noSmooth = createDrawingPreFunction("noSmooth"); |
|
16969 DrawingPre.prototype.point = createDrawingPreFunction("point"); |
|
16970 DrawingPre.prototype.vertex = createDrawingPreFunction("vertex"); |
|
16971 DrawingPre.prototype.endShape = createDrawingPreFunction("endShape"); |
|
16972 DrawingPre.prototype.bezierVertex = createDrawingPreFunction("bezierVertex"); |
|
16973 DrawingPre.prototype.curveVertex = createDrawingPreFunction("curveVertex"); |
|
16974 DrawingPre.prototype.curve = createDrawingPreFunction("curve"); |
|
16975 DrawingPre.prototype.line = createDrawingPreFunction("line"); |
|
16976 DrawingPre.prototype.bezier = createDrawingPreFunction("bezier"); |
|
16977 DrawingPre.prototype.rect = createDrawingPreFunction("rect"); |
|
16978 DrawingPre.prototype.ellipse = createDrawingPreFunction("ellipse"); |
|
16979 DrawingPre.prototype.background = createDrawingPreFunction("background"); |
|
16980 DrawingPre.prototype.image = createDrawingPreFunction("image"); |
|
16981 DrawingPre.prototype.textWidth = createDrawingPreFunction("textWidth"); |
|
16982 DrawingPre.prototype.text$line = createDrawingPreFunction("text$line"); |
|
16983 DrawingPre.prototype.$ensureContext = createDrawingPreFunction("$ensureContext"); |
|
16984 DrawingPre.prototype.$newPMatrix = createDrawingPreFunction("$newPMatrix"); |
|
16985 |
|
16986 DrawingPre.prototype.size = function(aWidth, aHeight, aMode) { |
|
16987 wireDimensionalFunctions(aMode === PConstants.WEBGL ? "3D" : "2D"); |
|
16988 p.size(aWidth, aHeight, aMode); |
|
16989 }; |
|
16990 |
|
16991 DrawingPre.prototype.$init = nop; |
|
16992 |
|
16993 Drawing2D.prototype.$init = function() { |
|
16994 // Setup default 2d canvas context. |
|
16995 // Moving this here removes the number of times we need to check the 3D variable |
|
16996 p.size(p.width, p.height); |
|
16997 |
|
16998 curContext.lineCap = 'round'; |
|
16999 |
|
17000 // Set default stroke and fill color |
|
17001 p.noSmooth(); |
|
17002 p.disableContextMenu(); |
|
17003 }; |
|
17004 Drawing3D.prototype.$init = function() { |
|
17005 // For ref/perf test compatibility until those are fixed |
|
17006 p.use3DContext = true; |
|
17007 }; |
|
17008 |
|
17009 DrawingShared.prototype.$ensureContext = function() { |
|
17010 return curContext; |
|
17011 }; |
|
17012 |
|
17013 ////////////////////////////////////////////////////////////////////////// |
|
17014 // Touch and Mouse event handling |
|
17015 ////////////////////////////////////////////////////////////////////////// |
|
17016 |
|
17017 function calculateOffset(curElement, event) { |
|
17018 var element = curElement, |
|
17019 offsetX = 0, |
|
17020 offsetY = 0; |
|
17021 |
|
17022 p.pmouseX = p.mouseX; |
|
17023 p.pmouseY = p.mouseY; |
|
17024 |
|
17025 // Find element offset |
|
17026 if (element.offsetParent) { |
|
17027 do { |
|
17028 offsetX += element.offsetLeft; |
|
17029 offsetY += element.offsetTop; |
|
17030 } while (!!(element = element.offsetParent)); |
|
17031 } |
|
17032 |
|
17033 // Find Scroll offset |
|
17034 element = curElement; |
|
17035 do { |
|
17036 offsetX -= element.scrollLeft || 0; |
|
17037 offsetY -= element.scrollTop || 0; |
|
17038 } while (!!(element = element.parentNode)); |
|
17039 |
|
17040 // Add padding and border style widths to offset |
|
17041 offsetX += stylePaddingLeft; |
|
17042 offsetY += stylePaddingTop; |
|
17043 |
|
17044 offsetX += styleBorderLeft; |
|
17045 offsetY += styleBorderTop; |
|
17046 |
|
17047 // Take into account any scrolling done |
|
17048 offsetX += window.pageXOffset; |
|
17049 offsetY += window.pageYOffset; |
|
17050 |
|
17051 return {'X':offsetX,'Y':offsetY}; |
|
17052 } |
|
17053 |
|
17054 function updateMousePosition(curElement, event) { |
|
17055 var offset = calculateOffset(curElement, event); |
|
17056 |
|
17057 // Dropping support for IE clientX and clientY, switching to pageX and pageY so we don't have to calculate scroll offset. |
|
17058 // Removed in ticket #184. See rev: 2f106d1c7017fed92d045ba918db47d28e5c16f4 |
|
17059 p.mouseX = event.pageX - offset.X; |
|
17060 p.mouseY = event.pageY - offset.Y; |
|
17061 } |
|
17062 |
|
17063 // Return a TouchEvent with canvas-specific x/y co-ordinates |
|
17064 function addTouchEventOffset(t) { |
|
17065 var offset = calculateOffset(t.changedTouches[0].target, t.changedTouches[0]), |
|
17066 i; |
|
17067 |
|
17068 for (i = 0; i < t.touches.length; i++) { |
|
17069 var touch = t.touches[i]; |
|
17070 touch.offsetX = touch.pageX - offset.X; |
|
17071 touch.offsetY = touch.pageY - offset.Y; |
|
17072 } |
|
17073 for (i = 0; i < t.targetTouches.length; i++) { |
|
17074 var targetTouch = t.targetTouches[i]; |
|
17075 targetTouch.offsetX = targetTouch.pageX - offset.X; |
|
17076 targetTouch.offsetY = targetTouch.pageY - offset.Y; |
|
17077 } |
|
17078 for (i = 0; i < t.changedTouches.length; i++) { |
|
17079 var changedTouch = t.changedTouches[i]; |
|
17080 changedTouch.offsetX = changedTouch.pageX - offset.X; |
|
17081 changedTouch.offsetY = changedTouch.pageY - offset.Y; |
|
17082 } |
|
17083 |
|
17084 return t; |
|
17085 } |
|
17086 |
|
17087 attachEventHandler(curElement, "touchstart", function (t) { |
|
17088 // Removes unwanted behaviour of the canvas when touching canvas |
|
17089 curElement.setAttribute("style","-webkit-user-select: none"); |
|
17090 curElement.setAttribute("onclick","void(0)"); |
|
17091 curElement.setAttribute("style","-webkit-tap-highlight-color:rgba(0,0,0,0)"); |
|
17092 // Loop though eventHandlers and remove mouse listeners |
|
17093 for (var i=0, ehl=eventHandlers.length; i<ehl; i++) { |
|
17094 var type = eventHandlers[i].type; |
|
17095 // Have this function remove itself from the eventHandlers list too |
|
17096 if (type === "mouseout" || type === "mousemove" || |
|
17097 type === "mousedown" || type === "mouseup" || |
|
17098 type === "DOMMouseScroll" || type === "mousewheel" || type === "touchstart") { |
|
17099 detachEventHandler(eventHandlers[i]); |
|
17100 } |
|
17101 } |
|
17102 |
|
17103 // If there are any native touch events defined in the sketch, connect all of them |
|
17104 // Otherwise, connect all of the emulated mouse events |
|
17105 if (p.touchStart !== undef || p.touchMove !== undef || |
|
17106 p.touchEnd !== undef || p.touchCancel !== undef) { |
|
17107 attachEventHandler(curElement, "touchstart", function(t) { |
|
17108 if (p.touchStart !== undef) { |
|
17109 t = addTouchEventOffset(t); |
|
17110 p.touchStart(t); |
|
17111 } |
|
17112 }); |
|
17113 |
|
17114 attachEventHandler(curElement, "touchmove", function(t) { |
|
17115 if (p.touchMove !== undef) { |
|
17116 t.preventDefault(); // Stop the viewport from scrolling |
|
17117 t = addTouchEventOffset(t); |
|
17118 p.touchMove(t); |
|
17119 } |
|
17120 }); |
|
17121 |
|
17122 attachEventHandler(curElement, "touchend", function(t) { |
|
17123 if (p.touchEnd !== undef) { |
|
17124 t = addTouchEventOffset(t); |
|
17125 p.touchEnd(t); |
|
17126 } |
|
17127 }); |
|
17128 |
|
17129 attachEventHandler(curElement, "touchcancel", function(t) { |
|
17130 if (p.touchCancel !== undef) { |
|
17131 t = addTouchEventOffset(t); |
|
17132 p.touchCancel(t); |
|
17133 } |
|
17134 }); |
|
17135 |
|
17136 } else { |
|
17137 // Emulated touch start/mouse down event |
|
17138 attachEventHandler(curElement, "touchstart", function(e) { |
|
17139 updateMousePosition(curElement, e.touches[0]); |
|
17140 |
|
17141 p.__mousePressed = true; |
|
17142 p.mouseDragging = false; |
|
17143 p.mouseButton = PConstants.LEFT; |
|
17144 |
|
17145 if (typeof p.mousePressed === "function") { |
|
17146 p.mousePressed(); |
|
17147 } |
|
17148 }); |
|
17149 |
|
17150 // Emulated touch move/mouse move event |
|
17151 attachEventHandler(curElement, "touchmove", function(e) { |
|
17152 e.preventDefault(); |
|
17153 updateMousePosition(curElement, e.touches[0]); |
|
17154 |
|
17155 if (typeof p.mouseMoved === "function" && !p.__mousePressed) { |
|
17156 p.mouseMoved(); |
|
17157 } |
|
17158 if (typeof p.mouseDragged === "function" && p.__mousePressed) { |
|
17159 p.mouseDragged(); |
|
17160 p.mouseDragging = true; |
|
17161 } |
|
17162 }); |
|
17163 |
|
17164 // Emulated touch up/mouse up event |
|
17165 attachEventHandler(curElement, "touchend", function(e) { |
|
17166 p.__mousePressed = false; |
|
17167 |
|
17168 if (typeof p.mouseClicked === "function" && !p.mouseDragging) { |
|
17169 p.mouseClicked(); |
|
17170 } |
|
17171 |
|
17172 if (typeof p.mouseReleased === "function") { |
|
17173 p.mouseReleased(); |
|
17174 } |
|
17175 }); |
|
17176 } |
|
17177 |
|
17178 // Refire the touch start event we consumed in this function |
|
17179 curElement.dispatchEvent(t); |
|
17180 }); |
|
17181 |
|
17182 (function() { |
|
17183 var enabled = true, |
|
17184 contextMenu = function(e) { |
|
17185 e.preventDefault(); |
|
17186 e.stopPropagation(); |
|
17187 }; |
|
17188 |
|
17189 p.disableContextMenu = function() { |
|
17190 if (!enabled) { |
|
17191 return; |
|
17192 } |
|
17193 attachEventHandler(curElement, 'contextmenu', contextMenu); |
|
17194 enabled = false; |
|
17195 }; |
|
17196 |
|
17197 p.enableContextMenu = function() { |
|
17198 if (enabled) { |
|
17199 return; |
|
17200 } |
|
17201 detachEventHandler({elem: curElement, type: 'contextmenu', fn: contextMenu}); |
|
17202 enabled = true; |
|
17203 }; |
|
17204 }()); |
|
17205 |
|
17206 attachEventHandler(curElement, "mousemove", function(e) { |
|
17207 updateMousePosition(curElement, e); |
|
17208 if (typeof p.mouseMoved === "function" && !p.__mousePressed) { |
|
17209 p.mouseMoved(); |
|
17210 } |
|
17211 if (typeof p.mouseDragged === "function" && p.__mousePressed) { |
|
17212 p.mouseDragged(); |
|
17213 p.mouseDragging = true; |
|
17214 } |
|
17215 }); |
|
17216 |
|
17217 attachEventHandler(curElement, "mouseout", function(e) { |
|
17218 if (typeof p.mouseOut === "function") { |
|
17219 p.mouseOut(); |
|
17220 } |
|
17221 }); |
|
17222 |
|
17223 attachEventHandler(curElement, "mouseover", function(e) { |
|
17224 updateMousePosition(curElement, e); |
|
17225 if (typeof p.mouseOver === "function") { |
|
17226 p.mouseOver(); |
|
17227 } |
|
17228 }); |
|
17229 |
|
17230 attachEventHandler(curElement, "mousedown", function(e) { |
|
17231 p.__mousePressed = true; |
|
17232 p.mouseDragging = false; |
|
17233 switch (e.which) { |
|
17234 case 1: |
|
17235 p.mouseButton = PConstants.LEFT; |
|
17236 break; |
|
17237 case 2: |
|
17238 p.mouseButton = PConstants.CENTER; |
|
17239 break; |
|
17240 case 3: |
|
17241 p.mouseButton = PConstants.RIGHT; |
|
17242 break; |
|
17243 } |
|
17244 |
|
17245 if (typeof p.mousePressed === "function") { |
|
17246 p.mousePressed(); |
|
17247 } |
|
17248 }); |
|
17249 |
|
17250 attachEventHandler(curElement, "mouseup", function(e) { |
|
17251 p.__mousePressed = false; |
|
17252 |
|
17253 if (typeof p.mouseClicked === "function" && !p.mouseDragging) { |
|
17254 p.mouseClicked(); |
|
17255 } |
|
17256 |
|
17257 if (typeof p.mouseReleased === "function") { |
|
17258 p.mouseReleased(); |
|
17259 } |
|
17260 }); |
|
17261 |
|
17262 var mouseWheelHandler = function(e) { |
|
17263 var delta = 0; |
|
17264 |
|
17265 if (e.wheelDelta) { |
|
17266 delta = e.wheelDelta / 120; |
|
17267 if (window.opera) { |
|
17268 delta = -delta; |
|
17269 } |
|
17270 } else if (e.detail) { |
|
17271 delta = -e.detail / 3; |
|
17272 } |
|
17273 |
|
17274 p.mouseScroll = delta; |
|
17275 |
|
17276 if (delta && typeof p.mouseScrolled === 'function') { |
|
17277 p.mouseScrolled(); |
|
17278 } |
|
17279 }; |
|
17280 |
|
17281 // Support Gecko and non-Gecko scroll events |
|
17282 attachEventHandler(document, 'DOMMouseScroll', mouseWheelHandler); |
|
17283 attachEventHandler(document, 'mousewheel', mouseWheelHandler); |
|
17284 |
|
17285 ////////////////////////////////////////////////////////////////////////// |
|
17286 // Keyboard Events |
|
17287 ////////////////////////////////////////////////////////////////////////// |
|
17288 |
|
17289 // Get the DOM element if string was passed |
|
17290 if (typeof curElement === "string") { |
|
17291 curElement = document.getElementById(curElement); |
|
17292 } |
|
17293 |
|
17294 // In order to catch key events in a canvas, it needs to be "specially focusable", |
|
17295 // by assigning it a tabindex. If no tabindex is specified on-page, set this to 0. |
|
17296 if (!curElement.getAttribute("tabindex")) { |
|
17297 curElement.setAttribute("tabindex", 0); |
|
17298 } |
|
17299 |
|
17300 function getKeyCode(e) { |
|
17301 var code = e.which || e.keyCode; |
|
17302 switch (code) { |
|
17303 case 13: // ENTER |
|
17304 return 10; |
|
17305 case 91: // META L (Saf/Mac) |
|
17306 case 93: // META R (Saf/Mac) |
|
17307 case 224: // META (FF/Mac) |
|
17308 return 157; |
|
17309 case 57392: // CONTROL (Op/Mac) |
|
17310 return 17; |
|
17311 case 46: // DELETE |
|
17312 return 127; |
|
17313 case 45: // INSERT |
|
17314 return 155; |
|
17315 } |
|
17316 return code; |
|
17317 } |
|
17318 |
|
17319 function getKeyChar(e) { |
|
17320 var c = e.which || e.keyCode; |
|
17321 var anyShiftPressed = e.shiftKey || e.ctrlKey || e.altKey || e.metaKey; |
|
17322 switch (c) { |
|
17323 case 13: |
|
17324 c = anyShiftPressed ? 13 : 10; // RETURN vs ENTER (Mac) |
|
17325 break; |
|
17326 case 8: |
|
17327 c = anyShiftPressed ? 127 : 8; // DELETE vs BACKSPACE (Mac) |
|
17328 break; |
|
17329 } |
|
17330 return new Char(c); |
|
17331 } |
|
17332 |
|
17333 function suppressKeyEvent(e) { |
|
17334 if (typeof e.preventDefault === "function") { |
|
17335 e.preventDefault(); |
|
17336 } else if (typeof e.stopPropagation === "function") { |
|
17337 e.stopPropagation(); |
|
17338 } |
|
17339 return false; |
|
17340 } |
|
17341 |
|
17342 function updateKeyPressed() { |
|
17343 var ch; |
|
17344 for (ch in pressedKeysMap) { |
|
17345 if (pressedKeysMap.hasOwnProperty(ch)) { |
|
17346 p.__keyPressed = true; |
|
17347 return; |
|
17348 } |
|
17349 } |
|
17350 p.__keyPressed = false; |
|
17351 } |
|
17352 |
|
17353 function resetKeyPressed() { |
|
17354 p.__keyPressed = false; |
|
17355 pressedKeysMap = []; |
|
17356 lastPressedKeyCode = null; |
|
17357 } |
|
17358 |
|
17359 function simulateKeyTyped(code, c) { |
|
17360 pressedKeysMap[code] = c; |
|
17361 lastPressedKeyCode = null; |
|
17362 p.key = c; |
|
17363 p.keyCode = code; |
|
17364 p.keyPressed(); |
|
17365 p.keyCode = 0; |
|
17366 p.keyTyped(); |
|
17367 updateKeyPressed(); |
|
17368 } |
|
17369 |
|
17370 function handleKeydown(e) { |
|
17371 var code = getKeyCode(e); |
|
17372 if (code === PConstants.DELETE) { |
|
17373 simulateKeyTyped(code, new Char(127)); |
|
17374 return; |
|
17375 } |
|
17376 if (codedKeys.indexOf(code) < 0) { |
|
17377 lastPressedKeyCode = code; |
|
17378 return; |
|
17379 } |
|
17380 var c = new Char(PConstants.CODED); |
|
17381 p.key = c; |
|
17382 p.keyCode = code; |
|
17383 pressedKeysMap[code] = c; |
|
17384 p.keyPressed(); |
|
17385 lastPressedKeyCode = null; |
|
17386 updateKeyPressed(); |
|
17387 return suppressKeyEvent(e); |
|
17388 } |
|
17389 |
|
17390 function handleKeypress(e) { |
|
17391 if (lastPressedKeyCode === null) { |
|
17392 return; // processed in handleKeydown |
|
17393 } |
|
17394 var code = lastPressedKeyCode, c = getKeyChar(e); |
|
17395 simulateKeyTyped(code, c); |
|
17396 return suppressKeyEvent(e); |
|
17397 } |
|
17398 |
|
17399 function handleKeyup(e) { |
|
17400 var code = getKeyCode(e), c = pressedKeysMap[code]; |
|
17401 if (c === undef) { |
|
17402 return; // no keyPressed event was generated. |
|
17403 } |
|
17404 p.key = c; |
|
17405 p.keyCode = code; |
|
17406 p.keyReleased(); |
|
17407 delete pressedKeysMap[code]; |
|
17408 updateKeyPressed(); |
|
17409 } |
|
17410 |
|
17411 // Send aCode Processing syntax to be converted to JavaScript |
|
17412 if (!pgraphicsMode) { |
|
17413 if (aCode instanceof Processing.Sketch) { |
|
17414 // Use sketch as is |
|
17415 curSketch = aCode; |
|
17416 } else if (typeof aCode === "function") { |
|
17417 // Wrap function with default sketch parameters |
|
17418 curSketch = new Processing.Sketch(aCode); |
|
17419 } else if (!aCode) { |
|
17420 // Empty sketch |
|
17421 curSketch = new Processing.Sketch(function (){}); |
|
17422 } else { |
|
17423 //#if PARSER |
|
17424 // Compile the code |
|
17425 curSketch = Processing.compile(aCode); |
|
17426 //#else |
|
17427 // throw "PJS compile is not supported"; |
|
17428 //#endif |
|
17429 } |
|
17430 |
|
17431 // Expose internal field for diagnostics and testing |
|
17432 p.externals.sketch = curSketch; |
|
17433 |
|
17434 wireDimensionalFunctions(); |
|
17435 |
|
17436 // the onfocus and onblur events are handled in two parts. |
|
17437 // 1) the p.focused property is handled per sketch |
|
17438 curElement.onfocus = function() { |
|
17439 p.focused = true; |
|
17440 }; |
|
17441 |
|
17442 curElement.onblur = function() { |
|
17443 p.focused = false; |
|
17444 if (!curSketch.options.globalKeyEvents) { |
|
17445 resetKeyPressed(); |
|
17446 } |
|
17447 }; |
|
17448 |
|
17449 // 2) looping status is handled per page, based on the pauseOnBlur @pjs directive |
|
17450 if (curSketch.options.pauseOnBlur) { |
|
17451 attachEventHandler(window, 'focus', function() { |
|
17452 if (doLoop) { |
|
17453 p.loop(); |
|
17454 } |
|
17455 }); |
|
17456 |
|
17457 attachEventHandler(window, 'blur', function() { |
|
17458 if (doLoop && loopStarted) { |
|
17459 p.noLoop(); |
|
17460 doLoop = true; // make sure to keep this true after the noLoop call |
|
17461 } |
|
17462 resetKeyPressed(); |
|
17463 }); |
|
17464 } |
|
17465 |
|
17466 // if keyboard events should be handled globally, the listeners should |
|
17467 // be bound to the document window, rather than to the current canvas |
|
17468 var keyTrigger = curSketch.options.globalKeyEvents ? window : curElement; |
|
17469 attachEventHandler(keyTrigger, "keydown", handleKeydown); |
|
17470 attachEventHandler(keyTrigger, "keypress", handleKeypress); |
|
17471 attachEventHandler(keyTrigger, "keyup", handleKeyup); |
|
17472 |
|
17473 // Step through the libraries that were attached at doc load... |
|
17474 for (var i in Processing.lib) { |
|
17475 if (Processing.lib.hasOwnProperty(i)) { |
|
17476 if(Processing.lib[i].hasOwnProperty("attach")) { |
|
17477 // use attach function if present |
|
17478 Processing.lib[i].attach(p); |
|
17479 } else if(Processing.lib[i] instanceof Function) { |
|
17480 // Init the libraries in the context of this p_instance (legacy) |
|
17481 Processing.lib[i].call(this); |
|
17482 } |
|
17483 } |
|
17484 } |
|
17485 |
|
17486 // sketch execute test interval, used to reschedule |
|
17487 // an execute when preloads have not yet finished. |
|
17488 var retryInterval = 100; |
|
17489 |
|
17490 var executeSketch = function(processing) { |
|
17491 // Don't start until all specified images and fonts in the cache are preloaded |
|
17492 if (!(curSketch.imageCache.pending || PFont.preloading.pending(retryInterval))) { |
|
17493 // the opera preload cache can only be cleared once we start |
|
17494 if (window.opera) { |
|
17495 var link, |
|
17496 element, |
|
17497 operaCache=curSketch.imageCache.operaCache; |
|
17498 for (link in operaCache) { |
|
17499 if(operaCache.hasOwnProperty(link)) { |
|
17500 element = operaCache[link]; |
|
17501 if (element !== null) { |
|
17502 document.body.removeChild(element); |
|
17503 } |
|
17504 delete(operaCache[link]); |
|
17505 } |
|
17506 } |
|
17507 } |
|
17508 |
|
17509 curSketch.attach(processing, defaultScope); |
|
17510 |
|
17511 // pass a reference to the p instance for this sketch. |
|
17512 curSketch.onLoad(processing); |
|
17513 |
|
17514 // Run void setup() |
|
17515 if (processing.setup) { |
|
17516 processing.setup(); |
|
17517 // if any transforms were performed in setup reset to identity matrix |
|
17518 // so draw loop is unpolluted |
|
17519 processing.resetMatrix(); |
|
17520 curSketch.onSetup(); |
|
17521 } |
|
17522 |
|
17523 // some pixels can be cached, flushing |
|
17524 resetContext(); |
|
17525 |
|
17526 if (processing.draw) { |
|
17527 if (!doLoop) { |
|
17528 processing.redraw(); |
|
17529 } else { |
|
17530 processing.loop(); |
|
17531 } |
|
17532 } |
|
17533 } else { |
|
17534 window.setTimeout(function() { executeSketch(processing); }, retryInterval); |
|
17535 } |
|
17536 }; |
|
17537 |
|
17538 // Only store an instance of non-createGraphics instances. |
|
17539 addInstance(this); |
|
17540 |
|
17541 // The parser adds custom methods to the processing context |
|
17542 // this renames p to processing so these methods will run |
|
17543 executeSketch(p); |
|
17544 } else { |
|
17545 // No executable sketch was specified |
|
17546 // or called via createGraphics |
|
17547 curSketch = new Processing.Sketch(); |
|
17548 |
|
17549 wireDimensionalFunctions(); |
|
17550 |
|
17551 // Hack to make PGraphics work again after splitting size() |
|
17552 p.size = function(w, h, render) { |
|
17553 if (render && render === PConstants.WEBGL) { |
|
17554 wireDimensionalFunctions('3D'); |
|
17555 } else { |
|
17556 wireDimensionalFunctions('2D'); |
|
17557 } |
|
17558 |
|
17559 p.size(w, h, render); |
|
17560 }; |
|
17561 } |
|
17562 }; // Processing() ends |
|
17563 |
|
17564 // Place-holder for overridable debugging function |
|
17565 Processing.debug = debug; |
|
17566 |
|
17567 Processing.prototype = defaultScope; |
|
17568 |
|
17569 //#if PARSER |
|
17570 // Processing global methods and constants for the parser |
|
17571 function getGlobalMembers() { |
|
17572 // The names array contains the names of everything that is inside "p." |
|
17573 // When something new is added to "p." it must also be added to this list. |
|
17574 var names = [ /* this code is generated by jsglobals.js */ |
|
17575 "abs", "acos", "alpha", "ambient", "ambientLight", "append", "applyMatrix", |
|
17576 "arc", "arrayCopy", "asin", "atan", "atan2", "background", "beginCamera", |
|
17577 "beginDraw", "beginShape", "bezier", "bezierDetail", "bezierPoint", |
|
17578 "bezierTangent", "bezierVertex", "binary", "blend", "blendColor", |
|
17579 "blit_resize", "blue", "box", "breakShape", "brightness", |
|
17580 "camera", "ceil", "Character", "color", "colorMode", |
|
17581 "concat", "constrain", "copy", "cos", "createFont", |
|
17582 "createGraphics", "createImage", "cursor", "curve", "curveDetail", |
|
17583 "curvePoint", "curveTangent", "curveTightness", "curveVertex", "day", |
|
17584 "degrees", "directionalLight", "disableContextMenu", |
|
17585 "dist", "draw", "ellipse", "ellipseMode", "emissive", "enableContextMenu", |
|
17586 "endCamera", "endDraw", "endShape", "exit", "exp", "expand", "externals", |
|
17587 "fill", "filter", "floor", "focused", "frameCount", "frameRate", "frustum", |
|
17588 "get", "glyphLook", "glyphTable", "green", "height", "hex", "hint", "hour", |
|
17589 "hue", "image", "imageMode", "intersect", "join", "key", |
|
17590 "keyCode", "keyPressed", "keyReleased", "keyTyped", "lerp", "lerpColor", |
|
17591 "lightFalloff", "lights", "lightSpecular", "line", "link", "loadBytes", |
|
17592 "loadFont", "loadGlyphs", "loadImage", "loadPixels", "loadShape", |
|
17593 "loadStrings", "log", "loop", "mag", "map", "match", "matchAll", "max", |
|
17594 "millis", "min", "minute", "mix", "modelX", "modelY", "modelZ", "modes", |
|
17595 "month", "mouseButton", "mouseClicked", "mouseDragged", "mouseMoved", |
|
17596 "mouseOut", "mouseOver", "mousePressed", "mouseReleased", "mouseScroll", |
|
17597 "mouseScrolled", "mouseX", "mouseY", "name", "nf", "nfc", "nfp", "nfs", |
|
17598 "noCursor", "noFill", "noise", "noiseDetail", "noiseSeed", "noLights", |
|
17599 "noLoop", "norm", "normal", "noSmooth", "noStroke", "noTint", "ortho", |
|
17600 "param", "parseBoolean", "parseByte", "parseChar", "parseFloat", |
|
17601 "parseInt", "peg", "perspective", "PImage", "pixels", "PMatrix2D", |
|
17602 "PMatrix3D", "PMatrixStack", "pmouseX", "pmouseY", "point", |
|
17603 "pointLight", "popMatrix", "popStyle", "pow", "print", "printCamera", |
|
17604 "println", "printMatrix", "printProjection", "PShape", "PShapeSVG", |
|
17605 "pushMatrix", "pushStyle", "quad", "radians", "random", "Random", |
|
17606 "randomSeed", "rect", "rectMode", "red", "redraw", "requestImage", |
|
17607 "resetMatrix", "reverse", "rotate", "rotateX", "rotateY", "rotateZ", |
|
17608 "round", "saturation", "save", "saveFrame", "saveStrings", "scale", |
|
17609 "screenX", "screenY", "screenZ", "second", "set", "setup", "shape", |
|
17610 "shapeMode", "shared", "shininess", "shorten", "sin", "size", "smooth", |
|
17611 "sort", "specular", "sphere", "sphereDetail", "splice", "split", |
|
17612 "splitTokens", "spotLight", "sq", "sqrt", "status", "str", "stroke", |
|
17613 "strokeCap", "strokeJoin", "strokeWeight", "subset", "tan", "text", |
|
17614 "textAlign", "textAscent", "textDescent", "textFont", "textLeading", |
|
17615 "textMode", "textSize", "texture", "textureMode", "textWidth", "tint", "toImageData", |
|
17616 "touchCancel", "touchEnd", "touchMove", "touchStart", "translate", |
|
17617 "triangle", "trim", "unbinary", "unhex", "updatePixels", "use3DContext", |
|
17618 "vertex", "width", "XMLElement", "year", "__contains", "__equals", |
|
17619 "__equalsIgnoreCase", "__frameRate", "__hashCode", "__int_cast", |
|
17620 "__instanceof", "__keyPressed", "__mousePressed", "__printStackTrace", |
|
17621 "__replace", "__replaceAll", "__replaceFirst", "__toCharArray", "__split", |
|
17622 "__codePointAt", "__startsWith", "__endsWith"]; |
|
17623 |
|
17624 var members = {}; |
|
17625 var i, l; |
|
17626 for (i = 0, l = names.length; i < l ; ++i) { |
|
17627 members[names[i]] = null; |
|
17628 } |
|
17629 for (var lib in Processing.lib) { |
|
17630 if (Processing.lib.hasOwnProperty(lib)) { |
|
17631 if (Processing.lib[lib].exports) { |
|
17632 var exportedNames = Processing.lib[lib].exports; |
|
17633 for (i = 0, l = exportedNames.length; i < l; ++i) { |
|
17634 members[exportedNames[i]] = null; |
|
17635 } |
|
17636 } |
|
17637 } |
|
17638 } |
|
17639 return members; |
|
17640 } |
|
17641 |
|
17642 /* |
|
17643 |
|
17644 Parser converts Java-like syntax into JavaScript. |
|
17645 Creates an Abstract Syntax Tree -- "Light AST" from the Java-like code. |
|
17646 |
|
17647 It is an object tree. The root object is created from the AstRoot class, which contains statements. |
|
17648 |
|
17649 A statement object can be of type: AstForStatement, AstCatchStatement, AstPrefixStatement, AstMethod, AstClass, |
|
17650 AstInterface, AstFunction, AstStatementBlock and AstLabel. |
|
17651 |
|
17652 AstPrefixStatement can be a statement of type: if, switch, while, with, do, else, finally, return, throw, try, break, and continue. |
|
17653 |
|
17654 These object's toString function returns the JavaScript code for the statement. |
|
17655 |
|
17656 Any processing calls need "processing." prepended to them. |
|
17657 |
|
17658 Similarly, calls from inside classes need "$this_1.", prepended to them, |
|
17659 with 1 being the depth level for inner classes. |
|
17660 This includes members passed down from inheritance. |
|
17661 |
|
17662 The resulting code is then eval'd and run. |
|
17663 |
|
17664 */ |
|
17665 |
|
17666 function parseProcessing(code) { |
|
17667 var globalMembers = getGlobalMembers(); |
|
17668 |
|
17669 // masks parentheses, brackets and braces with '"A5"' |
|
17670 // where A is the bracket type, and 5 is the index in an array containing all brackets split into atoms |
|
17671 // 'while(true){}' -> 'while"B1""A2"' |
|
17672 // parentheses() = B, brackets[] = C and braces{} = A |
|
17673 function splitToAtoms(code) { |
|
17674 var atoms = []; |
|
17675 var items = code.split(/([\{\[\(\)\]\}])/); |
|
17676 var result = items[0]; |
|
17677 |
|
17678 var stack = []; |
|
17679 for(var i=1; i < items.length; i += 2) { |
|
17680 var item = items[i]; |
|
17681 if(item === '[' || item === '{' || item === '(') { |
|
17682 stack.push(result); result = item; |
|
17683 } else if(item === ']' || item === '}' || item === ')') { |
|
17684 var kind = item === '}' ? 'A' : item === ')' ? 'B' : 'C'; |
|
17685 var index = atoms.length; atoms.push(result + item); |
|
17686 result = stack.pop() + '"' + kind + (index + 1) + '"'; |
|
17687 } |
|
17688 result += items[i + 1]; |
|
17689 } |
|
17690 atoms.unshift(result); |
|
17691 return atoms; |
|
17692 } |
|
17693 |
|
17694 // replaces strings and regexs keyed by index with an array of strings |
|
17695 function injectStrings(code, strings) { |
|
17696 return code.replace(/'(\d+)'/g, function(all, index) { |
|
17697 var val = strings[index]; |
|
17698 if(val.charAt(0) === "/") { |
|
17699 return val; |
|
17700 } |
|
17701 return (/^'((?:[^'\\\n])|(?:\\.[0-9A-Fa-f]*))'$/).test(val) ? "(new $p.Character(" + val + "))" : val; |
|
17702 }); |
|
17703 } |
|
17704 |
|
17705 // trims off leading and trailing spaces |
|
17706 // returns an object. object.left, object.middle, object.right, object.untrim |
|
17707 function trimSpaces(string) { |
|
17708 var m1 = /^\s*/.exec(string), result; |
|
17709 if(m1[0].length === string.length) { |
|
17710 result = {left: m1[0], middle: "", right: ""}; |
|
17711 } else { |
|
17712 var m2 = /\s*$/.exec(string); |
|
17713 result = {left: m1[0], middle: string.substring(m1[0].length, m2.index), right: m2[0]}; |
|
17714 } |
|
17715 result.untrim = function(t) { return this.left + t + this.right; }; |
|
17716 return result; |
|
17717 } |
|
17718 |
|
17719 // simple trim of leading and trailing spaces |
|
17720 function trim(string) { |
|
17721 return string.replace(/^\s+/,'').replace(/\s+$/,''); |
|
17722 } |
|
17723 |
|
17724 function appendToLookupTable(table, array) { |
|
17725 for(var i=0,l=array.length;i<l;++i) { |
|
17726 table[array[i]] = null; |
|
17727 } |
|
17728 return table; |
|
17729 } |
|
17730 |
|
17731 function isLookupTableEmpty(table) { |
|
17732 for(var i in table) { |
|
17733 if(table.hasOwnProperty(i)) { |
|
17734 return false; |
|
17735 } |
|
17736 } |
|
17737 return true; |
|
17738 } |
|
17739 |
|
17740 function getAtomIndex(templ) { return templ.substring(2, templ.length - 1); } |
|
17741 |
|
17742 // remove carriage returns "\r" |
|
17743 var codeWoExtraCr = code.replace(/\r\n?|\n\r/g, "\n"); |
|
17744 |
|
17745 // masks strings and regexs with "'5'", where 5 is the index in an array containing all strings and regexs |
|
17746 // also removes all comments |
|
17747 var strings = []; |
|
17748 var codeWoStrings = codeWoExtraCr.replace(/("(?:[^"\\\n]|\\.)*")|('(?:[^'\\\n]|\\.)*')|(([\[\(=|&!\^:?]\s*)(\/(?![*\/])(?:[^\/\\\n]|\\.)*\/[gim]*)\b)|(\/\/[^\n]*\n)|(\/\*(?:(?!\*\/)(?:.|\n))*\*\/)/g, |
|
17749 function(all, quoted, aposed, regexCtx, prefix, regex, singleComment, comment) { |
|
17750 var index; |
|
17751 if(quoted || aposed) { // replace strings |
|
17752 index = strings.length; strings.push(all); |
|
17753 return "'" + index + "'"; |
|
17754 } |
|
17755 if(regexCtx) { // replace RegExps |
|
17756 index = strings.length; strings.push(regex); |
|
17757 return prefix + "'" + index + "'"; |
|
17758 } |
|
17759 // kill comments |
|
17760 return comment !== "" ? " " : "\n"; |
|
17761 }); |
|
17762 |
|
17763 // removes generics |
|
17764 var genericsWereRemoved; |
|
17765 var codeWoGenerics = codeWoStrings; |
|
17766 var replaceFunc = function(all, before, types, after) { |
|
17767 if(!!before || !!after) { |
|
17768 return all; |
|
17769 } |
|
17770 genericsWereRemoved = true; |
|
17771 return ""; |
|
17772 }; |
|
17773 |
|
17774 do { |
|
17775 genericsWereRemoved = false; |
|
17776 codeWoGenerics = codeWoGenerics.replace(/([<]?)<\s*((?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?(?:\s*,\s*(?:\?|[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s+(?:extends|super)\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)?)*)\s*>([=]?)/g, replaceFunc); |
|
17777 } while (genericsWereRemoved); |
|
17778 |
|
17779 var atoms = splitToAtoms(codeWoGenerics); |
|
17780 var replaceContext; |
|
17781 var declaredClasses = {}, currentClassId, classIdSeed = 0; |
|
17782 |
|
17783 function addAtom(text, type) { |
|
17784 var lastIndex = atoms.length; |
|
17785 atoms.push(text); |
|
17786 return '"' + type + lastIndex + '"'; |
|
17787 } |
|
17788 |
|
17789 function generateClassId() { |
|
17790 return "class" + (++classIdSeed); |
|
17791 } |
|
17792 |
|
17793 function appendClass(class_, classId, scopeId) { |
|
17794 class_.classId = classId; |
|
17795 class_.scopeId = scopeId; |
|
17796 declaredClasses[classId] = class_; |
|
17797 } |
|
17798 |
|
17799 // functions defined below |
|
17800 var transformClassBody, transformInterfaceBody, transformStatementsBlock, transformStatements, transformMain, transformExpression; |
|
17801 |
|
17802 var classesRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)(class|interface)\s+([A-Za-z_$][\w$]*\b)(\s+extends\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?(\s+implements\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\b)*)?\s*("A\d+")/g; |
|
17803 var methodsRegex = /\b((?:(?:public|private|final|protected|static|abstract|synchronized)\s+)*)((?!(?:else|new|return|throw|function|public|private|protected)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+"|;)/g; |
|
17804 var fieldTest = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:else|new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*([A-Za-z_$][\w$]*\b)\s*(?:"C\d+"\s*)*([=,]|$)/; |
|
17805 var cstrsRegex = /\b((?:(?:public|private|final|protected|static|abstract)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b)\s*("B\d+")(\s*throws\s+[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*,\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)*)?\s*("A\d+")/g; |
|
17806 var attrAndTypeRegex = /^((?:(?:public|private|final|protected|static)\s+)*)((?!(?:new|return|throw)\b)[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*(?:\s*"C\d+")*)\s*/; |
|
17807 var functionsRegex = /\bfunction(?:\s+([A-Za-z_$][\w$]*))?\s*("B\d+")\s*("A\d+")/g; |
|
17808 |
|
17809 // This converts classes, methods and functions into atoms, and adds them to the atoms array. |
|
17810 // classes = E, methods = D and functions = H |
|
17811 function extractClassesAndMethods(code) { |
|
17812 var s = code; |
|
17813 s = s.replace(classesRegex, function(all) { |
|
17814 return addAtom(all, 'E'); |
|
17815 }); |
|
17816 s = s.replace(methodsRegex, function(all) { |
|
17817 return addAtom(all, 'D'); |
|
17818 }); |
|
17819 s = s.replace(functionsRegex, function(all) { |
|
17820 return addAtom(all, 'H'); |
|
17821 }); |
|
17822 return s; |
|
17823 } |
|
17824 |
|
17825 // This converts constructors into atoms, and adds them to the atoms array. |
|
17826 // constructors = G |
|
17827 function extractConstructors(code, className) { |
|
17828 var result = code.replace(cstrsRegex, function(all, attr, name, params, throws_, body) { |
|
17829 if(name !== className) { |
|
17830 return all; |
|
17831 } |
|
17832 return addAtom(all, 'G'); |
|
17833 }); |
|
17834 return result; |
|
17835 } |
|
17836 |
|
17837 // AstParam contains the name of a parameter inside a function declaration |
|
17838 function AstParam(name) { |
|
17839 this.name = name; |
|
17840 } |
|
17841 AstParam.prototype.toString = function() { |
|
17842 return this.name; |
|
17843 }; |
|
17844 // AstParams contains an array of AstParam objects |
|
17845 function AstParams(params) { |
|
17846 this.params = params; |
|
17847 } |
|
17848 AstParams.prototype.getNames = function() { |
|
17849 var names = []; |
|
17850 for(var i=0,l=this.params.length;i<l;++i) { |
|
17851 names.push(this.params[i].name); |
|
17852 } |
|
17853 return names; |
|
17854 }; |
|
17855 AstParams.prototype.toString = function() { |
|
17856 if(this.params.length === 0) { |
|
17857 return "()"; |
|
17858 } |
|
17859 var result = "("; |
|
17860 for(var i=0,l=this.params.length;i<l;++i) { |
|
17861 result += this.params[i] + ", "; |
|
17862 } |
|
17863 return result.substring(0, result.length - 2) + ")"; |
|
17864 }; |
|
17865 |
|
17866 function transformParams(params) { |
|
17867 var paramsWoPars = trim(params.substring(1, params.length - 1)); |
|
17868 var result = []; |
|
17869 if(paramsWoPars !== "") { |
|
17870 var paramList = paramsWoPars.split(","); |
|
17871 for(var i=0; i < paramList.length; ++i) { |
|
17872 var param = /\b([A-Za-z_$][\w$]*\b)(\s*"[ABC][\d]*")*\s*$/.exec(paramList[i]); |
|
17873 result.push(new AstParam(param[1])); |
|
17874 } |
|
17875 } |
|
17876 return new AstParams(result); |
|
17877 } |
|
17878 |
|
17879 function preExpressionTransform(expr) { |
|
17880 var s = expr; |
|
17881 // new type[] {...} --> {...} |
|
17882 s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"C\d+")+\s*("A\d+")/g, function(all, type, init) { |
|
17883 return init; |
|
17884 }); |
|
17885 // new Runnable() {...} --> "F???" |
|
17886 s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)(?:\s*"B\d+")\s*("A\d+")/g, function(all, type, init) { |
|
17887 return addAtom(all, 'F'); |
|
17888 }); |
|
17889 // function(...) { } --> "H???" |
|
17890 s = s.replace(functionsRegex, function(all) { |
|
17891 return addAtom(all, 'H'); |
|
17892 }); |
|
17893 // new type[?] --> createJavaArray('type', [?]) |
|
17894 s = s.replace(/\bnew\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*("C\d+"(?:\s*"C\d+")*)/g, function(all, type, index) { |
|
17895 var args = index.replace(/"C(\d+)"/g, function(all, j) { return atoms[j]; }) |
|
17896 .replace(/\[\s*\]/g, "[null]").replace(/\s*\]\s*\[\s*/g, ", "); |
|
17897 var arrayInitializer = "{" + args.substring(1, args.length - 1) + "}"; |
|
17898 var createArrayArgs = "('" + type + "', " + addAtom(arrayInitializer, 'A') + ")"; |
|
17899 return '$p.createJavaArray' + addAtom(createArrayArgs, 'B'); |
|
17900 }); |
|
17901 // .length() --> .length |
|
17902 s = s.replace(/(\.\s*length)\s*"B\d+"/g, "$1"); |
|
17903 // #000000 --> 0x000000 |
|
17904 s = s.replace(/#([0-9A-Fa-f]{6})\b/g, function(all, digits) { |
|
17905 return "0xFF" + digits; |
|
17906 }); |
|
17907 // delete (type)???, except (int)??? |
|
17908 s = s.replace(/"B(\d+)"(\s*(?:[\w$']|"B))/g, function(all, index, next) { |
|
17909 var atom = atoms[index]; |
|
17910 if(!/^\(\s*[A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*\s*(?:"C\d+"\s*)*\)$/.test(atom)) { |
|
17911 return all; |
|
17912 } |
|
17913 if(/^\(\s*int\s*\)$/.test(atom)) { |
|
17914 return "(int)" + next; |
|
17915 } |
|
17916 var indexParts = atom.split(/"C(\d+)"/g); |
|
17917 if(indexParts.length > 1) { |
|
17918 // even items contains atom numbers, can check only first |
|
17919 if(! /^\[\s*\]$/.test(atoms[indexParts[1]])) { |
|
17920 return all; // fallback - not a cast |
|
17921 } |
|
17922 } |
|
17923 return "" + next; |
|
17924 }); |
|
17925 // (int)??? -> __int_cast(???) |
|
17926 s = s.replace(/\(int\)([^,\]\)\}\?\:\*\+\-\/\^\|\%\&\~<\>\=]+)/g, function(all, arg) { |
|
17927 var trimmed = trimSpaces(arg); |
|
17928 return trimmed.untrim("__int_cast(" + trimmed.middle + ")"); |
|
17929 }); |
|
17930 // super() -> $superCstr(), super. -> $super.; |
|
17931 s = s.replace(/\bsuper(\s*"B\d+")/g, "$$superCstr$1").replace(/\bsuper(\s*\.)/g, "$$super$1"); |
|
17932 // 000.43->0.43 and 0010f->10, but not 0010 |
|
17933 s = s.replace(/\b0+((\d*)(?:\.[\d*])?(?:[eE][\-\+]?\d+)?[fF]?)\b/, function(all, numberWo0, intPart) { |
|
17934 if( numberWo0 === intPart) { |
|
17935 return all; |
|
17936 } |
|
17937 return intPart === "" ? "0" + numberWo0 : numberWo0; |
|
17938 }); |
|
17939 // 3.0f -> 3.0 |
|
17940 s = s.replace(/\b(\.?\d+\.?)[fF]\b/g, "$1"); |
|
17941 // Weird (?) parsing errors with % |
|
17942 s = s.replace(/([^\s])%([^=\s])/g, "$1 % $2"); |
|
17943 // Since frameRate() and frameRate are different things, |
|
17944 // we need to differentiate them somehow. So when we parse |
|
17945 // the Processing.js source, replace frameRate so it isn't |
|
17946 // confused with frameRate(), as well as keyPressed and mousePressed |
|
17947 s = s.replace(/\b(frameRate|keyPressed|mousePressed)\b(?!\s*"B)/g, "__$1"); |
|
17948 // "boolean", "byte", "int", etc. => "parseBoolean", "parseByte", "parseInt", etc. |
|
17949 s = s.replace(/\b(boolean|byte|char|float|int)\s*"B/g, function(all, name) { |
|
17950 return "parse" + name.substring(0, 1).toUpperCase() + name.substring(1) + "\"B"; |
|
17951 }); |
|
17952 // "pixels" replacements: |
|
17953 // pixels[i] = c => pixels.setPixel(i,c) | pixels[i] => pixels.getPixel(i) |
|
17954 // pixels.length => pixels.getLength() |
|
17955 // pixels = ar => pixels.set(ar) | pixels => pixels.toArray() |
|
17956 s = s.replace(/\bpixels\b\s*(("C(\d+)")|\.length)?(\s*=(?!=)([^,\]\)\}]+))?/g, |
|
17957 function(all, indexOrLength, index, atomIndex, equalsPart, rightSide) { |
|
17958 if(index) { |
|
17959 var atom = atoms[atomIndex]; |
|
17960 if(equalsPart) { |
|
17961 return "pixels.setPixel" + addAtom("(" +atom.substring(1, atom.length - 1) + |
|
17962 "," + rightSide + ")", 'B'); |
|
17963 } |
|
17964 return "pixels.getPixel" + addAtom("(" + atom.substring(1, atom.length - 1) + |
|
17965 ")", 'B'); |
|
17966 } |
|
17967 if(indexOrLength) { |
|
17968 // length |
|
17969 return "pixels.getLength" + addAtom("()", 'B'); |
|
17970 } |
|
17971 if(equalsPart) { |
|
17972 return "pixels.set" + addAtom("(" + rightSide + ")", 'B'); |
|
17973 } |
|
17974 return "pixels.toArray" + addAtom("()", 'B'); |
|
17975 }); |
|
17976 // Java method replacements for: replace, replaceAll, replaceFirst, equals, hashCode, etc. |
|
17977 // xxx.replace(yyy) -> __replace(xxx, yyy) |
|
17978 // "xx".replace(yyy) -> __replace("xx", yyy) |
|
17979 var repeatJavaReplacement; |
|
17980 function replacePrototypeMethods(all, subject, method, atomIndex) { |
|
17981 var atom = atoms[atomIndex]; |
|
17982 repeatJavaReplacement = true; |
|
17983 var trimmed = trimSpaces(atom.substring(1, atom.length - 1)); |
|
17984 return "__" + method + ( trimmed.middle === "" ? addAtom("(" + subject.replace(/\.\s*$/, "") + ")", 'B') : |
|
17985 addAtom("(" + subject.replace(/\.\s*$/, "") + "," + trimmed.middle + ")", 'B') ); |
|
17986 } |
|
17987 do { |
|
17988 repeatJavaReplacement = false; |
|
17989 s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*\.\s*(?:[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*\.\s*)*)(replace|replaceAll|replaceFirst|contains|equals|equalsIgnoreCase|hashCode|toCharArray|printStackTrace|split|startsWith|endsWith|codePointAt)\s*"B(\d+)"/g, |
|
17990 replacePrototypeMethods); |
|
17991 } while (repeatJavaReplacement); |
|
17992 // xxx instanceof yyy -> __instanceof(xxx, yyy) |
|
17993 function replaceInstanceof(all, subject, type) { |
|
17994 repeatJavaReplacement = true; |
|
17995 return "__instanceof" + addAtom("(" + subject + ", " + type + ")", 'B'); |
|
17996 } |
|
17997 do { |
|
17998 repeatJavaReplacement = false; |
|
17999 s = s.replace(/((?:'\d+'|\b[A-Za-z_$][\w$]*\s*(?:"[BC]\d+")*)\s*(?:\.\s*[A-Za-z_$][\w$]*\s*(?:"[BC]\d+"\s*)*)*)instanceof\s+([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)/g, |
|
18000 replaceInstanceof); |
|
18001 } while (repeatJavaReplacement); |
|
18002 // this() -> $constr() |
|
18003 s = s.replace(/\bthis(\s*"B\d+")/g, "$$constr$1"); |
|
18004 |
|
18005 return s; |
|
18006 } |
|
18007 |
|
18008 function AstInlineClass(baseInterfaceName, body) { |
|
18009 this.baseInterfaceName = baseInterfaceName; |
|
18010 this.body = body; |
|
18011 body.owner = this; |
|
18012 } |
|
18013 AstInlineClass.prototype.toString = function() { |
|
18014 return "new (" + this.body + ")"; |
|
18015 }; |
|
18016 |
|
18017 function transformInlineClass(class_) { |
|
18018 var m = new RegExp(/\bnew\s*([A-Za-z_$][\w$]*\s*(?:\.\s*[A-Za-z_$][\w$]*)*)\s*"B\d+"\s*"A(\d+)"/).exec(class_); |
|
18019 var oldClassId = currentClassId, newClassId = generateClassId(); |
|
18020 currentClassId = newClassId; |
|
18021 var uniqueClassName = m[1] + "$" + newClassId; |
|
18022 var inlineClass = new AstInlineClass(uniqueClassName, |
|
18023 transformClassBody(atoms[m[2]], uniqueClassName, "", "implements " + m[1])); |
|
18024 appendClass(inlineClass, newClassId, oldClassId); |
|
18025 currentClassId = oldClassId; |
|
18026 return inlineClass; |
|
18027 } |
|
18028 |
|
18029 function AstFunction(name, params, body) { |
|
18030 this.name = name; |
|
18031 this.params = params; |
|
18032 this.body = body; |
|
18033 } |
|
18034 AstFunction.prototype.toString = function() { |
|
18035 var oldContext = replaceContext; |
|
18036 // saving "this." and parameters |
|
18037 var names = appendToLookupTable({"this":null}, this.params.getNames()); |
|
18038 replaceContext = function (subject) { |
|
18039 return names.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); |
|
18040 }; |
|
18041 var result = "function"; |
|
18042 if(this.name) { |
|
18043 result += " " + this.name; |
|
18044 } |
|
18045 result += this.params + " " + this.body; |
|
18046 replaceContext = oldContext; |
|
18047 return result; |
|
18048 }; |
|
18049 |
|
18050 function transformFunction(class_) { |
|
18051 var m = new RegExp(/\b([A-Za-z_$][\w$]*)\s*"B(\d+)"\s*"A(\d+)"/).exec(class_); |
|
18052 return new AstFunction( m[1] !== "function" ? m[1] : null, |
|
18053 transformParams(atoms[m[2]]), transformStatementsBlock(atoms[m[3]])); |
|
18054 } |
|
18055 |
|
18056 function AstInlineObject(members) { |
|
18057 this.members = members; |
|
18058 } |
|
18059 AstInlineObject.prototype.toString = function() { |
|
18060 var oldContext = replaceContext; |
|
18061 replaceContext = function (subject) { |
|
18062 return subject.name === "this" ? "this" : oldContext(subject); // saving "this." |
|
18063 }; |
|
18064 var result = ""; |
|
18065 for(var i=0,l=this.members.length;i<l;++i) { |
|
18066 if(this.members[i].label) { |
|
18067 result += this.members[i].label + ": "; |
|
18068 } |
|
18069 result += this.members[i].value.toString() + ", "; |
|
18070 } |
|
18071 replaceContext = oldContext; |
|
18072 return result.substring(0, result.length - 2); |
|
18073 }; |
|
18074 |
|
18075 function transformInlineObject(obj) { |
|
18076 var members = obj.split(','); |
|
18077 for(var i=0; i < members.length; ++i) { |
|
18078 var label = members[i].indexOf(':'); |
|
18079 if(label < 0) { |
|
18080 members[i] = { value: transformExpression(members[i]) }; |
|
18081 } else { |
|
18082 members[i] = { label: trim(members[i].substring(0, label)), |
|
18083 value: transformExpression( trim(members[i].substring(label + 1)) ) }; |
|
18084 } |
|
18085 } |
|
18086 return new AstInlineObject(members); |
|
18087 } |
|
18088 |
|
18089 function expandExpression(expr) { |
|
18090 if(expr.charAt(0) === '(' || expr.charAt(0) === '[') { |
|
18091 return expr.charAt(0) + expandExpression(expr.substring(1, expr.length - 1)) + expr.charAt(expr.length - 1); |
|
18092 } |
|
18093 if(expr.charAt(0) === '{') { |
|
18094 if(/^\{\s*(?:[A-Za-z_$][\w$]*|'\d+')\s*:/.test(expr)) { |
|
18095 return "{" + addAtom(expr.substring(1, expr.length - 1), 'I') + "}"; |
|
18096 } |
|
18097 return "[" + expandExpression(expr.substring(1, expr.length - 1)) + "]"; |
|
18098 } |
|
18099 var trimmed = trimSpaces(expr); |
|
18100 var result = preExpressionTransform(trimmed.middle); |
|
18101 result = result.replace(/"[ABC](\d+)"/g, function(all, index) { |
|
18102 return expandExpression(atoms[index]); |
|
18103 }); |
|
18104 return trimmed.untrim(result); |
|
18105 } |
|
18106 |
|
18107 function replaceContextInVars(expr) { |
|
18108 return expr.replace(/(\.\s*)?((?:\b[A-Za-z_]|\$)[\w$]*)(\s*\.\s*([A-Za-z_$][\w$]*)(\s*\()?)?/g, |
|
18109 function(all, memberAccessSign, identifier, suffix, subMember, callSign) { |
|
18110 if(memberAccessSign) { |
|
18111 return all; |
|
18112 } |
|
18113 var subject = { name: identifier, member: subMember, callSign: !!callSign }; |
|
18114 return replaceContext(subject) + (suffix === undef ? "" : suffix); |
|
18115 }); |
|
18116 } |
|
18117 |
|
18118 function AstExpression(expr, transforms) { |
|
18119 this.expr = expr; |
|
18120 this.transforms = transforms; |
|
18121 } |
|
18122 AstExpression.prototype.toString = function() { |
|
18123 var transforms = this.transforms; |
|
18124 var expr = replaceContextInVars(this.expr); |
|
18125 return expr.replace(/"!(\d+)"/g, function(all, index) { |
|
18126 return transforms[index].toString(); |
|
18127 }); |
|
18128 }; |
|
18129 |
|
18130 transformExpression = function(expr) { |
|
18131 var transforms = []; |
|
18132 var s = expandExpression(expr); |
|
18133 s = s.replace(/"H(\d+)"/g, function(all, index) { |
|
18134 transforms.push(transformFunction(atoms[index])); |
|
18135 return '"!' + (transforms.length - 1) + '"'; |
|
18136 }); |
|
18137 s = s.replace(/"F(\d+)"/g, function(all, index) { |
|
18138 transforms.push(transformInlineClass(atoms[index])); |
|
18139 return '"!' + (transforms.length - 1) + '"'; |
|
18140 }); |
|
18141 s = s.replace(/"I(\d+)"/g, function(all, index) { |
|
18142 transforms.push(transformInlineObject(atoms[index])); |
|
18143 return '"!' + (transforms.length - 1) + '"'; |
|
18144 }); |
|
18145 |
|
18146 return new AstExpression(s, transforms); |
|
18147 }; |
|
18148 |
|
18149 function AstVarDefinition(name, value, isDefault) { |
|
18150 this.name = name; |
|
18151 this.value = value; |
|
18152 this.isDefault = isDefault; |
|
18153 } |
|
18154 AstVarDefinition.prototype.toString = function() { |
|
18155 return this.name + ' = ' + this.value; |
|
18156 }; |
|
18157 |
|
18158 function transformVarDefinition(def, defaultTypeValue) { |
|
18159 var eqIndex = def.indexOf("="); |
|
18160 var name, value, isDefault; |
|
18161 if(eqIndex < 0) { |
|
18162 name = def; |
|
18163 value = defaultTypeValue; |
|
18164 isDefault = true; |
|
18165 } else { |
|
18166 name = def.substring(0, eqIndex); |
|
18167 value = transformExpression(def.substring(eqIndex + 1)); |
|
18168 isDefault = false; |
|
18169 } |
|
18170 return new AstVarDefinition( trim(name.replace(/(\s*"C\d+")+/g, "")), |
|
18171 value, isDefault); |
|
18172 } |
|
18173 |
|
18174 function getDefaultValueForType(type) { |
|
18175 if(type === "int" || type === "float") { |
|
18176 return "0"; |
|
18177 } |
|
18178 if(type === "boolean") { |
|
18179 return "false"; |
|
18180 } |
|
18181 if(type === "color") { |
|
18182 return "0x00000000"; |
|
18183 } |
|
18184 return "null"; |
|
18185 } |
|
18186 |
|
18187 function AstVar(definitions, varType) { |
|
18188 this.definitions = definitions; |
|
18189 this.varType = varType; |
|
18190 } |
|
18191 AstVar.prototype.getNames = function() { |
|
18192 var names = []; |
|
18193 for(var i=0,l=this.definitions.length;i<l;++i) { |
|
18194 names.push(this.definitions[i].name); |
|
18195 } |
|
18196 return names; |
|
18197 }; |
|
18198 AstVar.prototype.toString = function() { |
|
18199 return "var " + this.definitions.join(","); |
|
18200 }; |
|
18201 function AstStatement(expression) { |
|
18202 this.expression = expression; |
|
18203 } |
|
18204 AstStatement.prototype.toString = function() { |
|
18205 return this.expression.toString(); |
|
18206 }; |
|
18207 |
|
18208 function transformStatement(statement) { |
|
18209 if(fieldTest.test(statement)) { |
|
18210 var attrAndType = attrAndTypeRegex.exec(statement); |
|
18211 var definitions = statement.substring(attrAndType[0].length).split(","); |
|
18212 var defaultTypeValue = getDefaultValueForType(attrAndType[2]); |
|
18213 for(var i=0; i < definitions.length; ++i) { |
|
18214 definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue); |
|
18215 } |
|
18216 return new AstVar(definitions, attrAndType[2]); |
|
18217 } |
|
18218 return new AstStatement(transformExpression(statement)); |
|
18219 } |
|
18220 |
|
18221 function AstForExpression(initStatement, condition, step) { |
|
18222 this.initStatement = initStatement; |
|
18223 this.condition = condition; |
|
18224 this.step = step; |
|
18225 } |
|
18226 AstForExpression.prototype.toString = function() { |
|
18227 return "(" + this.initStatement + "; " + this.condition + "; " + this.step + ")"; |
|
18228 }; |
|
18229 |
|
18230 function AstForInExpression(initStatement, container) { |
|
18231 this.initStatement = initStatement; |
|
18232 this.container = container; |
|
18233 } |
|
18234 AstForInExpression.prototype.toString = function() { |
|
18235 var init = this.initStatement.toString(); |
|
18236 if(init.indexOf("=") >= 0) { // can be without var declaration |
|
18237 init = init.substring(0, init.indexOf("=")); |
|
18238 } |
|
18239 return "(" + init + " in " + this.container + ")"; |
|
18240 }; |
|
18241 |
|
18242 function AstForEachExpression(initStatement, container) { |
|
18243 this.initStatement = initStatement; |
|
18244 this.container = container; |
|
18245 } |
|
18246 AstForEachExpression.iteratorId = 0; |
|
18247 AstForEachExpression.prototype.toString = function() { |
|
18248 var init = this.initStatement.toString(); |
|
18249 var iterator = "$it" + (AstForEachExpression.iteratorId++); |
|
18250 var variableName = init.replace(/^\s*var\s*/, "").split("=")[0]; |
|
18251 var initIteratorAndVariable = "var " + iterator + " = new $p.ObjectIterator(" + this.container + "), " + |
|
18252 variableName + " = void(0)"; |
|
18253 var nextIterationCondition = iterator + ".hasNext() && ((" + |
|
18254 variableName + " = " + iterator + ".next()) || true)"; |
|
18255 return "(" + initIteratorAndVariable + "; " + nextIterationCondition + ";)"; |
|
18256 }; |
|
18257 |
|
18258 function transformForExpression(expr) { |
|
18259 var content; |
|
18260 if (/\bin\b/.test(expr)) { |
|
18261 content = expr.substring(1, expr.length - 1).split(/\bin\b/g); |
|
18262 return new AstForInExpression( transformStatement(trim(content[0])), |
|
18263 transformExpression(content[1])); |
|
18264 } |
|
18265 if (expr.indexOf(":") >= 0 && expr.indexOf(";") < 0) { |
|
18266 content = expr.substring(1, expr.length - 1).split(":"); |
|
18267 return new AstForEachExpression( transformStatement(trim(content[0])), |
|
18268 transformExpression(content[1])); |
|
18269 } |
|
18270 content = expr.substring(1, expr.length - 1).split(";"); |
|
18271 return new AstForExpression( transformStatement(trim(content[0])), |
|
18272 transformExpression(content[1]), transformExpression(content[2])); |
|
18273 } |
|
18274 |
|
18275 function sortByWeight(array) { |
|
18276 array.sort(function (a,b) { |
|
18277 return b.weight - a.weight; |
|
18278 }); |
|
18279 } |
|
18280 |
|
18281 function AstInnerInterface(name, body, isStatic) { |
|
18282 this.name = name; |
|
18283 this.body = body; |
|
18284 this.isStatic = isStatic; |
|
18285 body.owner = this; |
|
18286 } |
|
18287 AstInnerInterface.prototype.toString = function() { |
|
18288 return "" + this.body; |
|
18289 }; |
|
18290 function AstInnerClass(name, body, isStatic) { |
|
18291 this.name = name; |
|
18292 this.body = body; |
|
18293 this.isStatic = isStatic; |
|
18294 body.owner = this; |
|
18295 } |
|
18296 AstInnerClass.prototype.toString = function() { |
|
18297 return "" + this.body; |
|
18298 }; |
|
18299 |
|
18300 function transformInnerClass(class_) { |
|
18301 var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body |
|
18302 classesRegex.lastIndex = 0; |
|
18303 var isStatic = m[1].indexOf("static") >= 0; |
|
18304 var body = atoms[getAtomIndex(m[6])], innerClass; |
|
18305 var oldClassId = currentClassId, newClassId = generateClassId(); |
|
18306 currentClassId = newClassId; |
|
18307 if(m[2] === "interface") { |
|
18308 innerClass = new AstInnerInterface(m[3], transformInterfaceBody(body, m[3], m[4]), isStatic); |
|
18309 } else { |
|
18310 innerClass = new AstInnerClass(m[3], transformClassBody(body, m[3], m[4], m[5]), isStatic); |
|
18311 } |
|
18312 appendClass(innerClass, newClassId, oldClassId); |
|
18313 currentClassId = oldClassId; |
|
18314 return innerClass; |
|
18315 } |
|
18316 |
|
18317 function AstClassMethod(name, params, body, isStatic) { |
|
18318 this.name = name; |
|
18319 this.params = params; |
|
18320 this.body = body; |
|
18321 this.isStatic = isStatic; |
|
18322 } |
|
18323 AstClassMethod.prototype.toString = function(){ |
|
18324 var paramNames = appendToLookupTable({}, this.params.getNames()); |
|
18325 var oldContext = replaceContext; |
|
18326 replaceContext = function (subject) { |
|
18327 return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); |
|
18328 }; |
|
18329 var result = "function " + this.methodId + this.params + " " + this.body +"\n"; |
|
18330 replaceContext = oldContext; |
|
18331 return result; |
|
18332 }; |
|
18333 |
|
18334 function transformClassMethod(method) { |
|
18335 var m = methodsRegex.exec(method); |
|
18336 methodsRegex.lastIndex = 0; |
|
18337 var isStatic = m[1].indexOf("static") >= 0; |
|
18338 var body = m[6] !== ';' ? atoms[getAtomIndex(m[6])] : "{}"; |
|
18339 return new AstClassMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]), |
|
18340 transformStatementsBlock(body), isStatic ); |
|
18341 } |
|
18342 |
|
18343 function AstClassField(definitions, fieldType, isStatic) { |
|
18344 this.definitions = definitions; |
|
18345 this.fieldType = fieldType; |
|
18346 this.isStatic = isStatic; |
|
18347 } |
|
18348 AstClassField.prototype.getNames = function() { |
|
18349 var names = []; |
|
18350 for(var i=0,l=this.definitions.length;i<l;++i) { |
|
18351 names.push(this.definitions[i].name); |
|
18352 } |
|
18353 return names; |
|
18354 }; |
|
18355 AstClassField.prototype.toString = function() { |
|
18356 var thisPrefix = replaceContext({ name: "[this]" }); |
|
18357 if(this.isStatic) { |
|
18358 var className = this.owner.name; |
|
18359 var staticDeclarations = []; |
|
18360 for(var i=0,l=this.definitions.length;i<l;++i) { |
|
18361 var definition = this.definitions[i]; |
|
18362 var name = definition.name, staticName = className + "." + name; |
|
18363 var declaration = "if(" + staticName + " === void(0)) {\n" + |
|
18364 " " + staticName + " = " + definition.value + "; }\n" + |
|
18365 "$p.defineProperty(" + thisPrefix + ", " + |
|
18366 "'" + name + "', { get: function(){return " + staticName + ";}, " + |
|
18367 "set: function(val){" + staticName + " = val;} });\n"; |
|
18368 staticDeclarations.push(declaration); |
|
18369 } |
|
18370 return staticDeclarations.join(""); |
|
18371 } |
|
18372 return thisPrefix + "." + this.definitions.join("; " + thisPrefix + "."); |
|
18373 }; |
|
18374 |
|
18375 function transformClassField(statement) { |
|
18376 var attrAndType = attrAndTypeRegex.exec(statement); |
|
18377 var isStatic = attrAndType[1].indexOf("static") >= 0; |
|
18378 var definitions = statement.substring(attrAndType[0].length).split(/,\s*/g); |
|
18379 var defaultTypeValue = getDefaultValueForType(attrAndType[2]); |
|
18380 for(var i=0; i < definitions.length; ++i) { |
|
18381 definitions[i] = transformVarDefinition(definitions[i], defaultTypeValue); |
|
18382 } |
|
18383 return new AstClassField(definitions, attrAndType[2], isStatic); |
|
18384 } |
|
18385 |
|
18386 function AstConstructor(params, body) { |
|
18387 this.params = params; |
|
18388 this.body = body; |
|
18389 } |
|
18390 AstConstructor.prototype.toString = function() { |
|
18391 var paramNames = appendToLookupTable({}, this.params.getNames()); |
|
18392 var oldContext = replaceContext; |
|
18393 replaceContext = function (subject) { |
|
18394 return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); |
|
18395 }; |
|
18396 var prefix = "function $constr_" + this.params.params.length + this.params.toString(); |
|
18397 var body = this.body.toString(); |
|
18398 if(!/\$(superCstr|constr)\b/.test(body)) { |
|
18399 body = "{\n$superCstr();\n" + body.substring(1); |
|
18400 } |
|
18401 replaceContext = oldContext; |
|
18402 return prefix + body + "\n"; |
|
18403 }; |
|
18404 |
|
18405 function transformConstructor(cstr) { |
|
18406 var m = new RegExp(/"B(\d+)"\s*"A(\d+)"/).exec(cstr); |
|
18407 var params = transformParams(atoms[m[1]]); |
|
18408 |
|
18409 return new AstConstructor(params, transformStatementsBlock(atoms[m[2]])); |
|
18410 } |
|
18411 |
|
18412 function AstInterfaceBody(name, interfacesNames, methodsNames, fields, innerClasses, misc) { |
|
18413 var i,l; |
|
18414 this.name = name; |
|
18415 this.interfacesNames = interfacesNames; |
|
18416 this.methodsNames = methodsNames; |
|
18417 this.fields = fields; |
|
18418 this.innerClasses = innerClasses; |
|
18419 this.misc = misc; |
|
18420 for(i=0,l=fields.length; i<l; ++i) { |
|
18421 fields[i].owner = this; |
|
18422 } |
|
18423 } |
|
18424 AstInterfaceBody.prototype.getMembers = function(classFields, classMethods, classInners) { |
|
18425 if(this.owner.base) { |
|
18426 this.owner.base.body.getMembers(classFields, classMethods, classInners); |
|
18427 } |
|
18428 var i, j, l, m; |
|
18429 for(i=0,l=this.fields.length;i<l;++i) { |
|
18430 var fieldNames = this.fields[i].getNames(); |
|
18431 for(j=0,m=fieldNames.length;j<m;++j) { |
|
18432 classFields[fieldNames[j]] = this.fields[i]; |
|
18433 } |
|
18434 } |
|
18435 for(i=0,l=this.methodsNames.length;i<l;++i) { |
|
18436 var methodName = this.methodsNames[i]; |
|
18437 classMethods[methodName] = true; |
|
18438 } |
|
18439 for(i=0,l=this.innerClasses.length;i<l;++i) { |
|
18440 var innerClass = this.innerClasses[i]; |
|
18441 classInners[innerClass.name] = innerClass; |
|
18442 } |
|
18443 }; |
|
18444 AstInterfaceBody.prototype.toString = function() { |
|
18445 function getScopeLevel(p) { |
|
18446 var i = 0; |
|
18447 while(p) { |
|
18448 ++i; |
|
18449 p=p.scope; |
|
18450 } |
|
18451 return i; |
|
18452 } |
|
18453 |
|
18454 var scopeLevel = getScopeLevel(this.owner); |
|
18455 |
|
18456 var className = this.name; |
|
18457 var staticDefinitions = ""; |
|
18458 var metadata = ""; |
|
18459 |
|
18460 var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {}; |
|
18461 this.getMembers(thisClassFields, thisClassMethods, thisClassInners); |
|
18462 |
|
18463 var i, l, j, m; |
|
18464 |
|
18465 if (this.owner.interfaces) { |
|
18466 // interface name can be present, but interface is not |
|
18467 var resolvedInterfaces = [], resolvedInterface; |
|
18468 for (i = 0, l = this.interfacesNames.length; i < l; ++i) { |
|
18469 if (!this.owner.interfaces[i]) { |
|
18470 continue; |
|
18471 } |
|
18472 resolvedInterface = replaceContext({name: this.interfacesNames[i]}); |
|
18473 resolvedInterfaces.push(resolvedInterface); |
|
18474 staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n"; |
|
18475 } |
|
18476 metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n"; |
|
18477 } |
|
18478 metadata += className + ".$isInterface = true;\n"; |
|
18479 metadata += className + ".$methods = [\'" + this.methodsNames.join("\', \'") + "\'];\n"; |
|
18480 |
|
18481 sortByWeight(this.innerClasses); |
|
18482 for (i = 0, l = this.innerClasses.length; i < l; ++i) { |
|
18483 var innerClass = this.innerClasses[i]; |
|
18484 if (innerClass.isStatic) { |
|
18485 staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n"; |
|
18486 } |
|
18487 } |
|
18488 |
|
18489 for (i = 0, l = this.fields.length; i < l; ++i) { |
|
18490 var field = this.fields[i]; |
|
18491 if (field.isStatic) { |
|
18492 staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n"; |
|
18493 } |
|
18494 } |
|
18495 |
|
18496 return "(function() {\n" + |
|
18497 "function " + className + "() { throw \'Unable to create the interface\'; }\n" + |
|
18498 staticDefinitions + |
|
18499 metadata + |
|
18500 "return " + className + ";\n" + |
|
18501 "})()"; |
|
18502 }; |
|
18503 |
|
18504 transformInterfaceBody = function(body, name, baseInterfaces) { |
|
18505 var declarations = body.substring(1, body.length - 1); |
|
18506 declarations = extractClassesAndMethods(declarations); |
|
18507 declarations = extractConstructors(declarations, name); |
|
18508 var methodsNames = [], classes = []; |
|
18509 declarations = declarations.replace(/"([DE])(\d+)"/g, function(all, type, index) { |
|
18510 if(type === 'D') { methodsNames.push(index); } |
|
18511 else if(type === 'E') { classes.push(index); } |
|
18512 return ""; |
|
18513 }); |
|
18514 var fields = declarations.split(/;(?:\s*;)*/g); |
|
18515 var baseInterfaceNames; |
|
18516 var i, l; |
|
18517 |
|
18518 if(baseInterfaces !== undef) { |
|
18519 baseInterfaceNames = baseInterfaces.replace(/^\s*extends\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g); |
|
18520 } |
|
18521 |
|
18522 for(i = 0, l = methodsNames.length; i < l; ++i) { |
|
18523 var method = transformClassMethod(atoms[methodsNames[i]]); |
|
18524 methodsNames[i] = method.name; |
|
18525 } |
|
18526 for(i = 0, l = fields.length - 1; i < l; ++i) { |
|
18527 var field = trimSpaces(fields[i]); |
|
18528 fields[i] = transformClassField(field.middle); |
|
18529 } |
|
18530 var tail = fields.pop(); |
|
18531 for(i = 0, l = classes.length; i < l; ++i) { |
|
18532 classes[i] = transformInnerClass(atoms[classes[i]]); |
|
18533 } |
|
18534 |
|
18535 return new AstInterfaceBody(name, baseInterfaceNames, methodsNames, fields, classes, { tail: tail }); |
|
18536 }; |
|
18537 |
|
18538 function AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, innerClasses, misc) { |
|
18539 var i,l; |
|
18540 this.name = name; |
|
18541 this.baseClassName = baseClassName; |
|
18542 this.interfacesNames = interfacesNames; |
|
18543 this.functions = functions; |
|
18544 this.methods = methods; |
|
18545 this.fields = fields; |
|
18546 this.cstrs = cstrs; |
|
18547 this.innerClasses = innerClasses; |
|
18548 this.misc = misc; |
|
18549 for(i=0,l=fields.length; i<l; ++i) { |
|
18550 fields[i].owner = this; |
|
18551 } |
|
18552 } |
|
18553 AstClassBody.prototype.getMembers = function(classFields, classMethods, classInners) { |
|
18554 if(this.owner.base) { |
|
18555 this.owner.base.body.getMembers(classFields, classMethods, classInners); |
|
18556 } |
|
18557 var i, j, l, m; |
|
18558 for(i=0,l=this.fields.length;i<l;++i) { |
|
18559 var fieldNames = this.fields[i].getNames(); |
|
18560 for(j=0,m=fieldNames.length;j<m;++j) { |
|
18561 classFields[fieldNames[j]] = this.fields[i]; |
|
18562 } |
|
18563 } |
|
18564 for(i=0,l=this.methods.length;i<l;++i) { |
|
18565 var method = this.methods[i]; |
|
18566 classMethods[method.name] = method; |
|
18567 } |
|
18568 for(i=0,l=this.innerClasses.length;i<l;++i) { |
|
18569 var innerClass = this.innerClasses[i]; |
|
18570 classInners[innerClass.name] = innerClass; |
|
18571 } |
|
18572 }; |
|
18573 AstClassBody.prototype.toString = function() { |
|
18574 function getScopeLevel(p) { |
|
18575 var i = 0; |
|
18576 while(p) { |
|
18577 ++i; |
|
18578 p=p.scope; |
|
18579 } |
|
18580 return i; |
|
18581 } |
|
18582 |
|
18583 var scopeLevel = getScopeLevel(this.owner); |
|
18584 |
|
18585 var selfId = "$this_" + scopeLevel; |
|
18586 var className = this.name; |
|
18587 var result = "var " + selfId + " = this;\n"; |
|
18588 var staticDefinitions = ""; |
|
18589 var metadata = ""; |
|
18590 |
|
18591 var thisClassFields = {}, thisClassMethods = {}, thisClassInners = {}; |
|
18592 this.getMembers(thisClassFields, thisClassMethods, thisClassInners); |
|
18593 |
|
18594 var oldContext = replaceContext; |
|
18595 replaceContext = function (subject) { |
|
18596 var name = subject.name; |
|
18597 if(name === "this") { |
|
18598 // returns "$this_N.$self" pointer instead of "this" in cases: |
|
18599 // "this()", "this.XXX()", "this", but not for "this.XXX" |
|
18600 return subject.callSign || !subject.member ? selfId + ".$self" : selfId; |
|
18601 } |
|
18602 if(thisClassFields.hasOwnProperty(name)) { |
|
18603 return thisClassFields[name].isStatic ? className + "." + name : selfId + "." + name; |
|
18604 } |
|
18605 if(thisClassInners.hasOwnProperty(name)) { |
|
18606 return selfId + "." + name; |
|
18607 } |
|
18608 if(thisClassMethods.hasOwnProperty(name)) { |
|
18609 return thisClassMethods[name].isStatic ? className + "." + name : selfId + ".$self." + name; |
|
18610 } |
|
18611 return oldContext(subject); |
|
18612 }; |
|
18613 |
|
18614 var resolvedBaseClassName; |
|
18615 if (this.baseClassName) { |
|
18616 resolvedBaseClassName = oldContext({name: this.baseClassName}); |
|
18617 result += "var $super = { $upcast: " + selfId + " };\n"; |
|
18618 result += "function $superCstr(){" + resolvedBaseClassName + |
|
18619 ".apply($super,arguments);if(!('$self' in $super)) $p.extendClassChain($super)}\n"; |
|
18620 metadata += className + ".$base = " + resolvedBaseClassName + ";\n"; |
|
18621 } else { |
|
18622 result += "function $superCstr(){$p.extendClassChain("+ selfId +")}\n"; |
|
18623 } |
|
18624 |
|
18625 if (this.owner.base) { |
|
18626 // base class name can be present, but class is not |
|
18627 staticDefinitions += "$p.extendStaticMembers(" + className + ", " + resolvedBaseClassName + ");\n"; |
|
18628 } |
|
18629 |
|
18630 var i, l, j, m; |
|
18631 |
|
18632 if (this.owner.interfaces) { |
|
18633 // interface name can be present, but interface is not |
|
18634 var resolvedInterfaces = [], resolvedInterface; |
|
18635 for (i = 0, l = this.interfacesNames.length; i < l; ++i) { |
|
18636 if (!this.owner.interfaces[i]) { |
|
18637 continue; |
|
18638 } |
|
18639 resolvedInterface = oldContext({name: this.interfacesNames[i]}); |
|
18640 resolvedInterfaces.push(resolvedInterface); |
|
18641 staticDefinitions += "$p.extendInterfaceMembers(" + className + ", " + resolvedInterface + ");\n"; |
|
18642 } |
|
18643 metadata += className + ".$interfaces = [" + resolvedInterfaces.join(", ") + "];\n"; |
|
18644 } |
|
18645 |
|
18646 if (this.functions.length > 0) { |
|
18647 result += this.functions.join('\n') + '\n'; |
|
18648 } |
|
18649 |
|
18650 sortByWeight(this.innerClasses); |
|
18651 for (i = 0, l = this.innerClasses.length; i < l; ++i) { |
|
18652 var innerClass = this.innerClasses[i]; |
|
18653 if (innerClass.isStatic) { |
|
18654 staticDefinitions += className + "." + innerClass.name + " = " + innerClass + ";\n"; |
|
18655 result += selfId + "." + innerClass.name + " = " + className + "." + innerClass.name + ";\n"; |
|
18656 } else { |
|
18657 result += selfId + "." + innerClass.name + " = " + innerClass + ";\n"; |
|
18658 } |
|
18659 } |
|
18660 |
|
18661 for (i = 0, l = this.fields.length; i < l; ++i) { |
|
18662 var field = this.fields[i]; |
|
18663 if (field.isStatic) { |
|
18664 staticDefinitions += className + "." + field.definitions.join(";\n" + className + ".") + ";\n"; |
|
18665 for (j = 0, m = field.definitions.length; j < m; ++j) { |
|
18666 var fieldName = field.definitions[j].name, staticName = className + "." + fieldName; |
|
18667 result += "$p.defineProperty(" + selfId + ", '" + fieldName + "', {" + |
|
18668 "get: function(){return " + staticName + "}, " + |
|
18669 "set: function(val){" + staticName + " = val}});\n"; |
|
18670 } |
|
18671 } else { |
|
18672 result += selfId + "." + field.definitions.join(";\n" + selfId + ".") + ";\n"; |
|
18673 } |
|
18674 } |
|
18675 var methodOverloads = {}; |
|
18676 for (i = 0, l = this.methods.length; i < l; ++i) { |
|
18677 var method = this.methods[i]; |
|
18678 var overload = methodOverloads[method.name]; |
|
18679 var methodId = method.name + "$" + method.params.params.length; |
|
18680 if (overload) { |
|
18681 ++overload; |
|
18682 methodId += "_" + overload; |
|
18683 } else { |
|
18684 overload = 1; |
|
18685 } |
|
18686 method.methodId = methodId; |
|
18687 methodOverloads[method.name] = overload; |
|
18688 if (method.isStatic) { |
|
18689 staticDefinitions += method; |
|
18690 staticDefinitions += "$p.addMethod(" + className + ", '" + method.name + "', " + methodId + ");\n"; |
|
18691 result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ");\n"; |
|
18692 } else { |
|
18693 result += method; |
|
18694 result += "$p.addMethod(" + selfId + ", '" + method.name + "', " + methodId + ");\n"; |
|
18695 } |
|
18696 } |
|
18697 result += trim(this.misc.tail); |
|
18698 |
|
18699 if (this.cstrs.length > 0) { |
|
18700 result += this.cstrs.join('\n') + '\n'; |
|
18701 } |
|
18702 |
|
18703 result += "function $constr() {\n"; |
|
18704 var cstrsIfs = []; |
|
18705 for (i = 0, l = this.cstrs.length; i < l; ++i) { |
|
18706 var paramsLength = this.cstrs[i].params.params.length; |
|
18707 cstrsIfs.push("if(arguments.length === " + paramsLength + ") { " + |
|
18708 "$constr_" + paramsLength + ".apply(" + selfId + ", arguments); }"); |
|
18709 } |
|
18710 if(cstrsIfs.length > 0) { |
|
18711 result += cstrsIfs.join(" else ") + " else "; |
|
18712 } |
|
18713 // ??? add check if length is 0, otherwise fail |
|
18714 result += "$superCstr();\n}\n"; |
|
18715 result += "$constr.apply(null, arguments);\n"; |
|
18716 |
|
18717 replaceContext = oldContext; |
|
18718 return "(function() {\n" + |
|
18719 "function " + className + "() {\n" + result + "}\n" + |
|
18720 staticDefinitions + |
|
18721 metadata + |
|
18722 "return " + className + ";\n" + |
|
18723 "})()"; |
|
18724 }; |
|
18725 |
|
18726 transformClassBody = function(body, name, baseName, interfaces) { |
|
18727 var declarations = body.substring(1, body.length - 1); |
|
18728 declarations = extractClassesAndMethods(declarations); |
|
18729 declarations = extractConstructors(declarations, name); |
|
18730 var methods = [], classes = [], cstrs = [], functions = []; |
|
18731 declarations = declarations.replace(/"([DEGH])(\d+)"/g, function(all, type, index) { |
|
18732 if(type === 'D') { methods.push(index); } |
|
18733 else if(type === 'E') { classes.push(index); } |
|
18734 else if(type === 'H') { functions.push(index); } |
|
18735 else { cstrs.push(index); } |
|
18736 return ""; |
|
18737 }); |
|
18738 var fields = declarations.replace(/^(?:\s*;)+/, "").split(/;(?:\s*;)*/g); |
|
18739 var baseClassName, interfacesNames; |
|
18740 var i; |
|
18741 |
|
18742 if(baseName !== undef) { |
|
18743 baseClassName = baseName.replace(/^\s*extends\s+([A-Za-z_$][\w$]*\b(?:\s*\.\s*[A-Za-z_$][\w$]*\b)*)\s*$/g, "$1"); |
|
18744 } |
|
18745 |
|
18746 if(interfaces !== undef) { |
|
18747 interfacesNames = interfaces.replace(/^\s*implements\s+(.+?)\s*$/g, "$1").split(/\s*,\s*/g); |
|
18748 } |
|
18749 |
|
18750 for(i = 0; i < functions.length; ++i) { |
|
18751 functions[i] = transformFunction(atoms[functions[i]]); |
|
18752 } |
|
18753 for(i = 0; i < methods.length; ++i) { |
|
18754 methods[i] = transformClassMethod(atoms[methods[i]]); |
|
18755 } |
|
18756 for(i = 0; i < fields.length - 1; ++i) { |
|
18757 var field = trimSpaces(fields[i]); |
|
18758 fields[i] = transformClassField(field.middle); |
|
18759 } |
|
18760 var tail = fields.pop(); |
|
18761 for(i = 0; i < cstrs.length; ++i) { |
|
18762 cstrs[i] = transformConstructor(atoms[cstrs[i]]); |
|
18763 } |
|
18764 for(i = 0; i < classes.length; ++i) { |
|
18765 classes[i] = transformInnerClass(atoms[classes[i]]); |
|
18766 } |
|
18767 |
|
18768 return new AstClassBody(name, baseClassName, interfacesNames, functions, methods, fields, cstrs, |
|
18769 classes, { tail: tail }); |
|
18770 }; |
|
18771 |
|
18772 function AstInterface(name, body) { |
|
18773 this.name = name; |
|
18774 this.body = body; |
|
18775 body.owner = this; |
|
18776 } |
|
18777 AstInterface.prototype.toString = function() { |
|
18778 return "var " + this.name + " = " + this.body + ";\n" + |
|
18779 "$p." + this.name + " = " + this.name + ";\n"; |
|
18780 }; |
|
18781 function AstClass(name, body) { |
|
18782 this.name = name; |
|
18783 this.body = body; |
|
18784 body.owner = this; |
|
18785 } |
|
18786 AstClass.prototype.toString = function() { |
|
18787 return "var " + this.name + " = " + this.body + ";\n" + |
|
18788 "$p." + this.name + " = " + this.name + ";\n"; |
|
18789 }; |
|
18790 |
|
18791 function transformGlobalClass(class_) { |
|
18792 var m = classesRegex.exec(class_); // 1 - attr, 2 - class|int, 3 - name, 4 - extends, 5 - implements, 6 - body |
|
18793 classesRegex.lastIndex = 0; |
|
18794 var body = atoms[getAtomIndex(m[6])]; |
|
18795 var oldClassId = currentClassId, newClassId = generateClassId(); |
|
18796 currentClassId = newClassId; |
|
18797 var globalClass; |
|
18798 if(m[2] === "interface") { |
|
18799 globalClass = new AstInterface(m[3], transformInterfaceBody(body, m[3], m[4]) ); |
|
18800 } else { |
|
18801 globalClass = new AstClass(m[3], transformClassBody(body, m[3], m[4], m[5]) ); |
|
18802 } |
|
18803 appendClass(globalClass, newClassId, oldClassId); |
|
18804 currentClassId = oldClassId; |
|
18805 return globalClass; |
|
18806 } |
|
18807 |
|
18808 function AstMethod(name, params, body) { |
|
18809 this.name = name; |
|
18810 this.params = params; |
|
18811 this.body = body; |
|
18812 } |
|
18813 AstMethod.prototype.toString = function(){ |
|
18814 var paramNames = appendToLookupTable({}, this.params.getNames()); |
|
18815 var oldContext = replaceContext; |
|
18816 replaceContext = function (subject) { |
|
18817 return paramNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); |
|
18818 }; |
|
18819 var result = "function " + this.name + this.params + " " + this.body + "\n" + |
|
18820 "$p." + this.name + " = " + this.name + ";"; |
|
18821 replaceContext = oldContext; |
|
18822 return result; |
|
18823 }; |
|
18824 |
|
18825 function transformGlobalMethod(method) { |
|
18826 var m = methodsRegex.exec(method); |
|
18827 var result = |
|
18828 methodsRegex.lastIndex = 0; |
|
18829 return new AstMethod(m[3], transformParams(atoms[getAtomIndex(m[4])]), |
|
18830 transformStatementsBlock(atoms[getAtomIndex(m[6])])); |
|
18831 } |
|
18832 |
|
18833 function preStatementsTransform(statements) { |
|
18834 var s = statements; |
|
18835 // turns multiple catch blocks into one, because we have no way to properly get into them anyway. |
|
18836 s = s.replace(/\b(catch\s*"B\d+"\s*"A\d+")(\s*catch\s*"B\d+"\s*"A\d+")+/g, "$1"); |
|
18837 return s; |
|
18838 } |
|
18839 |
|
18840 function AstForStatement(argument, misc) { |
|
18841 this.argument = argument; |
|
18842 this.misc = misc; |
|
18843 } |
|
18844 AstForStatement.prototype.toString = function() { |
|
18845 return this.misc.prefix + this.argument.toString(); |
|
18846 }; |
|
18847 function AstCatchStatement(argument, misc) { |
|
18848 this.argument = argument; |
|
18849 this.misc = misc; |
|
18850 } |
|
18851 AstCatchStatement.prototype.toString = function() { |
|
18852 return this.misc.prefix + this.argument.toString(); |
|
18853 }; |
|
18854 function AstPrefixStatement(name, argument, misc) { |
|
18855 this.name = name; |
|
18856 this.argument = argument; |
|
18857 this.misc = misc; |
|
18858 } |
|
18859 AstPrefixStatement.prototype.toString = function() { |
|
18860 var result = this.misc.prefix; |
|
18861 if(this.argument !== undef) { |
|
18862 result += this.argument.toString(); |
|
18863 } |
|
18864 return result; |
|
18865 }; |
|
18866 function AstSwitchCase(expr) { |
|
18867 this.expr = expr; |
|
18868 } |
|
18869 AstSwitchCase.prototype.toString = function() { |
|
18870 return "case " + this.expr + ":"; |
|
18871 }; |
|
18872 function AstLabel(label) { |
|
18873 this.label = label; |
|
18874 } |
|
18875 AstLabel.prototype.toString = function() { |
|
18876 return this.label; |
|
18877 }; |
|
18878 |
|
18879 transformStatements = function(statements, transformMethod, transformClass) { |
|
18880 var nextStatement = new RegExp(/\b(catch|for|if|switch|while|with)\s*"B(\d+)"|\b(do|else|finally|return|throw|try|break|continue)\b|("[ADEH](\d+)")|\b(case)\s+([^:]+):|\b([A-Za-z_$][\w$]*\s*:)|(;)/g); |
|
18881 var res = []; |
|
18882 statements = preStatementsTransform(statements); |
|
18883 var lastIndex = 0, m, space; |
|
18884 // m contains the matches from the nextStatement regexp, null if there are no matches. |
|
18885 // nextStatement.exec starts searching at nextStatement.lastIndex. |
|
18886 while((m = nextStatement.exec(statements)) !== null) { |
|
18887 if(m[1] !== undef) { // catch, for ... |
|
18888 var i = statements.lastIndexOf('"B', nextStatement.lastIndex); |
|
18889 var statementsPrefix = statements.substring(lastIndex, i); |
|
18890 if(m[1] === "for") { |
|
18891 res.push(new AstForStatement(transformForExpression(atoms[m[2]]), |
|
18892 { prefix: statementsPrefix }) ); |
|
18893 } else if(m[1] === "catch") { |
|
18894 res.push(new AstCatchStatement(transformParams(atoms[m[2]]), |
|
18895 { prefix: statementsPrefix }) ); |
|
18896 } else { |
|
18897 res.push(new AstPrefixStatement(m[1], transformExpression(atoms[m[2]]), |
|
18898 { prefix: statementsPrefix }) ); |
|
18899 } |
|
18900 } else if(m[3] !== undef) { // do, else, ... |
|
18901 res.push(new AstPrefixStatement(m[3], undef, |
|
18902 { prefix: statements.substring(lastIndex, nextStatement.lastIndex) }) ); |
|
18903 } else if(m[4] !== undef) { // block, class and methods |
|
18904 space = statements.substring(lastIndex, nextStatement.lastIndex - m[4].length); |
|
18905 if(trim(space).length !== 0) { continue; } // avoiding new type[] {} construct |
|
18906 res.push(space); |
|
18907 var kind = m[4].charAt(1), atomIndex = m[5]; |
|
18908 if(kind === 'D') { |
|
18909 res.push(transformMethod(atoms[atomIndex])); |
|
18910 } else if(kind === 'E') { |
|
18911 res.push(transformClass(atoms[atomIndex])); |
|
18912 } else if(kind === 'H') { |
|
18913 res.push(transformFunction(atoms[atomIndex])); |
|
18914 } else { |
|
18915 res.push(transformStatementsBlock(atoms[atomIndex])); |
|
18916 } |
|
18917 } else if(m[6] !== undef) { // switch case |
|
18918 res.push(new AstSwitchCase(transformExpression(trim(m[7])))); |
|
18919 } else if(m[8] !== undef) { // label |
|
18920 space = statements.substring(lastIndex, nextStatement.lastIndex - m[8].length); |
|
18921 if(trim(space).length !== 0) { continue; } // avoiding ?: construct |
|
18922 res.push(new AstLabel(statements.substring(lastIndex, nextStatement.lastIndex)) ); |
|
18923 } else { // semicolon |
|
18924 var statement = trimSpaces(statements.substring(lastIndex, nextStatement.lastIndex - 1)); |
|
18925 res.push(statement.left); |
|
18926 res.push(transformStatement(statement.middle)); |
|
18927 res.push(statement.right + ";"); |
|
18928 } |
|
18929 lastIndex = nextStatement.lastIndex; |
|
18930 } |
|
18931 var statementsTail = trimSpaces(statements.substring(lastIndex)); |
|
18932 res.push(statementsTail.left); |
|
18933 if(statementsTail.middle !== "") { |
|
18934 res.push(transformStatement(statementsTail.middle)); |
|
18935 res.push(";" + statementsTail.right); |
|
18936 } |
|
18937 return res; |
|
18938 }; |
|
18939 |
|
18940 function getLocalNames(statements) { |
|
18941 var localNames = []; |
|
18942 for(var i=0,l=statements.length;i<l;++i) { |
|
18943 var statement = statements[i]; |
|
18944 if(statement instanceof AstVar) { |
|
18945 localNames = localNames.concat(statement.getNames()); |
|
18946 } else if(statement instanceof AstForStatement && |
|
18947 statement.argument.initStatement instanceof AstVar) { |
|
18948 localNames = localNames.concat(statement.argument.initStatement.getNames()); |
|
18949 } else if(statement instanceof AstInnerInterface || statement instanceof AstInnerClass || |
|
18950 statement instanceof AstInterface || statement instanceof AstClass || |
|
18951 statement instanceof AstMethod || statement instanceof AstFunction) { |
|
18952 localNames.push(statement.name); |
|
18953 } |
|
18954 } |
|
18955 return appendToLookupTable({}, localNames); |
|
18956 } |
|
18957 |
|
18958 function AstStatementsBlock(statements) { |
|
18959 this.statements = statements; |
|
18960 } |
|
18961 AstStatementsBlock.prototype.toString = function() { |
|
18962 var localNames = getLocalNames(this.statements); |
|
18963 var oldContext = replaceContext; |
|
18964 |
|
18965 // replacing context only when necessary |
|
18966 if(!isLookupTableEmpty(localNames)) { |
|
18967 replaceContext = function (subject) { |
|
18968 return localNames.hasOwnProperty(subject.name) ? subject.name : oldContext(subject); |
|
18969 }; |
|
18970 } |
|
18971 |
|
18972 var result = "{\n" + this.statements.join('') + "\n}"; |
|
18973 replaceContext = oldContext; |
|
18974 return result; |
|
18975 }; |
|
18976 |
|
18977 transformStatementsBlock = function(block) { |
|
18978 var content = trimSpaces(block.substring(1, block.length - 1)); |
|
18979 return new AstStatementsBlock(transformStatements(content.middle)); |
|
18980 }; |
|
18981 |
|
18982 function AstRoot(statements) { |
|
18983 this.statements = statements; |
|
18984 } |
|
18985 AstRoot.prototype.toString = function() { |
|
18986 var classes = [], otherStatements = [], statement; |
|
18987 for (var i = 0, len = this.statements.length; i < len; ++i) { |
|
18988 statement = this.statements[i]; |
|
18989 if (statement instanceof AstClass || statement instanceof AstInterface) { |
|
18990 classes.push(statement); |
|
18991 } else { |
|
18992 otherStatements.push(statement); |
|
18993 } |
|
18994 } |
|
18995 sortByWeight(classes); |
|
18996 |
|
18997 var localNames = getLocalNames(this.statements); |
|
18998 replaceContext = function (subject) { |
|
18999 var name = subject.name; |
|
19000 if(localNames.hasOwnProperty(name)) { |
|
19001 return name; |
|
19002 } |
|
19003 if(globalMembers.hasOwnProperty(name) || |
|
19004 PConstants.hasOwnProperty(name) || |
|
19005 defaultScope.hasOwnProperty(name)) { |
|
19006 return "$p." + name; |
|
19007 } |
|
19008 return name; |
|
19009 }; |
|
19010 var result = "// this code was autogenerated from PJS\n" + |
|
19011 "(function($p) {\n" + |
|
19012 classes.join('') + "\n" + |
|
19013 otherStatements.join('') + "\n})"; |
|
19014 replaceContext = null; |
|
19015 return result; |
|
19016 }; |
|
19017 |
|
19018 transformMain = function() { |
|
19019 var statements = extractClassesAndMethods(atoms[0]); |
|
19020 statements = statements.replace(/\bimport\s+[^;]+;/g, ""); |
|
19021 return new AstRoot( transformStatements(statements, |
|
19022 transformGlobalMethod, transformGlobalClass) ); |
|
19023 }; |
|
19024 |
|
19025 function generateMetadata(ast) { |
|
19026 var globalScope = {}; |
|
19027 var id, class_; |
|
19028 for(id in declaredClasses) { |
|
19029 if(declaredClasses.hasOwnProperty(id)) { |
|
19030 class_ = declaredClasses[id]; |
|
19031 var scopeId = class_.scopeId, name = class_.name; |
|
19032 if(scopeId) { |
|
19033 var scope = declaredClasses[scopeId]; |
|
19034 class_.scope = scope; |
|
19035 if(scope.inScope === undef) { |
|
19036 scope.inScope = {}; |
|
19037 } |
|
19038 scope.inScope[name] = class_; |
|
19039 } else { |
|
19040 globalScope[name] = class_; |
|
19041 } |
|
19042 } |
|
19043 } |
|
19044 |
|
19045 function findInScopes(class_, name) { |
|
19046 var parts = name.split('.'); |
|
19047 var currentScope = class_.scope, found; |
|
19048 while(currentScope) { |
|
19049 if(currentScope.hasOwnProperty(parts[0])) { |
|
19050 found = currentScope[parts[0]]; break; |
|
19051 } |
|
19052 currentScope = currentScope.scope; |
|
19053 } |
|
19054 if(found === undef) { |
|
19055 found = globalScope[parts[0]]; |
|
19056 } |
|
19057 for(var i=1,l=parts.length;i<l && found;++i) { |
|
19058 found = found.inScope[parts[i]]; |
|
19059 } |
|
19060 return found; |
|
19061 } |
|
19062 |
|
19063 for(id in declaredClasses) { |
|
19064 if(declaredClasses.hasOwnProperty(id)) { |
|
19065 class_ = declaredClasses[id]; |
|
19066 var baseClassName = class_.body.baseClassName; |
|
19067 if(baseClassName) { |
|
19068 var parent = findInScopes(class_, baseClassName); |
|
19069 if (parent) { |
|
19070 class_.base = parent; |
|
19071 if (!parent.derived) { |
|
19072 parent.derived = []; |
|
19073 } |
|
19074 parent.derived.push(class_); |
|
19075 } |
|
19076 } |
|
19077 var interfacesNames = class_.body.interfacesNames, |
|
19078 interfaces = [], i, l; |
|
19079 if (interfacesNames && interfacesNames.length > 0) { |
|
19080 for (i = 0, l = interfacesNames.length; i < l; ++i) { |
|
19081 var interface_ = findInScopes(class_, interfacesNames[i]); |
|
19082 interfaces.push(interface_); |
|
19083 if (!interface_) { |
|
19084 continue; |
|
19085 } |
|
19086 if (!interface_.derived) { |
|
19087 interface_.derived = []; |
|
19088 } |
|
19089 interface_.derived.push(class_); |
|
19090 } |
|
19091 if (interfaces.length > 0) { |
|
19092 class_.interfaces = interfaces; |
|
19093 } |
|
19094 } |
|
19095 } |
|
19096 } |
|
19097 } |
|
19098 |
|
19099 function setWeight(ast) { |
|
19100 var queue = [], tocheck = {}; |
|
19101 var id, scopeId, class_; |
|
19102 // queue most inner and non-inherited |
|
19103 for (id in declaredClasses) { |
|
19104 if (declaredClasses.hasOwnProperty(id)) { |
|
19105 class_ = declaredClasses[id]; |
|
19106 if (!class_.inScope && !class_.derived) { |
|
19107 queue.push(id); |
|
19108 class_.weight = 0; |
|
19109 } else { |
|
19110 var dependsOn = []; |
|
19111 if (class_.inScope) { |
|
19112 for (scopeId in class_.inScope) { |
|
19113 if (class_.inScope.hasOwnProperty(scopeId)) { |
|
19114 dependsOn.push(class_.inScope[scopeId]); |
|
19115 } |
|
19116 } |
|
19117 } |
|
19118 if (class_.derived) { |
|
19119 dependsOn = dependsOn.concat(class_.derived); |
|
19120 } |
|
19121 tocheck[id] = dependsOn; |
|
19122 } |
|
19123 } |
|
19124 } |
|
19125 function removeDependentAndCheck(targetId, from) { |
|
19126 var dependsOn = tocheck[targetId]; |
|
19127 if (!dependsOn) { |
|
19128 return false; // no need to process |
|
19129 } |
|
19130 var i = dependsOn.indexOf(from); |
|
19131 if (i < 0) { |
|
19132 return false; |
|
19133 } |
|
19134 dependsOn.splice(i, 1); |
|
19135 if (dependsOn.length > 0) { |
|
19136 return false; |
|
19137 } |
|
19138 delete tocheck[targetId]; |
|
19139 return true; |
|
19140 } |
|
19141 while (queue.length > 0) { |
|
19142 id = queue.shift(); |
|
19143 class_ = declaredClasses[id]; |
|
19144 if (class_.scopeId && removeDependentAndCheck(class_.scopeId, class_)) { |
|
19145 queue.push(class_.scopeId); |
|
19146 declaredClasses[class_.scopeId].weight = class_.weight + 1; |
|
19147 } |
|
19148 if (class_.base && removeDependentAndCheck(class_.base.classId, class_)) { |
|
19149 queue.push(class_.base.classId); |
|
19150 class_.base.weight = class_.weight + 1; |
|
19151 } |
|
19152 if (class_.interfaces) { |
|
19153 var i, l; |
|
19154 for (i = 0, l = class_.interfaces.length; i < l; ++i) { |
|
19155 if (!class_.interfaces[i] || |
|
19156 !removeDependentAndCheck(class_.interfaces[i].classId, class_)) { |
|
19157 continue; |
|
19158 } |
|
19159 queue.push(class_.interfaces[i].classId); |
|
19160 class_.interfaces[i].weight = class_.weight + 1; |
|
19161 } |
|
19162 } |
|
19163 } |
|
19164 } |
|
19165 |
|
19166 var transformed = transformMain(); |
|
19167 generateMetadata(transformed); |
|
19168 setWeight(transformed); |
|
19169 |
|
19170 var redendered = transformed.toString(); |
|
19171 |
|
19172 // remove empty extra lines with space |
|
19173 redendered = redendered.replace(/\s*\n(?:[\t ]*\n)+/g, "\n\n"); |
|
19174 |
|
19175 return injectStrings(redendered, strings); |
|
19176 }// Parser ends |
|
19177 |
|
19178 function preprocessCode(aCode, sketch) { |
|
19179 // Parse out @pjs directive, if any. |
|
19180 var dm = new RegExp(/\/\*\s*@pjs\s+((?:[^\*]|\*+[^\*\/])*)\*\//g).exec(aCode); |
|
19181 if (dm && dm.length === 2) { |
|
19182 // masks contents of a JSON to be replaced later |
|
19183 // to protect the contents from further parsing |
|
19184 var jsonItems = [], |
|
19185 directives = dm.splice(1, 2)[0].replace(/\{([\s\S]*?)\}/g, (function() { |
|
19186 return function(all, item) { |
|
19187 jsonItems.push(item); |
|
19188 return "{" + (jsonItems.length-1) + "}"; |
|
19189 }; |
|
19190 }())).replace('\n', '').replace('\r', '').split(";"); |
|
19191 |
|
19192 // We'll L/RTrim, and also remove any surrounding double quotes (e.g., just take string contents) |
|
19193 var clean = function(s) { |
|
19194 return s.replace(/^\s*["']?/, '').replace(/["']?\s*$/, ''); |
|
19195 }; |
|
19196 |
|
19197 for (var i = 0, dl = directives.length; i < dl; i++) { |
|
19198 var pair = directives[i].split('='); |
|
19199 if (pair && pair.length === 2) { |
|
19200 var key = clean(pair[0]), |
|
19201 value = clean(pair[1]), |
|
19202 list = []; |
|
19203 // A few directives require work beyond storying key/value pairings |
|
19204 if (key === "preload") { |
|
19205 list = value.split(','); |
|
19206 // All pre-loaded images will get put in imageCache, keyed on filename |
|
19207 for (var j = 0, jl = list.length; j < jl; j++) { |
|
19208 var imageName = clean(list[j]); |
|
19209 sketch.imageCache.add(imageName); |
|
19210 } |
|
19211 // fonts can be declared as a string containing a url, |
|
19212 // or a JSON object, containing a font name, and a url |
|
19213 } else if (key === "font") { |
|
19214 list = value.split(","); |
|
19215 for (var x = 0, xl = list.length; x < xl; x++) { |
|
19216 var fontName = clean(list[x]), |
|
19217 index = /^\{(\d*?)\}$/.exec(fontName); |
|
19218 // if index is not null, send JSON, otherwise, send string |
|
19219 PFont.preloading.add(index ? JSON.parse("{" + jsonItems[index[1]] + "}") : fontName); |
|
19220 } |
|
19221 } else if (key === "pauseOnBlur") { |
|
19222 sketch.options.pauseOnBlur = value === "true"; |
|
19223 } else if (key === "globalKeyEvents") { |
|
19224 sketch.options.globalKeyEvents = value === "true"; |
|
19225 } else if (key.substring(0, 6) === "param-") { |
|
19226 sketch.params[key.substring(6)] = value; |
|
19227 } else { |
|
19228 sketch.options[key] = value; |
|
19229 } |
|
19230 } |
|
19231 } |
|
19232 } |
|
19233 return aCode; |
|
19234 } |
|
19235 |
|
19236 // Parse/compiles Processing (Java-like) syntax to JavaScript syntax |
|
19237 Processing.compile = function(pdeCode) { |
|
19238 var sketch = new Processing.Sketch(); |
|
19239 var code = preprocessCode(pdeCode, sketch); |
|
19240 var compiledPde = parseProcessing(code); |
|
19241 sketch.sourceCode = compiledPde; |
|
19242 return sketch; |
|
19243 }; |
|
19244 //#endif |
|
19245 |
|
19246 // tinylog lite JavaScript library |
|
19247 // http://purl.eligrey.com/tinylog/lite |
|
19248 /*global tinylog,print*/ |
|
19249 var tinylogLite = (function() { |
|
19250 "use strict"; |
|
19251 |
|
19252 var tinylogLite = {}, |
|
19253 undef = "undefined", |
|
19254 func = "function", |
|
19255 False = !1, |
|
19256 True = !0, |
|
19257 logLimit = 512, |
|
19258 log = "log"; |
|
19259 |
|
19260 if (typeof tinylog !== undef && typeof tinylog[log] === func) { |
|
19261 // pre-existing tinylog present |
|
19262 tinylogLite[log] = tinylog[log]; |
|
19263 } else if (typeof document !== undef && !document.fake) { |
|
19264 (function() { |
|
19265 // DOM document |
|
19266 var doc = document, |
|
19267 |
|
19268 $div = "div", |
|
19269 $style = "style", |
|
19270 $title = "title", |
|
19271 |
|
19272 containerStyles = { |
|
19273 zIndex: 10000, |
|
19274 position: "fixed", |
|
19275 bottom: "0px", |
|
19276 width: "100%", |
|
19277 height: "15%", |
|
19278 fontFamily: "sans-serif", |
|
19279 color: "#ccc", |
|
19280 backgroundColor: "black" |
|
19281 }, |
|
19282 outputStyles = { |
|
19283 position: "relative", |
|
19284 fontFamily: "monospace", |
|
19285 overflow: "auto", |
|
19286 height: "100%", |
|
19287 paddingTop: "5px" |
|
19288 }, |
|
19289 resizerStyles = { |
|
19290 height: "5px", |
|
19291 marginTop: "-5px", |
|
19292 cursor: "n-resize", |
|
19293 backgroundColor: "darkgrey" |
|
19294 }, |
|
19295 closeButtonStyles = { |
|
19296 position: "absolute", |
|
19297 top: "5px", |
|
19298 right: "20px", |
|
19299 color: "#111", |
|
19300 MozBorderRadius: "4px", |
|
19301 webkitBorderRadius: "4px", |
|
19302 borderRadius: "4px", |
|
19303 cursor: "pointer", |
|
19304 fontWeight: "normal", |
|
19305 textAlign: "center", |
|
19306 padding: "3px 5px", |
|
19307 backgroundColor: "#333", |
|
19308 fontSize: "12px" |
|
19309 }, |
|
19310 entryStyles = { |
|
19311 //borderBottom: "1px solid #d3d3d3", |
|
19312 minHeight: "16px" |
|
19313 }, |
|
19314 entryTextStyles = { |
|
19315 fontSize: "12px", |
|
19316 margin: "0 8px 0 8px", |
|
19317 maxWidth: "100%", |
|
19318 whiteSpace: "pre-wrap", |
|
19319 overflow: "auto" |
|
19320 }, |
|
19321 |
|
19322 view = doc.defaultView, |
|
19323 docElem = doc.documentElement, |
|
19324 docElemStyle = docElem[$style], |
|
19325 |
|
19326 setStyles = function() { |
|
19327 var i = arguments.length, |
|
19328 elemStyle, styles, style; |
|
19329 |
|
19330 while (i--) { |
|
19331 styles = arguments[i--]; |
|
19332 elemStyle = arguments[i][$style]; |
|
19333 |
|
19334 for (style in styles) { |
|
19335 if (styles.hasOwnProperty(style)) { |
|
19336 elemStyle[style] = styles[style]; |
|
19337 } |
|
19338 } |
|
19339 } |
|
19340 }, |
|
19341 |
|
19342 observer = function(obj, event, handler) { |
|
19343 if (obj.addEventListener) { |
|
19344 obj.addEventListener(event, handler, False); |
|
19345 } else if (obj.attachEvent) { |
|
19346 obj.attachEvent("on" + event, handler); |
|
19347 } |
|
19348 return [obj, event, handler]; |
|
19349 }, |
|
19350 unobserve = function(obj, event, handler) { |
|
19351 if (obj.removeEventListener) { |
|
19352 obj.removeEventListener(event, handler, False); |
|
19353 } else if (obj.detachEvent) { |
|
19354 obj.detachEvent("on" + event, handler); |
|
19355 } |
|
19356 }, |
|
19357 clearChildren = function(node) { |
|
19358 var children = node.childNodes, |
|
19359 child = children.length; |
|
19360 |
|
19361 while (child--) { |
|
19362 node.removeChild(children.item(0)); |
|
19363 } |
|
19364 }, |
|
19365 append = function(to, elem) { |
|
19366 return to.appendChild(elem); |
|
19367 }, |
|
19368 createElement = function(localName) { |
|
19369 return doc.createElement(localName); |
|
19370 }, |
|
19371 createTextNode = function(text) { |
|
19372 return doc.createTextNode(text); |
|
19373 }, |
|
19374 |
|
19375 createLog = tinylogLite[log] = function(message) { |
|
19376 // don't show output log until called once |
|
19377 var uninit, |
|
19378 originalPadding = docElemStyle.paddingBottom, |
|
19379 container = createElement($div), |
|
19380 containerStyle = container[$style], |
|
19381 resizer = append(container, createElement($div)), |
|
19382 output = append(container, createElement($div)), |
|
19383 closeButton = append(container, createElement($div)), |
|
19384 resizingLog = False, |
|
19385 previousHeight = False, |
|
19386 previousScrollTop = False, |
|
19387 messages = 0, |
|
19388 |
|
19389 updateSafetyMargin = function() { |
|
19390 // have a blank space large enough to fit the output box at the page bottom |
|
19391 docElemStyle.paddingBottom = container.clientHeight + "px"; |
|
19392 }, |
|
19393 setContainerHeight = function(height) { |
|
19394 var viewHeight = view.innerHeight, |
|
19395 resizerHeight = resizer.clientHeight; |
|
19396 |
|
19397 // constrain the container inside the viewport's dimensions |
|
19398 if (height < 0) { |
|
19399 height = 0; |
|
19400 } else if (height + resizerHeight > viewHeight) { |
|
19401 height = viewHeight - resizerHeight; |
|
19402 } |
|
19403 |
|
19404 containerStyle.height = height / viewHeight * 100 + "%"; |
|
19405 |
|
19406 updateSafetyMargin(); |
|
19407 }, |
|
19408 observers = [ |
|
19409 observer(doc, "mousemove", function(evt) { |
|
19410 if (resizingLog) { |
|
19411 setContainerHeight(view.innerHeight - evt.clientY); |
|
19412 output.scrollTop = previousScrollTop; |
|
19413 } |
|
19414 }), |
|
19415 |
|
19416 observer(doc, "mouseup", function() { |
|
19417 if (resizingLog) { |
|
19418 resizingLog = previousScrollTop = False; |
|
19419 } |
|
19420 }), |
|
19421 |
|
19422 observer(resizer, "dblclick", function(evt) { |
|
19423 evt.preventDefault(); |
|
19424 |
|
19425 if (previousHeight) { |
|
19426 setContainerHeight(previousHeight); |
|
19427 previousHeight = False; |
|
19428 } else { |
|
19429 previousHeight = container.clientHeight; |
|
19430 containerStyle.height = "0px"; |
|
19431 } |
|
19432 }), |
|
19433 |
|
19434 observer(resizer, "mousedown", function(evt) { |
|
19435 evt.preventDefault(); |
|
19436 resizingLog = True; |
|
19437 previousScrollTop = output.scrollTop; |
|
19438 }), |
|
19439 |
|
19440 observer(resizer, "contextmenu", function() { |
|
19441 resizingLog = False; |
|
19442 }), |
|
19443 |
|
19444 observer(closeButton, "click", function() { |
|
19445 uninit(); |
|
19446 }) |
|
19447 ]; |
|
19448 |
|
19449 uninit = function() { |
|
19450 // remove observers |
|
19451 var i = observers.length; |
|
19452 |
|
19453 while (i--) { |
|
19454 unobserve.apply(tinylogLite, observers[i]); |
|
19455 } |
|
19456 |
|
19457 // remove tinylog lite from the DOM |
|
19458 docElem.removeChild(container); |
|
19459 docElemStyle.paddingBottom = originalPadding; |
|
19460 |
|
19461 clearChildren(output); |
|
19462 clearChildren(container); |
|
19463 |
|
19464 tinylogLite[log] = createLog; |
|
19465 }; |
|
19466 |
|
19467 setStyles( |
|
19468 container, containerStyles, output, outputStyles, resizer, resizerStyles, closeButton, closeButtonStyles); |
|
19469 |
|
19470 closeButton[$title] = "Close Log"; |
|
19471 append(closeButton, createTextNode("\u2716")); |
|
19472 |
|
19473 resizer[$title] = "Double-click to toggle log minimization"; |
|
19474 |
|
19475 docElem.insertBefore(container, docElem.firstChild); |
|
19476 |
|
19477 tinylogLite[log] = function(message) { |
|
19478 if (messages === logLimit) { |
|
19479 output.removeChild(output.firstChild); |
|
19480 } else { |
|
19481 messages++; |
|
19482 } |
|
19483 |
|
19484 var entry = append(output, createElement($div)), |
|
19485 entryText = append(entry, createElement($div)); |
|
19486 |
|
19487 entry[$title] = (new Date()).toLocaleTimeString(); |
|
19488 |
|
19489 setStyles( |
|
19490 entry, entryStyles, entryText, entryTextStyles); |
|
19491 |
|
19492 append(entryText, createTextNode(message)); |
|
19493 output.scrollTop = output.scrollHeight; |
|
19494 }; |
|
19495 |
|
19496 tinylogLite[log](message); |
|
19497 updateSafetyMargin(); |
|
19498 }; |
|
19499 }()); |
|
19500 } else if (typeof print === func) { // JS shell |
|
19501 tinylogLite[log] = print; |
|
19502 } |
|
19503 |
|
19504 return tinylogLite; |
|
19505 }()); |
|
19506 // end of tinylog lite JavaScript library |
|
19507 |
|
19508 Processing.logger = tinylogLite; |
|
19509 |
|
19510 Processing.version = "@VERSION@"; |
|
19511 |
|
19512 // Share lib space |
|
19513 Processing.lib = {}; |
|
19514 |
|
19515 Processing.registerLibrary = function(name, desc) { |
|
19516 Processing.lib[name] = desc; |
|
19517 |
|
19518 if(desc.hasOwnProperty("init")) { |
|
19519 desc.init(defaultScope); |
|
19520 } |
|
19521 }; |
|
19522 |
|
19523 // Store Processing instances. Only Processing.instances, |
|
19524 // Processing.getInstanceById are exposed. |
|
19525 Processing.instances = processingInstances; |
|
19526 |
|
19527 Processing.getInstanceById = function(name) { |
|
19528 return processingInstances[processingInstanceIds[name]]; |
|
19529 }; |
|
19530 |
|
19531 Processing.Sketch = function(attachFunction) { |
|
19532 this.attachFunction = attachFunction; // can be optional |
|
19533 this.options = { |
|
19534 pauseOnBlur: false, |
|
19535 globalKeyEvents: false |
|
19536 }; |
|
19537 |
|
19538 /* Optional Sketch event hooks: |
|
19539 * onLoad - parsing/preloading is done, before sketch starts |
|
19540 * onSetup - setup() has been called, before first draw() |
|
19541 * onPause - noLoop() has been called, pausing draw loop |
|
19542 * onLoop - loop() has been called, resuming draw loop |
|
19543 * onFrameStart - draw() loop about to begin |
|
19544 * onFrameEnd - draw() loop finished |
|
19545 * onExit - exit() done being called |
|
19546 */ |
|
19547 this.onLoad = nop; |
|
19548 this.onSetup = nop; |
|
19549 this.onPause = nop; |
|
19550 this.onLoop = nop; |
|
19551 this.onFrameStart = nop; |
|
19552 this.onFrameEnd = nop; |
|
19553 this.onExit = nop; |
|
19554 |
|
19555 this.params = {}; |
|
19556 this.imageCache = { |
|
19557 pending: 0, |
|
19558 images: {}, |
|
19559 // Opera requires special administration for preloading |
|
19560 operaCache: {}, |
|
19561 // Specify an optional img arg if the image is already loaded in the DOM, |
|
19562 // otherwise href will get loaded. |
|
19563 add: function(href, img) { |
|
19564 // Prevent muliple loads for an image, in case it gets |
|
19565 // preloaded more than once, or is added via JS and then preloaded. |
|
19566 if (this.images[href]) { |
|
19567 return; |
|
19568 } |
|
19569 |
|
19570 if (!isDOMPresent) { |
|
19571 this.images[href] = null; |
|
19572 } |
|
19573 |
|
19574 // No image in the DOM, kick-off a background load |
|
19575 if (!img) { |
|
19576 img = new Image(); |
|
19577 img.onload = (function(owner) { |
|
19578 return function() { |
|
19579 owner.pending--; |
|
19580 }; |
|
19581 }(this)); |
|
19582 this.pending++; |
|
19583 img.src = href; |
|
19584 } |
|
19585 |
|
19586 this.images[href] = img; |
|
19587 |
|
19588 // Opera will not load images until they are inserted into the DOM. |
|
19589 if (window.opera) { |
|
19590 var div = document.createElement("div"); |
|
19591 div.appendChild(img); |
|
19592 // we can't use "display: none", since that makes it invisible, and thus not load |
|
19593 div.style.position = "absolute"; |
|
19594 div.style.opacity = 0; |
|
19595 div.style.width = "1px"; |
|
19596 div.style.height= "1px"; |
|
19597 if (!this.operaCache[href]) { |
|
19598 document.body.appendChild(div); |
|
19599 this.operaCache[href] = div; |
|
19600 } |
|
19601 } |
|
19602 } |
|
19603 }; |
|
19604 this.sourceCode = undefined; |
|
19605 this.attach = function(processing) { |
|
19606 // either attachFunction or sourceCode must be present on attach |
|
19607 if(typeof this.attachFunction === "function") { |
|
19608 this.attachFunction(processing); |
|
19609 } else if(this.sourceCode) { |
|
19610 var func = ((new Function("return (" + this.sourceCode + ");"))()); |
|
19611 func(processing); |
|
19612 this.attachFunction = func; |
|
19613 } else { |
|
19614 throw "Unable to attach sketch to the processing instance"; |
|
19615 } |
|
19616 }; |
|
19617 //#if PARSER |
|
19618 this.toString = function() { |
|
19619 var i; |
|
19620 var code = "((function(Sketch) {\n"; |
|
19621 code += "var sketch = new Sketch(\n" + this.sourceCode + ");\n"; |
|
19622 for(i in this.options) { |
|
19623 if(this.options.hasOwnProperty(i)) { |
|
19624 var value = this.options[i]; |
|
19625 code += "sketch.options." + i + " = " + |
|
19626 (typeof value === 'string' ? '\"' + value + '\"' : "" + value) + ";\n"; |
|
19627 } |
|
19628 } |
|
19629 for(i in this.imageCache) { |
|
19630 if(this.options.hasOwnProperty(i)) { |
|
19631 code += "sketch.imageCache.add(\"" + i + "\");\n"; |
|
19632 } |
|
19633 } |
|
19634 // TODO serialize fonts |
|
19635 code += "return sketch;\n})(Processing.Sketch))"; |
|
19636 return code; |
|
19637 }; |
|
19638 //#endif |
|
19639 }; |
|
19640 |
|
19641 //#if PARSER |
|
19642 /** |
|
19643 * aggregate all source code into a single file, then rewrite that |
|
19644 * source and bind to canvas via new Processing(canvas, sourcestring). |
|
19645 * @param {CANVAS} canvas The html canvas element to bind to |
|
19646 * @param {String[]} source The array of files that must be loaded |
|
19647 */ |
|
19648 var loadSketchFromSources = function(canvas, sources) { |
|
19649 var code = [], errors = [], sourcesCount = sources.length, loaded = 0; |
|
19650 |
|
19651 function ajaxAsync(url, callback) { |
|
19652 var xhr = new XMLHttpRequest(); |
|
19653 xhr.onreadystatechange = function() { |
|
19654 if (xhr.readyState === 4) { |
|
19655 var error; |
|
19656 if (xhr.status !== 200 && xhr.status !== 0) { |
|
19657 error = "Invalid XHR status " + xhr.status; |
|
19658 } else if (xhr.responseText === "") { |
|
19659 // Give a hint when loading fails due to same-origin issues on file:/// urls |
|
19660 if ( ("withCredentials" in new XMLHttpRequest()) && |
|
19661 (new XMLHttpRequest()).withCredentials === false && |
|
19662 window.location.protocol === "file:" ) { |
|
19663 error = "XMLHttpRequest failure, possibly due to a same-origin policy violation. You can try loading this page in another browser, or load it from http://localhost using a local webserver. See the Processing.js README for a more detailed explanation of this problem and solutions."; |
|
19664 } else { |
|
19665 error = "File is empty."; |
|
19666 } |
|
19667 } |
|
19668 |
|
19669 callback(xhr.responseText, error); |
|
19670 } |
|
19671 }; |
|
19672 xhr.open("GET", url, true); |
|
19673 if (xhr.overrideMimeType) { |
|
19674 xhr.overrideMimeType("application/json"); |
|
19675 } |
|
19676 xhr.setRequestHeader("If-Modified-Since", "Fri, 01 Jan 1960 00:00:00 GMT"); // no cache |
|
19677 xhr.send(null); |
|
19678 } |
|
19679 |
|
19680 function loadBlock(index, filename) { |
|
19681 function callback(block, error) { |
|
19682 code[index] = block; |
|
19683 ++loaded; |
|
19684 if (error) { |
|
19685 errors.push(filename + " ==> " + error); |
|
19686 } |
|
19687 if (loaded === sourcesCount) { |
|
19688 if (errors.length === 0) { |
|
19689 try { |
|
19690 return new Processing(canvas, code.join("\n")); |
|
19691 } catch(e) { |
|
19692 throw "Processing.js: Unable to execute pjs sketch: " + e; |
|
19693 } |
|
19694 } else { |
|
19695 throw "Processing.js: Unable to load pjs sketch files: " + errors.join("\n"); |
|
19696 } |
|
19697 } |
|
19698 } |
|
19699 if (filename.charAt(0) === '#') { |
|
19700 // trying to get script from the element |
|
19701 var scriptElement = document.getElementById(filename.substring(1)); |
|
19702 if (scriptElement) { |
|
19703 callback(scriptElement.text || scriptElement.textContent); |
|
19704 } else { |
|
19705 callback("", "Unable to load pjs sketch: element with id \'" + filename.substring(1) + "\' was not found"); |
|
19706 } |
|
19707 return; |
|
19708 } |
|
19709 |
|
19710 ajaxAsync(filename, callback); |
|
19711 } |
|
19712 |
|
19713 for (var i = 0; i < sourcesCount; ++i) { |
|
19714 loadBlock(i, sources[i]); |
|
19715 } |
|
19716 }; |
|
19717 |
|
19718 /** |
|
19719 * Automatic initialization function. |
|
19720 */ |
|
19721 var init = function() { |
|
19722 document.removeEventListener('DOMContentLoaded', init, false); |
|
19723 |
|
19724 var canvas = document.getElementsByTagName('canvas'), |
|
19725 filenames; |
|
19726 |
|
19727 for (var i = 0, l = canvas.length; i < l; i++) { |
|
19728 // datasrc and data-src are deprecated. |
|
19729 var processingSources = canvas[i].getAttribute('data-processing-sources'); |
|
19730 if (processingSources === null) { |
|
19731 // Temporary fallback for datasrc and data-src |
|
19732 processingSources = canvas[i].getAttribute('data-src'); |
|
19733 if (processingSources === null) { |
|
19734 processingSources = canvas[i].getAttribute('datasrc'); |
|
19735 } |
|
19736 } |
|
19737 if (processingSources) { |
|
19738 filenames = processingSources.split(' '); |
|
19739 for (var j = 0; j < filenames.length;) { |
|
19740 if (filenames[j]) { |
|
19741 j++; |
|
19742 } else { |
|
19743 filenames.splice(j, 1); |
|
19744 } |
|
19745 } |
|
19746 loadSketchFromSources(canvas[i], filenames); |
|
19747 } |
|
19748 } |
|
19749 |
|
19750 // also process all <script>-indicated sketches, if there are any |
|
19751 var scripts = document.getElementsByTagName('script'); |
|
19752 var s, source, instance; |
|
19753 for (s = 0; s < scripts.length; s++) { |
|
19754 var script = scripts[s]; |
|
19755 if (!script.getAttribute) { |
|
19756 continue; |
|
19757 } |
|
19758 |
|
19759 var type = script.getAttribute("type"); |
|
19760 if (type && (type.toLowerCase() === "text/processing" || type.toLowerCase() === "application/processing")) { |
|
19761 var target = script.getAttribute("data-processing-target"); |
|
19762 canvas = undef; |
|
19763 if (target) { |
|
19764 canvas = document.getElementById(target); |
|
19765 } else { |
|
19766 var nextSibling = script.nextSibling; |
|
19767 while (nextSibling && nextSibling.nodeType !== 1) { |
|
19768 nextSibling = nextSibling.nextSibling; |
|
19769 } |
|
19770 if (nextSibling.nodeName.toLowerCase() === "canvas") { |
|
19771 canvas = nextSibling; |
|
19772 } |
|
19773 } |
|
19774 |
|
19775 if (canvas) { |
|
19776 if (script.getAttribute("src")) { |
|
19777 filenames = script.getAttribute("src").split(/\s+/); |
|
19778 loadSketchFromSources(canvas, filenames); |
|
19779 continue; |
|
19780 } |
|
19781 source = script.textContent || script.text; |
|
19782 instance = new Processing(canvas, source); |
|
19783 } |
|
19784 } |
|
19785 } |
|
19786 }; |
|
19787 |
|
19788 /** |
|
19789 * Make loadSketchFromSources publically visible |
|
19790 */ |
|
19791 Processing.loadSketchFromSources = loadSketchFromSources; |
|
19792 |
|
19793 /** |
|
19794 * Disable the automatic loading of all sketches on the page |
|
19795 */ |
|
19796 Processing.disableInit = function() { |
|
19797 if(isDOMPresent) { |
|
19798 document.removeEventListener('DOMContentLoaded', init, false); |
|
19799 } |
|
19800 }; |
|
19801 //#endif |
|
19802 |
|
19803 if(isDOMPresent) { |
|
19804 window['Processing'] = Processing; |
|
19805 //#if PARSER |
|
19806 document.addEventListener('DOMContentLoaded', init, false); |
|
19807 //#endif |
|
19808 } else { |
|
19809 // DOM is not found |
|
19810 this.Processing = Processing; |
|
19811 } |
|
19812 }(window, window.document, Math)); |