Home | History | Annotate | Download | only in utils
      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