summaryrefslogtreecommitdiff
path: root/cgi.scala
diff options
context:
space:
mode:
Diffstat (limited to 'cgi.scala')
-rw-r--r--cgi.scala189
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
+ }
+}
+