diff options
Diffstat (limited to 'cgi.scala')
-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 + } +} + |