diff --git a/engine/src/main/java/org/terasology/engine/rendering/assets/animation/MeshAnimation.java b/engine/src/main/java/org/terasology/engine/rendering/assets/animation/MeshAnimation.java index 1dc7757d05d..67407ec01b9 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/assets/animation/MeshAnimation.java +++ b/engine/src/main/java/org/terasology/engine/rendering/assets/animation/MeshAnimation.java @@ -28,4 +28,8 @@ protected MeshAnimation(ResourceUrn urn, AssetType assetTy public abstract float getTimePerFrame(); public abstract AABBf getAabb(); + + public float getDuration() { + return getTimePerFrame() * (getFrameCount() - 1); + } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java b/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java index 74daaf84d0a..84c5093b32b 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java +++ b/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshComponent.java @@ -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; @@ -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 @@ -27,55 +25,35 @@ public class SkinnedMeshComponent implements VisualComponent animationPool = Lists.newArrayList(); - - public float animationRate = 1.0f; - @Range(min = -2.5f, max = 2.5f) - public float heightOffset; + public float currentTime; @Owns public Map boneEntities; public EntityRef rootBone = EntityRef.NULL; - public float animationTime; @Replicate - 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 - public Color color = Color.WHITE; + public Color color = new Color(Color.white); @Override public void copyFrom(SkinnedMeshComponent other) { this.mesh = other.mesh; this.material = other.material; this.animation = other.animation; - this.loop = other.loop; - this.animationPool = Lists.newArrayList(other.animationPool); - this.animationRate = other.animationRate; this.boneEntities = Maps.newHashMap(other.boneEntities); 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); } } diff --git a/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshRenderer.java b/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshRenderer.java index 63285407bcf..e9f317cbb71 100644 --- a/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshRenderer.java +++ b/engine/src/main/java/org/terasology/engine/rendering/logic/SkinnedMeshRenderer.java @@ -42,7 +42,6 @@ import org.terasology.nui.Color; import java.nio.FloatBuffer; -import java.util.List; import java.util.Random; /** @@ -130,10 +129,6 @@ 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; } @@ -141,83 +136,61 @@ private void updateSkeletalMeshOfEntity(EntityRef entity, float delta) { 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 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); + } } @@ -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)) { @@ -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()); @@ -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(); } @@ -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; @@ -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()); @@ -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);