/* * Copyright 2013 MicaByte Systems * * 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.micabytes.gfx; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; /** * SurfaceRenderer is the superclass of the renderer. The game should subclass the renderer and extend the drawing methods to add game drawing. * <p/> * - BitmapSurfaceRenderer can be extended for apps that require background images * - TileMapSurfaceRenderer can be extended for apps that need to display TileMaps (not currently onTouchUp to date) * - HexMapSurfaceRenderer can be extended for apps that need to display HexMaps */ public abstract class SurfaceRenderer { // View Size Minimum protected static final int MINIMUM_PIXELS_IN_VIEW = 50; // Context protected final Context context; // The ViewPort protected final ViewPort viewPort = new ViewPort(); // The Dimensions of the Game Area protected final Point backgroundSize = new Point(); /** * Constructor for the surface renderer * * @param con We need to pass in the context, so that we have it when we create bitmaps for drawing operations later. Since the draw operations are * run in a thread, we can't pass the context through the thread (at least not easily) */ protected SurfaceRenderer(Context con) { context = con; } /** * Rendering thread started */ public abstract void start(); /** * Rendering thread stopped */ public abstract void stop(); /** * Rendering updates can be suspended */ public abstract void suspend(); /** * Rendering updates can be resumed */ public abstract void resume(); /** * Draw to the canvas */ public void draw(Canvas canvas) { viewPort.draw(canvas); } /** * Draw the base (background) layer of the SurfaceView image */ protected abstract void drawBase(); /** * Draw the game (dynamic) layer of the SurfaceView image */ protected abstract void drawLayer(); /** * Draw any final touches */ protected abstract void drawFinal(); /** * Get the position (center) of the view */ public void getViewPosition(Point p) { viewPort.getOrigin(p); } /** * Set the position (center) of the view */ public void setViewPosition(int x, int y) { viewPort.setOrigin(x, y); } /** * Set the position (center) of the view based on map coordinates. This is intended to be used with Tile/HexMaps, and needs to be implemented in * the derived player Map class. */ public void setMapPosition(int x, int y) { viewPort.setOrigin(x, y); } /** * Get the dimensions of the view */ public void getViewSize(Point p) { viewPort.getSize(p); } /** * Set the dimensions of the view */ public void setViewSize(int w, int h) { viewPort.setSize(w, h); } /** * Returns a Point representing the size of the scene. Don't modify the returned Point! */ public Point getBackgroundSize() { return backgroundSize; } public void zoom(float scaleFactor, PointF screenFocus) { viewPort.zoom(scaleFactor, screenFocus); } public float getZoom() { return viewPort.getZoom(); } /** * View Port. This handles the actual drawing, managing dimensions, etc. */ @SuppressWarnings({"PublicInnerClass", "NonPrivateFieldAccessedInSynchronizedContext"}) public class ViewPort { // The Bitmap of the current ViewPort protected Bitmap bitmap; protected final Object bitmapLock = new Object(); // TODO: Bitmap needs checking. // The rect defining where the viewport is within the scene private final Rect window = new Rect(0, 0, 0, 0); // The zoom factor of the viewport private float zoom = 1.0f; public Bitmap getBitmap() { synchronized (bitmapLock) { return bitmap; } } public synchronized void getOrigin(Point p) { p.set(window.left, window.top); } public void setOrigin(int xp, int yp) { int x = xp; int y = yp; int w; int h; synchronized (this) { w = window.width(); h = window.height(); } // check bounds if (x < 0) x = 0; if (y < 0) y = 0; if ((x + w) > backgroundSize.x) x = backgroundSize.x - w; if ((y + h) > backgroundSize.y) y = backgroundSize.y - h; // Set the Window rect synchronized (this) { window.set(x, y, x + w, y + h); } } @SuppressWarnings("AssignmentToNull") public void setSize(int w, int h) { int x; int y; synchronized (bitmapLock) { if (bitmap != null) { bitmap.recycle(); bitmap = null; } bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); x = window.left; y = window.top; } // check bounds if (x < 0) x = 0; if (y < 0) y = 0; if ((x + w) > backgroundSize.x) x = backgroundSize.x - w; if ((y + h) > backgroundSize.y) y = backgroundSize.y - h; // Set the Window rect synchronized (this) { window.set(x, y, x + w, y + h); } } public synchronized void getSize(Point p) { p.x = window.width(); p.y = window.height(); } public void getPhysicalSize(Point p) { if (bitmap == null) return; p.x = getPhysicalWidth(); p.y = getPhysicalHeight(); } public int getPhysicalWidth() { synchronized (bitmapLock) { return bitmap.getWidth(); } } public int getPhysicalHeight() { synchronized (bitmapLock) { return bitmap.getHeight(); } } public synchronized float getZoom() { return zoom; } @SuppressWarnings("unused") public synchronized void setZoom(float f) { zoom = f; } public void zoom(float factor, PointF screenFocus) { PointF screenSize; synchronized(bitmapLock) { if (bitmap == null) return; screenSize = new PointF(bitmap.getWidth(), bitmap.getHeight()); } PointF sceneSize = new PointF(getBackgroundSize()); float screenWidthToHeight = screenSize.x / screenSize.y; float screenHeightToWidth = screenSize.y / screenSize.x; synchronized (this) { float newZoom = zoom * factor; RectF w1 = new RectF(window); RectF w2 = new RectF(); PointF sceneFocus = new PointF( w1.left + ((screenFocus.x / screenSize.x) * w1.width()), w1.top + ((screenFocus.y / screenSize.y) * w1.height()) ); float w2Width = getPhysicalWidth() * newZoom; if (w2Width > sceneSize.x) { w2Width = sceneSize.x; newZoom = w2Width / getPhysicalWidth(); } if (w2Width < MINIMUM_PIXELS_IN_VIEW) { w2Width = MINIMUM_PIXELS_IN_VIEW; newZoom = w2Width / getPhysicalWidth(); } float w2Height = w2Width * screenHeightToWidth; if (w2Height > sceneSize.y) { w2Height = sceneSize.y; w2Width = w2Height * screenWidthToHeight; newZoom = w2Width / getPhysicalWidth(); } if (w2Height < MINIMUM_PIXELS_IN_VIEW) { w2Height = MINIMUM_PIXELS_IN_VIEW; w2Width = w2Height * screenWidthToHeight; newZoom = w2Width / getPhysicalWidth(); } w2.left = sceneFocus.x - ((screenFocus.x / screenSize.x) * w2Width); w2.top = sceneFocus.y - ((screenFocus.y / screenSize.y) * w2Height); if (w2.left < 0) w2.left = 0; if (w2.top < 0) w2.top = 0; w2.right = w2.left + w2Width; w2.bottom = w2.top + w2Height; if (w2.right > sceneSize.x) { w2.right = sceneSize.x; w2.left = w2.right - w2Width; } if (w2.bottom > sceneSize.y) { w2.bottom = sceneSize.y; w2.top = w2.bottom - w2Height; } //noinspection NumericCastThatLosesPrecision window.set((int) w2.left, (int) w2.top, (int) w2.right, (int) w2.bottom); zoom = newZoom; } } void draw(Canvas canvas) { drawBase(); drawLayer(); drawFinal(); synchronized (bitmapLock) { if ((canvas != null) && (bitmap != null)) { canvas.drawBitmap(bitmap, 0.0F, 0.0F, null); } } } public synchronized Rect getWindow() { return window; } } }