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.