From 6eba0ebb93cafe8dd7c061d92bae0dd4c8d324e3 Mon Sep 17 00:00:00 2001 From: RainWarrior Date: Sun, 28 Dec 2014 07:10:54 +0300 Subject: [PATCH] Added model loader registry Entry point: ModelLoaderRegistry loader interface: ICustomModelLoader custom model: IModel ModelLoader is responsible for splicing into vanilla model system. (you probably don't need to use it directly) Interop with vanilla models isn't great yet (vanilla models can't refer to custom ones as parents), will improve in the future. Includes loader for B3D models, with animation support (net.minecraftforge.client.model.b3d). Blender export plugin with compatible coordinate system: https://github.com/RainWarrior/B3DExport OBJ loader is being written, will be included at some point in the future. For now you can convert OBJ to B3D via blender, or wait. --- .../WorldVertexBufferUploader.java.patch | 70 ++ .../block/model/FaceBakery.java.patch | 76 ++ .../model/ItemCameraTransforms.java.patch | 14 + .../block/model/ItemTransformVec3f.java.patch | 18 + .../renderer/entity/RenderItem.java.patch | 43 +- .../vertex/VertexFormatElement.java.patch | 49 + .../resources/model/IBakedModel.java.patch | 21 + .../resources/model/ModelBakery.java.patch | 53 + .../resources/model/ModelManager.java.patch | 8 +- .../resources/model/ModelRotation.java.patch | 21 + .../client/ForgeHooksClient.java | 154 +++ .../client/model/Attributes.java | 107 ++ .../client/model/ICustomModelLoader.java | 18 + .../client/model/IFlexibleBakedModel.java | 82 ++ .../minecraftforge/client/model/IModel.java | 45 + .../client/model/IModelPart.java | 8 + .../client/model/IModelState.java | 18 + .../client/model/IPerspectiveAwareModel.java | 22 + .../client/model/ITransformation.java | 18 + .../client/model/MapModelState.java | 31 + .../client/model/ModelLoader.java | 399 ++++++ .../client/model/ModelLoaderRegistry.java | 112 ++ .../client/model/TRSRTransformation.java | 445 +++++++ .../client/model/b3d/B3DLoader.java | 607 +++++++++ .../client/model/b3d/B3DModel.java | 1083 +++++++++++++++++ src/main/resources/forge.exc | 7 + src/main/resources/forge_at.cfg | 24 + .../debug/ModelLoaderRegistryDebug.java | 220 ++++ .../blockstates/CustomModelBlock.json | 5 + .../models/block/untitled2.b3d | Bin 0 -> 2173 bytes .../textures/texture.png | Bin 0 -> 168 bytes 31 files changed, 3773 insertions(+), 5 deletions(-) create mode 100644 patches/minecraft/net/minecraft/client/renderer/WorldVertexBufferUploader.java.patch create mode 100644 patches/minecraft/net/minecraft/client/renderer/block/model/FaceBakery.java.patch create mode 100644 patches/minecraft/net/minecraft/client/renderer/block/model/ItemCameraTransforms.java.patch create mode 100644 patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransformVec3f.java.patch create mode 100644 patches/minecraft/net/minecraft/client/renderer/vertex/VertexFormatElement.java.patch create mode 100644 patches/minecraft/net/minecraft/client/resources/model/IBakedModel.java.patch create mode 100644 patches/minecraft/net/minecraft/client/resources/model/ModelBakery.java.patch create mode 100644 patches/minecraft/net/minecraft/client/resources/model/ModelRotation.java.patch create mode 100644 src/main/java/net/minecraftforge/client/model/Attributes.java create mode 100644 src/main/java/net/minecraftforge/client/model/ICustomModelLoader.java create mode 100644 src/main/java/net/minecraftforge/client/model/IFlexibleBakedModel.java create mode 100644 src/main/java/net/minecraftforge/client/model/IModel.java create mode 100644 src/main/java/net/minecraftforge/client/model/IModelPart.java create mode 100644 src/main/java/net/minecraftforge/client/model/IModelState.java create mode 100644 src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java create mode 100644 src/main/java/net/minecraftforge/client/model/ITransformation.java create mode 100644 src/main/java/net/minecraftforge/client/model/MapModelState.java create mode 100644 src/main/java/net/minecraftforge/client/model/ModelLoader.java create mode 100644 src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java create mode 100644 src/main/java/net/minecraftforge/client/model/TRSRTransformation.java create mode 100644 src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java create mode 100644 src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java create mode 100644 src/test/java/net/minecraftforge/debug/ModelLoaderRegistryDebug.java create mode 100644 src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json create mode 100644 src/test/resources/assets/forgedebugmodelloaderregistry/models/block/untitled2.b3d create mode 100644 src/test/resources/assets/forgedebugmodelloaderregistry/textures/texture.png diff --git a/patches/minecraft/net/minecraft/client/renderer/WorldVertexBufferUploader.java.patch b/patches/minecraft/net/minecraft/client/renderer/WorldVertexBufferUploader.java.patch new file mode 100644 index 000000000..bf398cbd5 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/renderer/WorldVertexBufferUploader.java.patch @@ -0,0 +1,70 @@ +--- ../src-base/minecraft/net/minecraft/client/renderer/WorldVertexBufferUploader.java ++++ ../src-work/minecraft/net/minecraft/client/renderer/WorldVertexBufferUploader.java +@@ -29,35 +29,9 @@ + + while (iterator.hasNext()) + { ++ // moved to VertexFormatElement.preDraw + vertexformatelement = (VertexFormatElement)iterator.next(); +- enumusage = vertexformatelement.func_177375_c(); +- k = vertexformatelement.func_177367_b().func_177397_c(); +- int l = vertexformatelement.func_177369_e(); +- +- switch (WorldVertexBufferUploader.SwitchEnumUsage.field_178958_a[enumusage.ordinal()]) +- { +- case 1: +- bytebuffer.position(vertexformatelement.func_177373_a()); +- GL11.glVertexPointer(vertexformatelement.func_177370_d(), k, j, bytebuffer); +- GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY); +- break; +- case 2: +- bytebuffer.position(vertexformatelement.func_177373_a()); +- OpenGlHelper.func_77472_b(OpenGlHelper.field_77478_a + l); +- GL11.glTexCoordPointer(vertexformatelement.func_177370_d(), k, j, bytebuffer); +- GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY); +- OpenGlHelper.func_77472_b(OpenGlHelper.field_77478_a); +- break; +- case 3: +- bytebuffer.position(vertexformatelement.func_177373_a()); +- GL11.glColorPointer(vertexformatelement.func_177370_d(), k, j, bytebuffer); +- GL11.glEnableClientState(GL11.GL_COLOR_ARRAY); +- break; +- case 4: +- bytebuffer.position(vertexformatelement.func_177373_a()); +- GL11.glNormalPointer(k, j, bytebuffer); +- GL11.glEnableClientState(GL11.GL_NORMAL_ARRAY); +- } ++ vertexformatelement.func_177375_c().preDraw(vertexformatelement, j, bytebuffer); + } + + GL11.glDrawArrays(p_178177_1_.func_178979_i(), 0, p_178177_1_.func_178989_h()); +@@ -65,27 +39,9 @@ + + while (iterator.hasNext()) + { ++ // moved to VertexFormatElement.postDraw + vertexformatelement = (VertexFormatElement)iterator.next(); +- enumusage = vertexformatelement.func_177375_c(); +- k = vertexformatelement.func_177369_e(); +- +- switch (WorldVertexBufferUploader.SwitchEnumUsage.field_178958_a[enumusage.ordinal()]) +- { +- case 1: +- GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY); +- break; +- case 2: +- OpenGlHelper.func_77472_b(OpenGlHelper.field_77478_a + k); +- GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY); +- OpenGlHelper.func_77472_b(OpenGlHelper.field_77478_a); +- break; +- case 3: +- GL11.glDisableClientState(GL11.GL_COLOR_ARRAY); +- GlStateManager.func_179117_G(); +- break; +- case 4: +- GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY); +- } ++ vertexformatelement.func_177375_c().postDraw(vertexformatelement, j, bytebuffer); + } + } + diff --git a/patches/minecraft/net/minecraft/client/renderer/block/model/FaceBakery.java.patch b/patches/minecraft/net/minecraft/client/renderer/block/model/FaceBakery.java.patch new file mode 100644 index 000000000..c9b9efd62 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/renderer/block/model/FaceBakery.java.patch @@ -0,0 +1,76 @@ +--- ../src-base/minecraft/net/minecraft/client/renderer/block/model/FaceBakery.java ++++ ../src-work/minecraft/net/minecraft/client/renderer/block/model/FaceBakery.java +@@ -22,7 +22,12 @@ + + public BakedQuad func_178414_a(Vector3f p_178414_1_, Vector3f p_178414_2_, BlockPartFace p_178414_3_, TextureAtlasSprite p_178414_4_, EnumFacing p_178414_5_, ModelRotation p_178414_6_, BlockPartRotation p_178414_7_, boolean p_178414_8_, boolean p_178414_9_) + { +- int[] aint = this.func_178405_a(p_178414_3_, p_178414_4_, p_178414_5_, this.func_178403_a(p_178414_1_, p_178414_2_), p_178414_6_, p_178414_7_, p_178414_8_, p_178414_9_); ++ return makeBakedQuad(p_178414_1_, p_178414_2_, p_178414_3_, p_178414_4_, p_178414_5_, (net.minecraftforge.client.model.ITransformation)p_178414_6_, p_178414_7_, p_178414_8_, p_178414_9_); ++ } ++ ++ public BakedQuad makeBakedQuad(Vector3f p_178414_1_, Vector3f p_178414_2_, BlockPartFace p_178414_3_, TextureAtlasSprite p_178414_4_, EnumFacing p_178414_5_, net.minecraftforge.client.model.ITransformation p_178414_6_, BlockPartRotation p_178414_7_, boolean p_178414_8_, boolean p_178414_9_) ++ { ++ int[] aint = this.makeQuadVertexData(p_178414_3_, p_178414_4_, p_178414_5_, this.func_178403_a(p_178414_1_, p_178414_2_), p_178414_6_, p_178414_7_, p_178414_8_, p_178414_9_); + EnumFacing enumfacing1 = func_178410_a(aint); + + if (p_178414_8_) +@@ -40,11 +45,16 @@ + + private int[] func_178405_a(BlockPartFace p_178405_1_, TextureAtlasSprite p_178405_2_, EnumFacing p_178405_3_, float[] p_178405_4_, ModelRotation p_178405_5_, BlockPartRotation p_178405_6_, boolean p_178405_7_, boolean p_178405_8_) + { ++ return makeQuadVertexData(p_178405_1_, p_178405_2_, p_178405_3_, p_178405_4_, (net.minecraftforge.client.model.ITransformation)p_178405_5_, p_178405_6_, p_178405_7_, p_178405_8_); ++ } ++ ++ private int[] makeQuadVertexData(BlockPartFace p_178405_1_, TextureAtlasSprite p_178405_2_, EnumFacing p_178405_3_, float[] p_178405_4_, net.minecraftforge.client.model.ITransformation p_178405_5_, BlockPartRotation p_178405_6_, boolean p_178405_7_, boolean p_178405_8_) ++ { + int[] aint = new int[28]; + + for (int i = 0; i < 4; ++i) + { +- this.func_178402_a(aint, i, p_178405_3_, p_178405_1_, p_178405_4_, p_178405_2_, p_178405_5_, p_178405_6_, p_178405_7_, p_178405_8_); ++ this.fillVertexData(aint, i, p_178405_3_, p_178405_1_, p_178405_4_, p_178405_2_, p_178405_5_, p_178405_6_, p_178405_7_, p_178405_8_); + } + + return aint; +@@ -90,12 +100,17 @@ + + private void func_178402_a(int[] p_178402_1_, int p_178402_2_, EnumFacing p_178402_3_, BlockPartFace p_178402_4_, float[] p_178402_5_, TextureAtlasSprite p_178402_6_, ModelRotation p_178402_7_, BlockPartRotation p_178402_8_, boolean p_178402_9_, boolean p_178402_10_) + { +- EnumFacing enumfacing1 = p_178402_7_.func_177523_a(p_178402_3_); ++ fillVertexData(p_178402_1_, p_178402_2_, p_178402_3_, p_178402_4_, p_178402_5_, p_178402_6_, (net.minecraftforge.client.model.ITransformation)p_178402_7_, p_178402_8_, p_178402_9_, p_178402_10_); ++ } ++ ++ private void fillVertexData(int[] p_178402_1_, int p_178402_2_, EnumFacing p_178402_3_, BlockPartFace p_178402_4_, float[] p_178402_5_, TextureAtlasSprite p_178402_6_, net.minecraftforge.client.model.ITransformation p_178402_7_, BlockPartRotation p_178402_8_, boolean p_178402_9_, boolean p_178402_10_) ++ { ++ EnumFacing enumfacing1 = p_178402_7_.rotate(p_178402_3_); + int j = p_178402_10_ ? this.func_178413_a(enumfacing1) : -1; + EnumFaceDirection.VertexInformation vertexinformation = EnumFaceDirection.func_179027_a(p_178402_3_).func_179025_a(p_178402_2_); + Vector3d vector3d = new Vector3d((double)p_178402_5_[vertexinformation.field_179184_a], (double)p_178402_5_[vertexinformation.field_179182_b], (double)p_178402_5_[vertexinformation.field_179183_c]); + this.func_178407_a(vector3d, p_178402_8_); +- int k = this.func_178415_a(vector3d, p_178402_3_, p_178402_2_, p_178402_7_, p_178402_9_); ++ int k = this.rotateVertex(vector3d, p_178402_3_, p_178402_2_, p_178402_7_, p_178402_9_); + this.func_178404_a(p_178402_1_, k, p_178402_2_, vector3d, j, p_178402_6_, p_178402_4_.field_178243_e); + } + +@@ -156,14 +171,19 @@ + + public int func_178415_a(Vector3d p_178415_1_, EnumFacing p_178415_2_, int p_178415_3_, ModelRotation p_178415_4_, boolean p_178415_5_) + { ++ return rotateVertex(p_178415_1_, p_178415_2_, p_178415_3_, (net.minecraftforge.client.model.ITransformation)p_178415_4_, p_178415_5_); ++ } ++ ++ public int rotateVertex(Vector3d p_178415_1_, EnumFacing p_178415_2_, int p_178415_3_, net.minecraftforge.client.model.ITransformation p_178415_4_, boolean p_178415_5_) ++ { + if (p_178415_4_ == ModelRotation.X0_Y0) + { + return p_178415_3_; + } + else + { +- this.func_178406_a(p_178415_1_, new Vector3d(0.5D, 0.5D, 0.5D), p_178415_4_.func_177525_a(), new Vector3d(1.0D, 1.0D, 1.0D)); +- return p_178415_4_.func_177520_a(p_178415_2_, p_178415_3_); ++ this.func_178406_a(p_178415_1_, new Vector3d(0.5D, 0.5D, 0.5D), new Matrix4d(p_178415_4_.getMatrix()), new Vector3d(1.0D, 1.0D, 1.0D)); ++ return p_178415_4_.rotate(p_178415_2_, p_178415_3_); + } + } + diff --git a/patches/minecraft/net/minecraft/client/renderer/block/model/ItemCameraTransforms.java.patch b/patches/minecraft/net/minecraft/client/renderer/block/model/ItemCameraTransforms.java.patch new file mode 100644 index 000000000..48a2d8d09 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/renderer/block/model/ItemCameraTransforms.java.patch @@ -0,0 +1,14 @@ +--- ../src-base/minecraft/net/minecraft/client/renderer/block/model/ItemCameraTransforms.java ++++ ../src-work/minecraft/net/minecraft/client/renderer/block/model/ItemCameraTransforms.java +@@ -8,7 +8,11 @@ + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + ++/* ++ * @deprecated use {@link net.minecraftforge.client.model.IPerspectiveAwareModel} instead ++ */ + @SideOnly(Side.CLIENT) ++@Deprecated + public class ItemCameraTransforms + { + public static final ItemCameraTransforms field_178357_a = new ItemCameraTransforms(ItemTransformVec3f.field_178366_a, ItemTransformVec3f.field_178366_a, ItemTransformVec3f.field_178366_a, ItemTransformVec3f.field_178366_a); diff --git a/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransformVec3f.java.patch b/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransformVec3f.java.patch new file mode 100644 index 000000000..ef42bfbc9 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransformVec3f.java.patch @@ -0,0 +1,18 @@ +--- ../src-base/minecraft/net/minecraft/client/renderer/block/model/ItemTransformVec3f.java ++++ ../src-work/minecraft/net/minecraft/client/renderer/block/model/ItemTransformVec3f.java +@@ -13,9 +13,14 @@ + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + ++/* ++ * @deprecated use {@link net.minecraftforge.client.model.IModelState} and {@link net.minecraftforge.client.model.TRSRTransformation} ++ */ + @SideOnly(Side.CLIENT) +-public class ItemTransformVec3f ++@Deprecated ++public class ItemTransformVec3f implements net.minecraftforge.client.model.IModelState + { ++ public net.minecraftforge.client.model.TRSRTransformation apply(net.minecraftforge.client.model.IModelPart part) { return new net.minecraftforge.client.model.TRSRTransformation(this); } + public static final ItemTransformVec3f field_178366_a = new ItemTransformVec3f(new Vector3f(), new Vector3f(), new Vector3f(1.0F, 1.0F, 1.0F)); + public final Vector3f field_178364_b; + public final Vector3f field_178365_c; diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/RenderItem.java.patch b/patches/minecraft/net/minecraft/client/renderer/entity/RenderItem.java.patch index deaee40ac..48162e109 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/RenderItem.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/entity/RenderItem.java.patch @@ -20,7 +20,44 @@ if (modelresourcelocation != null) { -@@ -485,10 +489,11 @@ +@@ -314,6 +318,11 @@ + + protected void func_175034_a(ItemTransformVec3f p_175034_1_) + { ++ applyVanillaTransform(p_175034_1_); ++ } ++ ++ public static void applyVanillaTransform(ItemTransformVec3f p_175034_1_) ++ { + if (p_175034_1_ != ItemTransformVec3f.field_178366_a) + { + GlStateManager.func_179109_b(p_175034_1_.field_178365_c.x + field_175055_b, p_175034_1_.field_178365_c.y + field_175056_c, p_175034_1_.field_178365_c.z + field_175053_d); +@@ -335,23 +344,7 @@ + GlStateManager.func_179120_a(770, 771, 1, 0); + GlStateManager.func_179094_E(); + +- switch (RenderItem.SwitchTransformType.field_178640_a[p_175040_3_.ordinal()]) +- { +- case 1: +- default: +- break; +- case 2: +- this.func_175034_a(p_175040_2_.func_177552_f().field_178355_b); +- break; +- case 3: +- this.func_175034_a(p_175040_2_.func_177552_f().field_178356_c); +- break; +- case 4: +- this.func_175034_a(p_175040_2_.func_177552_f().field_178353_d); +- break; +- case 5: +- this.func_175034_a(p_175040_2_.func_177552_f().field_178354_e); +- } ++ p_175040_2_ = net.minecraftforge.client.ForgeHooksClient.handleCameraTransforms(p_175040_2_, p_175040_3_); + + this.func_180454_a(p_175040_1_, p_175040_2_); + GlStateManager.func_179121_F(); +@@ -485,10 +478,11 @@ GlStateManager.func_179126_j(); } @@ -35,7 +72,7 @@ GlStateManager.func_179140_f(); GlStateManager.func_179097_i(); GlStateManager.func_179090_x(); -@@ -501,7 +506,7 @@ +@@ -501,7 +495,7 @@ this.func_175044_a(worldrenderer, p_180453_3_ + 2, p_180453_4_ + 13, 13, 2, 0); this.func_175044_a(worldrenderer, p_180453_3_ + 2, p_180453_4_ + 13, 12, 1, i1); this.func_175044_a(worldrenderer, p_180453_3_ + 2, p_180453_4_ + 13, j1, 1, l); @@ -44,7 +81,7 @@ GlStateManager.func_179141_d(); GlStateManager.func_179098_w(); GlStateManager.func_179145_e(); -@@ -1078,6 +1083,19 @@ +@@ -1078,6 +1072,19 @@ { this.field_175059_m.func_178085_b(); } diff --git a/patches/minecraft/net/minecraft/client/renderer/vertex/VertexFormatElement.java.patch b/patches/minecraft/net/minecraft/client/renderer/vertex/VertexFormatElement.java.patch new file mode 100644 index 000000000..5c3f9e29e --- /dev/null +++ b/patches/minecraft/net/minecraft/client/renderer/vertex/VertexFormatElement.java.patch @@ -0,0 +1,49 @@ +--- ../src-base/minecraft/net/minecraft/client/renderer/vertex/VertexFormatElement.java ++++ ../src-work/minecraft/net/minecraft/client/renderer/vertex/VertexFormatElement.java +@@ -114,13 +114,20 @@ + @SideOnly(Side.CLIENT) + public static enum EnumType + { +- FLOAT(4, "Float", 5126), +- UBYTE(1, "Unsigned Byte", 5121), +- BYTE(1, "Byte", 5120), +- USHORT(2, "Unsigned Short", 5123), +- SHORT(2, "Short", 5122), +- UINT(4, "Unsigned Int", 5125), +- INT(4, "Int", 5124); ++ FLOAT(4, "Float", org.lwjgl.opengl.GL11.GL_FLOAT), ++ UBYTE(1, "Unsigned Byte", org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE), ++ BYTE(1, "Byte", org.lwjgl.opengl.GL11.GL_BYTE), ++ USHORT(2, "Unsigned Short", org.lwjgl.opengl.GL11.GL_UNSIGNED_SHORT), ++ SHORT(2, "Short", org.lwjgl.opengl.GL11.GL_SHORT), ++ UINT(4, "Unsigned Int", org.lwjgl.opengl.GL11.GL_UNSIGNED_INT), ++ INT(4, "Int", org.lwjgl.opengl.GL11.GL_INT); ++ // Commented for now, might be added in the future if anyone needs them ++ //HALF_FLOAT(2, "Half Float", org.lwjgl.opengl.GL30.GL_HALF_FLOAT), ++ //DOUBLE(8, "Double", org.lwjgl.opengl.GL11.GL_DOUBLE), ++ //INT_2_10_10_10_REV(4, "Int 2-10-10-10 reversed", org.lwjgl.opengl.GL33.GL_INT_2_10_10_10_REV), ++ //UINT_2_10_10_10_REV(4, "Unsigned Int 2-10-10-10 reversed", org.lwjgl.opengl.GL12.GL_UNSIGNED_INT_2_10_10_10_REV), ++ //UINT_10F_11F_11F_REV(4, "Unsigned Int 10F 11F 11F reversed", GL_UNSIGNED_INT_10F_11F_11F_REV); ++ + private final int field_177407_h; + private final String field_177408_i; + private final int field_177405_j; +@@ -157,9 +164,17 @@ + NORMAL("Normal"), + COLOR("Vertex Color"), + UV("UV"), ++ // As of 1.8 - unused in vanilla; use GENERIC for now ++ @Deprecated + MATRIX("Bone Matrix"), ++ @Deprecated + BLEND_WEIGHT("Blend Weight"), +- PADDING("Padding"); ++ PADDING("Padding"), ++ GENERIC("Generic Attribute"); ++ ++ public void preDraw(VertexFormatElement element, int stride, java.nio.ByteBuffer buffer) { net.minecraftforge.client.ForgeHooksClient.preDraw(this, element, stride, buffer); } ++ public void postDraw(VertexFormatElement element, int stride, java.nio.ByteBuffer buffer) { net.minecraftforge.client.ForgeHooksClient.postDraw(this, element, stride, buffer); } ++ + private final String field_177392_h; + + private static final String __OBFID = "CL_00002397"; diff --git a/patches/minecraft/net/minecraft/client/resources/model/IBakedModel.java.patch b/patches/minecraft/net/minecraft/client/resources/model/IBakedModel.java.patch new file mode 100644 index 000000000..92a87972d --- /dev/null +++ b/patches/minecraft/net/minecraft/client/resources/model/IBakedModel.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/client/resources/model/IBakedModel.java ++++ ../src-work/minecraft/net/minecraft/client/resources/model/IBakedModel.java +@@ -7,7 +7,11 @@ + import net.minecraftforge.fml.relauncher.Side; + import net.minecraftforge.fml.relauncher.SideOnly; + ++/* ++ * @deprecated use {@link net.minecraftforge.client.model.IFlexibleBakedModel}, {@link net.minecraftforge.client.model.IPerspectiveAwareModel} ++ */ + @SideOnly(Side.CLIENT) ++@Deprecated + public interface IBakedModel + { + List func_177551_a(EnumFacing p_177551_1_); +@@ -22,5 +26,6 @@ + + TextureAtlasSprite func_177554_e(); + ++ @Deprecated + ItemCameraTransforms func_177552_f(); + } diff --git a/patches/minecraft/net/minecraft/client/resources/model/ModelBakery.java.patch b/patches/minecraft/net/minecraft/client/resources/model/ModelBakery.java.patch new file mode 100644 index 000000000..0c6385f13 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/resources/model/ModelBakery.java.patch @@ -0,0 +1,53 @@ +--- ../src-base/minecraft/net/minecraft/client/resources/model/ModelBakery.java ++++ ../src-work/minecraft/net/minecraft/client/resources/model/ModelBakery.java +@@ -123,7 +123,7 @@ + } + catch (Exception exception) + { +- field_177603_c.warn("Unable to load variant: " + modelresourcelocation.func_177518_c() + " from " + modelresourcelocation); ++ field_177603_c.warn("Unable to load variant: " + modelresourcelocation.func_177518_c() + " from " + modelresourcelocation, exception); + } + } + catch (Exception exception1) +@@ -499,6 +499,11 @@ + + private IBakedModel func_177578_a(ModelBlock p_177578_1_, ModelRotation p_177578_2_, boolean p_177578_3_) + { ++ return bakeModel(p_177578_1_, (net.minecraftforge.client.model.ITransformation)p_177578_2_, p_177578_3_); ++ } ++ ++ protected IBakedModel bakeModel(ModelBlock p_177578_1_, net.minecraftforge.client.model.ITransformation p_177578_2_, boolean p_177578_3_) ++ { + TextureAtlasSprite textureatlassprite = (TextureAtlasSprite)this.field_177599_g.get(new ResourceLocation(p_177578_1_.func_178308_c("particle"))); + SimpleBakedModel.Builder builder = (new SimpleBakedModel.Builder(p_177578_1_)).func_177646_a(textureatlassprite); + Iterator iterator = p_177578_1_.func_178298_a().iterator(); +@@ -516,11 +521,11 @@ + + if (blockpartface.field_178244_b == null) + { +- builder.func_177648_a(this.func_177589_a(blockpart, blockpartface, textureatlassprite1, enumfacing, p_177578_2_, p_177578_3_)); ++ builder.func_177648_a(this.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, enumfacing, p_177578_2_, p_177578_3_)); + } + else + { +- builder.func_177650_a(p_177578_2_.func_177523_a(blockpartface.field_178244_b), this.func_177589_a(blockpart, blockpartface, textureatlassprite1, enumfacing, p_177578_2_, p_177578_3_)); ++ builder.func_177650_a(p_177578_2_.rotate(blockpartface.field_178244_b), this.makeBakedQuad(blockpart, blockpartface, textureatlassprite1, enumfacing, p_177578_2_, p_177578_3_)); + } + } + } +@@ -530,9 +535,14 @@ + + private BakedQuad func_177589_a(BlockPart p_177589_1_, BlockPartFace p_177589_2_, TextureAtlasSprite p_177589_3_, EnumFacing p_177589_4_, ModelRotation p_177589_5_, boolean p_177589_6_) + { +- return this.field_177607_l.func_178414_a(p_177589_1_.field_178241_a, p_177589_1_.field_178239_b, p_177589_2_, p_177589_3_, p_177589_4_, p_177589_5_, p_177589_1_.field_178237_d, p_177589_6_, p_177589_1_.field_178238_e); ++ return makeBakedQuad(p_177589_1_, p_177589_2_, p_177589_3_, p_177589_4_, (net.minecraftforge.client.model.ITransformation)p_177589_5_, p_177589_6_); + } + ++ private BakedQuad makeBakedQuad(BlockPart p_177589_1_, BlockPartFace p_177589_2_, TextureAtlasSprite p_177589_3_, EnumFacing p_177589_4_, net.minecraftforge.client.model.ITransformation p_177589_5_, boolean p_177589_6_) ++ { ++ return this.field_177607_l.makeBakedQuad(p_177589_1_.field_178241_a, p_177589_1_.field_178239_b, p_177589_2_, p_177589_3_, p_177589_4_, p_177589_5_, p_177589_1_.field_178237_d, p_177589_6_, p_177589_1_.field_178238_e); ++ } ++ + private void func_177597_h() + { + this.func_177574_i(); diff --git a/patches/minecraft/net/minecraft/client/resources/model/ModelManager.java.patch b/patches/minecraft/net/minecraft/client/resources/model/ModelManager.java.patch index 9b2da14ec..ec981fa49 100644 --- a/patches/minecraft/net/minecraft/client/resources/model/ModelManager.java.patch +++ b/patches/minecraft/net/minecraft/client/resources/model/ModelManager.java.patch @@ -1,7 +1,11 @@ --- ../src-base/minecraft/net/minecraft/client/resources/model/ModelManager.java +++ ../src-work/minecraft/net/minecraft/client/resources/model/ModelManager.java -@@ -28,6 +28,7 @@ - ModelBakery modelbakery = new ModelBakery(p_110549_1_, this.field_174956_b, this.field_174957_c); +@@ -25,9 +25,10 @@ + + public void func_110549_a(IResourceManager p_110549_1_) + { +- ModelBakery modelbakery = new ModelBakery(p_110549_1_, this.field_174956_b, this.field_174957_c); ++ ModelBakery modelbakery = new net.minecraftforge.client.model.ModelLoader(p_110549_1_, this.field_174956_b, this.field_174957_c); this.field_174958_a = modelbakery.func_177570_a(); this.field_174955_d = (IBakedModel)this.field_174958_a.func_82594_a(ModelBakery.field_177604_a); + net.minecraftforge.client.ForgeHooksClient.onModelBake(this, this.field_174958_a, modelbakery); diff --git a/patches/minecraft/net/minecraft/client/resources/model/ModelRotation.java.patch b/patches/minecraft/net/minecraft/client/resources/model/ModelRotation.java.patch new file mode 100644 index 000000000..01c361669 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/resources/model/ModelRotation.java.patch @@ -0,0 +1,21 @@ +--- ../src-base/minecraft/net/minecraft/client/resources/model/ModelRotation.java ++++ ../src-work/minecraft/net/minecraft/client/resources/model/ModelRotation.java +@@ -10,7 +10,7 @@ + import net.minecraftforge.fml.relauncher.SideOnly; + + @SideOnly(Side.CLIENT) +-public enum ModelRotation ++public enum ModelRotation implements net.minecraftforge.client.model.IModelState, net.minecraftforge.client.model.ITransformation + { + X0_Y0(0, 0), + X0_Y90(0, 90), +@@ -122,4 +122,9 @@ + field_177546_q.put(Integer.valueOf(var3.field_177545_r), var3); + } + } ++ ++ public net.minecraftforge.client.model.TRSRTransformation apply(net.minecraftforge.client.model.IModelPart part) { return new net.minecraftforge.client.model.TRSRTransformation(getMatrix()); } ++ public javax.vecmath.Matrix4f getMatrix() { return new javax.vecmath.Matrix4f(func_177525_a()); } ++ public EnumFacing rotate(EnumFacing facing) { return func_177523_a(facing); } ++ public int rotate(EnumFacing facing, int vertexIndex) { return func_177520_a(facing, vertexIndex); } + } diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index 39b4d21a5..f93959cb7 100644 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -2,6 +2,14 @@ package net.minecraftforge.client; import static net.minecraftforge.common.ForgeVersion.Status.BETA; import static net.minecraftforge.common.ForgeVersion.Status.BETA_OUTDATED; +import static org.lwjgl.opengl.GL11.*; +import static org.lwjgl.opengl.GL20.*; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import javax.vecmath.Matrix4f; + import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.client.Minecraft; @@ -13,10 +21,18 @@ import net.minecraft.client.gui.GuiMainMenu; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.model.ModelBase; import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.block.model.ItemTransformVec3f; +import net.minecraft.client.renderer.entity.RenderItem; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.vertex.VertexFormatElement; +import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumUsage; import net.minecraft.client.resources.I18n; +import net.minecraft.client.resources.model.IBakedModel; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.settings.GameSettings; @@ -42,13 +58,17 @@ import net.minecraftforge.client.event.RenderHandEvent; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.client.event.TextureStitchEvent; import net.minecraftforge.client.event.sound.PlaySoundEvent; +import net.minecraftforge.client.model.IPerspectiveAwareModel; import net.minecraftforge.common.ForgeModContainer; import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.common.ForgeVersion.Status; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.fluids.FluidRegistry; import net.minecraftforge.fml.client.FMLClientHandler; +import net.minecraftforge.fml.common.FMLLog; +import org.apache.commons.lang3.tuple.Pair; +import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GL11; //import static net.minecraftforge.client.IItemRenderer.ItemRenderType.*; //import static net.minecraftforge.client.IItemRenderer.ItemRendererHelper.*; @@ -469,4 +489,138 @@ public class ForgeHooksClient { MinecraftForge.EVENT_BUS.post(new ModelBakeEvent(modelManager, modelRegistry, modelBakery)); } + + public static Matrix4f getMatrix(ItemTransformVec3f transform) + { + javax.vecmath.Matrix4f m = new javax.vecmath.Matrix4f(), t = new javax.vecmath.Matrix4f(); + m.setIdentity(); + m.setTranslation(transform.translation); + t.setIdentity(); + t.rotY(transform.rotation.y); + m.mul(t); + t.setIdentity(); + t.rotX(transform.rotation.x); + m.mul(t); + t.setIdentity(); + t.rotZ(transform.rotation.z); + m.mul(t); + t.setIdentity(); + t.m00 = transform.scale.x; + t.m11 = transform.scale.y; + t.m22 = transform.scale.z; + m.mul(t); + return m; + } + + public static IBakedModel handleCameraTransforms(IBakedModel model, ItemCameraTransforms.TransformType cameraTransformType) + { + if(model instanceof IPerspectiveAwareModel) + { + Pair pair = ((IPerspectiveAwareModel)model).handlePerspective(cameraTransformType); + + if(pair.getRight() != null) multiplyCurrentGlMatrix(pair.getRight()); + return pair.getLeft(); + } + switch(cameraTransformType) + { + case FIRST_PERSON: + RenderItem.applyVanillaTransform(model.getItemCameraTransforms().firstPerson); + break; + case GUI: + RenderItem.applyVanillaTransform(model.getItemCameraTransforms().gui); + break; + case HEAD: + RenderItem.applyVanillaTransform(model.getItemCameraTransforms().head); + break; + case THIRD_PERSON: + RenderItem.applyVanillaTransform(model.getItemCameraTransforms().thirdPerson); + break; + default: + break; + } + return model; + } + + private static final FloatBuffer matrixBuf = BufferUtils.createFloatBuffer(16); + + public static void multiplyCurrentGlMatrix(Matrix4f matrix) + { + matrixBuf.clear(); + float[] t = new float[4]; + for(int i = 0; i < 4; i++) + { + matrix.getColumn(i, t); + matrixBuf.put(t); + } + matrixBuf.flip(); + GL11.glMultMatrix(matrixBuf); + } + + // moved and expanded from WorldVertexBufferUploader.draw + + public static void preDraw(EnumUsage attrType, VertexFormatElement attr, int stride, ByteBuffer buffer) + { + buffer.position(attr.getOffset()); + switch(attrType) + { + case POSITION: + glVertexPointer(attr.getElementCount(), attr.getType().getGlConstant(), stride, buffer); + glEnableClientState(GL_VERTEX_ARRAY); + break; + case NORMAL: + if(attr.getElementCount() != 3) + { + throw new IllegalArgumentException("Normal attribute should have the size 3: " + attr); + } + glNormalPointer(attr.getType().getGlConstant(), stride, buffer); + glEnableClientState(GL_NORMAL_ARRAY); + break; + case COLOR: + glColorPointer(attr.getElementCount(), attr.getType().getGlConstant(), stride, buffer); + glEnableClientState(GL_COLOR_ARRAY); + break; + case UV: + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit + attr.getIndex()); + glTexCoordPointer(attr.getElementCount(), attr.getType().getGlConstant(), stride, buffer); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); + break; + case PADDING: + break; + case GENERIC: + glEnableVertexAttribArray(attr.getIndex()); + glVertexAttribPointer(attr.getIndex(), attr.getElementCount(), attr.getType().getGlConstant(), false, stride, buffer); + default: + FMLLog.severe("Unimplemented vanilla attribute upload: %s", attrType.getDisplayName()); + } + } + + public static void postDraw(EnumUsage attrType, VertexFormatElement attr, int stride, ByteBuffer buffer) + { + switch(attrType) + { + case POSITION: + glDisableClientState(GL_VERTEX_ARRAY); + break; + case NORMAL: + glDisableClientState(GL_NORMAL_ARRAY); + break; + case COLOR: + glDisableClientState(GL_COLOR_ARRAY); + // is this really needed? + GlStateManager.resetColor(); + break; + case UV: + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit + attr.getIndex()); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit); + break; + case PADDING: + break; + case GENERIC: + glDisableVertexAttribArray(attr.getIndex()); + default: + FMLLog.severe("Unimplemented vanilla attribute upload: %s", attrType.getDisplayName()); + } + } } diff --git a/src/main/java/net/minecraftforge/client/model/Attributes.java b/src/main/java/net/minecraftforge/client/model/Attributes.java new file mode 100644 index 000000000..90e0d7697 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/Attributes.java @@ -0,0 +1,107 @@ +package net.minecraftforge.client.model; + +import java.nio.ByteBuffer; +import java.util.List; + +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.renderer.vertex.VertexFormatElement; +import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumType; +import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumUsage; + +public class Attributes +{ + /* + * Default format of the data in IBakedModel + */ + public static final VertexFormat DEFAULT_BAKED_FORMAT; + + static + { + DEFAULT_BAKED_FORMAT = new VertexFormat(); + DEFAULT_BAKED_FORMAT.setElement(new VertexFormatElement(0, EnumType.FLOAT, EnumUsage.POSITION, 3)); + DEFAULT_BAKED_FORMAT.setElement(new VertexFormatElement(0, EnumType.UBYTE, EnumUsage.COLOR, 4)); + DEFAULT_BAKED_FORMAT.setElement(new VertexFormatElement(0, EnumType.FLOAT, EnumUsage.UV, 2)); + DEFAULT_BAKED_FORMAT.setElement(new VertexFormatElement(0, EnumType.BYTE, EnumUsage.PADDING, 4)); + } + + /* + * Can first format be used where second is expected + */ + public static boolean moreSpecific(VertexFormat first, VertexFormat second) + { + int size = first.getNextOffset(); + if(size != second.getNextOffset()) return false; + + int padding = 0; + int j = 0; + for(VertexFormatElement firstAttr : (List)first.getElements()) + { + while(j < second.getElementCount() && second.getElement(j).getUsage() == EnumUsage.PADDING) + { + padding += second.getElement(j++).getSize(); + } + if(j >= second.getElementCount() && padding == 0) + { + // if no padding is left, but there are still elements in first (we're processing one) - it doesn't fit + return false; + } + if(padding == 0) + { + // no padding - attributes have to match + VertexFormatElement secondAttr = second.getElement(j++); + if( + firstAttr.getIndex() != secondAttr.getIndex() || + firstAttr.getElementCount() != secondAttr.getElementCount() || + firstAttr.getType() != secondAttr.getType() || + firstAttr.getUsage() != secondAttr.getUsage()) + { + return false; + } + } + else + { + // padding - attribute should fit in it + padding -= firstAttr.getSize(); + if(padding < 0) return false; + } + } + + if(padding != 0 || j != second.getElementCount()) return false; + return true; + } + + public static void put(ByteBuffer buf, VertexFormatElement e, boolean normalize, Number fill, Number... ns) + { + if(e.getElementCount() > ns.length && fill == null) throw new IllegalArgumentException("not enough elements"); + Number n; + for(int i = 0; i < e.getElementCount(); i++) + { + if(i < ns.length) n = ns[i]; + else n = fill; + switch(e.getType()) + { + case BYTE: + buf.put(normalize ? (byte)(n.floatValue() / (Byte.MAX_VALUE - 1)) : n.byteValue()); + break; + case UBYTE: + buf.put(normalize ? (byte)(n.floatValue() / ((byte) -1)) : n.byteValue()); + break; + case SHORT: + buf.putShort(normalize ? (short)(n.floatValue() / (Short.MAX_VALUE - 1)) : n.shortValue()); + break; + case USHORT: + buf.putShort(normalize ? (short)(n.floatValue() / ((short) -1)) : n.shortValue()); + break; + case INT: + buf.putInt(normalize ? (int)(n.doubleValue() / (Integer.MAX_VALUE - 1)) : n.intValue()); + break; + case UINT: + buf.putInt(normalize ? (int)(n.doubleValue() / ((int) - 1)) : n.intValue()); + break; + case FLOAT: + buf.putFloat(n.floatValue()); + break; + } + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/ICustomModelLoader.java b/src/main/java/net/minecraftforge/client/model/ICustomModelLoader.java new file mode 100644 index 000000000..6dd74668f --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ICustomModelLoader.java @@ -0,0 +1,18 @@ +package net.minecraftforge.client.model; + +import net.minecraft.client.resources.IResourceManagerReloadListener; +import net.minecraft.util.ResourceLocation; + +public interface ICustomModelLoader extends IResourceManagerReloadListener +{ + /* + * Checks if given model should be loaded by this loader. + * Reading file contents is inadvisable, if possible decision should be made based on the location alone. + */ + public boolean accepts(ResourceLocation modelLocation); + + /* + * loads (or reloads) specified model + */ + public IModel loadModel(ResourceLocation modelLocation); +} diff --git a/src/main/java/net/minecraftforge/client/model/IFlexibleBakedModel.java b/src/main/java/net/minecraftforge/client/model/IFlexibleBakedModel.java new file mode 100644 index 000000000..e01989a56 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IFlexibleBakedModel.java @@ -0,0 +1,82 @@ +package net.minecraftforge.client.model; + +import java.util.List; + +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.util.EnumFacing; + +/* + * Version of IBakedModel with less restriction on camera transformations and with explicit format of the baked array. + */ +public interface IFlexibleBakedModel extends IBakedModel +{ + // non-erased versions of the IBakedModel methods + List getFaceQuads(EnumFacing side); + List getGeneralQuads(); + /* + * Specifies the format which BakedQuads' getVertexData will have. + */ + VertexFormat getFormat(); + + /* + * Default implementation of IFlexibleBakedModel that should be useful in most cases + */ + public static class Wrapper implements IFlexibleBakedModel + { + private final IBakedModel parent; + VertexFormat format; + + public Wrapper(IBakedModel parent, VertexFormat format) + { + this.parent = parent; + this.format = format; + } + + @SuppressWarnings("unchecked") + public List getFaceQuads(EnumFacing side) + { + return parent.getFaceQuads(side); + } + + @SuppressWarnings("unchecked") + public List getGeneralQuads() + { + return parent.getGeneralQuads(); + } + + public boolean isAmbientOcclusion() + { + return parent.isAmbientOcclusion(); + } + + public boolean isGui3d() + { + return parent.isGui3d(); + } + + public boolean isBuiltInRenderer() + { + return parent.isBuiltInRenderer(); + } + + public TextureAtlasSprite getTexture() + { + return parent.getTexture(); + } + + @Deprecated + public ItemCameraTransforms getItemCameraTransforms() + { + return parent.getItemCameraTransforms(); + } + + public VertexFormat getFormat() + { + return new VertexFormat(format); + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/IModel.java b/src/main/java/net/minecraftforge/client/model/IModel.java new file mode 100644 index 000000000..457f8a581 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IModel.java @@ -0,0 +1,45 @@ +package net.minecraftforge.client.model; + +import java.util.Collection; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.util.ResourceLocation; + +import com.google.common.base.Function; + +/* + * Interface for models that can be baked + * (possibly to different vertex formats and with different state). + */ +public interface IModel extends IModelPart +{ + /* + * Returns all model locations that this model depends on. + * Assume that returned collection is immutable. + */ + Collection getDependencies(); + + /* + * Returns all texture locations that this model depends on. + * Assume that returned collection is immutable. + */ + Collection getTextures(); + + /* + * All model texture coordinates should be resolved at this method. + * Returned model should be in the simplest form possible, for performance + * reasons (if it's not ISmartBlock/ItemModel - then it should be + * represented by List internally). + * Returned model's getFormat() can me less specific than the passed + * format argument (some attributes can be replaced with padding), + * if there's no such info in this model. + */ + IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter); + + /* + * Default state this model will be baked with. + * See IModelState. + */ + IModelState getDefaultState(); +} diff --git a/src/main/java/net/minecraftforge/client/model/IModelPart.java b/src/main/java/net/minecraftforge/client/model/IModelPart.java new file mode 100644 index 000000000..e8fe6bedd --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IModelPart.java @@ -0,0 +1,8 @@ +package net.minecraftforge.client.model; + +/* + * Represents the part of the model you can refer to, for example: mesh, + * skeleton, bone or some separately animated submodel. + * Primary function of this interface is the argument type of IModelState.apply. + */ +public interface IModelPart {} diff --git a/src/main/java/net/minecraftforge/client/model/IModelState.java b/src/main/java/net/minecraftforge/client/model/IModelState.java new file mode 100644 index 000000000..927d135c3 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IModelState.java @@ -0,0 +1,18 @@ +package net.minecraftforge.client.model; + +import javax.vecmath.Matrix4f; + +import com.google.common.base.Function; + +/* + * Represents the dynamic information associated with the model. + * Common use case is (possibly interpolated) animation frame. + */ +public interface IModelState extends Function +{ + /* + * returns the transformation (in the local coordinates) that needs to be applied to the specific part of the model. + */ + @Override + TRSRTransformation apply(IModelPart part); +} diff --git a/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java b/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java new file mode 100644 index 000000000..930437b3d --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/IPerspectiveAwareModel.java @@ -0,0 +1,22 @@ +package net.minecraftforge.client.model; + +import javax.vecmath.Matrix4f; + +import org.apache.commons.lang3.tuple.Pair; + +import net.minecraft.item.ItemStack; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType; +import net.minecraft.client.resources.model.IBakedModel; + +/* + * Model that changes based on the rendering perspective + * (first-person, GUI, e.t.c - see TransformType) + */ +public interface IPerspectiveAwareModel extends IBakedModel +{ + /* + * Returns the pair of the model for the given perspective, and the matrix + * that should be applied to the GL state before rendering it (matrix may be null). + */ + Pair handlePerspective(TransformType cameraTransformType); +} diff --git a/src/main/java/net/minecraftforge/client/model/ITransformation.java b/src/main/java/net/minecraftforge/client/model/ITransformation.java new file mode 100644 index 000000000..36f22b03f --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ITransformation.java @@ -0,0 +1,18 @@ +package net.minecraftforge.client.model; + +import javax.vecmath.Matrix4f; + +import net.minecraft.util.EnumFacing; + +/* + * Replacement interface for ModelRotation to allow custom transformations of vanilla models. + * You should probably use TRSRTransformation directly. + */ +public interface ITransformation +{ + public Matrix4f getMatrix(); + + public EnumFacing rotate(EnumFacing facing); + + public int rotate(EnumFacing facing, int vertexIndex); +} diff --git a/src/main/java/net/minecraftforge/client/model/MapModelState.java b/src/main/java/net/minecraftforge/client/model/MapModelState.java new file mode 100644 index 000000000..72c874e0b --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/MapModelState.java @@ -0,0 +1,31 @@ +package net.minecraftforge.client.model; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +/* + * Simple implementation of IModelState via a map and a default value. + */ +public class MapModelState implements IModelState +{ + private final ImmutableMap map; + private final TRSRTransformation def; + + public MapModelState(Map map) + { + this(map, TRSRTransformation.identity()); + } + + public MapModelState(Map map, TRSRTransformation def) + { + this.map = ImmutableMap.copyOf(map); + this.def = def; + } + + public TRSRTransformation apply(IModelPart part) + { + if(!map.containsKey(part)) return def; + return map.get(part); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoader.java b/src/main/java/net/minecraftforge/client/model/ModelLoader.java new file mode 100644 index 000000000..72190cb86 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ModelLoader.java @@ -0,0 +1,399 @@ +package net.minecraftforge.client.model; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.BlockModelShapes; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.block.model.ItemModelGenerator; +import net.minecraft.client.renderer.block.model.ModelBlock; +import net.minecraft.client.renderer.block.model.ModelBlockDefinition; +import net.minecraft.client.renderer.block.model.ModelBlockDefinition.MissingVariantException; +import net.minecraft.client.renderer.block.model.ModelBlockDefinition.Variant; +import net.minecraft.client.renderer.block.model.ModelBlockDefinition.Variants; +import net.minecraft.client.renderer.texture.IIconCreator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.model.BuiltInModel; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.ModelRotation; +import net.minecraft.client.resources.model.WeightedBakedModel; +import net.minecraft.item.Item; +import net.minecraft.util.IRegistry; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.FMLLog; +import net.minecraftforge.fml.common.registry.GameData; + +import org.apache.logging.log4j.Level; + +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +public class ModelLoader extends ModelBakery +{ + private final Map stateModels = new HashMap(); + private final Set resolveTextures = new HashSet(); + private final Set uvLocked = new HashSet(); + private final Set textures = new HashSet(); + private final Set loadingModels = new HashSet(); + + public ModelLoader(IResourceManager manager, TextureMap map, BlockModelShapes shapes) + { + super(manager, map, shapes); + VanillaLoader.instance.setLoader(this); + ModelLoaderRegistry.clearModelCache(); + } + + @Override + public IRegistry setupModelRegistry() + { + loadBlocks(); + loadItems(); + stateModels.put(MODEL_MISSING, getModel(new ResourceLocation(MODEL_MISSING.getResourceDomain(), MODEL_MISSING.getResourcePath()))); + textures.remove(TextureMap.LOCATION_MISSING_TEXTURE); + textures.addAll(LOCATIONS_BUILTIN_TEXTURES); + textureMap.loadSprites(resourceManager, new IIconCreator() + { + public void registerSprites(TextureMap map) + { + for(ResourceLocation t : textures) + { + sprites.put(t, map.registerSprite(t)); + } + } + }); + sprites.put(new ResourceLocation("missingno"), textureMap.getMissingSprite()); + Function textureGetter = Functions.forMap(sprites, textureMap.getMissingSprite()); + for(Entry e : stateModels.entrySet()) + { + bakedRegistry.putObject(e.getKey(), e.getValue().bake(e.getValue().getDefaultState(), Attributes.DEFAULT_BAKED_FORMAT, textureGetter)); + } + return bakedRegistry; + } + + private void loadBlocks() + { + Map stateMap = blockModelShapes.getBlockStateMapper().putAllStateModelLocations(); + loadVariants(stateMap.values()); + } + + @Override + protected void registerVariant(ModelBlockDefinition definition, ModelResourceLocation location) + { + Variants variants = null; + try + { + variants = definition.getVariants(location.getVariant()); + } + catch(MissingVariantException e) {} + if(variants == null) + { + // adding default variant for simple blocks + ResourceLocation loc = new ResourceLocation(location.getResourceDomain(), "block/" + location.getResourcePath()); + variants = new Variants("normal", Lists.newArrayList(new Variant(loc, ModelRotation.X0_Y0, false, 1))); + } + if(!variants.getVariants().isEmpty()) + { + try + { + stateModels.put(location, new WeightedRandomModel(variants)); + } + catch(Throwable e) + { + throw new RuntimeException(e); + } + } + } + + private void loadItems() + { + registerVariantNames(); + for(Item item : GameData.getItemRegistry().typeSafeIterable()) + { + for(String s : (List)getVariantNames(item)) + { + ResourceLocation file = getItemLocation(s); + ModelResourceLocation memory = new ModelResourceLocation(s, "inventory"); + resolveTextures.add(ModelLoaderRegistry.getActualLocation(file)); + IModel model = getModel(file); + if(model != null) stateModels.put(memory, model); + } + } + } + + public IModel getModel(ResourceLocation location) + { + if(!ModelLoaderRegistry.loaded(location)) loadAnyModel(location); + return ModelLoaderRegistry.getModel(location); + } + + @Override + protected ResourceLocation getModelLocation(ResourceLocation model) + { + return new ResourceLocation(model.getResourceDomain(), model.getResourcePath() + ".json"); + } + + private void loadAnyModel(ResourceLocation location) + { + if(loadingModels.contains(location)) + { + throw new IllegalStateException("circular model dependencies involving model " + location); + } + loadingModels.add(location); + IModel model = ModelLoaderRegistry.getModel(location); + for(ResourceLocation dep : model.getDependencies()) + { + getModel(dep); + } + textures.addAll(model.getTextures()); + loadingModels.remove(location); + } + + private class VanillaModelWrapper implements IModel + { + private final ResourceLocation location; + private final ModelBlock model; + + public VanillaModelWrapper(ResourceLocation location, ModelBlock model) + { + this.location = location; + this.model = model; + } + + public Collection getDependencies() + { + if(model.getParentLocation() == null || model.getParentLocation().getResourcePath().startsWith("builtin/")) return Collections.emptyList(); + return Collections.singletonList(model.getParentLocation()); + } + + public Collection getTextures() + { + // setting parent here to make textures resolve properly + if(model.getParentLocation() != null) + { + IModel parent = getModel(model.getParentLocation()); + if(parent instanceof VanillaModelWrapper) + { + model.parent = ((VanillaModelWrapper) parent).model; + } + else + { + throw new IllegalStateException("vanilla model" + model + "can't have non-vanilla parent"); + } + } + + if(!resolveTextures.contains(location)) return Collections.emptyList(); + + ImmutableSet.Builder builder = ImmutableSet.builder(); + builder.add(new ResourceLocation(model.resolveTextureName("particle"))); + + if(hasItemModel(model)) + { + for(String s : (List)ItemModelGenerator.LAYERS) + { + String r = model.resolveTextureName(s); + ResourceLocation loc = new ResourceLocation(r); + if(!r.equals(s)) + { + builder.add(loc); + } + // mojang hardcode + if(model.getRootModel() == MODEL_COMPASS && !loc.equals(TextureMap.LOCATION_MISSING_TEXTURE)) + { + TextureAtlasSprite.setLocationNameCompass(loc.toString()); + } + else if(model.getRootModel() == MODEL_CLOCK && !loc.equals(TextureMap.LOCATION_MISSING_TEXTURE)) + { + TextureAtlasSprite.setLocationNameClock(loc.toString()); + } + } + } + if(location.getResourcePath().startsWith("models/block/") || !ModelLoader.this.isBuiltinModel(model.getRootModel())) + { + builder.addAll(ModelLoader.this.getTextureLocations(model)); + } + return builder.build(); + } + + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) + { + if(!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT)) + { + throw new IllegalArgumentException("can't bake vanilla models to the format that doesn't fit into the default one: " + format); + } + ModelBlock model = this.model; + if(hasItemModel(model)) model = makeItemModel(model); + if(isCustomRenderer(model)) return new IFlexibleBakedModel.Wrapper(new BuiltInModel(new ItemCameraTransforms(model.getThirdPersonTransform(), model.getFirstPersonTransform(), model.getHeadTransform(), model.getInGuiTransform())), Attributes.DEFAULT_BAKED_FORMAT); + return new IFlexibleBakedModel.Wrapper(bakeModel(model, state.apply(this), uvLocked.contains(location)), Attributes.DEFAULT_BAKED_FORMAT); + } + + public IModelState getDefaultState() + { + return ModelRotation.X0_Y0; + } + } + + // Weighted models can contain multiple copies of 1 model with different rotations - this is to make it work with IModelState (different copies will be different objects). + private static class WeightedPartWrapper implements IModel + { + private final IModel model; + + public WeightedPartWrapper(IModel model) + { + this.model = model; + } + + public Collection getDependencies() + { + return model.getDependencies(); + } + + public Collection getTextures() + { + return model.getTextures(); + } + + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) + { + return model.bake(state, format, bakedTextureGetter); + } + + public IModelState getDefaultState() + { + return model.getDefaultState(); + } + } + + private class WeightedRandomModel implements IModel + { + private final List variants; + private final List locations = new ArrayList(); + private final List models = new ArrayList(); + private final IModelState defaultState; + + public WeightedRandomModel(Variants variants) + { + this.variants = variants.getVariants(); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for(Variant v : (List)variants.getVariants()) + { + ResourceLocation loc = v.getModelLocation(); + resolveTextures.add(ModelLoaderRegistry.getActualLocation(loc)); + locations.add(loc); + IModel model = new WeightedPartWrapper(getModel(loc)); + models.add(model); + builder.put(model, new TRSRTransformation(v.getRotation())); + if(v.isUvLocked()) uvLocked.add(ModelLoaderRegistry.getActualLocation(loc)); + } + defaultState = new MapModelState(builder.build()); + } + + public Collection getDependencies() + { + return ImmutableList.copyOf(locations); + } + + public Collection getTextures() + { + /*ImmutableSet.Builder builder = ImmutableSet.builder(); + for(ResourceLocation loc : locations) + { + builder.addAll(getModel(loc).getTextures()); + } + return builder.build();*/ + return Collections.emptyList(); + } + + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) + { + if(!Attributes.moreSpecific(format, Attributes.DEFAULT_BAKED_FORMAT)) + { + throw new IllegalArgumentException("can't bake vanilla weighted models to the format that doesn't fit into the default one: " + format); + } + if(variants.size() == 1) + { + Variant v = variants.get(0); + IModel model = models.get(0); + return model.bake(state.apply(model), format, bakedTextureGetter); + } + WeightedBakedModel.Builder builder = new WeightedBakedModel.Builder(); + for(int i = 0; i < variants.size(); i++) + { + IModel model = models.get(i); + builder.add(model.bake(state.apply(model), format, bakedTextureGetter), variants.get(i).getWeight()); + } + return new IFlexibleBakedModel.Wrapper(builder.build(), Attributes.DEFAULT_BAKED_FORMAT); + } + + public IModelState getDefaultState() + { + return defaultState; + } + } + + private boolean isBuiltinModel(ModelBlock model) + { + return model == MODEL_GENERATED || model == MODEL_COMPASS || model == MODEL_CLOCK || model == MODEL_ENTITY; + } + + public IModel getMissingModel() + { + return getModel(new ResourceLocation(MODEL_MISSING.getResourceDomain(), MODEL_MISSING.getResourcePath())); + } + + static enum VanillaLoader implements ICustomModelLoader + { + instance; + + private ModelLoader loader; + + void setLoader(ModelLoader loader) + { + this.loader = loader; + } + + ModelLoader getLoader() + { + return loader; + } + + public void onResourceManagerReload(IResourceManager resourceManager) + { + // do nothing, cause loader will store the reference to the resourceManager + } + + public boolean accepts(ResourceLocation modelLocation) + { + return true; + } + + public IModel loadModel(ResourceLocation modelLocation) + { + try + { + return loader.new VanillaModelWrapper(modelLocation, loader.loadModel(modelLocation)); + } + catch(IOException e) + { + FMLLog.log(Level.ERROR, e, "Exception loading model %s with vanilla loader, skipping", modelLocation); + return loader.getMissingModel(); + } + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java new file mode 100644 index 000000000..db2410472 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java @@ -0,0 +1,112 @@ +package net.minecraftforge.client.model; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IReloadableResourceManager; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.IResourceManagerReloadListener; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.ModelLoader.VanillaLoader; +import net.minecraftforge.fml.common.FMLLog; + +import org.apache.logging.log4j.Level; + +/* + * Central hub for custom model loaders. + */ +public class ModelLoaderRegistry +{ + private static final Set loaders = new HashSet(); + private static final Map cache = new HashMap(); + + /* + * Makes system aware of your loader. + */ + public static void registerLoader(ICustomModelLoader loader) + { + loaders.add(loader); + ((IReloadableResourceManager)Minecraft.getMinecraft().getResourceManager()).registerReloadListener(new IResourceManagerReloadListener() + { + public void onResourceManagerReload(IResourceManager manager) + { + for(ICustomModelLoader loader : loaders) loader.onResourceManagerReload(manager); + } + }); + } + + public static boolean loaded(ResourceLocation location) + { + return cache.containsKey(location); + } + + + public static ResourceLocation getActualLocation(ResourceLocation location) + { + if(location.getResourcePath().startsWith("builtin/")) return location; + return new ResourceLocation(location.getResourceDomain(), "models/" + location.getResourcePath()); + } + + public static IModel getModel(ResourceLocation location) + { + ResourceLocation actual = getActualLocation(location); + if(cache.containsKey(location)) return cache.get(location); + ICustomModelLoader accepted = null; + for(ICustomModelLoader loader : loaders) + { + try + { + if(loader.accepts(actual)) + { + if(accepted != null) + { + FMLLog.severe("2 loaders (%s and %s) want to load the same model %s", accepted, loader, location); + throw new IllegalStateException("2 loaders want to load the same model"); + } + accepted = loader; + } + } + catch(Exception e) + { + FMLLog.log(Level.ERROR, e, "Exception checking if model %s can be loaded with loader %s, skipping", location, loader); + } + } + + // no custom loaders found, try vanilla one + if(accepted == null) + { + if(VanillaLoader.instance.accepts(actual)) accepted = VanillaLoader.instance; + } + + IModel model; + if(accepted == null) + { + FMLLog.severe("no suitable loader found for the model %s, skipping", location); + model = getMissingModel(); + } + else try + { + model = accepted.loadModel(actual); + } + catch(Exception e) + { + FMLLog.log(Level.ERROR, e, "Exception loading model %s with loader %s, skipping", location, accepted); + model = getMissingModel(); + } + cache.put(location, model); + return model; + } + + public static IModel getMissingModel() + { + return ModelLoader.VanillaLoader.instance.getLoader().getMissingModel(); + } + + public static void clearModelCache() + { + cache.clear(); + } +} diff --git a/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java b/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java new file mode 100644 index 000000000..c9c356fb8 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java @@ -0,0 +1,445 @@ +package net.minecraftforge.client.model; + +import javax.vecmath.Matrix3f; +import javax.vecmath.Matrix4f; +import javax.vecmath.Quat4f; +import javax.vecmath.Vector3f; +import javax.vecmath.Vector4f; + +import net.minecraft.client.renderer.block.model.ItemTransformVec3f; +import net.minecraft.client.resources.model.ModelRotation; +import net.minecraft.util.EnumFacing; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; + +/* + * Interpolation-friendly affine transformation. + * If created with matrix, should successfully decompose it to a composition + * of easily interpolatable transformations (translation, first rotation, scale + * (with generally speaking different factors for each axis) and second rotation. + * If the inpit matrix is a composition of translation, rotation and scale (in + * any order), then the interpolation of the derived primitive transformations + * should result in the same transformation as the interpolation of the originals. + * Decomposition happens lazily (and is hopefully fast enough), so performance + * should be comparable to using Matrix4f directly. + * Immutable. + */ +public class TRSRTransformation implements IModelState, ITransformation +{ + private final Matrix4f matrix; + + private boolean full; + private Vector3f translation; + private Quat4f leftRot; + private Vector3f scale; + private Quat4f rightRot; + + public TRSRTransformation(Matrix4f matrix) + { + this.matrix = matrix; + } + + public TRSRTransformation(Vector3f translation, Quat4f leftRot, Vector3f scale, Quat4f rightRot) + { + this.matrix = mul(translation, leftRot, scale, rightRot); + this.translation = translation != null ? translation : new Vector3f(); + this.leftRot = leftRot != null ? leftRot : new Quat4f(0, 0, 0, 1); + this.scale = scale != null ? scale : new Vector3f(1, 1, 1); + this.rightRot = rightRot!= null ? rightRot : new Quat4f(0, 0, 0, 1); + full = true; + } + + public TRSRTransformation(ItemTransformVec3f transform) + { + this(transform.translation, quatFromYXZ(transform.rotation), transform.scale, null); + } + + public TRSRTransformation(ModelRotation rotation) + { + this(new Matrix4f(rotation.getMatrix4d())); + } + + private static final TRSRTransformation identity; + + static + { + Matrix4f m = new Matrix4f(); + m.setIdentity(); + identity = new TRSRTransformation(m); + identity.getLeftRot(); + } + + public static TRSRTransformation identity() + { + return identity; + } + + public TRSRTransformation compose(TRSRTransformation b) + { + Matrix4f m = getMatrix(); + m.mul(b.getMatrix()); + return new TRSRTransformation(m); + } + + private void genCheck() + { + if(!full) + { + Pair pair = toAffine(matrix); + Triple triple = svdDecompose(pair.getLeft()); + this.translation = pair.getRight(); + this.leftRot = triple.getLeft(); + this.scale = triple.getMiddle(); + this.rightRot = triple.getRight(); + full = true; + } + } + + public static Quat4f quatFromYXZ(Vector3f yxz) + { + return quatFromYXZ(yxz.y, yxz.x, yxz.z); + } + + public static Quat4f quatFromYXZ(float y, float x, float z) + { + Quat4f ret = new Quat4f(0, 0, 0, 1), t = new Quat4f(); + t.set(0, (float)Math.sin(y/2), 0, (float)Math.cos(y/2)); + ret.mul(t); + t.set((float)Math.sin(x/2), 0, 0, (float)Math.cos(x/2)); + ret.mul(t); + t.set(0, 0, (float)Math.sin(z/2), (float)Math.cos(z/2)); + ret.mul(t); + return ret; + } + + public static Vector3f toYXZ(Quat4f q) + { + float w2 = q.w * q.w; + float x2 = q.x * q.x; + float y2 = q.y * q.y; + float z2 = q.z * q.z; + float l = w2 + x2 + y2 + z2; + float sx = 2 * q.y * q.z - 2 * q.w * q.x; + float x = (float)Math.asin(sx / l); + if(Math.abs(sx) > .999f * l) + { + return new Vector3f( + 2 * (float)Math.atan2(q.y, q.w), + x, + 0 + ); + } + return new Vector3f( + (float)Math.atan2(2 * q.x * q.z + 2 * q.y * q.w, w2 - x2 - y2 + z2), + x, + (float)Math.atan2(2 * q.x * q.y + 2 * q.w * q.z, w2 - x2 + y2 - z2) + ); + } + + public static Matrix4f mul(Vector3f translation, Quat4f leftRot, Vector3f scale, Quat4f rightRot) + { + Matrix4f res = new Matrix4f(), t = new Matrix4f(); + res.setIdentity(); + if(translation != null) res.setTranslation(translation); + if(leftRot != null) + { + t.set(leftRot); + res.mul(t); + } + if(scale != null) + { + t.setIdentity(); + t.m00 = scale.x; + t.m11 = scale.y; + t.m22 = scale.z; + res.mul(t); + } + if(rightRot != null) + { + t.set(rightRot); + res.mul(t); + } + return res; + } + + /* + * Performs SVD decomposition of m, accumulating reflection in the scale (U and V are pure rotations). + */ + public static Triple svdDecompose(Matrix3f m) + { + // determine V by doing 5 steps of Jacobi iteration on MT * M + Quat4f u = new Quat4f(0, 0, 0, 1), v = new Quat4f(0, 0, 0, 1), qt = new Quat4f(); + Matrix3f b = new Matrix3f(m), t = new Matrix3f(); + t.transpose(m); + b.mul(t, b); + + for(int i = 0; i < 5; i++) v.mul(stepJacobi(b)); + + v.normalize(); + t.set(v); + b.set(m); + b.mul(t); + + sortSingularValues(b, v); + + Pair p; + + float ul = 1f; + + p = qrGivensQuat(b.m00, b.m10); + qt.set(0, 0, p.getLeft(), p.getRight()); + u.mul(qt); + t.setIdentity(); + t.m00 = qt.w * qt.w - qt.z * qt.z; + t.m11 = t.m00; + t.m10 = -2 * qt.z * qt.w; + t.m01 = -t.m10; + t.m22 = qt.w * qt.w + qt.z * qt.z; + ul *= t.m22; + b.mul(t, b); + + p = qrGivensQuat(b.m00, b.m20); + qt.set(0, -p.getLeft(), 0, p.getRight()); + u.mul(qt); + t.setIdentity(); + t.m00 = qt.w * qt.w - qt.y * qt.y; + t.m22 = t.m00; + t.m20 = 2 * qt.y * qt.w; + t.m02 = -t.m20; + t.m11 = qt.w * qt.w + qt.y * qt.y; + ul *= t.m11; + b.mul(t, b); + + p = qrGivensQuat(b.m11, b.m21); + qt.set(p.getLeft(), 0, 0, p.getRight()); + u.mul(qt); + t.setIdentity(); + t.m11 = qt.w * qt.w - qt.x * qt.x; + t.m22 = t.m11; + t.m21 = -2 * qt.x * qt.w; + t.m12 = -t.m21; + t.m00 = qt.w * qt.w + qt.x * qt.x; + ul *= t.m00; + b.mul(t, b); + + ul = 1f / ul; + u.scale((float)Math.sqrt(ul)); + + Vector3f s = new Vector3f(b.m00 * ul, b.m11 * ul, b.m22 * ul); + + return Triple.of(u, s, v); + } + + private static float rsqrt(float f) + { + float f2 = .5f * f; + int i = Float.floatToIntBits(f); + i = 0x5f3759df - (i >> 1); + f = Float.intBitsToFloat(i); + f *= 1.5f - f2 * f * f; + return f; + } + + private static final float eps = 1e-7f; + private static final float g = 3f + 2f * (float)Math.sqrt(2); + private static final float cs = (float)Math.cos(Math.PI / 8); + private static final float ss = (float)Math.sin(Math.PI / 8); + private static final float sq2 = 1f / (float)Math.sqrt(2); + + private static Pair approxGivensQuat(float a11, float a12, float a22) + { + float ch = 2f * (a11 - a22); + float sh = a12; + boolean b = g * sh * sh < ch * ch; + float w = rsqrt(sh * sh + ch * ch); + ch = b ? w * ch : cs; + sh = b ? w * sh : ss; + return Pair.of(sh, ch); + } + + private static final void swapNeg(Matrix3f m, int i, int j) + { + float[] t = new float[3]; + m.getColumn(j, t); + for(int k = 0; k < 3; k++) + { + m.setElement(k, j, -m.getElement(k, i)); + } + m.setColumn(i, t); + } + + private static void sortSingularValues(Matrix3f b, Quat4f v) + { + float p0 = b.m00 * b.m00 + b.m10 * b.m10 + b.m20 * b.m20; + float p1 = b.m01 * b.m01 + b.m11 * b.m11 + b.m21 * b.m21; + float p2 = b.m02 * b.m02 + b.m12 * b.m12 + b.m22 * b.m22; + Quat4f t = new Quat4f(); + if(p0 < p1) + { + swapNeg(b, 0, 1); + t.set(0, 0, sq2, sq2); + v.mul(t); + float f = p0; + p0 = p1; + p1 = f; + } + if(p0 < p2) + { + swapNeg(b, 0, 2); + t.set(0, sq2, 0, sq2); + v.mul(t); + float f = p0; + p0 = p2; + p2 = f; + } + if(p1 < p2) + { + swapNeg(b, 1, 2); + t.set(sq2, 0, 0, sq2); + v.mul(t); + } + } + + private static Pair qrGivensQuat(float a1, float a2) + { + float p = (float)Math.sqrt(a1 * a1 + a2 * a2); + float sh = p > eps ? a2 : 0; + float ch = Math.abs(a1) + Math.max(p, eps); + if(a1 < 0) + { + float f = sh; + sh = ch; + ch = f; + } + //float w = 1.f / (float)Math.sqrt(ch * ch + sh * sh); + float w = rsqrt(ch * ch + sh * sh); + ch *= w; + sh *= w; + return Pair.of(sh, ch); + } + + private static Quat4f stepJacobi(Matrix3f m) + { + Matrix3f t = new Matrix3f(); + Quat4f qt = new Quat4f(), ret = new Quat4f(0, 0, 0, 1); + Pair p; + // 01 + p = approxGivensQuat(m.m00, .5f * (m.m01 + m.m10), m.m11); + qt.set(0, 0, p.getLeft(), p.getRight()); + //qt.normalize(); + ret.mul(qt); + //t.set(qt); + t.setIdentity(); + t.m00 = qt.w * qt.w - qt.z * qt.z; + t.m11 = t.m00; + t.m10 = 2 * qt.z * qt.w; + t.m01 = -t.m10; + t.m22 = qt.w * qt.w + qt.z * qt.z; + m.mul(m, t); + t.transpose(); + m.mul(t, m); + // 02 + p = approxGivensQuat(m.m00, .5f * (m.m02 + m.m20), m.m22); + qt.set(0, -p.getLeft(), 0, p.getRight()); + //qt.normalize(); + ret.mul(qt); + //t.set(qt); + t.setIdentity(); + t.m00 = qt.w * qt.w - qt.y * qt.y; + t.m22 = t.m00; + t.m20 = -2 * qt.y * qt.w; + t.m02 = -t.m20; + t.m11 = qt.w * qt.w + qt.y * qt.y; + m.mul(m, t); + t.transpose(); + m.mul(t, m); + // 12 + p = approxGivensQuat(m.m11, .5f * (m.m12 + m.m21), m.m22); + qt.set(p.getLeft(), 0, 0, p.getRight()); + //qt.normalize(); + ret.mul(qt); + //t.set(qt); + t.setIdentity(); + t.m11 = qt.w * qt.w - qt.x * qt.x; + t.m22 = t.m11; + t.m21 = 2 * qt.x * qt.w; + t.m12 = -t.m21; + t.m00 = qt.w * qt.w + qt.x * qt.x; + m.mul(m, t); + t.transpose(); + m.mul(t, m); + return ret; + } + + /* + * Divides m by m33, sets last row to (0, 0, 0, 1), extracts linear and translation parts + */ + public static Pair toAffine(Matrix4f m) + { + m.mul(1.f / m.m33); + Vector3f trans = new Vector3f(m.m03, m.m13, m.m23); + Matrix3f linear = new Matrix3f(m.m00, m.m01, m.m02, m.m10, m.m11, m.m12, m.m20, m.m21, m.m22); + return Pair.of(linear, trans); + } + + /* + * Don't use this if you don't need to, conversion is lossy (second rotation component is lost). + */ + public ItemTransformVec3f toItemTransform() + { + return new ItemTransformVec3f(getTranslation(), toYXZ(getLeftRot()), getScale()); + } + + public Matrix4f getMatrix() + { + return (Matrix4f)matrix.clone(); + } + + public Vector3f getTranslation() + { + genCheck(); + return (Vector3f)translation.clone(); + } + + public Quat4f getLeftRot() + { + genCheck(); + return (Quat4f)leftRot.clone(); + } + + public Vector3f getScale() + { + genCheck(); + return (Vector3f)scale.clone(); + } + + public Quat4f getRightRot() + { + genCheck(); + return (Quat4f)rightRot.clone(); + } + + public TRSRTransformation apply(IModelPart part) + { + return this; + } + + public EnumFacing rotate(EnumFacing facing) + { + return rotate(matrix, facing); + } + + public static EnumFacing rotate(Matrix4f matrix, EnumFacing facing) + { + Vector4f vec = new Vector4f(facing.getDirectionVec().getX(), facing.getDirectionVec().getY(), facing.getDirectionVec().getZ(), 1); + matrix.transform(vec); + return EnumFacing.getFacingFromVector(vec.x / vec.w, vec.y / vec.w, vec.z / vec.w); + } + + public int rotate(EnumFacing facing, int vertexIndex) + { + // FIXME check if this is good enough + return vertexIndex; + } +} diff --git a/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java b/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java new file mode 100644 index 000000000..1164197b9 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java @@ -0,0 +1,607 @@ +package net.minecraftforge.client.model.b3d; + +import static net.minecraftforge.client.model.b3d.B3DModel.logger; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import javax.vecmath.Matrix4f; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.renderer.vertex.VertexFormatElement; +import net.minecraft.client.resources.IResource; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.model.Attributes; +import net.minecraftforge.client.model.ICustomModelLoader; +import net.minecraftforge.client.model.IFlexibleBakedModel; +import net.minecraftforge.client.model.IModel; +import net.minecraftforge.client.model.IModelPart; +import net.minecraftforge.client.model.IModelState; +import net.minecraftforge.client.model.ISmartBlockModel; +import net.minecraftforge.client.model.ISmartItemModel; +import net.minecraftforge.client.model.ModelLoaderRegistry; +import net.minecraftforge.client.model.TRSRTransformation; +import net.minecraftforge.client.model.b3d.B3DModel.Animation; +import net.minecraftforge.client.model.b3d.B3DModel.Bone; +import net.minecraftforge.client.model.b3d.B3DModel.Face; +import net.minecraftforge.client.model.b3d.B3DModel.IKind; +import net.minecraftforge.client.model.b3d.B3DModel.Key; +import net.minecraftforge.client.model.b3d.B3DModel.Mesh; +import net.minecraftforge.client.model.b3d.B3DModel.Node; +import net.minecraftforge.client.model.b3d.B3DModel.Texture; +import net.minecraftforge.client.model.b3d.B3DModel.Vertex; +import net.minecraftforge.common.property.IExtendedBlockState; +import net.minecraftforge.common.property.IUnlistedProperty; +import net.minecraftforge.fml.common.FMLLog; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; +import org.apache.logging.log4j.Level; +import org.lwjgl.BufferUtils; + +import com.google.common.base.Function; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Multimap; + +/* + * Loader for Blitz3D models. + * To enable for your mod call instance.addDomain(modid). + * If you need more control over accepted resources - extend the class, and register a new instance with ModelLoaderRegistry. + */ +public class B3DLoader implements ICustomModelLoader +{ + public static final B3DLoader instance = new B3DLoader(); + + private IResourceManager manager; + + private final Set enabledDomains = new HashSet(); + private final Map cache = new HashMap(); + + public B3DLoader() + { + ModelLoaderRegistry.registerLoader(this); + } + + public void addDomain(String domain) + { + enabledDomains.add(domain.toLowerCase()); + } + + public void onResourceManagerReload(IResourceManager manager) + { + this.manager = manager; + cache.clear(); + } + + public boolean accepts(ResourceLocation modelLocation) + { + return enabledDomains.contains(modelLocation.getResourceDomain()) && modelLocation.getResourcePath().endsWith(".b3d"); + } + + @SuppressWarnings("unchecked") + public IModel loadModel(ResourceLocation modelLocation) + { + ResourceLocation file = new ResourceLocation(modelLocation.getResourceDomain(), modelLocation.getResourcePath()); + if(!cache.containsKey(file)) + { + try + { + IResource resource = null; + try + { + resource = manager.getResource(file); + } + catch(FileNotFoundException e) + { + if(modelLocation.getResourcePath().startsWith("models/block/")) + resource = manager.getResource(new ResourceLocation(file.getResourceDomain(), "models/item/" + file.getResourcePath().substring("models/block/".length()))); + else if(modelLocation.getResourcePath().startsWith("models/item/")) + resource = manager.getResource(new ResourceLocation(file.getResourceDomain(), "models/block/" + file.getResourcePath().substring("models/item/".length()))); + else throw e; + } + B3DModel.Parser parser = new B3DModel.Parser(resource.getInputStream()); + B3DModel model = parser.parse(); + cache.put(modelLocation, model); + } + catch(IOException e) + { + FMLLog.log(Level.ERROR, e, "Exception loading model %s with B3D loader, skipping", modelLocation); + cache.put(modelLocation, null); + } + } + B3DModel model = cache.get(file); + if(model == null) return ModelLoaderRegistry.getMissingModel(); + if(modelLocation instanceof B3DMeshLocation) + { + String mesh = ((B3DMeshLocation)modelLocation).getMesh(); + if(!model.getMeshes().containsKey(mesh)) + { + FMLLog.severe("No mesh named %s in model %s, skipping", mesh, modelLocation); + return ModelLoaderRegistry.getMissingModel(); + } + return new Wrapper(modelLocation, model.getTextures(), model.getMeshes().get(mesh)); + } + if(!(model.getRoot().getKind() instanceof Mesh)) + { + FMLLog.severe("No root mesh in model %s and no mesh name in location, skipping", modelLocation); + return ModelLoaderRegistry.getMissingModel(); + } + return new Wrapper(modelLocation, model.getTextures(), (Node)model.getRoot()); + } + + public static class B3DMeshLocation extends ResourceLocation + { + public final String mesh; + + public B3DMeshLocation(String domain, String path, String mesh) + { + super(domain, path); + this.mesh = mesh; + } + + public String getMesh() + { + return mesh; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((mesh == null) ? 0 : mesh.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (!super.equals(obj)) return false; + if (getClass() != obj.getClass()) return false; + B3DMeshLocation other = (B3DMeshLocation) obj; + if (mesh == null) + { + if (other.mesh != null) return false; + } + else if (!mesh.equals(other.mesh)) return false; + return true; + } + } + + public static class B3DState implements IModelState + { + private final Animation animation; + private final int frame; + + public B3DState(Animation animation, int frame) + { + this.animation = animation; + this.frame = frame; + } + + public Animation getAnimation() + { + return animation; + } + + public int getFrame() + { + return frame; + } + + public TRSRTransformation apply(IModelPart part) + { + if(!(part instanceof PartWrapper)) + { + throw new IllegalArgumentException("B3DState can only be applied to b3d models"); + } + return getNodeMatrix(((PartWrapper)part).getNode()); + } + + private static LoadingCache, Integer>, TRSRTransformation> cache = CacheBuilder.newBuilder() + .maximumSize(16384) + .expireAfterAccess(2, TimeUnit.MINUTES) + .build(new CacheLoader, Integer>, TRSRTransformation>() + { + public TRSRTransformation load(Triple, Integer> key) throws Exception + { + return getNodeMatrix(key.getLeft(), key.getMiddle(), key.getRight()); + } + }); + + public TRSRTransformation getNodeMatrix(Node node) + { + return cache.getUnchecked(Triple., Integer>of(animation, node, frame)); + } + + public static TRSRTransformation getNodeMatrix(Animation animation, Node node, int frame) + { + TRSRTransformation ret = TRSRTransformation.identity(); + Key key = null; + if(animation != null) key = animation.getKeys().get(frame, node); + else if(key == null && node.getAnimation() != null && node.getAnimation() != animation) key = node.getAnimation().getKeys().get(frame, node); + if(key == null) + { + FMLLog.severe("invalid key index: " + frame); + logger.debug(String.format("getMatrix: %s %s\n%s %s %s\n%s", frame, node, node.getPos(), node.getScale(), node.getRot(), ret)); + } + else + { + ret = ret.compose(new TRSRTransformation(key.getPos(), key.getRot(), key.getScale(), null)); + Matrix4f rm = new TRSRTransformation(node.getPos(), node.getRot(), node.getScale(), null).getMatrix(); + rm.invert(); + ret = ret.compose(new TRSRTransformation(rm)); + logger.debug(String.format("getMatrix: %s %s\n%s %s %s\n%s %s %s\n%s", frame, node, node.getPos(), node.getScale(), node.getRot(), key.getPos(), key.getScale(), key.getRot(), ret)); + } + return ret; + } + } + + public static enum B3DFrameProperty implements IUnlistedProperty + { + instance; + public String getName() + { + return "B3DFrame"; + } + + public boolean isValid(B3DState value) + { + return value instanceof B3DState; + } + + public Class getType() + { + return B3DState.class; + } + + public String valueToString(B3DState value) + { + return value.toString(); + } + } + + public static class PartWrapper> implements IModelPart + { + private final Node node; + + public static > PartWrapper create(Node node) + { + return new PartWrapper(node); + } + + public PartWrapper(Node node) + { + this.node = node; + } + + public Node getNode() + { + return node; + } + } + + public static class Wrapper extends PartWrapper implements IModel + { + private final ResourceLocation location; + private final ImmutableMap textures; + + public Wrapper(ResourceLocation location, List textures, B3DModel.Node mesh) + { + this(location, buildTextures(location, textures), mesh); + } + + public Wrapper(ResourceLocation location, ImmutableMap textures, B3DModel.Node mesh) + { + super(mesh); + this.location = location; + this.textures = textures; + } + + private static ImmutableMap buildTextures(ResourceLocation location, List textures) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for(Texture t : textures) + { + String path = t.getPath(); + if(path.endsWith(".png")) path = path.substring(0, path.length() - ".png".length()); + builder.put(t.getPath(), new ResourceLocation(location.getResourceDomain(), path)); + System.out.println("Texture: " + path); + } + return builder.build(); + } + + public Collection getDependencies() + { + // no dependencies for in-file models + // FIXME maybe add child meshes + return Collections.emptyList(); + } + + public Collection getTextures() + { + return textures.values(); + } + + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for(String path : textures.keySet()) + { + builder.put(path, bakedTextureGetter.apply(textures.get(path))); + } + builder.put("missingno", bakedTextureGetter.apply(new ResourceLocation("missingno"))); + return new BakedWrapper(this, state, format, builder.build()); + } + + public B3DState getDefaultState() + { + return new B3DState(getNode().getAnimation(), 1); + } + + public ResourceLocation getLocation() + { + return location; + } + + public ImmutableMap getTextureMap() + { + return textures; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((location == null) ? 0 : location.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Wrapper other = (Wrapper) obj; + if (location == null) + { + if (other.location != null) return false; + } + else if (!location.equals(other.location)) return false; + return true; + } + } + + private static class BakedWrapper implements IFlexibleBakedModel, ISmartBlockModel, ISmartItemModel + { + private final B3DLoader.Wrapper model; + private final IModelState state; + private final VertexFormat format; + private final ImmutableMap textures; + + private final ByteBuffer buf; + private ImmutableList quads; + + private static final int BYTES_IN_INT = Integer.SIZE / Byte.SIZE; + private static final int VERTICES_IN_QUAD = 4; + + public BakedWrapper(B3DLoader.Wrapper model, IModelState state, VertexFormat format, ImmutableMap textures) + { + this.model = model; + this.state = state; + this.format = format; + this.textures = textures; + + buf = BufferUtils.createByteBuffer(VERTICES_IN_QUAD * format.getNextOffset()); + } + + public List getFaceQuads(EnumFacing side) + { + return Collections.emptyList(); + } + + @SuppressWarnings("unchecked") + public List getGeneralQuads() + { + if(quads == null) + { + Node mesh = model.getNode(); + ImmutableList.Builder builder = ImmutableList.builder(); + for(Node child : mesh.getNodes().values()) + { + if(child.getKind() instanceof Mesh) + { + Node childMesh = (Node)child; + builder.addAll(new BakedWrapper(new B3DLoader.Wrapper(model.getLocation(), model.getTextureMap(), childMesh), state, format, textures).getGeneralQuads()); + } + } + Multimap>> weightMap = mesh.getKind().getWeightMap(); + Collection faces = mesh.getKind().getFaces(); + faces = mesh.getKind().bake(new Function, Matrix4f>() + { + // gets transformation in global space + public Matrix4f apply(Node node) + { + TRSRTransformation ret = TRSRTransformation.identity(), pm = null; + if(node.getParent() != null) pm = new TRSRTransformation(apply(node.getParent())); + if(pm != null) + { + ret = ret.compose(pm); + } + ret = ret.compose(state.apply(PartWrapper.create(node))); + return ret.getMatrix(); + } + }); + for(Face f : faces) + { + buf.clear(); + List textures = f.getBrush().getTextures(); + TextureAtlasSprite sprite; + if(textures.isEmpty()) sprite = this.textures.get("missingno"); + else sprite = this.textures.get(textures.get(0).getPath()); + putVertexData(f.getV1(), sprite); + putVertexData(f.getV2(), sprite); + putVertexData(f.getV3(), sprite); + putVertexData(f.getV3(), sprite); + buf.flip(); + int[] data = new int[VERTICES_IN_QUAD * format.getNextOffset() / BYTES_IN_INT]; + buf.asIntBuffer().get(data); + builder.add(new BakedQuad(data, -1, EnumFacing.getFacingFromVector(f.getNormal().x, f.getNormal().y, f.getNormal().z))); + } + quads = builder.build(); + } + return quads; + } + + private void put(VertexFormatElement e, Number... ns) + { + Attributes.put(buf, e, true, 0, ns); + } + + @SuppressWarnings("unchecked") + private final void putVertexData(Vertex v, TextureAtlasSprite sprite) + { + // TODO handle everything not handled (texture transformations, bones, transformations, normals, e.t.c) + int oldPos = buf.position(); + Number[] ns = new Number[16]; + for(int i = 0; i < ns.length; i++) ns[i] = 0f; + for(VertexFormatElement e : (List)format.getElements()) + { + switch(e.getUsage()) + { + case POSITION: + put(e, v.getPos().x, v.getPos().y, v.getPos().z, 1); + break; + case COLOR: + if(v.getColor() != null) + { + put(e, v.getColor().x, v.getColor().y, v.getColor().z, v.getColor().w); + } + else + { + put(e, 1, 1, 1, 0); + } + break; + case UV: + // TODO handle more brushes + if(e.getIndex() < v.getTexCoords().length) + { + put(e, + sprite.getInterpolatedU(v.getTexCoords()[0].x * 16), + sprite.getInterpolatedV(v.getTexCoords()[0].y * 16), + 0, + 1 + ); + } + else + { + put(e, 0, 0, 0, 1); + } + break; + case NORMAL: + // TODO + put(e, 0, 1, 0, 1); + break; + case GENERIC: + // TODO + put(e, 0, 0, 0, 1); + break; + default: + break; + } + } + buf.position(oldPos + format.getNextOffset()); + } + + public boolean isAmbientOcclusion() + { + return true; + } + + public boolean isGui3d() + { + return true; + } + + public boolean isBuiltInRenderer() + { + return false; + } + + public TextureAtlasSprite getTexture() + { + // FIXME somehow specify particle texture in the model + return textures.values().asList().get(0); + } + + public ItemCameraTransforms getItemCameraTransforms() + { + return ItemCameraTransforms.DEFAULT; + } + + @Override + public BakedWrapper handleBlockState(IBlockState state) + { + System.out.println("handleBlockState " + state); + if(state instanceof IExtendedBlockState) + { + IExtendedBlockState exState = (IExtendedBlockState)state; + if(exState.getUnlistedNames().contains(B3DFrameProperty.instance)) + { + B3DState s = (B3DState)exState.getValue(B3DFrameProperty.instance); + if(s != null) + { + return getCachedModel(s.getFrame()); + } + } + } + return this; + } + + private final Map cache = new HashMap(); + + public BakedWrapper getCachedModel(int frame) + { + if(!cache.containsKey(frame)) + { + cache.put(frame, new BakedWrapper(model, new B3DState(model.getNode().getAnimation(), frame), format, textures)); + } + return cache.get(frame); + } + + public VertexFormat getFormat() + { + return format; + } + + public BakedWrapper handleItemState(ItemStack stack) + { + // TODO specify how to get B3DState from ItemStack + return this; + } + } +} diff --git a/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java b/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java new file mode 100644 index 000000000..9c2e18b7f --- /dev/null +++ b/src/main/java/net/minecraftforge/client/model/b3d/B3DModel.java @@ -0,0 +1,1083 @@ +package net.minecraftforge.client.model.b3d; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.vecmath.Matrix4f; +import javax.vecmath.Quat4f; +import javax.vecmath.Vector2f; +import javax.vecmath.Vector3f; +import javax.vecmath.Vector4f; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.Table; + +public class B3DModel +{ + static final Logger logger = LogManager.getLogger(B3DModel.class); + private final List textures; + private final List brushes; + private final Node root; + private final ImmutableMap> meshes; + + public B3DModel(List textures, List brushes, Node root, ImmutableMap> meshes) + { + this.textures = textures; + this.brushes = brushes; + this.root = root; + this.meshes = meshes; + } + + public static class Parser + { + private static final int version = 0001; + private final ByteBuffer buf; + + private byte[] tag = new byte[4]; + private int length; + public Parser(InputStream in) throws IOException + { + if(in instanceof FileInputStream) + { + // fast shorthand for normal files + FileChannel channel = ((FileInputStream)in).getChannel(); + buf = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()).order(ByteOrder.LITTLE_ENDIAN); + } + else + { + // slower default for others + IOUtils.readFully(in, tag); + byte[] tmp = new byte[4]; + IOUtils.readFully(in, tmp); + int l = ByteBuffer.wrap(tmp).order(ByteOrder.LITTLE_ENDIAN).getInt(); + if(l < 0 || l + 8 < 0) throw new IOException("File is too large"); + buf = ByteBuffer.allocate(l + 8).order(ByteOrder.LITTLE_ENDIAN); + buf.clear(); + buf.put(tag); + buf.put(tmp); + buf.put(IOUtils.toByteArray(in, l)); + buf.flip(); + } + } + + private B3DModel res; + + public B3DModel parse() throws IOException + { + if(res != null) return res; + readHeader(); + res = bb3d(); + return res; + } + + private final List textures = new ArrayList(); + + private Texture getTexture(int texture) + { + if(texture > textures.size()) + { + logger.error(String.format("texture %s is out of range", texture)); + return null; + } + else if(texture == -1) return null; + return textures.get(texture); + } + + private final List brushes = new ArrayList(); + + private Brush getBrush(int brush) + { + if(brush > brushes.size()) + { + logger.error(String.format("brush %s is out of range", brush)); + return null; + } + else if(brush == -1) return null; + return brushes.get(brush); + } + + private final List vertices = new ArrayList(); + + private Vertex getVertex(int vertex) + { + if(vertex > vertices.size()) + { + logger.error(String.format("vertex %s is out of range", vertex)); + return null; + } + return vertices.get(vertex); + } + + private final ImmutableMap.Builder> meshes = ImmutableMap.builder(); + + private void readHeader() throws IOException + { + buf.get(tag); + length = buf.getInt(); + } + + private boolean isChunk(String tag) throws IOException + { + return Arrays.equals(this.tag, tag.getBytes("US-ASCII")); + } + + private void chunk(String tag) throws IOException + { + if(!isChunk(tag)) throw new IOException("Expected chunk " + tag + ", got " + new String(this.tag, "US-ASCII")); + pushLimit(); + } + + private String readString() throws IOException + { + int start = buf.position(); + while(buf.get() != 0); + int end = buf.position(); + byte[] tmp = new byte[end - start - 1]; + buf.position(start); + buf.get(tmp); + buf.get(); + String ret = new String(tmp, "UTF8"); + return ret; + } + + private Deque limitStack = new ArrayDeque(); + + private void pushLimit() + { + limitStack.push(buf.limit()); + buf.limit(buf.position() + length); + } + + private void popLimit() + { + buf.limit(limitStack.pop()); + } + + private B3DModel bb3d() throws IOException + { + chunk("BB3D"); + int version = buf.getInt(); + if(version / 100 > this.version / 100) + throw new IOException("Unsupported major model version: " + ((float)version / 100)); + if(version % 100 > this.version % 100) + logger.warn(String.format("Minor version differnce in model: ", ((float)version / 100))); + List textures = null; + List brushes = null; + Node root = null; + while(buf.hasRemaining()) + { + readHeader(); + if (isChunk("TEXS")) textures = texs(); + else if(isChunk("BRUS")) brushes = brus(); + else if(isChunk("NODE")) root = node(); + else skip(); + } + popLimit(); + return new B3DModel(textures, brushes, root, meshes.build()); + } + + private List texs() throws IOException + { + chunk("TEXS"); + List ret = new ArrayList(); + while(buf.hasRemaining()) + { + logger.debug("TEST"); + String path = readString(); + logger.debug("path: '" + path + "'"); + int flags = buf.getInt(); + int blend = buf.getInt(); + Vector2f pos = new Vector2f(buf.getFloat(), buf.getFloat()); + Vector2f scale = new Vector2f(buf.getFloat(), buf.getFloat()); + float rot = buf.getFloat(); + ret.add(new Texture(path, flags, blend, pos, scale, rot)); + logger.debug("TEX: '" + path + "' " + flags + " " + blend + " " + pos + " " + scale + " " + rot); + } + popLimit(); + this.textures.addAll(ret); + return ret; + } + + private List brus() throws IOException + { + chunk("BRUS"); + List ret = new ArrayList(); + int n_texs = buf.getInt(); + while(buf.hasRemaining()) + { + String name = readString(); + Vector4f color = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat()); + float shininess = buf.getFloat(); + int blend = buf.getInt(); + int fx = buf.getInt(); + List textures = new ArrayList(); + for(int i = 0; i < n_texs; i++) textures.add(getTexture(buf.getInt())); + ret.add(new Brush(name, color, shininess, blend, fx, textures)); + } + popLimit(); + this.brushes.addAll(ret); + return ret; + } + + private List vrts() throws IOException + { + chunk("VRTS"); + List ret = new ArrayList(); + int flags = buf.getInt(); + int tex_coord_sets = buf.getInt(); + int tex_coord_set_size = buf.getInt(); + while(buf.hasRemaining()) + { + Vector3f v = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()), n = null; + Vector4f color = null; + if((flags & 1) != 0) + { + n = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()); + } + if((flags & 2) != 0) + { + color = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat()); + } + Vector4f[] tex_coords = new Vector4f[tex_coord_sets]; + for(int i = 0; i < tex_coord_sets; i++) + { + switch(tex_coord_set_size) + { + case 1: + tex_coords[i] = new Vector4f(buf.getFloat(), 0, 0, 1); + break; + case 2: + tex_coords[i] = new Vector4f(buf.getFloat(), buf.getFloat(), 0, 1); + break; + case 3: + tex_coords[i] = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), 1); + break; + case 4: + tex_coords[i] = new Vector4f(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat()); + break; + default: + logger.error(String.format("Unsupported number of texture coords: ", tex_coord_set_size)); + tex_coords[i] = new Vector4f(0, 0, 0, 1); + } + } + ret.add(new Vertex(v, n, color, tex_coords)); + } + popLimit(); + this.vertices.clear(); + this.vertices.addAll(ret); + return ret; + } + + private List tris() throws IOException + { + chunk("TRIS"); + List ret = new ArrayList(); + int brush_id = buf.getInt(); + while(buf.hasRemaining()) + { + ret.add(new Face(getVertex(buf.getInt()), getVertex(buf.getInt()), getVertex(buf.getInt()), getBrush(brush_id))); + } + popLimit(); + return ret; + } + + private Pair> mesh() throws IOException + { + chunk("MESH"); + int brush_id = buf.getInt(); + readHeader(); + vrts(); + List ret = new ArrayList(); + while(buf.hasRemaining()) + { + readHeader(); + ret.addAll(tris()); + } + popLimit(); + return Pair.of(getBrush(brush_id), ret); + } + + private List> bone() throws IOException + { + chunk("BONE"); + List> ret = new ArrayList>(); + while(buf.hasRemaining()) + { + ret.add(Pair.of(getVertex(buf.getInt()), buf.getFloat())); + } + popLimit(); + return ret; + } + + private final Deque>, Key>> animations = new ArrayDeque>, Key>>(); + + private Map keys() throws IOException + { + chunk("KEYS"); + Map ret = new HashMap(); + int flags = buf.getInt(); + Vector3f pos = null, scale = null; + Quat4f rot = null; + while(buf.hasRemaining()) + { + int frame = buf.getInt(); + if((flags & 1) != 0) + { + pos = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()); + } + if((flags & 2) != 0) + { + scale = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()); + } + if((flags & 4) != 0) + { + rot = readQuat(); + } + Key key = new Key(pos, scale, rot); + //logger.debug("Key: " + frame + " " + key); + Key oldKey = animations.peek().get(frame, null); + if(oldKey != null) + { + if(pos != null) + { + if(oldKey.getPos() != null) logger.error("Duplicate keys: %s and %s (ignored)", oldKey, key); + else key = new Key(oldKey.getPos(), key.getScale(), key.getRot()); + } + if(scale != null) + { + if(oldKey.getScale() != null) logger.error("Duplicate keys: %s and %s (ignored)", oldKey, key); + else key = new Key(key.getPos(), oldKey.getScale(), key.getRot()); + } + if(rot != null) + { + if(oldKey.getRot() != null) logger.error("Duplicate keys: %s and %s (ignored)", oldKey, key); + else key = new Key(key.getPos(), key.getScale(), oldKey.getRot()); + } + } + animations.peek().put(frame, Optional.>absent(), key); + ret.put(frame, key); + } + popLimit(); + return ret; + } + + private Triple anim() throws IOException + { + chunk("ANIM"); + int flags = buf.getInt(); + int frames = buf.getInt(); + float fps = buf.getFloat(); + popLimit(); + return Triple.of(flags, frames, fps); + } + + private Node node() throws IOException + { + chunk("NODE"); + animations.push(HashBasedTable.>, Key>create()); + Triple animData = null; + Animation animation = null; + Pair> mesh = null; + List> bone = null; + Map keys = new HashMap(); + List> nodes = new ArrayList>(); + String name = readString(); + Vector3f pos = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()); + Vector3f scale = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat()); + Quat4f rot = readQuat(); + while(buf.hasRemaining()) + { + readHeader(); + if (isChunk("MESH")) mesh = mesh(); + else if(isChunk("BONE")) bone = bone(); + else if(isChunk("KEYS")) keys.putAll(keys()); + else if(isChunk("NODE")) nodes.add(node()); + else if(isChunk("ANIM")) animData = anim(); + else skip(); + } + popLimit(); + Table>, Key> keyData = animations.pop(); + Node node; + if(mesh != null) + { + Node mNode = Node.create(name, pos, scale, rot, nodes, new Mesh(mesh)); + meshes.put(name, mNode); + node = mNode; + } + else if(bone != null) node = Node.create(name, pos, scale, rot, nodes, new Bone(bone)); + else node = Node.create(name, pos, scale, rot, nodes, new Pivot()); + if(animData == null) + { + for(Table.Cell>, Key> key : keyData.cellSet()) + { + logger.debug("KEY1: " + key + " " + node); + animations.peek().put(key.getRowKey(), key.getColumnKey().or(Optional.of(node)), key.getValue()); + } + } + else + { + node.setAnimation(animData, keyData); + } + return node; + } + + private Quat4f readQuat() + { + float w = buf.getFloat(); + float x = buf.getFloat(); + float y = buf.getFloat(); + float z = buf.getFloat(); + return new Quat4f(x, y, z, w); + } + + private void skip() + { + buf.position(buf.position() + length); + } + } + + // boilerplate below + + public List getTextures() + { + return textures; + } + + public List getBrushes() + { + return brushes; + } + + public Node getRoot() + { + return root; + } + + public ImmutableMap> getMeshes() + { + return meshes; + } + + public static class Texture + { + private final String path; + private final int flags; + private final int blend; + private final Vector2f pos; + private final Vector2f scale; + private final float rot; + + public Texture(String path, int flags, int blend, Vector2f pos, Vector2f scale, float rot) + { + this.path = path; + this.flags = flags; + this.blend = blend; + this.pos = pos; + this.scale = scale; + this.rot = rot; + } + + public String getPath() + { + return path; + } + + public int getFlags() + { + return flags; + } + + public int getBlend() + { + return blend; + } + + public Vector2f getPos() + { + return pos; + } + + public Vector2f getScale() + { + return scale; + } + + public float getRot() + { + return rot; + } + + @Override + public String toString() + { + return String.format("Texture [path=%s, flags=%s, blend=%s, pos=%s, scale=%s, rot=%s]", path, flags, blend, pos, scale, rot); + } + } + + public static class Brush + { + private final String name; + private final Vector4f color; + private final float shininess; + private final int blend; + private final int fx; + private final List textures; + + public Brush(String name, Vector4f color, float shininess, int blend, int fx, List textures) + { + this.name = name; + this.color = color; + this.shininess = shininess; + this.blend = blend; + this.fx = fx; + this.textures = textures; + } + + public String getName() + { + return name; + } + + public Vector4f getColor() + { + return color; + } + + public float getShininess() + { + return shininess; + } + + public int getBlend() + { + return blend; + } + + public int getFx() + { + return fx; + } + + public List getTextures() + { + return textures; + } + + @Override + public String toString() + { + return String.format("Brush [name=%s, color=%s, shininess=%s, blend=%s, fx=%s, textures=%s]", name, color, shininess, blend, fx, textures); + } + } + + public static class Vertex + { + private final Vector3f pos; + private final Vector3f normal; + private final Vector4f color; + private final Vector4f[] texCoords; + public Vertex(Vector3f pos, Vector3f normal, Vector4f color, Vector4f[] texCoords) + { + this.pos = pos; + this.normal = normal; + this.color = color; + this.texCoords = texCoords; + } + + public Vertex bake(Mesh mesh, Function, Matrix4f> animator) + { + // geometry + Float totalWeight = 0f; + Matrix4f t = new Matrix4f(); + if(mesh.getWeightMap().get(this).isEmpty()) + { + t.setIdentity(); + } + else + { + for(Pair> bone : mesh.getWeightMap().get(this)) + { + totalWeight += bone.getLeft(); + Matrix4f bm = animator.apply(bone.getRight()); + logger.debug("w:" + totalWeight + " bone: " + bone.getRight().getName() + " bm: \n" + bm); + bm.mul(bone.getLeft()); + t.add(bm); + logger.debug("t: \n" + t); + } + if(totalWeight != 0) t.mul(1f / totalWeight); + } + + logger.debug("t: \n" + t); + + // pos + Vector4f pos = new Vector4f(this.pos), newPos = new Vector4f(); + pos.w = 1; + t.transform(pos, newPos); + Vector3f rPos = new Vector3f(newPos.x / newPos.w, newPos.y / newPos.w, newPos.z / newPos.w); + + // normal + t.invert(); + t.transpose(); + Vector4f normal = new Vector4f(this.normal), newNormal = new Vector4f(); + normal.w = 1; + t.transform(normal, newNormal); + Vector3f rNormal = new Vector3f(newNormal.x / newNormal.w, newNormal.y / newNormal.w, newNormal.z / newNormal.w); + rNormal.normalize(); + + // texCoords TODO + return new Vertex(rPos, rNormal, color, texCoords); + } + + public Vector3f getPos() + { + return pos; + } + + public Vector3f getNormal() + { + return normal; + } + + public Vector4f getColor() + { + return color; + } + + public Vector4f[] getTexCoords() + { + return texCoords; + } + + @Override + public String toString() + { + return String.format("Vertex [pos=%s, normal=%s, color=%s, texCoords=%s]", pos, normal, color, java.util.Arrays.toString(texCoords)); + } + } + + public static class Face + { + private final Vertex v1, v2, v3; + private final Brush brush; + private final Vector3f normal; + + public Face(Vertex v1, Vertex v2, Vertex v3, Brush brush) + { + this(v1, v2, v3, brush, getNormal(v1, v2, v3)); + } + + public Face(Vertex v1, Vertex v2, Vertex v3, Brush brush, Vector3f normal) + { + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + this.brush = brush; + this.normal = normal; + } + + public Vertex getV1() + { + return v1; + } + + public Vertex getV2() + { + return v2; + } + + public Vertex getV3() + { + return v3; + } + + public Brush getBrush() + { + return brush; + } + + @Override + public String toString() + { + return String.format("Face [v1=%s, v2=%s, v3=%s]", v1, v2, v3); + } + + public Vector3f getNormal() + { + return normal; + } + + public static Vector3f getNormal(Vertex v1, Vertex v2, Vertex v3) + { + Vector3f a = new Vector3f(v2.getPos()); + a.sub(v1.getPos()); + Vector3f b = new Vector3f(v3.getPos()); + b.sub(v1.getPos()); + Vector3f c = new Vector3f(); + c.cross(a, b); + c.normalize(); + return c; + } + } + + public static class Key + { + private final Vector3f pos; + private final Vector3f scale; + private final Quat4f rot; + + public Key(Vector3f pos, Vector3f scale, Quat4f rot) + { + this.pos = pos; + this.scale = scale; + this.rot = rot; + } + + public Vector3f getPos() + { + return pos; + } + + public Vector3f getScale() + { + return scale; + } + + public Quat4f getRot() + { + return rot; + } + + @Override + public String toString() + { + return String.format("Key [pos=%s, scale=%s, rot=%s]", pos, scale, rot); + } + } + + public static class Animation + { + private final int flags; + private final int frames; + private final float fps; + private final ImmutableTable, Key> keys; + + public Animation(int flags, int frames, float fps, ImmutableTable, Key> keys) + { + this.flags = flags; + this.frames = frames; + this.fps = fps; + this.keys = keys; + } + + public int getFlags() + { + return flags; + } + + public int getFrames() + { + return frames; + } + + public float getFps() + { + return fps; + } + + public ImmutableTable, Key> getKeys() + { + return keys; + } + + @Override + public String toString() + { + return String.format("Animation [flags=%s, frames=%s, fps=%s, keys=...]", flags, frames, fps); + } + } + + public static interface IKind> + { + void setParent(Node parent); + Node getParent(); + } + + public static class Node> + { + private final String name; + private final Vector3f pos; + private final Vector3f scale; + private final Quat4f rot; + private final ImmutableMap> nodes; + private Animation animation; + private final K kind; + private Node> parent; + + public static > Node create(String name, Vector3f pos, Vector3f scale, Quat4f rot, List> nodes, K kind) + { + return new Node(name, pos, scale, rot, nodes, kind); + } + + public Node(String name, Vector3f pos, Vector3f scale, Quat4f rot, List> nodes, K kind) + { + this.name = name; + this.pos = pos; + this.scale = scale; + this.rot = rot; + this.nodes = buildNodeMap(nodes); + this.kind = kind; + kind.setParent(this); + for(Node child : this.nodes.values()) + { + child.setParent(this); + } + } + + public void setAnimation(Animation animation) + { + logger.debug("setAnimation " + animation + " " + this); + this.animation = animation; + Deque> q = new ArrayDeque>(nodes.values()); + + while(!q.isEmpty()) + { + Node node = q.pop(); + if(node.getAnimation() != null) continue; + node.setAnimation(animation); + q.addAll(node.getNodes().values()); + } + } + + public void setAnimation(Triple animData, Table>, Key> keyData) + { + ImmutableTable.Builder, Key> builder = ImmutableTable.builder(); + for(Table.Cell>, Key> key : keyData.cellSet()) + { + //logger.debug("KEY2: " + key + " " + this); + builder.put(key.getRowKey(), key.getColumnKey().or(this), key.getValue()); + } + setAnimation(new Animation(animData.getLeft(), animData.getMiddle(), animData.getRight(), builder.build())); + } + + private ImmutableMap> buildNodeMap(List> nodes) + { + ImmutableMap.Builder> builder = ImmutableMap.builder(); + for(Node node : nodes) + { + builder.put(node.getName(), node); + } + return builder.build(); + } + + public String getName() + { + return name; + } + + public K getKind() + { + return kind; + } + + public Vector3f getPos() + { + return pos; + } + + public Vector3f getScale() + { + return scale; + } + + public Quat4f getRot() + { + return rot; + } + + public ImmutableMap> getNodes() + { + return nodes; + } + + public Animation getAnimation() + { + return animation; + } + + public Node> getParent() + { + return parent; + } + + public void setParent(Node> parent) + { + this.parent = parent; + } + + @Override + public String toString() + { + return String.format("Node [name=%s, kind=%s, pos=%s, scale=%s, rot=%s, keys=..., nodes=..., animation=%s]", name, kind, pos, scale, rot, animation); + } + } + + public static class Pivot implements IKind + { + private Node parent; + + public void setParent(Node parent) + { + this.parent = parent; + } + + public Node getParent() + { + return parent; + } + } + + public static class Mesh implements IKind + { + private Node parent; + private final Brush brush; + private final ImmutableList faces; + + private Set> bones = new HashSet>(); + + private ImmutableMultimap>> weightMap = ImmutableMultimap.of(); + + public Mesh(Pair> data) + { + this.brush = data.getLeft(); + this.faces = ImmutableList.copyOf(data.getRight()); + } + + public ImmutableMultimap>> getWeightMap() + { + return weightMap; + } + + public ImmutableList bake(Function, Matrix4f> animator) + { + ImmutableList.Builder builder = ImmutableList.builder(); + for(Face f : getFaces()) + { + Vertex v1 = f.getV1().bake(this, animator); + Vertex v2 = f.getV2().bake(this, animator); + Vertex v3 = f.getV3().bake(this, animator); + builder.add(new Face(v1, v2, v3, f.getBrush())); + } + return builder.build(); + } + + public Brush getBrush() + { + return brush; + } + + public ImmutableList getFaces() + { + return faces; + } + + public ImmutableSet> getBones() + { + return ImmutableSet.copyOf(bones); + } + + @Override + public String toString() + { + return String.format("Mesh [pivot=%s, brush=%s, data=...]", super.toString(), brush); + } + + @SuppressWarnings("unchecked") + public void setParent(Node parent) + { + this.parent = parent; + Deque> queue = new ArrayDeque>(parent.getNodes().values()); + while(!queue.isEmpty()) + { + Node node = queue.pop(); + if(node.getKind() instanceof Bone) + { + bones.add((Node)node); + queue.addAll(node.getNodes().values()); + } + } + ImmutableMultimap.Builder>> builder = ImmutableMultimap.builder(); + for(Node bone : getBones()) + { + for(Pair b : bone.getKind().getData()) + { + builder.put(b.getLeft(), Pair.of(b.getRight(), bone)); + logger.debug("Weight: " + b.getRight() + " " + bone.getName() + " " + b.getLeft().getPos()); + } + } + weightMap = builder.build(); + } + + public Node getParent() + { + return parent; + } + } + + public static class Bone implements IKind + { + private Node parent; + private final List> data; + + public Bone(List> data) + { + this.data = data; + } + + public List> getData() + { + return data; + } + + /*@Override + public String toString() + { + return String.format("Bone [data=%s]", data); + }*/ + + public void setParent(Node parent) + { + this.parent = parent; + } + + public Node getParent() + { + return parent; + } + } +} diff --git a/src/main/resources/forge.exc b/src/main/resources/forge.exc index f59ef2681..752a6af82 100644 --- a/src/main/resources/forge.exc +++ b/src/main/resources/forge.exc @@ -28,3 +28,10 @@ net/minecraft/item/ItemDye.applyBonemeal(Lnet/minecraft/item/ItemStack;Lnet/mine net/minecraft/server/management/ItemInWorldManager.removeBlock(Lnet/minecraft/util/BlockPos;Z)Z=|p_180235_1_,canHarvest net/minecraft/client/gui/GuiScreen.drawHoveringText(Ljava/util/List;IILnet/minecraft/client/gui/FontRenderer;)V=|p_146283_1_,p_146283_2_,p_146283_3_,font net/minecraft/block/state/BlockState.(Lnet/minecraft/block/Block;[Lnet/minecraft/block/properties/IProperty;Lcom/google/common/collect/ImmutableMap;)V=|p_i45663_1_,p_i45663_2_,unlistedProperties +net/minecraft/client/renderer/entity/RenderItem.applyVanillaTransform(Lnet/minecraft/client/renderer/block/model/ItemTransformVec3f;)V=|p_175034_1_ +net/minecraft/client/resources/model/ModelBakery.bakeModel(Lnet/minecraft/client/renderer/block/model/ModelBlock;Lnet/minecraftforge/client/model/ITransformation;Z)Lnet/minecraft/client/resources/model/IBakedModel;=|p_177578_1_,p_177578_2_,p_177578_3_ +net/minecraft/client/resources/model/ModelBakery.makeBakedQuad(Lnet/minecraft/client/renderer/block/model/BlockPart;Lnet/minecraft/client/renderer/block/model/BlockPartFace;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;Lnet/minecraft/util/EnumFacing;Lnet/minecraftforge/client/model/ITransformation;Z)Lnet/minecraft/client/renderer/block/model/BakedQuad;=|p_177589_1_,p_177589_2_,p_177589_3_,p_177589_4_,p_177589_5_,p_177589_6_ +net/minecraft/client/renderer/block/model/FaceBakery.makeBakedQuad(Ljavax/vecmath/Vector3f;Ljavax/vecmath/Vector3f;Lnet/minecraft/client/renderer/block/model/BlockPartFace;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;Lnet/minecraft/util/EnumFacing;Lnet/minecraftforge/client/model/ITransformation;Lnet/minecraft/client/renderer/block/model/BlockPartRotation;ZZ)Lnet/minecraft/client/renderer/block/model/BakedQuad;=|p_178414_1_,p_178414_2_,p_178414_3_,p_178414_4_,p_178414_5_,p_178414_6_,p_178414_7_,p_178414_8_,p_178414_9_ +net/minecraft/client/renderer/block/model/FaceBakery.makeQuadVertexData(Lnet/minecraft/client/renderer/block/model/BlockPartFace;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;Lnet/minecraft/util/EnumFacing;[FLnet/minecraftforge/client/model/ITransformation;Lnet/minecraft/client/renderer/block/model/BlockPartRotation;ZZ)[I=|p_178405_1_,p_178405_2_,p_178405_3_,p_178405_4_,p_178405_5_,p_178405_6_,p_178405_7_,p_178405_8_ +net/minecraft/client/renderer/block/model/FaceBakery.fillVertexData([IILnet/minecraft/util/EnumFacing;Lnet/minecraft/client/renderer/block/model/BlockPartFace;[FLnet/minecraft/client/renderer/texture/TextureAtlasSprite;Lnet/minecraftforge/client/model/ITransformation;Lnet/minecraft/client/renderer/block/model/BlockPartRotation;ZZ)V=|p_178402_1_,p_178402_2_,p_178402_3_,p_178402_4_,p_178402_5_,p_178402_6_,p_178402_7_,p_178402_8_,p_178402_9_,p_178402_10_ +net/minecraft/client/renderer/block/model/FaceBakery.rotateVertex(Ljavax/vecmath/Vector3d;Lnet/minecraft/util/EnumFacing;ILnet/minecraftforge/client/model/ITransformation;Z)I=|p_178415_1_,p_178415_2_,p_178415_3_,p_178415_4_,p_178415_5_ diff --git a/src/main/resources/forge_at.cfg b/src/main/resources/forge_at.cfg index f35c533b2..11d7172bb 100644 --- a/src/main/resources/forge_at.cfg +++ b/src/main/resources/forge_at.cfg @@ -123,3 +123,27 @@ public net.minecraft.item.crafting.RecipesBanners$RecipeDuplicatePattern public net.minecraft.block.state.BlockState$StateImplementation protected net.minecraft.block.state.BlockState$StateImplementation (Lnet/minecraft/block/Block;Lcom/google/common/collect/ImmutableMap;)V protected net.minecraft.block.state.BlockState$StateImplementation field_177238_c # propertyValueTable +public net.minecraft.client.renderer.block.model.ModelBlock field_178315_d # parent +protected net.minecraft.client.resources.model.ModelBakery field_177602_b # LOCATIONS_BUILTIN_TEXTURES +protected net.minecraft.client.resources.model.ModelBakery field_177598_f # resourceManager +protected net.minecraft.client.resources.model.ModelBakery field_177599_g # sprites +protected net.minecraft.client.resources.model.ModelBakery field_177609_j # textureMap +protected net.minecraft.client.resources.model.ModelBakery field_177610_k # blockModelShapes +protected net.minecraft.client.resources.model.ModelBakery field_177605_n # bakedRegistry +protected net.minecraft.client.resources.model.ModelBakery field_177606_o # MODEL_GENERATED +protected net.minecraft.client.resources.model.ModelBakery field_177618_p # MODEL_COMPASS +protected net.minecraft.client.resources.model.ModelBakery field_177617_q # MODEL_CLOCK +protected net.minecraft.client.resources.model.ModelBakery field_177616_r # MODEL_ENTITY +protected net.minecraft.client.resources.model.ModelBakery func_177591_a(Ljava/util/Collection;)V # loadVariants +protected net.minecraft.client.resources.model.ModelBakery func_177569_a(Lnet/minecraft/client/renderer/block/model/ModelBlockDefinition;Lnet/minecraft/client/resources/model/ModelResourceLocation;)V # registerVariant +protected net.minecraft.client.resources.model.ModelBakery func_177596_a(Lnet/minecraft/item/Item;)Ljava/util/List; # getVariantNames +protected net.minecraft.client.resources.model.ModelBakery func_177583_a(Ljava/lang/String;)Lnet/minecraft/util/ResourceLocation; # getItemLocation +protected net.minecraft.client.resources.model.ModelBakery func_177594_c(Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/client/renderer/block/model/ModelBlock; # loadModel +protected net.minecraft.client.resources.model.ModelBakery func_177592_e()V # registerVariantNames +protected net.minecraft.client.resources.model.ModelBakery func_177596_a(Lnet/minecraft/item/Item;)Ljava/util/List; # getVariantNames +protected net.minecraft.client.resources.model.ModelBakery func_177583_a(Ljava/lang/String;)Lnet/minecraft/util/ResourceLocation; # getItemLocation +protected net.minecraft.client.resources.model.ModelBakery func_177585_a(Lnet/minecraft/client/renderer/block/model/ModelBlock;)Ljava/util/Set; # getTextureLocations +protected net.minecraft.client.resources.model.ModelBakery func_177581_b(Lnet/minecraft/client/renderer/block/model/ModelBlock;)Z # hasItemModel +protected net.minecraft.client.resources.model.ModelBakery func_177587_c(Lnet/minecraft/client/renderer/block/model/ModelBlock;)Z # isCustomRenderer +protected net.minecraft.client.resources.model.ModelBakery func_177582_d(Lnet/minecraft/client/renderer/block/model/ModelBlock;)Lnet/minecraft/client/renderer/block/model/ModelBlock; # makeItemModel +protected net.minecraft.client.resources.model.ModelBakery func_177580_d(Lnet/minecraft/util/ResourceLocation;)Lnet/minecraft/util/ResourceLocation; # getModelLocation diff --git a/src/test/java/net/minecraftforge/debug/ModelLoaderRegistryDebug.java b/src/test/java/net/minecraftforge/debug/ModelLoaderRegistryDebug.java new file mode 100644 index 000000000..8a8ecd6c8 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/ModelLoaderRegistryDebug.java @@ -0,0 +1,220 @@ +package net.minecraftforge.debug; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemCameraTransforms; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.ModelRotation; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.util.BlockPos; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.client.model.Attributes; +import net.minecraftforge.client.model.ICustomModelLoader; +import net.minecraftforge.client.model.IFlexibleBakedModel; +import net.minecraftforge.client.model.IModel; +import net.minecraftforge.client.model.IModelState; +import net.minecraftforge.client.model.ModelLoaderRegistry; +import net.minecraftforge.client.model.b3d.B3DLoader; +import net.minecraftforge.common.property.ExtendedBlockState; +import net.minecraftforge.common.property.IExtendedBlockState; +import net.minecraftforge.common.property.IUnlistedProperty; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.registry.GameRegistry; + +import com.google.common.base.Function; +import com.google.common.primitives.Ints; + +@Mod(modid = ModelLoaderRegistryDebug.MODID, version = ModelLoaderRegistryDebug.VERSION) +public class ModelLoaderRegistryDebug +{ + public static final String MODID = "ForgeDebugModelLoaderRegistry"; + public static final String VERSION = "1.0"; + + @EventHandler + public void preInit(FMLPreInitializationEvent event) + { + //ModelLoaderRegistry.registerLoader(DummyModelLoader.instance); + B3DLoader.instance.addDomain(MODID.toLowerCase()); + GameRegistry.registerBlock(CustomModelBlock.instance, CustomModelBlock.name); + //ModelBakery.addVariantName(Item.getItemFromBlock(CustomModelBlock.instance), "forgedebug:dummymodel"); + ModelBakery.addVariantName(Item.getItemFromBlock(CustomModelBlock.instance), MODID.toLowerCase() + ":untitled2.b3d"); + } + + @EventHandler + public void init(FMLInitializationEvent event) + { + Item item = Item.getItemFromBlock(CustomModelBlock.instance); + //Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(item, 0, new ModelResourceLocation("forgedebug:dummymodel", "inventory")); + Minecraft.getMinecraft().getRenderItem().getItemModelMesher().register(item, 0, new ModelResourceLocation(MODID.toLowerCase() + ":untitled2.b3d", "inventory")); + } + + public static class CustomModelBlock extends Block + { + public static final CustomModelBlock instance = new CustomModelBlock(); + public static final String name = "CustomModelBlock"; + private int counter = 1; + private ExtendedBlockState state = new ExtendedBlockState(this, new IProperty[0], new IUnlistedProperty[]{ B3DLoader.B3DFrameProperty.instance }); + + private CustomModelBlock() + { + super(Material.iron); + setCreativeTab(CreativeTabs.tabBlock); + setUnlocalizedName(MODID + ":" + name); + } + + @Override + public boolean isOpaqueCube() { return false; } + + @Override + public boolean isFullCube() { return false; } + + @Override + public boolean isVisuallyOpaque() { return false; } + + @Override + public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos) + { + IModel model = ModelLoaderRegistry.getModel(new ResourceLocation(MODID.toLowerCase(),"block/untitled2.b3d")); + B3DLoader.B3DState defaultState = ((B3DLoader.Wrapper)model).getDefaultState(); + B3DLoader.B3DState newState = new B3DLoader.B3DState(defaultState.getAnimation(), counter); + return ((IExtendedBlockState)this.state.getBaseState()).withProperty(B3DLoader.B3DFrameProperty.instance, newState); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) + { + if(world.isRemote) + { + System.out.println("click " + counter); + if(player.isSneaking()) counter--; + else counter++; + //if(counter >= model.getNode().getKeys().size()) counter = 0; + world.markBlockRangeForRenderUpdate(pos, pos); + } + return false; + } + } + + public static class DummyModelLoader implements ICustomModelLoader + { + public static final DummyModelLoader instance = new DummyModelLoader(); + public static final ResourceLocation dummyTexture = new ResourceLocation("minecraft:blocks/dirt"); + + public boolean accepts(ResourceLocation modelLocation) + { + return modelLocation.getResourceDomain().equals("forgedebug") && modelLocation.getResourcePath().contains("dummymodel"); + } + + public IModel loadModel(ResourceLocation model) + { + return DummyModel.instance; + } + + public static enum DummyModel implements IModel + { + instance; + + public Collection getDependencies() + { + return Collections.emptyList(); + } + + public Collection getTextures() + { + return Collections.singletonList(dummyTexture); + } + + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function textures) + { + return new DummyBakedModel(textures.apply(dummyTexture)); + } + + public IModelState getDefaultState() + { + return ModelRotation.X0_Y0; + } + } + + public static class DummyBakedModel implements IFlexibleBakedModel + { + private final TextureAtlasSprite texture; + + public DummyBakedModel(TextureAtlasSprite texture) + { + this.texture = texture; + } + + public List getFaceQuads(EnumFacing side) + { + return Collections.emptyList(); + } + + private int[] vertexToInts(float x, float y, float z, int color, float u, float v) + { + return new int[] { + Float.floatToRawIntBits(x), + Float.floatToRawIntBits(y), + Float.floatToRawIntBits(z), + color, + Float.floatToRawIntBits(texture.getInterpolatedU(u)), + Float.floatToRawIntBits(texture.getInterpolatedV(v)), + 0 + }; + } + + public List getGeneralQuads() + { + List ret = new ArrayList(); + // 1 half-way rotated quad looking UP + ret.add(new BakedQuad(Ints.concat( + vertexToInts(0, .5f, .5f, -1, 0, 0), + vertexToInts(.5f, .5f, 1, -1, 0, 16), + vertexToInts(1, .5f, .5f, -1, 16, 16), + vertexToInts(.5f, .5f, 0, -1, 16, 0) + ), -1, EnumFacing.UP)); + return ret; + } + + public boolean isGui3d() { return true; } + + public boolean isAmbientOcclusion() { return true; } + + public boolean isBuiltInRenderer() { return false; } + + public TextureAtlasSprite getTexture() { return this.texture; } + + public ItemCameraTransforms getItemCameraTransforms() + { + return ItemCameraTransforms.DEFAULT; + } + + public VertexFormat getFormat() + { + return Attributes.DEFAULT_BAKED_FORMAT; + } + } + + public void onResourceManagerReload(IResourceManager resourceManager) {} + } +} diff --git a/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json b/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json new file mode 100644 index 000000000..1ebdfc8c8 --- /dev/null +++ b/src/test/resources/assets/forgedebugmodelloaderregistry/blockstates/CustomModelBlock.json @@ -0,0 +1,5 @@ +{ + "variants": { + "normal" : { "model" : "forgedebugmodelloaderregistry:untitled2.b3d" } + } +} diff --git a/src/test/resources/assets/forgedebugmodelloaderregistry/models/block/untitled2.b3d b/src/test/resources/assets/forgedebugmodelloaderregistry/models/block/untitled2.b3d new file mode 100644 index 0000000000000000000000000000000000000000..14e9c4d4aa5c34cb06e314805da0d8e991594eb3 GIT binary patch literal 2173 zcma)-O-NKx6vwY=_Wfz?Y7tRb&^Q{}Q0ko~Cz4QM#w0^Q#lbM}d(uVV17{blgW5!d zi)ayJtxQLum9#Nv(MqBAeUL~<+WEaVciww`;ej*fy?f5R=luWge>D=hT$9|ZRGw0* zF?u&%EO96?GL#%h1p0cq?Dbu;=9;CI!j+1|?!*HU*&7*14&Dz0gQfCpDok`LBw<^9 zLrpYaI^IZjB+Obinhhqp>AGnAR!hE8Yhq2Y#`q_hiMF^qp4n$*%!q@3M>U>3DZI4C zvzzZC_hLR|^2F}^a(h3q<&ETWdCuRMAIP1*F;A{PmsjES`@6|4(%;@|^3161IV{Op zpF{aMQk9xp`d&?bZn-m~Bu|fJ8SnR@aFye+8BJ_uZR9KhTmM?<(FKW;n(T!<5y;P`L*=+ z{BE+}$L}Wnv-~#3YU7Wb?e?!%*XUcMuL{I>ix*~}iF?H5E~$Ou`^6849~3{7eI_0j zKO%lq{8;vxcwD?l{Dk;P@$2=qb?n7BTdBSXm$gY6M|yj9c3A^clj%r9ee|t;fKm&A zA3?x8UAP(oNM~rE3NY^;PXqx(1QhEu)%tZgpz4a+O9A$(a=HR=yaSLtAXi233`+#$ znnZx|4p<_9SqCf^2EeQXnh0PHpfFUEd80;Z?^Dl;ZSRG;C251nboap!R|M= zc5Qf64=;>WwLO>(_BBtY;m2=oZGYRa{_=6Gx}j#aG~E3@4TpZSG}!Sosa9>plrE{L zEW0xJGVOd^Z?&cWNgbZ53+rDi#d`khaM_2#Cuz9wo2ByvKmNO*T>la8oJp0n&!p0b z<${obSnPiVfvIZ~E?MsI?wWJ7S1?PbN91tK(h$o~4r$yzXC1JtJ3uV$3~6Zu!#)H< zL@-1IgBf)r7!1K+2nItim?IeHhVYJHFa(1+f>CO{V0MZZTsy4=`&P9GhIud%&J_HV zcsMr#fCB^*d6+sk**Y>V!kLCc&ZUO^)Zo0I6~XlO=AD1`t#w?4lYwXNxkfD1Bbdd+ f#ULmATq(}*EiQRxFk+z=!Ia$WI3=QaE}r=hS1`0{ literal 0 HcmV?d00001 diff --git a/src/test/resources/assets/forgedebugmodelloaderregistry/textures/texture.png b/src/test/resources/assets/forgedebugmodelloaderregistry/textures/texture.png new file mode 100644 index 0000000000000000000000000000000000000000..e66fab953740ad4e6b6e461341645b2b5f659f62 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP%z5V z#WBP}FgYb5;RpYb6lS$erB0@N%*+uEaX