1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.terrain.heightmap; 34 35 import java.nio.ByteBuffer; 36 import com.jme3.math.ColorRGBA; 37 import com.jme3.texture.Image; 38 import java.nio.ShortBuffer; 39 40 /** 41 * <code>ImageBasedHeightMap</code> is a height map created from the grayscale 42 * conversion of an image. The image used currently must have an equal height 43 * and width, although future work could scale an incoming image to a specific 44 * height and width. 45 * 46 * @author Mike Kienenberger 47 * @version $id$ 48 */ 49 public class ImageBasedHeightMap extends AbstractHeightMap implements ImageHeightmap { 50 51 52 protected Image colorImage; 53 54 55 public void setImage(Image image) { 56 this.colorImage = image; 57 } 58 59 /** 60 * Creates a HeightMap from an Image. The image will be converted to 61 * grayscale, and the grayscale values will be used to generate the height 62 * map. White is highest point while black is lowest point. 63 * 64 * Currently, the Image used must be square (width == height), but future 65 * work could rescale the image. 66 * 67 * @param colorImage 68 * Image to map to the height map. 69 */ 70 public ImageBasedHeightMap(Image colorImage) { 71 this.colorImage = colorImage; 72 } 73 74 public ImageBasedHeightMap(Image colorImage, float heightScale) { 75 this.colorImage = colorImage; 76 this.heightScale = heightScale; 77 } 78 79 /** 80 * Loads the image data from top left to bottom right 81 */ 82 public boolean load() { 83 return load(false, false); 84 } 85 86 /** 87 * Get the grayscale value, or override in your own sub-classes 88 */ 89 protected float calculateHeight(float red, float green, float blue) { 90 return (float) (0.299 * red + 0.587 * green + 0.114 * blue); 91 } 92 93 public boolean load(boolean flipX, boolean flipY) { 94 95 int imageWidth = colorImage.getWidth(); 96 int imageHeight = colorImage.getHeight(); 97 98 if (imageWidth != imageHeight) 99 throw new RuntimeException("imageWidth: " + imageWidth 100 + " != imageHeight: " + imageHeight); 101 102 size = imageWidth; 103 104 ByteBuffer buf = colorImage.getData(0); 105 106 heightData = new float[(imageWidth * imageHeight)]; 107 108 ColorRGBA colorStore = new ColorRGBA(); 109 110 int index = 0; 111 if (flipY) { 112 for (int h = 0; h < imageHeight; ++h) { 113 if (flipX) { 114 for (int w = imageWidth - 1; w >= 0; --w) { 115 int baseIndex = (h * imageWidth)+ w; 116 heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale; 117 } 118 } else { 119 for (int w = 0; w < imageWidth; ++w) { 120 int baseIndex = (h * imageWidth)+ w; 121 heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale; 122 } 123 } 124 } 125 } else { 126 for (int h = imageHeight - 1; h >= 0; --h) { 127 if (flipX) { 128 for (int w = imageWidth - 1; w >= 0; --w) { 129 int baseIndex = (h * imageWidth)+ w; 130 heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale; 131 } 132 } else { 133 for (int w = 0; w < imageWidth; ++w) { 134 int baseIndex = (h * imageWidth)+ w; 135 heightData[index++] = getHeightAtPostion(buf, colorImage, baseIndex, colorStore)*heightScale; 136 } 137 } 138 } 139 } 140 141 return true; 142 } 143 144 protected float getHeightAtPostion(ByteBuffer buf, Image image, int position, ColorRGBA store) { 145 switch (image.getFormat()){ 146 case RGBA8: 147 buf.position( position * 4 ); 148 store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get())); 149 return calculateHeight(store.r, store.g, store.b); 150 case ABGR8: 151 buf.position( position * 4 ); 152 float a = byte2float(buf.get()); 153 float b = byte2float(buf.get()); 154 float g = byte2float(buf.get()); 155 float r = byte2float(buf.get()); 156 store.set(r,g,b,a); 157 return calculateHeight(store.r, store.g, store.b); 158 case RGB8: 159 buf.position( position * 3 ); 160 store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), 1); 161 return calculateHeight(store.r, store.g, store.b); 162 case Luminance8: 163 buf.position( position ); 164 return byte2float(buf.get())*255*heightScale; 165 case Luminance16: 166 ShortBuffer sbuf = buf.asShortBuffer(); 167 sbuf.position( position ); 168 return (sbuf.get() & 0xFFFF) / 65535f * 255f * heightScale; 169 default: 170 throw new UnsupportedOperationException("Image format: "+image.getFormat()); 171 } 172 } 173 174 private float byte2float(byte b){ 175 return ((float)(b & 0xFF)) / 255f; 176 } 177 }