1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.scenes.scene2d.utils; 18 19 import com.badlogic.gdx.Gdx; 20 import com.badlogic.gdx.graphics.Camera; 21 import com.badlogic.gdx.graphics.GL20; 22 import com.badlogic.gdx.graphics.glutils.HdpiUtils; 23 import com.badlogic.gdx.math.Matrix4; 24 import com.badlogic.gdx.math.Rectangle; 25 import com.badlogic.gdx.math.Vector3; 26 import com.badlogic.gdx.utils.Array; 27 28 /** A stack of {@link Rectangle} objects to be used for clipping via {@link GL20#glScissor(int, int, int, int)}. When a new 29 * Rectangle is pushed onto the stack, it will be merged with the current top of stack. The minimum area of overlap is then set as 30 * the real top of the stack. 31 * @author mzechner */ 32 public class ScissorStack { 33 private static Array<Rectangle> scissors = new Array<Rectangle>(); 34 static Vector3 tmp = new Vector3(); 35 static final Rectangle viewport = new Rectangle(); 36 37 /** Pushes a new scissor {@link Rectangle} onto the stack, merging it with the current top of the stack. The minimal area of 38 * overlap between the top of stack rectangle and the provided rectangle is pushed onto the stack. This will invoke 39 * {@link GL20#glScissor(int, int, int, int)} with the final top of stack rectangle. In case no scissor is yet on the stack 40 * this will also enable {@link GL20#GL_SCISSOR_TEST} automatically. 41 * <p> 42 * Any drawing should be flushed before pushing scissors. 43 * @return true if the scissors were pushed. false if the scissor area was zero, in this case the scissors were not pushed and 44 * no drawing should occur. */ pushScissors(Rectangle scissor)45 public static boolean pushScissors (Rectangle scissor) { 46 fix(scissor); 47 48 if (scissors.size == 0) { 49 if (scissor.width < 1 || scissor.height < 1) return false; 50 Gdx.gl.glEnable(GL20.GL_SCISSOR_TEST); 51 } else { 52 // merge scissors 53 Rectangle parent = scissors.get(scissors.size - 1); 54 float minX = Math.max(parent.x, scissor.x); 55 float maxX = Math.min(parent.x + parent.width, scissor.x + scissor.width); 56 if (maxX - minX < 1) return false; 57 58 float minY = Math.max(parent.y, scissor.y); 59 float maxY = Math.min(parent.y + parent.height, scissor.y + scissor.height); 60 if (maxY - minY < 1) return false; 61 62 scissor.x = minX; 63 scissor.y = minY; 64 scissor.width = maxX - minX; 65 scissor.height = Math.max(1, maxY - minY); 66 } 67 scissors.add(scissor); 68 HdpiUtils.glScissor((int)scissor.x, (int)scissor.y, (int)scissor.width, (int)scissor.height); 69 return true; 70 } 71 72 /** Pops the current scissor rectangle from the stack and sets the new scissor area to the new top of stack rectangle. In case 73 * no more rectangles are on the stack, {@link GL20#GL_SCISSOR_TEST} is disabled. 74 * <p> 75 * Any drawing should be flushed before popping scissors. */ popScissors()76 public static Rectangle popScissors () { 77 Rectangle old = scissors.pop(); 78 if (scissors.size == 0) 79 Gdx.gl.glDisable(GL20.GL_SCISSOR_TEST); 80 else { 81 Rectangle scissor = scissors.peek(); 82 HdpiUtils.glScissor((int)scissor.x, (int)scissor.y, (int)scissor.width, (int)scissor.height); 83 } 84 return old; 85 } 86 peekScissors()87 public static Rectangle peekScissors () { 88 return scissors.peek(); 89 } 90 fix(Rectangle rect)91 private static void fix (Rectangle rect) { 92 rect.x = Math.round(rect.x); 93 rect.y = Math.round(rect.y); 94 rect.width = Math.round(rect.width); 95 rect.height = Math.round(rect.height); 96 if (rect.width < 0) { 97 rect.width = -rect.width; 98 rect.x -= rect.width; 99 } 100 if (rect.height < 0) { 101 rect.height = -rect.height; 102 rect.y -= rect.height; 103 } 104 } 105 106 /** Calculates a scissor rectangle using 0,0,Gdx.graphics.getWidth(),Gdx.graphics.getHeight() as the viewport. 107 * @see #calculateScissors(Camera, float, float, float, float, Matrix4, Rectangle, Rectangle) */ calculateScissors(Camera camera, Matrix4 batchTransform, Rectangle area, Rectangle scissor)108 public static void calculateScissors (Camera camera, Matrix4 batchTransform, Rectangle area, Rectangle scissor) { 109 calculateScissors(camera, 0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), batchTransform, area, scissor); 110 } 111 112 /** Calculates a scissor rectangle in OpenGL ES window coordinates from a {@link Camera}, a transformation {@link Matrix4} and 113 * an axis aligned {@link Rectangle}. The rectangle will get transformed by the camera and transform matrices and is then 114 * projected to screen coordinates. Note that only axis aligned rectangles will work with this method. If either the Camera or 115 * the Matrix4 have rotational components, the output of this method will not be suitable for 116 * {@link GL20#glScissor(int, int, int, int)}. 117 * @param camera the {@link Camera} 118 * @param batchTransform the transformation {@link Matrix4} 119 * @param area the {@link Rectangle} to transform to window coordinates 120 * @param scissor the Rectangle to store the result in */ calculateScissors(Camera camera, float viewportX, float viewportY, float viewportWidth, float viewportHeight, Matrix4 batchTransform, Rectangle area, Rectangle scissor)121 public static void calculateScissors (Camera camera, float viewportX, float viewportY, float viewportWidth, 122 float viewportHeight, Matrix4 batchTransform, Rectangle area, Rectangle scissor) { 123 tmp.set(area.x, area.y, 0); 124 tmp.mul(batchTransform); 125 camera.project(tmp, viewportX, viewportY, viewportWidth, viewportHeight); 126 scissor.x = tmp.x; 127 scissor.y = tmp.y; 128 129 tmp.set(area.x + area.width, area.y + area.height, 0); 130 tmp.mul(batchTransform); 131 camera.project(tmp, viewportX, viewportY, viewportWidth, viewportHeight); 132 scissor.width = tmp.x - scissor.x; 133 scissor.height = tmp.y - scissor.y; 134 } 135 136 /** @return the current viewport in OpenGL ES window coordinates based on the currently applied scissor */ getViewport()137 public static Rectangle getViewport () { 138 if (scissors.size == 0) { 139 viewport.set(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 140 return viewport; 141 } else { 142 Rectangle scissor = scissors.peek(); 143 viewport.set(scissor); 144 return viewport; 145 } 146 } 147 } 148