MD3 - improve animations support

Hello everyone,

I tried testing around in the 3D Model Viewer example program that comes up as an option on “New Project” with the latest as of this date (7.0) version of the engine, and more specifically I tried testing around how to make vertex animations in Castle Engine itself, given the limitation that the software I am using for the particular format (MD3 export from Mistfit Model 3D, to be specific) does not actually allow you to export .MD3 files with multiple animations, it will always condense it into one long one as soon as you save.

I initially tried displaying “SceneMain.Time” where the FPS is normally inside the TMain.Update procedure, and then using “SceneMain.SetTime” but that did not work because the time was wildly inaccurate, like it was displaying numbers in the millions (not a joke, the numbere was somewhat like 127812721.5) instead of correctly starting at 0 and counting the seconds from there.

I then tried what looked like literally the only option left for controlling the frames of a 3D animation manually, namely to use the TFramesPerSecond class and use it to display the current frame as time went on inside the label that normally displayed FPS inside TMain.Update procedure, then tell it to reset the seconds passed to 0 so that the display would go back to frame 0 after a specific number of frames.

The good news is that the animation detects the frames accurately just fine and resets properly to the first frame, but the bad news and why I am posting is that it stays stuck at the first frame because the script never resets to zero seconds passed, and just accumulates the frames indefinitely, thus causing the “frame number/ID > 6” condition to repeat in an infinite loop.

Do you guys know why, even though the if conditional works properly and the play animation specified in it plays flawlessly, the time is never reset? Am I simply not using that specific piece of code properly?

Full code for reference, what I would like investigated for my case in particular is lines 130-144, where all the update logic takes place, but I figure the full code can help show if there is something earlier or later in the code that is making the application behave improperly.

{ Main view, where most of the application logic takes place.

  Feel free to use this code as a starting point for your own projects.
  This template code is in public domain, unlike most other CGE code which
  is covered by BSD or LGPL (see https://castle-engine.io/license). }
unit GameViewMain;

interface

uses Classes,
  CastleVectors, CastleComponentSerialize, CastleUIControls,
  CastleControls, CastleKeysMouse, CastleViewport, CastleScene,
  CastleSceneCore, CastleCameras, X3DNodes, CastleColors,
  CastleNotifications, CastleTimeUtils;

type
  { Main view, where most of the application logic takes place. }
  TViewMain = class(TCastleView)
  published
    { Components designed using CGE editor.
      These fields will be automatically initialized at Start. }
    Viewport: TCastleViewport;
    SceneMain: TCastleScene;
    ButtonLoadKnight: TCastleButton;
    ButtonLoadCar: TCastleButton;
    ButtonLoadCustom: TCastleButton;
    ButtonPlayAnimation: TCastleButton;
    ButtonStopAnimation: TCastleButton;
    LabelLoadedUrl, LabelFps: TCastleLabel;
  private
    procedure Load(const Url: String);
    { Methods assigned to handle buttons' OnClick events. }
    procedure ClickLoadKnight(Sender: TObject);
    procedure ClickLoadCar(Sender: TObject);
    procedure ClickLoadCustom(Sender: TObject);
    procedure ClickPlayAnimation(Sender: TObject);
    procedure ClickStopAnimation(Sender: TObject);
  public
    constructor Create(AOwner: TComponent); override;
    procedure Start; override;
    procedure Update(const SecondsPassed: Single; var HandleInput: Boolean); override;
    function Press(const Event: TInputPressRelease): Boolean; override;
  end;

var
  ViewMain: TViewMain;

implementation

uses SysUtils,
  CastleWindow, X3DLoad;

{ TViewMain ----------------------------------------------------------------- }

constructor TViewMain.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gameviewmain.castle-user-interface';
end;

procedure TViewMain.Start;
begin
  inherited;

  { Assign OnClick handler to buttons }
  ButtonLoadKnight.OnClick := {$ifdef FPC}@{$endif} ClickLoadKnight;
  ButtonLoadCar.OnClick := {$ifdef FPC}@{$endif} ClickLoadCar;
  ButtonLoadCustom.OnClick := {$ifdef FPC}@{$endif} ClickLoadCustom;
  ButtonPlayAnimation.OnClick := {$ifdef FPC}@{$endif} ClickPlayAnimation;
  ButtonStopAnimation.OnClick := {$ifdef FPC}@{$endif} ClickStopAnimation;

  { Although knight.gltf is already loaded at design-time,
    load it again, to always initialize all UI (e.g. LabelLoadedUrl.Caption)
    using our Load method. }
  Load('castle-data:/knight/knight.gltf');
end;

procedure TViewMain.Load(const Url: String);
begin
  SceneMain.Load(Url);
  LabelLoadedUrl.Caption := 'Loaded: ' + Url;
  Viewport.AssignDefaultCamera;
  SceneMain.SetTime(1);
end;

procedure TViewMain.ClickLoadKnight(Sender: TObject);
begin
  { Note that you load here any filename or URL (file://, http:// etc.).
    - See https://castle-engine.io/manual_network.php about CGE supported URLs.
    - See https://castle-engine.io/manual_data_directory.php about the special
      URL protocol "castle-data:/" }
  Load('castle-data:/knight/knight.gltf');
end;

procedure TViewMain.ClickLoadCar(Sender: TObject);
begin
  Load('castle-data:/car/car.x3d');
end;

procedure TViewMain.ClickLoadCustom(Sender: TObject);
var
  Url: String;
begin
  Url := SceneMain.Url;
  if Application.MainWindow.FileDialog('Open Model', Url, true, LoadScene_FileFilters) then
    Load(Url);
end;

procedure TViewMain.ClickPlayAnimation(Sender: TObject);
var
  PlayAnimationParams: TPlayAnimationParameters;
begin
  { Play the 1st animation in the scene.
    In an actual game, you usually hardcode the animation name to play.
    In a full 3D model viewer, you can display all known animations,
    and allow user to start the chosen one. }
  if SceneMain.AnimationsList.Count > 0 then
    PlayAnimationParams := TPlayAnimationParameters.Create;
  try
    PlayAnimationParams.InitialTime := 3;
    SceneMain.PlayAnimation(PlayAnimationParams);
  finally FreeandNil(PlayAnimationParams) end;
end;

procedure TViewMain.ClickStopAnimation(Sender: TObject);
begin
  SceneMain.StopAnimation;
end;

procedure TViewMain.Update(const SecondsPassed: Single; var HandleInput: Boolean);
var
  FramesPerSec: TFramesPerSecond;
begin
  inherited;
  { This virtual method is executed every frame (many times per second). }
  FramesPerSec := TFramesPerSecond.Create;
  Assert(LabelFps <> nil, 'If you remove LabelFps from the design, remember to remove also the assignment "LabelFps.Caption := ..." from code');
  LabelFps.Caption := 'Current Frame: ' + FramesPerSec.FrameId.ToString;
  if FramesPerSec.FrameId > 6 then
  begin
  FramesPerSec.ZeroNextSecondsPassed;
  SceneMain.PlayAnimation('animation', true, true);
  end;
end;

function TViewMain.Press(const Event: TInputPressRelease): Boolean;
begin
  Result := inherited;
  if Result then Exit; // allow the ancestor to handle keys

  { This virtual method is executed when user presses
    a key, a mouse button, or touches a touch-screen.

    Note that each UI control has also events like OnPress and OnClick.
    These events can be used to handle the "press", if it should do something
    specific when used in that UI control.
    The TViewMain.Press method should be used to handle keys
    not handled in children controls.
  }

  // when pressing Home, reset the camera, to view complete model
  if Event.IsKey(keyHome) then
  begin
    Viewport.AssignDefaultCamera;
    Exit(true); // key was handled
  end;
end;

end.                                                       

First of all:

Check out new CGE example I just added to answer this question: examples/animations/split_long_md3_animation_into_logical_pieces/ :slight_smile: It is already pushed to GitHub repo, in https://github.com/castle-engine/castle-engine/tree/master/examples/animations/split_long_md3_animation_into_logical_pieces .

It shows how to play a subset of a larger animation. In particular, it is also a ready code to read animation.cfg files that may define subranges of animations (e.g. Tremulous MD3 creatures use such files).

Hopefully you can adapt this to your purposes :slight_smile: You can

  • use the underlying technique (play a subset of a larger animation using ForceAnimationPose)

  • or just take a bigger piece of this example and have ready handling of animation.cfg file. You can just copy the TSceneSubAnimations class implementation (unit GameSceneSubAnimations) and reuse for your purposes. If “Mistfit Model 3D” exports the animation.cfg alongside MD3, then you’re all set.

To address the questions in your post:

  1. The number in TCastleScene.Time is indeed big, because it is seconds since Unix epoch.

    I agree it is somewhat weird, but it is clearly documented ( Castle Game Engine: CastleSceneCore: Class TCastleSceneCore ) and I also wrote about it in VRML / X3D time origin considered uncomfortable | Castle Game Engine . Unfortunately changing it to start at 0 isn’t that simple due to how X3D animations expect the time to be provided. Maybe at some point we will change this (that is, make MyScene.Time start from saner 0), or just hide / make it internal.

    For now it actually isn’t that important because we have better ways to explicitly request specific time within an animation. You can ignore Scene.Time and Scene.SetTime. Instead use approaches I describe below.

  2. We have 2 ways to control time within animation easily:

    1. Scene.PlayAnimation with TPlayAnimationParameters.InitialTime to start playing the animation in the middle. No need to call this each frame, just call it and let animation finish (or stop animation with Scene.StopAnimation). I see you found this already.

    2. Or you can manually “drive” the animation using ForceAnimationPose (it has TimeInAnimation parameter to explicitly say which animation point to show).

  3. As for your code:

    • This is not how you should use TFramesPerSecond. You actually should never construct instance of this class yourself. Doing this makes little use (the FramesPerSec you created is not used to affect any animation) and you would need to call a number of internal methods of TFramesPerSecond to make it really work.

      The only instance of TFramesPerSecond that you should actually use is the instance already created for you in Container.Fps.

      I have actually modified CGE now to make this explicit. Creating TFramesPerSecond will raise clear exception with message “Do not create instance of TFramesPerSecond class directly, only use the already-created instance in TCastleContainer.Fps”.

      In your case, I think you don’t need to deal with TFramesPerSecond or Container.Fps at all to do what you describe. See my solution with ForceAnimationPose in the example.

    • Note that your TViewMain.ClickPlayAnimation has potentially crashing bug: the if clause only guards the line PlayAnimationParams := TPlayAnimationParameters.Create. The rest of the lines are always executed, regardless of SceneMain.AnimationsList.Count. So they change and then pass an undefined (not created) PlayAnimationParams pointer. You probably wanted to add begin..end block there, like

procedure TViewMain.ClickPlayAnimation(Sender: TObject);
var
  PlayAnimationParams: TPlayAnimationParameters;
begin
  { Play the 1st animation in the scene.
    In an actual game, you usually hardcode the animation name to play.
    In a full 3D model viewer, you can display all known animations,
    and allow user to start the chosen one. }
  if SceneMain.AnimationsList.Count > 0 then
  begin
    PlayAnimationParams := TPlayAnimationParameters.Create;
    try
      PlayAnimationParams.InitialTime := 3;
      SceneMain.PlayAnimation(PlayAnimationParams);
    finally FreeandNil(PlayAnimationParams) end;
  end;
end;

Here’s a movie of me playing with Tremulous MD3 creatures animations :slight_smile:

Hi, unfortunately GitHub is being weird with downloads, and I tried Googling “how to download from Github on Windows 10”, but the YouTube videos only gave me crap solutions like saving the files as links to get around the normal download limitations, which meant that the animation.cfg and .skin files got saved as HTML websites rather than the original, correct configuration that the preview shows on GitHub.

In particular about the weirdness, Github does not allow me to download the entire demo at once, like there is no option to download folders, but only painstakingly save one file at a time, which is okay if extremely annoying, because I have the patience for a tedious task if it means I get great results in the end,

But unfortunately, I am prevented from getting the results, because as mentioned above, the animation.cfg and.skin files don’t download normally at all, and the same is true for .JPG’s, but I can work around it using the Snipping Tool. On top of that, the .MD3’s download corrupted and refuse to open, even though I know the software (Misfit Model 3D) works fine for normal Quake 3 MD3’s.

Do you know if it would be possible to simply upload a .ZIP version of all of this, for easy download because of how Github doesn’t seem to be working properly for downloads as of right now?

Thank you for all the help with everything else though, like pointing out where the script is wrong in my example.

Okay, I think I figured out why it’s not working with the methods I got from professional articles like LifeHacker Tech Tips, it’s because it’s a blob/snippet instead of the entire package, so I think I would have to re-download the entire castle engine project if I wanted to download a ZIP or application right now? I guess that makes sense cause it’s the latest version, so I can do that as well.

Okay, as it turns out on the main page of the version of castle engine associated with this, I can only download the source code as a ZIP which I will have to compile manually, like there’s no EXE files to be found.

I did figure out however, that I can simply read the parts of the mainview pascal file that I need, and put it in my project where appropriate, as well as obviously include the new subanimations pascal file.

To get things from GitHub ( GitHub - castle-engine/castle-engine: Cross-platform (desktop, mobile, console) 3D and 2D game engine supporting many asset formats (glTF, X3D, Spine...) and using modern Object Pascal ),

  1. You can click “Download ZIP” on GitHub, the option is available once you click “Code”. See the screenshot.

  2. Or use a GIT client. There are lots of good options, working on Windows too, in particular GitHub (and me too :slight_smile: ) recommend https://desktop.github.com/ . It is easy to use, has all basic features and works flawlessly on Windows.

  3. You can also do nothing :), just wait a few hours (let’s say 8, to be safe) and everything I have pushed to GitHub should be part of the regular “Castle Game Engine downloads” on Download | Castle Game Engine . IOW, these downloads (right now) reflect the GitHub state automatically, with a ~day delay, after a number of automatic tests pass.

Indeed, if you will get code from GitHub (following AD 1 or AD 2 I mentioned above), you will have only source code, not binaries. Though it should be OK in this particular case (when you only want to get a new example), as you can just copy examples/animation/split_long_md3_animation_into_logical_pieces/ directory from that, and build it with older CGE binary.

If this sounds complicated, I advise to follow AD 3, just wait a few hours and then get latest engine from Download | Castle Game Engine .

You can recognize that Download | Castle Game Engine are good when the page Comparing snapshot...master · castle-engine/castle-engine · GitHub will NOT show any longer the commit “New demo split_long_md3_animation_into_logical_pieces…”. This will imply that this example is incorporated into downloads on Download | Castle Game Engine .

  1. I can also make it easier for you and just attach here the example you want :slight_smile: Here it is.

split_long_md3_animation_into_logical_pieces-0.1-src.zip (8.2 MB)

Thank you so much for all of this! It’s exactly the help I needed :slight_smile:

I also know how to compile a project with the Castle Engine EXE provided on the downloads page as mentioned in options 1 and 2, so that works as well. I know to open the CastleSettings.XML file in the project’s main folder, and then click “Compile” in Castle Game Engine once the file is opened.

To be precise, to open CGE project, you point CGE editor at CastleEngineManifest.xml (in the root of any CGE project). Not CastleSettings.xml :slight_smile:

Yeah, that is the name I actually used in the beginning, I think I just didn’t remember it properly cause I was typing by memory rather than actually looking up what I clicked on. I know obviously to double and triple check things usually, cause that’s very important in programming and naming files correctly for game engines.

Hi, when I started importing my own models into the example to test if it worked with them too, it did, so I tested some more by modifying the code to load multiple model files at the same time, rather than one by one like the original example, for one parent transform, as I want to make a multi-model character, and see if they could translate/move individually, and the result of that was 100% perfect success.

However, when I try to load multiple parts as normal scenes and comment out anything involving subanimations (my plan is to create a new character, so test static scenes first and then add subanimations after), like in my attachment, I get an error about “access violation in castleengine/src/castlescenecore.pas line 3783” (not exactly the correct directory, but you get the picture), referring to the castlescenecore.pas used for handling scene loading and control.

This is very bizarre and I think most likely an engine bug, because if it was problematic then the compiler should have told me and stopped beforehand like it does for every other problem, and on top of that it should not be a problem at all, because I am simply telling it to load two simple scenes without any animations in the modified code, and it has always worked flawlessly before.

I commented the subanimation code rather than deleted it, because as mentioned I want to add subanimations after I get the static model imported properly.

Note as well I removed the “vertical buttons” section, because A: it relied entirely on the subanimations existing to begin with, like it had “for each subanimation” etc, and B: the compiler was acting weirdly even then, telling me it couldn’t find an identifier “i” (where the i in quotes is the identifier name it wanted), on line (53, 4), when that same exact section of code compiled perfectly before, like I didn’t touch it one bit, and also nothing with “I” is mentioned in the example until between line 70 and 80, and even then it’s capital specifically, so I decided to just remove the code if the compiler was going to be weird about it.

Here is my version of your example with the modified code.

nonsenseviolation.zip (57.7 MB)

Okay, I think I figured out a little of the problem… I know Castle Engine specifically allocates specific functions to specific classes, and I can see in the manual “Load” is not part of a Scene class, but only a SceneCore class, so maybe it’s having a difficult time with that? I am pretty sure the compiler would throw an error before the app launches and crashes then though…

Actually, it’s even worse that way… I get the existing problem removed by commenting out the line on scene load, but a whole lot more bunch of exceptions that crash the program occur instead, like about the bounding box (which is really, really weird cause I never touched anything in the code about it), so maybe just re-do it from the ground up with your example code?

The reason I edited the code to begin with, though, was that the “PlaySubAnimation” line after you click the button was not allowing me to not play any animations, like it would tell me it couldn’t find the animation named “none” when I specified “none” as the animation parameter, instead of recgonizing “none” as a pramater meaning “don’t play any animations, because there are none right now.”

Do you know of the appropriate fix for this, or should I simply only use models with subanimations? It’s a little annoying because I have to make the subanimation and configuration file before testing if it actually imports the model and surface texture correctly in the engine, but I suppose a good workaround is to have a 2-frame subanimation with no actual changes to any model data, like an extremely cheap idle animation almost.

Nevermind, there was a way in the original example/code to load an MD3 file without playing any animations, it was simply loading the file without playing an animation right away, like I was trying to do in my above code to make it a player character who played an idle animation right away.

Okay, at this point, I think the access violation thing is most certainly a bug with Castle Engine itself, and not human programming error, because using the example you gave me gives access violation crashes when I click on the sub-animations to play them using your example models, like Maurader, Basilisk etc.

When it didn’t previously

Okay, it turns out even that was actually my programming error, because instead of trying to re-write multiple parts and have it display only one model period, I re-wrote it this time to display a multi-model animation seperately from the main animation script, but borrowing heavily from it (mostly copy paste), but then used the “PlayAnimation” function for both Multi and regular models instead of creating a seperate one for multi-models, causing it to crash because there was no second model scene in the original examples.