added advanced texture support

This commit is contained in:
Lars Neuhaus 2025-03-22 17:28:43 +01:00
parent ab97315910
commit 2d4f820f04
7 changed files with 308 additions and 39 deletions

1
.idea/modules.xml generated
View File

@ -3,6 +3,7 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/PixelPic.main.iml" filepath="$PROJECT_DIR$/.idea/modules/PixelPic.main.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/PixelPic.main.iml" filepath="$PROJECT_DIR$/.idea/modules/PixelPic.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/PixelPics.PixelPic.main.iml" filepath="$PROJECT_DIR$/.idea/modules/PixelPics.PixelPic.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/eu.mhsl.minecraft.pixelpic.PixelPic.main.iml" filepath="$PROJECT_DIR$/.idea/modules/eu.mhsl.minecraft.pixelpic.PixelPic.main.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/eu.mhsl.minecraft.pixelpic.PixelPic.main.iml" filepath="$PROJECT_DIR$/.idea/modules/eu.mhsl.minecraft.pixelpic.PixelPic.main.iml" />
</modules> </modules>
</component> </component>

View File

@ -6,9 +6,16 @@ import eu.mhsl.minecraft.pixelpic.render.render.Renderer;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin; 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 { public final class Main extends JavaPlugin {
private static Main instance; private static Main instance;
@ -17,10 +24,27 @@ public final class Main extends JavaPlugin {
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
extractJsonResources();
Bukkit.getPluginCommand("pixelPic").setExecutor(new PixelPicCommand()); Bukkit.getPluginCommand("pixelPic").setExecutor(new PixelPicCommand());
Bukkit.getPluginCommand("test").setExecutor((sender, command, label, args) -> { Bukkit.getPluginCommand("test").setExecutor((sender, command, label, args) -> {
Material.getMaterial("acacia_button");
Bukkit.broadcast(Component.text(Material.STONE.getBlockTranslationKey().replace("block.minecraft.", ""))); 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; return true;
}); });
} }
@ -29,6 +53,49 @@ public final class Main extends JavaPlugin {
public void onDisable() { 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<JarEntry> 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() { public Renderer getScreenRenderer() {
if(this.screenRenderer == null) this.screenRenderer = new DefaultScreenRenderer(); if(this.screenRenderer == null) this.screenRenderer = new DefaultScreenRenderer();
return this.screenRenderer; return this.screenRenderer;

View File

@ -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;
}
}

View File

@ -10,6 +10,7 @@ import org.bukkit.Color;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@ -71,6 +72,7 @@ public class DefaultRaytracer implements Raytracer {
continue; continue;
} }
Biome biome = block.getBiome();
Model textureModel = textureRegistry.getModel(block); Model textureModel = textureRegistry.getModel(block);
Intersection currentIntersection = Intersection.of( Intersection currentIntersection = Intersection.of(
MathUtil.toVector(iterator.getIntersectionFace()), MathUtil.toVector(iterator.getIntersectionFace()),

View File

@ -1,5 +1,7 @@
package eu.mhsl.minecraft.pixelpic.render.registry; 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.AbstractModel;
import eu.mhsl.minecraft.pixelpic.render.model.Model; import eu.mhsl.minecraft.pixelpic.render.model.Model;
import org.bukkit.Color; import org.bukkit.Color;
@ -9,29 +11,48 @@ import org.bukkit.block.data.BlockData;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.*;
import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
import static eu.mhsl.minecraft.pixelpic.render.registry.DefaultModelRegistry.TEXTURE_SIZE; import static eu.mhsl.minecraft.pixelpic.render.registry.DefaultModelRegistry.TEXTURE_SIZE;
public class AdvancedModelRegistry implements ModelRegistry { public class AdvancedModelRegistry implements ModelRegistry {
private final Gson gson = new Gson();
private final Map<Material, Map<BlockData, Model>> modelMap = new HashMap<>(); private final Map<Material, Map<BlockData, Model>> modelMap = new HashMap<>();
private final Set<String> 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 @Override
public void initialize() { public void initialize() {
// registerModel(Material.STONE, AbstractModel.Builder.createSimple(getTextureArray(Material.STONE.getBlockTranslationKey().replace("block.minecraft.", ""))).build()); System.out.println(modelMap);
for (Material currentMaterial : Material.values()) {
if(!currentMaterial.isBlock()) continue; File blockDir = new File(Main.getInstance().getDataFolder(), "models/block");
List<String> blockTextures = getBlockTextures(currentMaterial); for (File file : Objects.requireNonNull(blockDir.listFiles())) {
for (String blockTexture : blockTextures) { addModelFromFile(file);
}
try { try {
registerModel(currentMaterial, AbstractModel.Builder.createSimple(getTextureArray(blockTexture)).build()); 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) { } } catch (Exception ignored) { }
} }
}
}
@Override @Override
public Model getModel(Block block) { public Model getModel(Block block) {
@ -40,6 +61,10 @@ public class AdvancedModelRegistry implements ModelRegistry {
@Override @Override
public Model getModel(Material material, BlockData blockData) { 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, return modelMap.computeIfAbsent(material, key -> new HashMap<>()).getOrDefault(blockData,
blockData == null ? getDefaultModel() blockData == null ? getDefaultModel()
: modelMap.get(material).getOrDefault(null, getDefaultModel())); : modelMap.get(material).getOrDefault(null, getDefaultModel()));
@ -55,39 +80,54 @@ public class AdvancedModelRegistry implements ModelRegistry {
.put(null, blockModel); .put(null, blockModel);
} }
private List<String> getBlockTextures(Material material) { private void addModelFromFile(File file) {
if(!material.isBlock()) return new ArrayList<>(); String blockName = file.getName().substring(0, file.getName().lastIndexOf('.'));
String blockName = material.name().toLowerCase(); Material material = Material.getMaterial(blockName.toUpperCase());
try { if(material == null) return;
if(!Arrays.deepEquals(getTextureArray(blockName), new int[0][0])) {
return List.of(blockName); 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) { } catch (Exception e) {
String[] possibleVariants = { System.out.println(e.getMessage());
blockName + "_top",
blockName + "_side",
blockName + "_bottom",
blockName + "_front",
blockName + "_back",
blockName + "_left",
blockName + "_right"
};
List<String> results = new ArrayList<>();
for (String variant : possibleVariants) {
try {
if(!Arrays.deepEquals(getTextureArray(variant), new int[0][0])) {
results.add(variant);
} }
} catch (Exception ignored) { } return null;
}
return results;
}
return new ArrayList<>();
} }
private int[][] getTextureArray(String textureName) { private int[][] getTextureArray(String textureName) {
int[][] texture = new int[TEXTURE_SIZE][TEXTURE_SIZE]; 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)); URL url = this.getClass().getClassLoader().getResource(String.format("textures/block/%s.png", textureName));
if (url == null) { if (url == null) {
throw new RuntimeException("Block Texture Resource not found."); 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 pixelY = 0; pixelY < TEXTURE_SIZE; pixelY++) {
for (int pixelX = 0; pixelX < TEXTURE_SIZE; pixelX++) { 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; 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;
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB