Skip to content

Instantly share code, notes, and snippets.

@w0wca7a
Last active April 2, 2025 05:28
Show Gist options
  • Select an option

  • Save w0wca7a/adfab40a37a89c9082b87162203a6b86 to your computer and use it in GitHub Desktop.

Select an option

Save w0wca7a/adfab40a37a89c9082b87162203a6b86 to your computer and use it in GitHub Desktop.
Model rotates head towards camera. Basic component realization for Stride game engine. Needs additional rework.
// Copyright (c) Stride contributors (https://stride3d.net).
// Distributed under the MIT license.
// This component must be attached to Entity with Skeleton (Model component with skeleton)
using Stride.Core;
using Stride.Engine;
using Stride.Core.Mathematics;
using Stride.Rendering;
using Stride.Core.Annotations;
namespace Additionals.Scripts.Animations
{
[DataContract(nameof(FollowHeadRotation))]
[ComponentCategory("Animations")]
[Display("Rotate head towards camera")]
public class FollowHeadRotationComponent : SyncScript
{
[DataMember]
[Display(1)]
public Entity EntityToFollow { get; set; }
[DataMember]
[Display(2)]
public string NeckBoneName { get; set; } = "CC_Base_NeckTwist02"; //"CC_Base_NeckTwist02"; // CATRigSpine3294 for default Stride model
[DataMember]
[Display(3)]
[DataMemberRange(1, 40, 1, 5, 1)]
public float ActivateDistance { get; set; }
[DataMember]
[Display(4)]
public float RotationSpeed { get; set; } = 2;
private static Quaternion originalRotation;
private static ModelNodeTransformation[] nodeTransformation;
private static SkeletonUpdater skeletonUpdater;
private static int? NeckBoneIndex; // 47 for Stride default model
public override void Start()
{
base.Start();
skeletonUpdater = Entity.Get<ModelComponent>().Skeleton;
if (skeletonUpdater == null) return;
for (int i = 0; i < skeletonUpdater.Nodes.Length; i++)
{ if (skeletonUpdater.Nodes[i].Name == NeckBoneName)
{
NeckBoneIndex = i;
}
}
// Bind entity skeleton pose with ModelNodeTransformation
nodeTransformation = skeletonUpdater.NodeTransformations;
// Save original bone Rotation
originalRotation = nodeTransformation[(int)NeckBoneIndex].Transform.Rotation;
}
// I would like to add a rotation and tilt limitation and correction.
// And also check the added animation component and overwrite it.
public override void Update()
{
if (NeckBoneIndex == null) return;
// Get positions
var entityPosition = Entity.Transform.WorldMatrix.TranslationVector;
var entityToFollow = EntityToFollow.Transform.WorldMatrix.TranslationVector;
// Get current bone rotation
var currentBoneRotation = nodeTransformation[(int)NeckBoneIndex].Transform.Rotation;
//nodeTransformation[(int)NeckBoneIndex].Transform.Rotation = targetRotation;
var distance = Vector3.Distance(entityPosition, entityToFollow);
if (distance > ActivateDistance & currentBoneRotation == originalRotation) return;
DebugText.Print("Distance beetween: " + distance, new Int2(40, 120));
if(distance < ActivateDistance)
{
// Calculate direction and normalize and then rotate bone
var direction = entityToFollow - entityPosition;
direction.Normalize();
var targetRotation = Quaternion.LookRotation(direction, Vector3.UnitY);
// This may be needed if source skeleton coordinate system is not equal to Stride
// targetRotation *= Quaternion.RotationY(MathUtil.DegreesToRadians(-90));
var speedFactor = RotationSpeed * (float)Game.UpdateTime.Elapsed.TotalSeconds;
Quaternion.Slerp(ref currentBoneRotation, ref targetRotation, speedFactor, out var newRotation);
nodeTransformation[(int)NeckBoneIndex].Transform.Rotation = newRotation;
return;
}
if (currentBoneRotation != originalRotation)
{
// Turn back to original
var factor = RotationSpeed * (float)Game.UpdateTime.Elapsed.TotalSeconds;
Quaternion.Slerp(ref currentBoneRotation, ref originalRotation, factor, out var newRotation);
nodeTransformation[(int)NeckBoneIndex].Transform.Rotation = newRotation;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment