1addScript(name, tooltip, method) { 2 if (tooltip == null) tooltip = name; 3 menuItem = new JMenuItem(name); 4 menuItem.addActionListener(this); 5 menuItem.setToolTipText(tooltip); 6 global.scriptMenu.add(menuItem); 7 actionPerformed(ae) { 8 this.invokeMethod(method, null); 9 } 10} 11 12addScript("Randomize", "Randomize the selected wall polygons", "randomize"); 13addScript("Shadow", "Adds a shadow to the selected wall polygons", "shadow"); 14addScript("Round", "Rounds the corners of the selected wall polygons", "round"); 15addScript("Image to polygon", "Creates a polygon from an image", "imageToPoly"); 16addScript("Bevel-border", "Adds a bevel border to a polygon", "bevelBorder"); 17addScript("Depth-decor", "Adds a depth decoration to a polygon", "depthDecor"); 18addScript("Select all polygons", "Selects all top-level polygons", "selectAllPolys"); 19 20import org.xpilot.jxpmap.*; 21 22 23/************************ Misc Utils ******************************/ 24 25 26/** 27 * Utility to check if a map objects is a regular wall polygon 28 */ 29isWall(p) { 30 return (p instanceof MapPolygon); 31} 32 33/** 34 * Finds a style with the matching id from the list 35 */ 36findStyle(id, list) { 37 for (style:list) 38 if (style.id.equals(id)) 39 return style; 40 return null; 41} 42 43/** 44 * Shows a message box. 45 */ 46alert(msg) { 47 JOptionPane.showMessageDialog(null, msg); 48} 49 50/** 51 * Prompts the user for input. 52 */ 53prompt(msg) { 54 return JOptionPane.showInputDialog(null, msg); 55} 56 57/** 58 * Prompts the user for input with the given default value. 59 */ 60prompt(msg, def) { 61 return JOptionPane.showInputDialog(null, msg, def); 62} 63 64/** 65 * Gets the center point of the visible area on the map. 66 */ 67center() { 68 center = new Point(editor.width / 2, editor.height / 2); 69 return editor.getInverse().transform(center, new Point()); 70} 71 72/** 73 * Selects all top-level polygons. 74 */ 75selectAllPolys() { 76 editor.setSelectedObject(null); 77 for(o:editor.model.objects) 78 if (o instanceof MapPolygon) 79 editor.addSelectedObject(o); 80 editor.repaint(); 81} 82 83/************************ Randomize script ******************************/ 84 85 86/** 87 * Randomizes a wall polygon with the given amount of pixels. 88 */ 89doRandomize(pixels, o) { 90 if (isWall(o)) { 91 for (i = 0; i < o.getNumPoints(); i++) { 92 p = o.getPoint(i); 93 p.translate((int)((Math.random() - 0.5) * pixels * 64), 94 (int)((Math.random() - 0.5) * pixels * 64)); 95 o.setPoint(i, p.x, p.y); 96 } 97 } else if (o instanceof Group) { 98 for (i:o.members) doRandomize(pixels, i); 99 o.invalidate(); 100 } 101} 102 103randomize() { 104 if (editor.getSelectedObjects().isEmpty()) { 105 alert("First select a polygon"); 106 return; 107 } 108 value = prompt("How many pixels to randomize"); 109 if (value == null) return; 110 for (o:editor.selectedObjects) 111 doRandomize(Integer.parseInt(value), o); 112 editor.repaint(); 113} 114 115 116/************************ Shadow script ******************************/ 117 118 119/** 120 * Adds a drop shadow to a wall polygon by creating 121 * a dark gray decoration polygon below it. 122 */ 123addShadow(polygon, style) { 124 p = polygon.getPolygon(); 125 sp = new Polygon(p.xpoints, p.ypoints, p.npoints); 126 for(i = 0; i < sp.npoints; i++) { 127 sp.xpoints[i] += 240; 128 sp.ypoints[i] -= 640; 129 } 130 sp.invalidate(); 131 decor = new Decoration(); 132 decor.addToFront(new MapPolygon(sp, style)); 133 editor.model.addToBack(decor); 134} 135 136/** 137 * Finds or creates the edge style and polygon style required 138 * for a drop shadow. 139 */ 140getShadowStyle() { 141 model = editor.model; 142 es = findStyle("hidden", model.edgeStyles); 143 if (es == null) { 144 es = new LineStyle("hidden", 1, Color.darkGray, LineStyle.STYLE_HIDDEN); 145 model.edgeStyles.add(es); 146 } 147 ps = findStyle("shadow", model.polyStyles); 148 if (ps == null) { 149 ps = new PolygonStyle(); 150 ps.setColor(new Color(50,50,50)); 151 ps.setFillStyle(PolygonStyle.FILL_COLOR); 152 ps.setId("shadow"); 153 ps.setVisibleInRadar(false); 154 ps.setDefaultEdgeStyle(es); 155 model.getPolyStyles().add(ps); 156 } 157 return ps; 158} 159 160doShadow(o) { 161 style = getShadowStyle(); 162 if (isWall(o)) addShadow(o, style); 163 else if (o instanceof Group) { 164 for (i:o.members) doShadow(i); 165 o.invalidate(); 166 } 167} 168 169shadow() { 170 if (editor.selectedObjects.isEmpty()) { 171 alert("First select a polygon"); 172 return; 173 } 174 for (o:editor.selectedObjects) doShadow(o); 175 editor.repaint(); 176} 177 178 179/************************ Round script ******************************/ 180 181/** 182 * Moves a point to the specified direction the specified length. 183 */ 184movePoint(p, dir, len) { 185 a = len * Math.pow(p.distanceSq(dir), -0.5); 186 p.translate((int)(a * (dir.x - p.x)), (int)(a * (dir.y - p.y))); 187} 188 189/** 190 * Rounds the corners of a wall polygon by adding one additional 191 * vertex to each corner and moving the original vertex. The max parameter 192 * controls the maximum amount of pixels a vertex gets moved. 193 */ 194doRound(mp, max) { 195 if (!isWall(mp)) return; 196 if (mp instanceof Group) { 197 for (o:mp.members) 198 doRound(o, max); 199 mp.invalidate(); 200 } 201 if (mp.getNumPoints() < 3) return; 202 minDist = 1 * 64; 203 maxMove = max * 64; 204 p2 = mp.getPoint(mp.getNumPoints() - 1); 205 p3 = mp.getPoint(0); 206 for (i = 1; i <= mp.getNumPoints(); i++) { 207 p1 = p2; 208 p2 = p3; 209 p3 = mp.getPoint(i % mp.getNumPoints()); 210 d12 = p1.distance(p2); 211 if (d12 <= minDist) continue; 212 d23 = p2.distance(p3); 213 if (d23 <= minDist) continue; 214 pn = new Point(p2); 215 movePoint(pn, p1, Math.min(maxMove, (int)(d12 / 2))); 216 movePoint(p2, p3, Math.min(maxMove, (int)(d23 / 2))); 217 mp.insertPoint(i - 1, pn); 218 mp.setPoint(i, p2.x, p2.y); 219 i++; 220 } 221} 222 223round() { 224 if (editor.selectedObjects.isEmpty()) { 225 alert("First select a polygon"); 226 return; 227 } 228 value = prompt("How many pixels to round"); 229 if (value == null) return; 230 max = Integer.parseInt(value); 231 value = prompt("How many times to run"); 232 if (value == null) return; 233 times = Integer.parseInt(value); 234 for (o:editor.selectedObjects) 235 for (i = 0; i < times; i++) 236 doRound(o, max); 237 editor.repaint(); 238} 239 240/*********************** Image to polygon ***************************/ 241 242imageToPoly(pixmap) { 243 model = editor.model; 244 poly = FindPolygon.findPolygon(pixmap.image); 245 if (poly == null) { 246 alert("Couldn't find a polygon in the image"); 247 return; 248 } 249 es = findStyle("hidden", model.edgeStyles); 250 if (es == null) { 251 es = new LineStyle("hidden", 1, Color.darkGray, LineStyle.STYLE_HIDDEN); 252 model.edgeStyles.add(es); 253 } 254 ps = findStyle(pixmap.fileName, model.polyStyles); 255 if (ps == null) { 256 ps = new PolygonStyle(); 257 ps.setId(pixmap.fileName); 258 ps.setColor(new Color(0,0,128)); 259 ps.setFillStyle(PolygonStyle.FILL_TEXTURED); 260 ps.setTexture(pixmap); 261 ps.setVisibleInRadar(true); 262 ps.setDefaultEdgeStyle(es); 263 model.getPolyStyles().add(ps); 264 } 265 p = center(); 266 poly.translate(p.x, p.y); 267 editor.addMapObject(new MapPolygon(poly, ps)); 268} 269 270imageToPoly() { 271 model = editor.model; 272 if (model.pixmaps.isEmpty()) { 273 alert("First add an image using the Image Manager"); 274 return; 275 } 276 map = new HashMap(); 277 for(p:editor.model.pixmaps) 278 map.put(p.fileName, p); 279 images = map.keySet().toArray(); 280 name = JOptionPane.showInputDialog(null, 281 "Select an image", "Question", 282 JOptionPane.INFORMATION_MESSAGE, null, 283 images, images[0]); 284 pixmap = map.get(name); 285 if (pixmap == null) return; 286 imageToPoly(pixmap); 287} 288 289/*********************** 3D effects ***************************/ 290 291import java.awt.image.*; 292 293/** 294 * A 2D vector object. 295 */ 296vec(x, y) { 297 add(v) { return vec(v.x + x, v.y + y); } 298 mul(a) { return vec(a*x, a*y); } 299 len() { return Math.sqrt(1.0*x*x + 1.0*y*y); } 300 unit() { return mul(1d/len()); } 301 normal() { return vec(-y, x); } 302 dot(v) { return x*v.x + y*v.y; } 303 eq(v) { return v.x == x && v.y == y; } 304 return this; 305} 306 307/** 308 * Gets the average color of the given polygon style. 309 */ 310getAverageColor(style) { 311 averageColor(img) { 312 bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); 313 bi.graphics.drawImage(img, 0, 0, 1, 1, Color.black, null); 314 return new Color(bi.getRGB(0, 0)); 315 } 316 switch(style.fillStyle) { 317 case PolygonStyle.FILL_COLOR: 318 if (!Color.black.equals(style.color)) 319 return style.color; 320 break; 321 case PolygonStyle.FILL_TEXTURED: 322 return averageColor(style.texture.image); 323 } 324 return Color.darkGray; 325} 326 327/** 328 * Returns an image that is a copy of the given image img 329 * with brightness adjusted by the given level. 330 * 331 * @param img the original image 332 * @param level a float that is used to multiply the color components 333 * of the original image 334 */ 335adjustBrightness(img, level) { 336 op = new ConvolveOp(new Kernel(1, 1, new float[] { level })); 337 dst = op.createCompatibleDestImage(img, 338 new DirectColorModel(24, 0xff0000, 0xff00, 0xff)); 339 op.filter(img, dst); 340 return dst; 341} 342 343 344getColoredStyle(level, name, orig) { 345 var id = level < -0.5 ? 0 : level < 0 ? 1 : level < 0.5 ? 2 : 3; 346 var sid = name + "-color" + id; 347 var ps = findStyle(sid, editor.model.polyStyles); 348 if (ps == null) { 349 var es = findStyle("hidden", editor.model.edgeStyles); 350 if (es == null) { 351 es = new LineStyle("hidden", 1, Color.darkGray, 352 LineStyle.STYLE_HIDDEN); 353 editor.model.edgeStyles.add(es); 354 } 355 var c = null; 356 switch(id) { 357 case 0: c = orig.brighter().brighter(); break; 358 case 1: c = orig.brighter(); break; 359 case 2: c = orig.darker(); break; 360 default: c = orig.darker().darker(); break; 361 } 362 ps = new PolygonStyle(); 363 ps.setId(sid); 364 ps.setColor(c); 365 ps.setFillStyle(PolygonStyle.FILL_COLOR); 366 ps.setVisibleInRadar(false); 367 ps.setDefaultEdgeStyle(es); 368 editor.model.polyStyles.add(ps); 369 } 370 return ps; 371} 372 373getTexturedStyle(level, orig) { 374 var id = level < -0.5 ? 0 : level < 0 ? 1 : level < 0.5 ? 2 : 3; 375 var sid = orig.id + "-light" + id; 376 var ps = findStyle(sid, editor.model.polyStyles); 377 if (ps == null) { 378 var es = findStyle("hidden", editor.model.edgeStyles); 379 if (es == null) { 380 es = new LineStyle("hidden", 1, Color.darkGray, 381 LineStyle.STYLE_HIDDEN); 382 editor.model.edgeStyles.add(es); 383 } 384 var c = orig.color; 385 if (c == null) c = Color.black; 386 switch(id) { 387 case 0: c = c.brighter().brighter(); break; 388 case 1: c = c.brighter(); break; 389 case 2: c = c.darker(); break; 390 default: c = c.darker().darker(); break; 391 } 392 var img = adjustBrightness(orig.texture.image, -level + 1.2); 393 var pixmap = new Pixmap(); 394 pixmap.image = img; 395 pixmap.fileName = "light" + id + "-" + orig.texture.fileName; 396 editor.model.pixmaps.add(pixmap); 397 ps = new PolygonStyle(); 398 ps.setId(sid); 399 ps.setColor(c); 400 ps.setTexture(pixmap); 401 ps.setFillStyle(PolygonStyle.FILL_TEXTURED); 402 ps.setVisibleInRadar(false); 403 ps.setDefaultEdgeStyle(es); 404 editor.model.polyStyles.add(ps); 405 } 406 return ps; 407} 408 409/** 410 * Returns equivalent polygon without edges with length 0. 411 */ 412filterPoints(poly) { 413 var pnew = new Polygon(); 414 int x = poly.xpoints[0]; 415 int y = poly.ypoints[0]; 416 pnew.addPoint(x, y); 417 for(int i = 1; i < poly.npoints; i++) { 418 int x2 = poly.xpoints[i]; 419 int y2 = poly.ypoints[i]; 420 if (x == x2 && y == y2) continue; 421 pnew.addPoint(x2, y2); 422 x = x2; y = y2; 423 } 424 return pnew; 425} 426 427/** 428 * Returns a group of polygons that makes the given map polygon look like 429 * it had 3rd dimension (depth). 430 * 431 * @param mp the map polygon 432 * @param d the depth in pixels 433 * @param view a vec that shows the view direction 434 * @param light a vec that shows the light direction 435 * @param tex if true then the added polygons are filled with textures 436 * otherwise they are filled with colors 437 * @param decor if true then the group is a Decoration otherwise it is a 438 * Group 439 */ 440makeDepthBorder(mp, d, view, light, tex, decor) { 441 var vu = view.unit(); 442 var lu = light.unit(); 443 int sgn = mp.isCounterClockwise() ? -1 : 1; 444 var dv = view.mul(-d); 445 var quads = new ArrayList(); 446 var p = filterPoints(mp.polygon); 447 int np = p.npoints; 448 if (np < 2) return; 449 if (mp.defaultStyle.fillStyle != PolygonStyle.FILL_TEXTURED) 450 tex = false; 451 var orig = tex ? mp.defaultStyle : getAverageColor(mp.defaultStyle); 452 var p1 = vec(p.xpoints[0], p.ypoints[0]); 453 for(int i = 0; i < np; i++) { 454 var p2 = vec(p.xpoints[(i + 1) % np], p.ypoints[(i + 1) % np]); 455 var n = p2.add(p1.mul(-1)).mul(sgn).normal().unit(); 456 if (n.dot(view) <= 0) { 457 var level = n.dot(lu); 458 var style = tex ? getTexturedStyle(level, orig) 459 : getColoredStyle(level, mp.defaultStyle.id, orig); 460 quads.add(new MapPolygon(new Polygon( 461 new int[] {p1.x, (int)(p1.x + dv.x), (int)(p2.x + dv.x), p2.x}, 462 new int[] {p1.y, (int)(p1.y + dv.y), (int)(p2.y + dv.y), p2.y}, 463 4), style)); 464 } 465 p1 = p2; 466 } 467 if (decor) return new Decoration(quads); 468 return new Group(quads); 469} 470 471makeBevelBorder(mp, d, light, tex) { 472 if (mp.defaultStyle.fillStyle != PolygonStyle.FILL_TEXTURED) 473 tex = false; 474 var lu = light.unit(); 475 int sgn = mp.isCounterClockwise() ? 1 : -1; 476 var quads = new ArrayList(); 477 var p = filterPoints(mp.polygon); 478 var np = p.npoints; 479 if (np < 3) return; 480 if (mp.defaultStyle.fillStyle != PolygonStyle.FILL_TEXTURED) 481 tex = false; 482 var orig = tex ? mp.defaultStyle : getAverageColor(mp.defaultStyle); 483 var p1 = vec(p.xpoints[0], p.ypoints[0]); 484 var p2 = vec(p.xpoints[1], p.ypoints[1]); 485 var p3 = vec(p.xpoints[2], p.ypoints[2]); 486 for(int i = 0; i < np; i++) { 487 var p4 = vec(p.xpoints[(i + 3) % np], p.ypoints[(i + 3) % np]); 488 var n12 = p2.add(p1.mul(-1)).mul(sgn).normal().unit(); 489 var n23 = p3.add(p2.mul(-1)).mul(sgn).normal().unit(); 490 var n34 = p4.add(p3.mul(-1)).mul(sgn).normal().unit(); 491 var newp2 = p2.add(n12.add(n23).unit().mul(d)); 492 var newp3 = p3.add(n23.add(n34).unit().mul(d)); 493 var level = -n23.dot(lu); 494 var style = tex ? getTexturedStyle(level, orig) 495 : getColoredStyle(level, mp.defaultStyle.id, orig); 496 quads.add(new MapPolygon(new Polygon( 497 new int[] {p2.x, p3.x, newp3.x, newp2.x}, 498 new int[] {p2.y, p3.y, newp3.y, newp2.y}, 499 4), style)); 500 p1 = p2; p2 = p3; p3 = p4; 501 } 502 return new Group(quads); 503} 504 505bevelBorder() { 506 if (editor.selectedObjects.isEmpty()) { 507 alert("First select a polygon"); 508 return; 509 } 510 var value = prompt("Enter border height in pixels", "10"); 511 if (value == null) return; 512 var height = Integer.parseInt(value) * 64; 513 for(o:editor.selectedObjects) { 514 if (isWall(o)) { 515 var bb = makeBevelBorder(o, height, vec(1,-1), true); 516 editor.addMapObject(bb); 517 } 518 } 519 editor.repaint(); 520} 521 522depthDecor() { 523 if (editor.selectedObjects.isEmpty()) { 524 alert("First select a polygon"); 525 return; 526 } 527 var value = prompt("Enter depth in pixels", "10"); 528 if (value == null) return; 529 var height = Integer.parseInt(value) * 64; 530 for(o:editor.selectedObjects) { 531 if (isWall(o)) { 532 var db = makeDepthBorder(o, height, 533 vec(-1,2), vec(1,-1), true, false); 534 editor.model.addToBack(db); 535 } 536 } 537 editor.repaint(); 538} 539 540 541 542/************************ User scripts ******************************/ 543 544 545loadUserScripts() { 546 String home = System.getProperty("user.home"); 547 f = new File(home + "/.jxpmaprc"); 548 if (!f.exists()) f = new File(home + "/jxpmaprc.bsh"); 549 if (f.exists()) source(f.getAbsolutePath()); 550} 551loadUserScripts(); 552