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