Description: Some changes for sweethome3d
Author: Emmanuel Puybaret, eTeks <info@eteks.com>

--- a/src/org/sunflow/core/accel/BoundingIntervalHierarchy.java
+++ b/src/org/sunflow/core/accel/BoundingIntervalHierarchy.java
@@ -256,7 +256,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 +275,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;
--- 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)
--- a/src/org/sunflow/core/LightServer.java
+++ b/src/org/sunflow/core/LightServer.java
@@ -30,6 +30,7 @@
     private GIEngine giEngine;
     private int photonCounter;
 
+
     LightServer(Scene scene) {
         this.scene = scene;
         lights = new LightSource[0];
@@ -143,7 +144,8 @@
                         synchronized (LightServer.this) {
                             UI.taskUpdate(photonCounter);
                             photonCounter++;
-                            if (UI.taskCanceled())
+                            // EP : Manage renderer stop with interruptions
+                            if (Thread.currentThread().isInterrupted())
                                 return;
                         }
 
@@ -176,18 +178,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());
--- 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/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);
--- 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/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
@@ -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 TexturedDiffuseShader extends DiffuseShader {
@@ -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/TexturedPhongShader.java
+++ b/src/org/sunflow/core/shader/TexturedPhongShader.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 TexturedPhongShader extends PhongShader {
@@ -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/TexturedShinyDiffuseShader.java
+++ b/src/org/sunflow/core/shader/TexturedShinyDiffuseShader.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 TexturedShinyDiffuseShader extends ShinyDiffuseShader {
@@ -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/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);
     }
 
--- 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);
--- a/src/org/sunflow/core/TextureCache.java
+++ b/src/org/sunflow/core/TextureCache.java
@@ -12,7 +12,8 @@
 public final class TextureCache {
     private static HashMap<String, Texture> textures = new HashMap<String, Texture>();
 
-    private TextureCache() {
+    // EP : Made texture cache local to SunFlow API
+    public TextureCache() {
     }
 
     /**
--- 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;
@@ -43,6 +45,23 @@
         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)
--- 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,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 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();
+        }
+        // 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/PNGBitmapReader.java
+++ b/src/org/sunflow/image/readers/PNGBitmapReader.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 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();
+        }
+        // EP : End of modification
+        
         int width = bi.getWidth();
         int height = bi.getHeight();
         byte[] pixels = new byte[4 * width * height];
--- 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
