]> icculus.org git repositories - divverent/nexuiz.git/blob - tools/ImgToMap/src/imgtomap/MapWriter.java
round z values down to integer values
[divverent/nexuiz.git] / tools / ImgToMap / src / imgtomap / MapWriter.java
1 /*
2  * To change this template, choose Tools | Templates
3  * and open the template in the editor.
4  */
5 package imgtomap;
6
7 import java.awt.image.BufferedImage;
8 import java.awt.image.Raster;
9 import java.io.File;
10 import java.io.FileNotFoundException;
11 import java.io.FileOutputStream;
12 import java.io.IOException;
13 import java.io.PrintWriter;
14 import java.util.LinkedList;
15 import java.util.List;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
18 import javax.imageio.ImageIO;
19
20 /**
21  *
22  * @author maik
23  */
24 public class MapWriter {
25
26     public int writeMap(Parameters p) {
27         if (!(new File(p.infile).exists())) {
28             return 1;
29         }
30
31         double[][] height = getHeightmap(p.infile);
32         double[][] columns = getColumns(height);
33         double units = 1d * p.pixelsize;
34         double max = p.height;
35
36         PrintWriter pw = null;
37         try {
38             pw = new PrintWriter(new FileOutputStream(new File(p.outfile)));
39         } catch (FileNotFoundException ex) {
40             Logger.getLogger(MapWriter.class.getName()).log(Level.SEVERE, null, ex);
41             return 1;
42         }
43
44         // worldspawn start
45         pw.print("{\n\"classname\" \"worldspawn\"\n");
46
47         double xmax = (columns.length - 1) * units;
48         double ymax = (columns[0].length - 1) * units;
49
50         if (p.skyfill) {
51             List<Block> fillers = genSkyFillers(columns);
52             for (Block b : fillers) {
53                 double x = b.x * units;
54                 double y = (b.y + b.ydim) * units;
55                 x = x > xmax ? xmax : x;
56                 y = y > ymax ? ymax : y;
57                 Vector3D p1 = new Vector3D(x, -y, -32.0);
58
59                 x = (b.x + b.xdim) * units;
60                 y = b.y * units;
61                 x = x > xmax ? xmax : x;
62                 y = y > ymax ? ymax : y;
63                 Vector3D p2 = new Vector3D(x, -y, p.skyheight);
64
65                 writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
66             }
67         }
68
69         if (p.sky) {
70             // generate skybox
71             int x = height.length - 1;
72             int y = height[0].length - 1;
73
74             // top
75             Vector3D p1 = new Vector3D(0, -y * units, p.skyheight);
76             Vector3D p2 = new Vector3D(x * units, 0, p.skyheight + 32.0);
77             writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
78
79             // bottom
80             p1 = new Vector3D(0, -y * units, -64.0);
81             p2 = new Vector3D(x * units, 0, -32.0);
82             writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
83
84             // north
85             p1 = new Vector3D(0, 0, -32.0);
86             p2 = new Vector3D(x * units, 32, p.skyheight);
87             writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
88
89             // east
90             p1 = new Vector3D(x * units, -y * units, -32.0);
91             p2 = new Vector3D(x * units + 32.0, 0, p.skyheight);
92             writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
93
94             // south
95             p1 = new Vector3D(0, -y * units - 32, -32.0);
96             p2 = new Vector3D(x * units, -y * units, p.skyheight);
97             writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
98
99
100             // west
101             p1 = new Vector3D(0 - 32.0, -y * units, -32.0);
102             p2 = new Vector3D(0, 0, p.skyheight);
103             writeBoxBrush(pw, p1, p2, false, p.skytexture, 1.0);
104
105         }
106
107         // genBlockers screws the columns array!
108         // this should be the last step!
109         if (p.visblockers) {
110             List<Block> blockers = genBlockers(columns, 0.15);
111             for (Block b : blockers) {
112                 double z = b.minheight * p.height - 1;
113                 z = Math.floor(z / 16);
114                 z = z * 16;
115
116                 if (z > 0) {
117                     double x = b.x * units;
118                     double y = (b.y + b.ydim) * units;
119                     x = x > xmax ? xmax : x;
120                     y = y > ymax ? ymax : y;
121                     Vector3D p1 = new Vector3D(x, -y, -32.0);
122
123                     x = (b.x + b.xdim) * units;
124                     y = b.y * units;
125                     x = x > xmax ? xmax : x;
126                     y = y > ymax ? ymax : y;
127                     Vector3D p2 = new Vector3D(x, -y, z);
128
129                     writeBoxBrush(pw, p1, p2, false, "common/caulk", 1.0);
130                 }
131
132             }
133         }
134
135         // worldspawn end
136         pw.print("}\n");
137
138         // func_group start
139         pw.print("{\n\"classname\" \"func_group\"\n");
140         pw.print("\n\"terrain\" \"1\"\n");
141         // wander through grid
142         for (int x = 0; x < height.length - 1; ++x) {
143             for (int y = 0; y < height[0].length - 1; ++y) {
144
145                 boolean skip = getMinMaxForRegion(height, x, y, 2)[0] < 0;
146
147                 if (!skip) {
148
149                     /*
150                      * 
151                      *      a +-------+ b
152                      *       /       /|
153                      *      /       / |
154                      *     /       /  |
155                      *  c +-------+ d + f   (e occluded, unused)
156                      *    |       |  /
157                      *    |       | /
158                      *    |       |/
159                      *  g +-------+ h
160                      * 
161                      */
162
163
164                     // delta a - d
165                     double grad1 = Math.abs(height[x][y] - height[x + 1][y + 1]);
166
167                     /// delta b - c
168                     double grad2 = Math.abs(height[x + 1][y] - height[x][y + 1]);
169
170                     Vector3D a = new Vector3D(x * units, -y * units, Math.floor(height[x][y] * max));
171                     Vector3D b = new Vector3D((x + 1) * units, -y * units, Math.floor(height[x + 1][y] * max));
172                     Vector3D c = new Vector3D(x * units, -(y + 1) * units, Math.floor(height[x][y + 1] * max));
173                     Vector3D d = new Vector3D((x + 1) * units, -(y + 1) * units, Math.floor(height[x + 1][y + 1] * max));
174                     //Vector3D e = new Vector3D(x * units, -y * units, -16.0);
175                     Vector3D f = new Vector3D((x + 1) * units, -y * units, -16.0);
176                     Vector3D g = new Vector3D(x * units, -(y + 1) * units, -16.0);
177                     Vector3D h = new Vector3D((x + 1) * units, -(y + 1) * units, -16.0);
178
179                     if (grad1 > grad2) {
180                         pw.print("{\n");
181                         pw.print(getMapPlaneString(a, b, c, p.detail, p.texture, p.texturescale));
182                         pw.print(getMapPlaneString(f, b, a, p.detail, "common/caulk", p.texturescale));
183                         pw.print(getMapPlaneString(a, c, g, p.detail, "common/caulk", p.texturescale));
184                         pw.print(getMapPlaneString(g, h, f, p.detail, "common/caulk", p.texturescale));
185                         pw.print(getMapPlaneString(g, c, b, p.detail, "common/caulk", p.texturescale));
186                         pw.print("}\n");
187
188
189                         pw.print("{\n");
190                         pw.print(getMapPlaneString(b, d, c, p.detail, p.texture, p.texturescale));
191                         pw.print(getMapPlaneString(d, h, g, p.detail, "common/caulk", p.texturescale));
192                         pw.print(getMapPlaneString(d, b, f, p.detail, "common/caulk", p.texturescale));
193                         pw.print(getMapPlaneString(f, b, c, p.detail, "common/caulk", p.texturescale));
194                         pw.print(getMapPlaneString(g, h, f, p.detail, "common/caulk", p.texturescale));
195                         pw.print("}\n");
196
197                     } else {
198
199                         pw.print("{\n");
200                         pw.print(getMapPlaneString(a, b, d, p.detail, p.texture, p.texturescale));
201                         pw.print(getMapPlaneString(d, b, f, p.detail, "common/caulk", p.texturescale));
202                         pw.print(getMapPlaneString(f, b, a, p.detail, "common/caulk", p.texturescale));
203                         pw.print(getMapPlaneString(a, d, h, p.detail, "common/caulk", p.texturescale));
204                         pw.print(getMapPlaneString(g, h, f, p.detail, "common/caulk", p.texturescale));
205                         pw.print("}\n");
206
207
208                         pw.print("{\n");
209                         pw.print(getMapPlaneString(d, c, a, p.detail, p.texture, p.texturescale));
210                         pw.print(getMapPlaneString(g, c, d, p.detail, "common/caulk", p.texturescale));
211                         pw.print(getMapPlaneString(c, g, a, p.detail, "common/caulk", p.texturescale));
212                         pw.print(getMapPlaneString(h, d, a, p.detail, "common/caulk", p.texturescale));
213                         pw.print(getMapPlaneString(g, h, f, p.detail, "common/caulk", p.texturescale));
214                         pw.print("}\n");
215                     }
216                 }
217             }
218         }
219         // func_group end
220         pw.print("}\n");
221
222         pw.close();
223         return 0;
224     }
225
226     private void writeBoxBrush(PrintWriter pw, Vector3D p1, Vector3D p2, boolean detail, String texture, double scale) {
227         Vector3D a = new Vector3D(p1.x, p2.y, p2.z);
228         Vector3D b = p2;
229         Vector3D c = new Vector3D(p1.x, p1.y, p2.z);
230         Vector3D d = new Vector3D(p2.x, p1.y, p2.z);
231         //Vector3D e unused
232         Vector3D f = new Vector3D(p2.x, p2.y, p1.z);
233         Vector3D g = p1;
234         Vector3D h = new Vector3D(p2.x, p1.y, p1.z);
235
236         pw.print("{\n");
237         pw.print(getMapPlaneString(a, b, d, detail, texture, scale));
238         pw.print(getMapPlaneString(d, b, f, detail, texture, scale));
239         pw.print(getMapPlaneString(c, d, h, detail, texture, scale));
240         pw.print(getMapPlaneString(a, c, g, detail, texture, scale));
241         pw.print(getMapPlaneString(f, b, a, detail, texture, scale));
242         pw.print(getMapPlaneString(g, h, f, detail, texture, scale));
243         pw.print("}\n");
244
245     }
246
247     private String getMapPlaneString(Vector3D p1, Vector3D p2, Vector3D p3, boolean detail, String material, double scale) {
248         int flag;
249         if (detail) {
250             flag = 134217728;
251         } else {
252             flag = 0;
253         }
254         return "( " + p1.x + " " + p1.y + " " + p1.z + " ) ( " + p2.x + " " + p2.y + " " + p2.z + " ) ( " + p3.x + " " + p3.y + " " + p3.z + " ) " + material + " 0 0 0 " + scale + " " + scale + " " + flag + " 0 0\n";
255     }
256
257     private double[][] getHeightmap(String file) {
258         try {
259             BufferedImage bimg = ImageIO.read(new File(file));
260             Raster raster = bimg.getRaster();
261             int x = raster.getWidth();
262             int y = raster.getHeight();
263
264             double[][] result = new double[x][y];
265
266             for (int xi = 0; xi < x; ++xi) {
267                 for (int yi = 0; yi < y; ++yi) {
268                     float[] pixel = raster.getPixel(xi, yi, (float[]) null);
269
270                     int channels;
271                     boolean alpha;
272                     if (pixel.length == 3) {
273                         // RGB
274                         channels = 3;
275                         alpha = false;
276                     } else if (pixel.length == 4) {
277                         // RGBA
278                         channels = 3;
279                         alpha = true;
280                     } else if (pixel.length == 1) {
281                         // grayscale
282                         channels = 1;
283                         alpha = false;
284                     } else {
285                         // grayscale with alpha
286                         channels = 1;
287                         alpha = true;
288                     }
289
290                     float tmp = 0f;
291                     for (int i = 0; i < channels; ++i) {
292                         tmp += pixel[i];
293                     }
294                     result[xi][yi] = tmp / (channels * 255f);
295
296                     if (alpha) {
297                         // mark this pixel to be skipped
298                         if (pixel[pixel.length - 1] < 64.0) {
299                             result[xi][yi] = -1.0;
300                         }
301                     }
302                 }
303             }
304
305
306             return result;
307         } catch (IOException ex) {
308             Logger.getLogger(MapWriter.class.getName()).log(Level.SEVERE, null, ex);
309         }
310
311         return null;
312     }
313
314     private double[][] getColumns(double[][] heights) {
315         double[][] result = new double[heights.length][heights[0].length];
316
317         for (int x = 0; x < heights.length; ++x) {
318             for (int y = 0; y < heights[0].length; ++y) {
319                 result[x][y] = getMinMaxForRegion(heights, x, y, 2)[0];
320             }
321         }
322
323         return result;
324     }
325
326     private double[] getMinMaxForRegion(double[][] field, int x, int y, int dim) {
327         return getMinMaxForRegion(field, x, y, dim, dim);
328     }
329
330     private double[] getMinMaxForRegion(double[][] field, int x, int y, int xdim, int ydim) {
331         double max = -100d;
332         double min = 100d;
333
334         for (int i = x; i < x + xdim; ++i) {
335             for (int j = y; j < y + ydim; ++j) {
336                 if (i >= 0 && j >= 0 && i < field.length && j < field[0].length) {
337                     min = field[i][j] < min ? field[i][j] : min;
338                     max = field[i][j] > max ? field[i][j] : max;
339                 }
340             }
341         }
342
343         double[] result = {min, max};
344         return result;
345     }
346
347     private List<Block> genBlockers(double[][] columns, double delta) {
348
349         Block[][] blockers = new Block[columns.length][columns[0].length];
350         LinkedList<Block> result = new LinkedList<Block>();
351
352         for (int x = 0; x < columns.length; ++x) {
353             for (int y = 0; y < columns[0].length; ++y) {
354                 if (blockers[x][y] == null && columns[x][y] >= 0) {
355                     // this pixel isn't covered by a blocker yet... so let's create one!
356                     Block b = new Block();
357                     result.add(b);
358                     b.x = x;
359                     b.y = y;
360                     b.minheight = b.origheight = columns[x][y];
361
362                     // grow till the delta hits
363                     int xdim = 1;
364                     int ydim = 1;
365                     boolean xgrow = true;
366                     boolean ygrow = true;
367                     double min = b.minheight;
368                     for (; xdim < columns.length && ydim < columns[0].length;) {
369                         double[] minmax = getMinMaxForRegion(columns, x, y, xdim + 1, ydim);
370                         if (Math.abs(b.origheight - minmax[0]) > delta || Math.abs(b.origheight - minmax[1]) > delta) {
371                             xgrow = false;
372                         }
373
374                         minmax = getMinMaxForRegion(columns, x, y, xdim, ydim + 1);
375                         if (Math.abs(b.origheight - minmax[0]) > delta || Math.abs(b.origheight - minmax[1]) > delta) {
376                             ygrow = false;
377                         }
378
379                         min = minmax[0];
380
381                         if (xgrow) {
382                             ++xdim;
383                         }
384                         if (ygrow) {
385                             ++ydim;
386                         }
387
388                         minmax = getMinMaxForRegion(columns, x, y, xdim, ydim);
389                         min = minmax[0];
390
391                         if (!(xgrow || ygrow)) {
392                             break;
393                         }
394                     }
395
396                     b.xdim = xdim;
397                     b.ydim = ydim;
398                     b.minheight = min;
399
400                     for (int i = x; i < x + b.xdim; ++i) {
401                         for (int j = y; j < y + b.ydim; ++j) {
402                             if (i >= 0 && j >= 0 && i < blockers.length && j < blockers[0].length) {
403                                 blockers[i][j] = b;
404                                 columns[i][j] = -1337.0;
405                             }
406                         }
407                     }
408
409                 }
410             }
411         }
412         return result;
413     }
414
415     private List<Block> genSkyFillers(double[][] columns) {
416
417         double delta = 0;
418
419         for (int x = 0; x < columns.length; ++x) {
420             for (int y = 0; y < columns[0].length; ++y) {
421                 if (columns[x][y] < 0) {
422                     // this is a skipped block, see if it neighbours a
423                     // relevant block
424                     if (getMinMaxForRegion(columns, x - 1, y - 1, 3)[1] >= 0) {
425                         columns[x][y] = -100d;
426                     }
427                 }
428             }
429         }
430
431
432         Block[][] fillers = new Block[columns.length][columns[0].length];
433         LinkedList<Block> result = new LinkedList<Block>();
434
435         for (int x = 0; x < columns.length; ++x) {
436             for (int y = 0; y < columns[0].length; ++y) {
437                 if (fillers[x][y] == null && columns[x][y] == -100d) {
438                     // this pixel is marked to be skyfill
439                     Block b = new Block();
440                     result.add(b);
441                     b.x = x;
442                     b.y = y;
443                     b.minheight = b.origheight = columns[x][y];
444
445                     // grow till the delta hits
446                     int xdim = 1;
447                     int ydim = 1;
448                     boolean xgrow = true;
449                     boolean ygrow = true;
450                     double min = b.minheight;
451                     for (; xdim < columns.length && ydim < columns[0].length;) {
452                         double[] minmax = getMinMaxForRegion(columns, x, y, xdim + 1, ydim);
453                         if (Math.abs(b.origheight - minmax[0]) > delta || Math.abs(b.origheight - minmax[1]) > delta) {
454                             xgrow = false;
455                         }
456
457                         minmax = getMinMaxForRegion(columns, x, y, xdim, ydim + 1);
458                         if (Math.abs(b.origheight - minmax[0]) > delta || Math.abs(b.origheight - minmax[1]) > delta) {
459                             ygrow = false;
460                         }
461
462                         min = minmax[0];
463
464                         if (xgrow) {
465                             ++xdim;
466                         }
467                         if (ygrow) {
468                             ++ydim;
469                         }
470
471                         minmax = getMinMaxForRegion(columns, x, y, xdim, ydim);
472                         min = minmax[0];
473
474                         if (!(xgrow || ygrow)) {
475                             break;
476                         }
477                     }
478
479                     b.xdim = xdim;
480                     b.ydim = ydim;
481                     b.minheight = min;
482
483                     for (int i = x; i < x + b.xdim; ++i) {
484                         for (int j = y; j < y + b.ydim; ++j) {
485                             if (i >= 0 && j >= 0 && i < fillers.length && j < fillers[0].length) {
486                                 fillers[i][j] = b;
487                                 columns[i][j] = -1337.0;
488                             }
489                         }
490                     }
491
492                 }
493             }
494         }
495         return result;
496     }
497
498     private class Vector3D {
499
500         public double x,  y,  z;
501
502         public Vector3D() {
503             this(0.0, 0.0, 0.0);
504         }
505
506         public Vector3D(double x, double y, double z) {
507             this.x = x;
508             this.y = y;
509             this.z = z;
510         }
511
512         public Vector3D crossproduct(Vector3D p1) {
513             Vector3D result = new Vector3D();
514
515             result.x = this.y * p1.z - this.z * p1.y;
516             result.y = this.z * p1.x - this.x * p1.z;
517             result.z = this.x * p1.y - this.y * p1.x;
518
519             return result;
520         }
521
522         public double dotproduct(Vector3D p1) {
523             return this.x * p1.x + this.y * p1.y + this.z * p1.z;
524         }
525
526         public Vector3D substract(Vector3D p1) {
527             Vector3D result = new Vector3D();
528
529             result.x = this.x - p1.x;
530             result.y = this.y - p1.y;
531             result.z = this.z - p1.z;
532
533             return result;
534         }
535
536         public void scale(double factor) {
537             x *= factor;
538             y *= factor;
539             z *= factor;
540         }
541
542         public double length() {
543             return Math.sqrt((x * x) + (y * y) + (z * z));
544         }
545
546         public void normalize() {
547             double l = length();
548
549             x /= l;
550             y /= l;
551             z /= l;
552         }
553     }
554
555     private class Block {
556
557         public int x,  y,  xdim,  ydim;
558         public double origheight,  minheight;
559     }
560 }