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 } }