diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java index a9f59a6844..2f104c3148 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java @@ -537,6 +537,9 @@ public synchronized void loadScene(RenderContext context, String sceneName, Task // Load the configured skymap file. sky.reloadSkymap(context); + // Load the configured custom texture + sun.loadCustomTextures(context); + loadedWorld = EmptyWorld.INSTANCE; if (!worldPath.isEmpty()) { File worldDirectory = new File(worldPath); diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/sky/Sun.java b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/Sun.java index 9458b57eb7..1e425b80ba 100644 --- a/chunky/src/java/se/llbit/chunky/renderer/scene/sky/Sun.java +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/Sun.java @@ -22,8 +22,9 @@ import org.apache.commons.math3.util.FastMath; import se.llbit.chunky.renderer.Refreshable; +import se.llbit.chunky.renderer.SceneIOProvider; import se.llbit.chunky.renderer.scene.Scene; -import se.llbit.chunky.resources.Texture; +import se.llbit.chunky.renderer.scene.sky.celestialbodies.CelestialBodyType; import se.llbit.json.JsonObject; import se.llbit.math.QuickMath; import se.llbit.math.Ray; @@ -116,11 +117,6 @@ public class Sun implements JsonSerializable { private static Vector3 D = new Vector3(); private static Vector3 E = new Vector3(); - /** - * Sun texture - */ - public static Texture texture = new Texture(); - static { A.x = mdx[0][0] * turb + mdx[0][1]; B.x = mdx[1][0] * turb + mdx[1][1]; @@ -150,6 +146,8 @@ public class Sun implements JsonSerializable { private final Refreshable scene; + private CelestialBodyType type = CelestialBodyType.DEFAULT; + /** * Sun radius */ @@ -236,6 +234,7 @@ public void set(Sun other) { radius = other.radius; enableTextureModification = other.enableTextureModification; luminosityPdf = other.luminosityPdf; + type = other.type; importanceSampleRadius = other.importanceSampleRadius; importanceSampleChance = other.importanceSampleChance; initSun(); @@ -328,7 +327,7 @@ public boolean intersect(Ray ray) { if (a >= 0 && a < width2) { double b = Math.PI / 2 - FastMath.acos(ray.d.dot(sv)) + width; if (b >= 0 && b < width2) { - texture.getColor(a / width2, b / width2, ray.color); + type.getTexture().getColor(a / width2, b / width2, ray.color); ray.color.x *= apparentTextureBrightness.x * 10; ray.color.y *= apparentTextureBrightness.y * 10; ray.color.z *= apparentTextureBrightness.z * 10; @@ -354,7 +353,7 @@ public boolean intersectDiffuse(Ray ray) { if (a >= 0 && a < width2) { double b = Math.PI / 2 - FastMath.acos(ray.d.dot(sv)) + width; if (b >= 0 && b < width2) { - texture.getColor(a / width2, b / width2, ray.color); + type.getTexture().getColor(a / width2, b / width2, ray.color); ray.color.x *= color.x * 10; ray.color.y *= color.y * 10; ray.color.z *= color.z * 10; @@ -516,6 +515,7 @@ public void getRandomSunDirection(Ray reflected, Random random) { importanceSamplingObj.add("radius", importanceSampleRadius); sun.add("importanceSampling", importanceSamplingObj); sun.add("drawTexture", drawTexture); + type.appendToConfig(sun); return sun; } @@ -550,9 +550,15 @@ public void importFromJson(JsonObject json) { drawTexture = json.get("drawTexture").boolValue(drawTexture); + type = CelestialBodyType.newFromJson(json); + initSun(); } + public void loadCustomTextures(SceneIOProvider ioContext) { + type.loadCustomTextures(ioContext); + } + /** * @return sun color */ @@ -579,6 +585,14 @@ public boolean drawTexture() { return drawTexture; } + public CelestialBodyType getType() { + return type; + } + + public void setType(CelestialBodyType type) { + this.type = type; + } + public double getImportanceSampleChance() { return importanceSampleChance; } public void setImportanceSampleChance(double d) { diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/CelestialBodyType.java b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/CelestialBodyType.java new file mode 100644 index 0000000000..304fecc353 --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/CelestialBodyType.java @@ -0,0 +1,55 @@ +package se.llbit.chunky.renderer.scene.sky.celestialbodies; + +import se.llbit.chunky.renderer.SceneIOProvider; +import se.llbit.chunky.resources.Texture; +import se.llbit.json.JsonObject; +import se.llbit.log.Log; +import se.llbit.util.Registerable; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +public abstract class CelestialBodyType implements Registerable { + private static final String CONFIG_TYPE_KEY = "celestialBodyType"; + + public final static CelestialBodyType DEFAULT = new Sun(); + + public static final Map> TYPES = new HashMap<>(3); + static { + TYPES.put(Sun.ID, Sun::new); + TYPES.put(Moon.ID, Moon::new); + TYPES.put(Custom.ID, Custom::new); + } + + public static CelestialBodyType newFromJson(JsonObject obj) { + String typeStr = obj.get(CONFIG_TYPE_KEY).asString(Sun.ID); + Supplier create = TYPES.get(typeStr); + if(create == null) { + Log.warnf("Unknown celestial body type \"%s\"", typeStr); + return DEFAULT; + } + CelestialBodyType type = create.get(); + type.importFromJson(obj); + return type; + } + + protected void importFromJson(JsonObject obj) {} + + public void appendToConfig(JsonObject obj) { + obj.add(CONFIG_TYPE_KEY, getId()); + } + + /** + * will be called when a scene is loaded to load associated custom textures + */ + public void loadCustomTextures(SceneIOProvider ioContext) { + } + + public abstract Texture getTexture(); + + @Override + public String getDescription() { + return getName(); + } +} diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Custom.java b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Custom.java new file mode 100644 index 0000000000..8b003fa068 --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Custom.java @@ -0,0 +1,74 @@ +package se.llbit.chunky.renderer.scene.sky.celestialbodies; + +import se.llbit.chunky.renderer.SceneIOProvider; +import se.llbit.chunky.resources.Texture; +import se.llbit.json.JsonObject; +import se.llbit.log.Log; +import se.llbit.resources.ImageLoader; + +import java.io.File; +import java.io.IOException; + +public class Custom extends CelestialBodyType { + public static final String ID = "CUSTOM"; + + @Override + public String getId() { + return ID; + } + + @Override + public String getName() { + return "Custom"; + } + + @Override + public String getDescription() { + return "Custom celestial body texture"; + } + + private final Texture texture = new Texture(); + private String fileName; + + public Custom() { + texture.setTexture(Sun.texture); + } + + @Override + protected void importFromJson(JsonObject obj) { + fileName = obj.get("customTextureFile").asString(null); + } + + @Override + public void appendToConfig(JsonObject obj) { + super.appendToConfig(obj); + obj.add("customTextureFile", fileName); + } + + public void loadCustomTextures(SceneIOProvider ioContext) { + if (fileName != null) { + try { + setFile(ioContext.resolveLinkedFile(fileName)); + } catch (IOException ex) { + Log.error("Failed to find custom skymap file: " + fileName); + } + } + } + + @Override + public Texture getTexture() { + return texture; + } + + public String getFileName() { + return fileName; + } + + public void setFile(File file) { + try { + texture.setTexture(ImageLoader.read(file)); + } catch (IOException ex) { + Log.error("Failed to load custom skymap: " + file, ex); + } + } +} diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Moon.java b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Moon.java new file mode 100644 index 0000000000..52392e53dc --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Moon.java @@ -0,0 +1,89 @@ +package se.llbit.chunky.renderer.scene.sky.celestialbodies; + +import se.llbit.chunky.resources.BitmapImage; +import se.llbit.chunky.resources.Texture; +import se.llbit.json.JsonObject; +import se.llbit.log.Log; + +public class Moon extends CelestialBodyType { + public static final String ID = "MOON"; + + @Override + public String getId() { + return ID; + } + + @Override + public String getName() { + return "Moon"; + } + + public enum Phase { + NEW_MOON(0, 1), + WAXING_CRESCENT(1, 1), + FIRST_QUARTER(2, 1), + WAXING_GIBBOUS(3, 1), + FULL_MOON(0, 0), + WANING_GIBBOUS(1, 0), + LAST_QUARTER(2, 0), + WANING_CRESCENT(3, 0); + + final byte textureAtlasPosX, textureAtlasPosY; + + Phase(int x, int y) { + textureAtlasPosX = (byte) x; + textureAtlasPosY = (byte) y; + } + } + + public static final Texture textureAtlas = new Texture(); + private final Texture texture = new Texture(); + private Phase phase; + + public Moon() { + setPhase(Phase.FULL_MOON); + } + + @Override + protected void importFromJson(JsonObject obj) { + setPhase(Phase.valueOf(obj.get("moonPhase").asString(Phase.FULL_MOON.toString()))); + } + + @Override + public void appendToConfig(JsonObject obj) { + super.appendToConfig(obj); + obj.add("moonPhase", phase.name()); + } + + @Override + public Texture getTexture() { + return texture; + } + + public Phase getPhase() { + return phase; + } + + public void setPhase(Phase phase) { + this.phase = phase; + + if (textureAtlas.getWidth() == textureAtlas.getHeight()) { + // only has 1 phase (phases atlas not found, fallback to default moon texture) + Log.info("Moon texture did not contain multiple phases"); + texture.setTexture(textureAtlas); + return; + } + + int textureSize = textureAtlas.getHeight() / 2; + BitmapImage phaseTexture = new BitmapImage(textureSize, textureSize); + phaseTexture.blit( + textureAtlas.getBitmap(), + 0, 0, + phase.textureAtlasPosX * textureSize, + phase.textureAtlasPosY * textureSize, + (phase.textureAtlasPosX + 1) * textureSize, + (phase.textureAtlasPosY + 1) * textureSize + ); + texture.setTexture(phaseTexture.rotated270()); + } +} diff --git a/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Sun.java b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Sun.java new file mode 100644 index 0000000000..d43d204287 --- /dev/null +++ b/chunky/src/java/se/llbit/chunky/renderer/scene/sky/celestialbodies/Sun.java @@ -0,0 +1,24 @@ +package se.llbit.chunky.renderer.scene.sky.celestialbodies; + +import se.llbit.chunky.resources.Texture; + +public class Sun extends CelestialBodyType { + public static final String ID = "SUN"; + + @Override + public String getId() { + return ID; + } + + @Override + public String getName() { + return "Sun"; + } + + public static final Texture texture = new Texture(); + + @Override + public Texture getTexture() { + return texture; + } +} diff --git a/chunky/src/java/se/llbit/chunky/resources/TexturePackLoader.java b/chunky/src/java/se/llbit/chunky/resources/TexturePackLoader.java index f772e33ca5..f460234a14 100644 --- a/chunky/src/java/se/llbit/chunky/resources/TexturePackLoader.java +++ b/chunky/src/java/se/llbit/chunky/resources/TexturePackLoader.java @@ -17,7 +17,8 @@ package se.llbit.chunky.resources; import se.llbit.chunky.renderer.scene.PlayerModel; -import se.llbit.chunky.renderer.scene.sky.Sun; +import se.llbit.chunky.renderer.scene.sky.celestialbodies.Moon; +import se.llbit.chunky.renderer.scene.sky.celestialbodies.Sun; import se.llbit.chunky.resources.texturepack.*; import java.lang.reflect.Field; @@ -100,9 +101,15 @@ public class TexturePackLoader { Texture.largeTrappedChestBottomLeft, Texture.largeTrappedChestBottomRight, Texture.largeTrappedChestBackLeft, Texture.largeTrappedChestBackRight))); ALL_TEXTURES.put("sun", new AlternateTextures( - new SimpleTexture("assets/minecraft/textures/environment/sun", Sun.texture),// MC 1.6 - new SimpleTexture("environment/sun", Sun.texture),// MC 1.5 + new SimpleTexture("assets/minecraft/textures/environment/sun", Sun.texture), // MC 1.6 + new SimpleTexture("environment/sun", Sun.texture), // MC 1.5 new SimpleTexture("terrain/sun", Sun.texture))); + ALL_TEXTURES.put("moon", new AlternateTextures( + new SimpleTexture("assets/minecraft/textures/environment/moon_phases", Moon.textureAtlas), // MC 1.6 + new SimpleTexture("environment/moon_phases", Moon.textureAtlas), // MC 1.5 + new SimpleTexture("terrain/moon_phases", Moon.textureAtlas), // fallback to non-phase texture + new SimpleTexture("environment/moon", Moon.textureAtlas), // MC 1.5 + new SimpleTexture("terrain/moon", Moon.textureAtlas))); // fallback to non-phase texture ALL_TEXTURES.put("clouds", new AlternateTextures( new CloudsTexture("assets/minecraft/textures/environment/clouds"), // MC 1.6