From 13d7530e437648a68dc96f46164c59a7be4fc5d3 Mon Sep 17 00:00:00 2001 From: RainWarrior Date: Mon, 16 Nov 2015 06:50:54 +0300 Subject: [PATCH] Implemented interpolation of TRSR transformations; B3D: added interpolation capabilities to B3DState, animated TESR example in ModelAnimationTest (pure TESR right now, no separation inside the example model between the static and dynamic parts right now). --- .../client/model/TRSRTransformation.java | 133 +++--- .../client/model/b3d/B3DLoader.java | 378 +++++++++++++++--- .../debug/ModelAnimationDebug.java | 285 +++++++++++++ .../blockstates/test_animation_block.json | 26 ++ 4 files changed, 716 insertions(+), 106 deletions(-) create mode 100644 src/test/java/net/minecraftforge/debug/ModelAnimationDebug.java create mode 100644 src/test/resources/assets/forgedebugmodelanimation/blockstates/test_animation_block.json diff --git a/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java b/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java index 189069c6d5..3aed561b0f 100644 --- a/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java +++ b/src/main/java/net/minecraftforge/client/model/TRSRTransformation.java @@ -3,6 +3,8 @@ package net.minecraftforge.client.model; import javax.vecmath.Matrix3f; import javax.vecmath.Matrix4f; import javax.vecmath.Quat4f; +import javax.vecmath.Tuple3f; +import javax.vecmath.Tuple4f; import javax.vecmath.Vector3f; import javax.vecmath.Vector4f; @@ -181,7 +183,6 @@ public class TRSRTransformation implements IModelState, ITransformation { Matrix4f res = new Matrix4f(), t = new Matrix4f(); res.setIdentity(); - if(translation != null) res.setTranslation(translation); if(leftRot != null) { t.set(leftRot); @@ -200,6 +201,7 @@ public class TRSRTransformation implements IModelState, ITransformation t.set(rightRot); res.mul(t); } + if(translation != null) res.setTranslation(translation); return res; } @@ -221,7 +223,8 @@ public class TRSRTransformation implements IModelState, ITransformation b.set(m); b.mul(t); - sortSingularValues(b, v); + // FIXME: this doesn't work correctly for some reason; not crucial, so disabling for now; investigate in the future. + //sortSingularValues(b, v); Pair p; @@ -281,7 +284,7 @@ public class TRSRTransformation implements IModelState, ITransformation return f; } - private static final float eps = 1e-7f; + private static final float eps = 1e-6f; 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); @@ -365,50 +368,59 @@ public class TRSRTransformation implements IModelState, ITransformation 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); + if(m.m01 * m.m01 + m.m10 * m.m10 > eps) + { + 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); + if(m.m02 * m.m02 + m.m20 * m.m20 > eps) + { + 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); + if(m.m12 * m.m12 + m.m21 * m.m21 > eps) + { + 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; } @@ -617,4 +629,35 @@ public class TRSRTransformation implements IModelState, ITransformation r.m33 = m.m33; return r; } + + public static Vector3f lerp(Tuple3f from, Tuple3f to, float progress) + { + Vector3f res = new Vector3f(from); + res.interpolate(from, to, progress); + return res; + } + + public static Vector4f lerp(Tuple4f from, Tuple4f to, float progress) + { + Vector4f res = new Vector4f(from); + res.interpolate(from, to, progress); + return res; + } + + public static Quat4f slerp(Quat4f from, Quat4f to, float progress) + { + Quat4f res = new Quat4f(); + res.interpolate(from, to, progress); + return res; + } + + public TRSRTransformation slerp(TRSRTransformation that, float progress) + { + return new TRSRTransformation( + lerp(this.getTranslation(), that.getTranslation(), progress), + slerp(this.getLeftRot(), that.getLeftRot(), progress), + lerp(this.getScale(), that.getScale(), progress), + slerp(this.getRightRot(), that.getRightRot(), progress) + ); + } } diff --git a/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java b/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java index d75ab37dab..5cfad738db 100644 --- a/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java +++ b/src/main/java/net/minecraftforge/client/model/b3d/B3DLoader.java @@ -25,6 +25,7 @@ import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.model.IBakedModel; import net.minecraft.item.ItemStack; import net.minecraft.util.EnumFacing; +import net.minecraft.util.MathHelper; import net.minecraft.util.ResourceLocation; import net.minecraftforge.client.model.ICustomModelLoader; import net.minecraftforge.client.model.IFlexibleBakedModel; @@ -65,6 +66,9 @@ import com.google.common.cache.LoadingCache; import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; /* * Loader for Blitz3D models. @@ -138,16 +142,19 @@ public class B3DLoader implements ICustomModelLoader 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)); + return new ModelWrapper(modelLocation, model, ImmutableSet.of(mesh), 1); } 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 ModelWrapper(modelLocation, model, ImmutableSet.of(), 1); } - return new Wrapper(modelLocation, model.getTextures(), (Node)model.getRoot()); + return new ModelWrapper(modelLocation, model, ImmutableSet.of(((Node)model.getRoot()).getName()), 1); } + /** + * @deprecated Use json or IModelCustomData.process + */ + @Deprecated public static class B3DMeshLocation extends ResourceLocation { public final String mesh; @@ -192,17 +199,31 @@ public class B3DLoader implements ICustomModelLoader { private final Animation animation; private final int frame; + private final int nextFrame; + private final float progress; private final IModelState parent; public B3DState(Animation animation, int frame) { - this(animation, frame, null); + this(animation, frame, frame, 0); } public B3DState(Animation animation, int frame, IModelState parent) + { + this(animation, frame, frame, 0, parent); + } + + public B3DState(Animation animation, int frame, int nextFrame, float progress) + { + this(animation, frame, nextFrame, progress, null); + } + + public B3DState(Animation animation, int frame, int nextFrame, float progress, IModelState parent) { this.animation = animation; this.frame = frame; + this.nextFrame = nextFrame; + this.progress = MathHelper.clamp_float(progress, 0, 1); this.parent = getParent(parent); } @@ -223,17 +244,47 @@ public class B3DLoader implements ICustomModelLoader return frame; } + public int getNextFrame() + { + return nextFrame; + } + + public float getProgress() + { + return progress; + } + + public IModelState getParent() + { + return parent; + } + public TRSRTransformation apply(IModelPart part) { if(!(part instanceof PartWrapper)) { throw new IllegalArgumentException("B3DState can only be applied to b3d models"); } + Node node = ((PartWrapper)part).getNode(); + TRSRTransformation nodeTransform; + if(progress < 1e-5 || frame == nextFrame) + { + nodeTransform = getNodeMatrix(node, frame); + } + else if(progress > 1 - 1e-5) + { + nodeTransform = getNodeMatrix(node, nextFrame); + } + else + { + nodeTransform = getNodeMatrix(node, frame); + nodeTransform = nodeTransform.slerp(getNodeMatrix(node, nextFrame), progress); + } if(parent != null) { - return parent.apply(part).compose(getNodeMatrix(((PartWrapper)part).getNode())); + return parent.apply(part).compose(nodeTransform); } - return getNodeMatrix(((PartWrapper)part).getNode()); + return nodeTransform; } private static LoadingCache, Integer>, TRSRTransformation> cache = CacheBuilder.newBuilder() @@ -252,6 +303,11 @@ public class B3DLoader implements ICustomModelLoader return cache.getUnchecked(Triple., Integer>of(animation, node, frame)); } + public TRSRTransformation getNodeMatrix(Node node, int frame) + { + return cache.getUnchecked(Triple., Integer>of(animation, node, frame)); + } + public static TRSRTransformation getNodeMatrix(Animation animation, Node node, int frame) { TRSRTransformation ret = TRSRTransformation.identity(); @@ -326,20 +382,36 @@ public class B3DLoader implements ICustomModelLoader } } + /** + * @deprecated Use ModelWrapper, this will be removed in 1.9 + */ + @Deprecated public static class Wrapper extends PartWrapper implements IRetexturableModel, IModelCustomData { private final ResourceLocation location; + private final ImmutableSet meshes; private final ImmutableMap textures; public Wrapper(ResourceLocation location, List textures, B3DModel.Node mesh) { - this(location, buildTextures(textures), mesh); + this(location, ImmutableSet.of(), buildTextures(textures), mesh); } public Wrapper(ResourceLocation location, ImmutableMap textures, B3DModel.Node mesh) + { + this(location, ImmutableSet.of(), textures, mesh); + } + + public Wrapper(ResourceLocation location, ImmutableSet meshes, List textures, B3DModel.Node mesh) + { + this(location, meshes, buildTextures(textures), mesh); + } + + public Wrapper(ResourceLocation location, ImmutableSet meshes, ImmutableMap textures, B3DModel.Node mesh) { super(mesh); this.location = location; + this.meshes = meshes; this.textures = textures; } @@ -398,7 +470,7 @@ public class B3DLoader implements ICustomModelLoader } } builder.put("missingno", missing); - return new BakedWrapper(this, state, format, builder.build()); + return new BakedWrapper(getNode(), state, format, meshes, builder.build()); } public B3DState getDefaultState() @@ -465,29 +537,211 @@ public class B3DLoader implements ICustomModelLoader @Override public IModel process(ImmutableMap customData) { - // TODO keyframe - return null; + return this; + } + } + public static class ModelWrapper implements IRetexturableModel, IModelCustomData + { + private final ResourceLocation modelLocation; + private final B3DModel model; + private final ImmutableSet meshes; + private final ImmutableMap textures; + private final int defaultKey; + + public ModelWrapper(ResourceLocation modelLocation, B3DModel model, ImmutableSet meshes, int defaultKey) + { + this(modelLocation, model, meshes, defaultKey, buildTextures(model.getTextures())); + } + + public ModelWrapper(ResourceLocation modelLocation, B3DModel model, ImmutableSet meshes, int defaultKey, ImmutableMap textures) + { + this.modelLocation = modelLocation; + this.model = model; + this.meshes = meshes; + this.textures = textures; + this.defaultKey = defaultKey; + } + + private static ImmutableMap buildTextures(List textures) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + + for(Texture t : textures) + { + String path = t.getPath(); + String location = getLocation(path); + if(!location.startsWith("#")) location = "#" + location; + builder.put(path, new ResourceLocation(location)); + } + return builder.build(); + } + + private static String getLocation(String path) + { + if(path.endsWith(".png")) path = path.substring(0, path.length() - ".png".length()); + return path; + } + + @Override + public Collection getDependencies() + { + return Collections.emptyList(); + } + + @Override + public Collection getTextures() + { + return Collections2.filter(textures.values(), new Predicate() + { + public boolean apply(ResourceLocation loc) + { + return !loc.getResourcePath().startsWith("#"); + } + }); + } + + @Override + public IFlexibleBakedModel bake(IModelState state, VertexFormat format, Function bakedTextureGetter) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + TextureAtlasSprite missing = bakedTextureGetter.apply(new ResourceLocation("missingno")); + for(Map.Entry e : textures.entrySet()) + { + if(e.getValue().getResourcePath().startsWith("#")) + { + FMLLog.severe("unresolved texture '%s' for b3d model '%s'", e.getValue().getResourcePath(), modelLocation); + builder.put(e.getKey(), missing); + } + else + { + builder.put(e.getKey(), bakedTextureGetter.apply(e.getValue())); + } + } + builder.put("missingno", missing); + return new BakedWrapper(model.getRoot(), state, format, meshes, builder.build()); + } + + @Override + public IModelState getDefaultState() + { + return new B3DState(model.getRoot().getAnimation(), defaultKey, defaultKey, 0); + } + + @Override + public IModel retexture(ImmutableMap textures) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for(Map.Entry e : this.textures.entrySet()) + { + String path = e.getKey(); + String loc = getLocation(path); + if(textures.containsKey(loc)) + { + String newLoc = textures.get(loc); + if(newLoc == null) newLoc = getLocation(path); + builder.put(e.getKey(), new ResourceLocation(newLoc)); + } + else + { + builder.put(e); + } + } + return new ModelWrapper(modelLocation, model, meshes, defaultKey, builder.build()); + } + + @Override + public IModel process(ImmutableMap data) + { + if(data.containsKey("mesh")) + { + JsonElement e = new JsonParser().parse(data.get("mesh")); + if(e.isJsonPrimitive() && e.getAsJsonPrimitive().isString()) + { + return new ModelWrapper(modelLocation, model, ImmutableSet.of(e.getAsString()), defaultKey, textures); + } + else if (e.isJsonArray()) + { + ImmutableSet.Builder builder = ImmutableSet.builder(); + for(JsonElement s : e.getAsJsonArray()) + { + if(s.isJsonPrimitive() && s.getAsJsonPrimitive().isString()) + { + builder.add(s.getAsString()); + } + else + { + FMLLog.severe("unknown mesh definition '%s' in array for b3d model '%s'", s.toString(), modelLocation); + return this; + } + } + return new ModelWrapper(modelLocation, model, builder.build(), defaultKey, textures); + } + else + { + FMLLog.severe("unknown mesh definition '%s' for b3d model '%s'", e.toString(), modelLocation); + return this; + } + } + if(data.containsKey("key")) + { + JsonElement e = new JsonParser().parse(data.get("key")); + if(e.isJsonPrimitive() && e.getAsJsonPrimitive().isNumber()) + { + return new ModelWrapper(modelLocation, model, meshes, e.getAsNumber().intValue(), textures); + } + else + { + FMLLog.severe("unknown keyframe definition '%s' for b3d model '%s'", e.toString(), modelLocation); + return this; + } + } + return this; } } private static class BakedWrapper implements IFlexibleBakedModel, ISmartBlockModel, ISmartItemModel, IPerspectiveAwareModel { - private final B3DLoader.Wrapper model; + private final Node node; private final IModelState state; private final VertexFormat format; + private final ImmutableSet meshes; private final ImmutableMap textures; + private final LoadingCache cache; 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) + public BakedWrapper(Node node, IModelState state, VertexFormat format, ImmutableSet meshes, ImmutableMap textures) { - this.model = model; + this(node, state, format, meshes, textures, CacheBuilder.newBuilder() + .maximumSize(128) + .expireAfterAccess(2, TimeUnit.MINUTES) + .build(new CacheLoader() + { + public BakedWrapper load(Integer frame) throws Exception + { + IModelState parent = state; + Animation newAnimation = null; + if(parent instanceof B3DState) + { + B3DState ps = (B3DState)parent; + parent = ps.getParent(); + } + if(newAnimation == null) + { + newAnimation = node.getAnimation(); + } + return new BakedWrapper(node, new B3DState(newAnimation, frame, frame, 0, parent), format, meshes, textures); + } + })); + } + public BakedWrapper(Node node, IModelState state, VertexFormat format, ImmutableSet meshes, ImmutableMap textures, LoadingCache cache) + { + this.node = node; this.state = state; this.format = format; + this.meshes = meshes; this.textures = textures; + this.cache = cache; } public List getFaceQuads(EnumFacing side) @@ -495,47 +749,43 @@ public class B3DLoader implements ICustomModelLoader 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()) + for(Node child : node.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()); - } + builder.addAll(new BakedWrapper(child, state, format, meshes, textures).getGeneralQuads()); } - mesh.getKind().getWeightMap(); - Collection faces = mesh.getKind().getFaces(); - faces = mesh.getKind().bake(new Function, Matrix4f>() + if(node.getKind() instanceof Mesh && meshes.contains(node.getName())) { - // gets transformation in global space - public Matrix4f apply(Node node) + Mesh mesh = (Mesh)node.getKind(); + Collection faces = mesh.bake(new Function, Matrix4f>() { - return state.apply(PartWrapper.create(node)).getMatrix(); + // gets transformation in global space + public Matrix4f apply(Node node) + { + return state.apply(PartWrapper.create(node)).getMatrix(); + } + }); + for(Face f : faces) + { + UnpackedBakedQuad.Builder quadBuilder = new UnpackedBakedQuad.Builder(format); + quadBuilder.setQuadOrientation(EnumFacing.getFacingFromVector(f.getNormal().x, f.getNormal().y, f.getNormal().z)); + quadBuilder.setQuadColored(); + List textures = null; + if(f.getBrush() != null) textures = f.getBrush().getTextures(); + TextureAtlasSprite sprite; + if(textures == null || textures.isEmpty()) sprite = this.textures.get("missingno"); + else if(textures.get(0) == B3DModel.Texture.White) sprite = ModelLoader.White.instance; + else sprite = this.textures.get(textures.get(0).getPath()); + putVertexData(quadBuilder, f.getV1(), f.getNormal(), sprite); + putVertexData(quadBuilder, f.getV2(), f.getNormal(), sprite); + putVertexData(quadBuilder, f.getV3(), f.getNormal(), sprite); + putVertexData(quadBuilder, f.getV3(), f.getNormal(), sprite); + builder.add(quadBuilder.build()); } - }); - for(Face f : faces) - { - UnpackedBakedQuad.Builder quadBuilder = new UnpackedBakedQuad.Builder(format); - quadBuilder.setQuadOrientation(EnumFacing.getFacingFromVector(f.getNormal().x, f.getNormal().y, f.getNormal().z)); - quadBuilder.setQuadColored(); - List textures = null; - if(f.getBrush() != null) textures = f.getBrush().getTextures(); - TextureAtlasSprite sprite; - if(textures == null || textures.isEmpty()) sprite = this.textures.get("missingno"); - else if(textures.get(0) == B3DModel.Texture.White) sprite = ModelLoader.White.instance; - else sprite = this.textures.get(textures.get(0).getPath()); - putVertexData(quadBuilder, f.getV1(), f.getNormal(), sprite); - putVertexData(quadBuilder, f.getV2(), f.getNormal(), sprite); - putVertexData(quadBuilder, f.getV3(), f.getNormal(), sprite); - putVertexData(quadBuilder, f.getV3(), f.getNormal(), sprite); - builder.add(quadBuilder.build()); } quads = builder.build(); } @@ -632,24 +882,30 @@ public class B3DLoader implements ICustomModelLoader B3DState s = exState.getValue(B3DFrameProperty.instance); if(s != null) { - return getCachedModel(s.getFrame()); + //return getCachedModel(s.getFrame()); + IModelState parent = this.state; + Animation newAnimation = s.getAnimation(); + if(parent instanceof B3DState) + { + B3DState ps = (B3DState)parent; + parent = ps.getParent(); + } + if(newAnimation == null) + { + newAnimation = node.getAnimation(); + } + if(s.getFrame() == s.getNextFrame()) + { + return cache.getUnchecked(s.getFrame()); + } + B3DState newState = new B3DState(newAnimation, s.getFrame(), s.getNextFrame(), s.getProgress(), parent); + return new BakedWrapper(node, newState, format, meshes, textures); } } } 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, state), format, textures)); - } - return cache.get(frame); - } - public VertexFormat getFormat() { return format; @@ -665,7 +921,7 @@ public class B3DLoader implements ICustomModelLoader { if(state instanceof IPerspectiveState) { - return Pair.of((IBakedModel)this, TRSRTransformation.blockCornerToCenter(((IPerspectiveState)state).forPerspective(cameraTransformType).apply(model)).getMatrix()); + return Pair.of((IBakedModel)this, TRSRTransformation.blockCornerToCenter(((IPerspectiveState)state).forPerspective(cameraTransformType).apply(PartWrapper.create(node))).getMatrix()); } return Pair.of((IBakedModel)this, null); } diff --git a/src/test/java/net/minecraftforge/debug/ModelAnimationDebug.java b/src/test/java/net/minecraftforge/debug/ModelAnimationDebug.java new file mode 100644 index 0000000000..48b8ec2a1d --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/ModelAnimationDebug.java @@ -0,0 +1,285 @@ +package net.minecraftforge.debug; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockPistonBase; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyDirection; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BlockRendererDispatcher; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.model.IBakedModel; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.BlockPos; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.ITickable; +import net.minecraft.world.World; +import net.minecraftforge.client.model.ISmartBlockModel; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.client.model.b3d.B3DLoader; +import net.minecraftforge.client.model.b3d.B3DLoader.B3DFrameProperty; +import net.minecraftforge.client.model.b3d.B3DLoader.B3DState; +import net.minecraftforge.common.property.ExtendedBlockState; +import net.minecraftforge.common.property.IExtendedBlockState; +import net.minecraftforge.common.property.IUnlistedProperty; +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventHandler; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.FMLInitializationEvent; +import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; +import net.minecraftforge.fml.common.registry.GameRegistry; + +import org.lwjgl.opengl.GL11; + +@Mod(modid = ModelAnimationDebug.MODID, version = ModelAnimationDebug.VERSION) +public class ModelAnimationDebug +{ + public static final String MODID = "forgedebugmodelanimation"; + public static final String VERSION = "0.0"; + + public static String blockName = "test_animation_block"; + public static final PropertyDirection FACING = PropertyDirection.create("facing"); + + @SidedProxy(serverSide = "net.minecraftforge.debug.ModelAnimationDebug$CommonProxy", clientSide = "net.minecraftforge.debug.ModelAnimationDebug$ClientProxy") + public static CommonProxy proxy; + + public static class CommonProxy + { + public void preInit(FMLPreInitializationEvent event) + { + B3DLoader.instance.addDomain(MODID); + GameRegistry.registerBlock(new Block(Material.wood) + { + { + setCreativeTab(CreativeTabs.tabBlock); + setUnlocalizedName(MODID + "." + blockName); + } + + @Override + public ExtendedBlockState createBlockState() + { + return new ExtendedBlockState(this, new IProperty[]{ FACING }, new IUnlistedProperty[]{ B3DFrameProperty.instance }); + } + + @Override + public int getRenderType() { return -1; } + + @Override + public boolean isOpaqueCube() { return false; } + + @Override + public boolean isFullCube() { return false; } + + @Override + public IBlockState onBlockPlaced(World world, BlockPos pos, EnumFacing facing, float hitX, float hitY, float hitZ, int meta, EntityLivingBase placer) + { + return this.getDefaultState().withProperty(FACING, BlockPistonBase.getFacingFromEntity(world, pos, placer)); + } + + @Override + public IBlockState getStateFromMeta(int meta) { + return getDefaultState().withProperty(FACING, EnumFacing.getFront(meta)); + } + + @Override + public int getMetaFromState(IBlockState state) { + return ((EnumFacing)state.getValue(FACING)).getIndex(); + } + + @Override + public boolean hasTileEntity(IBlockState state) { + return true; + } + + @Override + public TileEntity createTileEntity(World world, IBlockState state) { + return new Chest(state); + } + + /*@Override + public IBlockState getExtendedState(IBlockState state, IBlockAccess world, BlockPos pos) { + TileEntity te = world.getTileEntity(pos); + if(te instanceof Chest && state instanceof IExtendedBlockState) + { + return ((Chest)te).getState((IExtendedBlockState)state); + } + return super.getExtendedState(state, world, pos); + }*/ + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumFacing side, float hitX, float hitY, float hitZ) + { + if(world.isRemote) + { + TileEntity te = world.getTileEntity(pos); + if(te instanceof Chest) + { + ((Chest)te).click(); + } + } + return false; + } + }, blockName); + } + + public void init(FMLInitializationEvent event) {} + } + + public static class ClientProxy extends CommonProxy + { + @Override + public void preInit(FMLPreInitializationEvent event) + { + super.preInit(event); + ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(GameRegistry.findBlock(MODID, blockName)), 0, new ModelResourceLocation(MODID.toLowerCase() + ":" + blockName, "inventory")); + } + + @Override + public void init(FMLInitializationEvent event) + { + super.init(event); + ClientRegistry.bindTileEntitySpecialRenderer(Chest.class, ChestRenderer.instance); + } + } + + @EventHandler + public void preInit(FMLPreInitializationEvent event) { proxy.preInit(event); } + + @EventHandler + public void init(FMLInitializationEvent event) { proxy.init(event); } + + private static class Chest extends TileEntity implements ITickable + { + private final int minFrame = 1; + private final int maxFrame = 10; + private int tick = minFrame; + private boolean opening = false; + private boolean closing = false; + + public Chest(IBlockState state) { + } + + /*public IExtendedBlockState getState(IExtendedBlockState state) { + return state.withProperty(B3DFrameProperty.instance, curState); + }*/ + + public void click() + { + if(opening || tick == maxFrame) + { + opening = false; + closing = true; + return; + } + if(closing || tick == minFrame) + { + closing = false; + opening = true; + return; + } + opening = true; + } + + @Override + public void update() + { + if(opening) + { + tick++; + if(tick >= maxFrame) + { + tick = maxFrame; + opening = false; + } + } + if(closing) + { + tick--; + if(tick <= minFrame) + { + tick = minFrame; + closing = false; + } + } + } + + public int getCurFrame() + { + return tick; + } + + public int getNextFrame() + { + if(opening) return Math.min(tick + 1, maxFrame); + if(closing) return Math.max(tick - 1, minFrame); + return tick; + } + } + + private static class ChestRenderer extends TileEntitySpecialRenderer + { + public static ChestRenderer instance = new ChestRenderer(); + private ChestRenderer() {} + + private final BlockRendererDispatcher blockRenderer = Minecraft.getMinecraft().getBlockRendererDispatcher(); + + public void renderTileEntityAt(Chest te, double x, double y, double z, float partialTick, int breakStage) + { + IBlockState state = te.getWorld().getBlockState(te.getPos()); + IBakedModel model = this.blockRenderer.getModelFromBlockState(state, te.getWorld(), te.getPos()); + if(state instanceof IExtendedBlockState) + { + IExtendedBlockState exState = (IExtendedBlockState)state; + if(exState.getUnlistedNames().contains(B3DFrameProperty.instance)) + { + exState = exState.withProperty(B3DFrameProperty.instance, new B3DState(null, te.getCurFrame(), te.getNextFrame(), partialTick)); + if(model instanceof ISmartBlockModel) + { + model = ((ISmartBlockModel)model).handleBlockState(exState); + } + } + } + + Tessellator tessellator = Tessellator.getInstance(); + WorldRenderer worldrenderer = tessellator.getWorldRenderer(); + this.bindTexture(TextureMap.locationBlocksTexture); + RenderHelper.disableStandardItemLighting(); + GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GlStateManager.enableBlend(); + GlStateManager.disableCull(); + + if (Minecraft.isAmbientOcclusionEnabled()) + { + GlStateManager.shadeModel(GL11.GL_SMOOTH); + } + else + { + GlStateManager.shadeModel(GL11.GL_FLAT); + } + + worldrenderer.func_181668_a(7, DefaultVertexFormats.BLOCK); + worldrenderer.setTranslation(x - te.getPos().getX(), y - te.getPos().getY(), z - te.getPos().getZ()); + + this.blockRenderer.getBlockModelRenderer().renderModel(te.getWorld(), model, state, te.getPos(), worldrenderer, false); + + worldrenderer.setTranslation(0, 0, 0); + tessellator.draw(); + + RenderHelper.enableStandardItemLighting(); + } + } +} + diff --git a/src/test/resources/assets/forgedebugmodelanimation/blockstates/test_animation_block.json b/src/test/resources/assets/forgedebugmodelanimation/blockstates/test_animation_block.json new file mode 100644 index 0000000000..b12b0913d7 --- /dev/null +++ b/src/test/resources/assets/forgedebugmodelanimation/blockstates/test_animation_block.json @@ -0,0 +1,26 @@ +{ + "forge_marker": 1, + "defaults": { + "textures": { + "#chest": "entity/chest/normal" + }, + "model": "forgedebugmodelloaderregistry:chest.b3d", + "custom": { + "mesh": ["Cube.001"] + } + }, + "variants": { + "normal": [{}], + "inventory": [{ + "transform": "forge:default-block" + }], + "facing": { + "down": { "transform": { "rotation": { "x": 90 } } }, + "up": { "transform": { "rotation": { "x": 270 } } }, + "north": { "transform": { "rotation": { "y": 180 } } }, + "south": { "transform": "identity" }, + "west": { "transform": { "rotation": { "y": 90 } } }, + "east": { "transform": { "rotation": { "y": 270 } } } + } + } +}