diff --git a/src/main/java/ru/bclib/client/models/OBJBlockModel.java b/src/main/java/ru/bclib/client/models/OBJBlockModel.java new file mode 100644 index 00000000..b4a8fd20 --- /dev/null +++ b/src/main/java/ru/bclib/client/models/OBJBlockModel.java @@ -0,0 +1,241 @@ +package ru.bclib.client.models; + +import com.google.common.collect.Lists; +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemOverrides; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.util.Mth; +import net.minecraft.world.level.block.state.BlockState; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.function.Function; + +public class OBJBlockModel implements UnbakedModel, BakedModel { + private static final byte[] QUAD_INDEXES = new byte[] {0, 1, 2, 0, 2, 3}; + + protected TextureAtlasSprite[] sprites; + protected TextureAtlasSprite particles; + protected ItemTransforms transforms; + protected ItemOverrides overrides; + + protected List quadsUnbaked; + protected Material particleMaterial; + protected List materials; + protected List quads; + + public OBJBlockModel(ResourceLocation location, ResourceLocation particleTextureID, ResourceLocation... textureIDs) { + transforms = ItemTransforms.NO_TRANSFORMS; + overrides = ItemOverrides.EMPTY; + + quadsUnbaked = Lists.newArrayList(); + materials = Lists.newArrayList(); + + loadModel(location, textureIDs); + + quads = new ArrayList<>(quadsUnbaked.size()); + particleMaterial = new Material(TextureAtlas.LOCATION_BLOCKS, particleTextureID); + sprites = new TextureAtlasSprite[materials.size()]; + } + + // UnbakedModel // + + @Override + public Collection getDependencies() { + return Collections.emptyList(); + } + + @Override + public Collection getMaterials(Function function, Set> set) { + return materials; + } + + @Nullable + @Override + public BakedModel bake(ModelBakery modelBakery, Function textureGetter, ModelState modelState, ResourceLocation resourceLocation) { + for (int i = 0; i < sprites.length; i++) { + sprites[i] = textureGetter.apply(materials.get(i)); + } + particles = textureGetter.apply(particleMaterial); + quads.clear(); + quadsUnbaked.forEach(quad -> quads.add(quad.bake(sprites))); + return this; + } + + // Baked Model // + + @Override + public List getQuads(@Nullable BlockState blockState, @Nullable Direction direction, Random random) { + return direction == null ? quads : Collections.emptyList(); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean isGui3d() { + return true; + } + + @Override + public boolean usesBlockLight() { + return true; + } + + @Override + public boolean isCustomRenderer() { + return false; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + return particles; + } + + @Override + public ItemTransforms getTransforms() { + return transforms; + } + + @Override + public ItemOverrides getOverrides() { + return overrides; + } + + private Resource getResource(ResourceLocation location) { + Resource resource = null; + try { + resource = Minecraft.getInstance().getResourceManager().getResource(location); + } + catch (IOException e) { + e.printStackTrace(); + if (resource != null) { + try { + resource.close(); + } + catch (IOException ioException) { + ioException.printStackTrace(); + } + resource = null; + } + } + return resource; + } + + private void loadModel(ResourceLocation location, ResourceLocation[] textureIDs) { + Resource resource = getResource(location); + if (resource == null) { + return; + } + InputStream input = resource.getInputStream(); + + List vertecies = new ArrayList<>(12); + List uvs = new ArrayList<>(8); + + List vertexIndex = new ArrayList<>(4); + List uvIndex = new ArrayList<>(4); + + byte materialIndex = 0; + int vertCount = 0; + + try { + InputStreamReader streamReader = new InputStreamReader(input); + BufferedReader reader = new BufferedReader(streamReader); + String string; + + while ((string = reader.readLine()) != null) { + if ((string.startsWith("usemtl") || string.startsWith("g")) && vertCount != vertecies.size()) { + vertCount = vertecies.size(); + materialIndex++; + } + else if (string.startsWith("vt")) { + String[] uv = string.split(" "); + uvs.add(Float.parseFloat(uv[1])); + uvs.add(Float.parseFloat(uv[2])); + } + else if (string.startsWith("v")) { + String[] vert = string.split(" "); + for (int i = 1; i < 4; i++) { + vertecies.add(Float.parseFloat(vert[i])); + } + } + else if (string.startsWith("f")) { + String[] members = string.split(" "); + if (members.length != 5) { + System.out.println("Only quads in OBJ are supported! Model [" + location + "] has n-gons or triangles!"); + continue; + } + vertexIndex.clear(); + uvIndex.clear(); + + for (int i = 1; i < members.length; i++) { + String member = members[i]; + + if (member.contains("/")) { + String[] sub = member.split("/"); + vertexIndex.add(Integer.parseInt(sub[0]) - 1); // Vertex + uvIndex.add(Integer.parseInt(sub[1]) - 1); // UV + } + else { + vertexIndex.add(Integer.parseInt(member) - 1); // Vertex + } + } + + boolean hasUV = !uvIndex.isEmpty(); + UnbakedQuad quad = new UnbakedQuad(); + for (int i = 0; i < 4; i++) { + int index = vertexIndex.get(i) * 3; + int quadIndex = i * 5; + quad.addData(quadIndex++, vertecies.get(index++)); // X + quad.addData(quadIndex++, vertecies.get(index++)); // Y + quad.addData(quadIndex++, vertecies.get(index)); // Z + if (hasUV) { + index = uvIndex.get(i) * 2; + quad.addData(quadIndex++, uvs.get(index++) * 16F); // U + quad.addData(quadIndex, (1 - uvs.get(index)) * 16F); // V + } + } + quad.setSpriteIndex(materialIndex); + quadsUnbaked.add(quad); + } + } + + reader.close(); + streamReader.close(); + input.close(); + resource.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + + int maxID = textureIDs.length - 1; + for (int i = 0; i <= materialIndex; i++) { + int index = Math.min(materialIndex, maxID); + materials.add(new Material(TextureAtlas.LOCATION_BLOCKS, textureIDs[index])); + } + } +} diff --git a/src/main/java/ru/bclib/client/models/UnbakedQuad.java b/src/main/java/ru/bclib/client/models/UnbakedQuad.java new file mode 100644 index 00000000..60cd7561 --- /dev/null +++ b/src/main/java/ru/bclib/client/models/UnbakedQuad.java @@ -0,0 +1,35 @@ +package ru.bclib.client.models; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.Direction; + +public class UnbakedQuad { + float[] data = new float[20]; // 4 points with 3 positions and 2 uvs, 4 * (3 + 2) + int spriteIndex; + + public void addData(int index, float value) { + data[index] = value; + } + + public void setSpriteIndex(int index) { + spriteIndex = index; + } + + public BakedQuad bake(TextureAtlasSprite[] sprites) { + TextureAtlasSprite sprite = sprites[spriteIndex]; + int[] vertexData = new int[32]; + for (int i = 0; i < 4; i++) { + int index = i << 3; + int dataIndex = i * 5; + vertexData[index] = Float.floatToIntBits(data[dataIndex++]); // X + vertexData[index | 1] = Float.floatToIntBits(data[dataIndex++]); // Y + vertexData[index | 2] = Float.floatToIntBits(data[dataIndex++]); // Z + vertexData[index | 3] = -1; // Unknown constant + vertexData[index | 4] = Float.floatToIntBits(sprite.getU(data[dataIndex++])); // U + vertexData[index | 5] = Float.floatToIntBits(sprite.getV(data[dataIndex])); // V + } + // vertices, tint index, direction, sprite, shade + return new BakedQuad(vertexData, 0, Direction.UP, sprites[spriteIndex], true); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 01ea9f48..2a83ad36 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -34,6 +34,6 @@ "depends": { "fabricloader": ">=0.11.6", "fabric": ">=0.36.0", - "minecraft": ">=1.17" + "minecraft": ">=1.17.1" } }