diff --git a/.idea/modules.xml b/.idea/modules.xml index 0fe66a1..987850f 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + diff --git a/src/main/java/eu/mhsl/minecraft/pixelpic/Main.java b/src/main/java/eu/mhsl/minecraft/pixelpic/Main.java index 7a3d31e..0e04da3 100644 --- a/src/main/java/eu/mhsl/minecraft/pixelpic/Main.java +++ b/src/main/java/eu/mhsl/minecraft/pixelpic/Main.java @@ -6,9 +6,16 @@ import eu.mhsl.minecraft.pixelpic.render.render.Renderer; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; -import java.util.Objects; +import java.io.*; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; public final class Main extends JavaPlugin { private static Main instance; @@ -17,10 +24,27 @@ public final class Main extends JavaPlugin { @Override public void onEnable() { instance = this; + extractJsonResources(); + Bukkit.getPluginCommand("pixelPic").setExecutor(new PixelPicCommand()); Bukkit.getPluginCommand("test").setExecutor((sender, command, label, args) -> { + Material.getMaterial("acacia_button"); Bukkit.broadcast(Component.text(Material.STONE.getBlockTranslationKey().replace("block.minecraft.", ""))); + + if(!(sender instanceof Player player)) + throw new IllegalStateException("Dieser Command kann nur von einem Spieler ausgeführt werden!"); + + File blockDir = new File(getDataFolder(), "models/block"); + for (File file : blockDir.listFiles()) { + String blockName = file.getName().substring(0, file.getName().lastIndexOf('.')); + Material material = Material.getMaterial(blockName.toUpperCase()); + System.out.println(material); + if(material == null) { + System.out.println(blockName); + } + } + return true; }); } @@ -29,6 +53,49 @@ public final class Main extends JavaPlugin { public void onDisable() { } + public void extractJsonResources() { + String resourcePath = "models/block/"; // Pfad im JAR + File outputDir = new File(getDataFolder(), resourcePath); + if (outputDir.exists()) return; + outputDir.mkdirs(); + + try { + URL jarUrl = getClass().getProtectionDomain().getCodeSource().getLocation(); + File jarFile = new File(jarUrl.toURI()); + + try (JarFile jar = new JarFile(jarFile)) { + Enumeration entries = jar.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + + // Nur JSON-Dateien im gewünschten Ordner + if (entryName.startsWith(resourcePath) && entryName.endsWith(".json")) { + InputStream in = getResource(entryName); + if (in == null) continue; + + File outFile = new File(getDataFolder(), entryName); + outFile.getParentFile().mkdirs(); // Ordnerstruktur sicherstellen + + try (OutputStream out = new FileOutputStream(outFile)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + System.out.println("Extrahiert: " + entryName); + } + + in.close(); + } + } + } + } catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + } + public Renderer getScreenRenderer() { if(this.screenRenderer == null) this.screenRenderer = new DefaultScreenRenderer(); return this.screenRenderer; diff --git a/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/AdvancedRaytracer.java b/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/AdvancedRaytracer.java new file mode 100644 index 0000000..b58a4e4 --- /dev/null +++ b/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/AdvancedRaytracer.java @@ -0,0 +1,151 @@ +package eu.mhsl.minecraft.pixelpic.render.raytrace; + +import eu.mhsl.minecraft.pixelpic.render.model.Model; +import eu.mhsl.minecraft.pixelpic.render.registry.AdvancedModelRegistry; +import eu.mhsl.minecraft.pixelpic.render.registry.ModelRegistry; +import eu.mhsl.minecraft.pixelpic.render.util.BlockRaytracer; +import eu.mhsl.minecraft.pixelpic.render.util.Intersection; +import eu.mhsl.minecraft.pixelpic.render.util.MathUtil; +import org.bukkit.Color; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.util.Vector; + +public class AdvancedRaytracer implements Raytracer { + private final int maxDistance; + private final int reflectionDepth; + + private final AdvancedModelRegistry textureRegistry; + private Block reflectedBlock; + + public AdvancedRaytracer() { + this(300, 10); + } + + public AdvancedRaytracer(int maxDistance, int reflectionDepth) { + this.maxDistance = maxDistance; + this.reflectionDepth = reflectionDepth; + + this.textureRegistry = new AdvancedModelRegistry(); + this.textureRegistry.initialize(); + + this.reflectedBlock = null; + } + + @Override + public int trace(World world, Vector point, Vector direction) { + return trace(world, point, direction, reflectionDepth); + } + + private int trace(World world, Vector point, Vector direction, int reflectionDepth) { + Location loc = point.toLocation(world); + loc.setDirection(direction); + BlockRaytracer iterator = new BlockRaytracer(loc); + int baseColor = Color.fromRGB(65, 89, 252).asRGB(); + Vector finalIntersection = null; + + int reflectionColor = 0; + double reflectionFactor = 0; + boolean reflected = false; + + Vector transparencyStart = null; + int transparencyColor = 0; + double transparencyFactor = 0; + + Material occlusionMaterial = null; + BlockData occlusionData = null; + + for (int i = 0; i < maxDistance; i++) { + if (!iterator.hasNext()) break; + Block block = iterator.next(); + if (reflectedBlock != null && reflectedBlock.equals(block)) continue; + reflectedBlock = null; + + Material material = block.getType(); + if (material == Material.AIR) { + occlusionMaterial = null; + occlusionData = null; + continue; + } + + Model textureModel = textureRegistry.getModel(block.getType(), block.getBlockData(), block.getTemperature(), block.getHumidity()); + Intersection currentIntersection = Intersection.of( + MathUtil.toVector(iterator.getIntersectionFace()), + i == 0 ? point : iterator.getIntersectionPoint(), + direction + ); + Intersection newIntersection = textureModel.intersect(block, currentIntersection); + + if (newIntersection == null) continue; + + int color = newIntersection.getColor(); + + if (!reflected && textureModel.getReflectionFactor() > 0 && reflectionDepth > 0 && (color >> 24) != 0) { + reflectedBlock = block; + reflectionColor = trace( + world, + newIntersection.getPoint(), + MathUtil.reflectVector( + point, + direction, + newIntersection.getPoint(), + newIntersection.getNormal() + ), + reflectionDepth - 1 + ); + reflectionFactor = textureModel.getReflectionFactor(); + reflected = true; + } + + if (transparencyStart == null && textureModel.getTransparencyFactor() > 0) { + transparencyStart = newIntersection.getPoint(); + transparencyColor = newIntersection.getColor(); + transparencyFactor = textureModel.getTransparencyFactor(); + } + + if (textureModel.isOccluding()) { + BlockData data = block.getBlockData(); + + if (material == occlusionMaterial && data.equals(occlusionData)) continue; + + occlusionMaterial = material; + occlusionData = data; + } else { + occlusionMaterial = null; + occlusionData = null; + } + + if (transparencyStart != null && textureModel.getTransparencyFactor() > 0) continue; + if ((color >> 24) == 0) continue; + + baseColor = color; + finalIntersection = newIntersection.getPoint(); + break; + } + + if (transparencyStart != null) { + baseColor = MathUtil.weightedColorSum( + baseColor, + transparencyColor, + transparencyFactor, + (1 + - transparencyFactor) + * (1 + transparencyStart.distance(finalIntersection == null ? transparencyStart : finalIntersection) + / 5.0)); + } + if (reflected) { + baseColor = MathUtil.weightedColorSum( + baseColor, + reflectionColor, + 1 - reflectionFactor, + reflectionFactor + ); + } + + return baseColor & 0xFFFFFF; + } +} diff --git a/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/DefaultRaytracer.java b/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/DefaultRaytracer.java index 621e0a0..7851007 100644 --- a/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/DefaultRaytracer.java +++ b/src/main/java/eu/mhsl/minecraft/pixelpic/render/raytrace/DefaultRaytracer.java @@ -10,6 +10,7 @@ import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.Biome; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.util.Vector; @@ -71,6 +72,7 @@ public class DefaultRaytracer implements Raytracer { continue; } + Biome biome = block.getBiome(); Model textureModel = textureRegistry.getModel(block); Intersection currentIntersection = Intersection.of( MathUtil.toVector(iterator.getIntersectionFace()), diff --git a/src/main/java/eu/mhsl/minecraft/pixelpic/render/registry/AdvancedModelRegistry.java b/src/main/java/eu/mhsl/minecraft/pixelpic/render/registry/AdvancedModelRegistry.java index a8d8004..80f9218 100644 --- a/src/main/java/eu/mhsl/minecraft/pixelpic/render/registry/AdvancedModelRegistry.java +++ b/src/main/java/eu/mhsl/minecraft/pixelpic/render/registry/AdvancedModelRegistry.java @@ -1,5 +1,7 @@ package eu.mhsl.minecraft.pixelpic.render.registry; +import com.google.gson.Gson; +import eu.mhsl.minecraft.pixelpic.Main; import eu.mhsl.minecraft.pixelpic.render.model.AbstractModel; import eu.mhsl.minecraft.pixelpic.render.model.Model; import org.bukkit.Color; @@ -9,28 +11,47 @@ import org.bukkit.block.data.BlockData; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; import java.net.URL; import java.util.*; import static eu.mhsl.minecraft.pixelpic.render.registry.DefaultModelRegistry.TEXTURE_SIZE; public class AdvancedModelRegistry implements ModelRegistry { + private final Gson gson = new Gson(); + private final Map> modelMap = new HashMap<>(); + private final Set tintedBlocks = Set.of("grass", "grass_block", "leaves", "oak_leaves", "water", "vine", "sugar_cane"); + + public record BlockInfo(String parent, BlockTextures textures){} + public record BlockTextures( + String texture, + String bottom, + String top, + String all, + String particle, + String end, + String side, + String cross, + String rail, + String overlay + ){} @Override public void initialize() { -// registerModel(Material.STONE, AbstractModel.Builder.createSimple(getTextureArray(Material.STONE.getBlockTranslationKey().replace("block.minecraft.", ""))).build()); - for (Material currentMaterial : Material.values()) { - if(!currentMaterial.isBlock()) continue; - List blockTextures = getBlockTextures(currentMaterial); - for (String blockTexture : blockTextures) { - try { - registerModel(currentMaterial, AbstractModel.Builder.createSimple(getTextureArray(blockTexture)).build()); - } catch (Exception ignored) { } - } + System.out.println(modelMap); + + File blockDir = new File(Main.getInstance().getDataFolder(), "models/block"); + for (File file : Objects.requireNonNull(blockDir.listFiles())) { + addModelFromFile(file); } + + try { + registerModel(Material.LAVA, AbstractModel.Builder.createSimple(getTextureArray("lava_still")) + .transparency(0.15).reflection(0.05).occlusion().build()); + registerModel(Material.WATER, AbstractModel.Builder.createSimple(getTextureArray("water_still")) + .transparency(0.60).reflection(0.1).occlusion().build()); + } catch (Exception ignored) { } } @Override @@ -40,6 +61,10 @@ public class AdvancedModelRegistry implements ModelRegistry { @Override public Model getModel(Material material, BlockData blockData) { + return getModel(material, blockData, 0.8, 0.4); + } + + public Model getModel(Material material, BlockData blockData, double temperature, double humidity) { return modelMap.computeIfAbsent(material, key -> new HashMap<>()).getOrDefault(blockData, blockData == null ? getDefaultModel() : modelMap.get(material).getOrDefault(null, getDefaultModel())); @@ -55,39 +80,54 @@ public class AdvancedModelRegistry implements ModelRegistry { .put(null, blockModel); } - private List getBlockTextures(Material material) { - if(!material.isBlock()) return new ArrayList<>(); - String blockName = material.name().toLowerCase(); - try { - if(!Arrays.deepEquals(getTextureArray(blockName), new int[0][0])) { - return List.of(blockName); + private void addModelFromFile(File file) { + String blockName = file.getName().substring(0, file.getName().lastIndexOf('.')); + Material material = Material.getMaterial(blockName.toUpperCase()); + if(material == null) return; + + Model model = getModelFromFile(file); + if(model == null) return; + + registerModel(material, model); + } + + private Model getModelFromFile(File file) { + try (Reader reader = new FileReader(file)) { + BlockInfo blockInfo = gson.fromJson(reader, BlockInfo.class); + + if(blockInfo.textures.all != null) { + return AbstractModel.Builder.createSimple( + getTextureArray(blockInfo.textures.all.substring(blockInfo.textures.all.lastIndexOf('/') + 1)) + ).build(); + } + if(blockInfo.textures.cross != null) { + return AbstractModel.Builder.createCross( + getTextureArray(blockInfo.textures.cross.substring(blockInfo.textures.cross.lastIndexOf('/') + 1)) + ).build(); + } + if(blockInfo.textures.side != null && blockInfo.textures.bottom != null && blockInfo.textures.top != null) { + return AbstractModel.Builder.createMulti( + getTextureArray(blockInfo.textures.top.substring(blockInfo.textures.top.lastIndexOf('/') + 1)), + getTextureArray(blockInfo.textures.side.substring(blockInfo.textures.side.lastIndexOf('/') + 1)), + getTextureArray(blockInfo.textures.bottom.substring(blockInfo.textures.bottom.lastIndexOf('/') + 1)) + ).build(); + } + if(blockInfo.textures.side != null && blockInfo.textures.end != null) { + return AbstractModel.Builder.createMulti( + getTextureArray(blockInfo.textures.end.substring(blockInfo.textures.end.lastIndexOf('/') + 1)), + getTextureArray(blockInfo.textures.side.substring(blockInfo.textures.side.lastIndexOf('/') + 1)), + getTextureArray(blockInfo.textures.end.substring(blockInfo.textures.end.lastIndexOf('/') + 1)) + ).build(); } } catch (Exception e) { - String[] possibleVariants = { - blockName + "_top", - blockName + "_side", - blockName + "_bottom", - blockName + "_front", - blockName + "_back", - blockName + "_left", - blockName + "_right" - }; - List results = new ArrayList<>(); - for (String variant : possibleVariants) { - try { - if(!Arrays.deepEquals(getTextureArray(variant), new int[0][0])) { - results.add(variant); - } - } catch (Exception ignored) { } - } - return results; + System.out.println(e.getMessage()); } - return new ArrayList<>(); + return null; } private int[][] getTextureArray(String textureName) { int[][] texture = new int[TEXTURE_SIZE][TEXTURE_SIZE]; - BufferedImage img = null; + BufferedImage img; URL url = this.getClass().getClassLoader().getResource(String.format("textures/block/%s.png", textureName)); if (url == null) { throw new RuntimeException("Block Texture Resource not found."); @@ -100,10 +140,18 @@ public class AdvancedModelRegistry implements ModelRegistry { for (int pixelY = 0; pixelY < TEXTURE_SIZE; pixelY++) { for (int pixelX = 0; pixelX < TEXTURE_SIZE; pixelX++) { - texture[pixelY][pixelX] = img.getRGB(pixelX, pixelY); + texture[TEXTURE_SIZE - 1 - pixelY][TEXTURE_SIZE - 1 - pixelX] = img.getRGB(pixelX, pixelY); } } return texture; } + + private int tintPixel(int baseColor, int tintColor) { + int a = (baseColor >> 24) & 0xFF; + int r = ((baseColor >> 16) & 0xFF) * ((tintColor >> 16) & 0xFF) / 255; + int g = ((baseColor >> 8) & 0xFF) * ((tintColor >> 8) & 0xFF) / 255; + int b = (baseColor & 0xFF) * (tintColor & 0xFF) / 255; + return (a << 24) | (r << 16) | (g << 8) | b; + } } diff --git a/src/main/resources/colormap/foliage.png b/src/main/resources/colormap/foliage.png new file mode 100644 index 0000000..d58fce2 Binary files /dev/null and b/src/main/resources/colormap/foliage.png differ diff --git a/src/main/resources/colormap/grass.png b/src/main/resources/colormap/grass.png new file mode 100644 index 0000000..5b94654 Binary files /dev/null and b/src/main/resources/colormap/grass.png differ