package org.sunflow.core.parser; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import org.codehaus.janino.ClassBodyEvaluator; import org.codehaus.janino.CompileException; import org.codehaus.janino.Parser.ParseException; import org.codehaus.janino.Scanner; import org.codehaus.janino.Scanner.ScanException; import org.sunflow.SunflowAPI; import org.sunflow.core.PrimitiveList; import org.sunflow.core.SceneParser; import org.sunflow.core.Shader; import org.sunflow.core.Tesselatable; import org.sunflow.core.camera.FisheyeLens; import org.sunflow.core.camera.PinholeLens; import org.sunflow.core.camera.SphericalLens; import org.sunflow.core.camera.ThinLens; import org.sunflow.core.light.DirectionalSpotlight; import org.sunflow.core.light.ImageBasedLight; import org.sunflow.core.light.PointLight; import org.sunflow.core.light.SphereLight; import org.sunflow.core.light.SunSkyLight; import org.sunflow.core.light.TriangleMeshLight; import org.sunflow.core.modifiers.BumpMappingModifier; import org.sunflow.core.modifiers.NormalMapModifier; import org.sunflow.core.primitive.Background; import org.sunflow.core.primitive.BanchoffSurface; import org.sunflow.core.primitive.CornellBox; import org.sunflow.core.primitive.Hair; import org.sunflow.core.primitive.JuliaFractal; import org.sunflow.core.primitive.ParticleSurface; import org.sunflow.core.primitive.Plane; import org.sunflow.core.primitive.Sphere; import org.sunflow.core.primitive.Torus; import org.sunflow.core.primitive.TriangleMesh; import org.sunflow.core.shader.AmbientOcclusionShader; import org.sunflow.core.shader.AnisotropicWardShader; import org.sunflow.core.shader.ConstantShader; import org.sunflow.core.shader.DiffuseShader; import org.sunflow.core.shader.GlassShader; import org.sunflow.core.shader.IDShader; import org.sunflow.core.shader.MirrorShader; import org.sunflow.core.shader.PhongShader; import org.sunflow.core.shader.ShinyDiffuseShader; import org.sunflow.core.shader.TexturedAmbientOcclusionShader; import org.sunflow.core.shader.TexturedDiffuseShader; import org.sunflow.core.shader.TexturedPhongShader; import org.sunflow.core.shader.TexturedShinyDiffuseShader; import org.sunflow.core.shader.TexturedWardShader; import org.sunflow.core.shader.UberShader; import org.sunflow.core.shader.ViewCausticsShader; import org.sunflow.core.shader.ViewGlobalPhotonsShader; import org.sunflow.core.shader.ViewIrradianceShader; import org.sunflow.core.tesselatable.BezierMesh; import org.sunflow.core.tesselatable.FileMesh; import org.sunflow.core.tesselatable.Gumbo; import org.sunflow.core.tesselatable.Teapot; import org.sunflow.image.Color; import org.sunflow.math.Matrix4; import org.sunflow.math.Point3; import org.sunflow.math.Vector3; import org.sunflow.system.Parser; import org.sunflow.system.Parser.ParserException; import org.sunflow.system.Timer; import org.sunflow.system.UI; import org.sunflow.system.UI.Module; /** * This class provides a static method for loading files in the Sunflow scene * file format. */ public class SCParser implements SceneParser { private Parser p; private int numLightSamples; public SCParser() { } @Override public boolean parse(String filename, SunflowAPI api) { try { parse(new FileReader(filename), filename, api); } catch (FileNotFoundException e) { UI.printError(Module.API, "%s", e.getMessage()); return false; } return true; } public boolean parseFromString(String input, SunflowAPI api) { // convert String into InputStream InputStream is = new ByteArrayInputStream(input.getBytes()); // read it with BufferedReader BufferedReader br = new BufferedReader(new InputStreamReader(is)); parse(br, ".", api); return true; } public boolean parse(Reader reader, String filename, SunflowAPI api) { String localDir = new File(filename).getAbsoluteFile().getParentFile().getAbsolutePath(); numLightSamples = 1; Timer timer = new Timer(); timer.start(); UI.printInfo(Module.API, "Parsing \"%s\" ...", filename); try { p = new Parser(reader); while (true) { String token = p.getNextToken(); if (token == null) break; if (token.equals("image")) { UI.printInfo(Module.API, "Reading image settings ..."); parseImageBlock(api); } else if (token.equals("background")) { UI.printInfo(Module.API, "Reading background ..."); parseBackgroundBlock(api); } else if (token.equals("accel")) { UI.printInfo(Module.API, "Reading accelerator type ..."); p.getNextToken(); UI.printWarning(Module.API, "Setting accelerator type is not recommended - ignoring"); } else if (token.equals("filter")) { UI.printInfo(Module.API, "Reading image filter type ..."); parseFilter(api); } else if (token.equals("bucket")) { UI.printInfo(Module.API, "Reading bucket settings ..."); api.parameter("bucket.size", p.getNextInt()); api.parameter("bucket.order", p.getNextToken()); api.options(SunflowAPI.DEFAULT_OPTIONS); } else if (token.equals("photons")) { UI.printInfo(Module.API, "Reading photon settings ..."); parsePhotonBlock(api); } else if (token.equals("gi")) { UI.printInfo(Module.API, "Reading global illumination settings ..."); parseGIBlock(api); } else if (token.equals("lightserver")) { UI.printInfo(Module.API, "Reading light server settings ..."); parseLightserverBlock(api); } else if (token.equals("trace-depths")) { UI.printInfo(Module.API, "Reading trace depths ..."); parseTraceBlock(api); } else if (token.equals("camera")) { parseCamera(api); } else if (token.equals("shader")) { if (!parseShader(api)) return false; } else if (token.equals("modifier")) { if (!parseModifier(api)) return false; } else if (token.equals("override")) { api.shaderOverride(p.getNextToken(), p.getNextBoolean()); } else if (token.equals("object")) { parseObjectBlock(api); } else if (token.equals("instance")) { parseInstanceBlock(api); } else if (token.equals("light")) { parseLightBlock(api); } else if (token.equals("texturepath")) { String path = p.getNextToken(); if (!new File(path).isAbsolute()) path = localDir + File.separator + path; api.addTextureSearchPath(path); } else if (token.equals("includepath")) { String path = p.getNextToken(); if (!new File(path).isAbsolute()) path = localDir + File.separator + path; api.addIncludeSearchPath(path); } else if (token.equals("include")) { String file = p.getNextToken(); UI.printInfo(Module.API, "Including: \"%s\" ...", file); api.parse(file); } else UI.printWarning(Module.API, "Unrecognized token %s", token); } p.close(); } catch (ParserException e) { UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); return false; } catch (FileNotFoundException e) { UI.printError(Module.API, "%s", e.getMessage()); return false; } catch (IOException e) { UI.printError(Module.API, "%s", e.getMessage()); return false; } timer.end(); UI.printInfo(Module.API, "Done parsing."); UI.printInfo(Module.API, "Parsing time: %s", timer.toString()); return true; } private void parseImageBlock(SunflowAPI api) throws IOException, ParserException { p.checkNextToken("{"); if (p.peekNextToken("resolution")) { api.parameter("resolutionX", p.getNextInt()); api.parameter("resolutionY", p.getNextInt()); } if (p.peekNextToken("aa")) { api.parameter("aa.min", p.getNextInt()); api.parameter("aa.max", p.getNextInt()); } if (p.peekNextToken("samples")) api.parameter("aa.samples", p.getNextInt()); if (p.peekNextToken("contrast")) api.parameter("aa.contrast", p.getNextFloat()); if (p.peekNextToken("filter")) api.parameter("filter", p.getNextToken()); if (p.peekNextToken("jitter")) api.parameter("aa.jitter", p.getNextBoolean()); if (p.peekNextToken("show-aa")) { UI.printWarning(Module.API, "Deprecated: show-aa ignored"); p.getNextBoolean(); } if (p.peekNextToken("output")) { UI.printWarning(Module.API, "Deprecated: output statement ignored"); p.getNextToken(); } api.options(SunflowAPI.DEFAULT_OPTIONS); p.checkNextToken("}"); } private void parseBackgroundBlock(SunflowAPI api) throws IOException, ParserException { p.checkNextToken("{"); p.checkNextToken("color"); api.parameter("color", parseColor()); api.shader("background.shader", new ConstantShader()); api.geometry("background", new Background()); api.parameter("shaders", "background.shader"); api.instance("background.instance", "background"); p.checkNextToken("}"); } private void parseFilter(SunflowAPI api) throws IOException, ParserException { UI.printWarning(Module.API, "Deprecated keyword \"filter\" - set this option in the image block"); String name = p.getNextToken(); api.parameter("filter", name); api.options(SunflowAPI.DEFAULT_OPTIONS); boolean hasSizeParams = name.equals("box") || name.equals("gaussian") || name.equals("blackman-harris") || name.equals("sinc") || name.equals("triangle"); if (hasSizeParams) { p.getNextFloat(); p.getNextFloat(); } } private void parsePhotonBlock(SunflowAPI api) throws ParserException, IOException { int numEmit = 0; boolean globalEmit = false; p.checkNextToken("{"); if (p.peekNextToken("emit")) { UI.printWarning(Module.API, "Shared photon emit values are deprectated - specify number of photons to emit per map"); numEmit = p.getNextInt(); globalEmit = true; } if (p.peekNextToken("global")) { UI.printWarning(Module.API, "Global photon map setting belonds inside the gi block - ignoring"); if (!globalEmit) p.getNextInt(); p.getNextToken(); p.getNextInt(); p.getNextFloat(); } p.checkNextToken("caustics"); if (!globalEmit) numEmit = p.getNextInt(); api.parameter("caustics.emit", numEmit); api.parameter("caustics", p.getNextToken()); api.parameter("caustics.gather", p.getNextInt()); api.parameter("caustics.radius", p.getNextFloat()); api.options(SunflowAPI.DEFAULT_OPTIONS); p.checkNextToken("}"); } private void parseGIBlock(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); p.checkNextToken("type"); if (p.peekNextToken("irr-cache")) { api.parameter("gi.engine", "irr-cache"); p.checkNextToken("samples"); api.parameter("gi.irr-cache.samples", p.getNextInt()); p.checkNextToken("tolerance"); api.parameter("gi.irr-cache.tolerance", p.getNextFloat()); p.checkNextToken("spacing"); api.parameter("gi.irr-cache.min_spacing", p.getNextFloat()); api.parameter("gi.irr-cache.max_spacing", p.getNextFloat()); // parse global photon map info if (p.peekNextToken("global")) { api.parameter("gi.irr-cache.gmap.emit", p.getNextInt()); api.parameter("gi.irr-cache.gmap", p.getNextToken()); api.parameter("gi.irr-cache.gmap.gather", p.getNextInt()); api.parameter("gi.irr-cache.gmap.radius", p.getNextFloat()); } } else if (p.peekNextToken("path")) { api.parameter("gi.engine", "path"); p.checkNextToken("samples"); api.parameter("gi.path.samples", p.getNextInt()); if (p.peekNextToken("bounces")) { UI.printWarning(Module.API, "Deprecated setting: bounces - use diffuse trace depth instead"); p.getNextInt(); } } else if (p.peekNextToken("fake")) { api.parameter("gi.engine", "fake"); p.checkNextToken("up"); api.parameter("gi.fake.up", parseVector()); p.checkNextToken("sky"); api.parameter("gi.fake.sky", parseColor()); p.checkNextToken("ground"); api.parameter("gi.fake.ground", parseColor()); } else if (p.peekNextToken("igi")) { api.parameter("gi.engine", "igi"); p.checkNextToken("samples"); api.parameter("gi.igi.samples", p.getNextInt()); p.checkNextToken("sets"); api.parameter("gi.igi.sets", p.getNextInt()); if (!p.peekNextToken("b")) p.checkNextToken("c"); api.parameter("gi.igi.c", p.getNextFloat()); p.checkNextToken("bias-samples"); api.parameter("gi.igi.bias_samples", p.getNextInt()); } else if (p.peekNextToken("ambocc")) { api.parameter("gi.engine", "ambocc"); p.checkNextToken("bright"); api.parameter("gi.ambocc.bright", parseColor()); p.checkNextToken("dark"); api.parameter("gi.ambocc.dark", parseColor()); p.checkNextToken("samples"); api.parameter("gi.ambocc.samples", p.getNextInt()); if (p.peekNextToken("maxdist")) api.parameter("gi.ambocc.maxdist", p.getNextFloat()); } else if (p.peekNextToken("none") || p.peekNextToken("null")) { // disable GI api.parameter("gi.engine", "none"); } else UI.printWarning(Module.API, "Unrecognized gi engine type \"%s\" - ignoring", p.getNextToken()); api.options(SunflowAPI.DEFAULT_OPTIONS); p.checkNextToken("}"); } private void parseLightserverBlock(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); if (p.peekNextToken("shadows")) { UI.printWarning(Module.API, "Deprecated: shadows setting ignored"); p.getNextBoolean(); } if (p.peekNextToken("direct-samples")) { UI.printWarning(Module.API, "Deprecated: use samples keyword in area light definitions"); numLightSamples = p.getNextInt(); } if (p.peekNextToken("glossy-samples")) { UI.printWarning(Module.API, "Deprecated: use samples keyword in glossy shader definitions"); p.getNextInt(); } if (p.peekNextToken("max-depth")) { UI.printWarning(Module.API, "Deprecated: max-depth setting - use trace-depths block instead"); int d = p.getNextInt(); api.parameter("depths.diffuse", 1); api.parameter("depths.reflection", d - 1); api.parameter("depths.refraction", 0); api.options(SunflowAPI.DEFAULT_OPTIONS); } if (p.peekNextToken("global")) { UI.printWarning(Module.API, "Deprecated: global settings ignored - use photons block instead"); p.getNextBoolean(); p.getNextInt(); p.getNextInt(); p.getNextInt(); p.getNextFloat(); } if (p.peekNextToken("caustics")) { UI.printWarning(Module.API, "Deprecated: caustics settings ignored - use photons block instead"); p.getNextBoolean(); p.getNextInt(); p.getNextFloat(); p.getNextInt(); p.getNextFloat(); } if (p.peekNextToken("irr-cache")) { UI.printWarning(Module.API, "Deprecated: irradiance cache settings ignored - use gi block instead"); p.getNextInt(); p.getNextFloat(); p.getNextFloat(); p.getNextFloat(); } p.checkNextToken("}"); } private void parseTraceBlock(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); if (p.peekNextToken("diff")) api.parameter("depths.diffuse", p.getNextInt()); if (p.peekNextToken("refl")) api.parameter("depths.reflection", p.getNextInt()); if (p.peekNextToken("refr")) api.parameter("depths.refraction", p.getNextInt()); p.checkNextToken("}"); api.options(SunflowAPI.DEFAULT_OPTIONS); } private void parseCamera(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); p.checkNextToken("type"); String type = p.getNextToken(); UI.printInfo(Module.API, "Reading %s camera ...", type); parseCameraTransform(api); String name = api.getUniqueName("camera"); if (type.equals("pinhole")) { p.checkNextToken("fov"); api.parameter("fov", p.getNextFloat()); p.checkNextToken("aspect"); api.parameter("aspect", p.getNextFloat()); api.camera(name, new PinholeLens()); } else if (type.equals("thinlens")) { p.checkNextToken("fov"); api.parameter("fov", p.getNextFloat()); p.checkNextToken("aspect"); api.parameter("aspect", p.getNextFloat()); p.checkNextToken("fdist"); api.parameter("focus.distance", p.getNextFloat()); p.checkNextToken("lensr"); api.parameter("lens.radius", p.getNextFloat()); if (p.peekNextToken("sides")) api.parameter("lens.sides", p.getNextInt()); if (p.peekNextToken("rotation")) api.parameter("lens.rotation", p.getNextFloat()); api.camera(name, new ThinLens()); } else if (type.equals("spherical")) { // no extra arguments api.camera(name, new SphericalLens()); } else if (type.equals("fisheye")) { // no extra arguments api.camera(name, new FisheyeLens()); } else { UI.printWarning(Module.API, "Unrecognized camera type: %s", p.getNextToken()); p.checkNextToken("}"); return; } p.checkNextToken("}"); if (name != null) { api.parameter("camera", name); api.options(SunflowAPI.DEFAULT_OPTIONS); } } private void parseCameraTransform(SunflowAPI api) throws ParserException, IOException { if (p.peekNextToken("steps")) { // motion blur camera int n = p.getNextInt(); api.parameter("transform.steps", n); for (int i = 0; i < n; i++) parseCameraMatrix(i, api); } else parseCameraMatrix(-1, api); } private void parseCameraMatrix(int index, SunflowAPI api) throws IOException, ParserException { String offset = index < 0 ? "" : String.format("[%d]", index); if (p.peekNextToken("transform")) { // advanced camera api.parameter(String.format("transform%s", offset), parseMatrix()); } else { if (index >= 0) p.checkNextToken("{"); // regular camera specification p.checkNextToken("eye"); api.parameter(String.format("eye%s", offset), parsePoint()); p.checkNextToken("target"); api.parameter(String.format("target%s", offset), parsePoint()); p.checkNextToken("up"); api.parameter(String.format("up%s", offset), parseVector()); if (index >= 0) p.checkNextToken("}"); } } private boolean parseShader(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); p.checkNextToken("name"); String name = p.getNextToken(); UI.printInfo(Module.API, "Reading shader: %s ...", name); p.checkNextToken("type"); if (p.peekNextToken("diffuse")) { if (p.peekNextToken("diff")) { api.parameter("diffuse", parseColor()); api.shader(name, new DiffuseShader()); } else if (p.peekNextToken("texture")) { api.parameter("texture", p.getNextToken()); api.shader(name, new TexturedDiffuseShader()); } else UI.printWarning(Module.API, "Unrecognized option in diffuse shader block: %s", p.getNextToken()); } else if (p.peekNextToken("phong")) { String tex = null; if (p.peekNextToken("texture")) api.parameter("texture", tex = p.getNextToken()); else { p.checkNextToken("diff"); api.parameter("diffuse", parseColor()); } p.checkNextToken("spec"); api.parameter("specular", parseColor()); api.parameter("power", p.getNextFloat()); if (p.peekNextToken("samples")) api.parameter("samples", p.getNextInt()); if (tex != null) api.shader(name, new TexturedPhongShader()); else api.shader(name, new PhongShader()); } else if (p.peekNextToken("amb-occ") || p.peekNextToken("amb-occ2")) { String tex = null; if (p.peekNextToken("diff") || p.peekNextToken("bright")) api.parameter("bright", parseColor()); else if (p.peekNextToken("texture")) api.parameter("texture", tex = p.getNextToken()); if (p.peekNextToken("dark")) { api.parameter("dark", parseColor()); p.checkNextToken("samples"); api.parameter("samples", p.getNextInt()); p.checkNextToken("dist"); api.parameter("maxdist", p.getNextFloat()); } if (tex == null) api.shader(name, new AmbientOcclusionShader()); else api.shader(name, new TexturedAmbientOcclusionShader()); } else if (p.peekNextToken("mirror")) { p.checkNextToken("refl"); api.parameter("color", parseColor()); api.shader(name, new MirrorShader()); } else if (p.peekNextToken("glass")) { p.checkNextToken("eta"); api.parameter("eta", p.getNextFloat()); p.checkNextToken("color"); api.parameter("color", parseColor()); if (p.peekNextToken("absorbtion.distance")) api.parameter("absorbtion.distance", p.getNextFloat()); if (p.peekNextToken("absorbtion.color")) api.parameter("absorbtion.color", parseColor()); api.shader(name, new GlassShader()); } else if (p.peekNextToken("shiny")) { String tex = null; if (p.peekNextToken("texture")) api.parameter("texture", tex = p.getNextToken()); else { p.checkNextToken("diff"); api.parameter("diffuse", parseColor()); } p.checkNextToken("refl"); api.parameter("shiny", p.getNextFloat()); if (tex == null) api.shader(name, new ShinyDiffuseShader()); else api.shader(name, new TexturedShinyDiffuseShader()); } else if (p.peekNextToken("ward")) { String tex = null; if (p.peekNextToken("texture")) api.parameter("texture", tex = p.getNextToken()); else { p.checkNextToken("diff"); api.parameter("diffuse", parseColor()); } p.checkNextToken("spec"); api.parameter("specular", parseColor()); p.checkNextToken("rough"); api.parameter("roughnessX", p.getNextFloat()); api.parameter("roughnessY", p.getNextFloat()); if (p.peekNextToken("samples")) api.parameter("samples", p.getNextInt()); if (tex != null) api.shader(name, new TexturedWardShader()); else api.shader(name, new AnisotropicWardShader()); } else if (p.peekNextToken("view-caustics")) { api.shader(name, new ViewCausticsShader()); } else if (p.peekNextToken("view-irradiance")) { api.shader(name, new ViewIrradianceShader()); } else if (p.peekNextToken("view-global")) { api.shader(name, new ViewGlobalPhotonsShader()); } else if (p.peekNextToken("constant")) { // backwards compatibility -- peek only p.peekNextToken("color"); api.parameter("color", parseColor()); api.shader(name, new ConstantShader()); } else if (p.peekNextToken("janino")) { String code = p.getNextCodeBlock(); try { Shader shader = (Shader) ClassBodyEvaluator.createFastClassBodyEvaluator(new Scanner(null, new StringReader(code)), Shader.class, ClassLoader.getSystemClassLoader()); api.shader(name, shader); } catch (CompileException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); return false; } catch (ParseException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); return false; } catch (ScanException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); return false; } catch (IOException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); return false; } } else if (p.peekNextToken("id")) { api.shader(name, new IDShader()); } else if (p.peekNextToken("uber")) { if (p.peekNextToken("diff")) api.parameter("diffuse", parseColor()); if (p.peekNextToken("diff.texture")) api.parameter("diffuse.texture", p.getNextToken()); if (p.peekNextToken("diff.blend")) api.parameter("diffuse.blend", p.getNextFloat()); if (p.peekNextToken("refl") || p.peekNextToken("spec")) api.parameter("specular", parseColor()); if (p.peekNextToken("texture")) { // deprecated UI.printWarning(Module.API, "Deprecated uber shader parameter \"texture\" - please use \"diffuse.texture\" and \"diffuse.blend\" instead"); api.parameter("diffuse.texture", p.getNextToken()); api.parameter("diffuse.blend", p.getNextFloat()); } if (p.peekNextToken("spec.texture")) api.parameter("specular.texture", p.getNextToken()); if (p.peekNextToken("spec.blend")) api.parameter("specular.blend", p.getNextFloat()); if (p.peekNextToken("glossy")) api.parameter("glossyness", p.getNextFloat()); if (p.peekNextToken("samples")) api.parameter("samples", p.getNextInt()); api.shader(name, new UberShader()); } else UI.printWarning(Module.API, "Unrecognized shader type: %s", p.getNextToken()); p.checkNextToken("}"); return true; } private boolean parseModifier(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); p.checkNextToken("name"); String name = p.getNextToken(); UI.printInfo(Module.API, "Reading shader: %s ...", name); p.checkNextToken("type"); if (p.peekNextToken("bump")) { p.checkNextToken("texture"); api.parameter("texture", p.getNextToken()); p.checkNextToken("scale"); api.parameter("scale", p.getNextFloat()); api.modifier(name, new BumpMappingModifier()); } else if (p.peekNextToken("normalmap")) { p.checkNextToken("texture"); api.parameter("texture", p.getNextToken()); api.modifier(name, new NormalMapModifier()); } else { UI.printWarning(Module.API, "Unrecognized modifier type: %s", p.getNextToken()); } p.checkNextToken("}"); return true; } private void parseObjectBlock(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); boolean noInstance = false; Matrix4 transform = null; String name = null; String[] shaders = null; String[] modifiers = null; if (p.peekNextToken("noinstance")) { // this indicates that the geometry is to be created, but not // instanced into the scene noInstance = true; } else { // these are the parameters to be passed to the instance if (p.peekNextToken("shaders")) { int n = p.getNextInt(); shaders = new String[n]; for (int i = 0; i < n; i++) shaders[i] = p.getNextToken(); } else { p.checkNextToken("shader"); shaders = new String[] { p.getNextToken() }; } if (p.peekNextToken("modifiers")) { int n = p.getNextInt(); modifiers = new String[n]; for (int i = 0; i < n; i++) modifiers[i] = p.getNextToken(); } else if (p.peekNextToken("modifier")) modifiers = new String[] { p.getNextToken() }; if (p.peekNextToken("transform")) transform = parseMatrix(); } if (p.peekNextToken("accel")) api.parameter("accel", p.getNextToken()); p.checkNextToken("type"); String type = p.getNextToken(); if (p.peekNextToken("name")) name = p.getNextToken(); else name = api.getUniqueName(type); if (type.equals("mesh")) { UI.printWarning(Module.API, "Deprecated object type: mesh"); UI.printInfo(Module.API, "Reading mesh: %s ...", name); int numVertices = p.getNextInt(); int numTriangles = p.getNextInt(); float[] points = new float[numVertices * 3]; float[] normals = new float[numVertices * 3]; float[] uvs = new float[numVertices * 2]; for (int i = 0; i < numVertices; i++) { p.checkNextToken("v"); points[3 * i + 0] = p.getNextFloat(); points[3 * i + 1] = p.getNextFloat(); points[3 * i + 2] = p.getNextFloat(); normals[3 * i + 0] = p.getNextFloat(); normals[3 * i + 1] = p.getNextFloat(); normals[3 * i + 2] = p.getNextFloat(); uvs[2 * i + 0] = p.getNextFloat(); uvs[2 * i + 1] = p.getNextFloat(); } int[] triangles = new int[numTriangles * 3]; for (int i = 0; i < numTriangles; i++) { p.checkNextToken("t"); triangles[i * 3 + 0] = p.getNextInt(); triangles[i * 3 + 1] = p.getNextInt(); triangles[i * 3 + 2] = p.getNextInt(); } // create geometry api.parameter("triangles", triangles); api.parameter("points", "point", "vertex", points); api.parameter("normals", "vector", "vertex", normals); api.parameter("uvs", "texcoord", "vertex", uvs); api.geometry(name, new TriangleMesh()); } else if (type.equals("flat-mesh")) { UI.printWarning(Module.API, "Deprecated object type: flat-mesh"); UI.printInfo(Module.API, "Reading flat mesh: %s ...", name); int numVertices = p.getNextInt(); int numTriangles = p.getNextInt(); float[] points = new float[numVertices * 3]; float[] uvs = new float[numVertices * 2]; for (int i = 0; i < numVertices; i++) { p.checkNextToken("v"); points[3 * i + 0] = p.getNextFloat(); points[3 * i + 1] = p.getNextFloat(); points[3 * i + 2] = p.getNextFloat(); p.getNextFloat(); p.getNextFloat(); p.getNextFloat(); uvs[2 * i + 0] = p.getNextFloat(); uvs[2 * i + 1] = p.getNextFloat(); } int[] triangles = new int[numTriangles * 3]; for (int i = 0; i < numTriangles; i++) { p.checkNextToken("t"); triangles[i * 3 + 0] = p.getNextInt(); triangles[i * 3 + 1] = p.getNextInt(); triangles[i * 3 + 2] = p.getNextInt(); } // create geometry api.parameter("triangles", triangles); api.parameter("points", "point", "vertex", points); api.parameter("uvs", "texcoord", "vertex", uvs); api.geometry(name, new TriangleMesh()); } else if (type.equals("sphere")) { UI.printInfo(Module.API, "Reading sphere ..."); api.geometry(name, new Sphere()); if (transform == null && !noInstance) { // legacy method of specifying transformation for spheres p.checkNextToken("c"); float x = p.getNextFloat(); float y = p.getNextFloat(); float z = p.getNextFloat(); p.checkNextToken("r"); float radius = p.getNextFloat(); api.parameter("transform", Matrix4.translation(x, y, z).multiply(Matrix4.scale(radius))); api.parameter("shaders", shaders); if (modifiers != null) api.parameter("modifiers", modifiers); api.instance(name + ".instance", name); noInstance = true; // disable future auto-instancing because // instance has already been created } } else if (type.equals("banchoff")) { UI.printInfo(Module.API, "Reading banchoff ..."); api.geometry(name, new BanchoffSurface()); } else if (type.equals("torus")) { UI.printInfo(Module.API, "Reading torus ..."); p.checkNextToken("r"); api.parameter("radiusInner", p.getNextFloat()); api.parameter("radiusOuter", p.getNextFloat()); api.geometry(name, new Torus()); } else if (type.equals("plane")) { UI.printInfo(Module.API, "Reading plane ..."); p.checkNextToken("p"); api.parameter("center", parsePoint()); if (p.peekNextToken("n")) { api.parameter("normal", parseVector()); } else { p.checkNextToken("p"); api.parameter("point1", parsePoint()); p.checkNextToken("p"); api.parameter("point2", parsePoint()); } api.geometry(name, new Plane()); } else if (type.equals("cornellbox")) { UI.printInfo(Module.API, "Reading cornell box ..."); if (transform != null) UI.printWarning(Module.API, "Instancing is not supported on cornell box -- ignoring transform"); p.checkNextToken("corner0"); api.parameter("corner0", parsePoint()); p.checkNextToken("corner1"); api.parameter("corner1", parsePoint()); p.checkNextToken("left"); api.parameter("leftColor", parseColor()); p.checkNextToken("right"); api.parameter("rightColor", parseColor()); p.checkNextToken("top"); api.parameter("topColor", parseColor()); p.checkNextToken("bottom"); api.parameter("bottomColor", parseColor()); p.checkNextToken("back"); api.parameter("backColor", parseColor()); p.checkNextToken("emit"); api.parameter("radiance", parseColor()); if (p.peekNextToken("samples")) api.parameter("samples", p.getNextInt()); new CornellBox().init(name, api); noInstance = true; // instancing is handled natively by the init // method } else if (type.equals("generic-mesh")) { UI.printInfo(Module.API, "Reading generic mesh: %s ... ", name); // parse vertices p.checkNextToken("points"); int np = p.getNextInt(); api.parameter("points", "point", "vertex", parseFloatArray(np * 3)); // parse triangle indices p.checkNextToken("triangles"); int nt = p.getNextInt(); api.parameter("triangles", parseIntArray(nt * 3)); // parse normals p.checkNextToken("normals"); if (p.peekNextToken("vertex")) api.parameter("normals", "vector", "vertex", parseFloatArray(np * 3)); else if (p.peekNextToken("facevarying")) api.parameter("normals", "vector", "facevarying", parseFloatArray(nt * 9)); else p.checkNextToken("none"); // parse texture coordinates p.checkNextToken("uvs"); if (p.peekNextToken("vertex")) api.parameter("uvs", "texcoord", "vertex", parseFloatArray(np * 2)); else if (p.peekNextToken("facevarying")) api.parameter("uvs", "texcoord", "facevarying", parseFloatArray(nt * 6)); else p.checkNextToken("none"); if (p.peekNextToken("face_shaders")) api.parameter("faceshaders", parseIntArray(nt)); api.geometry(name, new TriangleMesh()); } else if (type.equals("hair")) { UI.printInfo(Module.API, "Reading hair curves: %s ... ", name); p.checkNextToken("segments"); api.parameter("segments", p.getNextInt()); p.checkNextToken("width"); api.parameter("widths", p.getNextFloat()); p.checkNextToken("points"); api.parameter("points", "point", "vertex", parseFloatArray(p.getNextInt())); api.geometry(name, new Hair()); } else if (type.equals("janino-tesselatable")) { UI.printInfo(Module.API, "Reading procedural primitive: %s ... ", name); String code = p.getNextCodeBlock(); try { Tesselatable tess = (Tesselatable) ClassBodyEvaluator.createFastClassBodyEvaluator(new Scanner(null, new StringReader( code)), Tesselatable.class, ClassLoader.getSystemClassLoader()); api.geometry(name, tess); } catch (CompileException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); noInstance = true; } catch (ParseException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); noInstance = true; } catch (ScanException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); noInstance = true; } catch (IOException e) { UI.printDetailed(Module.API, "Compiling: %s", code); UI.printError(Module.API, "%s", e.getMessage()); e.printStackTrace(); noInstance = true; } } else if (type.equals("teapot")) { UI.printInfo(Module.API, "Reading teapot: %s ... ", name); boolean hasTesselationArguments = false; if (p.peekNextToken("subdivs")) { api.parameter("subdivs", p.getNextInt()); hasTesselationArguments = true; } if (p.peekNextToken("smooth")) { api.parameter("smooth", p.getNextBoolean()); hasTesselationArguments = true; } if (hasTesselationArguments) api.geometry(name, (Tesselatable) new Teapot()); else api.geometry(name, (PrimitiveList) new Teapot()); } else if (type.equals("gumbo")) { UI.printInfo(Module.API, "Reading gumbo: %s ... ", name); boolean hasTesselationArguments = false; if (p.peekNextToken("subdivs")) { api.parameter("subdivs", p.getNextInt()); hasTesselationArguments = true; } if (p.peekNextToken("smooth")) { api.parameter("smooth", p.getNextBoolean()); hasTesselationArguments = true; } if (hasTesselationArguments) api.geometry(name, (Tesselatable) new Gumbo()); else api.geometry(name, (PrimitiveList) new Gumbo()); } else if (type.equals("julia")) { UI.printInfo(Module.API, "Reading julia fractal: %s ... ", name); if (p.peekNextToken("q")) { api.parameter("cw", p.getNextFloat()); api.parameter("cx", p.getNextFloat()); api.parameter("cy", p.getNextFloat()); api.parameter("cz", p.getNextFloat()); } if (p.peekNextToken("iterations")) api.parameter("iterations", p.getNextInt()); if (p.peekNextToken("epsilon")) api.parameter("epsilon", p.getNextFloat()); api.geometry(name, new JuliaFractal()); } else if (type.equals("particles") || type.equals("dlasurface")) { if (type.equals("dlasurface")) UI.printWarning(Module.API, "Deprecated object type: \"dlasurface\" - please use \"particles\" instead"); p.checkNextToken("filename"); String filename = p.getNextToken(); boolean littleEndian = false; if (p.peekNextToken("little_endian")) littleEndian = true; UI.printInfo(Module.USER, "Loading particle file: %s", filename); File file = new File(filename); FileInputStream stream = new FileInputStream(filename); MappedByteBuffer map = stream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length()); if (littleEndian) map.order(ByteOrder.LITTLE_ENDIAN); FloatBuffer buffer = map.asFloatBuffer(); float[] data = new float[buffer.capacity()]; for (int i = 0; i < data.length; i++) data[i] = buffer.get(i); stream.close(); api.parameter("particles", "point", "vertex", data); if (p.peekNextToken("num")) api.parameter("num", p.getNextInt()); else api.parameter("num", data.length / 3); p.checkNextToken("radius"); api.parameter("radius", p.getNextFloat()); api.geometry(name, new ParticleSurface()); } else if (type.equals("file-mesh")) { UI.printInfo(Module.API, "Reading file mesh: %s ... ", name); p.checkNextToken("filename"); api.parameter("filename", p.getNextToken()); if (p.peekNextToken("smooth_normals")) api.parameter("smooth_normals", p.getNextBoolean()); api.geometry(name, new FileMesh()); } else if (type.equals("bezier-mesh")) { UI.printInfo(Module.API, "Reading bezier mesh: %s ... ", name); p.checkNextToken("n"); int nu, nv; api.parameter("nu", nu = p.getNextInt()); api.parameter("nv", nv = p.getNextInt()); if (p.peekNextToken("wrap")) { api.parameter("uwrap", p.getNextBoolean()); api.parameter("vwrap", p.getNextBoolean()); } p.checkNextToken("points"); float[] points = new float[3 * nu * nv]; for (int i = 0; i < points.length; i++) points[i] = p.getNextFloat(); api.parameter("points", "point", "vertex", points); if (p.peekNextToken("subdivs")) api.parameter("subdivs", p.getNextInt()); if (p.peekNextToken("smooth")) api.parameter("smooth", p.getNextBoolean()); api.geometry(name, (Tesselatable) new BezierMesh()); } else { UI.printWarning(Module.API, "Unrecognized object type: %s", p.getNextToken()); noInstance = true; } if (!noInstance) { // create instance api.parameter("shaders", shaders); if (modifiers != null) api.parameter("modifiers", modifiers); if (transform != null) api.parameter("transform", transform); api.instance(name + ".instance", name); } p.checkNextToken("}"); } private void parseInstanceBlock(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); p.checkNextToken("name"); String name = p.getNextToken(); UI.printInfo(Module.API, "Reading instance: %s ...", name); p.checkNextToken("geometry"); String geoname = p.getNextToken(); p.checkNextToken("transform"); api.parameter("transform", parseMatrix()); String[] shaders; if (p.peekNextToken("shaders")) { int n = p.getNextInt(); shaders = new String[n]; for (int i = 0; i < n; i++) shaders[i] = p.getNextToken(); } else { p.checkNextToken("shader"); shaders = new String[] { p.getNextToken() }; } api.parameter("shaders", shaders); String[] modifiers = null; if (p.peekNextToken("modifiers")) { int n = p.getNextInt(); modifiers = new String[n]; for (int i = 0; i < n; i++) modifiers[i] = p.getNextToken(); } else if (p.peekNextToken("modifier")) modifiers = new String[] { p.getNextToken() }; if (modifiers != null) api.parameter("modifiers", modifiers); api.instance(name, geoname); p.checkNextToken("}"); } private void parseLightBlock(SunflowAPI api) throws ParserException, IOException { p.checkNextToken("{"); p.checkNextToken("type"); if (p.peekNextToken("mesh")) { UI.printWarning(Module.API, "Deprecated light type: mesh"); p.checkNextToken("name"); String name = p.getNextToken(); UI.printInfo(Module.API, "Reading light mesh: %s ...", name); p.checkNextToken("emit"); api.parameter("radiance", parseColor()); int samples = numLightSamples; if (p.peekNextToken("samples")) samples = p.getNextInt(); else UI.printWarning(Module.API, "Samples keyword not found - defaulting to %d", samples); api.parameter("samples", samples); int numVertices = p.getNextInt(); int numTriangles = p.getNextInt(); float[] points = new float[3 * numVertices]; int[] triangles = new int[3 * numTriangles]; for (int i = 0; i < numVertices; i++) { p.checkNextToken("v"); points[3 * i + 0] = p.getNextFloat(); points[3 * i + 1] = p.getNextFloat(); points[3 * i + 2] = p.getNextFloat(); // ignored p.getNextFloat(); p.getNextFloat(); p.getNextFloat(); p.getNextFloat(); p.getNextFloat(); } for (int i = 0; i < numTriangles; i++) { p.checkNextToken("t"); triangles[3 * i + 0] = p.getNextInt(); triangles[3 * i + 1] = p.getNextInt(); triangles[3 * i + 2] = p.getNextInt(); } api.parameter("points", "point", "vertex", points); api.parameter("triangles", triangles); TriangleMeshLight mesh = new TriangleMeshLight(); mesh.init(name, api); } else if (p.peekNextToken("point")) { UI.printInfo(Module.API, "Reading point light ..."); Color pow; if (p.peekNextToken("color")) { pow = parseColor(); p.checkNextToken("power"); float po = p.getNextFloat(); pow.mul(po); } else { UI.printWarning(Module.API, "Deprecated color specification - please use color and power instead"); p.checkNextToken("power"); pow = parseColor(); } p.checkNextToken("p"); api.parameter("center", parsePoint()); api.parameter("power", pow); api.light(api.getUniqueName("pointlight"), new PointLight()); } else if (p.peekNextToken("spherical")) { UI.printInfo(Module.API, "Reading spherical light ..."); p.checkNextToken("color"); Color pow = parseColor(); p.checkNextToken("radiance"); pow.mul(p.getNextFloat()); api.parameter("radiance", pow); p.checkNextToken("center"); api.parameter("center", parsePoint()); p.checkNextToken("radius"); api.parameter("radius", p.getNextFloat()); p.checkNextToken("samples"); api.parameter("samples", p.getNextInt()); SphereLight light = new SphereLight(); light.init(api.getUniqueName("spherelight"), api); } else if (p.peekNextToken("directional")) { UI.printInfo(Module.API, "Reading directional light ..."); p.checkNextToken("source"); Point3 s = parsePoint(); api.parameter("source", s); p.checkNextToken("target"); Point3 t = parsePoint(); api.parameter("dir", Point3.sub(t, s, new Vector3())); p.checkNextToken("radius"); api.parameter("radius", p.getNextFloat()); p.checkNextToken("emit"); Color e = parseColor(); if (p.peekNextToken("intensity")) { float i = p.getNextFloat(); e.mul(i); } else UI.printWarning(Module.API, "Deprecated color specification - please use emit and intensity instead"); api.parameter("radiance", e); api.light(api.getUniqueName("dirlight"), new DirectionalSpotlight()); } else if (p.peekNextToken("ibl")) { UI.printInfo(Module.API, "Reading image based light ..."); p.checkNextToken("image"); api.parameter("texture", p.getNextToken()); p.checkNextToken("center"); api.parameter("center", parseVector()); p.checkNextToken("up"); api.parameter("up", parseVector()); p.checkNextToken("lock"); api.parameter("fixed", p.getNextBoolean()); int samples = numLightSamples; if (p.peekNextToken("samples")) samples = p.getNextInt(); else UI.printWarning(Module.API, "Samples keyword not found - defaulting to %d", samples); api.parameter("samples", samples); ImageBasedLight ibl = new ImageBasedLight(); ibl.init(api.getUniqueName("ibl"), api); } else if (p.peekNextToken("meshlight")) { p.checkNextToken("name"); String name = p.getNextToken(); UI.printInfo(Module.API, "Reading meshlight: %s ...", name); p.checkNextToken("emit"); Color e = parseColor(); if (p.peekNextToken("radiance")) { float r = p.getNextFloat(); e.mul(r); } else UI.printWarning(Module.API, "Deprecated color specification - please use emit and radiance instead"); api.parameter("radiance", e); int samples = numLightSamples; if (p.peekNextToken("samples")) samples = p.getNextInt(); else UI.printWarning(Module.API, "Samples keyword not found - defaulting to %d", samples); api.parameter("samples", samples); // parse vertices p.checkNextToken("points"); int np = p.getNextInt(); api.parameter("points", "point", "vertex", parseFloatArray(np * 3)); // parse triangle indices p.checkNextToken("triangles"); int nt = p.getNextInt(); api.parameter("triangles", parseIntArray(nt * 3)); TriangleMeshLight mesh = new TriangleMeshLight(); mesh.init(name, api); } else if (p.peekNextToken("sunsky")) { p.checkNextToken("up"); api.parameter("up", parseVector()); p.checkNextToken("east"); api.parameter("east", parseVector()); p.checkNextToken("sundir"); api.parameter("sundir", parseVector()); p.checkNextToken("turbidity"); api.parameter("turbidity", p.getNextFloat()); if (p.peekNextToken("samples")) api.parameter("samples", p.getNextInt()); SunSkyLight sunsky = new SunSkyLight(); sunsky.init(api.getUniqueName("sunsky"), api); } else UI.printWarning(Module.API, "Unrecognized object type: %s", p.getNextToken()); p.checkNextToken("}"); } private Color parseColor() throws IOException, ParserException { if (p.peekNextToken("{")) { String space = p.getNextToken(); Color c = null; if (space.equals("sRGB nonlinear")) { float r = p.getNextFloat(); float g = p.getNextFloat(); float b = p.getNextFloat(); c = new Color(r, g, b); c.toLinear(); } else if (space.equals("sRGB linear")) { float r = p.getNextFloat(); float g = p.getNextFloat(); float b = p.getNextFloat(); c = new Color(r, g, b); } else UI.printWarning(Module.API, "Unrecognized color space: %s", space); p.checkNextToken("}"); return c; } else { float r = p.getNextFloat(); float g = p.getNextFloat(); float b = p.getNextFloat(); return new Color(r, g, b); } } private Point3 parsePoint() throws IOException { float x = p.getNextFloat(); float y = p.getNextFloat(); float z = p.getNextFloat(); return new Point3(x, y, z); } private Vector3 parseVector() throws IOException { float x = p.getNextFloat(); float y = p.getNextFloat(); float z = p.getNextFloat(); return new Vector3(x, y, z); } private int[] parseIntArray(int size) throws IOException { int[] data = new int[size]; for (int i = 0; i < size; i++) data[i] = p.getNextInt(); return data; } private float[] parseFloatArray(int size) throws IOException { float[] data = new float[size]; for (int i = 0; i < size; i++) data[i] = p.getNextFloat(); return data; } private Matrix4 parseMatrix() throws IOException, ParserException { if (p.peekNextToken("row")) { return new Matrix4(parseFloatArray(16), true); } else if (p.peekNextToken("col")) { return new Matrix4(parseFloatArray(16), false); } else { Matrix4 m = Matrix4.IDENTITY; p.checkNextToken("{"); while (!p.peekNextToken("}")) { Matrix4 t = null; if (p.peekNextToken("translate")) { float x = p.getNextFloat(); float y = p.getNextFloat(); float z = p.getNextFloat(); t = Matrix4.translation(x, y, z); } else if (p.peekNextToken("scaleu")) { float s = p.getNextFloat(); t = Matrix4.scale(s); } else if (p.peekNextToken("scale")) { float x = p.getNextFloat(); float y = p.getNextFloat(); float z = p.getNextFloat(); t = Matrix4.scale(x, y, z); } else if (p.peekNextToken("rotatex")) { float angle = p.getNextFloat(); t = Matrix4.rotateX((float) Math.toRadians(angle)); } else if (p.peekNextToken("rotatey")) { float angle = p.getNextFloat(); t = Matrix4.rotateY((float) Math.toRadians(angle)); } else if (p.peekNextToken("rotatez")) { float angle = p.getNextFloat(); t = Matrix4.rotateZ((float) Math.toRadians(angle)); } else if (p.peekNextToken("rotate")) { float x = p.getNextFloat(); float y = p.getNextFloat(); float z = p.getNextFloat(); float angle = p.getNextFloat(); t = Matrix4.rotate(x, y, z, (float) Math.toRadians(angle)); } else UI.printWarning(Module.API, "Unrecognized transformation type: %s", p.getNextToken()); if (t != null) m = t.multiply(m); } return m; } } }