Skip to content

Instantly share code, notes, and snippets.

@joreg
Created May 3, 2023 19:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joreg/ed8a6fa871de5ab33b0734f9530f51e6 to your computer and use it in GitHub Desktop.
Save joreg/ed8a6fa871de5ab33b0734f9530f51e6 to your computer and use it in GitHub Desktop.
vvvv beta Decay node implementation
unit DecayNode;
interface
uses
Basics, Graph, Nodes, ValuePins, EnumPins, EnumTypes, AnimUtils;
type
TMTerrain = (
ctAscending,
ctValley,
ctDescending,
ctHill
);
TMDecayState = class(TMNodeState)
protected
CurrentValue : TMValue;
LastTime : TMTime;
public
constructor Create; override;
end;
TMDecayNode = class(TMNode)
private
FInput: TMValuePin;
FOutput: TMValuePin;
FAttack: TMValuePin;
FDecay: TMValuePin;
FTurningPoint: TMValuePin;
FTerrain: TMEnumPin;
function GetState(Index: Integer): TMDecayState;
procedure EvaluateCB(const Pin : TMPin; const FromSlice, ToSlice, Count:
integer);
protected
property State[Index: Integer]: TMDecayState read GetState;
public
constructor Create(Parent: TMNode; ID: Integer); override;
class function StateClass: TMNodeStateClass; override;
class function Category: string; override;
class function Info: TMNodeInfo; override;
property Input: TMValuePin read FInput;
property Output: TMValuePin read FOutput;
property Attack: TMValuePin read FAttack;
property Decay: TMValuePin read FDecay;
end;
implementation
uses
Clock, Factories, Math, ValueTypes;
var
GTMTerrain : TMEnumSubType;
constructor TMDecayNode.Create(Parent: TMNode; ID: Integer);
begin
inherited;
FInput := TMValuePin.Create(Self, 'Input', nil, cmpdInput, GValueTypeLib.GenericReal0, cmsmSpread);
FAttack := TMValuePin.Create(Self, 'Attack', nil, cmpdInput, GValueTypeLib.Time0, cmsmSpread);
FDecay := TMValuePin.Create(Self, 'Decay', nil, cmpdInput, GValueTypeLib.Time0, cmsmSpread);
FTurningPoint := TMValuePin.Create(Self, 'Turning Point', nil, cmpdInput, GValueTypeLib.GenericReal0, cmsmSpread);
FTerrain := TMEnumPin.Create(Self, 'Terrain', nil, cmpdInput, GTMTerrain, cmsmSpread);
FOutput := TMValuePin.Create(Self, 'Output', EvaluateCB, cmpdOutput, GValueTypeLib.GenericReal0, cmsmSpread);
RegisterPrepareGraph;
end;
procedure TMDecayNode.EvaluateCB(const Pin : TMPin; const FromSlice, ToSlice,
Count: integer);
var
i : integer;
dt: TMTime;
climb, decay: TMTime;
climbvel, decayvel: TMValue;
climbToGoal, decayToGoal: Boolean;
current: PMValue;
zero, goal: TMValue;
lasttime: TMTime;
s : TMDecayState;
// retrieving a rest time if goal was reached in this frame (else 0); moving state as a side effect
function step(goal: TMValue; velocity: TMTime; jumpToGoal: Boolean; deltatime: TMTime): TMTime;
var
last: TMValue;
owndeltatime: TMTime;
begin
if jumpToGoal then
begin
current^:= goal;
Result := deltatime;
end
else
try
owndeltatime := clip((goal - current^) / velocity, 0.0, deltatime);
current^ := current^ + owndeltatime * velocity;
Result := deltatime - owndeltatime;
except
Result := deltatime;
end;
end;
begin
// compare CurrentValue with input value
// if we are going up, increment the current value with
// (DeltaTime / Attack) and clamp it to the input value
// in case Attack is near zero (smaller than EPSILON_TIME)
// just set it to the input value
ValidateAllInputs(FromSlice, ToSlice);
for i := FromSlice to ToSlice do
try
try
goal := FInput[i];
s := State[i];
current := @s.CurrentValue;
lasttime := s.LastTime;
s.LastTime := GClock.Time;
dt := GClock.Time - lasttime;
climb := max(FAttack[i], 0);
climbvel := 1.0/max(climb, EPSILON_DEFAULT);
climbToGoal := climb < EPSILON_DEFAULT;
decay := max(FDecay[i], 0);
decayvel := 1.0/max(decay, EPSILON_DEFAULT);
decayToGoal := decay < EPSILON_DEFAULT;
if abs(goal - current^) > EPSILON_DEFAULT then
case TMTerrain(FTerrain[i]) of
ctAscending:
if goal > current^ then
step(goal, climbvel, climbToGoal, dt)
else
step(goal, -decayvel, decayToGoal, dt);
ctValley:
begin
zero := FTurningPoint[i];
if goal > current^ then
begin
if current^ < zero then
dt := step( min(zero, goal), decayvel, decayToGoal, dt);
if dt > EPSILON_DEFAULT then
step(goal, climbvel, climbToGoal, dt);
end
else
begin
if current^ > zero then
dt := step( max(zero, goal), -decayvel, decayToGoal, dt);
if dt > EPSILON_DEFAULT then
step(goal, -climbvel, climbToGoal, dt);
end;
end;
ctDescending:
if goal > current^ then
step(goal, decayvel, decayToGoal, dt)
else
step(goal, -climbvel, climbToGoal, dt);
ctHill:
begin
zero := FTurningPoint[i];
if goal > current^ then
begin
if current^ < zero then
dt := step( min(zero, goal), climbvel, climbToGoal, dt);
if dt > EPSILON_DEFAULT then
step(goal, decayvel, decayToGoal, dt);
end
else
begin
if current^ > zero then
dt := step( max(zero, goal), -climbvel, climbToGoal, dt);
if dt > EPSILON_DEFAULT then
step(goal, -decayvel, decayToGoal, dt);
end;
end;
end
else
current^ := FInput[i];
except
current^ := FInput[i];
end;
finally
FOutput[i] := current^;
end;
// try
// if FInput[i] > State[i].CurrentValue
// then
// if FAttack[i]>EPSILON_TIME
// then
// State[i].CurrentValue := min( State[i].CurrentValue + (GClock.DeltaTime / FAttack[i]), FInput[i])
// else
// State[i].CurrentValue := FInput[i];
//
// // if we are going down, do basically the same with signs changed
// if FInput[i] < State[i].CurrentValue
// then
// if FDecay[i]>EPSILON_TIME
// then
// State[i].CurrentValue := max( State[i].CurrentValue - (GClock.DeltaTime / FDecay[i]), FInput[i])
// else
// State[i].CurrentValue := FInput[i];
// finally
// FOutput[i] := State[i].CurrentValue;
// end;
// end;
FOutput.SetValidated(FromSlice, ToSlice);
end;
function TMDecayNode.GetState(Index: Integer): TMDecayState;
begin
result := TMDecayState(AbstractState[Index]);
end;
class function TMDecayNode.StateClass: TMNodeStateClass;
begin
result := TMDecayState;
end;
class function TMDecayNode.Category: string;
begin
result := 'Animation';
end;
class function TMDecayNode.Info: TMNodeInfo;
begin
Result := inherited Info;
Result.Name := 'Decay';
Result.Category := 'Animation';
Result.Version := '';
Result.Help := 'follows the input value with a constant speed';
end;
constructor TMDecayState.Create;
begin
CurrentValue := 0;
LastTime := GClock.Time;
end;
initialization
CreateAllGlobals;
GTMTerrain := TMEnumSubType.Create('Terrain');
GTMTerrain.Add(TypeInfo(TMTerrain));
GTMTerrain.DefaultIndex := ord(ctAscending);
GTMAllEnums.RegisterType(GTMTerrain);
GNodeFactory.RegisterNode(TMDecayNode);
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment