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 */
32package com.jme3.terrain.heightmap;
33
34import java.util.logging.Logger;
35
36/**
37 * <code>CombinerHeightMap</code> generates a new height map based on
38 * two provided height maps. These had maps can either be added together
39 * or substracted from each other. Each heightmap has a weight to
40 * determine how much one will affect the other. By default it is set to
41 * 0.5, 0.5 and meaning the two heightmaps are averaged evenly. This
42 * value can be adjusted at will, as long as the two factors are equal
43 * to 1.0.
44 *
45 * @author Mark Powell
46 * @version $Id$
47 */
48public class CombinerHeightMap extends AbstractHeightMap {
49
50    private static final Logger logger = Logger.getLogger(CombinerHeightMap.class.getName());
51    /**
52     * Constant mode to denote adding the two heightmaps.
53     */
54    public static final int ADDITION = 0;
55    /**
56     * Constant mode to denote subtracting the two heightmaps.
57     */
58    public static final int SUBTRACTION = 1;
59    //the two maps.
60    private AbstractHeightMap map1;
61    private AbstractHeightMap map2;
62    //the two factors
63    private float factor1 = 0.5f;
64    private float factor2 = 0.5f;
65    //the combine mode.
66    private int mode;
67
68    /**
69     * Constructor combines two given heightmaps by the specified mode.
70     * The heightmaps will be evenly distributed. The heightmaps
71     * must be of the same size.
72     *
73     * @param map1 the first heightmap to combine.
74     * @param map2 the second heightmap to combine.
75     * @param mode denotes whether to add or subtract the heightmaps, may
76     *              be either ADDITION or SUBTRACTION.
77     * @throws JmeException if either map is null, their size
78     *              do not match or the mode is invalid.
79     */
80    public CombinerHeightMap(
81            AbstractHeightMap map1,
82            AbstractHeightMap map2,
83            int mode) throws Exception {
84
85
86        //insure all parameters are valid.
87        if (null == map1 || null == map2) {
88            throw new Exception("Height map may not be null");
89        }
90
91
92        if (map1.getSize() != map2.getSize()) {
93            throw new Exception("The two maps must be of the same size");
94        }
95
96
97        if ((factor1 + factor2) != 1.0f) {
98            throw new Exception("factor1 and factor2 must add to 1.0");
99        }
100
101
102        this.size = map1.getSize();
103        this.map1 = map1;
104        this.map2 = map2;
105
106
107        setMode(mode);
108
109        load();
110    }
111
112    /**
113     * Constructor combines two given heightmaps by the specified mode.
114     * The heightmaps will be distributed based on the given factors.
115     * For example, if factor1 is 0.6 and factor2 is 0.4, then 60% of
116     * map1 will be used with 40% of map2. The two factors must add up
117     * to 1.0. The heightmaps must also be of the same size.
118     *
119     * @param map1 the first heightmap to combine.
120     * @param factor1 the factor for map1.
121     * @param map2 the second heightmap to combine.
122     * @param factor2 the factor for map2.
123     * @param mode denotes whether to add or subtract the heightmaps, may
124     *              be either ADDITION or SUBTRACTION.
125     * @throws JmeException if either map is null, their size
126     *              do not match, the mode is invalid, or the factors do not add
127     *              to 1.0.
128     */
129    public CombinerHeightMap(
130            AbstractHeightMap map1,
131            float factor1,
132            AbstractHeightMap map2,
133            float factor2,
134            int mode) throws Exception {
135
136
137        //insure all parameters are valid.
138        if (null == map1 || null == map2) {
139            throw new Exception("Height map may not be null");
140        }
141
142
143        if (map1.getSize() != map2.getSize()) {
144            throw new Exception("The two maps must be of the same size");
145        }
146
147
148        if ((factor1 + factor2) != 1.0f) {
149            throw new Exception("factor1 and factor2 must add to 1.0");
150        }
151
152
153        setMode(mode);
154
155
156        this.size = map1.getSize();
157        this.map1 = map1;
158        this.map2 = map2;
159        this.factor1 = factor1;
160        this.factor2 = factor2;
161
162
163        this.mode = mode;
164
165
166        load();
167    }
168
169    /**
170     * <code>setFactors</code> sets the distribution of heightmaps.
171     * For example, if factor1 is 0.6 and factor2 is 0.4, then 60% of
172     * map1 will be used with 40% of map2. The two factors must add up
173     * to 1.0.
174     * @param factor1 the factor for map1.
175     * @param factor2 the factor for map2.
176     * @throws JmeException if the factors do not add to 1.0.
177     */
178    public void setFactors(float factor1, float factor2) throws Exception {
179        if ((factor1 + factor2) != 1.0f) {
180            throw new Exception("factor1 and factor2 must add to 1.0");
181        }
182
183
184        this.factor1 = factor1;
185        this.factor2 = factor2;
186    }
187
188    /**
189     * <code>setHeightMaps</code> sets the height maps to combine.
190     * The size of the height maps must be the same.
191     * @param map1 the first height map.
192     * @param map2 the second height map.
193     * @throws JmeException if the either heightmap is null, or their
194     *              sizes do not match.
195     */
196    public void setHeightMaps(AbstractHeightMap map1, AbstractHeightMap map2) throws Exception {
197        if (null == map1 || null == map2) {
198            throw new Exception("Height map may not be null");
199        }
200
201
202        if (map1.getSize() != map2.getSize()) {
203            throw new Exception("The two maps must be of the same size");
204        }
205
206
207        this.size = map1.getSize();
208        this.map1 = map1;
209        this.map2 = map2;
210    }
211
212    /**
213     * <code>setMode</code> sets the mode of the combiner. This may either
214     * be ADDITION or SUBTRACTION.
215     * @param mode the mode of the combiner.
216     * @throws JmeException if mode is not ADDITION or SUBTRACTION.
217     */
218    public void setMode(int mode) throws Exception {
219        if (mode != ADDITION && mode != SUBTRACTION) {
220            throw new Exception("Invalid mode");
221        }
222        this.mode = mode;
223    }
224
225    /**
226     * <code>load</code> builds a new heightmap based on the combination of
227     * two other heightmaps. The conditions of the combiner determine the
228     * final outcome of the heightmap.
229     *
230     * @return boolean if the heightmap was successfully created.
231     */
232    public boolean load() {
233        if (null != heightData) {
234            unloadHeightMap();
235        }
236
237
238        heightData = new float[size * size];
239
240
241        float[] temp1 = map1.getHeightMap();
242        float[] temp2 = map2.getHeightMap();
243
244
245        if (mode == ADDITION) {
246            for (int i = 0; i < size; i++) {
247                for (int j = 0; j < size; j++) {
248                    heightData[i + (j * size)] =
249                            (int) (temp1[i + (j * size)] * factor1
250                            + temp2[i + (j * size)] * factor2);
251                }
252            }
253        } else if (mode == SUBTRACTION) {
254            for (int i = 0; i < size; i++) {
255                for (int j = 0; j < size; j++) {
256                    heightData[i + (j * size)] =
257                            (int) (temp1[i + (j * size)] * factor1
258                            - temp2[i + (j * size)] * factor2);
259                }
260            }
261        }
262
263
264        logger.info("Created heightmap using Combiner");
265
266
267        return true;
268    }
269}
270