*/}}
Pārlūkot izejas kodu

Animation playhead and delta transformation basics

YimingWu 1 gadu atpakaļ
vecāks
revīzija
5229c31d66

+ 53 - 4
la_animation.c

@@ -22,7 +22,7 @@ extern LA MAIN;
 
 int la_GetKeyablePropertyStorageSize(laProp* p);
 
-laAction* laNewAction(char* Name){
+laAction* laAnimiationNewAction(char* Name){
     laAction* aa=memAcquire(sizeof(laAction));
     if(!Name || !Name[0]){ Name="New Action"; }
     strSafeSet(&aa->Name,Name);
@@ -74,13 +74,62 @@ laAnimationKey* laAnimationInsertKeyFrame(laAction* aa, void* hyper1, laProp* p)
     laAnimationStoreKeyValue(ac,ak);
 }
 
-int OPINV_NewAction(laOperator *a, laEvent *e){
-    laNewAction(0);
+
+void laAnimationSetPlayStatus(int PlayStatus){
+    if(PlayStatus>3||PlayStatus<0) PlayStatus=0; MAIN.AnimationPlayStatus=PlayStatus;
+    if(PlayStatus) laRecordTime(&MAIN.AnimationTimeOrigin);
+    laNotifyUsers("la.animation_play_status");
+}
+void laAnimationSetPlayHead(real time){
+    MAIN.AnimationPlayHead=time;
+}
+
+
+int OPINV_AnimationNewAction(laOperator *a, laEvent *e){
+    laAnimiationNewAction(0);
+    return LA_FINISHED;
+}
+int OPINV_AnimationPlayAction(laOperator *a, laEvent *e){
+    char* str=strGetArgumentString(a->ExtraInstructionsP, "mode");
+    int PlayStatus=LA_ANIMATION_STATUS_PAUSED;
+    if(strSame(str,"forward")){ PlayStatus=LA_ANIMATION_STATUS_PLAY_FWD; }
+    elif(strSame(str,"reverse")){ if(MAIN.AnimationPlayHead>0) PlayStatus=LA_ANIMATION_STATUS_PLAY_REV; }
+    laAnimationSetPlayStatus(PlayStatus);
+    return LA_FINISHED;
+}
+int OPINV_AnimationResetTime(laOperator *a, laEvent *e){
+    laAnimationSetPlayHead(0);
     return LA_FINISHED;
 }
 
+void la_AnimationEvaluateActions(){
+    /* ehh well */
+}
+
+void la_AnimationPreFrame(){
+    if(MAIN.AnimationPlayHead<0){
+        MAIN.AnimationPlayHead=0; laAnimationSetPlayStatus(0);
+        laNotifyUsers("la.animation_play_head");
+    }
+    if(MAIN.AnimationPlayStatus){
+        la_AnimationEvaluateActions();
+    }
+}
+void la_AnimationPostFrame(){
+    laTimeRecorder cur={0};
+    if(MAIN.AnimationPlayStatus){ laRecordTime(&cur);
+        real td=laTimeElapsedSecondsf(&cur,&MAIN.AnimationTimeOrigin);
+        laRecordTime(&MAIN.AnimationTimeOrigin);
+        if(MAIN.AnimationPlayStatus==LA_ANIMATION_STATUS_PLAY_REV){ td*=-1; }
+        MAIN.AnimationPlayHead+=td; laNotifyUsers("la.animation_play_head");
+    }
+}
+
+
 void la_RegisterAnimationResources(){
     laPropContainer *pc; laProp *p; laOperatorType *at; laEnumProp *ep;
-    laCreateOperatorType("LA_new_action", "New Action", "Add a new action",0,0,0,OPINV_NewAction,0,0,0);
+    laCreateOperatorType("LA_animation_new_action", "New Action", "Add a new action",0,0,0,OPINV_AnimationNewAction,0,0,0);
+    laCreateOperatorType("LA_animation_set_play_status", "Set Play", "Set global animation player status",0,0,0,OPINV_AnimationPlayAction,0,0,0);
+    laCreateOperatorType("LA_animation_reset_time", "Reset Time", "Reset Time",0,0,0,OPINV_AnimationResetTime,0,U'🡄',0);
 }
 

+ 19 - 0
la_data.h

@@ -987,3 +987,22 @@ void laSetDiffCallback(laDiffPushEverythingF PushEverything);
 
 void laUndo();
 void laRedo();
+
+//======================= animation
+
+#define LA_ANIMATION_STATUS_PAUSED   0
+#define LA_ANIMATION_STATUS_PLAY_FWD 1
+#define LA_ANIMATION_STATUS_PLAY_REV 2
+
+laAction* laAnimiationNewAction(char* Name);
+laAnimationChannel* laAnimationEnsureChannel(laAction* aa, void* hyper1, laProp* p);
+laAnimationChannel* laAnimationEnsureFrame(laAnimationChannel* ac, int frame);
+void laAnimationStoreKeyValue(laAnimationChannel* ac, laAnimationKey* ak);
+laAnimationKey* laAnimationInsertKeyFrame(laAction* aa, void* hyper1, laProp* p);
+
+void laAnimationSetPlayStatus(int PlayStatus);
+void laAnimationSetPlayHead(real time);
+
+void la_AnimationPreFrame();
+void la_AnimationPostFrame();
+

+ 5 - 17
la_interface.h

@@ -220,21 +220,6 @@ STRUCTURE(laSharedTypeItem){
     void *Pointer;
 };
 
-#define LA_ANIMATION_STOPPED 0
-#define LA_ANIMATION_PLAYING 1
-#define LA_ANIMATION_PLAYING_REVERSE 2
-
-STRUCTURE(laAnimationGlobal){
-    laListItem Item;
-
-    int PlayStatus;
-
-    int Frame;
-    int FrameBegin, FrameEnd;
-    int FrameStep;
-    int FrameRate;
-};
-
 NEED_STRUCTURE(laTranslation);
 NEED_STRUCTURE(laTheme);
 
@@ -413,8 +398,6 @@ STRUCTURE(LA){
     laPanel *PropMatcherContextP;
     laPanel* PendingSplash;
 
-    laAnimationGlobal Animation;
-
     laTheme*     CurrentTheme;
     laListHandle Themes;
     laListHandle UiTypes;
@@ -469,6 +452,9 @@ STRUCTURE(LA){
     laDiffPushEverythingF PushEverything;
 
     laListHandle Actions;
+    real AnimationPlayHead;
+    int AnimationPlayStatus;
+    laTimeRecorder AnimationTimeOrigin;
 
     real FontSize;
     real MarginSize;
@@ -934,6 +920,7 @@ STRUCTURE(laCanvasExtra){
     int SelectMode;
     int SelectThrough;
     int EditCenter;
+    int DeltaMode;
     real BL, BR, BU, BB;
 
     int Dragging;
@@ -1946,6 +1933,7 @@ laUiItem *laShowNodeSocket(laUiList *uil, laColumn *c, laPropPack *Base, const c
 laUiItem *laShowHeightAdjuster(laUiList *uil, laColumn *c, laPropPack *Base, const char *Path, char* instructions);
 laUiItem *laShowDetachedItem(laPanel *p, laUiList *uil, laColumn *c, laPropPack *Base, const char *Path, const char *Rename, laUiDefineFunc Template, laWidget* Widget);
 laUiItem *laShowCanvas(laUiList *uil, laColumn *c, laPropPack *Base, const char *Path, const char *id2DTemplate, int Height);
+laUiItem *laShow3DCanvasCombo(laUiList *uil, laColumn *c, laPropPack *Base, const char *Path, int Height);
 laUiItem *laShow2DContainerItem(laUiList *uil, laColumn *c, laPropPack *Base, const char *Path, int Height);
 void laDefault3DViewOverlay(laUiItem *ui);
 void laDefault2DViewOverlayRight(laUiItem *ui);

+ 16 - 8
la_kernel.c

@@ -866,13 +866,6 @@ int laGetReadyWith(int GLMajor, int GLMinor, int BufferSamples){
     MAIN.WireThickness = 5;
     MAIN.WireSaggyness = 5;
 
-    //timeline:
-    MAIN.Animation.FrameBegin = 1;
-    MAIN.Animation.FrameEnd = 250;
-    MAIN.Animation.Frame = 1;
-    MAIN.Animation.FrameRate = 25;
-    MAIN.Animation.FrameStep = 1;
-
     laAddResourceFolder(".");
 
     laSetMenuBarTemplates(laui_DefaultMenuButtons, laui_DefaultMenuExtras, "🧩LaGUI 2022");
@@ -3785,6 +3778,17 @@ laUiItem *laShowCanvas(laUiList *uil, laColumn *c, laPropPack *Base, const char
 
     return ui;
 }
+laUiItem *laShow3DCanvasCombo(laUiList *uil, laColumn *c, laPropPack *Base, const char *Path, int Height){
+    laUiItem* ui=laShowCanvas(uil,c,Base,Path,0,Height-1);
+    laDefault3DViewOverlay(ui);
+
+    laUiItem* b=laBeginRow(uil,c,0,0);
+    laShowItem(uil,c,&ui->ExtraPP,"select_mode")->Flags|=LA_UI_FLAGS_EXPAND;
+    laShowItem(uil,c,&ui->ExtraPP,"select_through");
+    laShowSeparator(uil,c)->Expand=1;
+    laShowItem(uil,c,&ui->ExtraPP,"delta_mode")->Flags|=LA_UI_FLAGS_CYCLE|LA_UI_FLAGS_HIGHLIGHT;
+    laEndRow(uil,b);
+}
 laUiItem *laShowColumnAdjuster(laUiList *uil, laColumn *c){
     laUiItem *ui = memAcquireSimple(sizeof(laUiItem));
     laCanvasExtra *e;
@@ -7158,6 +7162,8 @@ void laMainLoop(){
 
         if(MAIN.PreFrame){ MAIN.PreFrame(); }
 
+        la_AnimationPreFrame();
+
         if(!la_ProcessSysMessage()){ return; }
 
         la_UpdateControllerStatus();
@@ -7201,6 +7207,8 @@ void laMainLoop(){
         }
 
         MAIN.TimeAccum += (MAIN.LastFrameTime = Pause+TimeInterval);
-        FrameInterval = 1.0 / MAIN.Animation.FrameRate;
+        //FrameInterval = 1.0 / MAIN.Animation.FrameRate;
+
+        la_AnimationPostFrame();
     }
 }

+ 16 - 6
la_tns.h

@@ -583,11 +583,16 @@ struct _tnsObject
 
     real GLocation[3];
     real GRotation[3];
-    real GScale;
+    real GScale;//Only allow uniform scale
 
     real Location[3];
     real Rotation[4];
-    real Scale;//Only allow uniform scale
+    real Scale;
+
+    // delta transforms for animation & drivers.
+    real DLocation[3];
+    real DRotation[3];
+    real DScale;
 
     tnsMatrix44d GlobalTransform;
     tnsMatrix44d SelfTransform;
@@ -1101,7 +1106,7 @@ real tnsDot3d(tnsVector3d l, tnsVector3d r, int normalize);
 real tnsVectorCross3d(tnsVector3d result, tnsVector3d l, tnsVector3d r);
 void tnsVectorCrossOnly3d(tnsVector3d result, tnsVector3d l, tnsVector3d r);
 
-void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *x_result, real *y_result, real *z_result);
+void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *xyz_result);
 
 void tnsPrintMatrix44d(tnsMatrix44d l);
 
@@ -1109,14 +1114,19 @@ int tnsPointInsideTriangle3d(tnsVector3d v, tnsVector3d v0, tnsVector3d v1, tnsV
 
 int tnsIntersectPlaneRay(tnsVector3d n, tnsVector3d p0, tnsVector3d l0, tnsVector3d l, real* t);
 
-void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *x_result, real *y_result, real *z_result);
-void tnsExtractLocation44d(tnsMatrix44d mat, real *x_result, real *y_result, real *z_result);
-void tnsExtractUniformScale44d(tnsMatrix44d mat, real *result);
+void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *xyz_result);
+void tnsExtractLocation44d(tnsMatrix44d mat, real *xyz_result);
+void tnsExtractUniformScale44d(tnsMatrix44d mat, real *uniform_result);
 void tnsSelfMatrixChanged(tnsObject* o, int ApplyToChild);
 void tnsGlobalMatrixChanged(tnsObject* o, int ApplyToChild);
 void tnsSelfTransformValueChanged(tnsObject* o);
+void tnsDeltaTransformValueChanged(tnsObject* o);
 void tnsGlobalTransformValueChanged(tnsObject* o);
 
+void tnsExtractDeltaTransformValue(tnsObject *o);
+void tnsExtractSelfTransformValue(tnsObject *o);
+void tnsExtractGlobalTransformValue(tnsObject *o);
+
 void tnsSetCurrentRoot(tnsObject *o);
 void tnsInitObjectBase(tnsObject *o, tnsObject *under, char *Name, int Type,
                         real AtX, real AtY, real AtZ,

+ 45 - 15
la_tns_kernel.c

@@ -862,7 +862,7 @@ void tnsClearTranslation44d(tnsMatrix44d mat){
     mat[11] = 0;
 }
 
-void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *x_result, real *y_result, real *z_result){
+void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *xyz_result){
     real xRot, yRot, zRot;
 
     if (mat[2] < 1){
@@ -881,18 +881,18 @@ void tnsExtractXYZEuler44d(tnsMatrix44d mat, real *x_result, real *y_result, rea
         zRot = 0;
     }
 
-    (*x_result) = -xRot;
-    (*y_result) = -yRot;
-    (*z_result) = -zRot;
+    xyz_result[0] = -xRot;
+    xyz_result[1] = -yRot;
+    xyz_result[2] = -zRot;
 }
-void tnsExtractLocation44d(tnsMatrix44d mat, real *x_result, real *y_result, real *z_result){
-    *x_result = mat[12];
-    *y_result = mat[13];
-    *z_result = mat[14];
+void tnsExtractLocation44d(tnsMatrix44d mat, real *xyz_result){
+    xyz_result[0] = mat[12];
+    xyz_result[1] = mat[13];
+    xyz_result[2] = mat[14];
 }
-void tnsExtractUniformScale44d(tnsMatrix44d mat, real *result){
+void tnsExtractUniformScale44d(tnsMatrix44d mat, real *uniform_result){
     tnsVector3d v = {mat[0], mat[1], mat[2]};
-    *result = tnsLength3d(v);
+    *uniform_result = tnsLength3d(v);
 }
 
 #define L(row, col) l[(col << 2) + row]
@@ -3221,16 +3221,22 @@ tnsObject *tnsFindObject(char *Name, tnsObject *FromObj){
     return 0;
 }
 
+void tnsExtractDeltaTransformValue(tnsObject *o){
+    if (!o) return;
+    tnsExtractLocation44d(o->DeltaTransform, o->DLocation);
+    tnsExtractXYZEuler44d(o->DeltaTransform, o->DRotation);
+    tnsExtractUniformScale44d(o->DeltaTransform, &o->DScale);
+}
 void tnsExtractSelfTransformValue(tnsObject *o){
     if (!o) return;
-    tnsExtractLocation44d(o->SelfTransform, &o->Location[0], &o->Location[1], &o->Location[2]);
-    tnsExtractXYZEuler44d(o->SelfTransform, &o->Rotation[0], &o->Rotation[1], &o->Rotation[2]);
+    tnsExtractLocation44d(o->SelfTransform, o->Location);
+    tnsExtractXYZEuler44d(o->SelfTransform, o->Rotation);
     tnsExtractUniformScale44d(o->SelfTransform, &o->Scale);
 }
 void tnsExtractGlobalTransformValue(tnsObject *o){
     if (!o) return;
-    tnsExtractLocation44d(o->GlobalTransform, &o->GLocation[0], &o->GLocation[1], &o->GLocation[2]);
-    tnsExtractXYZEuler44d(o->GlobalTransform, &o->GRotation[0], &o->GRotation[1], &o->GRotation[2]);
+    tnsExtractLocation44d(o->GlobalTransform, o->GLocation);
+    tnsExtractXYZEuler44d(o->GlobalTransform, o->GRotation);
     tnsExtractUniformScale44d(o->GlobalTransform, &o->GScale);
 }
 void tnsCopyGlobalTransform(tnsObject *to, tnsObject *from){
@@ -3251,6 +3257,7 @@ void tnsSelfMatrixChanged(tnsObject* o, int ApplyToChild){
         tnsMultiply44d(mix, o->ParentObject->GlobalTransform, o->SelfTransform);
         tnsMultiply44d(o->GlobalTransform, mix, o->DeltaTransform);
     }
+    tnsExtractDeltaTransformValue(o);
     tnsExtractSelfTransformValue(o);
     tnsExtractGlobalTransformValue(o);
     if(ApplyToChild) for (laListItemPointer* li=o->ChildObjects.pFirst;li;li=li->pNext){ tnsSelfMatrixChanged(li->p,1); }
@@ -3284,6 +3291,26 @@ void tnsSelfTransformValueChanged(tnsObject* o){
     tnsMultiply44d(o->SelfTransform, Res1, Scale);
     tnsSelfMatrixChanged(o,1);
 }
+void tnsDeltaTransformValueChanged(tnsObject* o){
+    tnsMatrix44d Trans, Rot1, Rot2, Rot3, Scale, Res1, Res2;
+    tnsLoadIdentity44d(o->DeltaTransform);
+    tnsMakeTranslationMatrix44d(Trans, LA_COLOR3(o->DLocation));
+    tnsMakeScaleMatrix44d(Scale, o->DScale,o->DScale,o->DScale);
+
+    tnsMakeRotationXMatrix44d(Rot1, o->DRotation[0]);
+    tnsMakeRotationYMatrix44d(Rot2, o->DRotation[1]);
+    tnsMakeRotationZMatrix44d(Rot3, o->DRotation[2]);
+    switch (o->RotationMode){
+    case TNS_ROTATION_ZYX_EULER: tnsMultiply44d(Res1, Trans, Rot1); tnsMultiply44d(Res2, Res1, Rot2); tnsMultiply44d(Res1, Res2, Rot3); break;
+    case TNS_ROTATION_XZY_EULER: tnsMultiply44d(Res1, Trans, Rot1); tnsMultiply44d(Res2, Res1, Rot3); tnsMultiply44d(Res1, Res2, Rot2); break;
+    case TNS_ROTATION_YXZ_EULER: tnsMultiply44d(Res1, Trans, Rot2); tnsMultiply44d(Res2, Res1, Rot1); tnsMultiply44d(Res1, Res2, Rot3); break;
+    case TNS_ROTATION_YZX_EULER: tnsMultiply44d(Res1, Trans, Rot2); tnsMultiply44d(Res2, Res1, Rot3); tnsMultiply44d(Res1, Res2, Rot1); break;
+    case TNS_ROTATION_ZXY_EULER: tnsMultiply44d(Res1, Trans, Rot3); tnsMultiply44d(Res2, Res1, Rot1); tnsMultiply44d(Res1, Res2, Rot2); break;
+    case TNS_ROTATION_XYZ_EULER: tnsMultiply44d(Res1, Trans, Rot3); tnsMultiply44d(Res2, Res1, Rot2); tnsMultiply44d(Res1, Res2, Rot1); break;
+    }
+    tnsMultiply44d(o->DeltaTransform, Res1, Scale);
+    tnsSelfMatrixChanged(o,1);
+}
 void tnsGlobalTransformValueChanged(tnsObject* o){
     tnsMatrix44d Trans, Rot1, Rot2, Rot3, Scale, Res1, Res2;
     tnsLoadIdentity44d(o->GlobalTransform);
@@ -3388,6 +3415,7 @@ void tnsMoveObjectDelta(tnsObject *o, real x, real y, real z){
     tnsMakeTranslationMatrix44d(res2, x, y, z);
     tnsMultiply44d(res1, o->DeltaTransform, res2);
     memcpy(o->DeltaTransform, res1, sizeof(tnsMatrix44d));
+    tnsExtractDeltaTransformValue(o);
     tnsSelfMatrixChanged(o,1);
 }
 void tnsMoveObjectGlobal(tnsObject *o, real x, real y, real z){
@@ -3440,6 +3468,7 @@ void tnsRotateObjectDelta(tnsObject *o, real x, real y, real z, real angle, real
     tnsMultiply44d(res1,o->SelfTransform,tback);
     tnsMultiply44d(res2,res1,rot);
     tnsMultiply44d(o->SelfTransform,res2,tfwd);
+    tnsExtractDeltaTransformValue(o);
     tnsSelfMatrixChanged(o,1);
 }
 void tnsScaleObject(tnsObject *o, real fac, real cx,real cy,real cz){
@@ -3462,7 +3491,8 @@ void tnsScaleObjectDelta(tnsObject *o, real fac, real cx,real cy,real cz){
     tnsVectorMultiSelf3d(delta, fac);
     tnsVectorPlus3d(&res1[12],c,delta);
     memcpy(o->DeltaTransform, res1, sizeof(tnsMatrix44d));
-    tnsGlobalMatrixChanged(o,1);
+    tnsExtractDeltaTransformValue(o);
+    tnsSelfMatrixChanged(o,1);
 }
 void tnsZoomViewingCamera(tnsCamera *c, real Ratio){
     if (c->FocusDistance < 0.1) return;

+ 15 - 4
resources/la_properties.c

@@ -560,6 +560,9 @@ void *tnsget_FirstChildObject(tnsObject *ob){
 void tnsset_ObjectLocation(tnsObject* o, real val, int n){ o->Location[n]=val; tnsSelfTransformValueChanged(o); laNotifyUsers("tns.world"); }
 void tnsset_ObjectRotation(tnsObject* o, real val, int n){ o->Rotation[n]=val; tnsSelfTransformValueChanged(o); laNotifyUsers("tns.world"); }
 void tnsset_ObjectScale(tnsObject* o, real val){ o->Scale=val; tnsSelfTransformValueChanged(o); laNotifyUsers("tns.world"); }
+void tnsset_ObjectDLocationARR(tnsObject* o, real* arr){ tnsVectorCopy3d(arr,o->DLocation); tnsDeltaTransformValueChanged(o); laNotifyUsers("tns.world"); }
+void tnsset_ObjectDRotationARR(tnsObject* o, real* arr){ tnsVectorCopy3d(arr,o->DRotation); tnsDeltaTransformValueChanged(o); laNotifyUsers("tns.world"); }
+void tnsset_ObjectDScale(tnsObject* o, real val){ o->DScale=val; tnsDeltaTransformValueChanged(o); laNotifyUsers("tns.world"); }
 
 void laget_UiTemplateIdentifier(laUiTemplate *uit, char *result, char** here){
     *here=uit->Identifier->Ptr;
@@ -1001,6 +1004,11 @@ void la_RegisterInternalProps(){
             laAddSubGroup(p, "drivers", "Drivers", "Driver page collection","la_driver_collection",0,0,0,offsetof(LA,Drivers),0,0,0,0,0,0,0,LA_UDF_SINGLE);
 
             laAddSubGroup(p, "actions", "Actions", "All animation actions","la_animation_action",0,0,0,-1,0,0,0,0,0,0,offsetof(LA,Actions),0);
+            laAddFloatProperty(p, "animation_play_head","Play Head","Animation viewer global playhead",0,0,"s",0,0,0.1,0,0,offsetof(LA,AnimationPlayHead),0,0,0,0,0,0,0,0,0,0,0);
+            ep=laAddEnumProperty(p, "animation_play_status", "Play Status", "Animation viewer global play status", 0,0,0,0,0,offsetof(LA, AnimationPlayStatus),0,0,0,0,0,0,0,0,0,0);
+            laAddEnumItemAs(ep, "PAUSED", "Paused", "Animation is playing", LA_ANIMATION_STATUS_PAUSED, U'⏸');
+            laAddEnumItemAs(ep, "PLAY_FWD", "Playing", "File data is untouched", LA_ANIMATION_STATUS_PLAY_FWD,U'▶');
+            laAddEnumItemAs(ep, "PLAY_REV", "Reversing", "File data is untouched", LA_ANIMATION_STATUS_PLAY_REV,U'◀');
 
             laAddStringProperty(p, "identifier", "Identifier", "Identifier", 0,0,0,0,0,0,0,laget_MainIdentifier, 0,0,LA_AS_IDENTIFIER|LA_READ_ONLY);
             laAddSubGroup(p, "test_ucn", "TEST UCN", "---", "udf_content_node",0,0,0,offsetof(LA, TEST_Link), 0,0,0,0,0,0,0,0);
@@ -1403,7 +1411,7 @@ void la_RegisterInternalProps(){
             laAddStringProperty(p,"name","Name","Name of the action",0,0,0,0,1,offsetof(laAction,Name),0,0,0,0,LA_AS_IDENTIFIER);
             laAddSubGroup(p, "channels", "Channels", "Action channels", "la_animation_channel",0,0,0,-1,0,0,0,0,0,0,offsetof(laAction, Channels),0);
             laAddFloatProperty(p, "length","Length","Length of the action in seconds",0,0,"s",40,0,0.1,5,0,offsetof(laAction,Length),0,0,0,0,0,0,0,0,0,0,0);
-            laAddFloatProperty(p, "play_head","Play Head","PlayHead position",0,0,"s",40,0,0.1,5,0,offsetof(laAction,PlayHead),0,0,0,0,0,0,0,0,0,0,0);
+            laAddFloatProperty(p, "play_head","Play Head","PlayHead position",0,0,"s",30,0,0.1,5,0,offsetof(laAction,PlayHead),0,0,0,0,0,0,0,0,0,0,0);
             laAddIntProperty(p, "frame_count","Frame Count","Total frame count in the whole length of the action",0,0,0,0,0,0,120,0,offsetof(laAction,FrameCount),0,0,0,0,0,0,0,0,0,0,0);
         }
         p = laAddPropertyContainer("la_animation_channel", "Channel", "Animation channel",0,0,sizeof(laAnimationChannel),0,0,1);{
@@ -1483,9 +1491,12 @@ void la_RegisterInternalProps(){
         laAddFloatProperty(p, "location", "Location", "XYZ Location In Local Coordinates", 0,"X,Y,Z", 0,0,0,0.1, 0,0,offsetof(tnsObject, Location), 0,0,3, 0,tnsset_ObjectLocation, 0,0,0,0,0,0);
         laAddFloatProperty(p, "rotation", "Rotation", "Rotation In Local Coordinates", 0,"X,Y,Z", 0,0,0,0,0,0,offsetof(tnsObject, Rotation), 0,0,3, 0,tnsset_ObjectRotation, 0,0,0,0,0,0);
         laAddFloatProperty(p, "scale", "Scale", "Local Uniform Scale", 0,0,0,0,0,0,0,0,offsetof(tnsObject, Scale), 0,tnsset_ObjectScale, 0,0,0,0,0,0,0,0,0);
-        laAddFloatProperty(p, "glocation", "Global Location", "Location in global coordinates", 0,"X,Y,Z", 0,0,0,0.1, 0,0,offsetof(tnsObject, GLocation), 0,0,3, 0,0,0,0,0,0,0,0);
-        laAddFloatProperty(p, "grotation", "Global Rotation", "Rotation in global coordinates", 0,"X,Y,Z", 0,0,0,0,0,0,offsetof(tnsObject, GRotation), 0,0,3, 0,0,0,0,0,0,0,0);
-        laAddFloatProperty(p, "gscale", "Global Scale", "Global uniform scale", 0,0,0,0,0,0,0,0,offsetof(tnsObject, GScale), 0,0,0,0,0,0,0,0,0,0,0);
+        laAddFloatProperty(p, "glocation", "Global Location", "Location in global coordinates", 0,"X,Y,Z", 0,0,0,0.1, 0,0,offsetof(tnsObject, GLocation), 0,0,3, 0,0,0,0,0,0,0,LA_READ_ONLY);
+        laAddFloatProperty(p, "grotation", "Global Rotation", "Rotation in global coordinates", 0,"X,Y,Z", 0,0,0,0,0,0,offsetof(tnsObject, GRotation), 0,0,3, 0,0,0,0,0,0,0,LA_READ_ONLY);
+        laAddFloatProperty(p, "gscale", "Global Scale", "Global uniform scale", 0,0,0,0,0,0,0,0,offsetof(tnsObject, GScale), 0,0,0,0,0,0,0,0,0,0,LA_READ_ONLY);
+        laAddFloatProperty(p, "dlocation", "Delta Location", "XYZ delta transforms", 0,"X,Y,Z", 0,0,0,0.1, 0,0,offsetof(tnsObject, DLocation), 0,0,3,0,0,0,0,tnsset_ObjectDLocationARR,0,0,0);
+        laAddFloatProperty(p, "drotation", "Delta Rotation", "Delta rotation", 0,"X,Y,Z", 0,0,0,0,0,0,offsetof(tnsObject, DRotation), 0,0,3,0,0,0,0,tnsset_ObjectDRotationARR,0,0,0);
+        laAddFloatProperty(p, "dscale", "Delta Scale", "Delta scale", 0,0,0,0,0,0,0,0,offsetof(tnsObject, DScale), 0,tnsset_ObjectDScale,0,0,0,0,0,0,0,0,0);
         laAddFloatProperty(p, "global_mat", "Global Matrix", "Global transformation matrix", 0,0,0,0,0,0,0,0,offsetof(tnsObject, GlobalTransform), 0,0,16, 0,0,0,0,0,0,0,LA_READ_ONLY);
         laAddFloatProperty(p, "local_mat", "Local Matrix", "Local transformation matrix", 0,0,0,0,0,0,0,0,offsetof(tnsObject, SelfTransform), 0,0,16, 0,0,0,0,0,0,0,LA_READ_ONLY);
         ep = laAddEnumProperty(p, "rotation_mode", "Rotation Mode", "Rotation Mode Of This Object(e.g. XYZ/XZY/Quaternion...)", 0,0,0,0,0,offsetof(tnsObject, RotationMode), 0,0,0,0,0,0,0,0,0,0);{

+ 18 - 2
resources/la_templates.c

@@ -1565,11 +1565,27 @@ void laui_Drivers(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *
 }
 
 void laui_AnimationActions(laUiList *uil, laPropPack *This, laPropPack *Extra, laColumn *UNUSED, int context){
-    laColumn* c=laFirstColumn(uil),*cl, *cr;
+    laColumn* c=laFirstColumn(uil),*cl, *cr; laSplitColumn(uil,c,0.5); cl=laLeftColumn(c,5); cr=laRightColumn(c,0);
+    laUiItem* b,*ui;
+
+    laUiItem* row=laBeginRow(uil,cl,0,0);
+    laShowItemFull(uil,c,0,"LA_animation_reset_time",0,0,0,0)->Flags|=LA_UI_FLAGS_ICON|LA_UI_FLAGS_EXIT_WHEN_TRIGGERED;
+    b=laOnConditionThat(uil,c,laPropExpression(0,"la.animation_play_status"));{
+        ui=laShowItemFull(uil,c,0,"LA_animation_set_play_status",0,"text=❚❚;",0,0);
+        ui->Flags|=LA_UI_FLAGS_EXIT_WHEN_TRIGGERED|LA_TEXT_ALIGN_CENTER; ui->Expand=10;
+    }laElse(uil,b);{
+        ui=laShowItemFull(uil,c,0,"LA_animation_set_play_status",0,"icon=◀;mode=reverse;",0,0);
+        ui->Flags|=LA_UI_FLAGS_ICON|LA_UI_FLAGS_EXIT_WHEN_TRIGGERED; ui->Expand=10;
+        ui=laShowItemFull(uil,c,0,"LA_animation_set_play_status",0,"icon=▶;mode=forward;",0,0);
+        ui->Flags|=LA_UI_FLAGS_ICON|LA_UI_FLAGS_EXIT_WHEN_TRIGGERED; ui->Expand=10;
+    }laEndCondition(uil,b);
+    laEndRow(uil,row);
+
+    laShowItemFull(uil,cr,0,"la.animation_play_head",LA_WIDGET_INT_PLAIN,0,0,0);
     
     laShowItemFull(uil,c,0,"la.actions",LA_WIDGET_COLLECTION,0,0,0);
 
-    laShowItem(uil,c,0,"LA_new_action");
+    laShowItem(uil,c,0,"LA_animation_new_action");
 }
 
 

+ 1 - 1
resources/la_tns_drivers.c

@@ -43,7 +43,7 @@ int IDN_TransformVisit(tnsTransformNode* n, laListHandle* l){
 int IDN_TransformEval(tnsTransformNode* n){
     if(!n->Target) return 0;
     if((!n->Mat->Source) || (n->Mat->Source->DataType!=(LA_PROP_FLOAT|LA_PROP_ARRAY)) || (n->Mat->Source->ArrLen!=16)){
-        tnsLoadIdentity44d(&n->Target->DeltaTransform); tnsSelfMatrixChanged(n->Target, 1);
+        tnsLoadIdentity44d(n->Target->DeltaTransform); tnsSelfMatrixChanged(n->Target, 1);
     }else{
         memcpy(n->Target->DeltaTransform, n->Mat->Source->Data, sizeof(tnsMatrix44d)); tnsSelfMatrixChanged(n->Target, 1);
     }

+ 1 - 1
resources/la_widgets.c

@@ -896,7 +896,7 @@ void la_ButtonDraw(laUiItem *ui, int h){
     if (IconID) L = ui->L + bt->LM  + h;
     else L = ui->L + bt->LM;
 
-    if (IconID) tnsDrawIcon(IconID, laThemeColor(bt, ui->State|LA_BT_TEXT), ui->L, ui->L+LA_RH, ui->U, LA_TEXT_ALIGN_CENTER);
+    if (IconID) tnsDrawIcon(IconID, laThemeColor(bt, ui->State|LA_BT_TEXT), ui->L, IconOnly?(ui->R):(ui->L+LA_RH), ui->U, LA_TEXT_ALIGN_CENTER);
 
     if (!IconOnly) tnsDrawStringAuto(label, laThemeColor(bt, ui->State|LA_BT_TEXT), L, ui->R-bt->RM, ui->U, ui->Flags);
 }

+ 24 - 8
resources/la_widgets_viewers.c

@@ -467,7 +467,7 @@ void laDefault3DViewOverlay(laUiItem *ui){
     laUiList *uil, *gu;
     laColumn *c, *cl, *cll, *clr, *cr, *crl, *crr, *gc, *gcl, *gcr;
     laPropPack *e = &ui->ExtraPP;
-    laUiItem *b, *g;
+    laUiItem *b, *g, *b1;
 
     if (!(uil = ui->Subs.pFirst)) uil = laAddTabPage(ui, "New Group");
 
@@ -517,14 +517,26 @@ void laDefault3DViewOverlay(laUiItem *ui){
     g = laMakeFoldableGroup(uil, cr, "Object Info", 0, 0, 0);{
         gu = g->Page;
         gc = laFirstColumn(gu);
-        laSplitColumn(gu, gc, 0.35);
-        gcl = laLeftColumn(gc, 0);
+        laSplitColumn(gu, gc, 0.5);
+        gcl = laLeftColumn(gc, 6);
         gcr = laRightColumn(gc, 0);
-        laShowItem(gu, gc, &ui->PP, "active.name");
-        laShowLabel(gu, gcl, "Location:", 0, 0);laShowItem(gu, gcr, &ui->PP, "active.location")->Flags|=LA_UI_FLAGS_TRANSPOSE;
-        laShowSeparator(gu,gc);
-        laShowLabel(gu, gcl, "Rotation:", 0, 0);laShowItem(gu, gcr, &ui->PP, "active.rotation")->Flags|=LA_UI_FLAGS_TRANSPOSE;
-        laShowItem(gu, gc, &ui->PP, "active.scale");
+
+        b1=laOnConditionThat(gu,gc,laPropExpression(&ui->PP,"active"));{
+            laShowItem(gu, gc, &ui->PP, "active.name");
+            b=laOnConditionThat(gu,gc,laPropExpression(&ui->ExtraPP, "delta_mode"));{
+                laShowLabel(gu, gcl, "DLoc:", 0, 0);laShowItem(gu, gcr, &ui->PP, "active.dlocation")->Flags|=LA_UI_FLAGS_TRANSPOSE;
+                laShowSeparator(gu,gc);
+                laShowLabel(gu, gcl, "DRot:", 0, 0);laShowItem(gu, gcr, &ui->PP, "active.drotation")->Flags|=LA_UI_FLAGS_TRANSPOSE;
+                laShowItem(gu, gc, &ui->PP, "active.dscale");
+            }laElse(gu,b);{
+                laShowLabel(gu, gcl, "Loc:", 0, 0);laShowItem(gu, gcr, &ui->PP, "active.location")->Flags|=LA_UI_FLAGS_TRANSPOSE;
+                laShowSeparator(gu,gc);
+                laShowLabel(gu, gcl, "Rot:", 0, 0);laShowItem(gu, gcr, &ui->PP, "active.rotation")->Flags|=LA_UI_FLAGS_TRANSPOSE;
+                laShowItem(gu, gc, &ui->PP, "active.scale");
+            }laEndCondition(gu,b);
+        }laElse(gu,b1);{
+            laShowLabel(gu, gc, "No active object.", 0, 0);
+        }laEndCondition(gu,b1);
     }
     g = laMakeFoldableGroup(uil, cr, "Display", 0, 1, 0);{
         gu = g->Page;
@@ -1039,6 +1051,10 @@ void la_RegisterUiTypesViewerWidgets(){
             laAddEnumItemAs(p, "OFF", "Off", "Don't select through stuff", LA_CANVAS_SELECT_THROUGH_OFF,0);
             laAddEnumItemAs(p, "ON", "On", "Select through stuff", LA_CANVAS_SELECT_THROUGH_ON,0);
         }
+        p = laAddEnumProperty(pc, "delta_mode", "Delta Mode", "Toggle delta transformation mode for animation editing", 0, 0, 0, 0, 0, offsetof(laCanvasExtra, DeltaMode), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);{
+            laAddEnumItemAs(p, "NONE", "None", "Regular mode",0,0);
+            laAddEnumItemAs(p, "DELTA", "Delta", "Delta mode",1,0);
+        }
         laAddIntProperty(pc, "height_coeff", "Ui Height", "Ui Height Coefficiency Entry", 0, 0, "Rows", 100, -100, 1, 0, 0, offsetof(laCanvasExtra, HeightCoeff), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
         laAddOperatorProperty(pc, "zoom", "Zoom", "Zoom Viewing Camera", "LA_3d_view_camera_zoom", U'🔎', 0);
         laAddOperatorProperty(pc, "rotate", "Rotate", "Rotate Viewing Camera", "LA_3d_view_camera_rotate", U'🗘', 0);