Skip to content

Commit

Permalink
chore: simplify skinned mesh component
Browse files Browse the repository at this point in the history
  • Loading branch information
pollend committed May 12, 2022
1 parent 50f619e commit 6b60fbe
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ protected MeshAnimation(ResourceUrn urn, AssetType<?, MeshAnimationData> assetTy
public abstract float getTimePerFrame();

public abstract AABBf getAabb();

public float getDuration() {
return getTimePerFrame() * (getFrameCount() - 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

package org.terasology.engine.rendering.logic;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.terasology.engine.entitySystem.Owns;
import org.terasology.engine.entitySystem.entity.EntityRef;
Expand All @@ -14,9 +14,7 @@
import org.terasology.engine.rendering.assets.mesh.SkinnedMesh;
import org.terasology.engine.world.block.ForceBlockActive;
import org.terasology.nui.Color;
import org.terasology.nui.properties.Range;

import java.util.List;
import java.util.Map;

@ForceBlockActive
Expand All @@ -27,55 +25,35 @@ public class SkinnedMeshComponent implements VisualComponent<SkinnedMeshComponen
@Replicate
public Material material;

/**
* Should not be set manually. Stores the data of the selected animation variation.
*/
@Replicate
public MeshAnimation animation;

/**
* If true, an animation from {@link #animationPool} will be played when the current animation is done.
*/
@Replicate
public boolean loop;

/**
* When the current animation is done and loop is true then a random animation will be picked from this pool of
* animations.
*/
@Replicate
public List<MeshAnimation> animationPool = Lists.newArrayList();

public float animationRate = 1.0f;
@Range(min = -2.5f, max = 2.5f)
public float heightOffset;
public float currentTime;

@Owns
public Map<String, EntityRef> boneEntities;
public EntityRef rootBone = EntityRef.NULL;

Check warning on line 35 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.rootBone is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
public float animationTime;

@Replicate

Check warning on line 37 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.localScale is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
public Vector3f scale = new Vector3f(1, 1, 1);
public float localScale = 1.0f;
@Replicate
public Vector3f localOffset = new Vector3f();
@Replicate
public Vector3f translate = new Vector3f();
public Quaternionf localRotation = new Quaternionf();

@Replicate

Check warning on line 44 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.color is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
public Color color = Color.WHITE;
public Color color = new Color(Color.white);

@Override
public void copyFrom(SkinnedMeshComponent other) {
this.mesh = other.mesh;

Check warning on line 49 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.mesh is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
this.material = other.material;

Check warning on line 50 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.material is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
this.animation = other.animation;

Check warning on line 51 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.animation is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
this.loop = other.loop;
this.animationPool = Lists.newArrayList(other.animationPool);
this.animationRate = other.animationRate;
this.boneEntities = Maps.newHashMap(other.boneEntities);

Check warning on line 52 in engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java

View check run for this annotation

Terasology Jenkins.io / SpotBugs

PA_PUBLIC_PRIMITIVE_ATTRIBUTE

LOW: Primitive field org.terasology.engine.rendering.logic.SkinnedMeshComponent.boneEntities is public and set from inside the class, which makes it too exposed. Consider making it private to limit external accessibility.
Raw output
no message found
this.rootBone = other.rootBone;
this.animationTime = other.animationTime;
this.scale = new Vector3f(other.scale);
this.translate = new Vector3f(other.translate);
this.localScale = other.localScale;
this.localOffset.set(other.localOffset);
this.localRotation.set(other.localRotation);
this.color = new Color(other.color);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import org.terasology.engine.entitySystem.entity.EntityManager;
import org.terasology.engine.entitySystem.entity.EntityRef;
import org.terasology.engine.entitySystem.entity.lifecycleEvents.OnActivatedComponent;
import org.terasology.engine.entitySystem.event.ReceiveEvent;
import org.terasology.engine.entitySystem.systems.BaseComponentSystem;
import org.terasology.engine.entitySystem.systems.RegisterMode;
import org.terasology.engine.entitySystem.systems.RegisterSystem;
Expand All @@ -37,12 +36,12 @@
import org.terasology.engine.rendering.world.WorldRenderer;
import org.terasology.engine.utilities.Assets;
import org.terasology.gestalt.assets.management.AssetManager;
import org.terasology.gestalt.entitysystem.event.ReceiveEvent;
import org.terasology.joml.geom.AABBf;
import org.terasology.joml.geom.AABBfc;
import org.terasology.nui.Color;

import java.nio.FloatBuffer;
import java.util.List;
import java.util.Random;

/**
Expand Down Expand Up @@ -130,94 +129,68 @@ private void updateSkeletalMeshOfEntity(EntityRef entity, float delta) {
return;
}

if (skeletalMeshComp.animation == null && skeletalMeshComp.animationPool != null) {
skeletalMeshComp.animation = randomAnimationData(skeletalMeshComp, random);
}

if (skeletalMeshComp.animation == null) {
return;
}

if (skeletalMeshComp.animation.getFrameCount() < 1) {
return;
}
skeletalMeshComp.animationTime += delta * skeletalMeshComp.animationRate;
float animationDuration = getDurationOfAnimation(skeletalMeshComp);
while (skeletalMeshComp.animationTime >= animationDuration) {
MeshAnimation newAnimation;
if (!skeletalMeshComp.loop) {
newAnimation = null;
} else if (skeletalMeshComp.animationPool != null && !skeletalMeshComp.animationPool.isEmpty()) {
newAnimation = randomAnimationData(skeletalMeshComp, random);
} else {
newAnimation = skeletalMeshComp.animation;
}

if (newAnimation == null) {
MeshAnimation finishedAnimation = skeletalMeshComp.animation;
skeletalMeshComp.animationTime = animationDuration;
MeshAnimationFrame frame = skeletalMeshComp.animation.getFrame(skeletalMeshComp.animation.getFrameCount() - 1);
updateSkeleton(skeletalMeshComp, frame, frame, 1.0f);
// Set animation to null so that AnimEndEvent fires only once
skeletalMeshComp.animation = null;
entity.saveComponent(skeletalMeshComp);
entity.send(new AnimEndEvent(finishedAnimation));
return;
if (skeletalMeshComp.rootBone != null) {
LocationComponent locationComponent = skeletalMeshComp.rootBone.getComponent(LocationComponent.class);
if (locationComponent != null) {
locationComponent.setLocalPosition(skeletalMeshComp.localOffset);
locationComponent.setLocalScale(skeletalMeshComp.localScale);
locationComponent.setLocalRotation(skeletalMeshComp.localRotation);
}
skeletalMeshComp.animationTime -= animationDuration;
if (skeletalMeshComp.animationTime < 0) {
// In case the float calculation wasn't exact:
skeletalMeshComp.animationTime = 0;
}

float animationDuration = skeletalMeshComp.animation.getDuration();
if (skeletalMeshComp.currentTime >= animationDuration) {
skeletalMeshComp.currentTime -= animationDuration;
if (skeletalMeshComp.currentTime < 0) {
skeletalMeshComp.currentTime = 0;
}
skeletalMeshComp.animation = newAnimation;
animationDuration = getDurationOfAnimation(skeletalMeshComp);
}
float framePos = skeletalMeshComp.animationTime / skeletalMeshComp.animation.getTimePerFrame();
int frameAId = (int) framePos;
int frameBId = frameAId + 1;
if (frameBId >= skeletalMeshComp.animation.getFrameCount()) {
// In case the float calcuation wasn't exact:
frameBId = skeletalMeshComp.animation.getFrameCount() - 1;

float framePos = skeletalMeshComp.currentTime / skeletalMeshComp.animation.getTimePerFrame();
int currentFrame = (int) framePos;
int nextFrame = currentFrame + 1;
if (nextFrame >= skeletalMeshComp.animation.getFrameCount()) {
nextFrame = 0;
}
MeshAnimationFrame frameA = skeletalMeshComp.animation.getFrame(frameAId);
MeshAnimationFrame frameB = skeletalMeshComp.animation.getFrame(frameBId);
updateSkeleton(skeletalMeshComp, frameA, frameB, framePos - frameAId);
float frameDelta = framePos - currentFrame;
MeshAnimationFrame animatedFrame1 = skeletalMeshComp.animation.getFrame(currentFrame);
MeshAnimationFrame animatedFrame2 = skeletalMeshComp.animation.getFrame(nextFrame);
updateFrame(skeletalMeshComp, animatedFrame1, animatedFrame2, frameDelta);
entity.saveComponent(skeletalMeshComp);
}

private float getDurationOfAnimation(SkinnedMeshComponent skeletalMeshComp) {
return skeletalMeshComp.animation.getTimePerFrame() * (skeletalMeshComp.animation.getFrameCount() - 1);
}
private void updateFrame(SkinnedMeshComponent skeletalMeshComp, MeshAnimationFrame frameA, MeshAnimationFrame frameB,
float interpolationVal) {

private static MeshAnimation randomAnimationData(SkinnedMeshComponent skeletalMeshComp, Random random) {
List<MeshAnimation> animationPool = skeletalMeshComp.animationPool;
if (animationPool == null) {
return null;
}
if (animationPool.isEmpty()) {
return null;
}
return animationPool.get(random.nextInt(animationPool.size()));
}

private void updateSkeleton(SkinnedMeshComponent skeletalMeshComp, MeshAnimationFrame frameA, MeshAnimationFrame frameB,
float interpolationVal) {
for (int i = 0; i < skeletalMeshComp.animation.getBoneCount(); ++i) {
String boneName = skeletalMeshComp.animation.getBoneName(i);
EntityRef boneEntity = skeletalMeshComp.boneEntities.get(boneName);
if (boneEntity == null) {
continue;
}

LocationComponent boneLoc = boneEntity.getComponent(LocationComponent.class);
if (boneLoc != null) {
Vector3f newPos = frameA.getPosition(i).lerp(frameB.getPosition(i), interpolationVal, new Vector3f());
boneLoc.setLocalPosition(newPos);
Quaternionf newRot = frameA.getRotation(i).slerp(frameB.getRotation(i), interpolationVal, new Quaternionf());
newRot.normalize();
boneLoc.setLocalRotation(newRot);
boneLoc.setLocalScale(frameA.getBoneScale(i).lerp(frameB.getBoneScale(i), interpolationVal, new Vector3f()).x);
boneEntity.saveComponent(boneLoc);
if (boneLoc == null) {
continue;
}

Vector3f newPos = frameA.getPosition(i).lerp(frameB.getPosition(i), interpolationVal, new Vector3f());
boneLoc.setLocalPosition(newPos);
Quaternionf newRot = frameA.getRotation(i).slerp(frameB.getRotation(i), interpolationVal, new Quaternionf());
newRot.normalize();
boneLoc.setLocalRotation(newRot);
boneLoc.setLocalScale(frameA.getBoneScale(i).lerp(frameB.getBoneScale(i), interpolationVal, new Vector3f()).x);
boneEntity.saveComponent(boneLoc);

}
}

Expand Down Expand Up @@ -257,10 +230,10 @@ public void renderOpaque() {
aabb = aabb.transform(new Matrix4f().translationRotateScale(worldPos, worldRot, worldScale), new AABBf());

//Scale bounding box for skeletalMesh.
Vector3f scale = skeletalMesh.scale;
float scale = skeletalMesh.localScale;

Vector3f aabbCenter = aabb.center(new Vector3f());
Vector3f scaledExtents = aabb.extent(new Vector3f()).mul(scale.x(), scale.y(), scale.z());
Vector3f scaledExtents = aabb.extent(new Vector3f()).mul(scale, scale, scale);
aabb = new AABBf(aabbCenter, aabbCenter).expand(scaledExtents);

if (!worldRenderer.getActiveCamera().hasInSight(aabb)) {
Expand All @@ -279,7 +252,6 @@ public void renderOpaque() {
Vector3f worldPositionCameraSpace = new Vector3f();
worldPos.sub(cameraPosition, worldPositionCameraSpace);

worldPositionCameraSpace.y += skeletalMesh.heightOffset;
Matrix4f matrixCameraSpace = new Matrix4f().translationRotateScale(worldPositionCameraSpace, worldRot, worldScale);

Matrix4f modelViewMatrix = worldRenderer.getActiveCamera().getViewMatrix().mul(matrixCameraSpace, new Matrix4f());
Expand All @@ -292,25 +264,21 @@ public void renderOpaque() {
skeletalMesh.material.setFloat("sunlight", worldRenderer.getMainLightIntensityAt(worldPos), true);
skeletalMesh.material.setFloat("blockLight", worldRenderer.getBlockLightIntensityAt(worldPos), true);

Matrix4f[] boneTransforms = new Matrix4f[skeletalMesh.mesh.bones().size()];
Matrix4f boneTransform = new Matrix4f();
for (Bone bone : skeletalMesh.mesh.bones()) {
EntityRef boneEntity = skeletalMesh.boneEntities.get(bone.getName());
if (boneEntity == null) {
boneEntity = EntityRef.NULL;
}
LocationComponent boneLocation = boneEntity.getComponent(LocationComponent.class);
boneTransform.identity();
if (boneLocation != null) {
Matrix4f boneTransform = new Matrix4f();
boneLocation.getRelativeTransform(boneTransform, entity);
boneTransform.mul(bone.getInverseBindMatrix());
boneTransforms[bone.getIndex()] = boneTransform;
} else {
logger.warn("Unable to resolve bone \"{}\"", bone.getName());
boneTransforms[bone.getIndex()] = new Matrix4f();
}
}
for (int i = 0; i < boneTransforms.length; i++) {
skeletalMesh.material.setMatrix4("boneTransforms[" + i + "]", boneTransforms[i], true);
skeletalMesh.material.setMatrix4("boneTransforms[" + bone.getIndex() + "]", boneTransform, true);
}
skeletalMesh.mesh.render();
}
Expand All @@ -327,11 +295,7 @@ public void renderOverlay() {

Vector3f cameraPosition = worldRenderer.getActiveCamera().getPosition();

Matrix4f relMat = new Matrix4f();
Matrix4f relFinal = new Matrix4f();
Matrix4f entityTransform = new Matrix4f();

Matrix4f result = new Matrix4f();
Vector3f currentPos = new Vector3f();

int index = 0;
Expand All @@ -349,10 +313,6 @@ public void renderOverlay() {
// position is referenced around (0,0,0) (worldposition - cameraposition)
Vector3f worldPositionCameraSpace = cameraPosition.negate(new Vector3f());

// same heightOffset is applied to worldPositionCameraSpace from #renderOpaque()
// TODO: resolve repeated logic for transformation applied to bones
worldPositionCameraSpace.y += skeletalMesh.heightOffset;

Matrix4f matrixCameraSpace = new Matrix4f().translationRotateScale(worldPositionCameraSpace, new Quaternionf(), 1.0f);
Matrix4f modelViewMatrix = new Matrix4f(worldRenderer.getActiveCamera().getViewMatrix()).mul(matrixCameraSpace);
material.setMatrix4("projectionMatrix", worldRenderer.getActiveCamera().getProjectionMatrix());
Expand All @@ -370,27 +330,10 @@ public void renderOverlay() {
LocationComponent locCompA = boneEntity.getComponent(LocationComponent.class);
LocationComponent locCompB = boneParentEntity.getComponent(LocationComponent.class);

// need to calculate the relative transformation from the entity to the start of the bone
locCompA.getRelativeTransform(relMat.identity(), entity);
// entityTransform * (scale, translation) * relativeMat * [x,y,z,1]
result.set(entityTransform)
.mul(relFinal.identity()
.scale(skeletalMesh.scale)
.translate(skeletalMesh.translate)
.mul(relMat))
.transformPosition(currentPos.zero()); // get the position of the start of the bone
locCompA.getWorldPosition(currentPos);
meshData.position.put(currentPos); // the start of the bone

// need to calculate the relative transformation from the entity to the connecting bone
locCompB.getRelativeTransform(relMat.identity(), entity);
// entityTransform * (scale, translation) * relativeMat * [x,y,z,1]
result.set(entityTransform)
.mul(relFinal
.identity()
.scale(skeletalMesh.scale)
.translate(skeletalMesh.translate)
.mul(relMat))
.transformPosition(currentPos.zero()); // get the position to the connecting bone
locCompB.getWorldPosition(currentPos);
meshData.position.put(currentPos); // the end of the bone

meshData.color0.put(Color.white);
Expand Down

0 comments on commit 6b60fbe

Please sign in to comment.