Model animation system.
Main things of interest: * IAnimationStateMachine - state machine for animations; can load from json. * AnimationTESR - automatic TESR for animated models. * AnimationModelBase - same for entities. * ITimeValue - time-varying value, used to control animation parameters from code. * TESRs can now be batched - look at TESR.renderTileEntityFast + TE.hasFastRenderer. * RegionRenderCache is not accessible to TESRs and other client-side logic - MinecraftForgeClient.getRegionRenderCache.
This commit is contained in:
parent
e9a86f27fb
commit
0710bdf3f5
44 changed files with 3185 additions and 252 deletions
|
@ -56,7 +56,15 @@
|
|||
flag2 = this.field_175010_j.func_178635_a(entity2, p_180446_2_, d0, d1, d2) || entity2.field_70153_n == this.field_72777_q.field_71439_g;
|
||||
|
||||
if (!flag2)
|
||||
@@ -662,6 +673,7 @@
|
||||
@@ -654,6 +665,7 @@
|
||||
this.field_72769_h.field_72984_F.func_76318_c("blockentities");
|
||||
RenderHelper.func_74519_b();
|
||||
|
||||
+ TileEntityRendererDispatcher.field_147556_a.preDrawBatch();
|
||||
for (RenderGlobal.ContainerLocalRenderInformation renderglobal$containerlocalrenderinformation1 : this.field_72755_R)
|
||||
{
|
||||
List<TileEntity> list1 = renderglobal$containerlocalrenderinformation1.field_178036_a.func_178571_g().func_178485_b();
|
||||
@@ -662,6 +674,7 @@
|
||||
{
|
||||
for (TileEntity tileentity2 : list1)
|
||||
{
|
||||
|
@ -64,7 +72,7 @@
|
|||
TileEntityRendererDispatcher.field_147556_a.func_180546_a(tileentity2, p_180446_3_, -1);
|
||||
}
|
||||
}
|
||||
@@ -671,6 +683,7 @@
|
||||
@@ -671,9 +684,11 @@
|
||||
{
|
||||
for (TileEntity tileentity : this.field_181024_n)
|
||||
{
|
||||
|
@ -72,7 +80,11 @@
|
|||
TileEntityRendererDispatcher.field_147556_a.func_180546_a(tileentity, p_180446_3_, -1);
|
||||
}
|
||||
}
|
||||
@@ -700,7 +713,7 @@
|
||||
+ TileEntityRendererDispatcher.field_147556_a.drawBatch(pass);
|
||||
|
||||
this.func_180443_s();
|
||||
|
||||
@@ -700,7 +715,7 @@
|
||||
|
||||
Block block = this.field_72769_h.func_180495_p(blockpos).func_177230_c();
|
||||
|
||||
|
@ -81,7 +93,7 @@
|
|||
{
|
||||
TileEntityRendererDispatcher.field_147556_a.func_180546_a(tileentity1, p_180446_3_, destroyblockprogress.func_73106_e());
|
||||
}
|
||||
@@ -1161,6 +1174,12 @@
|
||||
@@ -1161,6 +1176,12 @@
|
||||
|
||||
public void func_174976_a(float p_174976_1_, int p_174976_2_)
|
||||
{
|
||||
|
@ -94,7 +106,7 @@
|
|||
if (this.field_72777_q.field_71441_e.field_73011_w.func_177502_q() == 1)
|
||||
{
|
||||
this.func_180448_r();
|
||||
@@ -1378,6 +1397,12 @@
|
||||
@@ -1378,6 +1399,12 @@
|
||||
|
||||
public void func_180447_b(float p_180447_1_, int p_180447_2_)
|
||||
{
|
||||
|
@ -107,7 +119,7 @@
|
|||
if (this.field_72777_q.field_71441_e.field_73011_w.func_76569_d())
|
||||
{
|
||||
if (this.field_72777_q.field_71474_y.func_181147_e() == 2)
|
||||
@@ -1793,8 +1818,11 @@
|
||||
@@ -1793,8 +1820,11 @@
|
||||
double d4 = (double)blockpos.func_177956_o() - d1;
|
||||
double d5 = (double)blockpos.func_177952_p() - d2;
|
||||
Block block = this.field_72769_h.func_180495_p(blockpos).func_177230_c();
|
||||
|
@ -120,7 +132,7 @@
|
|||
{
|
||||
if (d3 * d3 + d4 * d4 + d5 * d5 > 1024.0D)
|
||||
{
|
||||
@@ -1949,13 +1977,16 @@
|
||||
@@ -1949,13 +1979,16 @@
|
||||
if (p_174961_1_ != null)
|
||||
{
|
||||
ItemRecord itemrecord = ItemRecord.func_150926_b(p_174961_1_);
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
--- ../src-base/minecraft/net/minecraft/client/renderer/chunk/RenderChunk.java
|
||||
+++ ../src-work/minecraft/net/minecraft/client/renderer/chunk/RenderChunk.java
|
||||
@@ -131,7 +131,7 @@
|
||||
@@ -131,7 +131,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
- iblockaccess = new RegionRenderCache(this.field_178588_d, blockpos.func_177982_a(-1, -1, -1), blockpos1.func_177982_a(1, 1, 1), 1);
|
||||
+ iblockaccess = createRegionRenderCache(this.field_178588_d, blockpos.func_177982_a(-1, -1, -1), blockpos1.func_177982_a(1, 1, 1), 1);
|
||||
+ RegionRenderCache cache = createRegionRenderCache(this.field_178588_d, blockpos.func_177982_a(-1, -1, -1), blockpos1.func_177982_a(1, 1, 1), 1);
|
||||
+ net.minecraftforge.client.MinecraftForgeClient.onRebuildChunk(field_178588_d, field_178586_f, cache);
|
||||
+ iblockaccess = cache;
|
||||
p_178581_4_.func_178543_a(compiledchunk);
|
||||
}
|
||||
finally
|
||||
@@ -158,7 +158,7 @@
|
||||
@@ -158,7 +160,7 @@
|
||||
lvt_10_1_.func_178606_a(blockpos$mutableblockpos);
|
||||
}
|
||||
|
||||
|
@ -18,7 +20,7 @@
|
|||
{
|
||||
TileEntity tileentity = iblockaccess.func_175625_s(new BlockPos(blockpos$mutableblockpos));
|
||||
TileEntitySpecialRenderer<TileEntity> tileentityspecialrenderer = TileEntityRendererDispatcher.field_147556_a.<TileEntity>func_147547_b(tileentity);
|
||||
@@ -174,7 +174,9 @@
|
||||
@@ -174,7 +176,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +31,7 @@
|
|||
int j = enumworldblocklayer1.ordinal();
|
||||
|
||||
if (block.func_149645_b() != -1)
|
||||
@@ -189,6 +191,7 @@
|
||||
@@ -189,6 +193,7 @@
|
||||
|
||||
aboolean[j] |= blockrendererdispatcher.func_175018_a(iblockstate, blockpos$mutableblockpos, iblockaccess, worldrenderer);
|
||||
}
|
||||
|
@ -37,7 +39,7 @@
|
|||
}
|
||||
|
||||
for (EnumWorldBlockLayer enumworldblocklayer : EnumWorldBlockLayer.values())
|
||||
@@ -385,6 +388,26 @@
|
||||
@@ -385,6 +390,26 @@
|
||||
return this.field_178593_n;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
--- ../src-base/minecraft/net/minecraft/client/renderer/tileentity/TileEntityRendererDispatcher.java
|
||||
+++ ../src-work/minecraft/net/minecraft/client/renderer/tileentity/TileEntityRendererDispatcher.java
|
||||
@@ -102,11 +102,14 @@
|
||||
{
|
||||
if (p_180546_1_.func_145835_a(this.field_147560_j, this.field_147561_k, this.field_147558_l) < p_180546_1_.func_145833_n())
|
||||
{
|
||||
+ if(!p_180546_1_.hasFastRenderer())
|
||||
+ {
|
||||
int i = this.field_147550_f.func_175626_b(p_180546_1_.func_174877_v(), 0);
|
||||
int j = i % 65536;
|
||||
int k = i / 65536;
|
||||
OpenGlHelper.func_77475_a(OpenGlHelper.field_77476_b, (float)j / 1.0F, (float)k / 1.0F);
|
||||
GlStateManager.func_179131_c(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
+ }
|
||||
BlockPos blockpos = p_180546_1_.func_174877_v();
|
||||
this.func_178469_a(p_180546_1_, (double)blockpos.func_177958_n() - field_147554_b, (double)blockpos.func_177956_o() - field_147555_c, (double)blockpos.func_177952_p() - field_147552_d, p_180546_2_, p_180546_3_);
|
||||
}
|
||||
@@ -125,6 +128,11 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
+ if(p_178469_1_.hasFastRenderer())
|
||||
+ {
|
||||
+ tileentityspecialrenderer.renderTileEntityFast(p_178469_1_, p_178469_2_, p_178469_4_, p_178469_6_, p_178469_8_, p_178469_9_, batchBuffer.func_178180_c());
|
||||
+ }
|
||||
+ else
|
||||
tileentityspecialrenderer.func_180535_a(p_178469_1_, p_178469_2_, p_178469_4_, p_178469_6_, p_178469_8_, p_178469_9_);
|
||||
}
|
||||
catch (Throwable throwable)
|
||||
@@ -146,4 +154,49 @@
|
||||
{
|
||||
return this.field_147557_n;
|
||||
}
|
||||
+
|
||||
+ /* ======================================== FORGE START =====================================*/
|
||||
+ /**
|
||||
+ * Buffer used for batched TESRs
|
||||
+ */
|
||||
+ private net.minecraft.client.renderer.Tessellator batchBuffer = new net.minecraft.client.renderer.Tessellator(0x200000);
|
||||
+
|
||||
+ /**
|
||||
+ * Prepare for a batched TESR rendering.
|
||||
+ * You probably shouldn't call this manually.
|
||||
+ */
|
||||
+ public void preDrawBatch()
|
||||
+ {
|
||||
+ batchBuffer.func_178180_c().func_181668_a(org.lwjgl.opengl.GL11.GL_QUADS, net.minecraft.client.renderer.vertex.DefaultVertexFormats.field_176600_a);
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Render all TESRs batched so far.
|
||||
+ * You probably shouldn't call this manually.
|
||||
+ */
|
||||
+ public void drawBatch(int pass)
|
||||
+ {
|
||||
+ field_147553_e.func_110577_a(net.minecraft.client.renderer.texture.TextureMap.field_110575_b);
|
||||
+ net.minecraft.client.renderer.RenderHelper.func_74518_a();
|
||||
+ GlStateManager.func_179112_b(org.lwjgl.opengl.GL11.GL_SRC_ALPHA, org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
+ GlStateManager.func_179147_l();
|
||||
+ GlStateManager.func_179129_p();
|
||||
+
|
||||
+ if (net.minecraft.client.Minecraft.func_71379_u())
|
||||
+ {
|
||||
+ GlStateManager.func_179103_j(org.lwjgl.opengl.GL11.GL_SMOOTH);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ GlStateManager.func_179103_j(org.lwjgl.opengl.GL11.GL_FLAT);
|
||||
+ }
|
||||
+
|
||||
+ if(pass > 0)
|
||||
+ {
|
||||
+ batchBuffer.func_178180_c().func_181674_a((float)field_147554_b, (float)field_147555_c, (float)field_147552_d);
|
||||
+ }
|
||||
+ batchBuffer.func_78381_a();
|
||||
+
|
||||
+ net.minecraft.client.renderer.RenderHelper.func_74519_b();
|
||||
+ }
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
--- ../src-base/minecraft/net/minecraft/client/renderer/tileentity/TileEntitySpecialRenderer.java
|
||||
+++ ../src-work/minecraft/net/minecraft/client/renderer/tileentity/TileEntitySpecialRenderer.java
|
||||
@@ -45,4 +45,6 @@
|
||||
{
|
||||
return false;
|
||||
}
|
||||
+
|
||||
+ public void renderTileEntityFast(T te, double x, double y, double z, float partialTicks, int destroyStage, net.minecraft.client.renderer.WorldRenderer worldRenderer) {}
|
||||
}
|
|
@ -65,7 +65,7 @@
|
|||
public double func_145835_a(double p_145835_1_, double p_145835_3_, double p_145835_5_)
|
||||
{
|
||||
double d0 = (double)this.field_174879_c.func_177958_n() + 0.5D - p_145835_1_;
|
||||
@@ -279,4 +293,176 @@
|
||||
@@ -279,4 +293,185 @@
|
||||
func_145826_a(TileEntityFlowerPot.class, "FlowerPot");
|
||||
func_145826_a(TileEntityBanner.class, "Banner");
|
||||
}
|
||||
|
@ -212,6 +212,15 @@
|
|||
+ // NOOP
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * If the TileEntitySpecialRenderer associated with this TileEntity can be batched in with another renderers, and won't access the GL state.
|
||||
+ * If TileEntity returns true, then TESR should have the same functionality as (and probably extend) the FastTESR class.
|
||||
+ */
|
||||
+ public boolean hasFastRenderer()
|
||||
+ {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ private net.minecraftforge.common.capabilities.CapabilityDispatcher capabilities;
|
||||
+ public TileEntity()
|
||||
+ {
|
||||
|
|
|
@ -412,28 +412,30 @@ public class ForgeHooksClient
|
|||
public static void preDraw(EnumUsage attrType, VertexFormat format, int element, int stride, ByteBuffer buffer)
|
||||
{
|
||||
VertexFormatElement attr = format.getElement(element);
|
||||
int count = attr.getElementCount();
|
||||
int constant = attr.getType().getGlConstant();
|
||||
buffer.position(format.func_181720_d(element));
|
||||
switch(attrType)
|
||||
{
|
||||
case POSITION:
|
||||
glVertexPointer(attr.getElementCount(), attr.getType().getGlConstant(), stride, buffer);
|
||||
glVertexPointer(count, constant, stride, buffer);
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
break;
|
||||
case NORMAL:
|
||||
if(attr.getElementCount() != 3)
|
||||
if(count != 3)
|
||||
{
|
||||
throw new IllegalArgumentException("Normal attribute should have the size 3: " + attr);
|
||||
}
|
||||
glNormalPointer(attr.getType().getGlConstant(), stride, buffer);
|
||||
glNormalPointer(constant, stride, buffer);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
break;
|
||||
case COLOR:
|
||||
glColorPointer(attr.getElementCount(), attr.getType().getGlConstant(), stride, buffer);
|
||||
glColorPointer(count, constant, stride, buffer);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
break;
|
||||
case UV:
|
||||
OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit + attr.getIndex());
|
||||
glTexCoordPointer(attr.getElementCount(), attr.getType().getGlConstant(), stride, buffer);
|
||||
glTexCoordPointer(count, constant, stride, buffer);
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
OpenGlHelper.setClientActiveTexture(OpenGlHelper.defaultTexUnit);
|
||||
break;
|
||||
|
@ -441,7 +443,7 @@ public class ForgeHooksClient
|
|||
break;
|
||||
case GENERIC:
|
||||
glEnableVertexAttribArray(attr.getIndex());
|
||||
glVertexAttribPointer(attr.getIndex(), attr.getElementCount(), attr.getType().getGlConstant(), false, stride, buffer);
|
||||
glVertexAttribPointer(attr.getIndex(), count, constant, false, stride, buffer);
|
||||
default:
|
||||
FMLLog.severe("Unimplemented vanilla attribute upload: %s", attrType.getDisplayName());
|
||||
}
|
||||
|
|
|
@ -6,7 +6,18 @@
|
|||
package net.minecraftforge.client;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.minecraft.client.renderer.RegionRenderCache;
|
||||
import net.minecraft.util.BlockPos;
|
||||
import net.minecraft.util.EnumWorldBlockLayer;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
public class MinecraftForgeClient
|
||||
{
|
||||
|
@ -56,4 +67,29 @@ public class MinecraftForgeClient
|
|||
stencilBits.set(bit);
|
||||
}
|
||||
}
|
||||
|
||||
private static final LoadingCache<Pair<World, BlockPos>, RegionRenderCache> regionCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(500)
|
||||
.concurrencyLevel(5)
|
||||
.expireAfterAccess(1, TimeUnit.SECONDS)
|
||||
.build(new CacheLoader<Pair<World, BlockPos>, RegionRenderCache>()
|
||||
{
|
||||
public RegionRenderCache load(Pair<World, BlockPos> key) throws Exception
|
||||
{
|
||||
return new RegionRenderCache(key.getLeft(), key.getRight().add(-1, -1, -1), key.getRight().add(16, 16, 16), 1);
|
||||
}
|
||||
});
|
||||
|
||||
public static void onRebuildChunk(World world, BlockPos position, RegionRenderCache cache)
|
||||
{
|
||||
regionCache.put(Pair.of(world, position), cache);
|
||||
}
|
||||
|
||||
public static RegionRenderCache getRegionRenderCache(World world, BlockPos pos)
|
||||
{
|
||||
int x = pos.getX() & ~0xF;
|
||||
int y = pos.getY() & ~0xF;
|
||||
int z = pos.getZ() & ~0xF;
|
||||
return regionCache.getUnchecked(Pair.of(world, new BlockPos(x, y, z)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import net.minecraft.client.renderer.ItemMeshDefinition;
|
|||
import net.minecraft.client.renderer.ItemModelMesher;
|
||||
import net.minecraft.client.renderer.block.model.BlockPart;
|
||||
import net.minecraft.client.renderer.block.model.BlockPartFace;
|
||||
import net.minecraft.client.renderer.block.model.BlockPartRotation;
|
||||
import net.minecraft.client.renderer.block.model.ItemCameraTransforms;
|
||||
import net.minecraft.client.renderer.block.model.ItemCameraTransforms.TransformType;
|
||||
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
|
||||
|
@ -51,7 +52,13 @@ import net.minecraft.item.ItemStack;
|
|||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraft.util.IRegistry;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.model.animation.Animation;
|
||||
import net.minecraftforge.client.model.animation.IAnimatedModel;
|
||||
import net.minecraftforge.client.model.animation.IClip;
|
||||
import net.minecraftforge.client.model.animation.ModelBlockAnimation;
|
||||
import net.minecraftforge.common.ForgeModContainer;
|
||||
import net.minecraftforge.common.property.IExtendedBlockState;
|
||||
import net.minecraftforge.common.property.Properties;
|
||||
import net.minecraftforge.fluids.Fluid;
|
||||
import net.minecraftforge.fluids.FluidContainerRegistry;
|
||||
import net.minecraftforge.fluids.FluidRegistry;
|
||||
|
@ -129,14 +136,7 @@ public class ModelLoader extends ModelBakery
|
|||
}
|
||||
}
|
||||
});
|
||||
Function<ResourceLocation, TextureAtlasSprite> textureGetter = new Function<ResourceLocation, TextureAtlasSprite>()
|
||||
{
|
||||
public TextureAtlasSprite apply(ResourceLocation location)
|
||||
{
|
||||
return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString());
|
||||
}
|
||||
};
|
||||
IFlexibleBakedModel missingBaked = missingModel.bake(missingModel.getDefaultState(), DefaultVertexFormats.ITEM, textureGetter);
|
||||
IFlexibleBakedModel missingBaked = missingModel.bake(missingModel.getDefaultState(), DefaultVertexFormats.ITEM, DefaultTextureGetter.instance);
|
||||
for (Entry<ModelResourceLocation, IModel> e : stateModels.entrySet())
|
||||
{
|
||||
if(e.getValue() == getMissingModel())
|
||||
|
@ -145,7 +145,7 @@ public class ModelLoader extends ModelBakery
|
|||
}
|
||||
else
|
||||
{
|
||||
bakedRegistry.putObject(e.getKey(), e.getValue().bake(e.getValue().getDefaultState(), DefaultVertexFormats.ITEM, textureGetter));
|
||||
bakedRegistry.putObject(e.getKey(), e.getValue().bake(e.getValue().getDefaultState(), DefaultVertexFormats.ITEM, DefaultTextureGetter.instance));
|
||||
}
|
||||
}
|
||||
return bakedRegistry;
|
||||
|
@ -407,15 +407,17 @@ public class ModelLoader extends ModelBakery
|
|||
textures.addAll(model.getTextures());
|
||||
}
|
||||
|
||||
private class VanillaModelWrapper implements IRetexturableModel
|
||||
private class VanillaModelWrapper implements IRetexturableModel, IAnimatedModel
|
||||
{
|
||||
private final ResourceLocation location;
|
||||
private final ModelBlock model;
|
||||
private final ModelBlockAnimation animation;
|
||||
|
||||
public VanillaModelWrapper(ResourceLocation location, ModelBlock model)
|
||||
public VanillaModelWrapper(ResourceLocation location, ModelBlock model, ModelBlockAnimation animation)
|
||||
{
|
||||
this.location = location;
|
||||
this.model = model;
|
||||
this.animation = animation;
|
||||
}
|
||||
|
||||
public Collection<ResourceLocation> getDependencies()
|
||||
|
@ -508,7 +510,8 @@ public class ModelLoader extends ModelBakery
|
|||
List<TRSRTransformation> newTransforms = Lists.newArrayList();
|
||||
for(int i = 0; i < model.getElements().size(); i++)
|
||||
{
|
||||
newTransforms.add(null);
|
||||
BlockPart part = model.getElements().get(i);
|
||||
newTransforms.add(animation.getPartTransform(state, part, i));
|
||||
}
|
||||
|
||||
ItemCameraTransforms transforms = model.func_181682_g();
|
||||
|
@ -531,7 +534,7 @@ public class ModelLoader extends ModelBakery
|
|||
return bakeNormal(model, perState, state.apply(Optional.<IModelPart>absent()).or(TRSRTransformation.identity()), newTransforms, format, bakedTextureGetter, uvlock);
|
||||
}
|
||||
|
||||
private IFlexibleBakedModel bakeNormal(ModelBlock model, IModelState perState, final TRSRTransformation modelState, List<TRSRTransformation> newTransforms, VertexFormat format, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, boolean uvLocked)
|
||||
private IFlexibleBakedModel bakeNormal(ModelBlock model, IModelState perState, final TRSRTransformation modelState, List<TRSRTransformation> newTransforms, VertexFormat format, final Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, boolean uvLocked)
|
||||
{
|
||||
TextureAtlasSprite particle = bakedTextureGetter.apply(new ResourceLocation(model.resolveTextureName("particle")));
|
||||
SimpleBakedModel.Builder builder = (new SimpleBakedModel.Builder(model)).setTexture(particle);
|
||||
|
@ -542,6 +545,9 @@ public class ModelLoader extends ModelBakery
|
|||
if(newTransforms.get(i) != null)
|
||||
{
|
||||
transformation = transformation.compose(newTransforms.get(i));
|
||||
BlockPartRotation rot = part.partRotation;
|
||||
if(rot == null) rot = new BlockPartRotation(new org.lwjgl.util.vector.Vector3f(), EnumFacing.Axis.Y, 0, false);
|
||||
part = new BlockPart(part.positionFrom, part.positionTo, part.mapFaces, rot, part.shade);
|
||||
}
|
||||
for(Map.Entry<EnumFacing, BlockPartFace> e : (Iterable<Map.Entry<EnumFacing, BlockPartFace>>)part.mapFaces.entrySet())
|
||||
{
|
||||
|
@ -562,13 +568,25 @@ public class ModelLoader extends ModelBakery
|
|||
{
|
||||
public IBakedModel handleBlockState(IBlockState state)
|
||||
{
|
||||
return VanillaModelWrapper.this.handleBlockState(parent, modelState, state);
|
||||
return VanillaModelWrapper.this.handleBlockState(parent, bakedTextureGetter, modelState, state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IBakedModel handleBlockState(IFlexibleBakedModel model, TRSRTransformation modelState, IBlockState state)
|
||||
private IBakedModel handleBlockState(IFlexibleBakedModel model, Function<ResourceLocation, TextureAtlasSprite> bakedTextureGetter, TRSRTransformation modelState, IBlockState state)
|
||||
{
|
||||
if(state instanceof IExtendedBlockState)
|
||||
{
|
||||
IExtendedBlockState exState = (IExtendedBlockState)state;
|
||||
if(exState.getUnlistedNames().contains(Properties.AnimationProperty))
|
||||
{
|
||||
IModelState newState = exState.getValue(Properties.AnimationProperty);
|
||||
if(newState != null)
|
||||
{
|
||||
return VanillaModelWrapper.this.bake(new ModelStateComposition(modelState, newState), model.getFormat(), bakedTextureGetter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
|
@ -630,7 +648,17 @@ public class ModelLoader extends ModelBakery
|
|||
}
|
||||
}
|
||||
|
||||
return new VanillaModelWrapper(location, neweModel);
|
||||
return new VanillaModelWrapper(location, neweModel, animation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<? extends IClip> getClip(String name)
|
||||
{
|
||||
if(animation.getClips().containsKey(name))
|
||||
{
|
||||
return Optional.<IClip>fromNullable(animation.getClips().get(name));
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public IModelState getDefaultState()
|
||||
|
@ -829,7 +857,14 @@ public class ModelLoader extends ModelBakery
|
|||
|
||||
public IModel loadModel(ResourceLocation modelLocation) throws IOException
|
||||
{
|
||||
return loader.new VanillaModelWrapper(modelLocation, loader.loadModel(modelLocation));
|
||||
String modelPath = modelLocation.getResourcePath();
|
||||
if(modelLocation.getResourcePath().startsWith("models/"))
|
||||
{
|
||||
modelPath = modelPath.substring("models/".length());
|
||||
}
|
||||
ResourceLocation armatureLocation = new ResourceLocation(modelLocation.getResourceDomain(), "armatures/" + modelPath + ".json");
|
||||
ModelBlockAnimation animation = Animation.INSTANCE.loadVanillaAnimation(armatureLocation);
|
||||
return loader.new VanillaModelWrapper(modelLocation, loader.loadModel(modelLocation), animation);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -997,4 +1032,19 @@ public class ModelLoader extends ModelBakery
|
|||
mesher.register(e.getKey().getLeft().get(), e.getKey().getRight(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static enum DefaultTextureGetter implements Function<ResourceLocation, TextureAtlasSprite>
|
||||
{
|
||||
instance;
|
||||
|
||||
public TextureAtlasSprite apply(ResourceLocation location)
|
||||
{
|
||||
return Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(location.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static Function<ResourceLocation, TextureAtlasSprite> defaultTextureGetter()
|
||||
{
|
||||
return DefaultTextureGetter.instance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import net.minecraft.client.resources.IResource;
|
||||
import net.minecraft.client.resources.IResourceManager;
|
||||
import net.minecraft.client.resources.IResourceManagerReloadListener;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.model.animation.IAnimationStateMachine;
|
||||
import net.minecraftforge.common.util.JsonUtils;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
public enum Animation implements IResourceManagerReloadListener
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* Get the global world time for the current tick, in seconds.
|
||||
*/
|
||||
public static float getWorldTime(World world)
|
||||
{
|
||||
return getWorldTime(world, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global world time for the current tick + partial tick progress, in seconds.
|
||||
*/
|
||||
public static float getWorldTime(World world, float tickProgress)
|
||||
{
|
||||
return (world.getTotalWorldTime() + tickProgress) / 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a new instance if AnimationStateMachine at specified location, with specified custom parameters.
|
||||
*/
|
||||
public IAnimationStateMachine load(ResourceLocation location, ImmutableMap<String, ITimeValue> customParameters)
|
||||
{
|
||||
try
|
||||
{
|
||||
ClipResolver clipResolver = new ClipResolver();
|
||||
ParameterResolver parameterResolver = new ParameterResolver(customParameters);
|
||||
Clips.CommonClipTypeAdapterFactory.INSTANCE.setClipResolver(clipResolver);
|
||||
TimeValues.CommonTimeValueTypeAdapterFactory.INSTANCE.setValueResolver(parameterResolver);
|
||||
IResource resource = manager.getResource(location);
|
||||
AnimationStateMachine asm = asmGson.fromJson(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8), AnimationStateMachine.class);
|
||||
clipResolver.asm = asm;
|
||||
parameterResolver.asm = asm;
|
||||
asm.initialize();
|
||||
//String json = asmGson.toJson(asm);
|
||||
//System.out.println(location + ": " + json);
|
||||
return asm;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
FMLLog.log(Level.ERROR, e, "Exception loading Animation State Machine %s, skipping", location);
|
||||
return missing;
|
||||
}
|
||||
catch(JsonParseException e)
|
||||
{
|
||||
FMLLog.log(Level.ERROR, e, "Exception loading Animation State Machine %s, skipping", location);
|
||||
return missing;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Clips.CommonClipTypeAdapterFactory.INSTANCE.setClipResolver(null);
|
||||
TimeValues.CommonTimeValueTypeAdapterFactory.INSTANCE.setValueResolver(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load armature associated with a vanilla model.
|
||||
*/
|
||||
public ModelBlockAnimation loadVanillaAnimation(ResourceLocation armatureLocation)
|
||||
{
|
||||
try
|
||||
{
|
||||
IResource resource = null;
|
||||
try
|
||||
{
|
||||
resource = manager.getResource(armatureLocation);
|
||||
}
|
||||
catch(FileNotFoundException e)
|
||||
{
|
||||
// this is normal. FIXME: error reporting?
|
||||
return defaultModelBlockAnimation;
|
||||
}
|
||||
ModelBlockAnimation mba = mbaGson.fromJson(new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8), ModelBlockAnimation.class);
|
||||
String json = mbaGson.toJson(mba);
|
||||
return mba;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
FMLLog.log(Level.ERROR, e, "Exception loading vanilla model aniamtion %s, skipping", armatureLocation);
|
||||
return defaultModelBlockAnimation;
|
||||
}
|
||||
catch(JsonParseException e)
|
||||
{
|
||||
FMLLog.log(Level.ERROR, e, "Exception loading vanilla model aniamtion %s, skipping", armatureLocation);
|
||||
return defaultModelBlockAnimation;
|
||||
}
|
||||
}
|
||||
|
||||
private IResourceManager manager;
|
||||
|
||||
private final AnimationStateMachine missing = new AnimationStateMachine(
|
||||
ImmutableMap.<String, ITimeValue>of(),
|
||||
ImmutableMap.of("missingno", (IClip)Clips.IdentityClip.instance),
|
||||
ImmutableList.of("missingno"),
|
||||
ImmutableMap.<String, String>of(),
|
||||
"missingno");
|
||||
|
||||
{
|
||||
missing.initialize();
|
||||
}
|
||||
|
||||
private final Gson asmGson = new GsonBuilder()
|
||||
.registerTypeAdapter(ImmutableList.class, JsonUtils.ImmutableListTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ImmutableMap.class, JsonUtils.ImmutableMapTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapterFactory(Clips.CommonClipTypeAdapterFactory.INSTANCE)
|
||||
//.registerTypeAdapterFactory(ClipProviders.CommonClipProviderTypeAdapterFactory.INSTANCE)
|
||||
.registerTypeAdapterFactory(TimeValues.CommonTimeValueTypeAdapterFactory.INSTANCE)
|
||||
.setPrettyPrinting()
|
||||
.enableComplexMapKeySerialization()
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
private static final class ClipResolver implements Function<String, IClip>
|
||||
{
|
||||
private AnimationStateMachine asm;
|
||||
|
||||
public IClip apply(String name)
|
||||
{
|
||||
return asm.getClips().get(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ParameterResolver implements Function<String, ITimeValue>
|
||||
{
|
||||
private final ImmutableMap<String, ITimeValue> customParameters;
|
||||
private AnimationStateMachine asm;
|
||||
|
||||
public ParameterResolver(ImmutableMap<String, ITimeValue> customParameters)
|
||||
{
|
||||
this.customParameters = customParameters;
|
||||
}
|
||||
|
||||
public ITimeValue apply(String name)
|
||||
{
|
||||
if(asm.getParameters().containsKey(name))
|
||||
{
|
||||
return asm.getParameters().get(name);
|
||||
}
|
||||
return customParameters.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
private final Gson mbaGson = new GsonBuilder()
|
||||
.registerTypeAdapter(ImmutableList.class, JsonUtils.ImmutableListTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ImmutableMap.class, JsonUtils.ImmutableMapTypeAdapter.INSTANCE)
|
||||
.setPrettyPrinting()
|
||||
.enableComplexMapKeySerialization()
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
private final ModelBlockAnimation defaultModelBlockAnimation = new ModelBlockAnimation(ImmutableMap.<String, ImmutableMap<String, float[]>>of(), ImmutableMap.<String, ModelBlockAnimation.MBClip>of());
|
||||
|
||||
public void onResourceManagerReload(IResourceManager manager)
|
||||
{
|
||||
this.manager = manager;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.model.ModelBase;
|
||||
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.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.resources.model.IBakedModel;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.BlockPos;
|
||||
import net.minecraft.util.EnumFacing;
|
||||
import net.minecraftforge.client.model.IModel;
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.client.model.pipeline.VertexLighterFlat;
|
||||
import net.minecraftforge.client.model.pipeline.WorldRendererConsumer;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* ModelBase that works with the Forge model system and animations.
|
||||
* Some quirks are still left, deprecated for the moment.
|
||||
*/
|
||||
@Deprecated
|
||||
public class AnimationModelBase<T extends Entity & IAnimationProvider> extends ModelBase implements IEventHandler<T>
|
||||
{
|
||||
private final VertexLighterFlat lighter;
|
||||
private final IModel model;
|
||||
|
||||
public AnimationModelBase(IModel model, VertexLighterFlat lighter)
|
||||
{
|
||||
this.model = model;
|
||||
this.lighter = lighter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void render(Entity entity, float limbSwing, float limbSwingSpeed, float timeAlive, float yawHead, float rotationPitch, float scale)
|
||||
{
|
||||
if(!(entity instanceof IAnimationProvider))
|
||||
{
|
||||
throw new ClassCastException("AnimationModelBase expects IAnimationProvider");
|
||||
}
|
||||
|
||||
Pair<IModelState, Iterable<Event>> pair = ((IAnimationProvider)entity).asm().apply(timeAlive / 20);
|
||||
handleEvents((T)entity, timeAlive / 20, pair.getRight());
|
||||
IBakedModel bakedModel = model.bake(pair.getLeft(), DefaultVertexFormats.ITEM, ModelLoader.defaultTextureGetter());
|
||||
|
||||
BlockPos pos = new BlockPos(entity.posX, entity.posY + entity.height, entity.posZ);
|
||||
|
||||
RenderHelper.disableStandardItemLighting();
|
||||
GlStateManager.pushMatrix();
|
||||
GlStateManager.rotate(180, 0, 0, 1);
|
||||
Tessellator tessellator = Tessellator.getInstance();
|
||||
WorldRenderer worldRenderer = tessellator.getWorldRenderer();
|
||||
worldRenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
|
||||
worldRenderer.setTranslation(-0.5, -1.5, -0.5);
|
||||
|
||||
lighter.setParent(new WorldRendererConsumer(worldRenderer));
|
||||
lighter.setWorld(entity.worldObj);
|
||||
lighter.setBlock(Blocks.air);
|
||||
lighter.setBlockPos(pos);
|
||||
boolean empty = true;
|
||||
List<BakedQuad> quads = bakedModel.getGeneralQuads();
|
||||
if(!quads.isEmpty())
|
||||
{
|
||||
lighter.updateBlockInfo();
|
||||
empty = false;
|
||||
for(BakedQuad quad : quads)
|
||||
{
|
||||
quad.pipe(lighter);
|
||||
}
|
||||
}
|
||||
for(EnumFacing side : EnumFacing.values())
|
||||
{
|
||||
quads = bakedModel.getFaceQuads(side);
|
||||
if(!quads.isEmpty())
|
||||
{
|
||||
if(empty) lighter.updateBlockInfo();
|
||||
empty = false;
|
||||
for(BakedQuad quad : quads)
|
||||
{
|
||||
quad.pipe(lighter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// debug quad
|
||||
/*worldRenderer.pos(0, 1, 0).color(0xFF, 0xFF, 0xFF, 0xFF).tex(0, 0).lightmap(240, 0).endVertex();
|
||||
worldRenderer.pos(0, 1, 1).color(0xFF, 0xFF, 0xFF, 0xFF).tex(0, 1).lightmap(240, 0).endVertex();
|
||||
worldRenderer.pos(1, 1, 1).color(0xFF, 0xFF, 0xFF, 0xFF).tex(1, 1).lightmap(240, 0).endVertex();
|
||||
worldRenderer.pos(1, 1, 0).color(0xFF, 0xFF, 0xFF, 0xFF).tex(1, 0).lightmap(240, 0).endVertex();*/
|
||||
|
||||
worldRenderer.setTranslation(0, 0, 0);
|
||||
|
||||
tessellator.draw();
|
||||
GlStateManager.popMatrix();
|
||||
RenderHelper.enableStandardItemLighting();
|
||||
}
|
||||
|
||||
public void handleEvents(T instance, float time, Iterable<Event> pastEvents) {}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.common.model.animation.IAnimationStateMachine;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.commons.lang3.tuple.Triple;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
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.Iterables;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
class AnimationStateMachine implements IAnimationStateMachine
|
||||
{
|
||||
private final ImmutableMap<String, ITimeValue> parameters;
|
||||
private final ImmutableMap<String, IClip> clips;
|
||||
private final ImmutableList<String> states;
|
||||
private final ImmutableMap<String, String> transitions;
|
||||
@SerializedName("start_state")
|
||||
private final String startState;
|
||||
|
||||
private transient boolean shouldHandleSpecialEvents;
|
||||
private transient String currentStateName;
|
||||
private transient IClip currentState;
|
||||
private transient float lastPollTime;
|
||||
|
||||
private static final LoadingCache<Triple<? extends IClip, Float, Float>, Pair<IModelState, Iterable<Event>>> clipCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(100)
|
||||
.expireAfterWrite(100, TimeUnit.MILLISECONDS)
|
||||
.build(new CacheLoader<Triple<? extends IClip, Float, Float>, Pair<IModelState, Iterable<Event>>>()
|
||||
{
|
||||
public Pair<IModelState, Iterable<Event>> load(Triple<? extends IClip, Float, Float> key) throws Exception
|
||||
{
|
||||
return Clips.apply(key.getLeft(), key.getMiddle(), key.getRight());
|
||||
}
|
||||
});
|
||||
|
||||
public AnimationStateMachine(ImmutableMap<String, ITimeValue> parameters, ImmutableMap<String, IClip> clips, ImmutableList<String> states, ImmutableMap<String, String> transitions, String startState)
|
||||
{
|
||||
this.parameters = parameters;
|
||||
this.clips = clips;
|
||||
this.states = states;
|
||||
this.transitions = transitions;
|
||||
this.startState = startState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used during resolution of parameter references.
|
||||
*/
|
||||
ImmutableMap<String, ITimeValue> getParameters()
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used during resolution of clip references.
|
||||
*/
|
||||
ImmutableMap<String, IClip> getClips()
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
|
||||
/**
|
||||
* post-loading initialization hook.
|
||||
*/
|
||||
void initialize()
|
||||
{
|
||||
if(parameters == null)
|
||||
{
|
||||
throw new JsonParseException("Animation State Machine should contain \"parameters\" key.");
|
||||
}
|
||||
if(clips == null)
|
||||
{
|
||||
throw new JsonParseException("Animation State Machine should contain \"clips\" key.");
|
||||
}
|
||||
if(states == null)
|
||||
{
|
||||
throw new JsonParseException("Animation State Machine should contain \"states\" key.");
|
||||
}
|
||||
if(transitions == null)
|
||||
{
|
||||
throw new JsonParseException("Animation State Machine should contain \"transitions\" key.");
|
||||
}
|
||||
shouldHandleSpecialEvents = true;
|
||||
lastPollTime = Float.NEGATIVE_INFINITY;
|
||||
// setting the starting state
|
||||
IClip state = clips.get(startState);
|
||||
if(!clips.containsKey(startState) || !states.contains(startState))
|
||||
{
|
||||
throw new IllegalStateException("unknown state: " + startState);
|
||||
}
|
||||
currentStateName = startState;
|
||||
currentState = state;
|
||||
}
|
||||
|
||||
public Pair<IModelState, Iterable<Event>> apply(float time)
|
||||
{
|
||||
if(lastPollTime == Float.NEGATIVE_INFINITY)
|
||||
{
|
||||
lastPollTime = time;
|
||||
}
|
||||
Pair<IModelState, Iterable<Event>> pair = clipCache.getUnchecked(Triple.of(currentState, lastPollTime, time));
|
||||
lastPollTime = time;
|
||||
boolean shouldFilter = false;
|
||||
if(shouldHandleSpecialEvents)
|
||||
{
|
||||
for(Event event : ImmutableList.copyOf(pair.getRight()).reverse())
|
||||
{
|
||||
if(event.event().startsWith("!"))
|
||||
{
|
||||
shouldFilter = true;
|
||||
if(event.event().startsWith("!transition:"))
|
||||
{
|
||||
String newState = event.event().substring("!transition:".length());
|
||||
transition(newState);
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("Unknown special event \"" + event.event() + "\", ignoring");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!shouldFilter)
|
||||
{
|
||||
return pair;
|
||||
}
|
||||
return Pair.of(pair.getLeft(), Iterables.filter(pair.getRight(), new Predicate<Event>()
|
||||
{
|
||||
public boolean apply(Event event)
|
||||
{
|
||||
return !event.event().startsWith("!");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public void transition(String newState)
|
||||
{
|
||||
IClip nc = clips.get(newState);
|
||||
if(!clips.containsKey(newState) || !states.contains(newState))
|
||||
{
|
||||
throw new IllegalStateException("unknown state: " + newState);
|
||||
}
|
||||
if(!transitions.get(currentStateName).equals(newState))
|
||||
{
|
||||
throw new IllegalArgumentException("no transition from current clip \"" + currentStateName + "\" to the clip \"" + newState + "\" found.");
|
||||
}
|
||||
currentStateName = newState;
|
||||
currentState = nc;
|
||||
}
|
||||
|
||||
public String currentState()
|
||||
{
|
||||
return currentStateName;
|
||||
}
|
||||
|
||||
public void shouldHandleSpecialEvents(boolean value)
|
||||
{
|
||||
shouldHandleSpecialEvents = true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.minecraft.block.state.IBlockState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BlockRendererDispatcher;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.client.resources.model.IBakedModel;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.BlockPos;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraftforge.client.MinecraftForgeClient;
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.client.model.ISmartBlockModel;
|
||||
import net.minecraftforge.common.property.IExtendedBlockState;
|
||||
import net.minecraftforge.common.property.Properties;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
/**
|
||||
* Generic TileEntitySpecialRenderer that works with the Forge model system and animations.
|
||||
*/
|
||||
public class AnimationTESR<T extends TileEntity & IAnimationProvider> extends FastTESR<T> implements IEventHandler<T>
|
||||
{
|
||||
protected static BlockRendererDispatcher blockRenderer;
|
||||
|
||||
protected static final LoadingCache<Pair<IExtendedBlockState, IModelState>, IBakedModel> modelCache = CacheBuilder.newBuilder().maximumSize(10).expireAfterWrite(100, TimeUnit.MILLISECONDS).build(new CacheLoader<Pair<IExtendedBlockState, IModelState>, IBakedModel>()
|
||||
{
|
||||
public IBakedModel load(Pair<IExtendedBlockState, IModelState> key) throws Exception
|
||||
{
|
||||
IBakedModel model = blockRenderer.getBlockModelShapes().getModelForState(key.getLeft().getClean());
|
||||
if(model instanceof ISmartBlockModel)
|
||||
{
|
||||
model = ((ISmartBlockModel)model).handleBlockState(key.getLeft().withProperty(Properties.AnimationProperty, key.getRight()));
|
||||
}
|
||||
return model;
|
||||
}
|
||||
});
|
||||
|
||||
protected static IBakedModel getModel(IExtendedBlockState state, IModelState modelState)
|
||||
{
|
||||
return modelCache.getUnchecked(Pair.of(state, modelState));
|
||||
}
|
||||
|
||||
public void renderTileEntityFast(T te, double x, double y, double z, float partialTick, int breakStage, WorldRenderer renderer)
|
||||
{
|
||||
if(blockRenderer == null) blockRenderer = Minecraft.getMinecraft().getBlockRendererDispatcher();
|
||||
BlockPos pos = te.getPos();
|
||||
IBlockAccess world = MinecraftForgeClient.getRegionRenderCache(te.getWorld(), pos);
|
||||
IBlockState state = world.getBlockState(pos);
|
||||
if(state.getPropertyNames().contains(Properties.StaticProperty))
|
||||
{
|
||||
state = state.withProperty(Properties.StaticProperty, false);
|
||||
}
|
||||
if(state instanceof IExtendedBlockState)
|
||||
{
|
||||
IExtendedBlockState exState = (IExtendedBlockState)state;
|
||||
if(exState.getUnlistedNames().contains(Properties.AnimationProperty))
|
||||
{
|
||||
float time = Animation.getWorldTime(getWorld(), partialTick);
|
||||
Pair<IModelState, Iterable<Event>> pair = te.asm().apply(time);
|
||||
handleEvents(te, time, pair.getRight());
|
||||
|
||||
IBakedModel model = getModel(exState, pair.getLeft());
|
||||
|
||||
renderer.setTranslation(x - pos.getX(), y - pos.getY(), z - pos.getZ());
|
||||
|
||||
blockRenderer.getBlockModelRenderer().renderModel(world, model, state, pos, renderer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void handleEvents(T te, float time, Iterable<Event> pastEvents) {}
|
||||
}
|
|
@ -0,0 +1,538 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.util.IStringSerializable;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.client.model.IModel;
|
||||
import net.minecraftforge.client.model.IModelPart;
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.client.model.ModelLoaderRegistry;
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Various implementations of IClip, and utility methods.
|
||||
*/
|
||||
public final class Clips
|
||||
{
|
||||
/**
|
||||
* Clip that does nothing.
|
||||
*/
|
||||
public static enum IdentityClip implements IClip, IStringSerializable
|
||||
{
|
||||
instance;
|
||||
|
||||
public IJointClip apply(IJoint joint)
|
||||
{
|
||||
return JointClips.IdentityJointClip.instance;
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
return ImmutableSet.<Event>of();
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return "identity";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the clip from the model.
|
||||
*/
|
||||
public static IClip getModelClipNode(ResourceLocation modelLocation, String clipName)
|
||||
{
|
||||
IModel model;
|
||||
try
|
||||
{
|
||||
model = ModelLoaderRegistry.getModel(modelLocation);
|
||||
if(model instanceof IAnimatedModel)
|
||||
{
|
||||
Optional<? extends IClip> clip = ((IAnimatedModel)model).getClip(clipName);
|
||||
if(clip.isPresent())
|
||||
{
|
||||
return new ModelClip(clip.get(), modelLocation, clipName);
|
||||
}
|
||||
FMLLog.getLogger().error("Unable to find clip " + clipName + " in the model " + modelLocation);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
FMLLog.getLogger().error("Unable to load model" + modelLocation + " while loading clip " + clipName, e);
|
||||
}
|
||||
// FIXME: missing clip?
|
||||
return new ModelClip(IdentityClip.instance, modelLocation, clipName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for model clips; useful for debugging and serialization;
|
||||
*/
|
||||
public static final class ModelClip implements IClip
|
||||
{
|
||||
private final IClip childClip;
|
||||
private final ResourceLocation modelLocation;
|
||||
private final String clipName;
|
||||
|
||||
public ModelClip(IClip childClip, ResourceLocation modelLocation, String clipName)
|
||||
{
|
||||
this.childClip = childClip;
|
||||
this.modelLocation = modelLocation;
|
||||
this.clipName = clipName;
|
||||
}
|
||||
|
||||
public IJointClip apply(IJoint joint)
|
||||
{
|
||||
return childClip.apply(joint);
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
return childClip.pastEvents(lastPollTime, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(modelLocation, clipName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ModelClip other = (ModelClip) obj;
|
||||
return Objects.equal(modelLocation, other.modelLocation) && Objects.equal(clipName, other.clipName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clip with custom parameterization of the time.
|
||||
*/
|
||||
public static final class TimeClip implements IClip
|
||||
{
|
||||
private final IClip childClip;
|
||||
private final ITimeValue time;
|
||||
|
||||
public TimeClip(IClip childClip, ITimeValue time)
|
||||
{
|
||||
this.childClip = childClip;
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
public IJointClip apply(final IJoint joint)
|
||||
{
|
||||
return new IJointClip()
|
||||
{
|
||||
private final IJointClip parent = childClip.apply(joint);
|
||||
public TRSRTransformation apply(float time)
|
||||
{
|
||||
return parent.apply(TimeClip.this.time.apply(time));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
return childClip.pastEvents(this.time.apply(lastPollTime), this.time.apply(time));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(childClip, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
TimeClip other = (TimeClip) obj;
|
||||
return Objects.equal(childClip, other.childClip) && Objects.equal(time, other.time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Spherical linear blend between 2 clips.
|
||||
*/
|
||||
public static final class SlerpClip implements IClip
|
||||
{
|
||||
private final IClip from;
|
||||
private final IClip to;
|
||||
private final ITimeValue input;
|
||||
private final ITimeValue progress;
|
||||
|
||||
public SlerpClip(IClip from, IClip to, ITimeValue input, ITimeValue progress)
|
||||
{
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.input = input;
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public IJointClip apply(IJoint joint)
|
||||
{
|
||||
IJointClip fromClip = from.apply(joint);
|
||||
IJointClip toClip = to.apply(joint);
|
||||
return blendClips(joint, fromClip, toClip, input, progress);
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
float clipLastPollTime = input.apply(lastPollTime);
|
||||
float clipTime = input.apply(time);
|
||||
return Iterables.mergeSorted(ImmutableSet.of(
|
||||
from.pastEvents(clipLastPollTime, clipTime),
|
||||
to.pastEvents(clipLastPollTime, clipTime)
|
||||
), Ordering.<Event>natural());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(from, to, input, progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
SlerpClip other = (SlerpClip) obj;
|
||||
return Objects.equal(from, other.from) &&
|
||||
Objects.equal(to, other.to) &&
|
||||
Objects.equal(input, other.input) &&
|
||||
Objects.equal(progress, other.progress);
|
||||
}
|
||||
}
|
||||
|
||||
/*public static class AdditiveLerpClip implements IClip
|
||||
{
|
||||
private final IClip base;
|
||||
private final IClip add;
|
||||
private final IParameter progress;
|
||||
|
||||
public AdditiveLerpClip(IClip base, IClip add, IParameter progress)
|
||||
{
|
||||
this.base = base;
|
||||
this.add = add;
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public IJointClip apply(IJoint joint)
|
||||
{
|
||||
throw new NotImplementedException("AdditiveLerpClip.apply");
|
||||
}
|
||||
}*/
|
||||
|
||||
private static IJointClip blendClips(final IJoint joint, final IJointClip fromClip, final IJointClip toClip, final ITimeValue input, final ITimeValue progress)
|
||||
{
|
||||
return new IJointClip()
|
||||
{
|
||||
public TRSRTransformation apply(float time)
|
||||
{
|
||||
float clipTime = input.apply(time);
|
||||
return fromClip.apply(clipTime).slerp(toClip.apply(clipTime), MathHelper.clamp_float(progress.apply(time), 0, 1));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* IModelState wrapper for a Clip, sampled at specified time.
|
||||
*/
|
||||
public static Pair<IModelState, Iterable<Event>> apply(final IClip clip, final float lastPollTime, final float time)
|
||||
{
|
||||
return Pair.<IModelState, Iterable<Event>>of(new IModelState()
|
||||
{
|
||||
public Optional<TRSRTransformation> apply(Optional<? extends IModelPart> part)
|
||||
{
|
||||
if(!part.isPresent() || !(part.get() instanceof IJoint))
|
||||
{
|
||||
return Optional.absent();
|
||||
}
|
||||
IJoint joint = (IJoint)part.get();
|
||||
if(!joint.getParent().isPresent())
|
||||
{
|
||||
return Optional.of(clip.apply(joint).apply(time).compose(joint.getInvBindPose()));
|
||||
}
|
||||
return Optional.of(clip.apply(joint.getParent().get()).apply(time).compose(clip.apply(joint).apply(time)).compose(joint.getInvBindPose()));
|
||||
}
|
||||
}, clip.pastEvents(lastPollTime, time));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clip + Event, triggers when parameter becomes non-negative.
|
||||
*/
|
||||
public static final class TriggerClip implements IClip
|
||||
{
|
||||
private final IClip clip;
|
||||
private final ITimeValue parameter;
|
||||
private final String event;
|
||||
|
||||
public TriggerClip(IClip clip, ITimeValue parameter, String event)
|
||||
{
|
||||
this.clip = clip;
|
||||
this.parameter = parameter;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public IJointClip apply(IJoint joint)
|
||||
{
|
||||
return clip.apply(joint);
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
if(parameter.apply(lastPollTime) < 0 && parameter.apply(time) >= 0)
|
||||
{
|
||||
return Iterables.mergeSorted(ImmutableSet.of(
|
||||
clip.pastEvents(lastPollTime, time),
|
||||
ImmutableSet.of(new Event(event, 0))
|
||||
), Ordering.<Event>natural());
|
||||
}
|
||||
return clip.pastEvents(lastPollTime, time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reference to another clip.
|
||||
* Should only exist during debugging.
|
||||
*/
|
||||
public static final class ClipReference implements IClip, IStringSerializable
|
||||
{
|
||||
private final String clipName;
|
||||
private final Function<String, IClip> clipResolver;
|
||||
private IClip clip;
|
||||
|
||||
public ClipReference(String clipName, Function<String, IClip> clipResolver)
|
||||
{
|
||||
this.clipName = clipName;
|
||||
this.clipResolver = clipResolver;
|
||||
}
|
||||
|
||||
private void resolve()
|
||||
{
|
||||
if(clip == null)
|
||||
{
|
||||
if(clipResolver != null)
|
||||
{
|
||||
clip = clipResolver.apply(clipName);
|
||||
}
|
||||
if(clip == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Couldn't resolve clip " + clipName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IJointClip apply(final IJoint joint)
|
||||
{
|
||||
resolve();
|
||||
return clip.apply(joint);
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
resolve();
|
||||
return clip.pastEvents(lastPollTime, time);
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return clipName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
resolve();
|
||||
return clip.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ClipReference other = (ClipReference) obj;
|
||||
resolve();
|
||||
other.resolve();
|
||||
return Objects.equal(clip, other.clip);
|
||||
}
|
||||
}
|
||||
|
||||
public static enum CommonClipTypeAdapterFactory implements TypeAdapterFactory
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
private final ThreadLocal<Function<String, IClip>> clipResolver = new ThreadLocal<Function<String, IClip>>();
|
||||
|
||||
public void setClipResolver(Function<String, IClip> clipResolver)
|
||||
{
|
||||
this.clipResolver.set(clipResolver);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type)
|
||||
{
|
||||
if(type.getRawType() != IClip.class)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
final TypeAdapter<ITimeValue> parameterAdapter = gson.getAdapter(ITimeValue.class);
|
||||
|
||||
return (TypeAdapter<T>)new TypeAdapter<IClip>()
|
||||
{
|
||||
public void write(JsonWriter out, IClip clip) throws IOException
|
||||
{
|
||||
// IdentityClip + ClipReference
|
||||
if(clip instanceof IStringSerializable)
|
||||
{
|
||||
out.value("#" + ((IStringSerializable)clip).getName());
|
||||
return;
|
||||
}
|
||||
else if(clip instanceof TimeClip)
|
||||
{
|
||||
out.beginArray();
|
||||
out.value("apply");
|
||||
TimeClip timeClip = (TimeClip)clip;
|
||||
write(out, timeClip.childClip);
|
||||
parameterAdapter.write(out, timeClip.time);
|
||||
out.endArray();
|
||||
return;
|
||||
}
|
||||
else if(clip instanceof SlerpClip)
|
||||
{
|
||||
out.beginArray();
|
||||
out.value("slerp");
|
||||
SlerpClip slerpClip = (SlerpClip)clip;
|
||||
write(out, slerpClip.from);
|
||||
write(out, slerpClip.to);
|
||||
parameterAdapter.write(out, slerpClip.input);
|
||||
parameterAdapter.write(out, slerpClip.progress);
|
||||
out.endArray();
|
||||
return;
|
||||
}
|
||||
else if(clip instanceof TriggerClip)
|
||||
{
|
||||
out.beginArray();
|
||||
out.value("trigger_positive");
|
||||
TriggerClip triggerClip = (TriggerClip)clip;
|
||||
write(out, triggerClip.clip);
|
||||
parameterAdapter.write(out, triggerClip.parameter);
|
||||
out.value(triggerClip.event);
|
||||
out.endArray();
|
||||
return;
|
||||
}
|
||||
else if(clip instanceof ModelClip)
|
||||
{
|
||||
ModelClip modelClip = (ModelClip)clip;
|
||||
out.value(modelClip.modelLocation + "@" + modelClip.clipName);
|
||||
return;
|
||||
}
|
||||
// TODO custom clip writing?
|
||||
throw new NotImplementedException("unknown Clip to json: " + clip);
|
||||
}
|
||||
|
||||
public IClip read(JsonReader in) throws IOException
|
||||
{
|
||||
switch(in.peek())
|
||||
{
|
||||
case BEGIN_ARRAY:
|
||||
in.beginArray();
|
||||
String type = in.nextString();
|
||||
IClip clip;
|
||||
// TimeClip
|
||||
if("apply".equals(type))
|
||||
{
|
||||
clip = new TimeClip(read(in), parameterAdapter.read(in));
|
||||
}
|
||||
else if("slerp".equals(type))
|
||||
{
|
||||
clip = new SlerpClip(read(in), read(in), parameterAdapter.read(in), parameterAdapter.read(in));
|
||||
}
|
||||
else if("trigger_positive".equals(type))
|
||||
{
|
||||
clip = new TriggerClip(read(in), parameterAdapter.read(in), in.nextString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Unknown Clip type \"" + type + "\"");
|
||||
}
|
||||
in.endArray();
|
||||
return clip;
|
||||
case STRING:
|
||||
String string = in.nextString();
|
||||
// IdentityClip
|
||||
if(string.equals("#identity"))
|
||||
{
|
||||
return IdentityClip.instance;
|
||||
}
|
||||
// Clip reference
|
||||
if(string.startsWith("#"))
|
||||
{
|
||||
return new ClipReference(string.substring(1), clipResolver.get());
|
||||
}
|
||||
// ModelClip
|
||||
else
|
||||
{
|
||||
int at = string.lastIndexOf('@');
|
||||
String location = string.substring(0, at);
|
||||
String clipName = string.substring(at + 1, string.length());
|
||||
ResourceLocation model;
|
||||
if(location.indexOf('#') != -1)
|
||||
{
|
||||
model = new ModelResourceLocation(location);
|
||||
}
|
||||
else
|
||||
{
|
||||
model = new ResourceLocation(location);
|
||||
}
|
||||
return getModelClipNode(model, clipName);
|
||||
}
|
||||
default:
|
||||
throw new IOException("expected Clip, got " + in.peek());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
|
||||
/**
|
||||
* Event stored in the clip
|
||||
*/
|
||||
public final class Event implements Comparable<Event>
|
||||
{
|
||||
private final String event;
|
||||
private final float offset;
|
||||
|
||||
public Event(String event, float offset)
|
||||
{
|
||||
this.event = event;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the event.
|
||||
*/
|
||||
public String event()
|
||||
{
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return how long ago the event happened, relative to the next event / first query time
|
||||
*/
|
||||
public float offset()
|
||||
{
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int compareTo(Event event)
|
||||
{
|
||||
return new Float(offset).compareTo(event.offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return Objects.toStringHelper(getClass()).add("event", event).add("offset", offset).toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
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.tileentity.TileEntity;
|
||||
|
||||
public abstract class FastTESR<T extends TileEntity> extends TileEntitySpecialRenderer<T>
|
||||
{
|
||||
@Override
|
||||
public final void renderTileEntityAt(T te, double x, double y, double z, float partialTicks, int destroyStage)
|
||||
{
|
||||
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.begin(GL11.GL_QUADS, DefaultVertexFormats.BLOCK);
|
||||
|
||||
renderTileEntityFast(te, x, y, z, partialTicks, destroyStage, worldRenderer);
|
||||
worldRenderer.setTranslation(0, 0, 0);
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
RenderHelper.enableStandardItemLighting();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void renderTileEntityFast(T te, double x, double y, double z, float partialTicks, int destroyStage, WorldRenderer worldRenderer);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import net.minecraftforge.client.model.IModel;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
/**
|
||||
* IModel that has animation data.
|
||||
*/
|
||||
public interface IAnimatedModel extends IModel
|
||||
{
|
||||
Optional<? extends IClip> getClip(String name);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import net.minecraftforge.common.model.animation.IAnimationStateMachine;
|
||||
|
||||
|
||||
/**
|
||||
* Something that can provide the Animation State Machine, for example and Entity or a Block
|
||||
*/
|
||||
public interface IAnimationProvider
|
||||
{
|
||||
public IAnimationStateMachine asm();
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
|
||||
/**
|
||||
* Clip for a rigged model.
|
||||
*/
|
||||
public interface IClip
|
||||
{
|
||||
IJointClip apply(IJoint joint);
|
||||
|
||||
Iterable<Event> pastEvents(float lastPollTime, float time);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
|
||||
/**
|
||||
* Handler for animation events;
|
||||
*/
|
||||
public interface IEventHandler<T>
|
||||
{
|
||||
void handleEvents(T instance, float time, Iterable<Event> pastEvents);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import net.minecraftforge.client.model.IModelPart;
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
|
||||
/**
|
||||
* Model part that's a part of the hierarchical skeleton.
|
||||
*/
|
||||
public interface IJoint extends IModelPart
|
||||
{
|
||||
TRSRTransformation getInvBindPose();
|
||||
|
||||
Optional<? extends IJoint> getParent();
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
|
||||
/**
|
||||
* Returns Local joint pose; animation clip for specific model part.
|
||||
*/
|
||||
public interface IJointClip
|
||||
{
|
||||
TRSRTransformation apply(float time);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
/**
|
||||
* Time-varying value associated with the animation.
|
||||
* Return value should be constant with the respect to the input and reasonable context (current render frame).
|
||||
* Simplest example is the input time itself.
|
||||
* Unity calls them Parameters, Unreal calls them Variables.
|
||||
*/
|
||||
public interface ITimeValue
|
||||
{
|
||||
public float apply(float input);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
|
||||
/**
|
||||
* Various implementations of IJointClip.
|
||||
*/
|
||||
public final class JointClips
|
||||
{
|
||||
public static enum IdentityJointClip implements IJointClip
|
||||
{
|
||||
instance;
|
||||
|
||||
public TRSRTransformation apply(float time)
|
||||
{
|
||||
return TRSRTransformation.identity();
|
||||
}
|
||||
}
|
||||
|
||||
public static class NodeJointClip implements IJointClip
|
||||
{
|
||||
private final IJoint child;
|
||||
private final IClip clip;
|
||||
|
||||
public NodeJointClip(IJoint joint, IClip clip)
|
||||
{
|
||||
this.child = joint;
|
||||
this.clip = clip;
|
||||
}
|
||||
|
||||
public TRSRTransformation apply(float time)
|
||||
{
|
||||
return clip.apply(child).apply(time);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,509 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.vecmath.AxisAngle4f;
|
||||
import javax.vecmath.Matrix4f;
|
||||
import javax.vecmath.Quat4f;
|
||||
import javax.vecmath.Vector3f;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockPart;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
import net.minecraftforge.client.model.animation.ModelBlockAnimation.Parameter.Interpolation;
|
||||
import net.minecraftforge.client.model.animation.ModelBlockAnimation.Parameter.Type;
|
||||
import net.minecraftforge.client.model.animation.ModelBlockAnimation.Parameter.Variable;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.UnmodifiableIterator;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class ModelBlockAnimation
|
||||
{
|
||||
private final ImmutableMap<String, ImmutableMap<String, float[]>> joints;
|
||||
private final ImmutableMap<String, MBClip> clips;
|
||||
private transient ImmutableMultimap<Integer, MBJointWeight> jointIndexMap;
|
||||
|
||||
public ModelBlockAnimation(ImmutableMap<String, ImmutableMap<String, float[]>> joints, ImmutableMap<String, MBClip> clips)
|
||||
{
|
||||
this.joints = joints;
|
||||
this.clips = clips;
|
||||
}
|
||||
|
||||
public ImmutableMap<String, MBClip> getClips()
|
||||
{
|
||||
return clips;
|
||||
}
|
||||
|
||||
public ImmutableCollection<MBJointWeight> getJoint(int i)
|
||||
{
|
||||
if(jointIndexMap == null)
|
||||
{
|
||||
ImmutableMultimap.Builder<Integer, MBJointWeight> builder = ImmutableMultimap.builder();
|
||||
for(Map.Entry<String, ImmutableMap<String, float[]>> info : joints.entrySet())
|
||||
{
|
||||
ImmutableMap.Builder<Integer, float[]> weightBuilder = ImmutableMap.builder();
|
||||
for(Map.Entry<String, float[]> e : info.getValue().entrySet())
|
||||
{
|
||||
weightBuilder.put(Integer.parseInt(e.getKey()), e.getValue());
|
||||
}
|
||||
ImmutableMap<Integer, float[]> weightMap = weightBuilder.build();
|
||||
for(Map.Entry<Integer, float[]> e : weightMap.entrySet())
|
||||
{
|
||||
builder.put(e.getKey(), new MBJointWeight(info.getKey(), weightMap));
|
||||
}
|
||||
}
|
||||
jointIndexMap = builder.build();
|
||||
}
|
||||
return jointIndexMap.get(i);
|
||||
}
|
||||
|
||||
protected static class MBVariableClip
|
||||
{
|
||||
private final Variable variable;
|
||||
@SuppressWarnings("unused")
|
||||
private final Type type;
|
||||
private final Interpolation interpolation;
|
||||
private final float[] samples;
|
||||
|
||||
public MBVariableClip(Variable variable, Type type, Interpolation interpolation, float[] samples)
|
||||
{
|
||||
this.variable = variable;
|
||||
this.type = type;
|
||||
this.interpolation = interpolation;
|
||||
this.samples = samples;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MBClip implements IClip
|
||||
{
|
||||
private final boolean loop;
|
||||
@SerializedName("joint_clips")
|
||||
private final ImmutableMap<String, ImmutableList<MBVariableClip>> jointClipsFlat;
|
||||
private transient ImmutableMap<String, MBJointClip> jointClips;
|
||||
@SerializedName("events")
|
||||
private final ImmutableMap<String, String> eventsRaw;
|
||||
private transient TreeMap<Float, Event> events;
|
||||
|
||||
public MBClip(boolean loop, ImmutableMap<String, ImmutableList<MBVariableClip>> clips, ImmutableMap<String, String> events)
|
||||
{
|
||||
this.loop = loop;
|
||||
this.jointClipsFlat = clips;
|
||||
this.eventsRaw = events;
|
||||
}
|
||||
|
||||
private void initialize()
|
||||
{
|
||||
if(jointClips == null)
|
||||
{
|
||||
ImmutableMap.Builder<String, MBJointClip> builder = ImmutableMap.builder();
|
||||
for(Map.Entry<String, ImmutableList<MBVariableClip>> e : jointClipsFlat.entrySet())
|
||||
{
|
||||
builder.put(e.getKey(), new MBJointClip(loop, e.getValue()));
|
||||
}
|
||||
jointClips = builder.build();
|
||||
events = Maps.newTreeMap();
|
||||
TreeMap<Float, String> times = Maps.newTreeMap();
|
||||
for(String time : eventsRaw.keySet())
|
||||
{
|
||||
times.put(Float.parseFloat(time), time);
|
||||
}
|
||||
float lastTime = Float.POSITIVE_INFINITY;
|
||||
if(loop)
|
||||
{
|
||||
lastTime = times.firstKey();
|
||||
}
|
||||
for(Map.Entry<Float, String> entry : times.descendingMap().entrySet())
|
||||
{
|
||||
float time = entry.getKey();
|
||||
float offset = lastTime - time;
|
||||
if(loop)
|
||||
{
|
||||
offset = 1 - (1 - offset) % 1;
|
||||
}
|
||||
events.put(time, new Event(eventsRaw.get(entry.getValue()), offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IJointClip apply(IJoint joint)
|
||||
{
|
||||
initialize();
|
||||
if(joint instanceof MBJoint)
|
||||
{
|
||||
MBJoint mbJoint = (MBJoint)joint;
|
||||
//MBJointInfo = jointInfos.
|
||||
MBJointClip clip = jointClips.get(mbJoint.getName());
|
||||
if(clip != null) return clip;
|
||||
}
|
||||
return JointClips.IdentityJointClip.instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Event> pastEvents(final float lastPollTime, final float time)
|
||||
{
|
||||
initialize();
|
||||
return new Iterable<Event>()
|
||||
{
|
||||
public Iterator<Event> iterator()
|
||||
{
|
||||
return new UnmodifiableIterator<Event>()
|
||||
{
|
||||
private Float curKey;
|
||||
private Event firstEvent;
|
||||
private float stopTime;
|
||||
{
|
||||
if(lastPollTime >= time)
|
||||
{
|
||||
curKey = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
float fractTime = time - (float)Math.floor(time);
|
||||
float fractLastTime = lastPollTime - (float)Math.floor(lastPollTime);
|
||||
// swap if not in order
|
||||
if(fractLastTime > fractTime)
|
||||
{
|
||||
float tmp = fractTime;
|
||||
fractTime = fractLastTime;
|
||||
fractLastTime = tmp;
|
||||
}
|
||||
// need to wrap around, swap again
|
||||
if(fractTime - fractLastTime > .5f)
|
||||
{
|
||||
float tmp = fractTime;
|
||||
fractTime = fractLastTime;
|
||||
fractLastTime = tmp;
|
||||
}
|
||||
|
||||
stopTime = fractLastTime;
|
||||
|
||||
curKey = events.floorKey(fractTime);
|
||||
if(curKey == null && loop && !events.isEmpty())
|
||||
{
|
||||
curKey = events.lastKey();
|
||||
}
|
||||
if(curKey != null)
|
||||
{
|
||||
float checkCurTime = curKey;
|
||||
float checkStopTime = stopTime;
|
||||
if(checkCurTime >= fractTime) checkCurTime--;
|
||||
if(checkStopTime >= fractTime) checkStopTime--;
|
||||
float offset = fractTime - checkCurTime;
|
||||
Event event = events.get(curKey);
|
||||
if(checkCurTime < checkStopTime)
|
||||
{
|
||||
curKey = null;
|
||||
}
|
||||
else if(offset != event.offset())
|
||||
{
|
||||
firstEvent = new Event(event.event(), offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNext()
|
||||
{
|
||||
return curKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Event next()
|
||||
{
|
||||
if(curKey == null)
|
||||
{
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Event event;
|
||||
if(firstEvent == null)
|
||||
{
|
||||
event = events.get(curKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
event = firstEvent;
|
||||
firstEvent = null;
|
||||
}
|
||||
curKey = events.lowerKey(curKey);
|
||||
if(curKey == null && loop)
|
||||
{
|
||||
curKey = events.lastKey();
|
||||
}
|
||||
if(curKey != null)
|
||||
{
|
||||
float checkStopTime = stopTime;
|
||||
while(curKey + events.get(curKey).offset() < checkStopTime) checkStopTime--;
|
||||
while(curKey + events.get(curKey).offset() >= checkStopTime + 1) checkStopTime++;
|
||||
if(curKey <= checkStopTime)
|
||||
{
|
||||
curKey = null;
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected static class MBJointClip implements IJointClip
|
||||
{
|
||||
private final boolean loop;
|
||||
private final ImmutableList<MBVariableClip> variables;
|
||||
|
||||
public MBJointClip(boolean loop, ImmutableList<MBVariableClip> variables)
|
||||
{
|
||||
this.loop = loop;
|
||||
this.variables = variables;
|
||||
EnumSet<Variable> hadVar = Sets.newEnumSet(Collections.<Variable>emptyList(), Variable.class);
|
||||
for(MBVariableClip var : variables)
|
||||
{
|
||||
if(hadVar.contains(var.variable))
|
||||
{
|
||||
throw new IllegalArgumentException("duplicate variable: " + var);
|
||||
}
|
||||
hadVar.add(var.variable);
|
||||
}
|
||||
}
|
||||
|
||||
public TRSRTransformation apply(float time)
|
||||
{
|
||||
time -= Math.floor(time);
|
||||
Vector3f translation = new Vector3f(0, 0, 0);
|
||||
Vector3f scale = new Vector3f(1, 1, 1);
|
||||
AxisAngle4f rotation = new AxisAngle4f(0, 0, 0, 0);
|
||||
for(MBVariableClip var : variables)
|
||||
{
|
||||
int length = loop ? var.samples.length : (var.samples.length - 1);
|
||||
float timeScaled = time * length;
|
||||
int s1 = MathHelper.clamp_int((int)Math.round(Math.floor(timeScaled)), 0, length - 1);
|
||||
float progress = timeScaled - s1;
|
||||
int s2 = s1 + 1;
|
||||
if(s2 == length && loop) s2 = 0;
|
||||
float value = 0;
|
||||
switch(var.interpolation)
|
||||
{
|
||||
case LINEAR:
|
||||
if(var.variable == Variable.ANGLE)
|
||||
{
|
||||
float v1 = var.samples[s1];
|
||||
float v2 = var.samples[s2];
|
||||
float diff = ((v2 - v1) % 360 + 540) % 360 - 180;
|
||||
value = v1 + diff * progress;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = var.samples[s1] * (1 - progress) + var.samples[s2] * progress;
|
||||
}
|
||||
break;
|
||||
case NEAREST:
|
||||
value = var.samples[progress < .5f ? s1 : s2];
|
||||
break;
|
||||
}
|
||||
switch(var.variable)
|
||||
{
|
||||
case X:
|
||||
translation.x = value;
|
||||
break;
|
||||
case Y:
|
||||
translation.y = value;
|
||||
break;
|
||||
case Z:
|
||||
translation.z = value;
|
||||
break;
|
||||
case XROT:
|
||||
rotation.x = value;
|
||||
break;
|
||||
case YROT:
|
||||
rotation.y = value;
|
||||
break;
|
||||
case ZROT:
|
||||
rotation.z = value;
|
||||
break;
|
||||
case ANGLE:
|
||||
rotation.angle = (float)Math.toRadians(value);
|
||||
break;
|
||||
case SCALE:
|
||||
scale.x = scale.y = scale.z = value;
|
||||
break;
|
||||
case XS:
|
||||
scale.x = value;
|
||||
break;
|
||||
case YS:
|
||||
scale.y = value;
|
||||
break;
|
||||
case ZS:
|
||||
scale.z = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Quat4f rot = new Quat4f();
|
||||
rot.set(rotation);
|
||||
return TRSRTransformation.blockCenterToCorner(new TRSRTransformation(translation, rot, scale, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MBJoint implements IJoint
|
||||
{
|
||||
private final String name;
|
||||
private final TRSRTransformation invBindPose;
|
||||
|
||||
public MBJoint(String name, BlockPart part)
|
||||
{
|
||||
this.name = name;
|
||||
if(part.partRotation != null)
|
||||
{
|
||||
float x = 0, y = 0, z = 0;
|
||||
switch(part.partRotation.axis)
|
||||
{
|
||||
case X:
|
||||
x = 1;
|
||||
case Y:
|
||||
y = 1;
|
||||
case Z:
|
||||
z = 1;
|
||||
}
|
||||
Quat4f rotation = new Quat4f();
|
||||
rotation.set(new AxisAngle4f(x, y, z, 0));
|
||||
Matrix4f m = new TRSRTransformation(
|
||||
TRSRTransformation.toVecmath(part.partRotation.origin),
|
||||
rotation,
|
||||
null,
|
||||
null).getMatrix();
|
||||
m.invert();
|
||||
invBindPose = new TRSRTransformation(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
invBindPose = TRSRTransformation.identity();
|
||||
}
|
||||
}
|
||||
|
||||
public TRSRTransformation getInvBindPose()
|
||||
{
|
||||
return invBindPose;
|
||||
}
|
||||
|
||||
public Optional<? extends IJoint> getParent()
|
||||
{
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class MBJointWeight
|
||||
{
|
||||
private final String name;
|
||||
private final ImmutableMap<Integer, float[]> weights;
|
||||
|
||||
public MBJointWeight(String name, ImmutableMap<Integer, float[]> weights)
|
||||
{
|
||||
this.name = name;
|
||||
this.weights = weights;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public ImmutableMap<Integer, float[]> getWeights()
|
||||
{
|
||||
return weights;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class Parameter
|
||||
{
|
||||
public static enum Variable
|
||||
{
|
||||
@SerializedName("offset_x")
|
||||
X,
|
||||
@SerializedName("offset_y")
|
||||
Y,
|
||||
@SerializedName("offset_z")
|
||||
Z,
|
||||
@SerializedName("axis_x")
|
||||
XROT,
|
||||
@SerializedName("axis_y")
|
||||
YROT,
|
||||
@SerializedName("axis_z")
|
||||
ZROT,
|
||||
@SerializedName("angle")
|
||||
ANGLE,
|
||||
@SerializedName("scale")
|
||||
SCALE,
|
||||
@SerializedName("scale_x")
|
||||
XS,
|
||||
@SerializedName("scale_y")
|
||||
YS,
|
||||
@SerializedName("scale_z")
|
||||
ZS;
|
||||
}
|
||||
|
||||
public static enum Type
|
||||
{
|
||||
@SerializedName("uniform")
|
||||
UNIFORM;
|
||||
}
|
||||
|
||||
public static enum Interpolation
|
||||
{
|
||||
@SerializedName("linear")
|
||||
LINEAR,
|
||||
@SerializedName("nearest")
|
||||
NEAREST;
|
||||
}
|
||||
}
|
||||
|
||||
public TRSRTransformation getPartTransform(IModelState state, BlockPart part, int i)
|
||||
{
|
||||
ImmutableCollection<MBJointWeight> infos = getJoint(i);
|
||||
if(!infos.isEmpty())
|
||||
{
|
||||
Matrix4f m = new Matrix4f(), tmp;
|
||||
float weight = 0;
|
||||
for(MBJointWeight info : infos)
|
||||
{
|
||||
if(info.getWeights().containsKey(i))
|
||||
{
|
||||
ModelBlockAnimation.MBJoint joint = new ModelBlockAnimation.MBJoint(info.getName(), part);
|
||||
Optional<TRSRTransformation> trOp = state.apply(Optional.of(joint));
|
||||
if(trOp.isPresent() && trOp.get() != TRSRTransformation.identity())
|
||||
{
|
||||
float w = info.getWeights().get(i)[0];
|
||||
tmp = trOp.get().getMatrix();
|
||||
tmp.mul(w);
|
||||
m.add(tmp);
|
||||
weight += w;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(weight > 1e-5)
|
||||
{
|
||||
m.mul(1f / weight);
|
||||
return new TRSRTransformation(m);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
package net.minecraftforge.client.model.animation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.minecraft.util.IStringSerializable;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* Various implementations of ITimeValue.
|
||||
*/
|
||||
public final class TimeValues
|
||||
{
|
||||
public static enum IdentityValue implements ITimeValue, IStringSerializable
|
||||
{
|
||||
instance;
|
||||
|
||||
public float apply(float input)
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return "identity";
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ConstValue implements ITimeValue
|
||||
{
|
||||
private final float output;
|
||||
|
||||
public ConstValue(float output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
public float apply(float input)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ConstValue other = (ConstValue) obj;
|
||||
return output == other.output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple value holder.
|
||||
*/
|
||||
public static final class VariableValue implements ITimeValue
|
||||
{
|
||||
private float output;
|
||||
|
||||
public VariableValue(float initialValue)
|
||||
{
|
||||
this.output = initialValue;
|
||||
}
|
||||
|
||||
public void setValue(float newValue)
|
||||
{
|
||||
this.output = newValue;
|
||||
}
|
||||
|
||||
public float apply(float input)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
VariableValue other = (VariableValue) obj;
|
||||
return output == other.output;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SimpleExprValue implements ITimeValue
|
||||
{
|
||||
private static final Pattern opsPattern = Pattern.compile("[+\\-*/mMrRfF]+");
|
||||
|
||||
private final String operators;
|
||||
private final ImmutableList<ITimeValue> args;
|
||||
|
||||
public SimpleExprValue(String operators, ImmutableList<ITimeValue> args)
|
||||
{
|
||||
this.operators = operators;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public float apply(float input)
|
||||
{
|
||||
float ret = input;
|
||||
for(int i = 0; i < operators.length(); i++)
|
||||
{
|
||||
float arg = args.get(i).apply(input);
|
||||
switch(operators.charAt(i))
|
||||
{
|
||||
case '+': ret += arg; break;
|
||||
case '-': ret -= arg; break;
|
||||
case '*': ret *= arg; break;
|
||||
case '/': ret /= arg; break;
|
||||
case 'm': ret = Math.min(ret, arg); break;
|
||||
case 'M': ret = Math.max(ret, arg); break;
|
||||
case 'r': ret = (float)Math.floor(ret / arg) * arg; break;
|
||||
case 'R': ret = (float)Math.ceil(ret / arg) * arg; break;
|
||||
case 'f': ret -= Math.floor(ret / arg) * arg; break;
|
||||
case 'F': ret = (float)Math.ceil(ret / arg) * arg - ret; break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(operators, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
SimpleExprValue other = (SimpleExprValue) obj;
|
||||
return Objects.equal(operators, other.operators) && Objects.equal(args, other.args);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class CompositionValue implements ITimeValue
|
||||
{
|
||||
private final ITimeValue g;
|
||||
private final ITimeValue f;
|
||||
|
||||
public CompositionValue(ITimeValue g, ITimeValue f)
|
||||
{
|
||||
super();
|
||||
this.g = g;
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
public float apply(float input)
|
||||
{
|
||||
return g.apply(f.apply(input));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hashCode(g, f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
CompositionValue other = (CompositionValue) obj;
|
||||
return Objects.equal(g, other.g) && Objects.equal(f, other.f);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ParameterValue implements ITimeValue, IStringSerializable
|
||||
{
|
||||
private final String parameterName;
|
||||
private final Function<String, ITimeValue> valueResolver;
|
||||
private ITimeValue parameter;
|
||||
|
||||
public ParameterValue(String parameterName, Function<String, ITimeValue> valueResolver)
|
||||
{
|
||||
this.parameterName = parameterName;
|
||||
this.valueResolver = valueResolver;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return parameterName;
|
||||
}
|
||||
|
||||
private void resolve()
|
||||
{
|
||||
if(parameter == null)
|
||||
{
|
||||
if(valueResolver != null)
|
||||
{
|
||||
parameter = valueResolver.apply(parameterName);
|
||||
}
|
||||
if(parameter == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Couldn't resolve parameter value " + parameterName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float apply(float input)
|
||||
{
|
||||
resolve();
|
||||
return parameter.apply(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
resolve();
|
||||
return parameter.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ParameterValue other = (ParameterValue) obj;
|
||||
resolve();
|
||||
other.resolve();
|
||||
return Objects.equal(parameter, other.parameter);
|
||||
}
|
||||
}
|
||||
|
||||
public static enum CommonTimeValueTypeAdapterFactory implements TypeAdapterFactory
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
private final ThreadLocal<Function<String, ITimeValue>> valueResolver = new ThreadLocal<Function<String, ITimeValue>>();
|
||||
|
||||
public void setValueResolver(Function<String, ITimeValue> valueResolver)
|
||||
{
|
||||
this.valueResolver.set(valueResolver);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type)
|
||||
{
|
||||
if(type.getRawType() != ITimeValue.class)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (TypeAdapter<T>)new TypeAdapter<ITimeValue>()
|
||||
{
|
||||
public void write(JsonWriter out, ITimeValue parameter) throws IOException
|
||||
{
|
||||
if(parameter instanceof ConstValue)
|
||||
{
|
||||
out.value(((ConstValue)parameter).output);
|
||||
}
|
||||
else if(parameter instanceof SimpleExprValue)
|
||||
{
|
||||
SimpleExprValue p = (SimpleExprValue)parameter;
|
||||
out.beginArray();
|
||||
out.value(p.operators);
|
||||
for(ITimeValue v : p.args)
|
||||
{
|
||||
write(out, v);
|
||||
}
|
||||
out.endArray();
|
||||
}
|
||||
else if(parameter instanceof CompositionValue)
|
||||
{
|
||||
CompositionValue p = (CompositionValue)parameter;
|
||||
out.beginArray();
|
||||
out.value("compose");
|
||||
write(out, p.g);
|
||||
write(out, p.f);
|
||||
out.endArray();
|
||||
}
|
||||
else if(parameter instanceof IStringSerializable)
|
||||
{
|
||||
out.value("#" + ((IStringSerializable)parameter).getName());
|
||||
}
|
||||
}
|
||||
|
||||
public ITimeValue read(JsonReader in) throws IOException
|
||||
{
|
||||
switch(in.peek())
|
||||
{
|
||||
case NUMBER:
|
||||
return new ConstValue((float)in.nextDouble());
|
||||
case BEGIN_ARRAY:
|
||||
in.beginArray();
|
||||
String type = in.nextString();
|
||||
ITimeValue p;
|
||||
if(SimpleExprValue.opsPattern.matcher(type).matches())
|
||||
{
|
||||
ImmutableList.Builder<ITimeValue> builder = ImmutableList.builder();
|
||||
while(in.peek() != JsonToken.END_ARRAY)
|
||||
{
|
||||
builder.add(read(in));
|
||||
}
|
||||
p = new SimpleExprValue(type, builder.build());
|
||||
}
|
||||
else if("compose".equals(type))
|
||||
{
|
||||
p = new CompositionValue(read(in), read(in));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IOException("Unknown TimeValue type \"" + type + "\"");
|
||||
}
|
||||
in.endArray();
|
||||
return p;
|
||||
case STRING:
|
||||
String string = in.nextString();
|
||||
if(string.equals("#identity"))
|
||||
{
|
||||
return IdentityValue.instance;
|
||||
}
|
||||
if(!string.startsWith("#"))
|
||||
{
|
||||
throw new IOException("Expected TimeValue reference, got \"" + string + "\"");
|
||||
}
|
||||
// User Parameter TimeValue
|
||||
return new ParameterValue(string.substring(1), valueResolver.get());
|
||||
default:
|
||||
throw new IOException("Expected TimeValue, got " + in.peek());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package net.minecraftforge.client.model.b3d;
|
||||
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
import net.minecraftforge.client.model.animation.Event;
|
||||
import net.minecraftforge.client.model.animation.IClip;
|
||||
import net.minecraftforge.client.model.animation.IJoint;
|
||||
import net.minecraftforge.client.model.animation.IJointClip;
|
||||
import net.minecraftforge.client.model.animation.JointClips;
|
||||
import net.minecraftforge.client.model.b3d.B3DLoader.NodeJoint;
|
||||
import net.minecraftforge.client.model.b3d.B3DModel.Key;
|
||||
import net.minecraftforge.client.model.b3d.B3DModel.Node;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
// FIXME: is this fast enough?
|
||||
public enum B3DClip implements IClip
|
||||
{
|
||||
instance;
|
||||
|
||||
public IJointClip apply(final IJoint joint)
|
||||
{
|
||||
if(!(joint instanceof NodeJoint))
|
||||
{
|
||||
return JointClips.IdentityJointClip.instance;
|
||||
}
|
||||
return new NodeClip(((NodeJoint)joint).getNode());
|
||||
}
|
||||
|
||||
public Iterable<Event> pastEvents(float lastPollTime, float time)
|
||||
{
|
||||
return ImmutableSet.<Event>of();
|
||||
}
|
||||
|
||||
protected static class NodeClip implements IJointClip
|
||||
{
|
||||
private final Node<?> node;
|
||||
|
||||
public NodeClip(Node<?> node)
|
||||
{
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public TRSRTransformation apply(float time)
|
||||
{
|
||||
TRSRTransformation ret = TRSRTransformation.identity();
|
||||
if(node.getAnimation() == null)
|
||||
{
|
||||
return ret.compose(new TRSRTransformation(node.getPos(), node.getRot(), node.getScale(), null));
|
||||
}
|
||||
int start = Math.max(1, (int)Math.round(Math.floor(time)));
|
||||
int end = Math.min(start + 1, (int)Math.round(Math.ceil(time)));
|
||||
float progress = time - (float)Math.floor(time);
|
||||
Key keyStart = node.getAnimation().getKeys().get(start, node);
|
||||
Key keyEnd = node.getAnimation().getKeys().get(end, node);
|
||||
TRSRTransformation startTr = keyStart == null ? null : new TRSRTransformation(keyStart.getPos(), keyStart.getRot(),keyStart.getScale(), null);
|
||||
TRSRTransformation endTr = keyEnd == null ? null : new TRSRTransformation(keyEnd.getPos(), keyEnd.getRot(),keyEnd.getScale(), null);
|
||||
if(keyStart == null)
|
||||
{
|
||||
if(keyEnd == null)
|
||||
{
|
||||
ret = ret.compose(new TRSRTransformation(node.getPos(), node.getRot(), node.getScale(), null));
|
||||
}
|
||||
// TODO animated TRSR for speed?
|
||||
else
|
||||
{
|
||||
ret = ret.compose(endTr);
|
||||
}
|
||||
}
|
||||
else if(progress < 1e-5 || keyEnd == null)
|
||||
{
|
||||
ret = ret.compose(startTr);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = ret.compose(startTr.slerp(endTr, progress));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,11 @@ import net.minecraftforge.client.model.ISmartBlockModel;
|
|||
import net.minecraftforge.client.model.ISmartItemModel;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.client.model.ModelLoaderRegistry;
|
||||
import net.minecraftforge.client.model.ModelStateComposition;
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
import net.minecraftforge.client.model.animation.IClip;
|
||||
import net.minecraftforge.client.model.animation.IAnimatedModel;
|
||||
import net.minecraftforge.client.model.animation.IJoint;
|
||||
import net.minecraftforge.client.model.b3d.B3DModel.Animation;
|
||||
import net.minecraftforge.client.model.b3d.B3DModel.Face;
|
||||
import net.minecraftforge.client.model.b3d.B3DModel.IKind;
|
||||
|
@ -51,6 +55,7 @@ import net.minecraftforge.client.model.pipeline.LightUtil;
|
|||
import net.minecraftforge.client.model.pipeline.UnpackedBakedQuad;
|
||||
import net.minecraftforge.common.property.IExtendedBlockState;
|
||||
import net.minecraftforge.common.property.IUnlistedProperty;
|
||||
import net.minecraftforge.common.property.Properties;
|
||||
import net.minecraftforge.fml.common.FMLLog;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
@ -366,7 +371,7 @@ public class B3DLoader implements ICustomModelLoader
|
|||
}
|
||||
}
|
||||
|
||||
public static class NodeJoint implements IModelPart
|
||||
public static class NodeJoint implements IJoint
|
||||
{
|
||||
private final Node<?> node;
|
||||
|
||||
|
@ -553,11 +558,6 @@ public class B3DLoader implements ICustomModelLoader
|
|||
return new BakedWrapper(getNode(), state, format, meshes, builder.build());
|
||||
}
|
||||
|
||||
public B3DState getDefaultState()
|
||||
{
|
||||
return new B3DState(getNode().getAnimation(), 1);
|
||||
}
|
||||
|
||||
public ResourceLocation getLocation()
|
||||
{
|
||||
return location;
|
||||
|
@ -619,8 +619,15 @@ public class B3DLoader implements ICustomModelLoader
|
|||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IModelState getDefaultState()
|
||||
{
|
||||
return new B3DState(getNode().getAnimation(), 1);
|
||||
}
|
||||
}
|
||||
public static class ModelWrapper implements IRetexturableModel, IModelCustomData
|
||||
|
||||
public static class ModelWrapper implements IRetexturableModel, IModelCustomData, IAnimatedModel
|
||||
{
|
||||
private final ResourceLocation modelLocation;
|
||||
private final B3DModel model;
|
||||
|
@ -701,12 +708,6 @@ public class B3DLoader implements ICustomModelLoader
|
|||
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<String, String> textures)
|
||||
{
|
||||
|
@ -777,6 +778,20 @@ public class B3DLoader implements ICustomModelLoader
|
|||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Optional<IClip> getClip(String name)
|
||||
{
|
||||
if(name.equals("main"))
|
||||
{
|
||||
return Optional.<IClip>of(B3DClip.instance);
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
public IModelState getDefaultState()
|
||||
{
|
||||
return new B3DState(model.getRoot().getAnimation(), defaultKey, defaultKey, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static class BakedWrapper implements IFlexibleBakedModel, ISmartBlockModel, ISmartItemModel, IPerspectiveAwareModel
|
||||
|
@ -840,9 +855,19 @@ public class B3DLoader implements ICustomModelLoader
|
|||
Collection<Face> faces = mesh.bake(new Function<Node<?>, Matrix4f>()
|
||||
{
|
||||
private final TRSRTransformation global = state.apply(Optional.<IModelPart>absent()).or(TRSRTransformation.identity());
|
||||
private final LoadingCache<Node<?>, TRSRTransformation> localCache = CacheBuilder.newBuilder()
|
||||
.maximumSize(32)
|
||||
.build(new CacheLoader<Node<?>, TRSRTransformation>()
|
||||
{
|
||||
public TRSRTransformation load(Node<?> node) throws Exception
|
||||
{
|
||||
return state.apply(Optional.of(new NodeJoint(node))).or(TRSRTransformation.identity());
|
||||
}
|
||||
});
|
||||
|
||||
public Matrix4f apply(Node<?> node)
|
||||
{
|
||||
return global.compose(state.apply(Optional.of(new NodeJoint(node))).or(TRSRTransformation.identity())).getMatrix();
|
||||
return global.compose(localCache.getUnchecked(node)).getMatrix();
|
||||
}
|
||||
});
|
||||
for(Face f : faces)
|
||||
|
@ -978,6 +1003,21 @@ public class B3DLoader implements ICustomModelLoader
|
|||
return new BakedWrapper(node, newState, format, meshes, textures);
|
||||
}
|
||||
}
|
||||
else if(exState.getUnlistedNames().contains(Properties.AnimationProperty))
|
||||
{
|
||||
// FIXME: should animation state handle the parent state, or should it remain here?
|
||||
IModelState parent = this.state;
|
||||
if(parent instanceof B3DState)
|
||||
{
|
||||
B3DState ps = (B3DState)parent;
|
||||
parent = ps.getParent();
|
||||
}
|
||||
IModelState newState = exState.getValue(Properties.AnimationProperty);
|
||||
if(newState != null)
|
||||
{
|
||||
return new BakedWrapper(node, new ModelStateComposition(parent, newState), format, meshes, textures);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.logging.log4j.LogManager;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
@ -43,6 +44,7 @@ import com.google.common.collect.Table;
|
|||
public class B3DModel
|
||||
{
|
||||
static final Logger logger = LogManager.getLogger(B3DModel.class);
|
||||
private static final boolean printLoadedModels = "true".equals(System.getProperty("b3dloader.printLoadedModels"));
|
||||
private final List<Texture> textures;
|
||||
private final List<Brush> brushes;
|
||||
private final Node<?> root;
|
||||
|
@ -88,13 +90,27 @@ public class B3DModel
|
|||
}
|
||||
}
|
||||
|
||||
private String dump = "";
|
||||
private void dump(String str)
|
||||
{
|
||||
if(printLoadedModels)
|
||||
{
|
||||
dump += str + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private B3DModel res;
|
||||
|
||||
public B3DModel parse() throws IOException
|
||||
{
|
||||
if(res != null) return res;
|
||||
dump = "\n";
|
||||
readHeader();
|
||||
res = bb3d();
|
||||
if(printLoadedModels)
|
||||
{
|
||||
logger.info(dump);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -192,6 +208,7 @@ public class B3DModel
|
|||
List<Texture> textures = Collections.emptyList();
|
||||
List<Brush> brushes = Collections.emptyList();
|
||||
Node<?> root = null;
|
||||
dump("BB3D(version = " + version + ") {");
|
||||
while(buf.hasRemaining())
|
||||
{
|
||||
readHeader();
|
||||
|
@ -200,6 +217,7 @@ public class B3DModel
|
|||
else if(isChunk("NODE")) root = node();
|
||||
else skip();
|
||||
}
|
||||
dump("}");
|
||||
popLimit();
|
||||
return new B3DModel(textures, brushes, root, meshes.build());
|
||||
}
|
||||
|
@ -218,6 +236,7 @@ public class B3DModel
|
|||
float rot = buf.getFloat();
|
||||
ret.add(new Texture(path, flags, blend, pos, scale, rot));
|
||||
}
|
||||
dump("TEXS([" + Joiner.on(", ").join(ret) + "])");
|
||||
popLimit();
|
||||
this.textures.addAll(ret);
|
||||
return ret;
|
||||
|
@ -239,6 +258,7 @@ public class B3DModel
|
|||
for(int i = 0; i < n_texs; i++) textures.add(getTexture(buf.getInt()));
|
||||
ret.add(new Brush(name, color, shininess, blend, fx, textures));
|
||||
}
|
||||
dump("BRUS([" + Joiner.on(", ").join(ret) + "])");
|
||||
popLimit();
|
||||
this.brushes.addAll(ret);
|
||||
return ret;
|
||||
|
@ -287,6 +307,7 @@ public class B3DModel
|
|||
}
|
||||
ret.add(new Vertex(v, n, color, tex_coords));
|
||||
}
|
||||
dump("VRTS([" + Joiner.on(", ").join(ret) + "])");
|
||||
popLimit();
|
||||
this.vertices.clear();
|
||||
this.vertices.addAll(ret);
|
||||
|
@ -302,6 +323,7 @@ public class B3DModel
|
|||
{
|
||||
ret.add(new Face(getVertex(buf.getInt()), getVertex(buf.getInt()), getVertex(buf.getInt()), getBrush(brush_id)));
|
||||
}
|
||||
dump("TRIS([" + Joiner.on(", ").join(ret) + "])");
|
||||
popLimit();
|
||||
return ret;
|
||||
}
|
||||
|
@ -311,6 +333,7 @@ public class B3DModel
|
|||
chunk("MESH");
|
||||
int brush_id = buf.getInt();
|
||||
readHeader();
|
||||
dump("MESH(brush = " + brush_id + ") {");
|
||||
vrts();
|
||||
List<Face> ret = new ArrayList<Face>();
|
||||
while(buf.hasRemaining())
|
||||
|
@ -318,6 +341,7 @@ public class B3DModel
|
|||
readHeader();
|
||||
ret.addAll(tris());
|
||||
}
|
||||
dump("}");
|
||||
popLimit();
|
||||
return Pair.of(getBrush(brush_id), ret);
|
||||
}
|
||||
|
@ -330,6 +354,7 @@ public class B3DModel
|
|||
{
|
||||
ret.add(Pair.of(getVertex(buf.getInt()), buf.getFloat()));
|
||||
}
|
||||
dump("BONE(...)");
|
||||
popLimit();
|
||||
return ret;
|
||||
}
|
||||
|
@ -381,6 +406,7 @@ public class B3DModel
|
|||
animations.peek().put(frame, Optional.<Node<?>>absent(), key);
|
||||
ret.put(frame, key);
|
||||
}
|
||||
dump("KEYS([(" + Joiner.on("), (").withKeyValueSeparator(" -> ").join(ret) + ")])");
|
||||
popLimit();
|
||||
return ret;
|
||||
}
|
||||
|
@ -391,6 +417,7 @@ public class B3DModel
|
|||
int flags = buf.getInt();
|
||||
int frames = buf.getInt();
|
||||
float fps = buf.getFloat();
|
||||
dump("ANIM(" + flags + ", " + frames + ", " + fps + ")");
|
||||
popLimit();
|
||||
return Triple.of(flags, frames, fps);
|
||||
}
|
||||
|
@ -408,6 +435,7 @@ public class B3DModel
|
|||
Vector3f pos = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
|
||||
Vector3f scale = new Vector3f(buf.getFloat(), buf.getFloat(), buf.getFloat());
|
||||
Quat4f rot = readQuat();
|
||||
dump("NODE(" + name + ", " + pos + ", " + scale + ", " + rot + ") {");
|
||||
while(buf.hasRemaining())
|
||||
{
|
||||
readHeader();
|
||||
|
@ -418,6 +446,7 @@ public class B3DModel
|
|||
else if(isChunk("ANIM")) animData = anim();
|
||||
else skip();
|
||||
}
|
||||
dump("}");
|
||||
popLimit();
|
||||
Table<Integer, Optional<Node<?>>, Key> keyData = animations.pop();
|
||||
Node<?> node;
|
||||
|
@ -965,6 +994,7 @@ public class B3DModel
|
|||
private Node<Mesh> parent;
|
||||
private final Brush brush;
|
||||
private final ImmutableList<Face> faces;
|
||||
//private final ImmutableList<Bone> bones;
|
||||
|
||||
private Set<Node<Bone>> bones = new HashSet<Node<Bone>>();
|
||||
|
||||
|
|
|
@ -81,6 +81,8 @@ public class LightUtil
|
|||
}
|
||||
});
|
||||
|
||||
private static final int itemCount = DefaultVertexFormats.ITEM.getElementCount();
|
||||
|
||||
public static void putBakedQuad(IVertexConsumer consumer, BakedQuad quad)
|
||||
{
|
||||
consumer.setQuadOrientation(quad.getFace());
|
||||
|
@ -94,12 +96,14 @@ public class LightUtil
|
|||
}
|
||||
//int[] eMap = mapFormats(consumer.getVertexFormat(), DefaultVertexFormats.ITEM);
|
||||
float[] data = new float[4];
|
||||
int[] eMap = formatMaps.getUnchecked(consumer.getVertexFormat());
|
||||
VertexFormat format = consumer.getVertexFormat();
|
||||
int count = format.getElementCount();
|
||||
int[] eMap = formatMaps.getUnchecked(format);
|
||||
for(int v = 0; v < 4; v++)
|
||||
{
|
||||
for(int e = 0; e < consumer.getVertexFormat().getElementCount(); e++)
|
||||
for(int e = 0; e < count; e++)
|
||||
{
|
||||
if(eMap[e] != DefaultVertexFormats.ITEM.getElementCount())
|
||||
if(eMap[e] != itemCount)
|
||||
{
|
||||
unpack(quad.getVertexData(), data, DefaultVertexFormats.ITEM, v, eMap[e]);
|
||||
consumer.put(e, data);
|
||||
|
@ -114,13 +118,15 @@ public class LightUtil
|
|||
|
||||
public static int[] mapFormats(VertexFormat from, VertexFormat to)
|
||||
{
|
||||
int[] eMap = new int[from.getElementCount()];
|
||||
int fromCount = from.getElementCount();
|
||||
int toCount = to.getElementCount();
|
||||
int[] eMap = new int[fromCount];
|
||||
|
||||
for(int e = 0; e < from.getElementCount(); e++)
|
||||
for(int e = 0; e < fromCount; e++)
|
||||
{
|
||||
VertexFormatElement expected = from.getElement(e);
|
||||
int e2;
|
||||
for(e2 = 0; e2 < to.getElementCount(); e2++)
|
||||
for(e2 = 0; e2 < toCount; e2++)
|
||||
{
|
||||
VertexFormatElement current = to.getElement(e2);
|
||||
if(expected.getUsage() == current.getUsage() && expected.getIndex() == current.getIndex())
|
||||
|
@ -135,44 +141,50 @@ public class LightUtil
|
|||
|
||||
public static void unpack(int[] from, float[] to, VertexFormat formatFrom, int v, int e)
|
||||
{
|
||||
int length = 4 < to.length ? 4 : to.length;
|
||||
VertexFormatElement element = formatFrom.getElement(e);
|
||||
int length = 4 <+ to.length ? 4 : to.length;
|
||||
int vertexStart = v * formatFrom.getNextOffset() + formatFrom.func_181720_d(e);
|
||||
int count = element.getElementCount();
|
||||
VertexFormatElement.EnumType type = element.getType();
|
||||
int size = type.getSize();
|
||||
int mask = (256 << (8 * (size - 1))) - 1;
|
||||
for(int i = 0; i < length; i++)
|
||||
{
|
||||
if(i < element.getElementCount())
|
||||
if(i < count)
|
||||
{
|
||||
int pos = v * formatFrom.getNextOffset() + formatFrom.func_181720_d(e) + element.getType().getSize() * i;
|
||||
int pos = vertexStart + size * i;
|
||||
int index = pos >> 2;
|
||||
int offset = pos & 3;
|
||||
int bits = from[index];
|
||||
bits = bits >>> (offset * 8);
|
||||
if((pos + element.getType().getSize() - 1) / 4 != index)
|
||||
if((pos + size - 1) / 4 != index)
|
||||
{
|
||||
bits |= from[index + 1] << ((4 - offset) * 8);
|
||||
}
|
||||
int mask = (256 << (8 * (element.getType().getSize() - 1))) - 1;
|
||||
bits &= mask;
|
||||
switch(element.getType())
|
||||
if(type == VertexFormatElement.EnumType.FLOAT)
|
||||
{
|
||||
case FLOAT:
|
||||
to[i] = Float.intBitsToFloat(bits);
|
||||
break;
|
||||
case UBYTE:
|
||||
case USHORT:
|
||||
to[i] = (float)bits / mask;
|
||||
break;
|
||||
case UINT:
|
||||
to[i] = (float)((double)(bits & 0xFFFFFFFFL) / 0xFFFFFFFFL);
|
||||
break;
|
||||
case BYTE:
|
||||
to[i] = ((float)(byte)bits) / mask * 2;
|
||||
break;
|
||||
case SHORT:
|
||||
to[i] = ((float)(short)bits) / mask * 2;
|
||||
break;
|
||||
case INT:
|
||||
to[i] = ((float)(bits & 0xFFFFFFFFL)) / 0xFFFFFFFFL * 2;
|
||||
break;
|
||||
to[i] = Float.intBitsToFloat(bits);
|
||||
}
|
||||
else if(type == VertexFormatElement.EnumType.UBYTE || type == VertexFormatElement.EnumType.USHORT)
|
||||
{
|
||||
to[i] = (float)bits / mask;
|
||||
}
|
||||
else if(type == VertexFormatElement.EnumType.UINT)
|
||||
{
|
||||
to[i] = (float)((double)(bits & 0xFFFFFFFFL) / 0xFFFFFFFFL);
|
||||
}
|
||||
else if(type == VertexFormatElement.EnumType.BYTE)
|
||||
{
|
||||
to[i] = ((float)(byte)bits) / mask * 2;
|
||||
}
|
||||
else if(type == VertexFormatElement.EnumType.SHORT)
|
||||
{
|
||||
to[i] = ((float)(short)bits) / mask * 2;
|
||||
}
|
||||
else if(type == VertexFormatElement.EnumType.INT)
|
||||
{
|
||||
to[i] = ((float)(bits & 0xFFFFFFFFL)) / 0xFFFFFFFFL * 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -185,31 +197,35 @@ public class LightUtil
|
|||
public static void pack(float[] from, int[] to, VertexFormat formatTo, int v, int e)
|
||||
{
|
||||
VertexFormatElement element = formatTo.getElement(e);
|
||||
int vertexStart = v * formatTo.getNextOffset() + formatTo.func_181720_d(e);
|
||||
int count = element.getElementCount();
|
||||
VertexFormatElement.EnumType type = element.getType();
|
||||
int size = type.getSize();
|
||||
int mask = (256 << (8 * (size - 1))) - 1;
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
if(i < element.getElementCount())
|
||||
if(i < count)
|
||||
{
|
||||
int pos = v * formatTo.getNextOffset() + formatTo.func_181720_d(e) + element.getType().getSize() * i;
|
||||
int pos = vertexStart + size * i;
|
||||
int index = pos >> 2;
|
||||
int offset = pos & 3;
|
||||
int bits = 0;
|
||||
int mask = (256 << (8 * (element.getType().getSize() - 1))) - 1;
|
||||
float f = i < from.length ? from[i] : 0;
|
||||
switch(element.getType())
|
||||
if(type == VertexFormatElement.EnumType.FLOAT)
|
||||
{
|
||||
case FLOAT:
|
||||
bits = Float.floatToRawIntBits(f);
|
||||
break;
|
||||
case UBYTE:
|
||||
case USHORT:
|
||||
case UINT:
|
||||
bits = (int)(f * mask);
|
||||
break;
|
||||
case BYTE:
|
||||
case SHORT:
|
||||
case INT:
|
||||
bits = (int)(f * mask / 2);
|
||||
break;
|
||||
bits = Float.floatToRawIntBits(f);
|
||||
}
|
||||
else if(
|
||||
type == VertexFormatElement.EnumType.UBYTE ||
|
||||
type == VertexFormatElement.EnumType.USHORT ||
|
||||
type == VertexFormatElement.EnumType.UINT
|
||||
)
|
||||
{
|
||||
bits = (int)(f * mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
bits = (int)(f * mask / 2);
|
||||
}
|
||||
to[index] &= ~(mask << (offset * 8));
|
||||
to[index] |= (((bits & mask) << (offset * 8)));
|
||||
|
|
|
@ -114,6 +114,9 @@ public class VertexLighterFlat extends QuadGatheringTransformer
|
|||
multiplier = blockInfo.getColorMultiplier(tint);
|
||||
}
|
||||
|
||||
VertexFormat format = parent.getVertexFormat();
|
||||
int count = format.getElementCount();
|
||||
|
||||
for(int v = 0; v < 4; v++)
|
||||
{
|
||||
position[v][0] += blockInfo.getShx();
|
||||
|
@ -145,9 +148,10 @@ public class VertexLighterFlat extends QuadGatheringTransformer
|
|||
}
|
||||
|
||||
// no need for remapping cause all we could've done is add 1 element to the end
|
||||
for(int e = 0; e < parent.getVertexFormat().getElementCount(); e++)
|
||||
for(int e = 0; e < count; e++)
|
||||
{
|
||||
switch(parent.getVertexFormat().getElement(e).getUsage())
|
||||
VertexFormatElement element = format.getElement(e);
|
||||
switch(element.getUsage())
|
||||
{
|
||||
case POSITION:
|
||||
// position adding moved to WorldRendererConsumer due to x and z not fitting completely into a float
|
||||
|
@ -166,7 +170,7 @@ public class VertexLighterFlat extends QuadGatheringTransformer
|
|||
case COLOR:
|
||||
parent.put(e, color[v]);
|
||||
break;
|
||||
case UV: if(getVertexFormat().getElement(e).getIndex() == 1)
|
||||
case UV: if(element.getIndex() == 1)
|
||||
{
|
||||
parent.put(e, lightmap[v]);
|
||||
break;
|
||||
|
|
|
@ -40,25 +40,28 @@ public class VertexLighterSmoothAo extends VertexLighterFlat
|
|||
y *= s;
|
||||
z *= s;
|
||||
}
|
||||
float ax = Math.abs(x);
|
||||
float ay = Math.abs(y);
|
||||
float az = Math.abs(z);
|
||||
float ax = x > 0 ? x : -x;
|
||||
float ay = y > 0 ? y : -y;
|
||||
float az = z > 0 ? z : -z;
|
||||
float e1 = 1 + 1e-4f;
|
||||
if(ax > 2 - 1e-4f && ay <= e1 && az <= e1)
|
||||
{
|
||||
x = MathHelper.clamp_float(x, -2 + 1e-4f, 2 - 1e-4f);
|
||||
if(x > -2 + 1e-4f) x = -2 + 1e-4f;
|
||||
if(x < 2 - 1e-4f) x = 2 - 1e-4f;
|
||||
}
|
||||
else if(ay > 2 - 1e-4f && az <= e1 && ax <= e1)
|
||||
{
|
||||
y = MathHelper.clamp_float(y, -2 + 1e-4f, 2 - 1e-4f);
|
||||
if(y > -2 + 1e-4f) y = -2 + 1e-4f;
|
||||
if(y < 2 - 1e-4f) y = 2 - 1e-4f;
|
||||
}
|
||||
else if(az > 2 - 1e-4f && ax <= e1 && ay <= e1)
|
||||
{
|
||||
z = MathHelper.clamp_float(z, -2 + 1e-4f, 2 - 1e-4f);
|
||||
if(z > -2 + 1e-4f) z = -2 + 1e-4f;
|
||||
if(z < 2 - 1e-4f) z = 2 - 1e-4f;
|
||||
}
|
||||
ax = Math.abs(x);
|
||||
ay = Math.abs(y);
|
||||
az = Math.abs(z);
|
||||
ax = x > 0 ? x : -x;
|
||||
ay = y > 0 ? y : -y;
|
||||
az = z > 0 ? z : -z;
|
||||
if(ax <= e1 && ay + az > 3f - 1e-4f)
|
||||
{
|
||||
float s = (3f - 1e-4f) / (ay + az);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package net.minecraftforge.client.model.pipeline;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormatElement.EnumUsage;
|
||||
|
@ -46,7 +44,7 @@ public class WorldRendererConsumer implements IVertexConsumer
|
|||
{
|
||||
renderer.addVertexData(quadData);
|
||||
renderer.putPosition(offset.getX(), offset.getY(), offset.getZ());
|
||||
Arrays.fill(quadData, 0);
|
||||
//Arrays.fill(quadData, 0);
|
||||
v = 0;
|
||||
}
|
||||
}
|
||||
|
@ -60,4 +58,4 @@ public class WorldRendererConsumer implements IVertexConsumer
|
|||
public void setQuadTint(int tint) {}
|
||||
public void setQuadOrientation(EnumFacing orientation) {}
|
||||
public void setQuadColored() {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package net.minecraftforge.common.model.animation;
|
||||
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.client.model.animation.Event;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* State machine representing the model animation.
|
||||
*/
|
||||
public interface IAnimationStateMachine
|
||||
{
|
||||
/**
|
||||
* Sample the state and events at the current time.
|
||||
* Event iterable will contain all events that happened from the last invocation of this method, from most to least recent.
|
||||
* Event offset is relative to the previous event, and for the first event it's relative to the current time.
|
||||
*/
|
||||
Pair<IModelState, Iterable<Event>> apply(float time);
|
||||
|
||||
/**
|
||||
* Transition to a new state.
|
||||
*/
|
||||
void transition(String newState);
|
||||
|
||||
/**
|
||||
* Get current state name.
|
||||
*/
|
||||
String currentState();
|
||||
|
||||
/**
|
||||
* Set to true if the machine should handle special events that come from the clips (they start with '!').
|
||||
* Right now only implemented event is "!transition:<state_name>".
|
||||
* Default value is true.
|
||||
*/
|
||||
void shouldHandleSpecialEvents(boolean value);
|
||||
}
|
|
@ -1,9 +1,27 @@
|
|||
package net.minecraftforge.common.property;
|
||||
|
||||
import net.minecraft.block.properties.IProperty;
|
||||
import net.minecraft.block.properties.PropertyBool;
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
|
||||
public class Properties
|
||||
{
|
||||
/**
|
||||
* Property indicating if the model should be rendered in the static renderer or in the TESR. AnimationTESR sets it to false.
|
||||
*/
|
||||
public static final PropertyBool StaticProperty = PropertyBool.create("static");
|
||||
|
||||
/**
|
||||
* Property holding the IModelState used for animating the model in the TESR.
|
||||
*/
|
||||
public static final IUnlistedProperty<IModelState> AnimationProperty = new IUnlistedProperty<IModelState>()
|
||||
{
|
||||
public String getName() { return "forge_animation"; }
|
||||
public boolean isValid(IModelState state) { return true; }
|
||||
public Class<IModelState> getType() { return IModelState.class; }
|
||||
public String valueToString(IModelState state) { return state.toString(); }
|
||||
};
|
||||
|
||||
public static <V extends Comparable<V>> IUnlistedProperty<V> toUnlisted(IProperty<V> property)
|
||||
{
|
||||
return new PropertyAdapter<V>(property);
|
||||
|
|
73
src/main/java/net/minecraftforge/common/util/JsonUtils.java
Normal file
73
src/main/java/net/minecraftforge/common/util/JsonUtils.java
Normal file
|
@ -0,0 +1,73 @@
|
|||
package net.minecraftforge.common.util;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.reflect.TypeParameter;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
public class JsonUtils
|
||||
{
|
||||
// http://stackoverflow.com/questions/7706772/deserializing-immutablelist-using-gson/21677349#21677349
|
||||
public enum ImmutableListTypeAdapter implements JsonDeserializer<ImmutableList<?>>, JsonSerializer<ImmutableList<?>>
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
public ImmutableList<?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
final Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
|
||||
final Type parameterizedType = listOf(typeArguments[0]).getType();
|
||||
final List<?> list = context.deserialize(json, parameterizedType);
|
||||
return ImmutableList.copyOf(list);
|
||||
}
|
||||
|
||||
public JsonElement serialize(ImmutableList<?> src, Type type, JsonSerializationContext context)
|
||||
{
|
||||
final Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
|
||||
final Type parameterizedType = listOf(typeArguments[0]).getType();
|
||||
return context.serialize(src, parameterizedType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "serial", "unchecked" })
|
||||
private static <E> TypeToken<List<E>> listOf(final Type arg)
|
||||
{
|
||||
return new TypeToken<List<E>>() {}.where(new TypeParameter<E>() {}, (TypeToken<E>) TypeToken.of(arg));
|
||||
}
|
||||
|
||||
public enum ImmutableMapTypeAdapter implements JsonDeserializer<ImmutableMap<String, ?>>, JsonSerializer<ImmutableMap<String, ?>>
|
||||
{
|
||||
INSTANCE;
|
||||
|
||||
public ImmutableMap<String, ?> deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException
|
||||
{
|
||||
final Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
|
||||
final Type parameterizedType = mapOf(typeArguments[1]).getType();
|
||||
final Map<String, ?> map = context.deserialize(json, parameterizedType);
|
||||
return ImmutableMap.copyOf(map);
|
||||
}
|
||||
|
||||
public JsonElement serialize(ImmutableMap<String, ?> src, Type type, JsonSerializationContext context)
|
||||
{
|
||||
final Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments();
|
||||
final Type parameterizedType = mapOf(typeArguments[1]).getType();
|
||||
return context.serialize(src, parameterizedType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "serial", "unchecked" })
|
||||
private static <E> TypeToken<Map<String, E>> mapOf(final Type arg)
|
||||
{
|
||||
return new TypeToken<Map<String, E>>() {}.where(new TypeParameter<E>() {}, (TypeToken<E>) TypeToken.of(arg));
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ import net.minecraft.util.ResourceLocation;
|
|||
import net.minecraft.util.StringUtils;
|
||||
import net.minecraft.world.WorldSettings;
|
||||
import net.minecraft.world.storage.SaveFormatOld;
|
||||
import net.minecraftforge.client.model.animation.Animation;
|
||||
import net.minecraftforge.fml.client.registry.RenderingRegistry;
|
||||
import net.minecraftforge.fml.common.DummyModContainer;
|
||||
import net.minecraftforge.fml.common.DuplicateModsFoundException;
|
||||
|
@ -199,6 +200,8 @@ public class FMLClientHandler implements IFMLSidedHandler
|
|||
return;
|
||||
}
|
||||
|
||||
resourceManager.registerReloadListener(Animation.INSTANCE);
|
||||
|
||||
FMLCommonHandler.instance().beginLoading(this);
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,50 +1,64 @@
|
|||
package net.minecraftforge.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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.PropertyBool;
|
||||
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.entity.Render;
|
||||
import net.minecraft.client.renderer.entity.RenderLiving;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
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.EntityLiving;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import net.minecraft.entity.SharedMonsterAttributes;
|
||||
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.util.ResourceLocation;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.model.ISmartBlockModel;
|
||||
import net.minecraftforge.client.model.IModel;
|
||||
import net.minecraftforge.client.model.IModelState;
|
||||
import net.minecraftforge.client.model.IRetexturableModel;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.client.model.ModelLoaderRegistry;
|
||||
import net.minecraftforge.client.model.MultiModel;
|
||||
import net.minecraftforge.client.model.TRSRTransformation;
|
||||
import net.minecraftforge.client.model.animation.Animation;
|
||||
import net.minecraftforge.client.model.animation.AnimationModelBase;
|
||||
import net.minecraftforge.client.model.animation.AnimationTESR;
|
||||
import net.minecraftforge.client.model.animation.Event;
|
||||
import net.minecraftforge.client.model.animation.IAnimationProvider;
|
||||
import net.minecraftforge.client.model.animation.ITimeValue;
|
||||
import net.minecraftforge.client.model.animation.TimeValues.VariableValue;
|
||||
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.client.model.pipeline.VertexLighterSmoothAo;
|
||||
import net.minecraftforge.common.model.animation.IAnimationStateMachine;
|
||||
import net.minecraftforge.common.property.ExtendedBlockState;
|
||||
import net.minecraftforge.common.property.IExtendedBlockState;
|
||||
import net.minecraftforge.common.property.IUnlistedProperty;
|
||||
import net.minecraftforge.common.property.Properties;
|
||||
import net.minecraftforge.fml.client.registry.ClientRegistry;
|
||||
import net.minecraftforge.fml.client.registry.IRenderFactory;
|
||||
import net.minecraftforge.fml.client.registry.RenderingRegistry;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.Mod.EventHandler;
|
||||
import net.minecraftforge.fml.common.Mod.Instance;
|
||||
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.EntityRegistry;
|
||||
import net.minecraftforge.fml.common.registry.GameRegistry;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
@Mod(modid = ModelAnimationDebug.MODID, version = ModelAnimationDebug.VERSION)
|
||||
public class ModelAnimationDebug
|
||||
|
@ -54,12 +68,14 @@ public class ModelAnimationDebug
|
|||
|
||||
public static String blockName = "test_animation_block";
|
||||
public static final PropertyDirection FACING = PropertyDirection.create("facing");
|
||||
public static final PropertyBool STATIC = PropertyBool.create("static");;
|
||||
|
||||
@Instance(MODID)
|
||||
public static ModelAnimationDebug instance;
|
||||
|
||||
@SidedProxy
|
||||
public static CommonProxy proxy;
|
||||
|
||||
public static class CommonProxy
|
||||
public static abstract class CommonProxy
|
||||
{
|
||||
public void preInit(FMLPreInitializationEvent event)
|
||||
{
|
||||
|
@ -73,7 +89,7 @@ public class ModelAnimationDebug
|
|||
@Override
|
||||
public ExtendedBlockState createBlockState()
|
||||
{
|
||||
return new ExtendedBlockState(this, new IProperty[]{ FACING, STATIC }, new IUnlistedProperty[]{ B3DFrameProperty.instance });
|
||||
return new ExtendedBlockState(this, new IProperty[]{ FACING, Properties.StaticProperty }, new IUnlistedProperty[]{ Properties.AnimationProperty });
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -105,12 +121,12 @@ public class ModelAnimationDebug
|
|||
|
||||
@Override
|
||||
public TileEntity createTileEntity(World world, IBlockState state) {
|
||||
return new Chest(state);
|
||||
return new Chest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockState getActualState(IBlockState state, IBlockAccess world, BlockPos pos) {
|
||||
return state.withProperty(STATIC, true);
|
||||
return state.withProperty(Properties.StaticProperty, true);
|
||||
}
|
||||
|
||||
/*@Override
|
||||
|
@ -131,18 +147,25 @@ public class ModelAnimationDebug
|
|||
TileEntity te = world.getTileEntity(pos);
|
||||
if(te instanceof Chest)
|
||||
{
|
||||
((Chest)te).click();
|
||||
((Chest)te).click(player.isSneaking());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}, blockName);
|
||||
GameRegistry.registerTileEntity(Chest.class, MODID + ":" + "tile_" + blockName);
|
||||
}
|
||||
|
||||
public void init(FMLInitializationEvent event) {}
|
||||
public abstract IAnimationStateMachine load(ResourceLocation location, ImmutableMap<String, ITimeValue> parameters);
|
||||
}
|
||||
|
||||
public static class ServerProxy extends CommonProxy {}
|
||||
public static class ServerProxy extends CommonProxy
|
||||
{
|
||||
public IAnimationStateMachine load(ResourceLocation location, ImmutableMap<String, ITimeValue> parameters)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientProxy extends CommonProxy
|
||||
{
|
||||
|
@ -152,142 +175,212 @@ public class ModelAnimationDebug
|
|||
super.preInit(event);
|
||||
B3DLoader.instance.addDomain(MODID);
|
||||
ModelLoader.setCustomModelResourceLocation(Item.getItemFromBlock(GameRegistry.findBlock(MODID, blockName)), 0, new ModelResourceLocation(MODID.toLowerCase() + ":" + blockName, "inventory"));
|
||||
ClientRegistry.bindTileEntitySpecialRenderer(Chest.class, ChestRenderer.instance);
|
||||
ClientRegistry.bindTileEntitySpecialRenderer(Chest.class, new AnimationTESR<Chest>()
|
||||
{
|
||||
@Override
|
||||
public void handleEvents(Chest chest, float time, Iterable<Event> pastEvents)
|
||||
{
|
||||
chest.handleEvents(time, pastEvents);
|
||||
}
|
||||
});
|
||||
String entityName = MODID + ":entity_chest";
|
||||
//EntityRegistry.registerGlobalEntityID(EntityChest.class, entityName, EntityRegistry.findGlobalUniqueEntityId());
|
||||
EntityRegistry.registerModEntity(EntityChest.class, entityName, 0, ModelAnimationDebug.instance, 64, 20, true, 0xFFAAAA00, 0xFFDDDD00);
|
||||
RenderingRegistry.registerEntityRenderingHandler(EntityChest.class, new IRenderFactory<EntityChest>()
|
||||
{
|
||||
public Render<EntityChest> createRenderFor(RenderManager manager)
|
||||
{
|
||||
try
|
||||
{
|
||||
/*model = ModelLoaderRegistry.getModel(new ResourceLocation(ModelLoaderRegistryDebug.MODID, "block/chest.b3d"));
|
||||
if(model instanceof IRetexturableModel)
|
||||
{
|
||||
model = ((IRetexturableModel)model).retexture(ImmutableMap.of("#chest", "entity/chest/normal"));
|
||||
}
|
||||
if(model instanceof IModelCustomData)
|
||||
{
|
||||
model = ((IModelCustomData)model).process(ImmutableMap.of("mesh", "[\"Base\", \"Lid\"]"));
|
||||
}*/
|
||||
IModel base = ModelLoaderRegistry.getModel(new ResourceLocation(ModelAnimationDebug.MODID, "block/engine"));
|
||||
IModel ring = ModelLoaderRegistry.getModel(new ResourceLocation(ModelAnimationDebug.MODID, "block/engine_ring"));
|
||||
ImmutableMap<String, String> textures = ImmutableMap.of(
|
||||
"base", "blocks/stone",
|
||||
"front", "blocks/log_oak",
|
||||
"chamber", "blocks/redstone_block",
|
||||
"trunk", "blocks/end_stone"
|
||||
);
|
||||
if(base instanceof IRetexturableModel)
|
||||
{
|
||||
base = ((IRetexturableModel)base).retexture(textures);
|
||||
}
|
||||
if(ring instanceof IRetexturableModel)
|
||||
{
|
||||
ring = ((IRetexturableModel)ring).retexture(textures);
|
||||
}
|
||||
IModel model = new MultiModel(
|
||||
new ResourceLocation(ModelAnimationDebug.MODID, "builtin/engine"),
|
||||
ring,
|
||||
TRSRTransformation.identity(),
|
||||
ImmutableMap.of(
|
||||
"base", Pair.<IModel, IModelState>of(base, TRSRTransformation.identity())
|
||||
)
|
||||
);
|
||||
return new RenderLiving<EntityChest>(manager, new AnimationModelBase<EntityChest>(model, new VertexLighterSmoothAo())
|
||||
{
|
||||
@Override
|
||||
public void handleEvents(EntityChest chest, float time, Iterable<Event> pastEvents)
|
||||
{
|
||||
chest.handleEvents(time, pastEvents);
|
||||
}
|
||||
}, 0.5f)
|
||||
{
|
||||
protected ResourceLocation getEntityTexture(EntityChest entity)
|
||||
{
|
||||
return TextureMap.locationBlocksTexture;
|
||||
}
|
||||
};
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FMLInitializationEvent event)
|
||||
public IAnimationStateMachine load(ResourceLocation location, ImmutableMap<String, ITimeValue> parameters)
|
||||
{
|
||||
super.init(event);
|
||||
return Animation.INSTANCE.load(location, parameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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
|
||||
public static class Chest extends TileEntity implements IAnimationProvider
|
||||
{
|
||||
private final int minFrame = 1;
|
||||
private final int maxFrame = 10;
|
||||
private int tick = minFrame;
|
||||
private boolean opening = false;
|
||||
private boolean closing = false;
|
||||
private final IAnimationStateMachine asm;
|
||||
private final VariableValue cycleLength = new VariableValue(4);
|
||||
private final VariableValue clickTime = new VariableValue(Float.NEGATIVE_INFINITY);
|
||||
//private final VariableValue offset = new VariableValue(0);
|
||||
|
||||
public Chest(IBlockState state) {
|
||||
public Chest() {
|
||||
/*asm = proxy.load(new ResourceLocation(MODID.toLowerCase(), "asms/block/chest.json"), ImmutableMap.<String, ITimeValue>of(
|
||||
"click_time", clickTime
|
||||
));*/
|
||||
asm = proxy.load(new ResourceLocation(MODID.toLowerCase(), "asms/block/engine.json"), ImmutableMap.<String, ITimeValue>of(
|
||||
"cycle_length", cycleLength,
|
||||
"click_time", clickTime
|
||||
//"offset", offset
|
||||
));
|
||||
}
|
||||
|
||||
public void handleEvents(float time, Iterable<Event> pastEvents)
|
||||
{
|
||||
for(Event event : pastEvents)
|
||||
{
|
||||
System.out.println("Event: " + event.event() + " " + event.offset() + " " + getPos() + " " + time);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFastRenderer()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*public IExtendedBlockState getState(IExtendedBlockState state) {
|
||||
return state.withProperty(B3DFrameProperty.instance, curState);
|
||||
}*/
|
||||
|
||||
public void click()
|
||||
public void click(boolean sneaking)
|
||||
{
|
||||
if(opening || tick == maxFrame)
|
||||
if(asm != null)
|
||||
{
|
||||
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)
|
||||
if(sneaking)
|
||||
{
|
||||
tick = maxFrame;
|
||||
opening = false;
|
||||
cycleLength.setValue(6 - cycleLength.apply(0));
|
||||
}
|
||||
}
|
||||
if(closing)
|
||||
{
|
||||
tick--;
|
||||
if(tick <= minFrame)
|
||||
/*else if(asm.currentState().equals("closed"))
|
||||
{
|
||||
tick = minFrame;
|
||||
closing = false;
|
||||
clickTime.setValue(Animation.getWorldTime(getWorld()));
|
||||
asm.transition("opening");
|
||||
}
|
||||
else if(asm.currentState().equals("open"))
|
||||
{
|
||||
clickTime.setValue(Animation.getWorldTime(getWorld()));
|
||||
asm.transition("closing");
|
||||
}*/
|
||||
else if(asm.currentState().equals("default"))
|
||||
{
|
||||
float time = Animation.getWorldTime(getWorld());
|
||||
clickTime.setValue(time);
|
||||
//offset.setValue(time);
|
||||
//asm.transition("moving");
|
||||
asm.transition("starting");
|
||||
}
|
||||
else if(asm.currentState().equals("moving"))
|
||||
{
|
||||
clickTime.setValue(Animation.getWorldTime(getWorld()));
|
||||
asm.transition("stopping");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurFrame()
|
||||
public IAnimationStateMachine asm()
|
||||
{
|
||||
return tick;
|
||||
}
|
||||
|
||||
public int getNextFrame()
|
||||
{
|
||||
if(opening) return Math.min(tick + 1, maxFrame);
|
||||
if(closing) return Math.max(tick - 1, minFrame);
|
||||
return tick;
|
||||
return asm;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ChestRenderer extends TileEntitySpecialRenderer<Chest>
|
||||
public static class EntityChest extends EntityLiving implements IAnimationProvider
|
||||
{
|
||||
public static ChestRenderer instance = new ChestRenderer();
|
||||
private ChestRenderer() {}
|
||||
private final IAnimationStateMachine asm;
|
||||
private VariableValue cycleLength;
|
||||
|
||||
private BlockRendererDispatcher blockRenderer;
|
||||
|
||||
public void renderTileEntityAt(Chest te, double x, double y, double z, float partialTick, int breakStage)
|
||||
public EntityChest(World world)
|
||||
{
|
||||
if(blockRenderer == null) blockRenderer = Minecraft.getMinecraft().getBlockRendererDispatcher();
|
||||
IBlockState state = te.getWorld().getBlockState(te.getPos());
|
||||
state = state.withProperty(STATIC, false);
|
||||
IBakedModel model = this.blockRenderer.getBlockModelShapes().getModelForState(state);
|
||||
if(state instanceof IExtendedBlockState)
|
||||
super(world);
|
||||
setSize(1, 1);
|
||||
if(cycleLength == null)
|
||||
{
|
||||
IExtendedBlockState exState = (IExtendedBlockState)state;
|
||||
if(exState.getUnlistedNames().contains(B3DFrameProperty.instance))
|
||||
cycleLength = new VariableValue(getHealth() / 5);
|
||||
}
|
||||
asm = proxy.load(new ResourceLocation(MODID.toLowerCase(), "asms/block/engine.json"), ImmutableMap.<String, ITimeValue>of(
|
||||
"cycle_length", cycleLength
|
||||
));
|
||||
}
|
||||
|
||||
public void handleEvents(float time, Iterable<Event> pastEvents)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
public IAnimationStateMachine asm()
|
||||
{
|
||||
return asm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataWatcherUpdate(int id)
|
||||
{
|
||||
super.onDataWatcherUpdate(id);
|
||||
if(id == 6) // health
|
||||
{
|
||||
if(cycleLength == null)
|
||||
{
|
||||
exState = exState.withProperty(B3DFrameProperty.instance, new B3DState(null, te.getCurFrame(), te.getNextFrame(), partialTick));
|
||||
if(model instanceof ISmartBlockModel)
|
||||
{
|
||||
model = ((ISmartBlockModel)model).handleBlockState(exState);
|
||||
}
|
||||
cycleLength = new VariableValue(0);
|
||||
}
|
||||
cycleLength.setValue(getHealth() / 5);
|
||||
}
|
||||
}
|
||||
|
||||
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.begin(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();
|
||||
@Override
|
||||
protected void applyEntityAttributes()
|
||||
{
|
||||
super.applyEntityAttributes();
|
||||
this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(60);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"joints": {
|
||||
"ring": { "0": [ 1.0 ] },
|
||||
"chamber": { "1": [ 1.0 ] },
|
||||
"trunk": { "2": [ 1.0 ] }
|
||||
},
|
||||
"clips": {
|
||||
"default": {
|
||||
"loop": false,
|
||||
"joint_clips": {},
|
||||
"events": {}
|
||||
},
|
||||
"moving": {
|
||||
"loop": true,
|
||||
"joint_clips": {
|
||||
"ring": [
|
||||
{
|
||||
"variable": "offset_y",
|
||||
"type": "uniform",
|
||||
"interpolation": "linear",
|
||||
"samples": [ 0, 0.08, 0.25, 0.42, 0.5, 0.42, 0.25, 0.08 ]
|
||||
},
|
||||
{
|
||||
"variable": "axis_y",
|
||||
"type": "uniform",
|
||||
"interpolation": "nearest",
|
||||
"samples": [ 1 ]
|
||||
},
|
||||
{
|
||||
"variable": "angle",
|
||||
"type": "uniform",
|
||||
"interpolation": "linear",
|
||||
"samples": [
|
||||
0, 120, 240,
|
||||
0, 120, 240,
|
||||
0, 120, 240,
|
||||
0, 120, 240
|
||||
]
|
||||
}
|
||||
],
|
||||
"chamber": [
|
||||
{
|
||||
"variable": "scale",
|
||||
"type": "uniform",
|
||||
"interpolation": "nearest",
|
||||
"samples": [ 0, 1 ]
|
||||
}
|
||||
],
|
||||
"trunk": [
|
||||
{
|
||||
"variable": "scale",
|
||||
"type": "uniform",
|
||||
"interpolation": "nearest",
|
||||
"samples": [ 1, 0 ]
|
||||
}
|
||||
]
|
||||
},
|
||||
"events": {
|
||||
"0.5": "boop"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"parameters": {
|
||||
"end_anim": [ "compose", [ "+", 1 ] , "#click_time" ],
|
||||
"trigger_anim": [ "-", "#end_anim" ],
|
||||
"progress": [ "-", "#click_time" ]
|
||||
},
|
||||
"clips": {
|
||||
"model": "forgedebugmodelloaderregistry:block/chest.b3d@main",
|
||||
"closed": [ "apply", "#model", 0 ],
|
||||
"opening": [
|
||||
"trigger_positive",
|
||||
[ "slerp", "#closed", "#open", "#identity", "#progress" ],
|
||||
"#trigger_anim",
|
||||
"!transition:open"
|
||||
],
|
||||
"open": [ "apply", "#model", 10 ],
|
||||
"closing": [
|
||||
"trigger_positive",
|
||||
[ "slerp", "#open", "#closed", "#identity", "#progress" ],
|
||||
"#trigger_anim",
|
||||
"!transition:closed"
|
||||
]
|
||||
},
|
||||
"states": [
|
||||
"closed",
|
||||
"opening",
|
||||
"open",
|
||||
"closing"
|
||||
],
|
||||
"transitions": {
|
||||
"closed": "opening",
|
||||
"opening": "open",
|
||||
"open": "closing",
|
||||
"closing": "closed"
|
||||
},
|
||||
"start_state": "closed"
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"parameters": {
|
||||
"world_to_cycle": [ "/", "#cycle_length" ],
|
||||
"round_cycle": [ "compose", [ "R", "#cycle_length" ] , "#click_time" ],
|
||||
"end_cycle": [ "-", "#round_cycle" ]
|
||||
},
|
||||
"clips": {
|
||||
"default": "forgedebugmodelanimation:block/engine_ring@default",
|
||||
"starting": [ "trigger_positive", "#default", "#end_cycle", "!transition:moving" ],
|
||||
"moving": [ "apply", "forgedebugmodelanimation:block/engine_ring@moving", "#world_to_cycle" ],
|
||||
"stopping": [ "trigger_positive", "#moving", "#end_cycle", "!transition:default" ]
|
||||
},
|
||||
"states": [
|
||||
"default",
|
||||
"starting",
|
||||
"moving",
|
||||
"stopping"
|
||||
],
|
||||
"transitions": {
|
||||
"default": "starting",
|
||||
"starting": "moving",
|
||||
"moving": "stopping",
|
||||
"stopping": "default"
|
||||
},
|
||||
"start_state": "moving"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"parameters": {
|
||||
"clip_time": [ "-/", "#offset", "#cycle_length" ],
|
||||
"round_cycle": [ "compose", [ "-R+", "#offset", "#cycle_length", "#offset" ] , "#click_time" ],
|
||||
"end_cycle": [ "-", "#round_cycle" ]
|
||||
},
|
||||
"clips": {
|
||||
"default": "forgedebugmodelanimation:block/engine_ring@default",
|
||||
"moving": [ "apply", "forgedebugmodelanimation:block/engine_ring@moving", "#clip_time" ],
|
||||
"stopping": [ "trigger_positive", "#moving", "#end_cycle", "!transition:default" ]
|
||||
},
|
||||
"states": [
|
||||
"default",
|
||||
"moving",
|
||||
"stopping"
|
||||
],
|
||||
"transitions": {
|
||||
"default": "moving",
|
||||
"moving": "stopping",
|
||||
"stopping": "default"
|
||||
},
|
||||
"start_state": "moving"
|
||||
}
|
|
@ -2,12 +2,16 @@
|
|||
"forge_marker": 1,
|
||||
"defaults": {
|
||||
"textures": {
|
||||
"#chest": "entity/chest/normal"
|
||||
},
|
||||
"#chest": "entity/chest/normal",
|
||||
"base": "blocks/stone",
|
||||
"front": "blocks/log_oak",
|
||||
"chamber": "blocks/redstone_block",
|
||||
"trunk": "blocks/end_stone"
|
||||
}/*,
|
||||
"model": "forgedebugmodelloaderregistry:chest.b3d",
|
||||
"custom": {
|
||||
"mesh": ["Base", "Lid"]
|
||||
}
|
||||
}*/
|
||||
},
|
||||
"variants": {
|
||||
"normal": [{}],
|
||||
|
@ -16,9 +20,13 @@
|
|||
"rotation": { "y": 180 },
|
||||
"thirdperson": {
|
||||
"rotation": [ { "z": 170 }, { "y": -45 }, { "z": 20 } ],
|
||||
"translation": [ 0, 0.09375, -0.171875 ],
|
||||
"scale": 0.375
|
||||
"translation": [ 0, 0.09375, -0.171875 ],
|
||||
"scale": 0.375
|
||||
}
|
||||
},
|
||||
"model": "forgedebugmodelanimation:engine",
|
||||
"submodel": {
|
||||
"ring": { "model": "forgedebugmodelanimation:engine_ring" }
|
||||
}
|
||||
}],
|
||||
"facing": {
|
||||
|
@ -31,14 +39,16 @@
|
|||
},
|
||||
"static": {
|
||||
"true": {
|
||||
"custom": {
|
||||
/*"custom": {
|
||||
"mesh": ["Base"]
|
||||
}
|
||||
}*/
|
||||
"model": "forgedebugmodelanimation:engine"
|
||||
},
|
||||
"false": {
|
||||
"custom": {
|
||||
/*"custom": {
|
||||
"mesh": ["Lid"]
|
||||
}
|
||||
}*/
|
||||
"model": "forgedebugmodelanimation:engine_ring"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"from": [ 4, 0, 4 ],
|
||||
"to": [ 12, 4, 12 ],
|
||||
"from": [ 4, 8, 4 ],
|
||||
"to": [ 12, 12, 12 ],
|
||||
"faces": {
|
||||
"north": { "uv": [ 4, 8, 12, 12 ], "texture":"#chamber" },
|
||||
"south": { "uv": [ 4, 8, 12, 12 ], "texture":"#chamber" },
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"#chest": "entity/chest/normal"
|
||||
},
|
||||
"model": "forgedebugmodelloaderregistry:chest.b3d",
|
||||
"transform": "forge:default-block",
|
||||
"custom": {
|
||||
"mesh": ["Base", "Lid"]
|
||||
}
|
||||
|
@ -14,7 +13,14 @@
|
|||
"variants": {
|
||||
"normal": [{}],
|
||||
"inventory": [{
|
||||
"y": 180
|
||||
"transform": {
|
||||
"rotation": { "y": 180 },
|
||||
"thirdperson": {
|
||||
"rotation": [ { "z": 170 }, { "y": -45 }, { "z": 20 } ],
|
||||
"translation": [ 0, 0.09375, -0.171875 ],
|
||||
"scale": 0.375
|
||||
}
|
||||
}
|
||||
}],
|
||||
"facing": {
|
||||
"down": {"x": 90},
|
||||
|
|
Loading…
Reference in a new issue