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