From 0cad6aefc631ac4ef2f71302aefd7be5034c4f57 Mon Sep 17 00:00:00 2001 From: Tim Conkling Date: Mon, 4 Feb 2013 16:35:45 -0800 Subject: [PATCH] Scissor rect support for DisplayObjectContainer --- starling/src/starling/core/RenderSupport.as | 59 +++++++++++---- .../display/DisplayObjectContainer.as | 75 +++++++++++++++++-- .../src/starling/textures/RenderTexture.as | 4 +- 3 files changed, 115 insertions(+), 23 deletions(-) diff --git a/starling/src/starling/core/RenderSupport.as b/starling/src/starling/core/RenderSupport.as index 5a7bfb8f6..497df9499 100644 --- a/starling/src/starling/core/RenderSupport.as +++ b/starling/src/starling/core/RenderSupport.as @@ -28,6 +28,7 @@ package starling.core import starling.textures.Texture; import starling.utils.Color; import starling.utils.MatrixUtil; + import starling.utils.RectangleUtil; /** A class that contains helper methods simplifying Stage3D rendering. * @@ -51,7 +52,8 @@ package starling.core private var mRenderTarget:Texture; private var mBackBufferWidth:int; private var mBackBufferHeight:int; - private var mScissorRectangle:Rectangle; + private var mScissorRectStack:Vector.; + private var mScissorRectStackSize:int; private var mQuadBatches:Vector.; private var mCurrentQuadBatchID:int; @@ -75,7 +77,7 @@ package starling.core mDrawCount = 0; mRenderTarget = null; mBlendMode = BlendMode.NORMAL; - mScissorRectangle = new Rectangle(); + mScissorRectStack = new []; mCurrentQuadBatchID = 0; mQuadBatches = new [new QuadBatch()]; @@ -247,36 +249,61 @@ package starling.core /** The scissor rectangle can be used to limit rendering in the current render target to * a certain area. This method expects the rectangle in stage coordinates - * (different to the context3D method with the same name, which expects pixels). - * Pass null to turn off scissoring. - * CAUTION: not a copy -- use with care! */ - public function get scissorRectangle():Rectangle - { - return mScissorRectangle.isEmpty() ? null : mScissorRectangle; + * (different to the context3D method with the same name, which expects pixels). */ + public function pushScissorRect(rect:Rectangle):Rectangle + { + if (mScissorRectStack.length < mScissorRectStackSize + 1) + mScissorRectStack.push(new Rectangle()); + + mScissorRectStack[mScissorRectStackSize].copyFrom(rect); + rect = mScissorRectStack[mScissorRectStackSize]; + + // intersect with the last pushed scissor rect + if (mScissorRectStackSize > 0) + rect = RectangleUtil.intersect(rect, mScissorRectStack[mScissorRectStackSize-1], rect); + + ++mScissorRectStackSize; + + updateClipping(); + + // return the intersected scissor rect so callers can skip draw calls if it's empty + return rect; } - public function set scissorRectangle(value:Rectangle):void + public function popScissorRect():void { - if (value) + if (mScissorRectStackSize > 0) { - mScissorRectangle.setTo(value.x, value.y, value.width, value.height); - + --mScissorRectStackSize; + updateClipping(); + } + } + + protected function updateClipping():void + { + finishQuadBatch(); + + if (mScissorRectStackSize > 0) + { + var rect:Rectangle = mScissorRectStack[mScissorRectStackSize-1]; + sRectangle.setTo(rect.x, rect.y, rect.width, rect.height); + var width:int = mRenderTarget ? mRenderTarget.root.nativeWidth : mBackBufferWidth; var height:int = mRenderTarget ? mRenderTarget.root.nativeHeight : mBackBufferHeight; - MatrixUtil.transformCoords(mProjectionMatrix, value.x, value.y, sPoint); + // convert to pixel coordinates + MatrixUtil.transformCoords(mProjectionMatrix, rect.x, rect.y, sPoint); sRectangle.x = Math.max(0, ( sPoint.x + 1) / 2) * width; sRectangle.y = Math.max(0, (-sPoint.y + 1) / 2) * height; - MatrixUtil.transformCoords(mProjectionMatrix, value.right, value.bottom, sPoint); + MatrixUtil.transformCoords(mProjectionMatrix, rect.right, rect.bottom, sPoint); sRectangle.right = Math.min(1, ( sPoint.x + 1) / 2) * width; sRectangle.bottom = Math.min(1, (-sPoint.y + 1) / 2) * height; Starling.context.setScissorRectangle(sRectangle); } - else + else { - mScissorRectangle.setEmpty(); Starling.context.setScissorRectangle(null); } } diff --git a/starling/src/starling/display/DisplayObjectContainer.as b/starling/src/starling/display/DisplayObjectContainer.as index 74eddf48a..2030bd6c4 100644 --- a/starling/src/starling/display/DisplayObjectContainer.as +++ b/starling/src/starling/display/DisplayObjectContainer.as @@ -22,6 +22,7 @@ package starling.display import starling.events.Event; import starling.filters.FragmentFilter; import starling.utils.MatrixUtil; + import starling.utils.RectangleUtil; use namespace starling_internal; @@ -65,12 +66,13 @@ package starling.display public class DisplayObjectContainer extends DisplayObject { // members - + private var mScissorRect:Rectangle; private var mChildren:Vector.; /** Helper objects. */ private static var sHelperMatrix:Matrix = new Matrix(); private static var sHelperPoint:Point = new Point(); + private static var sHelperRect:Rectangle = new Rectangle(); private static var sBroadcastListeners:Vector. = new []; // construction @@ -96,6 +98,48 @@ package starling.display super.dispose(); } + /** The object's scissor rect. + * Note: clip rects are axis aligned with the screen, so they + * will not be rotated or skewed if the DisplayObjectContainer is. */ + public function get scissorRect():Rectangle { return mScissorRect; } + public function set scissorRect(val:Rectangle) :void { mScissorRect = val; } + + /** Returns the bounds of the container's scissorRect in the given coordinate space, or + * null if the container doens't have a scissorRect. */ + public function getScissorBounds(targetSpace:DisplayObject, resultRect:Rectangle=null):Rectangle + { + if (mScissorRect == null) return null; + + if (resultRect == null) resultRect = new Rectangle(); + + var minX:Number = Number.MAX_VALUE; + var maxX:Number = -Number.MAX_VALUE; + var minY:Number = Number.MAX_VALUE; + var maxY :Number = -Number.MAX_VALUE; + + var transformMatrix:Matrix = getTransformationMatrix(targetSpace, sHelperMatrix); + var x :Number = 0; + var y :Number = 0; + for (var i:int=0; i<4; ++i) + { + switch(i) + { + case 0: x = mScissorRect.left; y = mScissorRect.top; break; + case 1: x = mScissorRect.left; y = mScissorRect.bottom; break; + case 2: x = mScissorRect.right; y = mScissorRect.top; break; + case 3: x = mScissorRect.right; y = mScissorRect.bottom; break; + } + var transformedPoint :Point = MatrixUtil.transformCoords(transformMatrix, x, y, sHelperPoint); + minX = Math.min(minX, transformedPoint.x); + maxX = Math.max(maxX, transformedPoint.x); + minY = Math.min(minY, transformedPoint.y); + maxY = Math.max(maxY, transformedPoint.y); + } + + resultRect.setTo(minX, minY, maxX-minX, maxY-minY); + return resultRect; + } + // child management /** Adds a child to the container. It will be at the frontmost position. */ @@ -267,11 +311,10 @@ package starling.display getTransformationMatrix(targetSpace, sHelperMatrix); MatrixUtil.transformCoords(sHelperMatrix, 0.0, 0.0, sHelperPoint); resultRect.setTo(sHelperPoint.x, sHelperPoint.y, 0, 0); - return resultRect; } else if (numChildren == 1) { - return mChildren[0].getBounds(targetSpace, resultRect); + resultRect = mChildren[0].getBounds(targetSpace, resultRect); } else { @@ -288,8 +331,13 @@ package starling.display } resultRect.setTo(minX, minY, maxX - minX, maxY - minY); - return resultRect; } + + // if we have a scissor rect, intersect it with our bounds + if (mScissorRect != null) + resultRect = RectangleUtil.intersect(resultRect, getScissorBounds(targetSpace, resultRect)); + + return resultRect; } /** @inheritDoc */ @@ -298,6 +346,9 @@ package starling.display if (forTouch && (!visible || !touchable)) return null; + if (mScissorRect != null && !mScissorRect.containsPoint(localPoint)) + return null; + var localX:Number = localPoint.x; var localY:Number = localPoint.y; @@ -318,7 +369,18 @@ package starling.display /** @inheritDoc */ public override function render(support:RenderSupport, parentAlpha:Number):void - { + { + if (mScissorRect != null) + { + var scissor:Rectangle = support.pushScissorRect(getScissorBounds(this.stage, sHelperRect)); + // don't bother rendering our children if our scissored bounds are empty + if (scissor.isEmpty()) + { + support.popScissorRect(); + return; + } + } + var alpha:Number = parentAlpha * this.alpha; var numChildren:int = mChildren.length; var blendMode:String = support.blendMode; @@ -342,6 +404,9 @@ package starling.display support.popMatrix(); } } + + if (mScissorRect != null) + support.popScissorRect(); } /** Dispatches an event on all children (recursively). The event must not bubble. */ diff --git a/starling/src/starling/textures/RenderTexture.as b/starling/src/starling/textures/RenderTexture.as index 2305cf275..46c6db450 100644 --- a/starling/src/starling/textures/RenderTexture.as +++ b/starling/src/starling/textures/RenderTexture.as @@ -163,7 +163,7 @@ package starling.textures // limit drawing to relevant area sScissorRect.setTo(0, 0, mActiveTexture.nativeWidth, mActiveTexture.nativeHeight); - mSupport.scissorRectangle = sScissorRect; + mSupport.pushScissorRect(sScissorRect); mSupport.renderTarget = mActiveTexture; mSupport.clear(); @@ -187,7 +187,7 @@ package starling.textures mSupport.finishQuadBatch(); mSupport.nextFrame(); mSupport.renderTarget = null; - mSupport.scissorRectangle = null; + mSupport.popScissorRect(); } }