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. */ 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. */ 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 87 public static Rectangle peekScissors () { 88 return scissors.peek(); 89 } 90 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) */ 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 */ 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 */ 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