/******************************************************************************* * Copyright 2011 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. ******************************************************************************/ package com.badlogic.gdx.scenes.scene2d.utils; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.GLCommon; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.utils.Array; /** * A stack of {@link Rectangle} objects to be used for clipping via {@link GLCommon#glScissor(int, int, int, int)}. When * a new 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 the real top of the stack. * * @author mzechner */ public class ScissorStack { private static Array<Rectangle> scissors = new Array<Rectangle>(); static Vector3 tmp = new Vector3(); static final Rectangle viewport = new Rectangle(); /** * Pushes a new scissor {@link Rectangle} onto the stack, merging it with the current top of the stack. The minimal * area of overlap between the top of stack rectangle and the provided rectangle is pushed onto the stack. This will * invoke {@link GLCommon#glScissor(int, int, int, int)} with the final top of stack rectangle. In case no scissor * is yet on the stack this will also enable {@link GL10#GL_SCISSOR_TEST} automatically. * * @return true if the scissors were pushed. false if the scissor area was zero, in this case the scissors were not * pushed and no drawing should occur. */ public static boolean pushScissors(Rectangle scissor) { fix(scissor); if (scissors.size == 0) { if (scissor.width < 1 || scissor.height < 1) return false; Gdx.gl.glEnable(GL10.GL_SCISSOR_TEST); } else { // merge scissors Rectangle parent = scissors.get(scissors.size - 1); float minX = Math.max(parent.x, scissor.x); float maxX = Math.min(parent.x + parent.width, scissor.x + scissor.width); if (maxX - minX < 1) return false; float minY = Math.max(parent.y, scissor.y); float maxY = Math.min(parent.y + parent.height, scissor.y + scissor.height); if (maxY - minY < 1) return false; scissor.x = minX; scissor.y = minY; scissor.width = maxX - minX; scissor.height = Math.max(1, maxY - minY); } scissors.add(scissor); Gdx.gl.glScissor((int) scissor.x, (int) scissor.y, (int) scissor.width, (int) scissor.height); return true; } /** * Pops the current scissor rectangle from the stack and sets the new scissor area to the new top of stack * rectangle. In case no more rectangles are on the stack, {@link GL10#GL_SCISSOR_TEST} is disabled. */ public static Rectangle popScissors() { Rectangle old = scissors.pop(); if (scissors.size == 0) Gdx.gl.glDisable(GL10.GL_SCISSOR_TEST); else { Rectangle scissor = scissors.peek(); Gdx.gl.glScissor((int) scissor.x, (int) scissor.y, (int) scissor.width, (int) scissor.height); } return old; } private static void fix(Rectangle rect) { rect.x = Math.round(rect.x); rect.y = Math.round(rect.y); rect.width = Math.round(rect.width); rect.height = Math.round(rect.height); if (rect.width < 0) { rect.width = -rect.width; rect.x -= rect.width; } if (rect.height < 0) { rect.height = -rect.height; rect.y -= rect.height; } } /** * Calculates a scissor rectangle in OpenGL ES window coordinates from a {@link Camera}, a transformation * {@link Matrix4} and an axis aligned {@link Rectangle}. The rectangle will get transformed by the camera and * transform matrices and is then projected to screen coordinates. Note that only axis aligned rectangles will work * with this method. If either the Camera or the Matrix4 have rotational components, the output of this method will * not be suitable for {@link GLCommon#glScissor(int, int, int, int)}. * * @param camera * the {@link Camera} * @param batchTransform * the transformation {@link Matrix4} * @param area * the {@link Rectangle} to transform to window coordinates * @param scissor * the Rectangle to store the result in */ public static void calculateScissors(Camera camera, Matrix4 batchTransform, Rectangle area, Rectangle scissor) { tmp.set(area.x, area.y, 0); tmp.mul(batchTransform); camera.project(tmp); scissor.x = tmp.x; scissor.y = tmp.y; tmp.set(area.x + area.width, area.y + area.height, 0); tmp.mul(batchTransform); camera.project(tmp); scissor.width = tmp.x - scissor.x; scissor.height = tmp.y - scissor.y; } /** @return the current viewport in OpenGL ES window coordinates based on the currently applied scissor */ public static Rectangle getViewport() { if (scissors.size == 0) { viewport.set(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); return viewport; } else { Rectangle scissor = scissors.peek(); viewport.set(scissor); return viewport; } } /** * Transforms a point to real window coordinates (as oposed to OpenGL ES window coordinates), where the origin is in * the top left and the the y-axis is pointing downwards * * @param camera * the {@link Camera} * @param transformMatrix * the transformation {@link Matrix4} * @param point * the point to be transformed. */ public static void toWindowCoordinates(Camera camera, Matrix4 transformMatrix, Vector2 point) { tmp.set(point.x, point.y, 0); tmp.mul(transformMatrix); camera.project(tmp); tmp.y = Gdx.graphics.getHeight() - tmp.y; point.x = tmp.x; point.y = tmp.y; } }