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
33package com.jme3.terrain.heightmap;
34
35import java.nio.ByteBuffer;
36import com.jme3.math.ColorRGBA;
37import com.jme3.texture.Image;
38import 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 */
49public 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}