/*
 * Copyright © 2001-2008 Wouter van Oortmerssen
 * Copyright © 2008-2016 Jonathan Dowland <jon@dow.land>
 *
 * Distributed under the terms of the GNU GPL Version 2
 * See file LICENSE.txt
 */

package org.redmars.wadc;
import java.util.*;

import java.util.stream.Collectors;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;

class WadRun {
  WadParse wp;
  WadCPrefs prefs;
  // state variables

  int xp = 0;
  int yp = 0;
  int orient = 0;

  boolean pendown = true;
  Vertex lastvertex = null;
  Vertex beforelastvertex = null;
  Line lastline = null;

  ArrayDeque<Integer> sectorStack = new ArrayDeque<>();

  int curthingtype = 1;
  int thingflags = 7; // all skill levels

  int curlinetype = 0;
  int curlinetag = 0;
  int cursectortype = 0;
  int cursectortag = 0;
  boolean mergesectors = false;
  boolean prunelines = false;
  boolean undefx = false;
  int forcesec = -1;
  boolean hexen = false;
  boolean midtex = false;
  int curlinearg[] = new int[4];
  int curthingarg[] = new int[5];

  Hashtable gvars = new Hashtable();
  Vector objects = new Vector();
  Vector stacktrace = new Vector();

  int lightlevel = 160;
  int ceil = 128, floor = 0;

  String texceil = "RROCK10";
  String texfloor = "SLIME13";
  String textop = "BRICK7";
  String texbot = "BRICK7";
  String texmid = "BRICK7";
  String mapname = "MAP01";

  int xoff = 0, yoff = 0;
  int lineflags = 0;

  // generated data

  Vector<Vertex> vertices = new Vector<>();
  Vector<Line> lines = new Vector<>();
  Vector<Side> sides = new Vector<>();
  Vector<Sector> sectors = new Vector<>();
  Vector<Thing> things = new Vector<>();

  Vector<Integer> vcoord = new Vector<>();
  Vector<Vector<Vertex>> vlists = new Vector<>();

  AutoRule texrules = null;

  // merging and splitting

  Vector<Integer> xcoord = new Vector<>();
  Vector<Integer> ycoord = new Vector<>();
  Vector<Vector<Line>> xlists = new Vector<>();
  Vector<Vector<Line>> ylists = new Vector<>();

  // rendering

  int maxx = 0, maxy = 0, minx = 0, miny = 0;
  int xmid, ymid;
  float scale, basescale = 1.0f;
  boolean zoomed = false;
  Rectangle r;
  int gridsnap = 16;
  Vector xtraverts = new Vector();
  Sector errsec = null;

  LinkedHashSet<Vertex> collect = new LinkedHashSet<>();

  static Random rnd = new Random();

  WadRun(WadParse p) {
      wp = p;
      prefs = wp.mf.prefs;
  }

  void deprecated(String fn) {
      wp.mf.msg("WARNING: " +fn + " is deprecated and will be removed in a future release");
  }

  void addbuiltins() {

    builtin("rotright", 0, new Builtin() { Exp eval() {
      rotate(1);
      return n;
    }});

    builtin("rotleft", 0, new Builtin() { Exp eval() {
      rotate(-1);
      return n;
    }});

    builtin("up", 0, new Builtin() { Exp eval() {
      pendown = false;
      return n;
    }});

    builtin("down", 0, new Builtin() { Exp eval() {
      pendown = true;
      return n;
    }});

    builtin("step", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      step(a.ival(),b.ival());
      return n;
    }});

    builtin("curve", 4, new Builtin() { Exp eval(Exp a, Exp b, Exp c, Exp d) {
      int steps = c.ival();
      if(steps<1) steps = 1;
      if(steps>1000) steps = 1000;
      int fwd = a.ival();
      int side = b.ival();
      int cur = 0;
      int curside = 0;
      int xoffinc = -d.ival();
      boolean ux = undefx;
      undefx = false;
      for(int i = 1; i<=steps; i++) {
        double angle = ((3.14159/2)*i/(double)steps);
        double seg = (Math.sin(angle))*fwd;
        double segs = (1.0-Math.cos(angle))*side;
        if(i==steps) { seg = fwd; segs = side; }   // protect against float inacuracies
        int f = (int)seg-cur;
        int s = (int)segs-curside;
        step(f,s);
        xoff += ((int)Math.sqrt(f*f+s*s))*xoffinc;
        cur = (int)seg;
        curside = (int)segs;
      };
      undefx = ux;
      rotate((side>0)?1:-1);
      return n;
    }});

    builtin("arch", 6, new Builtin() { Exp eval(Exp a, Exp b, Exp c, Exp d, Exp e, Exp f) {
      int height = a.ival();
      int width = b.ival();
      int depth = c.ival();
      int steps = d.ival();
      int step = width/steps;
      floor = e.ival();
      lightlevel = f.ival();
      for(int i = 0; i<steps; i++) {
        int xtra = (int)(Math.sin(Math.acos(2*i/(double)steps-1.0))*width/2);
        step(step,0);
        step(0,depth);
        step(-step,0);
        step(0,-depth);
        step(step,0);
        makesector(true,-1,floor,floor+height+xtra,lightlevel);
        xoff += step;
      };
      return n;
    }});

    builtin("leftsector", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      makesector(false,-1,a.ival(),b.ival(),c.ival());
      return n;
    }});

    builtin("rightsector", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      makesector(true,-1,a.ival(),b.ival(),c.ival());
      return n;
    }});

    builtin("innerleftsector", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      makesector(false,sectorStack.peek(),a.ival(),b.ival(),c.ival());
      return n;
    }});

    builtin("innerrightsector", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      makesector(true,sectorStack.peek(),a.ival(),b.ival(),c.ival());
      return n;
    }});

    builtin("landscape", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      deprecated("landscape");
      landscape(a.ival(),b.ival(),c.ival());
      return n;
    }});

    builtin("marchingcubes", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      deprecated("marchingcubes");
      marchingcubes(a.ival(),b.ival(),c.ival());
      return n;
    }});

    builtin("simplex", 2, new Builtin() { Exp eval(Exp x, Exp y) {
      return new Int(simplex(x.ival(), y.ival()));
    }});

    builtin("mergesectors", 0, new Builtin() { Exp eval() {
      mergesectors = true;
      return n;
    }});

    builtin("prunelines", 0, new Builtin() { Exp eval() {
      prunelines = true;
      return n;
    }});

    builtin("thing", 0, new Builtin() { Exp eval() {
      makething((-orient+3)*90);
      return n;
    }});

    builtin("thingangle", 1, new Builtin() { Exp eval(Exp a) {
      makething(a.ival());
      return n;
    }});

    builtin("setthing", 1, new Builtin() { Exp eval(Exp a) {
      curthingtype = a.ival();
      return n;
    }});

    // thing flags
    builtin("setthingflags", 1, new Builtin() { Exp eval(Exp a) {
        thingflags = a.ival();
        return n;
    }});
    builtin("getthingflags", 0, new Builtin() { Exp eval() {
        return new Int(thingflags);
    }});

    builtin("linetype", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      curlinetype = a.ival();
      curlinetag = b.ival();
      for(int i = 0; i < curlinearg.length; ++i) {
          curlinearg[i] = 0;
      }
      return n;
    }});

    builtin("linetypehexen", 6, new Builtin() { Exp eval(Exp a, Exp b, Exp c, Exp d, Exp e, Exp f) {
      curlinetype = a.ival();
      curlinetag = b.ival();
      curlinearg[0] = c.ival();
      curlinearg[1] = d.ival();
      curlinearg[2] = e.ival();
      curlinearg[3] = f.ival();
      hexen = true;
      return n;
    }});

    // line flags
    builtin("setlineflags", 1, new Builtin() { Exp eval(Exp a) {
        lineflags = a.ival();
        return n;
    }});
    builtin("getlineflags", 0, new Builtin() { Exp eval() {
        return new Int(lineflags);
    }});

    builtin("setthinghexen", 6, new Builtin() { Exp eval(Exp a, Exp b, Exp c, Exp d, Exp e, Exp f) {
      curthingtype = a.ival();
      curthingarg[0] = b.ival();
      curthingarg[1] = c.ival();
      curthingarg[2] = d.ival();
      curthingarg[3] = e.ival();
      curthingarg[4] = f.ival();
      hexen = true;
      return n;
    }});

    builtin("hexenformat", 0, new Builtin() { Exp eval() {
        hexen = true;
        return n;
    }});

    builtin("is_hexenformat", 0, new Builtin() { Exp eval() {
      return new Int(hexen?1:0);
    }});

    builtin("sectortype", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      cursectortype = a.ival();
      cursectortag = b.ival();
      return n;
    }});

    builtin("floor", 1, new Builtin() { Exp eval(Exp s) {
      texfloor = s.sval();
      return n;
    }});

    builtin("ceil", 1, new Builtin() { Exp eval(Exp s) {
      texceil = s.sval();
      return n;
    }});

    builtin("top", 1, new Builtin() { Exp eval(Exp s) {
      textop = s.sval();
      return n;
    }});

    builtin("mid", 1, new Builtin() { Exp eval(Exp s) {
      texmid = s.sval();
      return n;
    }});

    builtin("bot", 1, new Builtin() { Exp eval(Exp s) {
      texbot = s.sval();
      return n;
    }});

    // accessors for the pen's current texture
    Arrays.stream( new String [][] {
        { "getfloor", texfloor },
        { "getceil",  texceil  },
        { "gettop",   textop   },
        { "getbot",   texbot   },
        { "getmid",   texmid   },
    }).forEach(p ->
        builtin(p[0], 0, new Builtin() { Exp eval() {
          return new Str(p[1]);
        }})
    );

    builtin("xoff", 1, new Builtin() { Exp eval(Exp s) {
      xoff = s.ival();
      undefx = false;
      return n;
    }});

    builtin("yoff", 1, new Builtin() { Exp eval(Exp s) {
      yoff = s.ival();
      return n;
    }});

    builtin("midtex", 0, new Builtin() { Exp eval() {
      midtex = !midtex;
      return n;
    }});

    builtin("unpegged", 0, new Builtin() { Exp eval() {
      if((lineflags&24)==0) { lineflags |= 24; } else { lineflags &= ~24; };
      return n;
    }});

    builtin("impassable", 0, new Builtin() { Exp eval() {
      if((lineflags&0x01)==0) { lineflags |= 0x01; } else { lineflags &= ~0x01; };
      return n;
    }});

    builtin("sin", 1, new Builtin() { Exp eval(Exp a) {
      double d = (a.ival()*3.14159)/1800.0;
      return new Int((int)(Math.sin(d)*1024.0));
    }});

    builtin("asin", 1, new Builtin() { Exp eval(Exp a) {
      double d = a.ival()/1024.0;
      return new Int((int)((Math.asin(d)*1800.0)/3.14159));
    }});

    builtin("add", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival()+b.ival());
    }});

    builtin("sub", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival()-b.ival());
    }});

    builtin("mul", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival()*b.ival());
    }});

    builtin("div", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      if(b.ival()==0) wp.error("division by zero");
      return new Int(a.ival()/b.ival());
    }});

    builtin("and", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival() & b.ival());
    }});

    builtin("not", 1, new Builtin() { Exp eval(Exp a) {
      return new Int(~ a.ival());
    }});

    builtin("or", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival() | b.ival());
    }});

    builtin("texture", 3, new Builtin() { Exp eval(Exp s, Exp w, Exp h) {
        wp.setTexture(s.sval(), w.ival(), h.ival());
        return n;
    }});

    builtin("addpatch", 3, new Builtin() { Exp eval(Exp s, Exp x, Exp y) {
        wp.addPatch(s.sval(), x.ival(), y.ival());
        return n;
    }});

    builtin("eq", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival()==b.ival()?1:0);
    }});

    builtin("lessthaneq", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Int(a.ival()<=b.ival()?1:0);
    }});

    builtin("print", 1, new Builtin() { Exp eval(Exp s) {
      wp.mf.msg(s.show());
      return n;
    }});

    builtin("cat", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      return new Str(a.sval() + b.sval());
    }});

    builtin("die", 1, new Builtin() { Exp eval(Exp s) {
      wp.mf.msg(s.show());
      wp.error("died");
      return n;
    }});

    builtin("lastfile", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("lastfile");
      prefs.put("basename", s.sval());
      return n;
    }});

    builtin("doomexe", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("doomexe");
      prefs.put("doomexe", s.sval());
      return n;
    }});

    builtin("doomargs", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("doomargs");
      prefs.put("doomargs", s.sval());
      return n;
    }});

    builtin("bspcmd", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("bspcmd");
      prefs.put("bspcmd", s.sval());
      return n;
    }});

    builtin("iwad", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("iwad");
      prefs.put("iwad", s.sval());
      return n;
    }});

    builtin("twad1", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("twad1");
      prefs.put("twad1", s.sval());
      return n;
    }});

    builtin("twad2", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("twad2");
      prefs.put("twad2", s.sval());
      return n;
    }});

    builtin("twad3", 1, new Builtin() { Exp eval(Exp s) {
      deprecated("twad3");
      prefs.put("twad3", s.sval());
      return n;
    }});

    builtin("togglevertices", 0, new Builtin() { Exp eval() {
      deprecated("togglevertices");
      prefs.putBoolean("renderverts", !prefs.getBoolean("renderverts"));
      return n;
    }});

    builtin("togglethings", 0, new Builtin() { Exp eval() {
      deprecated("togglethings");
      prefs.putBoolean("renderthings", !prefs.getBoolean("renderthings"));
      return n;
    }});

    builtin("autotex", 5, new Builtin() { Exp eval(Exp a, Exp b, Exp c, Exp d, Exp e) {
      String t[] = { "L", "U", "N" };
      if(!a.sval().equals("W")) { t = new String[1]; t[0] = a.sval(); };
      for(int i = 0; i<t.length; i++) {
        AutoRule ar = new AutoRule();
        ar.next = texrules;
        texrules = ar;
        ar.type = t[i];
        ar.h = b.ival();
        ar.w = c.ival();
        ar.f = d.ival();
        ar.tex = e.sval();
      };
      return n;
    }});

    builtin("set", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      gvars.put(a.sval(), b);
      return b;
    }});

    builtin("get", 1, new Builtin() { Exp eval(Exp a) {
      Exp e = (Exp)gvars.get(a.sval());
      if(e==null) wp.error("get: uninitialised variable: "+a.sval());
      return e;
    }});

    builtin("onew", 0, new Builtin() { Exp eval() {
      int n = objects.size();
      objects.addElement(new Hashtable());
      return new Int(n);
    }});

    builtin("oset", 3, new Builtin() { Exp eval(Exp a, Exp b, Exp c) {
      int i = a.ival();
      if(i<0 || i>=objects.size()) wp.error("oset: illegal object pointer");
      ((Hashtable)objects.elementAt(i)).put(b.sval(), c);
      return c;
    }});

    builtin("oget", 2, new Builtin() { Exp eval(Exp a, Exp b) {
      int i = a.ival();
      if(i<0 || i>=objects.size()) wp.error("oget: illegal object pointer");
      Exp e = (Exp)((Hashtable)objects.elementAt(i)).get(b.sval());
      if(e==null) wp.error("oget: uninitialised object field: "+b.sval());
      return e;
    }});

    builtin("undefx", 0, new Builtin() { Exp eval() {
      undefx = true;
      return n;
    }});

    builtin("popsector", 0, new Builtin() { Exp eval() {
      if(sectorStack.size() <= 1) {
          wp.error("error: can't pop the last sector");
      } else {
          sectorStack.pop();
      }
      return n;
    }});

    builtin("lastsector", 0, new Builtin() { Exp eval() {
      return new Int(sectorStack.peek());
    }});

    builtin("forcesector", 1, new Builtin() { Exp eval(Exp a) {
      forcesec = a.ival();
      return n;
    }});

    builtin("seed", 1, new Builtin() { Exp eval(Exp a) {
      Choice.setSeed(a.ival());
      wp.mf.msg("random seed set to " + Choice.seed);
      return n;
    }});

    builtin("newtag", 0, new Builtin() { Exp eval() {
      return new Int(wp.curtag++);
    }});

    builtin("mapname", 1, new Builtin() { Exp eval(Exp a) {
      mapname = a.sval();
      return n;
    }});
  }

  void builtin(String s, int nargs, Builtin b) {
    b.nargs = nargs;
    Fun f = new Fun(s);
    f.builtin = b;
    wp.funs.put(s,f);
  }

  void step(int f, int s) {
    switch(orient) {
      case 0: yp-=f; xp+=s; break;
      case 1: xp+=f; yp+=s; break;
      case 2: yp+=f; xp-=s; break;
      case 3: xp-=f; yp-=s; break;
    };
    makevertex();
    if(pendown) makeline();
  };

  void rotate(int sign) {
    orient = (orient+sign)&3;
  }

  void makevertex() {
    Vertex v = makevertex(xp,yp);
    beforelastvertex = lastvertex;
    lastvertex = v;
  }

  Vertex makevertex(int xp, int yp) {
    Vertex v = null;
    Vector vlist = coordlookup(xp,vcoord,vlists);
    if(vlist!=null) {
      for(int i = 0;i<vlist.size();i++) {
        v = (Vertex)vlist.elementAt(i);
        if(v.x==xp && v.y==yp) break;
        v = null;
      };
    };
    if(v==null) {
      v = new Vertex();
      v.x = xp;
      v.y = yp;
      vertices.addElement(v);
      addcoordlists(v,xp,vcoord,vlists);
    };
    return v;
  }

  void makeline(Vertex a, Vertex b) {
    beforelastvertex = a;
    lastvertex = b;
    makeline();
  };

  void makeline() {
    Vector<Line> v = lastvertex.v;
    for(int i = 0; i < v.size(); i++) {
      Line l = v.elementAt(i);
      if((l.from==lastvertex && l.to==beforelastvertex) ||
         (l.to==lastvertex && l.from==beforelastvertex)) {
        lastline = l;
        return;
      }
    }
    collect.clear();
    collect.add(beforelastvertex);

    if(beforelastvertex.x==lastvertex.x) {
      splitlines(beforelastvertex, lastvertex, true, lastvertex.x);
    } else if(beforelastvertex.y==lastvertex.y) {
      splitlines(beforelastvertex, lastvertex, false, lastvertex.y);
    } else {
      makeline_really(beforelastvertex, lastvertex);
    };

    // This fixes the case where a line is split by a second linedef
    // which has the opposite direction to the overdrawn one
    for(Line l : lastvertex.v) {
      Vertex other = lastvertex==l.from ? l.to : l.from;

      if(other == beforelastvertex) {
          lastline = l;
          return;
      }
    }
    // no exact line found above; use collect
    for(int i = 0; i < lastvertex.v.size(); i++) {
      Line l = lastvertex.v.elementAt(i);
      Vertex other = lastvertex==l.from ? l.to : l.from;
      for(Vertex o : collect) {
        if(other==o) {
          lastline = l;
          beforelastvertex = o;
          return;
        }
      }
    }
    wp.error("could not locate last line?");
  }

  void makeline_really(Vertex from, Vertex to) {
    Line l = makeline_minimal(from,to);
    lastline = l;
    l.t = textop;
    l.m = texmid;
    l.b = texbot;
    l.xoff = xoff;
    l.yoff = yoff;
    l.undefx = undefx;
    l.type = curlinetype;
    l.tag = curlinetag;
    l.flags |= lineflags;
    for(int i = 0; i < curlinearg.length; i++) l.specialargs[i] = curlinearg[i];
  };

  Line makeline_minimal(Vertex from, Vertex to) {
    if(from==to) {
      wp.error("line endpoints are identical?");
    };
    Line l = new Line(midtex);
    l.from = from;
    l.to = to;
    l.idx = lines.size();
    lines.addElement(l);
    from.insert(l);
    to.insert(l);
    if(from.x==to.x) addcoordlists(l,from.x,xcoord,xlists);
    if(from.y==to.y) addcoordlists(l,from.y,ycoord,ylists);
    return l;
  };

  <T> void addcoordlists(T o, int coord, Vector<Integer> coords, Vector<Vector<T>> lists) {
    int i = 0;
    for(;i<coords.size();i++) if(coords.elementAt(i)==coord) break;
    Vector<T> v;
    if(i<coords.size()) {
      v = lists.elementAt(i);
    } else {
      coords.addElement(coord);
      lists.addElement(v = new Vector<T>());
    };
    v.addElement(o);
  }

  <T> Vector<T> coordlookup(int coord, Vector<Integer> coords, Vector<Vector<T>> lists) {
    for(int i = 0; i<coords.size(); i++) {
      if(coords.elementAt(i) == coord) {
        return lists.elementAt(i);
      }
    }
    return null;
  }

  void splitlines(Vertex a, Vertex b, boolean eqx, int coord) {
    Vector<Line> v = coordlookup(coord, eqx ? xcoord : ycoord,
                                  eqx ? xlists : ylists);
    if(v!=null) for(int i = 0; i<v.size(); i++) {
      int c1 = eqx ? a.y : a.x;
      int c2 = eqx ? b.y : b.x;
      Line l = v.elementAt(i);
      int lc1 = eqx ? l.from.y : l.from.x;
      int lc2 = eqx ? l.to.y : l.to.x;
      Vertex ca = a;
      Vertex cb = b;
      Vertex lca = l.from;
      Vertex lcb = l.to;
      if(c1>c2) { int temp = c2; c2 = c1; c1 = temp; ca = b; cb = a; };
      if(lc1>lc2) { int temp = lc2; lc2 = lc1; lc1 = temp; lca = l.to; lcb = l.from; };
      if(c2<=lc1 || c1>=lc2) continue;       // no overlap
      collect.add(lca);
      collect.add(lcb);
      collect.add(ca);
      collect.add(cb);
      v.removeElementAt(i);
      if(c1<lc1) {    // overlap on the left
        splitlines((ca==a ? ca : lca), (ca==a ? lca : ca), eqx, coord);
      };
      if(c2>lc2) {    // overlap on the right
        splitlines((cb==b ? cb : lcb), (cb==b ? lcb : cb), eqx, coord);
      };
      v.addElement(l);
      if(c1>lc1) {   // overlap starts within
        l = splitatvertex(l, lca==l.from, eqx ? coord : c1, eqx ? c1 : coord);
      };
      if(c2<lc2) {   // overlap ends within
        splitatvertex(l, lcb==l.to, eqx ? coord : c2, eqx ? c2 : coord);
      };
      return;
    };
    makeline_really(a,b);
  }

  Line splitatvertex(Line l, boolean fromto, int x, int y) {
    Vertex mid = makevertex(x, y);
    l.to.remove(l);
    Line sec = makeline_minimal(mid,l.to);
    sec.copyattrs(l,sides);
    l.to = mid;
    mid.insert(l);
    return fromto ? sec : l;
  };

  void makething(int angle) {
    Thing t = new Thing();
    t.x = xp;
    t.y = yp;
    t.type = curthingtype;
    t.opt = thingflags;
    t.idx = things.size();
    t.angle = angle;

    for(int i = 0; i < curthingarg.length; i++) t.specialargs[i] = curthingarg[i];
    things.addElement(t);
  }

  Sector newsector() {
    if(forcesec>=0 && forcesec<sectors.size()) return (Sector)sectors.elementAt(forcesec);
    if(mergesectors) {
      for(int i = 0;i<sectors.size();i++) {
        Sector s = (Sector)sectors.elementAt(i);
        if(s.ceil==ceil &&
           s.floor==floor &&
           s.light==lightlevel &&
           s.tag==cursectortag &&
           s.type==cursectortype &&
           s.ctex.compareTo(texceil)==0 &&
           s.ftex.compareTo(texfloor)==0) return s;
      };
    };
    return new Sector(texceil,texfloor,ceil,floor,lightlevel,sectors,cursectortype,cursectortag);
  }

  void makesector(boolean rightside, int lastsec, int flr, int cl, int ll) {
    //wp.mf.msg("START SECTOR");
    floor = flr;
    ceil = cl;
    lightlevel = ll;
    Sector sec = newsector();
    forcesec = -1;
    sectorStack.push(sec.idx);
    Vertex v = beforelastvertex;
    Line l = lastline;
    main: for(;;) {
      boolean right = true;
      if(v==l.from) {
        if(!rightside) right = false;
      } else {
        if(rightside) right = false;
      };
      right = !right;
      if((right?l.right:l.left)!=null) {
         errsec = sec; wp.error("sidedef already assigned to a sector");
      };
      Side other = right?l.left:l.right;
      if(other!=null && other.s==sec && !prunelines) {
        errsec = sec; wp.error("both sides assigned to the same sector");
      };
      Side ns = new Side(l,sides);
      if(right) { l.right = ns; } else { l.left = ns; };
      ns.s = sec;
      sec.boundlen += l.width();
      if(lastsec>=0) {
        Sector inside = (Sector)sectors.elementAt(lastsec);
        if(other!=null) {
          errsec = inside; wp.error("cannot make inner sector: sidedef already assigned");
        };
        Side nis = new Side(l,sides);
        if(right) { l.left = nis; } else { l.right = nis; };
        nis.s = inside;
      };
      if(v==lastvertex) return;
      //for(int i = 0;i<v.v.size();i++) { Line m = (Line)v.v.elementAt(i); wp.mf.msg("line: "+v.angle(m)); };
      for(int i = 0;i<v.v.size();i++) {
        Line m = v.v.elementAt(i);
        if(m==l) {
          //wp.mf.msg("pick: "+i);
          if(rightside) { i--; } else { i++; };
          if(i<0) i = v.v.size()-1;
          if(i>=v.v.size()) i = 0;
          m = v.v.elementAt(i);
          if(m==l) { errsec = sec; wp.error("trying to make sector on unconnected line"); };
          l = m;
          v = l.from==v?l.to:l.from;
          continue main;
        };
      };
      errsec = sec;
      wp.error("make sector: no line found?");
    }
  }

  void pan(int fromx, int fromy, int tox, int toy) {
     xmid = (int)((fromx-tox)*scale)+xmid;
     ymid = (int)((fromy-toy)*scale)+ymid;
  }

  void zoom(int clickx, int clicky, float factor) {
     xmid = (int)((clickx-(r.width/2))*scale)+xmid;
     ymid = (int)((clicky-(r.height/2))*scale)+ymid;
     scale *= factor;
     basescale *= factor;
  }

  void addstep(int clickx, int clicky, int type) {
    if(wp.editchanged==2) {
      wp.mf.msg("Code changed, please re-run before drawing next vertex!");
      return;
    };
    int x = (int)((clickx-(r.width/2))*scale)+xmid;
    int y = (int)((clicky-(r.height/2))*scale)+ymid;
    int gs = gridsnap/2;
    x = x+(x<0 ? -gs : gs);
    y = y+(y<0 ? -gs : gs);
    Vertex v = new Vertex();
    v.x = x/gridsnap*gridsnap;
    v.y = y/gridsnap*gridsnap;
    Vertex last = xtraverts.size()>0 ? (Vertex)xtraverts.lastElement() : lastvertex;
    if(last.x==v.x && last.y==v.y) return;
    xtraverts.addElement(v);
    int dx = v.x-last.x;
    int dy = v.y-last.y;
    switch(orient) {
      case 0: x=-dy; y=dx;  break;
      case 1: x=dx;  y=dy;  break;
      case 2: x=dy;  y=-dx; break;
      case 3: x=-dx; y=-dy; break;
    };
    String s;
    if(type=='C') {
      s = "  curve("+x+","+y+",10,1)\n";
      rotate(y<0 ? -1 : 1);
    } else if(type=='J') {
      s = "  movestep("+x+","+y+")\n";
    } else if(y==0 && x>=0) {
      s = "  straight("+x+")\n";
    } else if(y==0 && x<0) {
      s = "  turnaround\n  straight("+(-x)+")\n";
      rotate(2);
    } else if(x==0 && y>0) {
      s = "  right("+y+")\n";
      rotate(1);
    } else if(x==0 && y<0) {
      s = "  left("+(-y)+")\n";
      rotate(-1);
    } else if(x==y) {
      s = "  eright("+x+")\n";
      rotate(1);
    } else if(x==-y) {
      s = "  eleft("+x+")\n";
      rotate(-1);
    } else {
      s = "  step("+x+","+y+")\n";
    }
    wp.editchanged = 1;
    wp.mf.insert(s,wp.editinsertpos);
    wp.editinsertpos += s.length();
  }

  void crosshair(Graphics g, boolean show) {
  }

  void renderxtraverts(Graphics g) {
    int x = lastvertex.x;
    int y = lastvertex.y;
    int gxmid = r.width/2;
    int gymid = r.height/2;
    g.setColor(Color.yellow);
    for(int i = 0; i<xtraverts.size(); i++) {
      Vertex v = (Vertex)xtraverts.elementAt(i);
      g.drawLine((int)((x-xmid)/scale)+gxmid,
                 (int)((y-ymid)/scale)+gymid,
                 (int)((v.x-xmid)/scale)+gxmid,
                 (int)((v.y-ymid)/scale)+gymid);
      x = v.x;
      y = v.y;
    };
  }
  private static int scale(short x, short min, short max)
  {
      int denominator = max - min;
      if(0 != denominator) {
        return ((255 * (x - min)) / denominator);
      }
      return 127; // default to mid-range
  }

  /*
   * orderByPath(vertices, lines): returns a re-ordered (possibly reduced) list
   * of vertices that follow a path based on lines, which are treated as
   * bi-directional
   * converted from the following Haskell
   *
   *  sortme vs ss = (head vs) : sortme' (head vs) (tail vs) (ss ++ (map swap ss))
   *  sortme' v [] _  = []
   *  sortme' v vs ss | vs == newvs = []
   *                  | otherwise = newv : (sortme' newv newvs ss) where
   *      newv  = snd $ head $ filter (\(x,y) -> x == v) ss
   *      newvs = filter ((/=) newv) vs
   */
  List<Vertex> orderByPath(Set<Vertex> set_v, List<Line> ls)
  {
    List<Vertex> vs = new ArrayList<>();
    vs.addAll(set_v);

    // Lines have direction, but we need bi-directional for this purpose.
    // Build a new list, appending reversed lines to the end.
    ArrayList<Line> ls2 = new ArrayList<>(ls);

    for(Line l : ls) {
        Line nl = new Line();
        nl.from = l.to;
        nl.to = l.from;
        ls2.add(nl);
    }

    Vertex head = vs.remove(0);
    List<Vertex> ret = nextNode(head, vs, ls2);
    ret.add(head);
    return ret;
  }

  // internal recursive function for the above
  List<Vertex> nextNode(Vertex v, List<Vertex> vs, List<Line> ls)
  {
    Optional<Vertex> o = ls.stream()
                        .filter(l -> l.from == v)
                        .map(l -> l.to)
                        .filter(v_ -> vs.contains(v_))
                        .findFirst();

    // this can happen if vs is empty (normal situation at the end of recursion)
    // or the input sector was disjoint (e.g. mergesectors)
    if(!o.isPresent())
    {
        return new ArrayList<Vertex>();
    }
    Vertex newv = o.get();

    vs.remove(newv);
    List<Vertex> ret = nextNode(newv, vs, ls);

    ret.add(newv);
    return ret;
  }

  void experimentalFillSector(Graphics g, int xmid, int ymid, int gxmid, int gymid)
  {
      // collect sector height/etc information
      short min_val = 0, max_val = 0, comp = 0;

      if(SectorFill.CEILINGHEIGHT == prefs.getEnum("fillsectors") || SectorFill.FLOORHEIGHT == prefs.getEnum("fillsectors"))
      {
          for(Sector sector : sectors)
          {
              if(SectorFill.FLOORHEIGHT == prefs.getEnum("fillsectors")) {
                  comp = (short)sector.floor;
              }
              else if(SectorFill.CEILINGHEIGHT == prefs.getEnum("fillsectors")) {
                  comp = (short)sector.ceil;
              }

              if(comp < min_val) {
                  min_val = comp;
              }
              if(comp > max_val) {
                  max_val = comp;
              }
          }
      }

      // we need to get a list of vertices that are related to each sector,
      // which we (sadly) can't get from the Sectors themselves

      for(Sector sector : sectors) {
          // We don't want duplicate vertexes in this set. LinkedHashSet doesn't
          // require the Vertex class to implement interface Comparable, but
          // still offers insertion-order preservation.
          //
          // XXX: we need to subdivide sectors into concave (and properly
          // connected) polygons or the draw will be corrupted.
          Set<Vertex> av = new LinkedHashSet<Vertex>();

          // sidedefs are the bridge between sectors and vertexes
          for(Side side : sides) {
              if(side.s == sector) {
                  av.add(side.l.from);
                  av.add(side.l.to);
              }
          }

          List<Line> ls = sides.stream()
                         .filter(side -> side.s == sector)
                         .map(side -> side.l)
                         .collect(Collectors.toList());

          int [] xPoints = new int[av.size()];
          int [] yPoints = new int[av.size()];
          int    nPoints = av.size();

          int i = 0;
          for(Vertex v : orderByPath(av, ls)) {
              xPoints[i] = (int)((v.x-xmid)/scale) + gxmid;
              yPoints[i] = (int)((v.y-ymid)/scale) + gymid;
              i++;
          }

          int c;
          short v;
          switch(prefs.getEnum("fillsectors"))
          {
              case CEILINGHEIGHT:
                  v = (short)sector.ceil;
                  c = scale(v, min_val, max_val);
                  break;

              case FLOORHEIGHT:
                  v = (short)sector.floor;
                  c = scale(v, min_val, max_val);
                  break;

              case LIGHTLEVEL:
                  v = (short)sector.light;
                  c = scale(v, (short)0, (short)255);
                  break;

              default: // should be unreachable
                  c = 127;
                  break;
          }
          g.setColor(new Color(c,c,c));

          g.fillPolygon(xPoints, yPoints, nPoints);
      }
  }

  int thingsize(int n) {
    switch(n) {
      case    1: return  16;
      case    2: return  16;
      case    3: return  16;
      case    4: return  16;
      case   11: return  16;
      case 3004: return  20;
      case   84: return  20;
      case    9: return  20;
      case   65: return  20;
      case 3001: return  20;
      case 3002: return  30;
      case   58: return  30;
      case 3006: return  16;
      case 3005: return  31;
      case   69: return  24;
      case 3003: return  24;
      case   68: return  64;
      case   71: return  31;
      case   66: return  20;
      case   67: return  48;
      case   64: return  20;
      case    7: return 128;
      case   16: return  40;
      case   88: return  16;
      case   72: return  16;
      case 2035: return  10;
      case   54: return  32;
      default:   return  20;
    }
  }

  void render(Graphics g) {
    r = g.getClip().getBounds();
    for(int i = 0;i<vertices.size();i++) {
      Vertex v = vertices.elementAt(i);
      if(v.x>maxx) maxx = v.x;
      if(v.x<minx) minx = v.x;
      if(v.y>maxy) maxy = v.y;
      if(v.y<miny) miny = v.y;
    };
    if(!zoomed) {
      xmid = (maxx+minx)/2;
      ymid = (maxy+miny)/2;
      scale = (maxx-minx)/(float)r.width;
      float yscale = (maxy-miny)/(float)r.height;
      if(yscale>scale) scale = yscale;
      scale *= 1.05;
      zoomed = true;
    };
    int gxmid = r.width/2;
    int gymid = r.height/2;
    int grmaxx = (maxx+128)&0xFFFFFC0;
    int grminx = -((-minx+128)&0xFFFFFC0);
    int grmaxy = (maxy+128)&0xFFFFFC0;
    int grminy = -((-miny+128)&0xFFFFFC0);

    g.setColor(new Color(50,50,50));
    for(int x = grminx;x<=grmaxx;x+=64) {
      g.drawLine((int)((x-xmid)/scale)+gxmid,
                 (int)((grminy-ymid)/scale)+gymid,
                 (int)((x-xmid)/scale)+gxmid,
                 (int)((grmaxy-ymid)/scale)+gymid);
      for(int y = grminy;y<=grmaxy;y+=64)
        g.drawLine((int)((grminx-xmid)/scale)+gxmid,
                   (int)((y-ymid)/scale)+gymid,
                   (int)((grmaxx-xmid)/scale)+gxmid,
                   (int)((y-ymid)/scale)+gymid);
    };

    if(prefs.getEnum("fillsectors") != SectorFill.NONE) {
        experimentalFillSector(g,xmid,ymid,gxmid,gymid);
    }

    for(Line l : lines) {
      if(l.right!=null) {
        g.setColor(l.left!=null?Color.gray:Color.white);
      } else {
        g.setColor(l.left!=null?Color.white:Color.green);
      };
      if(errsec!=null)
        if((l.right!=null && l.right.s==errsec) ||
           (l.left!=null && l.left.s==errsec)) g.setColor(Color.red);
      if(l.type!=0) g.setColor(Color.blue);
      if(l==lastline) { g.setColor(Color.magenta); };
      g.drawLine((int)((l.from.x-xmid)/scale)+gxmid,
                 (int)((l.from.y-ymid)/scale)+gymid,
                 (int)((l.to.x-xmid)/scale)+gxmid,
                 (int)((l.to.y-ymid)/scale)+gymid);
    };
    if(prefs.getBoolean("renderverts")) for(Vertex v : vertices) {
      int d = 2;
      g.setColor(Color.green);
      if(v==lastvertex) { d=5; g.setColor(Color.magenta); };
      int x = (int)((v.x-xmid)/scale)+gxmid;
      int y = (int)((v.y-ymid)/scale)+gymid;
      g.drawLine(x-d,y-d,x+d,y+d);
      g.drawLine(x+d,y-d,x-d,y+d);
    };
    if(prefs.getBoolean("renderthings")) for(Thing t : things) {
      g.setColor(Color.blue);
      int rad = thingsize(t.type);
      int x1 = (int)((t.x-rad-xmid)/scale)+gxmid;
      int y1 = (int)((t.y-rad-ymid)/scale)+gymid;
      int x2 = (int)((t.x+rad-xmid)/scale)+gxmid;
      int y2 = (int)((t.y+rad-ymid)/scale)+gymid;
      g.drawOval(x1,y1,x2-x1,y2-y1);
    };
    renderxtraverts(g);
  }

  void run() throws Error {
      Choice.setSeed((int)System.currentTimeMillis());
      wp.mf.msg("random seed set to " + Choice.seed);
      makevertex();
      call(new Id("main"));
      for(int i = 0; i<vertices.size(); i++) {
        Vertex v = vertices.elementAt(i);
        if(v.v.size()==0) {
          vertices.remove(v);
          i--;
        } else {
          v.idx = i;
        };
      };
      wp.mf.msg(vertices.size()+" vertices, "+lines.size()+" lines, "+sectors.size()+" sectors, "+
        things.size()+" things.");
  }

  void varerr(String s) { wp.error("variable "+s+" never set"); }

  Exp call(Id caller) {
    //System.out.println(caller.show());
    stacktrace.addElement(caller.show());
    Fun f = (Fun)wp.funs.get(caller.s);
    Vector v = caller.v;
    int nargs = 0;
    if(v!=null) nargs = v.size();
    //mf.msg("debug: "+caller.s+"/"+nargs+" "+f);
    if(f==null) wp.error("undefined identifier: "+caller.s);
    Builtin b = f.builtin;
    Exp r = null;
    if(b!=null) {
      if(nargs!=b.nargs) wp.error("wrong number of arguments for builtin: "+caller.s);
      switch(nargs) {
        case 0: r = b.eval(); break;
        case 1: r = b.eval(((Exp)v.elementAt(0)).eval(this)); break;
        case 2: r = b.eval(((Exp)v.elementAt(0)).eval(this),
                           ((Exp)v.elementAt(1)).eval(this)); break;
        case 3: r = b.eval(((Exp)v.elementAt(0)).eval(this),
                           ((Exp)v.elementAt(1)).eval(this),
                           ((Exp)v.elementAt(2)).eval(this)); break;
        case 4: r = b.eval(((Exp)v.elementAt(0)).eval(this),
                           ((Exp)v.elementAt(1)).eval(this),
                           ((Exp)v.elementAt(2)).eval(this),
                           ((Exp)v.elementAt(3)).eval(this)); break;
        case 5: r = b.eval(((Exp)v.elementAt(0)).eval(this),
                           ((Exp)v.elementAt(1)).eval(this),
                           ((Exp)v.elementAt(2)).eval(this),
                           ((Exp)v.elementAt(3)).eval(this),
                           ((Exp)v.elementAt(4)).eval(this)); break;
        case 6: r = b.eval(((Exp)v.elementAt(0)).eval(this),
                           ((Exp)v.elementAt(1)).eval(this),
                           ((Exp)v.elementAt(2)).eval(this),
                           ((Exp)v.elementAt(3)).eval(this),
                           ((Exp)v.elementAt(4)).eval(this),
                           ((Exp)v.elementAt(5)).eval(this)); break;
        default: wp.error("oops");
      };
    } else {
      if(nargs!=f.args.size()) wp.error("wrong number of arguments for macro: "+caller.s);
      if(v!=null) for(int i = 0; i<v.size(); i++) {
        if(((String)f.args.get(i)).charAt(0)=='_') v.set(i, ((Exp)v.elementAt(i)).eval(this));
      };
      Exp e = f.body;
      //if(nargs>0)
      e = e.replace(f.args,v);
      r = e.eval(this);
    };
    stacktrace.setSize(stacktrace.size()-1);
    return r;
  }

  int rnd(int n) { return Math.abs(rnd.nextInt())%n; }

  float dist(Vertex a, Vertex b) {
    int dx = Math.abs(a.x-b.x);
    int dy = Math.abs(a.y-b.y);
    return (float)Math.sqrt(dx*dx+dy*dy);
  }

  void landscape(int width, int height, int density) {
    int xstart = xp;
    int ystart = yp;
    for(int i = 0; i<density; i++) makevertex(xstart+rnd(width),ystart+rnd(height));
    for(int i = 0; i<vertices.size(); i++) {
      Vertex v = vertices.elementAt(i);
      if(v.v.size()>=3) continue;
      Vertex closest = null;
      Vertex nextclosest = null;
      Vertex thirdclosest = null;
      float distclose = 999999;
      float distnextclose = 999999;
      float distthirdclose = 999999;
      for(int j = 0; j<vertices.size(); j++) {
        Vertex t = vertices.elementAt(j);
        if(t==v) continue;
        float dist = dist(v,t);
        if(dist<distclose) {
          thirdclosest = nextclosest;
          distthirdclose = distnextclose;
          nextclosest = closest;
          distnextclose = distclose;
          closest = t;
          distclose = dist;
        } else if(dist<distnextclose) {
          thirdclosest = nextclosest;
          distthirdclose = distnextclose;
          nextclosest = t;
          distnextclose = dist;
        } else if(dist<distthirdclose) {
          thirdclosest = t;
          distthirdclose = dist;
        };
      };
      xp = v.x;
      yp = v.y;
      makeline(closest,v);
      if(v.v.size()==3) continue;
      makeline(v,nextclosest);
      if(v.v.size()==3) continue;
      makeline(v,thirdclosest);
    };
  }

  void marchingcubes(int scale, int pscale, int __density) {
    int gridx = scale;
    int gridy = scale;
    int numlevels = 3;
    int gridsize = 64;
    int step = gridsize/2;
    int grid[][] = new int[gridx][gridy];
    int seed = rnd(10000);
    for(int x = 0; x<gridx; x++) for(int y = 0; y<gridy; y++) {
      grid[x][y] = (x==0 || y==0 || x==gridx-1 || y==gridy-1) ? 0 : prnd(numlevels,x,y,seed,pscale)+1;
    };
    for(int x = 0; x<gridx-1; x++) for(int y = 0; y<gridy-1; y++) {
      // A-ab-B
      // |    |
      // ac   bd
      // |    |
      // C-cd-D
      int a = grid[x][y];
      int b = grid[x+1][y];
      int c = grid[x][y+1];
      int d = grid[x+1][y+1];
      int xp = x*gridsize;
      int yp = y*gridsize;
      Vertex ab = makevertex(xp+step,yp);
      Vertex ac = makevertex(xp,yp+step);
      Vertex bd = makevertex(xp+gridsize,yp+step);
      Vertex cd = makevertex(xp+step,yp+gridsize);
      Vertex mi = makevertex(xp+step,yp+step);
      if(a==b && c==d && a!=c) {      // two sides
        makeline(ac,bd);
      } else if(a==c && b==d && a!=b) {
        makeline(ab,cd);

      } else if(a==b && a==c && a==d) {     // empty

      } else if(a==b && a==c && a!=d) {    // 1 corner different
        makeline(cd,bd);
      } else if(a==b && a==d && b!=c) {
        makeline(ac,cd);
      } else if(d==c && d==b && d!=a) {
        makeline(ac,ab);
      } else if(a==c && d==c && c!=b) {
        makeline(ab,bd);

      } else if(a==d && b==c && a!=b) {    // diagonal
        if(rnd(2)==0) {
          makeline(ab,ac);
          makeline(bd,cd);
        } else {
          makeline(ab,bd);
          makeline(ac,cd);
        };

      } else if(a==b && a!=c && a!=d && c!=d) {    // 2 corners different
        makeline(ac,mi);
        makeline(mi,bd);
        makeline(mi,cd);
      } else if(a==c && a!=b && a!=d && b!=d) {
        makeline(ab,mi);
        makeline(mi,cd);
        makeline(mi,bd);
      } else if(b==d && b!=a && b!=c && a!=c) {
        makeline(ab,mi);
        makeline(mi,cd);
        makeline(mi,ac);
      } else if(c==d && c!=a && b!=c && a!=b) {
        makeline(ac,mi);
        makeline(mi,bd);
        makeline(mi,ab);

      } else /*if(a!=b && d!=c && a!=c && d!=b && a!=d && c!=b) */{
        makeline(ab,ac);
        makeline(ac,cd);
        makeline(cd,bd);
        makeline(bd,ab);

      };
    };
    int farx = gridx*gridsize-gridsize-step;
    int fary = gridy*gridsize-gridsize-step;
    for(int i = 2; i<gridx*2-4; i++) tryline(i*step,step,(i+1)*step,step);
    for(int i = 2; i<gridx*2-4; i++) tryline(farx,i*step,farx,(i+1)*step);
    for(int i = gridx*2-4; i>2; i--) tryline(i*step,fary,(i-1)*step,fary);
    for(int i = gridx*2-4; i>2; i--) tryline(step,i*step,step,(i-1)*step);

    for(int i = 0; i<gridx*gridy; i++) {
      Line l = lines.elementAt(rnd(lines.size()));
      int fbound = (gridx-2)*gridsize;
      int nbound = 1*gridsize;
      if(l.from.x<=nbound || l.from.x>=fbound) continue;
      if(l.from.y<=nbound || l.from.y>=fbound) continue;
      if(l.to.x<=nbound   || l.to.x>=fbound) continue;
      if(l.to.y<=nbound   || l.to.y>=fbound) continue;
      makeline(l.from,l.to);
      if(l.left!=null && l.right==null) {
        rndsector(false);
      } else if(l.left==null && l.right!=null) {
        rndsector(true);
      };
    };
    
  }

  void tryline(int x1, int y1, int x2, int y2) {
    //if(rnd(6)==0) return;
    makeline(makevertex(x1,y1),makevertex(x2,y2));
    if(lastline.left==null && lastline.right==null) {
      rndsector(true);
    };
  }

  void rndsector(boolean right) {
    int floor = rnd(4)*8;
    int ceilrnd = rnd(3);
    texceil = (ceilrnd==2) ? "F_SKY1" : "FLAT5_3";
    int ceil = ceilrnd*32+128;
    int light = ceil-floor; // 96 to 192
    makesector(right,-1,floor,ceil,light);
  }

  int prnd(int max, int x, int y, int seed, float scale) {
    return (int)(Perlin.perlinnoise_2D(x/scale+seed,y/scale+seed,1000,0.01f)*3.5f+100.0f);
  }

  int simplex(int x, int y) {
    double simple = SimplexNoise.noise(x,y); // -1 - 1
    return
      (int)(
      (simple + 1) // 0-2
      * 500_000  // 0-1,000,000
    );
  }
}


