Figura

Figura

509k Downloads

Figura is not compatible with physics mod ragdolls

NobleDraconian opened this issue ยท 6 comments

commented

As of 0.1.0-rc.14+1.19.2, the figura mod is not compatible with player ragdolls generated by the physics mod. This can be fixed by having figura call the ragdoll API that the physics mod provides.

Issue 163 in the old repository gave info on this : https://github.com/Moonlight-MC/Figura/issues/163

Here's a video of the issue occurring. Notice how the figura avatar does the vanilla death animation instead of ragdolling, while the vanilla model ragdolls:

MCDeath_Compressed.mp4
commented

It seems like an issue of the physics mod itself not updating the player model, nor calling the vanilla player rendering, during the ragdoll phase

Also their API (which has no reference anywhere on their website), after analyzing the example code from the past Issue, it will be impossible to make them compatible, as Figura has its own model system added on top of vanilla instead of extending the vanilla model

And due to their code being closed source, theres nothing else we could do, where mostly the incompatibility comes from what it was said above

commented

I'll reach out to the physics mod author on Discord to see if there's some compatibility they can add on their end

commented

@Francy-chan I found the source code for the ragdoll hook used in the physics mod :
https://github.com/haubna/PhysicsMod/blob/main/VanillaRagdollHook.java

commented

Yeah, its pretty much the same issue as before; the hook expects a vanilla model, with the vanilla model parts
where the issue might be caused by a couple of different factors, like for example:

  • not calling the vanilla rendering for ragdolls
  • not updating the vanilla model pose
  • using a different UUID for the ragdoll entity (if its even an entity)

What this hook could be used for, is, assuming Figura works with their ragdolls, to fix possible parenting issues like if the avatar have 4 arms

commented

Got it. I'll continue to investigate, I'll see if I can get the physics mod author to expose an API that gives the ragdoll limb's rotations & positions.

commented

This was taken from a convo in the physics mod discord server:
image

For gecko lib support I use this (old and hacky code):

package net.diebuddies.compat;

import java.util.List;

import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;

import com.mojang.blaze3d.vertex.PoseStack;

import net.diebuddies.config.ConfigMobs;
import net.diebuddies.opengl.TextureHelper;
import net.diebuddies.physics.Mesh;
import net.diebuddies.physics.PhysicsEntity;
import net.diebuddies.physics.PhysicsMod;
import net.diebuddies.physics.StarterClient;
import net.diebuddies.physics.settings.mobs.MobPhysicsType;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.EntityRenderer;
import net.minecraft.client.renderer.entity.layers.RenderLayer;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import software.bernie.geckolib.cache.object.GeoCube;
import software.bernie.geckolib.cache.object.GeoQuad;
import software.bernie.geckolib.cache.object.GeoVertex;

public class GeckoLib {
	
	public static void createParticlesFromCuboids(PoseStack local, GeoCube cube, Entity entity, EntityRenderer renderer, RenderLayer feature,
			int overlay, float red, float green, float blue) {
		Matrix4f localM = local.last().pose();
		Matrix3f localNM = local.last().normal();
		
		PhysicsMod mod = PhysicsMod.getInstance(entity.getCommandSenderWorld());
		
		int textureID = TextureHelper.getLoadedTextures();
		
		float partialTicks = Minecraft.getInstance().getFrameTime();
	    double px = Mth.lerp(partialTicks, entity.xo, entity.getX());
	    double py = Mth.lerp(partialTicks, entity.yo, entity.getY());
	    double pz = Mth.lerp(partialTicks, entity.zo, entity.getZ());
		
		Vector4f[] minMax = new Vector4f[6];
		Vector3f min = new Vector3f(Float.MAX_VALUE);
		Vector3f max = new Vector3f(-Float.MAX_VALUE);

		Vector3f tmpNormal = new Vector3f();
		Vector4f tmpPos = new Vector4f();
		for (int i = 0; i < minMax.length; i++) minMax[i] = new org.joml.Vector4f();
		MobPhysicsType type = ConfigMobs.getMobSetting(entity).getType();

		Vector3f tmp = new Vector3f();
		
		for (int i = 0; i < cube.quads().length; i++) {
			GeoQuad quad = cube.quads()[i];
			
			float minU = 1.0f, maxU = 0.0f;
			float minV = 1.0f, maxV = 0.0f;
			
			for (GeoVertex vertex : quad.vertices()) {
				if (vertex.texU() < minU) minU = vertex.texU();
				if (vertex.texV() < minV) minV = vertex.texV();
				if (vertex.texU() > maxU) maxU = vertex.texU();
				if (vertex.texV() > maxV) maxV = vertex.texV();
				
				tmp.set(vertex.position().x(), vertex.position().y(), vertex.position().z());
				min.min(tmp);
				max.max(tmp);
			}
			
			minMax[i].set(minU, maxU, minV, maxV);
		}
		
		int[] remap = new int[] { 3, 0, 2, 1, 4, 5 };
		
		float volume = (Math.abs(max.x - min.x)) * (Math.abs(max.y - min.y)) * (Math.abs(max.z - min.z));
		boolean isBlocky = type == MobPhysicsType.BLOCKY || type == MobPhysicsType.RAGDOLL || type == MobPhysicsType.RAGDOLL_BREAK || type == MobPhysicsType.RAGDOLL_BREAK_BLOOD;
		
		// this is the proper solution but crashes with the convex physx solver since the convex mesh would be coplanar
//			if (volume <= 0.0001 && !isBlocky) continue;
		// so just skip small volumes
		boolean noVolume = false;
		
		if (volume <= 0.0001) {
			if (isBlocky) {
				noVolume = true;
			} else {
				return;
			}
		}
		
		boolean mirror = cube.mirror();
		
		List<Mesh> meshes = PhysicsMod.brokenBlocksLittle.get((int) (net.diebuddies.math.Math.random() * PhysicsMod.brokenBlocksLittle.size()));
    	
		if (volume <= 0.04 || isBlocky) meshes = PhysicsMod.brokenBlock;
		
    	for (Mesh mesh : meshes) {
    		PhysicsEntity particle = new PhysicsEntity(PhysicsEntity.Type.MOB, entity.getType());
    		particle.feature = feature;
    		particle.noVolume = noVolume;
    		particle.models.get(0).textureID = textureID;
    		Mesh clone = new Mesh();
    		particle.models.get(0).mesh = clone;
    		particle.getTransformation().translation(px, py, pz);
    		particle.getOldTransformation().set(particle.getTransformation());
    		int count = 0;
    		Vector3f offset = new Vector3f();
    		
        	for (int i = 0; i < mesh.indices.size(); i++) {
        		int index = mesh.indices.getInt(i);
				byte sideIndex = mesh.sides.getByte(index);
				
        		Vector3f position = mesh.positions.get(index);
        		Vector2f uv = mesh.uvs.get(index);
        		Vector3f normal = mesh.normals.get(index);
        		
        		float r = red, g = green, b = blue;
				
				if (sideIndex == -1) {
	        		if (type == MobPhysicsType.FRACTURED_BLOOD) {
	        			r = 0.6f;
	        			g = 0.0f;
	        			b = 0.0f;
	        		}
	        		
					sideIndex = 0;
				}
				
				tmpNormal.set((mirror) ? (float) -normal.x : (float) normal.x, (float) normal.y, (float) normal.z);
				localNM.transform(tmpNormal);
		        
				org.joml.Vector4f minMaxUVs = minMax[remap[sideIndex]];
				
				boolean lmirror = (mirror && (sideIndex == 0 || sideIndex == 2)) || (!mirror && (sideIndex == 1 || sideIndex == 3));
				
				tmpPos.set(
						(float) net.diebuddies.math.Math.remap(position.x + mesh.offset.x, lmirror ? 0.5 : -0.5, lmirror ? -0.5 : 0.5, min.x, max.x),
						(float) net.diebuddies.math.Math.remap(position.y + mesh.offset.y, -0.5, 0.5, min.y, max.y),
						(float) net.diebuddies.math.Math.remap(position.z + mesh.offset.z, -0.5, 0.5, min.z, max.z), 1.0f);
				
				localM.transform(tmpPos);

				clone.indices.add(count);
				offset.add(tmpPos.x(), tmpPos.y(), tmpPos.z());
				count++;
				Vector3f posR = new Vector3f(tmpPos.x(), tmpPos.y(), tmpPos.z());
				clone.positions.add(posR);
				
				if (sideIndex == 4 || sideIndex == 5) {
					clone.uvs.add(new Vector2f(
							net.diebuddies.math.Math.remap((float) uv.x, 0.0f, 1.0f, minMaxUVs.x, minMaxUVs.y),
			    			net.diebuddies.math.Math.remap((float) uv.y, 0.0f, 1.0f, minMaxUVs.z, minMaxUVs.w)));
				} else if (sideIndex == 0 || sideIndex == 2) {
					clone.uvs.add(new Vector2f(
							net.diebuddies.math.Math.remap((float) uv.x, 0.0f, 1.0f, minMaxUVs.x, minMaxUVs.y),
			    			net.diebuddies.math.Math.remap((float) uv.y, 1.0f, 0.0f, minMaxUVs.z, minMaxUVs.w)));
				} else {
					clone.uvs.add(new Vector2f(
							net.diebuddies.math.Math.remap((float) uv.x, 1.0f, 0.0f, minMaxUVs.x, minMaxUVs.y),
			    			net.diebuddies.math.Math.remap((float) uv.y, 1.0f, 0.0f, minMaxUVs.z, minMaxUVs.w)));
				}
				
				clone.normals.add(new org.joml.Vector3f(tmpNormal.x(), tmpNormal.y(), tmpNormal.z()));
				clone.addColor(r, g, b);
        	}
    		
    		if (StarterClient.iris || StarterClient.optifabric) {
    			clone.calculatePBRData(false);
    		}

    		offset.div(clone.positions.size());
    		
    		for (Vector3f position : clone.positions) {
    			position.sub(offset);
    		}
    		
    		clone.offset = offset;

    		mod.blockifiedEntity.add(particle);
    	}
	}

}

And this gets called from this method during the offscreen rendering:

@Pseudo
@Mixin(GeoRenderer.class)
public interface MixinIGeoRenderer {

    @Inject(at = @At("HEAD"), method = "renderCube", remap = false)
    default void renderCube(PoseStack stack, GeoCube cube, VertexConsumer bufferIn, int packedLightIn,
            int packedOverlayIn, float red, float green, float blue, float alpha, CallbackInfo info) {
        if (PhysicsMod.getCurrentInstance() != null && PhysicsMod.getCurrentInstance().blockify) {
            GeckoLib.createParticlesFromCuboids(stack, cube,
                    PhysicsMod.getCurrentInstance().cubifyEntity, PhysicsMod.getCurrentInstance().cubifyEntityRenderer, PhysicsMod.getCurrentInstance().blockifyFeature,
                    packedOverlayIn, red, green, blue);
        }
    }

}

Geckolib doesn't support ragdolls since I never created a ragdoll hook for it but this is basically to have the limbs fall apart
And this is the offscreen rendering part:

EntityRenderer entityRenderer = Minecraft.getInstance().getEntityRenderDispatcher().getRenderer(entity);
DummyMultiBufferSource source = new DummyMultiBufferSource();

try {
    renderer.render(entity, 0.0f, Minecraft.getInstance().getFrameTime(), stack, source, 0);
} catch (Exception e) {
    System.err.println("error rendering " + entity.getClass());
    e.printStackTrace();
} finally {
    if (source.lastLayer != null) source.lastLayer.clearRenderState();
}