Created
May 3, 2023 19:26
-
-
Save joreg/ed8a6fa871de5ab33b0734f9530f51e6 to your computer and use it in GitHub Desktop.
vvvv beta Decay node implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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