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:
RainWarrior 2016-01-01 18:15:48 +03:00
parent e9a86f27fb
commit 0710bdf3f5
44 changed files with 3185 additions and 252 deletions

View file

@ -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_);

View file

@ -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;
}

View file

@ -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();
+ }
}

View file

@ -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) {}
}

View file

@ -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()
+ {

View file

@ -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());
}

View file

@ -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)));
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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) {}
}

View file

@ -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;
}
}

View file

@ -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) {}
}

View file

@ -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());
}
}
};
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}
};
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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>>();

View file

@ -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)));

View file

@ -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;

View file

@ -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);

View file

@ -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() {}
}
}

View file

@ -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);
}

View file

@ -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);

View 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));
}
}

View file

@ -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
{

View file

@ -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);
}
}
}

View file

@ -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"
}
}
}
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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"
}
}
}

View file

@ -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" },

View file

@ -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},