/* * Aphelion * Copyright (c) 2013 Joris van der Wel & Joshua Edwards * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version (for example by appending a copyright notice). * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.client.graphics.world; import aphelion.client.graphics.Graph; import aphelion.client.graphics.screen.Camera; import aphelion.client.resource.AsyncTexture; import aphelion.shared.resource.ResourceDB; import aphelion.shared.swissarmyknife.Point; import aphelion.shared.swissarmyknife.SwissArmyKnife; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.newdawn.slick.Color; import org.newdawn.slick.Image; /** * Starfield with depth. * * @author Joris */ public class StarField { private final int seed; private static final int STAR_TILE_SIZE = 512; private final Color layer1Color; private final Color layer2Color; private final Color layer3Color; private final Color layer4Color; private final Color layer5Color; private final ResourceDB db; //Background objects private final AsyncTexture[] bg = new AsyncTexture[14]; //Star objects private final AsyncTexture[] star = new AsyncTexture[7]; private boolean lineEffect = false; private Point last_position; public StarField(int seed, @Nonnull ResourceDB db) { this.db = db; loadBackgroundObjects(); this.seed = seed; layer1Color = new Color(.7f, .7f, .7f); layer2Color = new Color(.5f, .5f, .5f); layer3Color = new Color(.3f, .3f, .3f); layer4Color = new Color(.2f, .2f, .2f); layer5Color = new Color(.15f, .15f, .15f); } public void setLineEffect(boolean lineEffect) { this.lineEffect = lineEffect; } private void loadBackgroundObjects() { //Load background objects for (int n = 0; n < bg.length; ++n) { String key = String.format("classic.background.bg%02d", n); if (db.resourceExists(key)) { bg[n] = db.getTextureLoader().getTexture(key); } } //Load star objects for (int n = 0; n < star.length; ++n) { String key = String.format("classic.background.star%02d", n); if (db.resourceExists(key)) { star[n] = db.getTextureLoader().getTexture(key); } } } private static Color colorWithZoom(Color color, float cameraZoomFactor) { float subInt; if (cameraZoomFactor < 1) { subInt = 1 - cameraZoomFactor; } else { subInt = 0; } return new Color(color.r - 2 * subInt, color.g - 2 * subInt, color.b - 2 * subInt); } public void render(@Nonnull Camera camera) { Color layer1Color_modified, layer2Color_modified, layer3Color_modified, layer4Color_modified, layer6Color_modified, layer5Color_modified; int layer1_starscale = 2, layer2_starscale = 4, layer3_starscale = 4, layer4_starscale = 4, layer5_starscale = 4, layer6_starscale = 2; float layer1_depth_factor = 0.8F, layer2_depth_factor = 0.5F, layer3_depth_factor = 0.4F, layer4_depth_factor = 0.3F, layer5_depth_factor = 0.2F, layer6_depth_factor = 0.1F; float sparkle_factor = 6F; layer1Color_modified = new Color( layer1Color.r + (SwissArmyKnife.random.nextFloat() / (layer1_depth_factor + sparkle_factor)), layer1Color.g + (SwissArmyKnife.random.nextFloat() / (layer1_depth_factor + sparkle_factor)), layer1Color.b + (SwissArmyKnife.random.nextFloat() / (layer1_depth_factor + sparkle_factor))); layer2Color_modified = new Color( layer1Color.r + (SwissArmyKnife.random.nextFloat() / (layer2_depth_factor + sparkle_factor)), layer1Color.g + (SwissArmyKnife.random.nextFloat() / (layer2_depth_factor + sparkle_factor)), layer1Color.b + (SwissArmyKnife.random.nextFloat() / (layer2_depth_factor + sparkle_factor))); layer3Color_modified = new Color( layer2Color.r + (SwissArmyKnife.random.nextFloat() / (layer3_depth_factor + sparkle_factor)), layer2Color.g + (SwissArmyKnife.random.nextFloat() / (layer3_depth_factor + sparkle_factor)), layer2Color.b + (SwissArmyKnife.random.nextFloat() / (layer3_depth_factor + sparkle_factor))); layer4Color_modified = new Color( layer3Color.r + (SwissArmyKnife.random.nextFloat() / (layer4_depth_factor + sparkle_factor)), layer3Color.g + (SwissArmyKnife.random.nextFloat() / (layer4_depth_factor + sparkle_factor)), layer3Color.b + (SwissArmyKnife.random.nextFloat() / (layer4_depth_factor + sparkle_factor))); layer5Color_modified = new Color( layer4Color.r + (SwissArmyKnife.random.nextFloat() / (layer5_depth_factor + sparkle_factor)), layer4Color.g + (SwissArmyKnife.random.nextFloat() / (layer5_depth_factor + sparkle_factor)), layer4Color.b + (SwissArmyKnife.random.nextFloat() / (layer5_depth_factor + sparkle_factor))); layer6Color_modified = new Color( layer5Color.r + (SwissArmyKnife.random.nextFloat() / (layer6_depth_factor + sparkle_factor)), layer5Color.g + (SwissArmyKnife.random.nextFloat() / (layer6_depth_factor + sparkle_factor)), layer5Color.b + (SwissArmyKnife.random.nextFloat() / (layer6_depth_factor + sparkle_factor))); Graph.g.setAntiAlias(true); drawStars(camera, colorWithZoom(layer1Color_modified, camera.zoom), layer1_depth_factor, layer1_starscale, 0); drawStars(camera, colorWithZoom(layer2Color_modified, camera.zoom), layer2_depth_factor, layer2_starscale, 1); drawStars(camera, colorWithZoom(layer3Color_modified, camera.zoom), layer3_depth_factor, layer3_starscale, 2); drawStars(camera, colorWithZoom(layer4Color_modified, camera.zoom), layer4_depth_factor, layer4_starscale, 3); drawStars(camera, colorWithZoom(layer5Color_modified, camera.zoom), layer5_depth_factor, layer5_starscale, 4); drawStars(camera, colorWithZoom(layer6Color_modified, camera.zoom), layer6_depth_factor, layer6_starscale, 5); Graph.g.setAntiAlias(false); last_position = camera.pos; } private void drawStars(@Nonnull Camera camera, @Nullable Color color, float depthFactor, int starscale, int seedOffset) { if (last_position == null) { last_position = camera.pos; } float dx = (last_position.x - camera.pos.x) / (6 + depthFactor); float dy = (last_position.y - camera.pos.y) / (6 + depthFactor); // http://nullprogram.com/blog/2011/06/13/ int size = STAR_TILE_SIZE / starscale; int fieldSeed = this.seed + seedOffset; int mapX = (int) ((camera.pos.x) * depthFactor); int mapY = (int) ((camera.pos.y) * depthFactor); int startX = ((mapX - camera.dimensionX / 2) / size) * size - size; int startY = ((mapY - camera.dimensionY / 2) / size) * size - size; int endX = camera.dimensionX + startX + size * 3; int endY = camera.dimensionY + startY + size * 3; if (color != null) { Graph.g.setColor(color); } for (int x = startX; x <= endX; x += size) { for (int y = startY; y <= endY; y += size) { int hash = SwissArmyKnife.jenkinMix(fieldSeed, x, y); for (int n = 0; n < 3; n++) { int px = (hash % size) + (x - mapX); hash >>= 3; int py = (hash % size) + (y - mapY); hash >>= 3; float pxf = px + camera.dimensionHalf.x; float pyf = py + camera.dimensionHalf.y; byte backgroundObjectType = getBackgroundType(px, py, hash, seedOffset); float pxi = SwissArmyKnife.floor(pxf); float pyi = SwissArmyKnife.floor(pyf); if (backgroundObjectType == 0) { if (lineEffect) { Graph.g.drawLine(pxf, pyf, SwissArmyKnife.floor(pxf + dx), SwissArmyKnife.floor(pyf + dy)); } else { Graph.g.drawLine(pxf, pyf, pxf, pyf); } } else if (backgroundObjectType > 0) { AsyncTexture tex = star[backgroundObjectType-1]; if (tex != null) { Image img = tex.getCachedImage(); if (img != null) { img.draw(pxi, pyi); } } } else if (backgroundObjectType < 0) { AsyncTexture tex = bg[-backgroundObjectType-1]; if (tex != null) { Image img = tex.getCachedImage(); if (img != null) { img.draw(pxi, pyi); } } } } } } } private byte getBackgroundType(int px, int py, int hash, int layer) { int value; int offset; if (hash > 100000) { value = 100000; //planets layer if (layer == 1) { offset = 4000; if (hash > value && hash < value + (offset)) { return -8; } else if (hash > value + (offset) && hash < value + (offset * 2)) { return -1; } else if (hash > value + (offset * 2) && hash < value + (offset * 3)) { return -9; } else if (hash > value + (offset * 3) && hash < value + (offset * 4)) { return -2; } else if (hash > value + (offset * 4) && hash < value + (offset) * 5) { return -10; } else if (hash > value + (offset * 5) && hash < value + (offset * 6)) { return -3; } else if (hash > value + (offset * 6) && hash < value + (offset * 7)) { return -12; } else if (hash > value + (offset * 7) && hash < value + (offset * 8)) { return -4; } else if (hash > value + (offset * 8) && hash < value + (offset * 9)) { return -13; } else if (hash > value + (offset * 9) && hash < value + (offset * 10)) { return -5; } } else if (layer == 4) { value = 2000; offset = 10000; if (hash > value && hash < value + (offset)) { return -6; } else if (hash > value && hash < value + (offset * 2)) { return -7; } else if (hash > value && hash < value + (offset * 3)) { return -14; } } } else { if (layer == 0) { value = 7000; offset = 100; if (hash > value && hash < value + (offset)) { return 1; } else if (hash > value + (offset) && hash < value + (offset * 2)) { return 2; } } else if (layer > 0 && layer < 3) { value = 4000; offset = 250; if (hash > value && hash < value + (offset)) { return 3; } else if (hash > value + (offset) && hash < value + (offset * 2)) { return 4; } else if (hash > value + (offset * 2) && hash < value + (offset * 3)) { return 5; } } else if (layer == 3) { value = 10000; offset = 1000; if (hash > value && hash < value + (offset)) { return 6; } else if (hash > value + (offset) && hash < value + (offset * 2)) { return 7; } } } return 0; } }