Skip to content

Commit 6c60593

Browse files
authored
Merge pull request #68 from Nogard-YT/feat/translucent-textures
Feat/translucent textures
2 parents 646ffbd + 6df1e50 commit 6c60593

7 files changed

Lines changed: 317 additions & 6 deletions

File tree

geyser/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
}
55

66
group = "re.imc"
7-
version = "1.0.7"
7+
version = "1.0.8"
88

99
repositories {
1010
mavenCentral()

geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/ResourcePackManager.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ private void generateResourcePack(File inputFolder, File output) {
147147

148148
Entity entity = entityCache.get(entry.getKey());
149149
if (entity != null) {
150+
if (extension.getConfigManager().getConfig().getBoolean("options.resource-pack.translucent-materials", true)) {
151+
double translucentThreshold = extension.getConfigManager().getConfig().getDouble("options.resource-pack.translucent-threshold");
152+
if (translucentThreshold <= 0) translucentThreshold = 0.5;
153+
TranslucencySplitter.split(entry.getValue(), entity, translucentThreshold);
154+
}
150155
ModelConfig modelConfig = entity.getModelConfig();
151156
if (!modelConfig.getPerTextureUvSize().isEmpty()) {
152157
for (Map.Entry<String, TextureData> textureEntry : entity.getTextureMap().entrySet()) {
@@ -220,7 +225,7 @@ private void generateResourcePack(File inputFolder, File output) {
220225

221226
String id = entity.getModelId();
222227
if (!geometryCache.containsKey(id)) continue;
223-
RenderController controller = new RenderController(id, geometryCache.get(id).getBones(), entity);
228+
RenderController controller = new RenderController(id, geometryCache.get(id), entity);
224229
entity.setRenderController(controller);
225230
Path renderPath = new File(renderControllersFolder, id + ".json").toPath();
226231
if (renderPath.toFile().exists()) continue;

geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Entity.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class Entity {
2222
private String path;
2323
private Map<String, TextureData> textureMap = new HashMap<>();
2424
private ModelConfig modelConfig;
25+
private final Map<String, List<String[]>> translucentSplits = new HashMap<>();
2526

2627
public static final String TEMPLATE = """
2728
{
@@ -31,7 +32,8 @@ public class Entity {
3132
"identifier": "%namespace%:%entity_id%",
3233
"materials": {
3334
"default": "%material%",
34-
"anim": "entity_alphatest_anim_change_color_one_sided"
35+
"anim": "entity_alphatest_anim_change_color_one_sided",
36+
"blend": "entity_alphablend_change_color_one_sided"
3537
},
3638
"textures": {
3739
},
@@ -220,4 +222,12 @@ public Map<String, TextureData> getTextureMap() {
220222
public ModelConfig getModelConfig() {
221223
return modelConfig;
222224
}
225+
226+
public void addTranslucentSplit(String texture, String tBone, String cloneFrom) {
227+
translucentSplits.computeIfAbsent(texture, k -> new ArrayList<>()).add(new String[]{tBone, cloneFrom});
228+
}
229+
230+
public Map<String, List<String[]>> getTranslucentSplits() {
231+
return translucentSplits;
232+
}
223233
}

geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/Material.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ public class Material {
3131
"+defines":[
3232
"USE_UV_ANIM"
3333
]
34+
},
35+
"entity_alphablend_change_color_one_sided:entity_alphablend": {
36+
"+defines": [
37+
"USE_OVERLAY",
38+
"USE_COLOR_MASK"
39+
],
40+
"+samplerStates": [
41+
{
42+
"samplerIndex": 1,
43+
"textureWrap": "Repeat"
44+
}
45+
],
46+
"msaaSupport": "Both"
3447
}
3548
}
3649
}

geyser/src/main/java/re/imc/geysermodelengineextension/managers/resourcepack/generator/RenderController.java

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package re.imc.geysermodelengineextension.managers.resourcepack.generator;
22

33
import com.google.gson.JsonArray;
4+
import com.google.gson.JsonElement;
45
import com.google.gson.JsonObject;
6+
import re.imc.geysermodelengineextension.GeyserModelEngineExtension;
57
import re.imc.geysermodelengineextension.managers.resourcepack.generator.data.BoneData;
8+
import re.imc.geysermodelengineextension.managers.resourcepack.generator.data.TextureData;
69
import re.imc.geysermodelengineextension.util.ShortHashUtil;
710

11+
import javax.imageio.ImageIO;
12+
import java.awt.image.BufferedImage;
13+
import java.io.ByteArrayInputStream;
14+
import java.io.IOException;
815
import java.util.*;
916

1017
public class RenderController {
@@ -14,15 +21,24 @@ public class RenderController {
1421
private final String modelId;
1522
private final Map<String, BoneData> bones;
1623
private final Entity entity;
24+
private final Geometry geometry;
25+
private final Map<String, BufferedImage> imageCache = new HashMap<>();
1726

18-
public RenderController(String modelId, Map<String, BoneData> bones, Entity entity) {
27+
public RenderController(String modelId, Geometry geometry, Entity entity) {
1928
this.modelId = modelId;
20-
this.bones = bones;
29+
this.geometry = geometry;
30+
this.bones = geometry.getBones();
2131
this.entity = entity;
2232
}
2333

2434
// look, I'm fine with your other code and stuff, but I ain't using templates for JSON lmao
2535
public String generate(String namespace, boolean hashEnabled) {
36+
boolean translucency = GeyserModelEngineExtension.getExtension().getConfigManager()
37+
.getConfig().getBoolean("options.resource-pack.translucent-materials", true);
38+
double translucentThreshold = GeyserModelEngineExtension.getExtension().getConfigManager()
39+
.getConfig().getDouble("options.resource-pack.translucent-threshold");
40+
if (translucentThreshold <= 0) translucentThreshold = 0.5;
41+
2642
List<String> se = new ArrayList<>(bones.keySet());
2743
Collections.sort(se);
2844
JsonObject root = new JsonObject();
@@ -74,6 +90,7 @@ public String generate(String namespace, boolean hashEnabled) {
7490
String material = entity.getModelConfig().getTextureMaterials().get(key);
7591

7692
JsonObject materialItem = new JsonObject();
93+
boolean defaultMaterial = false;
7794
if (material != null) {
7895
materialItem.addProperty("*", "Material." + material);
7996
} else if (anim != null) {
@@ -90,10 +107,25 @@ public String generate(String namespace, boolean hashEnabled) {
90107
scale.add(1.0);
91108
scale.add("1 / " + anim.frames);
92109
uvAnim.add("scale", scale);
110+
} else if (translucency && semiFraction(key) >= 0.999) {
111+
materialItem.addProperty("*", "Material.blend");
93112
} else {
94113
materialItem.addProperty("*", "Material.default");
114+
defaultMaterial = true;
95115
}
96116
materials.add(materialItem);
117+
if (translucency && defaultMaterial) {
118+
for (String tBone : translucentBones(key, uvBonesId, translucentThreshold)) {
119+
JsonObject boneMaterial = new JsonObject();
120+
boneMaterial.addProperty(tBone, "Material.blend");
121+
materials.add(boneMaterial);
122+
}
123+
for (String[] split : entity.getTranslucentSplits().getOrDefault(key, Collections.emptyList())) {
124+
JsonObject boneMaterial = new JsonObject();
125+
boneMaterial.addProperty(split[0], "Material.blend");
126+
materials.add(boneMaterial);
127+
}
128+
}
97129
controller.add("materials", materials);
98130

99131
JsonArray textures = new JsonArray();
@@ -111,6 +143,7 @@ public String generate(String namespace, boolean hashEnabled) {
111143
visibilityDefault.addProperty("*", false);
112144
partVisibility.add(visibilityDefault);
113145
int i = 0;
146+
Map<String, JsonElement> capturedVisibility = new HashMap<>();
114147
List<String> sorted = new ArrayList<>(bones.keySet());
115148
Map<String, String> originalId = new HashMap<>();
116149
ListIterator<String> iterator = sorted.listIterator();
@@ -172,6 +205,7 @@ public String generate(String namespace, boolean hashEnabled) {
172205
visibilityItem.addProperty(boneName, "math.mod(math.floor(query.property('" + namespace + ":bone" + index / 24 + "') / " + n + "), 2) == 1");
173206
}
174207
partVisibility.add(visibilityItem);
208+
capturedVisibility.put(boneName, visibilityItem.get(boneName));
175209
if (!uvBonesId.contains("*")) {
176210
processedBones.add(bone);
177211
}
@@ -180,11 +214,82 @@ public String generate(String namespace, boolean hashEnabled) {
180214
i++;
181215
}
182216
}
217+
for (String[] split : entity.getTranslucentSplits().getOrDefault(key, Collections.emptyList())) {
218+
JsonElement vis = capturedVisibility.get(split[1]);
219+
if (vis != null) {
220+
JsonObject splitVisibility = new JsonObject();
221+
splitVisibility.add(split[0], vis.deepCopy());
222+
partVisibility.add(splitVisibility);
223+
}
224+
}
183225
controller.add("part_visibility", partVisibility);
184226
//}
185227
}
186228

187229
return root.toString();
188230
}
189231

232+
private BufferedImage image(String key) {
233+
if (imageCache.containsKey(key)) return imageCache.get(key);
234+
BufferedImage img = null;
235+
TextureData texture = entity.getTextureMap().get(key);
236+
if (texture != null && texture.getImage() != null) {
237+
try {
238+
img = ImageIO.read(new ByteArrayInputStream(texture.getImage()));
239+
if (img != null && !img.getColorModel().hasAlpha()) img = null;
240+
} catch (IOException ignored) {
241+
}
242+
}
243+
imageCache.put(key, img);
244+
return img;
245+
}
246+
247+
private double semiFraction(String key) {
248+
BufferedImage img = image(key);
249+
if (img == null) return 0;
250+
int w = img.getWidth(), h = img.getHeight();
251+
long semi = 0, visible = 0;
252+
for (int y = 0; y < h; y++) {
253+
for (int x = 0; x < w; x++) {
254+
int a = (img.getRGB(x, y) >>> 24) & 0xFF;
255+
if (a == 0) continue;
256+
visible++;
257+
if (a < 255) semi++;
258+
}
259+
}
260+
return visible == 0 ? 0 : (double) semi / visible;
261+
}
262+
263+
private Set<String> translucentBones(String key, Set<String> uvBonesId, double threshold) {
264+
BufferedImage img = image(key);
265+
if (img == null) return Collections.emptySet();
266+
267+
JsonObject description = geometry.getInternal().get("description").getAsJsonObject();
268+
Integer[] uvSize = entity.getModelConfig().getPerTextureUvSize().get(key);
269+
double[] sc = TranslucencySplitter.scale(description, uvSize, img);
270+
double sx = sc[0], sy = sc[1];
271+
272+
boolean allBones = uvBonesId.contains("*");
273+
Set<String> bound = new HashSet<>();
274+
for (String b : uvBonesId) bound.add(b.toLowerCase());
275+
276+
Set<String> result = new LinkedHashSet<>();
277+
for (JsonElement element : geometry.getInternal().get("bones").getAsJsonArray()) {
278+
JsonObject bone = element.getAsJsonObject();
279+
String name = bone.get("name").getAsString();
280+
if (name.endsWith("_t")) continue;
281+
if (!allBones && !bound.contains(name.toLowerCase())) continue;
282+
if (!bone.has("cubes")) continue;
283+
284+
boolean anyGlass = false, anyOpaque = false;
285+
for (JsonElement cube : bone.get("cubes").getAsJsonArray()) {
286+
int type = TranslucencySplitter.cubeClass(cube.getAsJsonObject(), img, sx, sy, threshold);
287+
if (type == TranslucencySplitter.GLASS) anyGlass = true;
288+
else if (type == TranslucencySplitter.OPAQUE) anyOpaque = true;
289+
}
290+
if (anyGlass && !anyOpaque) result.add(name);
291+
}
292+
return result;
293+
}
294+
190295
}

0 commit comments

Comments
 (0)