1 /** 2 * The Render Engine 3 * AbstractRenderContext 4 * 5 * @fileoverview The base class for all render contexts. 6 * 7 * @author: Brett Fattori (brettf@renderengine.com) 8 * @author: $Author: bfattori@gmail.com $ 9 * @version: $Revision: 1570 $ 10 * 11 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy 14 * of this software and associated documentation files (the "Software"), to deal 15 * in the Software without restriction, including without limitation the rights 16 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 * copies of the Software, and to permit persons to whom the Software is 18 * furnished to do so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in 21 * all copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 * THE SOFTWARE. 30 * 31 */ 32 33 // The class this file defines and its required classes 34 R.Engine.define({ 35 "class":"R.rendercontexts.AbstractRenderContext", 36 "requires":[ 37 "R.struct.Container", 38 "R.math.Math2D", 39 "R.engine.GameObject", 40 "R.struct.MouseInfo" 41 ] 42 }); 43 44 /** 45 * @class A base rendering context. Game objects are rendered to a context 46 * during engine runtime. A render context is a container of all of the objects 47 * added to it so that each object is given the chance to render. A render context 48 * is logically a scene graph. While each context can have multiple contexts associated 49 * with it, the root of the scene graph is always located at {@link R.Engine#getDefaultContext}. 50 * 51 * @param contextName {String} The name of this context. Default: RenderContext 52 * @param [surface] {HTMLElement} The surface node that all objects will be rendered to. 53 * @extends R.struct.Container 54 * @constructor 55 * @description Creates a render context 56 */ 57 R.rendercontexts.AbstractRenderContext = function () { 58 return R.struct.Container.extend(/** @scope R.rendercontexts.AbstractRenderContext.prototype */{ 59 60 surface:null, 61 transformStackDepth:0, 62 worldBoundary:null, 63 viewport:null, 64 expViewport:null, 65 worldPosition:null, 66 worldRotation:null, 67 worldScale:null, 68 staticCtx:null, 69 safeRemoveList:null, 70 _handlers:null, 71 72 /** @private */ 73 constructor:function (contextName, surface) { 74 this.worldBoundary = null; 75 this.worldScale = 1; 76 this.worldPosition = R.math.Point2D.create(0, 0); 77 this.worldRotation = 0; 78 this.viewport = R.math.Rectangle2D.create(0, 0, 100, 100); 79 this.expViewport = R.math.Rectangle2D.create(-25, -25, 125, 125); 80 this.staticCtx = false; 81 this.safeRemoveList = []; 82 this._handlers = []; 83 84 this.base(contextName || "RenderContext"); 85 this.surface = surface; 86 this.setElement(surface); 87 88 var pType = this.jQ().css("position"); 89 if (pType === "" || pType === "auto" || pType === "static") { 90 // Make it relative so we can get it's offset in the document 91 this.jQ().css("position", "relative"); 92 } 93 }, 94 95 /** 96 * Releases this context back into the pool for reuse 97 */ 98 release:function () { 99 this.base(); 100 this.surface = null; 101 this.transformStackDepth = 0; 102 this.worldScale = null; 103 this.worldPosition = null; 104 this.worldRotation = null; 105 this.staticCtx = null; 106 this.safeRemoveList = null; 107 this.viewport = null; 108 this.expViewport = null; 109 this.worldBoundary = null; 110 this._handlers = null; 111 }, 112 113 /** 114 * Destroy the rendering context, any objects within the context, and detach 115 * the surface from its parent container. 116 */ 117 destroy:function () { 118 // Destroy all of the objects 119 this.cleanUp(); 120 this.surface = null; 121 this.viewport.destroy(); 122 this.expViewport.destroy(); 123 this.worldPosition.destroy(); 124 if (this.worldBoundary != null) { 125 this.worldBoundary.destroy(); 126 } 127 if (this._handlers[0]) { 128 this.uncaptureMouse(); 129 } 130 if (this._handlers[1]) { 131 this.uncaptureTouch(); 132 } 133 this.base(); 134 }, 135 136 /** 137 * Set the surface element that objects will be rendered to. 138 * 139 * @param element {HTMLElement} The document node that all objects will be rendered to. 140 */ 141 setSurface:function (element) { 142 this.surface = element; 143 this.setElement(element); 144 }, 145 146 /** 147 * Get the surface node that all objects will be rendered to. 148 * @return {HTMLElement} The document node that represents the rendering surface 149 */ 150 getSurface:function () { 151 return this.surface; 152 }, 153 154 /** 155 * Set the context to be static. Setting a context to be static effectively removes 156 * it from the automatic update when the world is updated. The user will need to call 157 * {@link #render}, passing the world time (gotten with {@link R.Engine#worldTime}) 158 * to manually render the context. Any objects within the context will then render 159 * to the context. 160 * 161 * @param state {Boolean} <tt>true</tt> to set the context to static 162 */ 163 setStatic:function (state) { 164 this.staticCtx = state; 165 }, 166 167 /** 168 * Determine if the context is static. 169 * @return {Boolean} 170 */ 171 isStatic:function () { 172 return this.staticCtx; 173 }, 174 175 /** 176 * Enable mouse event capturing within the render context. The mouse 177 * event data will be accessible by calling {@link #getMouseInfo}. 178 */ 179 captureMouse:function () { 180 if (!this._handlers[0]) { 181 R.rendercontexts.AbstractRenderContext.assignMouseHandler(this); 182 var self = this; 183 R.Engine.onShutdown(function () { 184 self.uncaptureMouse(); 185 }); 186 this._handlers[0] = true; 187 } 188 }, 189 190 /** 191 * Disable mouse event capturing within the render context. 192 */ 193 uncaptureMouse:function () { 194 R.rendercontexts.AbstractRenderContext.removeMouseHandler(this); 195 this._handlers[0] = false; 196 }, 197 198 /** 199 * Get the state of the mouse in the rendering context. You must first enable mouse 200 * event capturing with {@link #captureMouse}. See {@link R.struct.MouseInfo} for more 201 * information about what is in the object. 202 * 203 * @return {R.struct.MouseInfo} The current state of the mouse 204 */ 205 getMouseInfo:function () { 206 return this.getObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL); 207 }, 208 209 /** 210 * Enable touch event capturing within the render context. The touch 211 * event data will be accessible by calling {@link #getTouchInfo}. 212 */ 213 captureTouch:function () { 214 if (!this._handlers[1]) { 215 R.rendercontexts.AbstractRenderContext.assignTouchHandler(this); 216 var self = this; 217 R.Engine.onShutdown(function () { 218 self.uncaptureTouch(); 219 }); 220 this._handlers[1] = true; 221 } 222 }, 223 224 /** 225 * Disable touch event capturing within the render context. 226 */ 227 uncaptureTouch:function () { 228 R.rendercontexts.AbstractRenderContext.removeTouchHandler(this); 229 this._handlers[1] = false; 230 }, 231 232 /** 233 * Get the state of touches in the rendering context. You must first enable touch 234 * event capturing with {@link #captureTouch}. See {@link R.struct.TouchInfo} for more 235 * information about what is in the object. 236 * 237 * @return {R.struct.TouchInfo} The current state of touches 238 */ 239 getTouchInfo:function () { 240 return this.getObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL); 241 }, 242 243 /** 244 * [ABSTRACT] Set the scale of the rendering context. 245 * 246 * @param scaleX {Number} The scale along the X dimension 247 * @param scaleY {Number} The scale along the Y dimension 248 */ 249 setScale:function (scaleX, scaleY) { 250 }, 251 252 /** 253 * Set the world scale of the rendering context. All objects should 254 * be adjusted by this scale when the context renders. 255 * 256 * @param scale {Number} The uniform scale of the world 257 */ 258 setWorldScale:function (scale) { 259 this.worldScale = scale; 260 }, 261 262 /** 263 * Set the world rotation of the rendering context. All objects 264 * should be adjusted by this rotation when the context renders. 265 * @param rotation {Number} The rotation angle 266 */ 267 setWorldRotation:function (rotation) { 268 this.worldRotation = rotation; 269 }, 270 271 /** 272 * Set the world position of the rendering context. All objects 273 * should be adjuest by this position when the context renders. 274 * @param point {R.math.Point2D|Number} The world position or X coordinate 275 * @param [y] {Number} If <tt>point</tt> is a number, this is the Y coordinate 276 */ 277 setWorldPosition:function (point, y) { 278 this.worldPosition.set(point, y); 279 }, 280 281 /** 282 * Gets an array representing the rendering scale of the world. 283 * @return {Array} The first element is the X axis, the second is the Y axis 284 */ 285 getWorldScale:function () { 286 return this.worldScale; 287 }, 288 289 /** 290 * Gets the world rotation angle. 291 * @return {Number} 292 */ 293 getWorldRotation:function () { 294 return this.worldRotation; 295 }, 296 297 /** 298 * Get the world render position. 299 * @return {R.math.Point2D} 300 */ 301 getWorldPosition:function () { 302 return this.worldPosition; 303 }, 304 305 /** 306 * Set the world boundaries. Set the world boundaries bigger than the viewport 307 * to create a virtual world. By default, the world boundary matches the viewport. 308 * @param rect {R.math.Rectangle2D} 309 */ 310 setWorldBoundary:function (rect) { 311 this.worldBoundary = rect; 312 }, 313 314 /** 315 * get the world boundaries. 316 * @return {R.math.Rectangle2D} 317 */ 318 getWorldBoundary:function (rect) { 319 return this.worldBoundary; 320 }, 321 322 /** 323 * Set the viewport of the render context. The viewport is a window 324 * upon the world so that not all of the world is rendered at one time. 325 * @param rect {R.math.Rectangle2D} A rectangle defining the viewport 326 */ 327 setViewport:function (rect) { 328 this.viewport.set(rect); 329 330 // Calculate the expanded viewport 331 var w = rect.getDims().x * 0.25, h = rect.getDims().y * 0.25, 332 tl = rect.getTopLeft(), d = rect.getDims(); 333 this.expViewport.set(tl.x - w, tl.y - h, tl.x + d.x + (w * 2), tl.y + d.y + (h * 2)); 334 }, 335 336 /** 337 * Get the viewport of the render context. 338 * @return {R.math.Rectangle2D} 339 */ 340 getViewport:function () { 341 return this.viewport; 342 }, 343 344 /** 345 * A viewport that is 25% larger than {@link #getViewport} to account for 346 * an area slightly outside the viewing area. Typically used to determin 347 * what objects are to be processed in the scenegraph. 348 * @return {R.math.Rectangle2D} 349 */ 350 getExpandedViewport:function () { 351 return this.expViewport; 352 }, 353 354 /** 355 * Add an object to the context. Only objects 356 * within the context will be rendered. If an object declared 357 * an <tt>afterAdd()</tt> method, it will be called after the object 358 * has been added to the context. 359 * 360 * @param obj {R.engine.BaseObject} The object to add to the render list 361 */ 362 add:function (obj) { 363 this.base(obj); 364 365 // Create a structure to hold information that is related to 366 // the render context that keeps it separate from the rest of the object. 367 obj.setObjectDataModel(R.rendercontexts.AbstractRenderContext.DATA_MODEL, {}); 368 369 if (obj instanceof R.engine.GameObject) { 370 obj.setRenderContext(this); 371 this.sort(); 372 } 373 }, 374 375 /** 376 * Remove an object from the render context. The object is 377 * not destroyed when it is removed from the container. The removal 378 * occurs after each update to avoid disrupting the flow of object 379 * traversal. 380 * 381 * @param obj {Object} The object to remove from the container. 382 * @return {Object} The object that was removed 383 */ 384 remove:function (obj) { 385 this.safeRemoveList.push(obj); 386 }, 387 388 /** 389 * Remove an object from the render context at the specified index. 390 * The object is not destroyed when it is removed. The removal 391 * occurs after each update to avoid disrupting the flow of object 392 * traversal. 393 * 394 * @param idx {Number} An index between zero and the size of the container minus 1. 395 * @return {Object} The object removed from the container. 396 */ 397 removeAtIndex:function (idx) { 398 this.safeRemoveList.push(this.get(idx)); 399 }, 400 401 /** 402 * This method is called after the update to remove items from the 403 * context. 404 * @private 405 */ 406 _safeRemove:function () { 407 var obj; 408 while ((obj = this.safeRemoveList.shift()) != null) { 409 R.struct.Container.prototype.remove.call(this, obj); 410 } 411 this.safeRemoveList.length = 0; 412 }, 413 414 /** 415 * Returns the structure that contains information held about 416 * the rendering context. This object allows a context to store 417 * extra information on an object that an object wouldn't know about. 418 * @return {Object} An object with data used by the context 419 */ 420 getContextData:function (obj) { 421 return obj.getObjectDataModel(R.rendercontexts.AbstractRenderContext.DATA_MODEL); 422 }, 423 424 /** 425 * [ABSTRACT] Sort the render context's objects. 426 * @param sortFn {Function} A function to sort with, or <tt>null</tt> to use the default 427 */ 428 sort:function (sortFn) { 429 this.base(sortFn); 430 }, 431 432 /** 433 * [ABSTRACT] Clear the context and prepare it for rendering. If you pass a 434 * rectangle, only that portion of the world will be cleared. If 435 * you don't pass a rectangle, the entire context is cleared. 436 * 437 * @param rect {R.math.Rectangle2D} The area to clear in the context, or 438 * <tt>null</tt> to clear the entire context. 439 */ 440 reset:function (rect) { 441 }, 442 443 /** 444 * Update the render context before rendering the objects to the surface. 445 * If the context isn't static, this will reset and then render the context. 446 * If the context is static, you'll need to perform the reset and render yourself. 447 * This allows you to update objects in the world, skip the reset, and then 448 * render yourself. This can be an effective way to handle redrawing the world 449 * only as needed. 450 * 451 * @param parentContext {R.rendercontexts.AbstractRenderContext} A parent context, or <tt>null</tt> 452 * @param time {Number} The current render time in milliseconds from the engine. 453 * @param dt {Number} The delta between the world time and the last time the world was updated 454 * in milliseconds. 455 */ 456 update:function (parentContext, time, dt) { 457 if (!this.staticCtx) { 458 // Clear and render world 459 this.reset(); 460 this.render(time, dt); 461 } 462 }, 463 464 /** 465 * Called to render all of the objects to the context. 466 * 467 * @param time {Number} The current world time in milliseconds from the engine. 468 * @param dt {Number} The delta between the world time and the last time the world was updated 469 * in milliseconds. 470 */ 471 render:function (time, dt) { 472 // Push the world transform 473 this.pushTransform(); 474 475 this.setupWorld(time, dt); 476 477 // Run the objects through their components 478 var objs = this.iterator(); 479 while (objs.hasNext()) { 480 this.renderObject(objs.next(), time, dt); 481 } 482 483 objs.destroy(); 484 485 // Restore the world transform 486 this.popTransform(); 487 488 // Safely remove any objects that were removed from 489 // the context while it was rendering 490 if (this.safeRemoveList.length > 0) { 491 this._safeRemove(); 492 } 493 }, 494 495 /** 496 * Render a single object into the world for the given time. 497 * @param obj {R.engine.BaseObject} An object to render 498 * @param time {Number} The world time, in milliseconds 499 * @param dt {Number} The delta between the world time and the last time the world was updated 500 * in milliseconds. 501 */ 502 renderObject:function (obj, time, dt) { 503 obj.update(this, time, dt); 504 }, 505 506 /** 507 * [ABSTRACT] Gives the render context a chance to initialize the world. 508 * Use this method to change the world position, rotation, scale, etc. 509 * 510 * @param time {Number} The current world time 511 * @param dt {Number} The delta between the world time and the last time the world was updated 512 * in milliseconds. 513 */ 514 setupWorld:function (time, dt) { 515 }, 516 517 /** 518 * Increment the transform stack counter. 519 */ 520 pushTransform:function () { 521 this.transformStackDepth++; 522 }, 523 524 /** 525 * Decrement the transform stack counter and ensure that the stack 526 * is not unbalanced. An unbalanced stack can be indicative of 527 * objects that do not reset the state after rendering themselves. 528 */ 529 popTransform:function () { 530 this.transformStackDepth--; 531 Assert((this.transformStackDepth >= 0), "Unbalanced transform stack!"); 532 }, 533 534 /** 535 * This is a potentially expensive call, and can lead to rendering 536 * errors. It is recommended against calling this method! 537 */ 538 resetTransformStack:function () { 539 while (this.transformStackDepth > 0) { 540 this.popTransform(); 541 } 542 } 543 }, /** @scope R.rendercontexts.AbstractRenderContext.prototype */{ 544 545 /** 546 * Get the class name of this object 547 * @return {String} "R.rendercontexts.AbstractRenderContext" 548 */ 549 getClassName:function () { 550 return "R.rendercontexts.AbstractRenderContext"; 551 }, 552 553 /** 554 * @private 555 */ 556 DATA_MODEL:"RenderContext", 557 558 /** 559 * @private 560 */ 561 MOUSE_DATA_MODEL:"mouseInfo", 562 563 /** 564 * @private 565 */ 566 TOUCH_DATA_MODEL:"touchInfo", 567 568 /** 569 * Assigns the mouse handlers. 570 * @private 571 * @param ctx {R.rendercontexts.AbstractRenderContext} The context to assign the handlers to 572 */ 573 assignMouseHandler:function (ctx) { 574 // Assign handlers to the context, making sure to only add 575 // the handler once. This way we don't have hundreds of mouse move 576 // handlers taking up precious milliseconds. 577 var mouseInfo = ctx.getObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL); 578 579 if (!mouseInfo) { 580 mouseInfo = ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL, 581 R.struct.MouseInfo.create()); 582 583 ctx.addEvent(ctx, "mousemove", function (evt) { 584 if (mouseInfo.moveTimer != null) { 585 mouseInfo.moveTimer.destroy(); 586 mouseInfo.moveTimer = null; 587 } 588 mouseInfo.lastPosition.set(mouseInfo.position); 589 590 // BAF: 05/31/2011 - https://github.com/bfattori/TheRenderEngine/issues/7 591 // BAF: 06/17/2011 - https://github.com/bfattori/TheRenderEngine/issues/9 592 // Adjust for position of context 593 var x = evt.pageX, y = evt.pageY; 594 mouseInfo.position.set(x, y).sub(ctx.getObjectDataModel("DOMPosition")); 595 596 // Move vector is calculated relative to the last position 597 mouseInfo.moveVec.set(mouseInfo.lastPosition); 598 mouseInfo.moveVec.sub(mouseInfo.position); 599 600 if (mouseInfo.button != R.engine.Events.MOUSE_NO_BUTTON) { 601 // Drag vector originates from the "down" position and is normalized 602 mouseInfo.dragVec.set(mouseInfo.downPosition); 603 mouseInfo.dragVec.sub(mouseInfo.position).normalize(); 604 } 605 606 mouseInfo.moveTimer = R.lang.Timeout.create("mouseMove", 33, function () { 607 mouseInfo.moveVec.set(0, 0); 608 }); 609 }); 610 611 ctx.addEvent(ctx, "mousedown", function (evt) { 612 mouseInfo.button = evt.which; 613 var x = evt.pageX, y = evt.pageY; 614 615 mouseInfo.downPosition.set(x, y).sub(ctx.getObjectDataModel("DOMPosition")); 616 evt.preventDefault(); 617 }); 618 619 ctx.addEvent(ctx, "mouseup", function (evt) { 620 mouseInfo.button = R.engine.Events.MOUSE_NO_BUTTON; 621 mouseInfo.dragVec.set(0, 0); 622 }); 623 624 } 625 }, 626 627 /** 628 * Remove the mouse handlers 629 * @param ctx 630 * @private 631 */ 632 removeMouseHandler:function (ctx) { 633 ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.MOUSE_DATA_MODEL, undefined); 634 ctx.removeEvent(ctx, "mousemove"); 635 ctx.removeEvent(ctx, "mousedown"); 636 ctx.removeEvent(ctx, "mouseup"); 637 }, 638 639 /** 640 * Assigns the touch handlers. 641 * @private 642 * @param ctx {R.rendercontexts.AbstractRenderContext} The context to assign the handlers to 643 */ 644 assignTouchHandler:function (ctx) { 645 // Assign handlers to the context, making sure to only add 646 // the handler once. This way we don't have hundreds of mouse move 647 // handlers taking up precious milliseconds. 648 var touchInfo = ctx.getObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL); 649 650 if (!touchInfo) { 651 touchInfo = ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL, 652 R.struct.TouchInfo.create()); 653 654 ctx.addEvent(ctx, "touchmove", function (evt) { 655 touchInfo.touches = R.struct.TouchInfo.processTouches(evt.originalEvent); 656 657 if (touchInfo.moveTimer != null) { 658 touchInfo.moveTimer.destroy(); 659 touchInfo.moveTimer = null; 660 } 661 touchInfo.lastPosition.set(touchInfo.position); 662 663 // Use the first touch as the position 664 var x = touchInfo.touches[0].getX(), y = touchInfo.touches[0].getY(); 665 666 touchInfo.position.set(x, y).sub(ctx.getObjectDataModel("DOMPosition")); 667 668 // Move vector is calculated relative to the last position 669 touchInfo.moveVec.set(touchInfo.lastPosition); 670 touchInfo.moveVec.sub(touchInfo.position); 671 672 // Drag vector originates from the "down" touch and is normalized 673 touchInfo.dragVec.set(touchInfo.downPosition); 674 touchInfo.dragVec.sub(touchInfo.position).normalize(); 675 676 touchInfo.moveTimer = R.lang.Timeout.create("touchMove", 33, function () { 677 touchInfo.moveVec.set(0, 0); 678 }); 679 }); 680 681 ctx.addEvent(ctx, "touchstart", function (evt) { 682 touchInfo.touches = R.struct.TouchInfo.processTouches(evt.originalEvent); 683 touchInfo.button = R.engine.Events.MOUSE_LEFT_BUTTON; 684 685 // Use the first touch as the down position 686 var x = touchInfo.touches[0].getX(), y = touchInfo.touches[0].getY(); 687 688 touchInfo.downPosition.set(x, y).sub(this._offset); 689 evt.preventDefault(); 690 }); 691 692 ctx.addEvent(ctx, "touchend", function (evt) { 693 touchInfo.touches = R.struct.TouchInfo.processTouches(evt.originalEvent); 694 touchInfo.button = R.engine.Events.MOUSE_NO_BUTTON; 695 touchInfo.dragVec.set(0, 0); 696 }); 697 698 } 699 }, 700 701 /** 702 * Remove the touch handlers 703 * @param ctx 704 * @private 705 */ 706 removeTouchHandler:function (ctx) { 707 ctx.setObjectDataModel(R.rendercontexts.AbstractRenderContext.TOUCH_DATA_MODEL, undefined); 708 ctx.removeEvent(ctx, "touchmove"); 709 ctx.removeEvent(ctx, "touchstart"); 710 ctx.removeEvent(ctx, "touchend"); 711 } 712 713 }); 714 715 }; 716