Arcturus Posted April 8 Report Posted April 8 Darkmod underutilizes, in my opinion, capabilities for procedural animation. We already do some, like the random head and torso turns that are added on top of existing animations. There's also possibility for separate animation for top and bottom of the character as well as head. There's a missing opportunity for varied facial expressions. I talked about animation blending in this thread. Here's something that for example Thief: Deadly Shadows did. It's swaying side to side while turning. I used Gemini to add code to the tdm_ai_base.script. I don't know if this is the most efficient way of doing it, probably not. Here are the parts added: Spoiler // Procedural Banking Variables (it's a term from car racing). float m_bankingLastYaw; // Stores the entity's yaw from the previous frame to calculate turn rate float m_bankingCurrentValue; // The smoothed banking offset currently being applied to joints float m_bankingJointHandle; // Primary joint handle float m_bankingJointHandle1; // Secondary joint handle float m_bankingJointHandle2; // Tertiary joint handle boolean m_bankingActive; // Control flag to manage the lifecycle of the banking thread void initBanking(); // Setup function to identify skeletal joints and initialize tracking variables void updateBankingLoop(); // Background thread that calculates turn deltas and applies banking offsets with LOD skipping Spoiler //Initializes the procedural banking system (it's a term from car racing). //Acquires skeletal joint handles and launches the background update thread. void ai_darkmod_base::initBanking() { // Acquire handles for the skeletal hierarchy [1] // m_bankingJointHandle is mapped to 'Origin' for full-body tilt m_bankingJointHandle = getJointHandle("Origin"); m_bankingJointHandle1 = getJointHandle("Spine_Dummy"); m_bankingJointHandle2 = getJointHandle("Neck"); // Fallback: If 'Spine_Dummy' is not found, attempt to use 'Spine' if (m_bankingJointHandle1 == -1) { m_bankingJointHandle1 = getJointHandle("Spine"); } // Only launch the thread if at least the primary joint is valid if (m_bankingJointHandle!= -1) { vector initialAngles = getAngles(); m_bankingLastYaw = initialAngles_y; // Initialize with current Yaw to prevent a delta-spike m_bankingCurrentValue = 0.0; m_bankingActive = true; // Launch banking logic as an independent thread thread updateBankingLoop(); } } Spoiler //Arcturus / Gemini - Main worker thread for procedural swaying (while turning). //Calculates angular delta, applies smoothing, and distributes rotation across the spine. void ai_darkmod_base::updateBankingLoop() { float currentYaw; float deltaYaw; float targetBanking; vector jointRotation; vector currentAngles; vector distVec; float dist; sys.waitFrame(); // Synchronize with the animation system's first frame [4] while (m_bankingActive && getHealth() > 0) { // --- LOD (Level of Detail) Check --- // Optimization: Disable processing if the AI is far from the player distVec = getOrigin() - $player1.getOrigin(); dist = sys.vecLength(distVec); // Fixed: Use sys.vecLength instead of.getLength() [1, 2] if (dist > 1500) { sys.wait(1.0); // Sleep for 1 second if distant to save CPU cycles // Re-sync Yaw after waking up to avoid a massive jump in deltaYaw currentAngles = getAngles(); m_bankingLastYaw = currentAngles_y; continue; } // --- Turning Logic --- currentAngles = getAngles(); currentYaw = currentAngles_y; // Access Yaw component via suffix [1, 5] // Shortest path normalization for the 360-degree heading wrap deltaYaw = m_bankingLastYaw - currentYaw; if (deltaYaw > 180) { deltaYaw -= 360; } else if (deltaYaw < -180) { deltaYaw += 360; } // targetBanking: Turn delta multiplied by intensity targetBanking = deltaYaw * 1.4; // Safety Clamp: Prevents excessive skeletal deformation [6] if (targetBanking > 20) targetBanking = 20; if (targetBanking < -20) targetBanking = -20; // Smoothing (EMA): Blend current value with target // Note: Sum of weights (0.96 + 0.04) should ideally be 1.0 for full reach m_bankingCurrentValue = (m_bankingCurrentValue * 0.95) + (targetBanking * 0.05); // --- Joint Transformation --- // Offset applied to X axis (Roll) for banking effect jointRotation_x = m_bankingCurrentValue; jointRotation_y = 0; jointRotation_z = 0; // Apply distributed rotation across the spine and neck in Mode 1 (JOINTMOD_LOCAL) [7] setJointAngle(m_bankingJointHandle, 1, jointRotation * 1); setJointAngle(m_bankingJointHandle1, 1, jointRotation * 0.6); setJointAngle(m_bankingJointHandle2, 1, jointRotation * -1.5); m_bankingLastYaw = currentYaw; sys.waitFrame(); // Synchronize with game frame rate [4, 8] } } Gemini came up with the term "banking" which is used in car racing. I don't know if that's what it's called in animation or games. There's a lot of variables that can be tweaked. You can rotate this way any bone you wish. I use 'Origin', 'Spine_Dummy' and 'Neck' bones: You can tweak amount of rotation for each bone. setJointAngle(m_bankingJointHandle, 1, jointRotation * 1); setJointAngle(m_bankingJointHandle1, 1, jointRotation * 0.6); setJointAngle(m_bankingJointHandle2, 1, jointRotation * -1.5); There's also global intensity: (targetBanking = deltaYaw * 1.4;) and attenuation: m_bankingCurrentValue = (m_bankingCurrentValue * 0.95) + (targetBanking * 0.05); modifiers. Gemini also proposed to add an LOD system, which I thought was a great idea. So currently at a distance of 1500 from player the swaying will be disabled. Here are examples, each with slightly different values: And here's comically exaggerated one: 4 Quote It's only a model...
wesp5 Posted April 9 Report Posted April 9 (edited) To be honest, this look unrealistic at the speed the NPCs are walking. It might make more sense for running... Edited April 9 by wesp5 Quote
Arcturus Posted April 9 Author Report Posted April 9 @wesp5 Does this look realistic? Quote It's only a model...
wesp5 Posted April 9 Report Posted April 9 This looks better, but maybe you could also post a video of the current implementation. 1 Quote
Arcturus Posted April 9 Author Report Posted April 9 @wesp5 Read the title of the video. And no, it doesn't look better. 1 Quote It's only a model...
Arcturus Posted April 9 Author Report Posted April 9 Here's more subtle version. Added slight arm movement outwards. Also added speed modifier, so the faster the NPC moves forward the stronger the tilt: Example of the speed modifier's effect: They will never be fully realistic, but at least we can make them less stiff. Quote It's only a model...
Taffingtaffer Posted April 9 Report Posted April 9 (edited) 3 hours ago, Arcturus said: Here's more subtle version. Added slight arm movement outwards. Also added speed modifier, so the faster the NPC moves forward the stronger the tilt: They will never be fully realistic, but at least we can make them less stiff. That test looks great! Not sure what "realistic" animations would add here (are we supposed to imitate mocapped animations by hand?); as long as they add convincing liveliness to these tree trunks acting as humanoids, that will do to spice up the stealth simulation. I hope something can be done to AI visual detection and barks when alerted, too. Edited April 9 by Taffingtaffer Quote
Arcturus Posted April 9 Author Report Posted April 9 @Taffingtaffer Thanks. There's still room for tweaking. I have some ideas for other uses of this method. I wonder how far can we push scripting. Quote It's only a model...
Arcturus Posted April 10 Author Report Posted April 10 I don't think we need to use spine. Rotation on the origin bone plus slight arms and head rotation is enough. Added a clamp on arms rotation. Attenuation is slightly faster. They return to straight pose a bit quicker. Should I make it stronger during walking? I guess because of the speed multiplier, they no longer sway while rotating in place, which I guess is ok. tdm_ai_base.script Quote It's only a model...
chumbucket91 Posted April 10 Report Posted April 10 Well Met! Unintentional comedy of the test videos aside, I support this idea in general - I think it does accomplish its stated goal of making the characters look a little less stiff. I think I prefer the earlier videos that apply the rotation at the spine? Applying the rotation at the origin seems a little bit off if you focus on their feet while they turn+move. Hard to articulate what I'm seeing, but it seems less natural or floatier somehow (to my admittedly untrained eye). Also, I think keeping the effect subtle is important here - having exxagerated movements fits better on cartoon-ier characters, whereas TDM's general house style leans more towards gritty realism. Quote
stgatilov Posted April 11 Report Posted April 11 Those AIs indeed love to generate lots of code... I believe this smoothing might be FPS-dependent: m_bankingCurrentValue = (m_bankingCurrentValue * 0.94) + (targetBanking * 0.06); If you set com_maxfps 10 or com_maxfps 300 without vsync, does it work the same? Interestingly, I looked in the source code, and I see that sys.waitFrame still waits 16 ms instead of one frame. And given that com_maxTicTimestep = 17, I guess you can´t get very different results here. Also, did you test it on non-human AIs? I guess this code applies to various spiders and elementals as well... Quote
datiswous Posted April 11 Report Posted April 11 On 4/8/2026 at 11:37 PM, Arcturus said: Gemini So you're using "ai" to improve ai. Quote
datiswous Posted April 12 Report Posted April 12 I don't think you turn your head while turning except when running really fast around a short corner. So all of these examples look unrealistic to me. Quote
Arcturus Posted April 12 Author Report Posted April 12 On 4/11/2026 at 4:35 PM, stgatilov said: If you set com_maxfps 10 or com_maxfps 300 without vsync, does it work the same? Gemini managed to come up with a solution that's frame rate independent. However, I discovered a bug in Darkmod (unless it's a known issue). With vertical sync off, uncap FPS on and high max FPS values (around 100 and above), the character animations get restarted (walking, running) when AI makes a turn. It looks bad and causes NPCs to stop a lot, too: Beyond that, some additional checks were added, whether AI is dormant and for speed. It's possible to limit which AIs use this script by adding "can_bank" "1" argument in the def files. It would be better to make separate script files for different types of creatures. A horse especially could use a custom one. On 4/11/2026 at 7:40 PM, datiswous said: So you're using "ai" to improve ai. Those are just visual changes. They don't affect the behavior of AI. Right now NPCs don't slow down at all when they make a turn, which makes them look like trains on tracks. Click on images to watch the videos: Quote It's only a model...
Arcturus Posted April 12 Author Report Posted April 12 3 hours ago, Arcturus said: With vertical sync off, uncap FPS on and high max FPS values (around 100 and above), the character animations get restarted (walking, running) when AI makes a turn. The bug is more than cosmetic. It affects pathfinding. I noticed in test/horse.map that a horse can't find path nodes with those settings. Turning v-sync on, or lowering FPS fixes it: Quote It's only a model...
Arcturus Posted April 12 Author Report Posted April 12 I animated spine in the other direction. 7 hours ago, datiswous said: I don't think you turn your head while turning What I mean is that the head bone needs to be animated too, in order to compensate for the body tilt. tdm_ai_base.script Quote It's only a model...
SeriousToni Posted April 13 Report Posted April 13 Anything subtle that improves the stiff animations is welcome IMHO. I think this is worth exploring and maybe implementing into the game. It doesn't look truely realistic but it's definitely better than what we have now for human NPCs. Also please make sure to report this bug in the bug tracker or it will get lost and forgotten in this thread. Quote "Einen giftigen Trank aus Kräutern und Wurzeln für die närrischen Städter wollen wir brauen." - Text aus einem verlassenen Heidenlager
Arcturus Posted April 14 Author Report Posted April 14 12 hours ago, SeriousToni said: Also please make sure to report this bug in the bug tracker or it will get lost and forgotten in this thread. Reported. 1 Quote It's only a model...
datiswous Posted April 14 Report Posted April 14 I wonder if it happens on all fps settings above 60fps. Uncapped now defaults to the highest setting of 300 (which I think is stupid), but what if it's set to 100fps for example? Quote
datiswous Posted April 14 Report Posted April 14 (edited) On 4/12/2026 at 11:17 PM, Arcturus said: This looks pretty good actually! I think maybe there should be set a lower maximum bend per step if possible. If you look close you see the guard on the right walk normaly but do a 180 degree turn in one step. Edited April 14 by datiswous Quote
Arcturus Posted April 14 Author Report Posted April 14 1 hour ago, datiswous said: Uncapped now defaults to the highest setting of 300 Seems to work: I have only 60hz monitor, so I can't really see those FPS above 60. 1 hour ago, datiswous said: I think maybe there should be set a lower maximum bend per step if possible. If you look close you see the guard on the right walk normaly but do a 180 degree turn in one step. Animations play in a loop and turning can happen at any point during that cycle, but the speed never changes. Origin bone moves at a constant rate forward. Tilting is modified by speed already, so walking animation has lower tilt and running has bigger. It's also proportional to the turning arc. What I've been thinking is to try to lower walking / running speed on turns. "anim_rate_run" is set to "0.8" in tdm_ai_humanoid.def which means that running animations are already slowed down. That would however further complicate the code, if it's even possible to do. Quote It's only a model...
datiswous Posted April 14 Report Posted April 14 (edited) 57 minutes ago, Arcturus said: Seems to work: I don't understand what you are trying to say here. Yes you can set the Max fps setting. I meant that when you install tdm, or a new version, it defaults to the 300fps setting, which is the highest nr for that setting. If you play the game on 300 fps the gpu will do that, the monitor just doesn't follow. I just wonder if this bug still happens if max fps is set to 100 instead of 300. Edited April 14 by datiswous Quote
Arcturus Posted April 14 Author Report Posted April 14 3 hours ago, datiswous said: I just wonder if this bug still happens if max fps is set to 100 instead of 300. Bug happens at any frame rate higher than 60. But it's more pronounced the higher it is. I just checked and in test/horse.map, the horse loses track at 240 FPS. 1 Quote It's only a model...
stgatilov Posted Saturday at 09:45 AM Report Posted Saturday at 09:45 AM On an unrelated note... Judging from tdm_ai_showfov, a horse sees to its right: I think it was not very important, because horses are rarely used and usually don´t react to player's presence in their FOV. But still it's strange 1 Quote
wesp5 Posted Saturday at 12:21 PM Report Posted Saturday at 12:21 PM (edited) 2 hours ago, stgatilov said: On an unrelated note... Judging from tdm_ai_showfov, a horse sees to its right: Funny :)! Can you fix that or is it something connected to the model itself? I believe there are several missions in which horses are set to make loud noises to attract guards, if they do see you which it seems is a 50% chance ;). Edited Saturday at 12:23 PM by wesp5 Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.