Description: Changes for sweethome3d. From sh3d upstream tarball, version 3.3.
Author: Emmanuel Puybaret, eTeks <info@eteks.com>
--- a/src/org/sunflow/core/accel/BoundingIntervalHierarchy.java
+++ b/src/org/sunflow/core/accel/BoundingIntervalHierarchy.java
@@ -177,7 +177,13 @@
float clipL = Float.NaN, clipR = Float.NaN, prevClip = Float.NaN;
float split = Float.NaN, prevSplit;
boolean wasLeft = true;
- while (true) {
+ // EP : Added a loop counter to avoid endless loop
+ float[] gridBoxCopy = gridBox.clone();
+ float[] nodeBoxCopy = nodeBox.clone();
+ int loopCount = 0;
+ // EP : End of modification
+ do {
+ // while (true) {
prevAxis = axis;
prevSplit = split;
// perform quick consistency checks
@@ -256,7 +262,8 @@
// ensure we are making progress in the subdivision
if (right == rightOrig) {
// all left
- if (clipL <= split) {
+ // EP : Added additional test to avoid endless loop
+ if (clipL < split || (clipL == split && !(prevAxis == axis && prevSplit == split))) {
// keep looping on left half
gridBox[2 * axis + 1] = split;
prevClip = clipL;
@@ -274,7 +281,8 @@
} else if (left > right) {
// all right
right = rightOrig;
- if (clipR >= split) {
+ // EP : Added additional test to avoid endless loop
+ if (clipR > split || (clipR == split && !(prevAxis == axis && prevSplit == split))) {
// keep looping on right half
gridBox[2 * axis + 0] = split;
prevClip = clipR;
@@ -322,7 +330,15 @@
}
break;
}
+ // EP : Added test to avoid endless loop
+ } while (loopCount++ < 100);
+ if (loopCount > 100) {
+ System.arraycopy(gridBoxCopy, 0, gridBox, 0, gridBox.length);
+ System.arraycopy(nodeBoxCopy, 0, nodeBox, 0, nodeBox.length);
+ return;
}
+ // EP : End of modification
+
// compute index of child nodes
int nextIndex = tempTree.getSize();
// allocate left node
@@ -482,8 +498,13 @@
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
continue;
}
+ // EP : Give up if stack is full
+ if (stackPos == stack.length) {
+ break pushloop;
+ }
+ // EP : End of modification
// ray passes through both nodes
- // push back node
+ // push back node
stack[stackPos].node = back;
stack[stackPos].near = (tb >= intervalMin) ? tb : intervalMin;
stack[stackPos].far = intervalMax;
@@ -511,6 +532,11 @@
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
continue;
}
+ // EP : Give up if stack is full
+ if (stackPos == stack.length) {
+ break pushloop;
+ }
+ // EP : End of modification
// ray passes through both nodes
// push back node
stack[stackPos].node = back;
@@ -541,6 +567,11 @@
intervalMax = (tf <= intervalMax) ? tf : intervalMax;
continue;
}
+ // EP : Give up if stack is full
+ if (stackPos == stack.length) {
+ break pushloop;
+ }
+ // EP : End of modification
// ray passes through both nodes
// push back node
stack[stackPos].node = back;
@@ -597,7 +628,12 @@
} // traversal loop
do {
// stack is empty?
- if (stackPos == 0)
+ if (stackPos == 0
+ // EP : Check ray values aren't NaN
+ || Float.isNaN(r.dx)
+ || Float.isNaN(r.dy)
+ || Float.isNaN(r.dz))
+ // EP : End of modification
return;
// move back up the stack
stackPos--;
--- a/src/org/sunflow/core/light/ImageBasedLight.java
+++ b/src/org/sunflow/core/light/ImageBasedLight.java
@@ -11,7 +11,6 @@
import org.sunflow.core.Shader;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Bitmap;
import org.sunflow.image.Color;
import org.sunflow.math.BoundingBox;
@@ -55,7 +54,8 @@
numLowSamples = pl.getInt("lowsamples", numLowSamples);
String filename = pl.getString("texture", null);
if (filename != null)
- texture = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ texture = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
// no texture provided
if (texture == null)
@@ -272,4 +272,14 @@
public Instance createInstance() {
return Instance.createTemporary(this, null, this);
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/light/SphereLight.java
+++ b/src/org/sunflow/core/light/SphereLight.java
@@ -150,4 +150,14 @@
public Instance createInstance() {
return Instance.createTemporary(new Sphere(), Matrix4.translation(center.x, center.y, center.z).multiply(Matrix4.scale(radius)), this);
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/light/SunSkyLight.java
+++ b/src/org/sunflow/core/light/SunSkyLight.java
@@ -315,6 +315,12 @@
return getSkyRGB(basis.untransform(state.getRay().getDirection())).constrainRGB();
}
+ // EP : Reused sun sky color
+ public Color getSunColor() {
+ return getSkyRGB(basis.untransform(sunDirWorld)).constrainRGB();
+ }
+ // EP : End of modification
+
public void scatterPhoton(ShadingState state, Color power) {
// let photon escape
}
@@ -334,4 +340,14 @@
public Instance createInstance() {
return Instance.createTemporary(this, null, this);
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/light/TriangleMeshLight.java
+++ b/src/org/sunflow/core/light/TriangleMeshLight.java
@@ -269,4 +269,14 @@
}
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/LightServer.java
+++ b/src/org/sunflow/core/LightServer.java
@@ -24,12 +24,15 @@
private int maxDiffuseDepth;
private int maxReflectionDepth;
private int maxRefractionDepth;
+ // EP : Added transparency management
+ private int maxTransparencyDepth;
// indirect illumination
private CausticPhotonMapInterface causticPhotonMap;
private GIEngine giEngine;
private int photonCounter;
+
LightServer(Scene scene) {
this.scene = scene;
lights = new LightSource[0];
@@ -41,6 +44,8 @@
maxDiffuseDepth = 1;
maxReflectionDepth = 4;
maxRefractionDepth = 4;
+ // EP : Added transparency management
+ maxTransparencyDepth = 4;
causticPhotonMap = null;
giEngine = null;
@@ -64,6 +69,8 @@
maxDiffuseDepth = options.getInt("depths.diffuse", maxDiffuseDepth);
maxReflectionDepth = options.getInt("depths.reflection", maxReflectionDepth);
maxRefractionDepth = options.getInt("depths.refraction", maxRefractionDepth);
+ // EP : Added transparency management
+ maxTransparencyDepth = options.getInt("depths.transparency", maxTransparencyDepth);
String giEngineType = options.getString("gi.engine", null);
giEngine = PluginRegistry.giEnginePlugins.createObject(giEngineType);
String caustics = options.getString("caustics", null);
@@ -143,7 +150,8 @@
synchronized (LightServer.this) {
UI.taskUpdate(photonCounter);
photonCounter++;
- if (UI.taskCanceled())
+ // EP : Manage renderer stop with interruptions
+ if (Thread.currentThread().isInterrupted())
return;
}
@@ -176,18 +184,19 @@
photonThreads[i].setPriority(scene.getThreadPriority());
photonThreads[i].start();
}
- for (int i = 0; i < photonThreads.length; i++) {
- try {
+ // EP : Moved InterruptedException out of loop to be able to stop all rendering threads
+ try {
+ for (int i = 0; i < photonThreads.length; i++) {
photonThreads[i].join();
- } catch (InterruptedException e) {
- UI.printError(Module.LIGHT, "Photon thread %d of %d was interrupted", i + 1, photonThreads.length);
- return false;
}
- }
- if (UI.taskCanceled()) {
- UI.taskStop(); // shut down task cleanly
+ } catch (InterruptedException e) {
+ for (int i = 0; i < photonThreads.length; i++) {
+ photonThreads[i].interrupt();
+ }
+ UI.printError(Module.BCKT, "Photon thread was interrupted");
return false;
}
+ // EP : End of modification
photonTimer.end();
UI.taskStop();
UI.printInfo(Module.LIGHT, "Tracing time for %s photons: %s", type, photonTimer.toString());
@@ -294,7 +303,7 @@
Color shadeHit(ShadingState state) {
state.getInstance().prepareShadingState(state);
Shader shader = getShader(state);
- return (shader != null) ? shader.getRadiance(state) : Color.BLACK;
+ return (shader != null) ? shader.getRadiance(state) : Color.BLACK;
}
Color traceGlossy(ShadingState previous, Ray r, int i) {
@@ -357,4 +366,28 @@
if (causticPhotonMap != null)
causticPhotonMap.getSamples(state);
}
+
+ // EP : Added transparency management
+ Color traceShadow(Ray r, ShadingState previous) {
+ float maxDist = r.getMax();
+ scene.traceShadow(r, previous.getIntersectionState());
+ if (previous.getIntersectionState().hit()) {
+ Shader previousShader = previous.getIntersectionState().instance.getShader(0);
+ if (previousShader == null || previousShader.isOpaque() || previous.getShadowDepth() >= maxTransparencyDepth) {
+ return Color.WHITE; // fully opaque hit
+ }
+ ShadingState sstate = ShadingState.createShadowState(previous, r);
+ sstate.getInstance().prepareShadingState(sstate);
+ Shader shader = getShader(sstate);
+ if (shader == null || shader.isOpaque())
+ return Color.WHITE;
+ Color opac = shader.getOpacity(sstate);
+ if (opac.isWhite())
+ return opac;
+ else
+ return opac.copy().madd(Color.sub(Color.WHITE, opac), sstate.traceTransparentShadow(maxDist));
+ } else
+ return Color.BLACK;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/modifiers/BumpMappingModifier.java
+++ b/src/org/sunflow/core/modifiers/BumpMappingModifier.java
@@ -5,7 +5,6 @@
import org.sunflow.core.ParameterList;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.math.OrthoNormalBasis;
public class BumpMappingModifier implements Modifier {
@@ -20,7 +19,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- bumpTexture = TextureCache.getTexture(api.resolveTextureFilename(filename), true);
+ // EP : Made texture cache local to a SunFlow API instance
+ bumpTexture = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), true);
scale = pl.getFloat("scale", scale);
return bumpTexture != null;
}
--- a/src/org/sunflow/core/modifiers/NormalMapModifier.java
+++ b/src/org/sunflow/core/modifiers/NormalMapModifier.java
@@ -5,7 +5,6 @@
import org.sunflow.core.ParameterList;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.math.OrthoNormalBasis;
public class NormalMapModifier implements Modifier {
@@ -18,7 +17,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- normalMap = TextureCache.getTexture(api.resolveTextureFilename(filename), true);
+ // EP : Made texture cache local to a SunFlow API instance
+ normalMap = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), true);
return normalMap != null;
}
--- a/src/org/sunflow/core/photonmap/GlobalPhotonMap.java
+++ b/src/org/sunflow/core/photonmap/GlobalPhotonMap.java
@@ -260,7 +260,8 @@
t.start();
balance();
t.end();
- UI.taskStop();
+ // EP : Replaced task management with interruptions
+ // UI.taskStop();
UI.printInfo(Module.LIGHT, "Global photon map:");
UI.printInfo(Module.LIGHT, " * Photons stored: %d", storedPhotons);
UI.printInfo(Module.LIGHT, " * Photons/estimate: %d", numGather);
@@ -328,7 +329,8 @@
curr.data = irr.toRGBE();
temp[i] = curr;
}
- UI.taskStop();
+ // EP : Replaced task management with interruptions
+ // UI.taskStop();
// resize photon map to only include irradiance photons
numGather /= 4;
--- a/src/org/sunflow/core/primitive/CornellBox.java
+++ b/src/org/sunflow/core/primitive/CornellBox.java
@@ -443,4 +443,14 @@
public Instance createInstance() {
return Instance.createTemporary(this, null, this);
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/primitive/Hair.java
+++ b/src/org/sunflow/core/primitive/Hair.java
@@ -258,4 +258,14 @@
public PrimitiveList getBakingPrimitives() {
return null;
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/Ray.java
+++ b/src/org/sunflow/core/Ray.java
@@ -214,4 +214,11 @@
public final void setMax(float t) {
tMax = t;
}
+
+ // EP : Added transparency management
+ public void setMinMax(float min, float max) {
+ tMin = min;
+ tMax = max;
+ }
+ // EP : end of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/renderer/BucketRenderer.java
+++ b/src/org/sunflow/core/renderer/BucketRenderer.java
@@ -157,15 +157,22 @@
renderThreads[i].setPriority(scene.getThreadPriority());
renderThreads[i].start();
}
- for (int i = 0; i < renderThreads.length; i++) {
- try {
- renderThreads[i].join();
- } catch (InterruptedException e) {
- UI.printError(Module.BCKT, "Bucket processing thread %d of %d was interrupted", i + 1, renderThreads.length);
- } finally {
- renderThreads[i].updateStats();
+ // EP : Moved InterruptedException out of loop to be able to stop all rendering threads
+ try {
+ for (int i = 0; i < renderThreads.length; i++) {
+ try {
+ renderThreads[i].join();
+ } finally {
+ renderThreads[i].updateStats();
+ }
+ }
+ } catch (InterruptedException e) {
+ for (int i = 0; i < renderThreads.length; i++) {
+ renderThreads[i].interrupt();
}
+ UI.printError(Module.BCKT, "Bucket processing was interrupted");
}
+ // EP : End of modification
UI.taskStop();
timer.end();
UI.printInfo(Module.BCKT, "Render time: %s", timer.toString());
@@ -183,7 +190,8 @@
@Override
public void run() {
- while (true) {
+ // EP : Check rendering isn't interrupted
+ while (!isInterrupted()) {
int bx, by;
synchronized (BucketRenderer.this) {
if (bucketCounter >= bucketCoords.length)
@@ -194,8 +202,6 @@
bucketCounter += 2;
}
renderBucket(display, bx, by, threadID, istate);
- if (UI.taskCanceled())
- return;
}
}
@@ -251,8 +257,13 @@
}
}
for (int x = 0; x < sbw - 1; x += maxStepSize)
- for (int y = 0; y < sbh - 1; y += maxStepSize)
+ for (int y = 0; y < sbh - 1; y += maxStepSize) {
+ // EP : Check rendering isn't interrupted
+ if (Thread.currentThread().isInterrupted()) {
+ return;
+ }
refineSamples(samples, sbw, x, y, maxStepSize, thresh, istate);
+ }
if (dumpBuckets) {
UI.printInfo(Module.BCKT, "Dumping bucket [%d, %d] to file ...", bx, by);
GenericBitmap bitmap = new GenericBitmap(sbw, sbh);
@@ -297,7 +308,9 @@
if (Math.abs(dy) > fhs)
continue;
float f = filter.get(dx, dy);
- c.madd(f, samples[s].c);
+ // EP : Test if color isn't null
+ if (samples[s].c != null)
+ c.madd(f, samples[s].c);
a += f * samples[s].alpha;
weight += f;
--- a/src/org/sunflow/core/renderer/MultipassRenderer.java
+++ b/src/org/sunflow/core/renderer/MultipassRenderer.java
@@ -86,15 +86,22 @@
renderThreads[i].setPriority(scene.getThreadPriority());
renderThreads[i].start();
}
- for (int i = 0; i < renderThreads.length; i++) {
- try {
- renderThreads[i].join();
- } catch (InterruptedException e) {
- UI.printError(Module.BCKT, "Bucket processing thread %d of %d was interrupted", i + 1, renderThreads.length);
- } finally {
- renderThreads[i].updateStats();
+ // EP : Moved InterruptedException out of loop to be able to stop all rendering threads
+ try {
+ for (int i = 0; i < renderThreads.length; i++) {
+ try {
+ renderThreads[i].join();
+ } finally {
+ renderThreads[i].updateStats();
+ }
}
+ } catch (InterruptedException e) {
+ for (int i = 0; i < renderThreads.length; i++) {
+ renderThreads[i].interrupt();
+ }
+ UI.printError(Module.BCKT, "Bucket processing was interrupted");
}
+ // EP : End of modification
UI.taskStop();
timer.end();
UI.printInfo(Module.BCKT, "Render time: %s", timer.toString());
@@ -114,7 +121,8 @@
@Override
public void run() {
- while (true) {
+ // EP : Check rendering isn't interrupted or canceled
+ while (!isInterrupted()) {
int bx, by;
synchronized (MultipassRenderer.this) {
if (bucketCounter >= bucketCoords.length)
--- a/src/org/sunflow/core/renderer/ProgressiveRenderer.java
+++ b/src/org/sunflow/core/renderer/ProgressiveRenderer.java
@@ -58,15 +58,22 @@
renderThreads[i] = new SmallBucketThread();
renderThreads[i].start();
}
- for (int i = 0; i < renderThreads.length; i++) {
- try {
- renderThreads[i].join();
- } catch (InterruptedException e) {
- UI.printError(Module.IPR, "Thread %d of %d was interrupted", i + 1, renderThreads.length);
- } finally {
- renderThreads[i].updateStats();
+ // EP : Moved InterruptedException out of loop to be able to stop all rendering threads
+ try {
+ for (int i = 0; i < renderThreads.length; i++) {
+ try {
+ renderThreads[i].join();
+ } finally {
+ renderThreads[i].updateStats();
+ }
+ }
+ } catch (InterruptedException e) {
+ for (int i = 0; i < renderThreads.length; i++) {
+ renderThreads[i].interrupt();
}
+ UI.printError(Module.IPR, "Thread was interrupted");
}
+ // EP : End of modification
UI.taskStop();
t.end();
UI.printInfo(Module.IPR, "Rendering time: %s", t.toString());
@@ -78,7 +85,8 @@
@Override
public void run() {
- while (true) {
+ // EP : Check rendering isn't interrupted
+ while (!isInterrupted()) {
int n = progressiveRenderNext(istate);
synchronized (ProgressiveRenderer.this) {
if (counter >= counterMax)
@@ -86,8 +94,6 @@
counter += n;
UI.taskUpdate(counter);
}
- if (UI.taskCanceled())
- return;
}
}
--- a/src/org/sunflow/core/renderer/SimpleRenderer.java
+++ b/src/org/sunflow/core/renderer/SimpleRenderer.java
@@ -41,15 +41,23 @@
renderThreads[i] = new BucketThread();
renderThreads[i].start();
}
- for (int i = 0; i < renderThreads.length; i++) {
- try {
- renderThreads[i].join();
- } catch (InterruptedException e) {
- UI.printError(Module.BCKT, "Bucket processing thread %d of %d was interrupted", i + 1, renderThreads.length);
- } finally {
- renderThreads[i].updateStats();
+ // EP : Moved InterruptedException out of loop to be able to stop all rendering threads
+ try {
+ for (int i = 0; i < renderThreads.length; i++) {
+ try {
+ renderThreads[i].join();
+ } finally {
+ renderThreads[i].updateStats();
+ }
+ }
+ } catch (InterruptedException e) {
+ for (int i = 0; i < renderThreads.length; i++) {
+ renderThreads[i].interrupt();
}
+ UI.printError(Module.BCKT, "Bucket processing was interrupted");
}
+ UI.taskStop();
+ // EP : End of modification
timer.end();
UI.printInfo(Module.BCKT, "Render time: %s", timer.toString());
display.imageEnd();
@@ -60,7 +68,8 @@
@Override
public void run() {
- while (true) {
+ // EP : Check rendering isn't interrupted
+ while (!isInterrupted()) {
int bx, by;
synchronized (SimpleRenderer.this) {
if (bucketCounter >= numBuckets)
@@ -90,6 +99,9 @@
for (int y = 0, i = 0; y < bh; y++) {
for (int x = 0; x < bw; x++, i++) {
+ // EP : Check rendering isn't interrupted
+ if (Thread.currentThread().isInterrupted())
+ return;
ShadingState state = scene.getRadiance(istate, x0 + x, imageHeight - 1 - (y0 + y), 0.0, 0.0, 0.0, 0, 0, null);
bucketRGB[i] = (state != null) ? state.getResult() : Color.BLACK;
bucketAlpha[i] = (state != null) ? 1 : 0;
--- a/src/org/sunflow/core/shader/AmbientOcclusionShader.java
+++ b/src/org/sunflow/core/shader/AmbientOcclusionShader.java
@@ -45,4 +45,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/AnisotropicWardShader.java
+++ b/src/org/sunflow/core/shader/AnisotropicWardShader.java
@@ -208,4 +208,14 @@
state.traceReflectionPhoton(r, power);
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/ConstantShader.java
+++ b/src/org/sunflow/core/shader/ConstantShader.java
@@ -24,4 +24,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/DiffuseShader.java
+++ b/src/org/sunflow/core/shader/DiffuseShader.java
@@ -58,4 +58,14 @@
state.traceDiffusePhoton(new Ray(state.getPoint(), w), power);
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/GlassShader.java
+++ b/src/org/sunflow/core/shader/GlassShader.java
@@ -136,4 +136,14 @@
}
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return absorptionColor.isWhite();
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return absorptionColor;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/IDShader.java
+++ b/src/org/sunflow/core/shader/IDShader.java
@@ -20,4 +20,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/MirrorShader.java
+++ b/src/org/sunflow/core/shader/MirrorShader.java
@@ -59,4 +59,14 @@
dir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
state.traceReflectionPhoton(new Ray(state.getPoint(), dir), power);
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/NormalShader.java
+++ b/src/org/sunflow/core/shader/NormalShader.java
@@ -24,4 +24,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/PhongShader.java
+++ b/src/org/sunflow/core/shader/PhongShader.java
@@ -82,4 +82,14 @@
state.traceReflectionPhoton(new Ray(state.getPoint(), w), power);
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/PrimIDShader.java
+++ b/src/org/sunflow/core/shader/PrimIDShader.java
@@ -23,4 +23,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/QuickGrayShader.java
+++ b/src/org/sunflow/core/shader/QuickGrayShader.java
@@ -56,4 +56,14 @@
state.traceDiffusePhoton(new Ray(state.getPoint(), w), power);
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/ShinyDiffuseShader.java
+++ b/src/org/sunflow/core/shader/ShinyDiffuseShader.java
@@ -24,6 +24,12 @@
return true;
}
+ // EP : Added getter to read shininess from subclasses
+ protected float getShininess() {
+ return this.refl;
+ }
+ // EP : End of modification
+
public Color getDiffuse(ShadingState state) {
return diff;
}
@@ -90,4 +96,14 @@
state.traceReflectionPhoton(new Ray(state.getPoint(), dir), power);
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/SimpleShader.java
+++ b/src/org/sunflow/core/shader/SimpleShader.java
@@ -17,4 +17,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/TexturedAmbientOcclusionShader.java
+++ b/src/org/sunflow/core/shader/TexturedAmbientOcclusionShader.java
@@ -4,7 +4,6 @@
import org.sunflow.core.ParameterList;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Color;
public class TexturedAmbientOcclusionShader extends AmbientOcclusionShader {
@@ -18,7 +17,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- tex = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ tex = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
return tex != null && super.update(pl, api);
}
--- a/src/org/sunflow/core/shader/TexturedDiffuseShader.java
+++ b/src/org/sunflow/core/shader/TexturedDiffuseShader.java
@@ -2,10 +2,11 @@
import org.sunflow.SunflowAPI;
import org.sunflow.core.ParameterList;
+import org.sunflow.core.Ray;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Color;
+import org.sunflow.math.Vector3;
public class TexturedDiffuseShader extends DiffuseShader {
private Texture tex;
@@ -18,7 +19,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- tex = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ tex = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
return tex != null && super.update(pl, api);
}
@@ -26,4 +28,32 @@
public Color getDiffuse(ShadingState state) {
return tex.getPixel(state.getUV().x, state.getUV().y);
}
+
+ // EP : Added transparency management
+ @Override
+ public Color getRadiance(ShadingState state) {
+ Color opacity;
+ if (isOpaque() || (opacity = getOpacity(state)).isWhite()) {
+ // Pixel is fully opaque
+ return super.getRadiance(state);
+ } else {
+ state.faceforward();
+ state.initLightSamples();
+ state.initCausticSamples();
+ Vector3 refrDir = state.getRay().getDirection();
+ Color refraction = state.traceRefraction(new Ray(state.getPoint(), refrDir), 0);
+ return Color.sub(Color.WHITE, opacity).mul(refraction);
+ }
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return !(tex.isTransparent());
+ }
+
+ @Override
+ public Color getOpacity(ShadingState state) {
+ return tex.getOpacity(state.getUV().x, state.getUV().y);
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/TexturedPhongShader.java
+++ b/src/org/sunflow/core/shader/TexturedPhongShader.java
@@ -2,10 +2,11 @@
import org.sunflow.SunflowAPI;
import org.sunflow.core.ParameterList;
+import org.sunflow.core.Ray;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Color;
+import org.sunflow.math.Vector3;
public class TexturedPhongShader extends PhongShader {
private Texture tex;
@@ -18,7 +19,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- tex = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ tex = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
return tex != null && super.update(pl, api);
}
@@ -26,4 +28,31 @@
public Color getDiffuse(ShadingState state) {
return tex.getPixel(state.getUV().x, state.getUV().y);
}
+
+ // EP : Added transparency management
+ @Override
+ public Color getRadiance(ShadingState state) {
+ Color opacity;
+ if (isOpaque() || (opacity = getOpacity(state)).isWhite()) {
+ return super.getRadiance(state);
+ } else {
+ state.faceforward();
+ state.initLightSamples();
+ state.initCausticSamples();
+ Vector3 refrDir = state.getRay().getDirection();
+ Color refraction = state.traceRefraction(new Ray(state.getPoint(), refrDir), 0);
+ return Color.sub(Color.WHITE, opacity).mul(refraction);
+ }
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return !(tex.isTransparent());
+ }
+
+ @Override
+ public Color getOpacity(ShadingState state) {
+ return tex.getOpacity(state.getUV().x, state.getUV().y);
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/TexturedShinyDiffuseShader.java
+++ b/src/org/sunflow/core/shader/TexturedShinyDiffuseShader.java
@@ -2,10 +2,11 @@
import org.sunflow.SunflowAPI;
import org.sunflow.core.ParameterList;
+import org.sunflow.core.Ray;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Color;
+import org.sunflow.math.Vector3;
public class TexturedShinyDiffuseShader extends ShinyDiffuseShader {
private Texture tex;
@@ -18,7 +19,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- tex = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ tex = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
return tex != null && super.update(pl, api);
}
@@ -26,4 +28,56 @@
public Color getDiffuse(ShadingState state) {
return tex.getPixel(state.getUV().x, state.getUV().y);
}
+
+ // EP : Added transparency management
+ @Override
+ public Color getRadiance(ShadingState state) {
+ Color opacity;
+ if (isOpaque() || (opacity = getOpacity(state)).isWhite()) {
+ // Pixel is fully opaque
+ return super.getRadiance(state);
+ } else {
+ state.faceforward();
+ // direct lighting
+ state.initLightSamples();
+ state.initCausticSamples();
+ Color d = Color.sub(Color.WHITE, opacity);
+ Vector3 refrDir = state.getRay().getDirection();
+ Color refraction = state.traceRefraction(new Ray(state.getPoint(), refrDir), 0);
+ d.mul(refraction);
+ if (!state.includeSpecular()
+ || opacity.isBlack()) { // No reflection when fully transparent
+ return d;
+ }
+ float cos = state.getCosND();
+ float dn = 2 * cos;
+ Vector3 refDir = new Vector3();
+ refDir.x = (dn * state.getNormal().x) + state.getRay().getDirection().x;
+ refDir.y = (dn * state.getNormal().y) + state.getRay().getDirection().y;
+ refDir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
+ Ray refRay = new Ray(state.getPoint(), refDir);
+ // compute Fresnel term
+ cos = 1 - cos;
+ float cos2 = cos * cos;
+ float cos5 = cos2 * cos2 * cos;
+
+ Color ret = Color.white();
+ Color r = Color.sub(Color.WHITE, opacity).mul(getShininess());
+ ret.sub(r);
+ ret.mul(cos5);
+ ret.add(r);
+ return d.add(ret.mul(state.traceReflection(refRay, 0)));
+ }
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return !(tex.isTransparent());
+ }
+
+ @Override
+ public Color getOpacity(ShadingState state) {
+ return tex.getOpacity(state.getUV().x, state.getUV().y);
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/TexturedWardShader.java
+++ b/src/org/sunflow/core/shader/TexturedWardShader.java
@@ -4,7 +4,6 @@
import org.sunflow.core.ParameterList;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Color;
public class TexturedWardShader extends AnisotropicWardShader {
@@ -18,7 +17,8 @@
public boolean update(ParameterList pl, SunflowAPI api) {
String filename = pl.getString("texture", null);
if (filename != null)
- tex = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ tex = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
return tex != null && super.update(pl, api);
}
@@ -26,4 +26,16 @@
public Color getDiffuse(ShadingState state) {
return tex.getPixel(state.getUV().x, state.getUV().y);
}
+
+ // EP : Added transparency management
+ @Override
+ public boolean isOpaque() {
+ return !(tex.isTransparent());
+ }
+
+ @Override
+ public Color getOpacity(ShadingState state) {
+ return tex.getOpacity(state.getUV().x, state.getUV().y);
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/UberShader.java
+++ b/src/org/sunflow/core/shader/UberShader.java
@@ -6,7 +6,6 @@
import org.sunflow.core.Shader;
import org.sunflow.core.ShadingState;
import org.sunflow.core.Texture;
-import org.sunflow.core.TextureCache;
import org.sunflow.image.Color;
import org.sunflow.math.MathUtils;
import org.sunflow.math.OrthoNormalBasis;
@@ -36,10 +35,12 @@
String filename;
filename = pl.getString("diffuse.texture", null);
if (filename != null)
- diffmap = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ diffmap = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
filename = pl.getString("specular.texture", null);
if (filename != null)
- specmap = TextureCache.getTexture(api.resolveTextureFilename(filename), false);
+ // EP : Made texture cache local to a SunFlow API instance
+ specmap = api.getTextureCache().getTexture(api.resolveTextureFilename(filename), false);
diffBlend = MathUtils.clamp(pl.getFloat("diffuse.blend", diffBlend), 0, 1);
specBlend = MathUtils.clamp(pl.getFloat("specular.blend", diffBlend), 0, 1);
glossyness = MathUtils.clamp(pl.getFloat("glossyness", glossyness), 0, 1);
@@ -61,30 +62,41 @@
// direct lighting
state.initLightSamples();
state.initCausticSamples();
- Color d = getDiffuse(state);
- Color lr = state.diffuse(d);
- if (!state.includeSpecular())
- return lr;
- if (glossyness == 0) {
- float cos = state.getCosND();
- float dn = 2 * cos;
- Vector3 refDir = new Vector3();
- refDir.x = (dn * state.getNormal().x) + state.getRay().getDirection().x;
- refDir.y = (dn * state.getNormal().y) + state.getRay().getDirection().y;
- refDir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
- Ray refRay = new Ray(state.getPoint(), refDir);
- // compute Fresnel term
- cos = 1 - cos;
- float cos2 = cos * cos;
- float cos5 = cos2 * cos2 * cos;
- Color spec = getSpecular(state);
- Color ret = Color.white();
- ret.sub(spec);
- ret.mul(cos5);
- ret.add(spec);
- return lr.add(ret.mul(state.traceReflection(refRay, 0)));
- } else
- return lr.add(state.specularPhong(getSpecular(state), 2 / glossyness, numSamples));
+ // EP : Added transparency management
+ Color opacity;
+ if (!isOpaque() && !(opacity = getOpacity(state)).isWhite()) {
+ Vector3 refrDir = state.getRay().getDirection();
+ Color refraction = state.traceRefraction(new Ray(state.getPoint(), refrDir), 0);
+ return Color.sub(Color.WHITE, opacity).mul(refraction);
+ } else {
+ // EP : End of modification
+ Color d = getDiffuse(state);
+ Color lr = state.diffuse(d);
+ if (!state.includeSpecular())
+ return lr;
+ if (glossyness == 0) {
+ float cos = state.getCosND();
+ float dn = 2 * cos;
+ Vector3 refDir = new Vector3();
+ refDir.x = (dn * state.getNormal().x) + state.getRay().getDirection().x;
+ refDir.y = (dn * state.getNormal().y) + state.getRay().getDirection().y;
+ refDir.z = (dn * state.getNormal().z) + state.getRay().getDirection().z;
+ Ray refRay = new Ray(state.getPoint(), refDir);
+ // compute Fresnel term
+ cos = 1 - cos;
+ float cos2 = cos * cos;
+ float cos5 = cos2 * cos2 * cos;
+ Color spec = getSpecular(state);
+ Color ret = Color.white();
+ ret.sub(spec);
+ ret.mul(cos5);
+ ret.add(spec);
+ return lr.add(ret.mul(state.traceReflection(refRay, 0)));
+ } else
+ return lr.add(state.specularPhong(getSpecular(state), 2 / glossyness, numSamples));
+ // EP : Added transparency management
+ }
+ // EP : End of modification
}
public void scatterPhoton(ShadingState state, Color power) {
@@ -138,4 +150,14 @@
}
}
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return diffmap == null || !(diffmap.isTransparent());
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return diffmap != null ? diffmap.getOpacity(state.getUV().x, state.getUV().y) : Color.WHITE;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/UVShader.java
+++ b/src/org/sunflow/core/shader/UVShader.java
@@ -19,4 +19,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/ViewCausticsShader.java
+++ b/src/org/sunflow/core/shader/ViewCausticsShader.java
@@ -25,4 +25,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/ViewGlobalPhotonsShader.java
+++ b/src/org/sunflow/core/shader/ViewGlobalPhotonsShader.java
@@ -18,4 +18,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/ViewIrradianceShader.java
+++ b/src/org/sunflow/core/shader/ViewIrradianceShader.java
@@ -18,4 +18,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/shader/WireframeShader.java
+++ b/src/org/sunflow/core/shader/WireframeShader.java
@@ -73,4 +73,14 @@
public void scatterPhoton(ShadingState state, Color power) {
}
+
+ // EP : Added transparency management
+ public boolean isOpaque() {
+ return true;
+ }
+
+ public Color getOpacity(ShadingState state) {
+ return null;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/Shader.java
+++ b/src/org/sunflow/core/Shader.java
@@ -26,4 +26,18 @@
* @param power power of the incoming photon.
*/
public void scatterPhoton(ShadingState state, Color power);
+
+ // EP : Added transparency management
+ /**
+ * Returns <code>true</code> if this shader is fully opaque.
+ * This gives a quick way to find out if a shader needs further processing
+ * when hit by a shadow ray.
+ */
+ public boolean isOpaque();
+
+ /**
+ * Returns how much light is blocked by this shader.
+ */
+ public Color getOpacity(ShadingState state);
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/ShadingState.java
+++ b/src/org/sunflow/core/ShadingState.java
@@ -46,9 +46,12 @@
private boolean includeSpecular;
private LightSample lightSample;
private PhotonStore map;
+ // EP : Added transparency management
+ private int shadowDepth;
static ShadingState createPhotonState(Ray r, IntersectionState istate, int i, PhotonStore map, LightServer server) {
- ShadingState s = new ShadingState(null, istate, r, i, 4);
+ // EP : Added ignoreHalton parameter
+ ShadingState s = new ShadingState(null, istate, r, i, 4, false);
s.server = server;
s.map = map;
return s;
@@ -56,7 +59,8 @@
}
static ShadingState createState(IntersectionState istate, float rx, float ry, float time, Ray r, int i, int d, LightServer server) {
- ShadingState s = new ShadingState(null, istate, r, i, d);
+ // EP : Added ignoreHalton parameter
+ ShadingState s = new ShadingState(null, istate, r, i, d, false);
s.server = server;
s.rx = rx;
s.ry = ry;
@@ -65,40 +69,48 @@
}
static ShadingState createDiffuseBounceState(ShadingState previous, Ray r, int i) {
- ShadingState s = new ShadingState(previous, previous.istate, r, i, 2);
+ // EP : Added ignoreHalton parameter
+ ShadingState s = new ShadingState(previous, previous.istate, r, i, 2, false);
s.diffuseDepth++;
return s;
}
static ShadingState createGlossyBounceState(ShadingState previous, Ray r, int i) {
- ShadingState s = new ShadingState(previous, previous.istate, r, i, 2);
+ // EP : Added ignoreHalton parameter
+ ShadingState s = new ShadingState(previous, previous.istate, r, i, 2, false);
s.includeLights = false;
- s.includeSpecular = false;
- s.reflectionDepth++;
+ // EP : Set includeSpecular to true to get the reflects
+ s.includeSpecular = true;
+ // EP : Very dirty hack to let mirror shader manage more bounces than uber shader
+ s.reflectionDepth += 4;
return s;
}
static ShadingState createReflectionBounceState(ShadingState previous, Ray r, int i) {
- ShadingState s = new ShadingState(previous, previous.istate, r, i, 2);
+ // EP : Added ignoreHalton parameter
+ ShadingState s = new ShadingState(previous, previous.istate, r, i, 2, false);
s.reflectionDepth++;
return s;
}
static ShadingState createRefractionBounceState(ShadingState previous, Ray r, int i) {
- ShadingState s = new ShadingState(previous, previous.istate, r, i, 2);
+ // EP : Added ignoreHalton parameter
+ ShadingState s = new ShadingState(previous, previous.istate, r, i, 2, false);
s.refractionDepth++;
return s;
}
static ShadingState createFinalGatherState(ShadingState state, Ray r, int i) {
- ShadingState finalGatherState = new ShadingState(state, state.istate, r, i, 2);
+ // EP : Added ignoreHalton parameter
+ ShadingState finalGatherState = new ShadingState(state, state.istate, r, i, 2, false);
finalGatherState.diffuseDepth++;
finalGatherState.includeLights = false;
finalGatherState.includeSpecular = false;
return finalGatherState;
}
- private ShadingState(ShadingState previous, IntersectionState istate, Ray r, int i, int d) {
+ // EP : Added ignoreHalton parameter
+ private ShadingState(ShadingState previous, IntersectionState istate, Ray r, int i, int d, boolean ignoreHalton) {
this.r = r;
this.istate = istate;
this.i = i;
@@ -121,6 +133,8 @@
diffuseDepth = previous.diffuseDepth;
reflectionDepth = previous.reflectionDepth;
refractionDepth = previous.refractionDepth;
+ // EP : copy shadow depth
+ shadowDepth = previous.shadowDepth;
server = previous.server;
map = previous.map;
rx = previous.rx;
@@ -131,8 +145,12 @@
behind = false;
cosND = Float.NaN;
includeLights = includeSpecular = true;
- qmcD0I = QMC.halton(this.d, this.i);
- qmcD1I = QMC.halton(this.d + 1, this.i);
+ // EP : Ignore Halton values for transparency computing
+ if (!ignoreHalton) {
+ qmcD0I = QMC.halton(this.d, this.i);
+ qmcD1I = QMC.halton(this.d + 1, this.i);
+ }
+ // EP : End of modification
result = null;
bias = 0.001f;
}
@@ -691,7 +709,8 @@
* @return opacity along the shadow ray
*/
public final Color traceShadow(Ray r) {
- return server.getScene().traceShadow(r, istate);
+ // EP : Added transparency management
+ return server.traceShadow(r, this);
}
/**
@@ -926,4 +945,22 @@
throw new UnsupportedOperationException();
}
}
+
+ // EP : Added transparency management
+ static ShadingState createShadowState(ShadingState previous, Ray r) {
+ ShadingState s = new ShadingState(previous, previous.istate, r, previous.i, previous.d, true);
+ s.shadowDepth++;
+ return s;
+ }
+
+ public final int getShadowDepth() {
+ return shadowDepth;
+ }
+
+ public Color traceTransparentShadow(float oldMaxT) {
+ Ray tr = new Ray(r.ox, r.oy, r.oz, r.dx, r.dy, r.dz);
+ tr.setMinMax(r.getMax(), oldMaxT);
+ return traceShadow(tr);
+ }
+ // EP : end of modification
}
\ No newline at end of file
--- a/src/org/sunflow/core/TextureCache.java
+++ b/src/org/sunflow/core/TextureCache.java
@@ -10,9 +10,11 @@
* texture might be used more than once in your scene.
*/
public final class TextureCache {
- private static HashMap<String, Texture> textures = new HashMap<String, Texture>();
+ // EP : Removed static to enable GC to free Texture memory
+ private HashMap<String, Texture> textures = new HashMap<String, Texture>();
- private TextureCache() {
+ // EP : Made texture cache local to SunFlow API
+ public TextureCache() {
}
/**
@@ -25,7 +27,8 @@
* @return texture object
* @see Texture
*/
- public synchronized static Texture getTexture(String filename, boolean isLinear) {
+ // EP : Removed static to enable GC to free Texture memory
+ public synchronized Texture getTexture(String filename, boolean isLinear) {
if (textures.containsKey(filename)) {
UI.printInfo(Module.TEX, "Using cached copy for file \"%s\" ...", filename);
return textures.get(filename);
@@ -40,7 +43,8 @@
* Flush all textures from the cache, this will cause them to be reloaded
* anew the next time they are accessed.
*/
- public synchronized static void flush() {
+ // EP : Removed static to enable GC to free Texture memory
+ public synchronized void flush() {
UI.printInfo(Module.TEX, "Flushing texture cache");
textures.clear();
}
--- a/src/org/sunflow/core/Texture.java
+++ b/src/org/sunflow/core/Texture.java
@@ -1,6 +1,8 @@
package org.sunflow.core;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
import org.sunflow.PluginRegistry;
import org.sunflow.image.Bitmap;
@@ -23,6 +25,8 @@
private boolean isLinear;
private Bitmap bitmap;
private int loaded;
+ // EP : Added bitmap transparency support
+ private boolean isTransparent;
/**
* Creates a new texture from the specfied file.
@@ -43,11 +47,38 @@
try {
UI.printInfo(Module.TEX, "Reading texture bitmap from: \"%s\" ...", filename);
BitmapReader reader = PluginRegistry.bitmapReaderPlugins.createObject(extension);
+ // EP : Tolerate no extension in URLs
+ if (reader == null) {
+ try {
+ // Choose a reader depending on the magic number of the file
+ URL url = new URL(filename);
+ InputStream in = url.openStream();
+ int firstByte = in.read();
+ int secondByte = in.read();
+ in.close();
+ reader = firstByte == 0xFF && secondByte == 0xD8
+ ? PluginRegistry.bitmapReaderPlugins.createObject("jpg")
+ : PluginRegistry.bitmapReaderPlugins.createObject("png");
+ } catch (IOException ex) {
+ // Don't try to search an other reader
+ }
+ }
+ // EP : End of modification
if (reader != null) {
bitmap = reader.load(filename, isLinear);
if (bitmap.getWidth() == 0 || bitmap.getHeight() == 0)
bitmap = null;
}
+ // EP : Check transparency
+ for (int x = 0; x < bitmap.getWidth(); x++) {
+ for (int y = 0; y < bitmap.getHeight(); y++) {
+ if (bitmap.readAlpha(x, y) < 1) {
+ this.isTransparent = true;
+ break;
+ }
+ }
+ }
+ // EP : End of modification
if (bitmap == null) {
UI.printError(Module.TEX, "Bitmap reading failed");
bitmap = new BitmapBlack();
@@ -105,7 +136,51 @@
c.madd(k11, c11);
return c;
}
-
+
+ // EP : Added bitmap transparency support
+ public Color getOpacity(float x, float y) {
+ Bitmap bitmap = getBitmap();
+ x = MathUtils.frac(x);
+ y = MathUtils.frac(y);
+ float dx = x * (bitmap.getWidth() - 1);
+ float dy = y * (bitmap.getHeight() - 1);
+ int ix0 = (int) dx;
+ int iy0 = (int) dy;
+ int ix1 = (ix0 + 1) % bitmap.getWidth();
+ int iy1 = (iy0 + 1) % bitmap.getHeight();
+ float u = dx - ix0;
+ float v = dy - iy0;
+ u = u * u * (3.0f - (2.0f * u));
+ v = v * v * (3.0f - (2.0f * v));
+ float k00 = (1.0f - u) * (1.0f - v);
+ float a00 = bitmap.readAlpha(ix0, iy0);
+ float k01 = (1.0f - u) * v;
+ float a01 = bitmap.readAlpha(ix0, iy1);
+ float k10 = u * (1.0f - v);
+ float a10 = bitmap.readAlpha(ix1, iy0);
+ float k11 = u * v;
+ float a11 = bitmap.readAlpha(ix1, iy1);
+ float transparency = k00 * a00 + k01 * a01 + k10 * a10 + k11 * a11;
+ if (transparency <= 0.9999) {
+ Color c00 = bitmap.readColor(ix0, iy0);
+ Color c01 = bitmap.readColor(ix0, iy1);
+ Color c10 = bitmap.readColor(ix1, iy0);
+ Color c11 = bitmap.readColor(ix1, iy1);
+ Color c = Color.mul(k00, c00);
+ c.madd(k01, c01);
+ c.madd(k10, c10);
+ c.madd(k11, c11);
+ return c.opposite().mul(transparency);
+ } else {
+ return Color.WHITE;
+ }
+ }
+
+ public boolean isTransparent() {
+ return this.isTransparent;
+ }
+ // EP : End of modification
+
public Vector3 getNormal(float x, float y, OrthoNormalBasis basis) {
float[] rgb = getPixel(x, y).getRGB();
return basis.transform(new Vector3(2 * rgb[0] - 1, 2 * rgb[1] - 1, 2 * rgb[2] - 1)).normalize();
--- a/src/org/sunflow/image/Color.java
+++ b/src/org/sunflow/image/Color.java
@@ -116,6 +116,12 @@
return r <= 0 && g <= 0 && b <= 0;
}
+ // EP : Added to manage white colors
+ public boolean isWhite() {
+ return r >= 1 && g >= 1 && b >= 1;
+ }
+ // EP : End of modification
+
public final float getLuminance() {
return (0.2989f * r) + (0.5866f * g) + (0.1145f * b);
}
--- a/src/org/sunflow/image/readers/BMPBitmapReader.java
+++ b/src/org/sunflow/image/readers/BMPBitmapReader.java
@@ -1,8 +1,11 @@
package org.sunflow.image.readers;
import java.awt.image.BufferedImage;
-import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import javax.imageio.ImageIO;
@@ -13,8 +16,25 @@
public class BMPBitmapReader implements BitmapReader {
public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
- // regular image, load using Java api - ignore alpha channel
- BufferedImage bi = ImageIO.read(new File(filename));
+ // EP : Try to read filename as an URL or as a file
+ InputStream f;
+ try {
+ // Let's try first to read filename as an URL
+ f = new URL(filename).openStream();
+ } catch (MalformedURLException ex) {
+ // Let's try to read filename as a file
+ f = new FileInputStream(filename);
+ }
+
+ BufferedImage bi;
+ try {
+ // regular image, load using Java api - ignore alpha channel
+ bi = ImageIO.read(f);
+ } finally {
+ f.close();
+ }
+ // EP : End of modification
+
int width = bi.getWidth();
int height = bi.getHeight();
byte[] pixels = new byte[3 * width * height];
--- a/src/org/sunflow/image/readers/HDRBitmapReader.java
+++ b/src/org/sunflow/image/readers/HDRBitmapReader.java
@@ -4,6 +4,8 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import org.sunflow.image.Bitmap;
import org.sunflow.image.BitmapReader;
@@ -11,8 +13,19 @@
public class HDRBitmapReader implements BitmapReader {
public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
- // load radiance rgbe file
- InputStream f = new BufferedInputStream(new FileInputStream(filename));
+ // EP : Try to read filename as an URL or as a file
+ InputStream f;
+ try {
+ // Let's try first to read filename as an URL
+ f = new URL(filename).openStream();
+ } catch (MalformedURLException ex) {
+ // Let's try to read filename as a file
+ f = new FileInputStream(filename);
+ }
+
+ f = new BufferedInputStream(f);
+ // End of modification
+
// parse header
boolean parseWidth = false, parseHeight = false;
int width = 0, height = 0;
--- a/src/org/sunflow/image/readers/IGIBitmapReader.java
+++ b/src/org/sunflow/image/readers/IGIBitmapReader.java
@@ -4,6 +4,8 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import org.sunflow.image.Bitmap;
import org.sunflow.image.BitmapReader;
@@ -15,7 +17,19 @@
*/
public class IGIBitmapReader implements BitmapReader {
public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
- InputStream stream = new BufferedInputStream(new FileInputStream(filename));
+ // EP : Try to read filename as an URL or as a file
+ InputStream stream;
+ try {
+ // Let's try first to read filename as an URL
+ stream = new URL(filename).openStream();
+ } catch (MalformedURLException ex) {
+ // Let's try to read filename as a file
+ stream = new FileInputStream(filename);
+ }
+
+ stream = new BufferedInputStream(stream);
+ // End of modification
+
// read header
int magic = read32i(stream);
int version = read32i(stream);
--- a/src/org/sunflow/image/readers/JPGBitmapReader.java
+++ b/src/org/sunflow/image/readers/JPGBitmapReader.java
@@ -1,8 +1,13 @@
package org.sunflow.image.readers;
+import java.awt.Graphics2D;
+import java.awt.Transparency;
import java.awt.image.BufferedImage;
-import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import javax.imageio.ImageIO;
@@ -13,14 +18,47 @@
public class JPGBitmapReader implements BitmapReader {
public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
- // regular image, load using Java api - ignore alpha channel
- BufferedImage bi = ImageIO.read(new File(filename));
+ // EP : Try to read filename as an URL or as a file
+ InputStream f;
+ try {
+ // Let's try first to read filename as an URL
+ f = new URL(filename).openStream();
+ } catch (MalformedURLException ex) {
+ // Let's try to read filename as a file
+ f = new FileInputStream(filename);
+ }
+
+ BufferedImage bi;
+ try {
+ // regular image, load using Java api - ignore alpha channel
+ bi = ImageIO.read(f);
+ } finally {
+ f.close();
+ }
+
+ if (bi.getType() != BufferedImage.TYPE_INT_RGB
+ && bi.getType() != BufferedImage.TYPE_INT_ARGB) {
+ // Transform as TYPE_INT_ARGB or TYPE_INT_RGB (much faster than calling image.getRGB())
+ BufferedImage tmp = new BufferedImage(bi.getWidth(), bi.getHeight(),
+ bi.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D)tmp.getGraphics();
+ g.drawImage(bi, null, 0, 0);
+ g.dispose();
+ bi = tmp;
+ }
+ // Retrieve image bits
+ int [] imageBits = (int [])bi.getRaster().getDataElements(0, 0, bi.getWidth(), bi.getHeight(), null);
+ // EP : End of modification
+
int width = bi.getWidth();
int height = bi.getHeight();
byte[] pixels = new byte[3 * width * height];
for (int y = 0, index = 0; y < height; y++) {
for (int x = 0; x < width; x++, index += 3) {
- int argb = bi.getRGB(x, height - 1 - y);
+ // EP : Retrieved image data with raster data
+ // int argb = bi.getRGB(x, height - 1 - y);
+ int argb = imageBits [x + (height - 1 - y) * width];
+ // EP : End of modification
pixels[index + 0] = (byte) (argb >> 16);
pixels[index + 1] = (byte) (argb >> 8);
pixels[index + 2] = (byte) argb;
--- a/src/org/sunflow/image/readers/PNGBitmapReader.java
+++ b/src/org/sunflow/image/readers/PNGBitmapReader.java
@@ -1,8 +1,13 @@
package org.sunflow.image.readers;
+import java.awt.Graphics2D;
+import java.awt.Transparency;
import java.awt.image.BufferedImage;
-import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import javax.imageio.ImageIO;
@@ -13,18 +18,54 @@
public class PNGBitmapReader implements BitmapReader {
public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
- // regular image, load using Java api
- BufferedImage bi = ImageIO.read(new File(filename));
+ // EP : Try to read filename as an URL or as a file
+ InputStream f;
+ try {
+ // Let's try first to read filename as an URL
+ f = new URL(filename).openStream();
+ } catch (MalformedURLException ex) {
+ // Let's try to read filename as a file
+ f = new FileInputStream(filename);
+ }
+
+ BufferedImage bi;
+ try {
+ // regular image, load using Java api
+ bi = ImageIO.read(f);
+ } finally {
+ f.close();
+ }
+
+ boolean opaque = bi.getTransparency() == Transparency.OPAQUE;
+ if (bi.getType() != BufferedImage.TYPE_INT_RGB
+ && bi.getType() != BufferedImage.TYPE_INT_ARGB) {
+ // Transform as TYPE_INT_ARGB or TYPE_INT_RGB (much faster than calling image.getRGB())
+ BufferedImage tmp = new BufferedImage(bi.getWidth(), bi.getHeight(),
+ opaque ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D)tmp.getGraphics();
+ g.drawImage(bi, null, 0, 0);
+ g.dispose();
+ bi = tmp;
+ }
+ // Retrieve image bits
+ int [] imageBits = (int [])bi.getRaster().getDataElements(0, 0, bi.getWidth(), bi.getHeight(), null);
+ // EP : End of modification
+
int width = bi.getWidth();
int height = bi.getHeight();
byte[] pixels = new byte[4 * width * height];
for (int y = 0, index = 0; y < height; y++) {
for (int x = 0; x < width; x++, index += 4) {
- int argb = bi.getRGB(x, height - 1 - y);
+ // EP : Retrieved image data with raster data
+ // int argb = bi.getRGB(x, height - 1 - y);
+ int argb = imageBits [x + (height - 1 - y) * width];
+ // EP : End of modification
pixels[index + 0] = (byte) (argb >> 16);
pixels[index + 1] = (byte) (argb >> 8);
pixels[index + 2] = (byte) argb;
- pixels[index + 3] = (byte) (argb >> 24);
+ // EP : Added opaque transparency
+ pixels[index + 3] = opaque ? (byte)0xFF : (byte) (argb >> 24);
+ // EP : End of modification
}
}
if (!isLinear) {
--- a/src/org/sunflow/image/readers/TGABitmapReader.java
+++ b/src/org/sunflow/image/readers/TGABitmapReader.java
@@ -4,6 +4,8 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import org.sunflow.image.Bitmap;
import org.sunflow.image.BitmapReader;
@@ -16,7 +18,19 @@
private static final int[] CHANNEL_INDEX = { 2, 1, 0, 3 };
public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
- InputStream f = new BufferedInputStream(new FileInputStream(filename));
+ // EP : Try to read filename as an URL or as a file
+ InputStream f;
+ try {
+ // Let's try first to read filename as an URL
+ f = new URL(filename).openStream();
+ } catch (MalformedURLException ex) {
+ // Let's try to read filename as a file
+ f = new FileInputStream(filename);
+ }
+
+ f = new BufferedInputStream(f);
+ // End of modification
+
byte[] read = new byte[4];
// read header
--- a/src/org/sunflow/PluginRegistry.java
+++ b/src/org/sunflow/PluginRegistry.java
@@ -309,6 +309,8 @@
bitmapReaderPlugins.registerPlugin("jpg", JPGBitmapReader.class);
bitmapReaderPlugins.registerPlugin("bmp", BMPBitmapReader.class);
bitmapReaderPlugins.registerPlugin("igi", IGIBitmapReader.class);
+ // EP : Added extension jpeg
+ bitmapReaderPlugins.registerPlugin("jpeg", JPGBitmapReader.class);
}
static {
--- a/src/org/sunflow/SunflowAPI.java
+++ b/src/org/sunflow/SunflowAPI.java
@@ -26,6 +26,7 @@
import org.sunflow.core.SceneParser;
import org.sunflow.core.Shader;
import org.sunflow.core.Tesselatable;
+import org.sunflow.core.TextureCache;
import org.sunflow.core.ParameterList.InterpolationType;
import org.sunflow.image.ColorFactory;
import org.sunflow.image.ColorFactory.ColorSpecificationException;
@@ -697,4 +698,12 @@
public void currentFrame(int currentFrame) {
this.currentFrame = currentFrame;
}
+
+ // EP : Made texture cache local to a SunFlow API instance
+ private TextureCache textureCache = new TextureCache();
+
+ public TextureCache getTextureCache() {
+ return this.textureCache;
+ }
+ // EP : End of modification
}
\ No newline at end of file
--- a/src/SunflowGUI.java
+++ b/src/SunflowGUI.java
@@ -43,7 +43,6 @@
import org.sunflow.RealtimeBenchmark;
import org.sunflow.SunflowAPI;
import org.sunflow.core.Display;
-import org.sunflow.core.TextureCache;
import org.sunflow.core.accel.KDTree;
import org.sunflow.core.display.FileDisplay;
import org.sunflow.core.display.FrameDisplay;
@@ -52,9 +51,9 @@
import org.sunflow.system.ImagePanel;
import org.sunflow.system.Timer;
import org.sunflow.system.UI;
-import org.sunflow.system.UserInterface;
import org.sunflow.system.UI.Module;
import org.sunflow.system.UI.PrintLevel;
+import org.sunflow.system.UserInterface;
@SuppressWarnings("serial")
public class SunflowGUI extends javax.swing.JFrame implements UserInterface {
@@ -1069,7 +1068,8 @@
}
private void textureCacheClearMenuItemActionPerformed(ActionEvent evt) {
- TextureCache.flush();
+ // EP : Made texture cache local to SunFlow API
+ api.getTextureCache().flush();
}
private void smallTrianglesMenuItemActionPerformed(ActionEvent evt) {