From fa3aa84daaa41debf876af2823604ae265bf4689 Mon Sep 17 00:00:00 2001 From: iximeow Date: Wed, 22 Mar 2017 01:00:01 -0700 Subject: add fcgi-compatible main --- cgi.scala | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 cgi.scala 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("") + val content = if (!parsedObjects.exists(_.isInstanceOf[Succ])) { + usage() + } else { + render(parsedObjects) + } + println(content) + println("") + + def render(objects: Seq[ParseResult]) = { + val imgPath = renderImage(objects) + s"""""" + } + + 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("

Rendering a scene with " + sceneElems.size + " surfaces and " + rays.size + " rays

") + + 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) { + "x" // 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() = { + """|

USAGE:

+ |

object:params&object2:params&object3:params&....

+ |

Version may be specified by beginning with version:str. By default, assumes the latest version of each object. Some object's parameters may change from version to version.

+ |

Additionally, you may specify desired resolutions by providing res:x:y where x and y are integers.

+ |

(by default, resolution is 800x600)

+ |
+ |

OBJECTS:

+ |""".stripMargin + } +} + -- cgit v1.1