diff options
| author | iximeow <me@iximeow.net> | 2017-03-22 01:00:01 -0700 | 
|---|---|---|
| committer | iximeow <me@iximeow.net> | 2017-03-22 01:00:01 -0700 | 
| commit | fa3aa84daaa41debf876af2823604ae265bf4689 (patch) | |
| tree | f2a731b7c733ded5c5e7f78783ab458c667389d5 | |
| parent | b1376dff1a375c1371e175817c392f9cbce79377 (diff) | |
| -rw-r--r-- | cgi.scala | 189 | 
1 files changed, 189 insertions, 0 deletions
| diff --git a/cgi.scala b/cgi.scala new file mode 100644 index 0000000..11a7fd6 --- /dev/null +++ b/cgi.scala @@ -0,0 +1,189 @@ +import net.iximeow.raytrace._ +import Objects._ +import scala.util.Try +import java.time._ + +object cgi_main extends App { +  val params = System.getenv("QUERY_STRING") +  val parsedObjects = params.split("&").map(parse _) +  println("HTTP/1.1 200 OK\nContent-type: text/html\n\n") +  println("<html><head><style>.mono { font-family: monospace; }</style></head><body>") +  val content = if (!parsedObjects.exists(_.isInstanceOf[Succ])) { +    usage() +  } else { +    render(parsedObjects) +  } +  println(content) +  println("</body></html>") + +  def render(objects: Seq[ParseResult]) = { +    val imgPath = renderImage(objects) +    s"""<img src="/projects/raycast/${imgPath}"></img>""" +  } + +  def renderImage(objects: Seq[ParseResult]): String = { +    val sceneElems = objects.foldLeft(Seq.empty[Surface]) { +      case (elems: Seq[Surface], o: ParseResult) => o match { +        case Succ(Right(xs)) => elems ++ xs +        case _ => elems +      } +    } +    val rays = objects.foldLeft(Seq.empty[Ray]) { +      case (rays: Seq[Ray], o: ParseResult) => o match { +        case Succ(Left(xs)) => rays ++ xs +        case _ => rays +      } +    } +    val res = objects.foldLeft(Option[Res](null)) { +      case (r: Option[Res], o: ParseResult) => o match { +        case x: Res => Some(x) +        case _ => r +      } +    }.getOrElse(Res(800, 600)) +    val version = objects.foldLeft(Option[Version](null)) { +      case (v: Option[Version], o: ParseResult) => o match { +        case x: Version => Some(x) +        case _ => v +      } +    } +    val fuccs = objects.foldLeft(Seq.empty[String]) { +      case (fuc: Seq[String], o: ParseResult) => o match { +        case Fucc(err) => fuc :+ err +        case _ => fuc +      } +    } + +    val scene = Scene(sceneElems) + +    println("<p>Rendering a scene with " + sceneElems.size + " surfaces and " + rays.size + " rays</p>") + +    scene.render(scale = 20, color = 0xc0c0c0, normals = true) +    rays +      .flatMap(x => scene.cast(x, 63)) +      .foreach(_.renderTo(scene.buffer, 20, res.resX / 2, res.resY / 2)) + +    /* +    val destFile = DateTimeFormatter.ofPattern("YYYYMMdd-HHmmss") +      .format(LocalDateTime.now()) +      */ +     // because shitty vps doesn't have java 8 atm +    val destFile = System.currentTimeMillis + ".bmp" +    scene.save(destFile) +    destFile +  } + +  sealed trait ParseResult { } +  case class Succ(surface: Either[Seq[Ray], Seq[Surface]]) extends ParseResult +  case class Fucc(error: String) extends ParseResult +  case class Version(version: String) extends ParseResult +  case class Res(resX: Int, resY: Int) extends ParseResult + +  def parse(str: String): ParseResult = { +    val parts = str.split(":").toList +    parts match { +      case "Ray" :: rest => parseRay(rest) +      case "Light" :: rest => parseLight(rest) +      case "Segment" :: rest => parseSegment(rest) +      case "ParabolaMirror" :: rest => parseParabolicMirror(rest) +      case "SphericalMirror" :: rest => parseSphericalMirror(rest) +      case "ParabolicLens" :: rest => parseParabolicLens(rest) +      case "res" :: rest => parseRes(rest) +      case "version" :: rest => parseVersion(rest) +      case x => Fucc("Unkown param: " + x) +    } +  } + +  private def parseDoubles(params: Seq[String], count: Int, tpe: String): Either[Fucc, Seq[Double]] = { +    if (params.length != count) return Left(Fucc("Invalid parameters for " + tpe + ": " + params.mkString(":"))) +    val parsed = params.map(x => Try(java.lang.Double.parseDouble(x))) +    if (parsed.exists(_.isFailure)) return Left(Fucc( +      parsed.zip(params).map { case (res, src) => if (res.isFailure) { +        "<span class=red>x</span>" // maybe escape and print the invalid value here one day... +      } else { +        "_" +      }}.mkString(":") +    )) + +    val succ = parsed.flatMap(_.toOption) +    if (succ.length != count) return Left(Fucc("oops")) +    Right(succ) +  } + +  private def parseLight(params: Seq[String]): ParseResult = { +    parseDoubles(params, 6, "light") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Succ(Left(Scene.rays(parsed(4).toInt, parsed(5), Point(parsed(1), parsed(2)), Point(4 * Math.cos(parsed(0)), 4 * Math.sin(parsed(0)))))) +    } +  } + +  private def parseRay(params: Seq[String]): ParseResult = { +    parseDoubles(params, 4, "ray") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Succ(Left(Seq(Ray(parsed(0), parsed(1), Point(parsed(2), parsed(3)))))) +    } +  } + +  private def parseSegment(params: Seq[String]): ParseResult = { +    parseDoubles(params, 4, "segment") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Succ(Right(Seq(Segment(parsed(0), parsed(1), Point(parsed(2), parsed(3)))))) +    } +  } + +  private def parseParabolicMirror(params: Seq[String]) = +    parseDoubles(params, 7, "parabolic mirror") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Succ(Right(Scene.generateParabola(parsed(0), parsed(1), parsed(2), 0, parsed(3).toInt, Point(parsed(4), parsed(5)), parsed(6)))) +    } + +  private def parseSphericalMirror(params: Seq[String]) = +    parseDoubles(params, 7, "spherical mirror") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Succ(Right(Scene.generateMirror(parsed(0), parsed(1).toInt, parsed(2), Point(parsed(3), parsed(4)), parsed(5)))) +    } + +  private def parseParabolicLens(params: Seq[String]) = +    parseDoubles(params, 5, "parabolic lens") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Succ(Right(Seq(ParabolicLens(Point(parsed(0), parsed(1)), parsed(2), parsed(3), parsed(4))))) +    } + +  private def parseRes(params: Seq[String]) = +    parseDoubles(params, 2, "res") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Res(parsed(0).toInt, parsed(1).toInt) +    } + +  private def parseVersion(params: Seq[String]) = +    parseDoubles(params, 1, "version") match { +      case Left(fucc) => fucc +      case Right(parsed) => +        Version(parsed(0).toString) +    } + +  def usage() = { +    """|<h2>USAGE:</h2> +    |<p style="font-family:monospace">object:params&object2:params&object3:params&....</p> +    |<p>Version may be specified by beginning with <span class=mono>version:str</span>. By default, assumes the latest version of each object. Some object's parameters may change from version to version.</p> +    |<p>Additionally, you may specify desired resolutions by providing <span class=mono>res:x:y</span> where <span class=mono>x</span> and <span class=mono>y</span> are integers.</p> +    |<p>(by default, resolution is 800x600)</p> +    |</br> +    |<h3>OBJECTS:</h3> +    |<ul> +    |  <li><pre class=mono>Ray:dx:dy:xi:yi                                     </pre><p>Ray to trace.</p></li> +    |  <li><pre class=mono>Light:rotation:atx:aty:number:spacing               </pre><p>Bulk batch of light from some place.</p></li> +    |  <li><pre class=mono>Segment:dx:dy:xi:yi                                 </pre><p>Reflective surface. Careful of normals.</p></li> +    |  <li><pre class=mono>ParabolaMirror:a:b:w_i:segments:atx:aty:rotated   </pre><p>Parabola with coefficients like x(t) = a * t, y(t) = b * t^2, centered at <span class=mono>(atx, aty)</span>, rotated <span class=mono>rotated</span> radians with <span class=mono>segments</span> segments of approximation.</p></li> +    |  <li><pre class=mono>SphericalMirror:r:segments:arcSize:atx:aty:rotated  </pre><p>doc: TODO</p></li> +    |  <li><pre class=mono>ParabolicLens:atx:aty:rotation:radius:focal         </pre><p>doc: TODO. ALSO, BUGGY.</p></li> +    |</ul>""".stripMargin +  } +} + | 
