From 35f134e6adc739e12d8b5565a5871f491c0cb69e Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Thu, 12 Jan 2023 16:58:43 +0100
Subject: [PATCH 01/49] first baby steps...

---
 src/Scene/LevelScene.php      | 28 +++++++++------
 src/System/AircraftSystem.php | 68 +++++++++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 10 deletions(-)
 create mode 100644 src/System/AircraftSystem.php

diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 37dd5e6..a03fba1 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -5,6 +5,7 @@
 use GameContainer;
 use GL\Math\Vec3;
 use TowerDefense\Renderer\TerrainRenderer;
+use TowerDefense\System\AircraftSystem;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\Geo\Transform;
 use VISU\Graphics\Camera;
@@ -22,6 +23,8 @@ class LevelScene extends BaseScene
     private VISULowPolyRenderingSystem $renderingSystem;
     private VISUCameraSystem $cameraSystem;
 
+    private AircraftSystem $aircraftSystem;
+
     private LPVertexBuffer $objectVertexBuffer;
     private LPObjLoader $objectLoader;
     private array $loadedObjects = [];
@@ -40,17 +43,19 @@ public function __construct(
         $this->objectLoader = new LPObjLoader($container->resolveGL());
         $this->loadedObjects = $this->objectLoader->loadAllInDirectory(VISU_PATH_RESOURCES . '/models/spacekit');
 
-        // prepare the rendering systems 
+        // prepare the rendering systems
         $this->terrainRenderer = new TerrainRenderer($container->resolveGL());
         $this->renderingSystem = new VISULowPolyRenderingSystem($container->resolveGL());
         $this->renderingSystem->addGeometryRenderer($this->terrainRenderer);
 
         $this->cameraSystem = new VISUCameraSystem(
-            $this->container->resolveInput(), 
+            $this->container->resolveInput(),
             $this->container->resolveVisuDispatcher()
         );
 
-        // prepare the scene 
+        $this->aircraftSystem = new AircraftSystem($this->loadedObjects);
+
+        // prepare the scene
         $this->prepareScene();
     }
 
@@ -69,12 +74,13 @@ private function prepareScene()
     {
         $this->cameraSystem->register($this->entities);
         $this->renderingSystem->register($this->entities);
+        $this->aircraftSystem->register($this->entities);
 
         // create a camera
         $cameraEntity = $this->entities->create();
         $camera = $this->entities->attach($cameraEntity, new Camera(CameraProjectionMode::perspective));
-        $camera->transform->position->y = 100;
-        $camera->transform->position->z = 50;
+        $camera->transform->position->y = 250;
+        $camera->transform->position->z = 500;
         $this->cameraSystem->setActiveCameraEntity($cameraEntity);
 
         // create some random renderable objects
@@ -89,8 +95,8 @@ private function prepareScene()
 
     /**
      * Loads resources required for the scene
-     * 
-     * @return void 
+     *
+     * @return void
      */
     public function load() : void
     {
@@ -99,21 +105,23 @@ public function load() : void
 
     /**
      * Updates the scene state
-     * 
-     * @return void 
+     *
+     * @return void
      */
     public function update() : void
     {
         $this->cameraSystem->update($this->entities);
+        $this->aircraftSystem->update($this->entities);
     }
 
     /**
      * Renders the scene
-     * 
+     *
      * @param RenderContext $context
      */
     public function render(RenderContext $context) : void
     {
+        $this->aircraftSystem->render($this->entities, $context);
         $this->cameraSystem->render($this->entities, $context);
 
         $backbuffer = $context->data->get(BackbufferData::class);
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
new file mode 100644
index 0000000..d258d72
--- /dev/null
+++ b/src/System/AircraftSystem.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace TowerDefense\System;
+
+use VISU\Component\VISULowPoly\DynamicRenderableModel;
+use VISU\Geo\Transform;
+use VISU\Graphics\Rendering\RenderContext;
+
+class AircraftSystem implements \VISU\ECS\SystemInterface
+{
+    private array $loadedObjects;
+
+    private int $aircraft;
+
+    /**
+     * Constructor
+     *
+     * @param array $loadedObjects
+     *
+     */
+    public function __construct(array &$loadedObjects)
+    {
+        $this->loadedObjects = $loadedObjects;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function register(\VISU\ECS\EntitiesInterface $entities): void
+    {
+        // create some random renderable objects
+        $this->aircraft = $entities->create();
+        $renderable = $entities->attach($this->aircraft, new DynamicRenderableModel);
+        $renderable->model = $this->loadedObjects['craft_speederA.obj'];
+        $transform = $entities->attach($this->aircraft, new Transform);
+        $transform->scale = $transform->scale * 100;
+        $transform->position->y = 250;
+        $transform->position->x = 250;
+        $transform->position->z = -1000;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function unregister(\VISU\ECS\EntitiesInterface $entities): void
+    {
+        $entities->detach($this->aircraft, DynamicRenderableModel::class);
+        $entities->detach($this->aircraft, Transform::class);
+        $entities->destory($this->aircraft);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function update(\VISU\ECS\EntitiesInterface $entities): void
+    {
+        $transform = $entities->get($this->aircraft, Transform::class);
+        $transform->moveBackward(5.0); // <- move the aircraft forward, seems that the model itself is not correctly rotated? so forward is actually backward?!
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function render(\VISU\ECS\EntitiesInterface $entities, RenderContext $context): void
+    {
+        // TODO: Implement render() method.
+    }
+}

From 33be388376317cd28d2a8f14f74a01fd82a6d08d Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Thu, 12 Jan 2023 17:25:28 +0100
Subject: [PATCH 02/49] code cleanup

---
 src/System/AircraftSystem.php | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index d258d72..43ea0b6 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -3,10 +3,12 @@
 namespace TowerDefense\System;
 
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
+use VISU\ECS\EntitiesInterface;
+use VISU\ECS\SystemInterface;
 use VISU\Geo\Transform;
 use VISU\Graphics\Rendering\RenderContext;
 
-class AircraftSystem implements \VISU\ECS\SystemInterface
+class AircraftSystem implements SystemInterface
 {
     private array $loadedObjects;
 
@@ -26,7 +28,7 @@ public function __construct(array &$loadedObjects)
     /**
      * @inheritDoc
      */
-    public function register(\VISU\ECS\EntitiesInterface $entities): void
+    public function register(EntitiesInterface $entities): void
     {
         // create some random renderable objects
         $this->aircraft = $entities->create();
@@ -42,7 +44,7 @@ public function register(\VISU\ECS\EntitiesInterface $entities): void
     /**
      * @inheritDoc
      */
-    public function unregister(\VISU\ECS\EntitiesInterface $entities): void
+    public function unregister(EntitiesInterface $entities): void
     {
         $entities->detach($this->aircraft, DynamicRenderableModel::class);
         $entities->detach($this->aircraft, Transform::class);
@@ -52,7 +54,7 @@ public function unregister(\VISU\ECS\EntitiesInterface $entities): void
     /**
      * @inheritDoc
      */
-    public function update(\VISU\ECS\EntitiesInterface $entities): void
+    public function update(EntitiesInterface $entities): void
     {
         $transform = $entities->get($this->aircraft, Transform::class);
         $transform->moveBackward(5.0); // <- move the aircraft forward, seems that the model itself is not correctly rotated? so forward is actually backward?!
@@ -61,7 +63,7 @@ public function update(\VISU\ECS\EntitiesInterface $entities): void
     /**
      * @inheritDoc
      */
-    public function render(\VISU\ECS\EntitiesInterface $entities, RenderContext $context): void
+    public function render(EntitiesInterface $entities, RenderContext $context): void
     {
         // TODO: Implement render() method.
     }

From e42287d881451ac7b175df0fbb985dd95c0a8413 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Thu, 12 Jan 2023 20:54:48 +0100
Subject: [PATCH 03/49] aircraft flying forward and backward to a position in a
 loop

---
 src/Component/OrientationComponent.php | 10 ++++++++
 src/Component/PositionComponent.php    | 12 ++++++++++
 src/System/AircraftSystem.php          | 33 +++++++++++++++++++++++++-
 3 files changed, 54 insertions(+), 1 deletion(-)
 create mode 100644 src/Component/OrientationComponent.php
 create mode 100644 src/Component/PositionComponent.php

diff --git a/src/Component/OrientationComponent.php b/src/Component/OrientationComponent.php
new file mode 100644
index 0000000..cb19767
--- /dev/null
+++ b/src/Component/OrientationComponent.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace TowerDefense\Component;
+
+use GL\Math\Quat;
+
+class OrientationComponent
+{
+    public Quat $targetOrientation;
+}
\ No newline at end of file
diff --git a/src/Component/PositionComponent.php b/src/Component/PositionComponent.php
new file mode 100644
index 0000000..7b7ad9f
--- /dev/null
+++ b/src/Component/PositionComponent.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace TowerDefense\Component;
+
+use GL\Math\Vec3;
+
+class PositionComponent
+{
+    public Vec3 $targetPosition;
+
+    public bool $movingForward = false;
+}
\ No newline at end of file
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 43ea0b6..955cdaa 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -2,6 +2,11 @@
 
 namespace TowerDefense\System;
 
+use GL\Math\GLM;
+use GL\Math\Quat;
+use GL\Math\Vec3;
+use TowerDefense\Component\PositionComponent;
+use TowerDefense\Component\OrientationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
@@ -30,6 +35,10 @@ public function __construct(array &$loadedObjects)
      */
     public function register(EntitiesInterface $entities): void
     {
+        // register components
+        $entities->registerComponent(PositionComponent::class);
+        $entities->registerComponent(OrientationComponent::class);
+
         // create some random renderable objects
         $this->aircraft = $entities->create();
         $renderable = $entities->attach($this->aircraft, new DynamicRenderableModel);
@@ -39,6 +48,16 @@ public function register(EntitiesInterface $entities): void
         $transform->position->y = 250;
         $transform->position->x = 250;
         $transform->position->z = -1000;
+
+        $targetPosition = $entities->attach($this->aircraft, new PositionComponent);
+        $targetPosition->targetPosition = $transform->position;
+
+        $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
+        $targetPosition->targetPosition += $dir * 1000.0;
+        $targetPosition->movingForward = true;
+
+        $targetOrientation = $entities->attach($this->aircraft, new OrientationComponent);
+        $targetOrientation->targetOrientation = $transform->orientation;
     }
 
     /**
@@ -46,6 +65,8 @@ public function register(EntitiesInterface $entities): void
      */
     public function unregister(EntitiesInterface $entities): void
     {
+        $entities->detach($this->aircraft, PositionComponent::class);
+        $entities->detach($this->aircraft, OrientationComponent::class);
         $entities->detach($this->aircraft, DynamicRenderableModel::class);
         $entities->detach($this->aircraft, Transform::class);
         $entities->destory($this->aircraft);
@@ -57,7 +78,17 @@ public function unregister(EntitiesInterface $entities): void
     public function update(EntitiesInterface $entities): void
     {
         $transform = $entities->get($this->aircraft, Transform::class);
-        $transform->moveBackward(5.0); // <- move the aircraft forward, seems that the model itself is not correctly rotated? so forward is actually backward?!
+        $targetPosition = $entities->get($this->aircraft, PositionComponent::class);
+        $distance = $transform->position->distanceTo($targetPosition->targetPosition);
+        if ($distance > 5) {
+            $transform->moveBackward(5.0);
+        } else {
+            $targetPosition->movingForward = !$targetPosition->movingForward;
+            $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
+            $targetPosition->targetPosition = $transform->position;
+            $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
+            $targetPosition->targetPosition += $dir * 1000.0;
+        }
     }
 
     /**

From 988187975a60ef474b6dca2706523b13befc359c Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 13 Jan 2023 09:46:48 +0100
Subject: [PATCH 04/49] optimizations

---
 src/Component/PositionComponent.php |  4 +---
 src/System/AircraftSystem.php       | 13 +++++--------
 2 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/Component/PositionComponent.php b/src/Component/PositionComponent.php
index 7b7ad9f..bfb30c7 100644
--- a/src/Component/PositionComponent.php
+++ b/src/Component/PositionComponent.php
@@ -7,6 +7,4 @@
 class PositionComponent
 {
     public Vec3 $targetPosition;
-
-    public bool $movingForward = false;
-}
\ No newline at end of file
+}
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 955cdaa..dc277b1 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -46,15 +46,14 @@ public function register(EntitiesInterface $entities): void
         $transform = $entities->attach($this->aircraft, new Transform);
         $transform->scale = $transform->scale * 100;
         $transform->position->y = 250;
-        $transform->position->x = 250;
-        $transform->position->z = -1000;
+        $transform->position->x = 500;
+        $transform->position->z = -550;
+        $transform->orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
 
         $targetPosition = $entities->attach($this->aircraft, new PositionComponent);
         $targetPosition->targetPosition = $transform->position;
-
         $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
         $targetPosition->targetPosition += $dir * 1000.0;
-        $targetPosition->movingForward = true;
 
         $targetOrientation = $entities->attach($this->aircraft, new OrientationComponent);
         $targetOrientation->targetOrientation = $transform->orientation;
@@ -80,15 +79,13 @@ public function update(EntitiesInterface $entities): void
         $transform = $entities->get($this->aircraft, Transform::class);
         $targetPosition = $entities->get($this->aircraft, PositionComponent::class);
         $distance = $transform->position->distanceTo($targetPosition->targetPosition);
-        if ($distance > 5) {
-            $transform->moveBackward(5.0);
-        } else {
-            $targetPosition->movingForward = !$targetPosition->movingForward;
+        if ($distance <= 5) {
             $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
             $targetPosition->targetPosition = $transform->position;
             $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
             $targetPosition->targetPosition += $dir * 1000.0;
         }
+        $transform->moveBackward(5.0);
     }
 
     /**

From cc72ca0bc151a858e42f7d33e40874c85cfb6e42 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 13 Jan 2023 11:53:39 +0100
Subject: [PATCH 05/49] little changes

---
 src/System/AircraftSystem.php | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index dc277b1..c48f860 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -79,13 +79,19 @@ public function update(EntitiesInterface $entities): void
         $transform = $entities->get($this->aircraft, Transform::class);
         $targetPosition = $entities->get($this->aircraft, PositionComponent::class);
         $distance = $transform->position->distanceTo($targetPosition->targetPosition);
-        if ($distance <= 5) {
+        if ($distance <= 0) {
             $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
             $targetPosition->targetPosition = $transform->position;
             $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
             $targetPosition->targetPosition += $dir * 1000.0;
+            $distance = $transform->position->distanceTo($targetPosition->targetPosition);
+        }
+        $move = 5.0;
+        if ($move > $distance) {
+            $transform->setPosition($targetPosition->targetPosition);
+        } else {
+            $transform->moveBackward($move);
         }
-        $transform->moveBackward(5.0);
     }
 
     /**

From 2a290f8260166adda5354e0195c7a3fb8e841447 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 13 Jan 2023 14:13:58 +0100
Subject: [PATCH 06/49] using lerp

---
 src/Component/PositionComponent.php |  1 +
 src/System/AircraftSystem.php       | 33 +++++++++++++++++++++--------
 2 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/src/Component/PositionComponent.php b/src/Component/PositionComponent.php
index bfb30c7..bbe96ec 100644
--- a/src/Component/PositionComponent.php
+++ b/src/Component/PositionComponent.php
@@ -6,5 +6,6 @@
 
 class PositionComponent
 {
+    public Vec3 $startPosition;
     public Vec3 $targetPosition;
 }
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index c48f860..32ae19d 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -52,8 +52,11 @@ public function register(EntitiesInterface $entities): void
 
         $targetPosition = $entities->attach($this->aircraft, new PositionComponent);
         $targetPosition->targetPosition = $transform->position;
+        $targetPosition->startPosition = $transform->position;
         $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
         $targetPosition->targetPosition += $dir * 1000.0;
+        $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
+        $targetPosition->targetPosition += $dir * 100.0;
 
         $targetOrientation = $entities->attach($this->aircraft, new OrientationComponent);
         $targetOrientation->targetOrientation = $transform->orientation;
@@ -78,20 +81,32 @@ public function update(EntitiesInterface $entities): void
     {
         $transform = $entities->get($this->aircraft, Transform::class);
         $targetPosition = $entities->get($this->aircraft, PositionComponent::class);
-        $distance = $transform->position->distanceTo($targetPosition->targetPosition);
-        if ($distance <= 0) {
+        $travelProgress = 1.0 - (abs($transform->position->x - $targetPosition->targetPosition->x) / abs($targetPosition->targetPosition->x - $targetPosition->startPosition->x));
+        if ($travelProgress >= 1) {
+            $goUp = true;
+            if ($targetPosition->startPosition->y < $targetPosition->targetPosition->y) {
+                $goUp = false;
+            }
+
             $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
             $targetPosition->targetPosition = $transform->position;
+            $targetPosition->startPosition = $transform->position;
             $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
             $targetPosition->targetPosition += $dir * 1000.0;
-            $distance = $transform->position->distanceTo($targetPosition->targetPosition);
-        }
-        $move = 5.0;
-        if ($move > $distance) {
-            $transform->setPosition($targetPosition->targetPosition);
-        } else {
-            $transform->moveBackward($move);
+
+            if ($goUp) {
+                $dir = Quat::multiplyVec3($transform->orientation, $transform::worldUp());
+                $targetPosition->targetPosition += $dir * 100.0;
+            } else {
+                $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
+                $targetPosition->targetPosition += $dir * 100.0;
+            }
+
+            $travelProgress = 0.0;
         }
+        $newTravelProgress = min($travelProgress + 0.005, 1.0);
+        $newPosition = Vec3::lerp($targetPosition->startPosition, $targetPosition->targetPosition, $newTravelProgress);
+        $transform->setPosition($newPosition);
     }
 
     /**

From 76025406ca837e121295385fb208a136d10788ea Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 14 Jan 2023 20:14:42 +0100
Subject: [PATCH 07/49] composer update

---
 composer.lock | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/composer.lock b/composer.lock
index cfedb3d..6677a93 100644
--- a/composer.lock
+++ b/composer.lock
@@ -117,12 +117,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpgl/ide-stubs.git",
-                "reference": "d5d7957204bf4e3d859e3e19c4da4cd343226a63"
+                "reference": "eeab6f607df9b6e5265fbfabfb7dd5e61db2c8b9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpgl/ide-stubs/zipball/d5d7957204bf4e3d859e3e19c4da4cd343226a63",
-                "reference": "d5d7957204bf4e3d859e3e19c4da4cd343226a63",
+                "url": "https://api.github.com/repos/phpgl/ide-stubs/zipball/eeab6f607df9b6e5265fbfabfb7dd5e61db2c8b9",
+                "reference": "eeab6f607df9b6e5265fbfabfb7dd5e61db2c8b9",
                 "shasum": ""
             },
             "require": {
@@ -160,7 +160,7 @@
                 "issues": "https://github.com/phpgl/ide-stubs/issues",
                 "source": "https://github.com/phpgl/ide-stubs/tree/main"
             },
-            "time": "2023-01-07T20:44:23+00:00"
+            "time": "2023-01-14T18:20:56+00:00"
         },
         {
             "name": "phpgl/visu",
@@ -168,7 +168,7 @@
             "dist": {
                 "type": "path",
                 "url": "../visu",
-                "reference": "5023dfb4a5226b6c89bd419104b8597f655bfacd"
+                "reference": "0f74bc1acd2e4839c179c419ba3151d6c2365cca"
             },
             "require": {
                 "clancats/container": "^1.3",
@@ -327,5 +327,5 @@
     "prefer-lowest": false,
     "platform": [],
     "platform-dev": [],
-    "plugin-api-version": "2.2.0"
+    "plugin-api-version": "2.1.0"
 }

From a87a05596920b062621d9d0e7b8913548f1c349b Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Thu, 19 Jan 2023 14:04:05 +0100
Subject: [PATCH 08/49] multiple aircrafts

---
 src/Scene/LevelScene.php      | 36 ++++++++++++++++++++++++++++++-----
 src/System/AircraftSystem.php | 29 ++++++++++++++++------------
 2 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 36b7dff..9be4cee 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -3,7 +3,11 @@
 namespace TowerDefense\Scene;
 
 use GameContainer;
+use GL\Math\GLM;
+use GL\Math\Quat;
 use GL\Math\Vec3;
+use TowerDefense\Component\OrientationComponent;
+use TowerDefense\Component\PositionComponent;
 use TowerDefense\Debug\DebugTextOverlay;
 use TowerDefense\Renderer\TerrainRenderer;
 use TowerDefense\System\AircraftSystem;
@@ -29,7 +33,7 @@ class LevelScene extends BaseScene implements DevEntityPickerDelegate
     private VISULowPolyRenderingSystem $renderingSystem;
     private VISUCameraSystem $cameraSystem;
 
-    private AircraftSystem $aircraftSystem;
+    private array $aircraftSystems;
 
     private LPObjLoader $objectLoader;
     private array $loadedObjects = [];
@@ -73,7 +77,17 @@ public function __construct(
             $this->container->resolveVisuDispatcher()
         );
 
-        $this->aircraftSystem = new AircraftSystem($this->loadedObjects);
+        // add aircrafts
+        $initialPosition = new Vec3(500, 250, -1500);
+        $initialOrientation = new Quat();
+        $initialOrientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
+        for($i = 0; $i < 8; $i++) {
+            $initialPosition->z = $initialPosition->z + 250;
+            for($j = 0; $j < 8; $j++) {
+                $initialPosition->x = 500 + 250 * $j;
+                $this->aircraftSystems[] = new AircraftSystem($this->loadedObjects, $initialPosition->copy(), new Quat($initialOrientation->w, $initialOrientation->x, $initialOrientation->y, $initialOrientation->z));
+            }
+        }
 
         // prepare the scene
         $this->constructDevEntityPicker();
@@ -136,9 +150,16 @@ public function getName() : string
      */
     private function prepareScene()
     {
+        // register components
+        $this->entities->registerComponent(PositionComponent::class);
+        $this->entities->registerComponent(OrientationComponent::class);
+
         $this->cameraSystem->register($this->entities);
         $this->renderingSystem->register($this->entities);
-        $this->aircraftSystem->register($this->entities);
+
+        foreach ($this->aircraftSystems as $aircraftSystem) {
+            $aircraftSystem->register($this->entities);
+        }
 
         // create a camera
         $cameraEntity = $this->entities->create();
@@ -185,7 +206,10 @@ public function load() : void
     public function update() : void
     {
         $this->cameraSystem->update($this->entities);
-        $this->aircraftSystem->update($this->entities);
+
+        foreach ($this->aircraftSystems as $aircraftSystem) {
+            $aircraftSystem->update($this->entities);
+        }
     }
 
     /**
@@ -197,7 +221,9 @@ public function render(RenderContext $context) : void
     {
         DebugTextOverlay::debugString("Press 'SHIFT' + NUM for render debug: NONE=0, POS=1, VPOS=2, NORM=3, DEPTH=4, ALBEDO=5");
 
-        $this->aircraftSystem->render($this->entities, $context);
+        foreach ($this->aircraftSystems as $aircraftSystem) {
+            $aircraftSystem->render($this->entities, $context);
+        }
 
         $this->cameraSystem->render($this->entities, $context);
 
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 32ae19d..0eab071 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -15,8 +15,6 @@
 
 class AircraftSystem implements SystemInterface
 {
-    private array $loadedObjects;
-
     private int $aircraft;
 
     /**
@@ -25,9 +23,10 @@ class AircraftSystem implements SystemInterface
      * @param array $loadedObjects
      *
      */
-    public function __construct(array &$loadedObjects)
+    public function __construct(private array          &$loadedObjects,
+                                private readonly ?Vec3 $initialPosition = null,
+                                private readonly ?Quat $initialOrientation = null)
     {
-        $this->loadedObjects = $loadedObjects;
     }
 
     /**
@@ -35,20 +34,26 @@ public function __construct(array &$loadedObjects)
      */
     public function register(EntitiesInterface $entities): void
     {
-        // register components
-        $entities->registerComponent(PositionComponent::class);
-        $entities->registerComponent(OrientationComponent::class);
-
         // create some random renderable objects
         $this->aircraft = $entities->create();
         $renderable = $entities->attach($this->aircraft, new DynamicRenderableModel);
         $renderable->model = $this->loadedObjects['craft_speederA.obj'];
         $transform = $entities->attach($this->aircraft, new Transform);
         $transform->scale = $transform->scale * 100;
-        $transform->position->y = 250;
-        $transform->position->x = 500;
-        $transform->position->z = -550;
-        $transform->orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
+
+        if ($this->initialPosition != null) {
+            $transform->position = $this->initialPosition;
+        } else {
+            $transform->position->y = 250;
+            $transform->position->x = 500;
+            $transform->position->z = -550;
+        }
+
+        if ($this->initialOrientation != null) {
+            $transform->orientation = $this->initialOrientation;
+        } else {
+            $transform->orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
+        }
 
         $targetPosition = $entities->attach($this->aircraft, new PositionComponent);
         $targetPosition->targetPosition = $transform->position;

From dfe1e502dd2553e4d13fdb5fbef623587e67ff96 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Thu, 19 Jan 2023 16:53:53 +0100
Subject: [PATCH 09/49] refactoring

---
 src/Component/AircraftComponent.php |   7 ++
 src/Scene/LevelScene.php            |  47 +++++-------
 src/System/AircraftSystem.php       | 114 ++++++++++++++--------------
 3 files changed, 80 insertions(+), 88 deletions(-)
 create mode 100644 src/Component/AircraftComponent.php

diff --git a/src/Component/AircraftComponent.php b/src/Component/AircraftComponent.php
new file mode 100644
index 0000000..e17be2a
--- /dev/null
+++ b/src/Component/AircraftComponent.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace TowerDefense\Component;
+class AircraftComponent
+{
+
+}
diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 9be4cee..d2728a8 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -6,8 +6,6 @@
 use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
-use TowerDefense\Component\OrientationComponent;
-use TowerDefense\Component\PositionComponent;
 use TowerDefense\Debug\DebugTextOverlay;
 use TowerDefense\Renderer\TerrainRenderer;
 use TowerDefense\System\AircraftSystem;
@@ -33,7 +31,7 @@ class LevelScene extends BaseScene implements DevEntityPickerDelegate
     private VISULowPolyRenderingSystem $renderingSystem;
     private VISUCameraSystem $cameraSystem;
 
-    private array $aircraftSystems;
+    private AircraftSystem $aircraftSystem;
 
     private LPObjLoader $objectLoader;
     private array $loadedObjects = [];
@@ -77,17 +75,7 @@ public function __construct(
             $this->container->resolveVisuDispatcher()
         );
 
-        // add aircrafts
-        $initialPosition = new Vec3(500, 250, -1500);
-        $initialOrientation = new Quat();
-        $initialOrientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
-        for($i = 0; $i < 8; $i++) {
-            $initialPosition->z = $initialPosition->z + 250;
-            for($j = 0; $j < 8; $j++) {
-                $initialPosition->x = 500 + 250 * $j;
-                $this->aircraftSystems[] = new AircraftSystem($this->loadedObjects, $initialPosition->copy(), new Quat($initialOrientation->w, $initialOrientation->x, $initialOrientation->y, $initialOrientation->z));
-            }
-        }
+        $this->aircraftSystem = new AircraftSystem($this->loadedObjects);
 
         // prepare the scene
         $this->constructDevEntityPicker();
@@ -130,6 +118,7 @@ public function constructDevEntityPicker() : void
     public function __destruct()
     {
         // unregister systems
+        $this->aircraftSystem->unregister($this->entities);
         $this->cameraSystem->unregister($this->entities);
         $this->renderingSystem->unregister($this->entities);
 
@@ -150,16 +139,9 @@ public function getName() : string
      */
     private function prepareScene()
     {
-        // register components
-        $this->entities->registerComponent(PositionComponent::class);
-        $this->entities->registerComponent(OrientationComponent::class);
-
         $this->cameraSystem->register($this->entities);
         $this->renderingSystem->register($this->entities);
-
-        foreach ($this->aircraftSystems as $aircraftSystem) {
-            $aircraftSystem->register($this->entities);
-        }
+        $this->aircraftSystem->register($this->entities);
 
         // create a camera
         $cameraEntity = $this->entities->create();
@@ -186,6 +168,18 @@ private function prepareScene()
         $transform->position->y = 50;
         $transform->position->x = 100;
         $transform->position->z = -50;
+
+        // add aircrafts
+        $initialPosition = new Vec3(-400.0, 250.0, -900.0);
+        $initialOrientation = new Quat();
+        $initialOrientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
+        for ($i = 0; $i < 8; $i++) {
+            $initialPosition->z = -900.0 + (250.0 * $i);
+            for ($j = 0; $j < 8; $j++) {
+                $initialPosition->x = -400.0 + (250.0 * $j);
+                $this->aircraftSystem->spawnAircraft($this->entities, $initialPosition->copy(), $initialOrientation->copy());
+            }
+        }
     }
 
     /**
@@ -206,10 +200,7 @@ public function load() : void
     public function update() : void
     {
         $this->cameraSystem->update($this->entities);
-
-        foreach ($this->aircraftSystems as $aircraftSystem) {
-            $aircraftSystem->update($this->entities);
-        }
+        $this->aircraftSystem->update($this->entities);
     }
 
     /**
@@ -221,9 +212,7 @@ public function render(RenderContext $context) : void
     {
         DebugTextOverlay::debugString("Press 'SHIFT' + NUM for render debug: NONE=0, POS=1, VPOS=2, NORM=3, DEPTH=4, ALBEDO=5");
 
-        foreach ($this->aircraftSystems as $aircraftSystem) {
-            $aircraftSystem->render($this->entities, $context);
-        }
+        $this->aircraftSystem->render($this->entities, $context);
 
         $this->cameraSystem->render($this->entities, $context);
 
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 0eab071..93d0522 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -5,6 +5,7 @@
 use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
+use TowerDefense\Component\AircraftComponent;
 use TowerDefense\Component\PositionComponent;
 use TowerDefense\Component\OrientationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
@@ -15,47 +16,31 @@
 
 class AircraftSystem implements SystemInterface
 {
-    private int $aircraft;
-
     /**
      * Constructor
      *
      * @param array $loadedObjects
      *
      */
-    public function __construct(private array          &$loadedObjects,
-                                private readonly ?Vec3 $initialPosition = null,
-                                private readonly ?Quat $initialOrientation = null)
+    public function __construct(private array &$loadedObjects)
     {
     }
 
-    /**
-     * @inheritDoc
-     */
-    public function register(EntitiesInterface $entities): void
+    public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition, Quat $initialOrientation): void
     {
-        // create some random renderable objects
-        $this->aircraft = $entities->create();
-        $renderable = $entities->attach($this->aircraft, new DynamicRenderableModel);
-        $renderable->model = $this->loadedObjects['craft_speederA.obj'];
-        $transform = $entities->attach($this->aircraft, new Transform);
-        $transform->scale = $transform->scale * 100;
+        $newAircraft = $entities->create();
 
-        if ($this->initialPosition != null) {
-            $transform->position = $this->initialPosition;
-        } else {
-            $transform->position->y = 250;
-            $transform->position->x = 500;
-            $transform->position->z = -550;
-        }
+        $aircraftComponent = $entities->attach($newAircraft, new AircraftComponent);
 
-        if ($this->initialOrientation != null) {
-            $transform->orientation = $this->initialOrientation;
-        } else {
-            $transform->orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
-        }
+        $renderable = $entities->attach($newAircraft, new DynamicRenderableModel);
+        $renderable->model = $this->loadedObjects['craft_speederA.obj'];
+
+        $transform = $entities->attach($newAircraft, new Transform);
+        $transform->scale = $transform->scale * 100;
+        $transform->position = $initialPosition;
+        $transform->orientation = $initialOrientation;
 
-        $targetPosition = $entities->attach($this->aircraft, new PositionComponent);
+        $targetPosition = $entities->attach($newAircraft, new PositionComponent);
         $targetPosition->targetPosition = $transform->position;
         $targetPosition->startPosition = $transform->position;
         $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
@@ -63,20 +48,29 @@ public function register(EntitiesInterface $entities): void
         $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
         $targetPosition->targetPosition += $dir * 100.0;
 
-        $targetOrientation = $entities->attach($this->aircraft, new OrientationComponent);
+        $targetOrientation = $entities->attach($newAircraft, new OrientationComponent);
         $targetOrientation->targetOrientation = $transform->orientation;
     }
 
+    /**
+     * @inheritDoc
+     */
+    public function register(EntitiesInterface $entities): void
+    {
+        // register components
+        $entities->registerComponent(AircraftComponent::class);
+        $entities->registerComponent(PositionComponent::class);
+        $entities->registerComponent(OrientationComponent::class);
+    }
+
     /**
      * @inheritDoc
      */
     public function unregister(EntitiesInterface $entities): void
     {
-        $entities->detach($this->aircraft, PositionComponent::class);
-        $entities->detach($this->aircraft, OrientationComponent::class);
-        $entities->detach($this->aircraft, DynamicRenderableModel::class);
-        $entities->detach($this->aircraft, Transform::class);
-        $entities->destory($this->aircraft);
+        foreach ($entities->view(AircraftComponent::class) as $entity => $aircraft) {
+            $entities->destory($entity);
+        }
     }
 
     /**
@@ -84,34 +78,36 @@ public function unregister(EntitiesInterface $entities): void
      */
     public function update(EntitiesInterface $entities): void
     {
-        $transform = $entities->get($this->aircraft, Transform::class);
-        $targetPosition = $entities->get($this->aircraft, PositionComponent::class);
-        $travelProgress = 1.0 - (abs($transform->position->x - $targetPosition->targetPosition->x) / abs($targetPosition->targetPosition->x - $targetPosition->startPosition->x));
-        if ($travelProgress >= 1) {
-            $goUp = true;
-            if ($targetPosition->startPosition->y < $targetPosition->targetPosition->y) {
-                $goUp = false;
-            }
+        foreach ($entities->view(AircraftComponent::class) as $entity => $aircraft) {
+            $transform = $entities->get($entity, Transform::class);
+            $targetPosition = $entities->get($entity, PositionComponent::class);
+            $travelProgress = 1.0 - (abs($transform->position->x - $targetPosition->targetPosition->x) / abs($targetPosition->targetPosition->x - $targetPosition->startPosition->x));
+            if ($travelProgress >= 1) {
+                $goUp = true;
+                if ($targetPosition->startPosition->y < $targetPosition->targetPosition->y) {
+                    $goUp = false;
+                }
 
-            $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
-            $targetPosition->targetPosition = $transform->position;
-            $targetPosition->startPosition = $transform->position;
-            $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
-            $targetPosition->targetPosition += $dir * 1000.0;
-
-            if ($goUp) {
-                $dir = Quat::multiplyVec3($transform->orientation, $transform::worldUp());
-                $targetPosition->targetPosition += $dir * 100.0;
-            } else {
-                $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
-                $targetPosition->targetPosition += $dir * 100.0;
-            }
+                $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
+                $targetPosition->targetPosition = $transform->position;
+                $targetPosition->startPosition = $transform->position;
+                $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
+                $targetPosition->targetPosition += $dir * 1000.0;
+
+                if ($goUp) {
+                    $dir = Quat::multiplyVec3($transform->orientation, $transform::worldUp());
+                    $targetPosition->targetPosition += $dir * 100.0;
+                } else {
+                    $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
+                    $targetPosition->targetPosition += $dir * 100.0;
+                }
 
-            $travelProgress = 0.0;
+                $travelProgress = 0.0;
+            }
+            $newTravelProgress = min($travelProgress + 0.005, 1.0);
+            $newPosition = Vec3::lerp($targetPosition->startPosition, $targetPosition->targetPosition, $newTravelProgress);
+            $transform->setPosition($newPosition);
         }
-        $newTravelProgress = min($travelProgress + 0.005, 1.0);
-        $newPosition = Vec3::lerp($targetPosition->startPosition, $targetPosition->targetPosition, $newTravelProgress);
-        $transform->setPosition($newPosition);
     }
 
     /**

From 45b2cb965d208feeb87916d3d3cfab98c5e43628 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Thu, 19 Jan 2023 17:03:52 +0100
Subject: [PATCH 10/49] more refactoring

---
 src/Component/AircraftComponent.php    |  5 +++-
 src/Component/OrientationComponent.php | 10 --------
 src/Component/PositionComponent.php    | 11 ---------
 src/System/AircraftSystem.php          | 33 ++++++++++----------------
 4 files changed, 16 insertions(+), 43 deletions(-)
 delete mode 100644 src/Component/OrientationComponent.php
 delete mode 100644 src/Component/PositionComponent.php

diff --git a/src/Component/AircraftComponent.php b/src/Component/AircraftComponent.php
index e17be2a..574133b 100644
--- a/src/Component/AircraftComponent.php
+++ b/src/Component/AircraftComponent.php
@@ -1,7 +1,10 @@
 <?php
 
 namespace TowerDefense\Component;
+use GL\Math\Vec3;
+
 class AircraftComponent
 {
-
+    public Vec3 $startPosition;
+    public Vec3 $targetPosition;
 }
diff --git a/src/Component/OrientationComponent.php b/src/Component/OrientationComponent.php
deleted file mode 100644
index cb19767..0000000
--- a/src/Component/OrientationComponent.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace TowerDefense\Component;
-
-use GL\Math\Quat;
-
-class OrientationComponent
-{
-    public Quat $targetOrientation;
-}
\ No newline at end of file
diff --git a/src/Component/PositionComponent.php b/src/Component/PositionComponent.php
deleted file mode 100644
index bbe96ec..0000000
--- a/src/Component/PositionComponent.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-namespace TowerDefense\Component;
-
-use GL\Math\Vec3;
-
-class PositionComponent
-{
-    public Vec3 $startPosition;
-    public Vec3 $targetPosition;
-}
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 93d0522..b6346fb 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -6,8 +6,6 @@
 use GL\Math\Quat;
 use GL\Math\Vec3;
 use TowerDefense\Component\AircraftComponent;
-use TowerDefense\Component\PositionComponent;
-use TowerDefense\Component\OrientationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
@@ -40,16 +38,12 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->position = $initialPosition;
         $transform->orientation = $initialOrientation;
 
-        $targetPosition = $entities->attach($newAircraft, new PositionComponent);
-        $targetPosition->targetPosition = $transform->position;
-        $targetPosition->startPosition = $transform->position;
+        $aircraftComponent->targetPosition = $transform->position;
+        $aircraftComponent->startPosition = $transform->position;
         $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
-        $targetPosition->targetPosition += $dir * 1000.0;
+        $aircraftComponent->targetPosition += $dir * 1000.0;
         $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
-        $targetPosition->targetPosition += $dir * 100.0;
-
-        $targetOrientation = $entities->attach($newAircraft, new OrientationComponent);
-        $targetOrientation->targetOrientation = $transform->orientation;
+        $aircraftComponent->targetPosition += $dir * 100.0;
     }
 
     /**
@@ -59,8 +53,6 @@ public function register(EntitiesInterface $entities): void
     {
         // register components
         $entities->registerComponent(AircraftComponent::class);
-        $entities->registerComponent(PositionComponent::class);
-        $entities->registerComponent(OrientationComponent::class);
     }
 
     /**
@@ -80,32 +72,31 @@ public function update(EntitiesInterface $entities): void
     {
         foreach ($entities->view(AircraftComponent::class) as $entity => $aircraft) {
             $transform = $entities->get($entity, Transform::class);
-            $targetPosition = $entities->get($entity, PositionComponent::class);
-            $travelProgress = 1.0 - (abs($transform->position->x - $targetPosition->targetPosition->x) / abs($targetPosition->targetPosition->x - $targetPosition->startPosition->x));
+            $travelProgress = 1.0 - (abs($transform->position->x - $aircraft->targetPosition->x) / abs($aircraft->targetPosition->x - $aircraft->startPosition->x));
             if ($travelProgress >= 1) {
                 $goUp = true;
-                if ($targetPosition->startPosition->y < $targetPosition->targetPosition->y) {
+                if ($aircraft->startPosition->y < $aircraft->targetPosition->y) {
                     $goUp = false;
                 }
 
                 $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
-                $targetPosition->targetPosition = $transform->position;
-                $targetPosition->startPosition = $transform->position;
+                $aircraft->targetPosition = $transform->position;
+                $aircraft->startPosition = $transform->position;
                 $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
-                $targetPosition->targetPosition += $dir * 1000.0;
+                $aircraft->targetPosition += $dir * 1000.0;
 
                 if ($goUp) {
                     $dir = Quat::multiplyVec3($transform->orientation, $transform::worldUp());
-                    $targetPosition->targetPosition += $dir * 100.0;
+                    $aircraft->targetPosition += $dir * 100.0;
                 } else {
                     $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
-                    $targetPosition->targetPosition += $dir * 100.0;
+                    $aircraft->targetPosition += $dir * 100.0;
                 }
 
                 $travelProgress = 0.0;
             }
             $newTravelProgress = min($travelProgress + 0.005, 1.0);
-            $newPosition = Vec3::lerp($targetPosition->startPosition, $targetPosition->targetPosition, $newTravelProgress);
+            $newPosition = Vec3::lerp($aircraft->startPosition, $aircraft->targetPosition, $newTravelProgress);
             $transform->setPosition($newPosition);
         }
     }

From 5a159b8e0311ebc77dc4604e65ab99f3055878c9 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 20 Jan 2023 17:07:52 +0100
Subject: [PATCH 11/49] rough skeleton for the animation system (work in
 progress);

---
 src/Animation/AnimationContainerInterface.php |  8 ++++
 src/Animation/AnimationContainerType.php      | 10 +++++
 src/Animation/AnimationEasingType.php         | 11 +++++
 src/Animation/AnimationInterface.php          |  8 ++++
 src/Animation/AnimationSequence.php           | 11 +++++
 src/Animation/AnimationType.php               | 10 +++++
 src/Animation/BaseAnimation.php               | 32 +++++++++++++
 src/Animation/BaseAnimationContainer.php      | 14 ++++++
 src/Animation/ParallelAnimations.php          | 11 +++++
 .../TransformOrientationAnimation.php         | 38 ++++++++++++++++
 src/Animation/TransformPositionAnimation.php  | 38 ++++++++++++++++
 src/Animation/TransformScaleAnimation.php     | 38 ++++++++++++++++
 src/Component/AnimationComponent.php          | 10 +++++
 src/Scene/LevelScene.php                      | 32 ++++++++-----
 src/System/AnimationSystem.php                | 45 +++++++++++++++++++
 15 files changed, 304 insertions(+), 12 deletions(-)
 create mode 100644 src/Animation/AnimationContainerInterface.php
 create mode 100644 src/Animation/AnimationContainerType.php
 create mode 100644 src/Animation/AnimationEasingType.php
 create mode 100644 src/Animation/AnimationInterface.php
 create mode 100644 src/Animation/AnimationSequence.php
 create mode 100644 src/Animation/AnimationType.php
 create mode 100644 src/Animation/BaseAnimation.php
 create mode 100644 src/Animation/BaseAnimationContainer.php
 create mode 100644 src/Animation/ParallelAnimations.php
 create mode 100644 src/Animation/TransformOrientationAnimation.php
 create mode 100644 src/Animation/TransformPositionAnimation.php
 create mode 100644 src/Animation/TransformScaleAnimation.php
 create mode 100644 src/Component/AnimationComponent.php
 create mode 100644 src/System/AnimationSystem.php

diff --git a/src/Animation/AnimationContainerInterface.php b/src/Animation/AnimationContainerInterface.php
new file mode 100644
index 0000000..5eccde7
--- /dev/null
+++ b/src/Animation/AnimationContainerInterface.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+interface AnimationContainerInterface
+{
+    public function getAnimationContainerType(): AnimationContainerType;
+}
diff --git a/src/Animation/AnimationContainerType.php b/src/Animation/AnimationContainerType.php
new file mode 100644
index 0000000..6dc521c
--- /dev/null
+++ b/src/Animation/AnimationContainerType.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+enum AnimationContainerType
+{
+    case SINGLE_ANIMATION;
+    case SEQUENCE_OF_ANIMATIONS;
+    case PARALLEL_ANIMATIONS;
+}
diff --git a/src/Animation/AnimationEasingType.php b/src/Animation/AnimationEasingType.php
new file mode 100644
index 0000000..be30ce9
--- /dev/null
+++ b/src/Animation/AnimationEasingType.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+enum AnimationEasingType
+{
+    case LINEAR;
+    case EASE_IN;
+    case EASE_OUT;
+    case EASE_IN_OUT;
+}
diff --git a/src/Animation/AnimationInterface.php b/src/Animation/AnimationInterface.php
new file mode 100644
index 0000000..7d26a39
--- /dev/null
+++ b/src/Animation/AnimationInterface.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+interface AnimationInterface
+{
+    public function getAnimationType(): AnimationType;
+}
diff --git a/src/Animation/AnimationSequence.php b/src/Animation/AnimationSequence.php
new file mode 100644
index 0000000..34bfb6f
--- /dev/null
+++ b/src/Animation/AnimationSequence.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+class AnimationSequence extends BaseAnimationContainer implements AnimationContainerInterface
+{
+    public function getAnimationContainerType(): AnimationContainerType
+    {
+        return AnimationContainerType::SEQUENCE_OF_ANIMATIONS;
+    }
+}
diff --git a/src/Animation/AnimationType.php b/src/Animation/AnimationType.php
new file mode 100644
index 0000000..b692417
--- /dev/null
+++ b/src/Animation/AnimationType.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+enum AnimationType
+{
+    case TRANSFORM_ORIENTATION;
+    case TRANSFORM_SCALE;
+    case TRANSFORM_POSITION;
+}
diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
new file mode 100644
index 0000000..1b3c9a2
--- /dev/null
+++ b/src/Animation/BaseAnimation.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+class BaseAnimation implements AnimationContainerInterface
+{
+    public int $runCount = 0;
+    public int $repeatedCount = 0;
+    public int $reversedCount = 0;
+
+    public bool $finished = false;
+
+    public bool $running = false;
+
+    public function __construct(
+        public AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        public int                 $initialDelay = 0,
+        public bool                $repeat = false,
+        public int                 $repeatCount = 0,
+        public int                 $repeatDelay = 0,
+        public bool                $reverse = false,
+        public int                 $reverseCount = 0,
+        public int                 $reverseDelay = 0,
+    )
+    {
+    }
+
+    public function getAnimationContainerType(): AnimationContainerType
+    {
+        return AnimationContainerType::SINGLE_ANIMATION;
+    }
+}
diff --git a/src/Animation/BaseAnimationContainer.php b/src/Animation/BaseAnimationContainer.php
new file mode 100644
index 0000000..577bf23
--- /dev/null
+++ b/src/Animation/BaseAnimationContainer.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+class BaseAnimationContainer
+{
+    public bool $finished = false;
+
+    public bool $running = false;
+
+    public function __construct(public array $animations)
+    {
+    }
+}
diff --git a/src/Animation/ParallelAnimations.php b/src/Animation/ParallelAnimations.php
new file mode 100644
index 0000000..0da68ad
--- /dev/null
+++ b/src/Animation/ParallelAnimations.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+class ParallelAnimations extends BaseAnimationContainer implements AnimationContainerInterface
+{
+    public function getAnimationContainerType(): AnimationContainerType
+    {
+        return AnimationContainerType::PARALLEL_ANIMATIONS;
+    }
+}
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
new file mode 100644
index 0000000..f3d78bd
--- /dev/null
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+use GL\Math\Quat;
+
+class TransformOrientationAnimation extends BaseAnimation implements AnimationInterface
+{
+    public function __construct(
+        public Quat         $initialOrientation,
+        public Quat         $finalOrientation,
+        AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        int                 $initialDelay = 0,
+        bool                $repeat = false,
+        int                 $repeatCount = 0,
+        int                 $repeatDelay = 0,
+        bool                $reverse = false,
+        int                 $reverseCount = 0,
+        int                 $reverseDelay = 0,
+    )
+    {
+        parent::__construct(
+            $easingType,
+            $initialDelay,
+            $repeat,
+            $repeatCount,
+            $repeatDelay,
+            $reverse,
+            $reverseCount,
+            $reverseDelay,
+        );
+    }
+
+    public function getAnimationType(): AnimationType
+    {
+        return AnimationType::TRANSFORM_ORIENTATION;
+    }
+}
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
new file mode 100644
index 0000000..73293fd
--- /dev/null
+++ b/src/Animation/TransformPositionAnimation.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+use GL\Math\Vec3;
+
+class TransformPositionAnimation extends BaseAnimation implements AnimationInterface
+{
+    public function __construct(
+        public Vec3         $initialPosition,
+        public Vec3         $finalPosition,
+        AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        int                 $initialDelay = 0,
+        bool                $repeat = false,
+        int                 $repeatCount = 0,
+        int                 $repeatDelay = 0,
+        bool                $reverse = false,
+        int                 $reverseCount = 0,
+        int                 $reverseDelay = 0,
+    )
+    {
+        parent::__construct(
+            $easingType,
+            $initialDelay,
+            $repeat,
+            $repeatCount,
+            $repeatDelay,
+            $reverse,
+            $reverseCount,
+            $reverseDelay,
+        );
+    }
+
+    public function getAnimationType(): AnimationType
+    {
+        return AnimationType::TRANSFORM_POSITION;
+    }
+}
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
new file mode 100644
index 0000000..fc8c020
--- /dev/null
+++ b/src/Animation/TransformScaleAnimation.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+use GL\Math\Vec3;
+
+class TransformScaleAnimation extends BaseAnimation implements AnimationInterface
+{
+    public function __construct(
+        public Vec3         $initialScale,
+        public Vec3         $finalScale,
+        AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        int                 $initialDelay = 0,
+        bool                $repeat = false,
+        int                 $repeatCount = 0,
+        int                 $repeatDelay = 0,
+        bool                $reverse = false,
+        int                 $reverseCount = 0,
+        int                 $reverseDelay = 0,
+    )
+    {
+        parent::__construct(
+            $easingType,
+            $initialDelay,
+            $repeat,
+            $repeatCount,
+            $repeatDelay,
+            $reverse,
+            $reverseCount,
+            $reverseDelay,
+        );
+    }
+
+    public function getAnimationType(): AnimationType
+    {
+        return AnimationType::TRANSFORM_SCALE;
+    }
+}
diff --git a/src/Component/AnimationComponent.php b/src/Component/AnimationComponent.php
new file mode 100644
index 0000000..7e816ea
--- /dev/null
+++ b/src/Component/AnimationComponent.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace TowerDefense\Component;
+
+use TowerDefense\Animation\AnimationContainerInterface;
+
+class AnimationComponent
+{
+    public AnimationContainerInterface $animation;
+}
diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index d2728a8..8ab3d29 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -9,6 +9,7 @@
 use TowerDefense\Debug\DebugTextOverlay;
 use TowerDefense\Renderer\TerrainRenderer;
 use TowerDefense\System\AircraftSystem;
+use TowerDefense\System\AnimationSystem;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\Picker\DevEntityPicker;
 use VISU\ECS\Picker\DevEntityPickerDelegate;
@@ -32,6 +33,7 @@ class LevelScene extends BaseScene implements DevEntityPickerDelegate
     private VISUCameraSystem $cameraSystem;
 
     private AircraftSystem $aircraftSystem;
+    private AnimationSystem $animationSystem;
 
     private LPObjLoader $objectLoader;
     private array $loadedObjects = [];
@@ -57,7 +59,7 @@ public function __construct(
     {
         parent::__construct($container);
 
-        // register key handler for debugging 
+        // register key handler for debugging
         // usally a system should handle this but this is temporary
         $this->keyboardHandlerId = $this->container->resolveVisuDispatcher()->register('input.key', [$this, 'handleKeyboardEvent']);
 
@@ -65,7 +67,7 @@ public function __construct(
         $this->objectLoader = new LPObjLoader($container->resolveGL());
         $this->loadedObjects = $this->objectLoader->loadAllInDirectory(VISU_PATH_RESOURCES . '/models/spacekit');
 
-        // prepare the rendering systems 
+        // prepare the rendering systems
         $this->terrainRenderer = new TerrainRenderer($container->resolveGL(), $container->resolveShaders());
         $this->renderingSystem = new VISULowPolyRenderingSystem($container->resolveGL(), $container->resolveShaders());
         $this->renderingSystem->addGeometryRenderer($this->terrainRenderer);
@@ -75,6 +77,7 @@ public function __construct(
             $this->container->resolveVisuDispatcher()
         );
 
+        $this->animationSystem = new AnimationSystem();
         $this->aircraftSystem = new AircraftSystem($this->loadedObjects);
 
         // prepare the scene
@@ -89,16 +92,16 @@ public function constructDevEntityPicker() : void
     {
         // right now using the window framebuffer as render target
         // for entity picking, we need to test if is more efficient
-        // to use a separate framebuffer for this, that could have a much 
+        // to use a separate framebuffer for this, that could have a much
         // lower resolution
         $this->devPickerRenderTarget = $this->container->resolveWindowMain()->getRenderTarget();
 
         // not to happy with the dependency on the rendering system here
-        // but I tried a few architectures now and did not want to waste 
+        // but I tried a few architectures now and did not want to waste
         // more time on this...
         $this->devPicker = new DevEntityPicker(
             $this,
-            $this->entities, 
+            $this->entities,
             $this->container->resolveVisuDispatcher(),
             $this->devPickerRenderTarget,
             [
@@ -119,6 +122,7 @@ public function __destruct()
     {
         // unregister systems
         $this->aircraftSystem->unregister($this->entities);
+        $this->animationSystem->unregister($this->entities);
         $this->cameraSystem->unregister($this->entities);
         $this->renderingSystem->unregister($this->entities);
 
@@ -141,6 +145,7 @@ private function prepareScene()
     {
         $this->cameraSystem->register($this->entities);
         $this->renderingSystem->register($this->entities);
+        $this->animationSystem->register($this->entities);
         $this->aircraftSystem->register($this->entities);
 
         // create a camera
@@ -201,6 +206,7 @@ public function update() : void
     {
         $this->cameraSystem->update($this->entities);
         $this->aircraftSystem->update($this->entities);
+        $this->animationSystem->update($this->entities);
     }
 
     /**
@@ -214,6 +220,8 @@ public function render(RenderContext $context) : void
 
         $this->aircraftSystem->render($this->entities, $context);
 
+        $this->animationSystem->render($this->entities, $context);
+
         $this->cameraSystem->render($this->entities, $context);
 
         $backbuffer = $context->data->get(BackbufferData::class);
@@ -250,15 +258,15 @@ public function handleKeyboardEvent(KeySignal $signal) : void
             $this->renderingSystem->debugMode = VISULowPolyRenderingSystem::DEBUG_MODE_ALBEDO;
         } elseif ($signal->key === KEY::NUM_6) {
             $this->renderingSystem->debugMode = VISULowPolyRenderingSystem::DEBUG_MODE_SSAO;
-        } 
-         
+        }
+
     }
 
     /**
      * Called when the dev entity picker has selected an entity
-     * 
-     * @param int $entityId 
-     * @return void 
+     *
+     * @param int $entityId
+     * @return void
      */
     public function devEntityPickerDidSelectEntity(int $entityId): void
     {
@@ -266,9 +274,9 @@ public function devEntityPickerDidSelectEntity(int $entityId): void
     }
 
     /**
-     * Called when the dev entity picker is about to initate a selection and requires 
+     * Called when the dev entity picker is about to initate a selection and requires
      * the delegate to return the current camera data
-     * 
+     *
      * @return CameraData
      */
     public function devEntityPickerRequestsCameraData(): CameraData
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
new file mode 100644
index 0000000..7a2b4d8
--- /dev/null
+++ b/src/System/AnimationSystem.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace TowerDefense\System;
+
+use TowerDefense\Component\AnimationComponent;
+use VISU\ECS\EntitiesInterface;
+use VISU\ECS\SystemInterface;
+use VISU\Graphics\Rendering\RenderContext;
+
+class AnimationSystem implements SystemInterface
+{
+
+    /**
+     * @inheritDoc
+     */
+    public function register(EntitiesInterface $entities): void
+    {
+        // register components
+        $entities->registerComponent(AnimationComponent::class);
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function unregister(EntitiesInterface $entities): void
+    {
+        // TODO: Implement unregister() method.
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function update(EntitiesInterface $entities): void
+    {
+        // TODO: Implement update() method.
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function render(EntitiesInterface $entities, RenderContext $context): void
+    {
+        // TODO: Implement render() method.
+    }
+}

From 493f77974c4201343031619c252e0763ccf1fd2a Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Fri, 20 Jan 2023 18:03:21 +0100
Subject: [PATCH 12/49] only use a modifier which will be applied (added) to
 the target transform;

---
 src/Animation/BaseAnimation.php                 | 2 ++
 src/Animation/TransformOrientationAnimation.php | 3 +--
 src/Animation/TransformPositionAnimation.php    | 3 +--
 src/Animation/TransformScaleAnimation.php       | 3 +--
 4 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index 1b3c9a2..80c1678 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -8,6 +8,8 @@ class BaseAnimation implements AnimationContainerInterface
     public int $repeatedCount = 0;
     public int $reversedCount = 0;
 
+    public float $progress = 0.0;
+
     public bool $finished = false;
 
     public bool $running = false;
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index f3d78bd..19942f0 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -7,8 +7,7 @@
 class TransformOrientationAnimation extends BaseAnimation implements AnimationInterface
 {
     public function __construct(
-        public Quat         $initialOrientation,
-        public Quat         $finalOrientation,
+        public Quat         $modifier,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         int                 $initialDelay = 0,
         bool                $repeat = false,
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index 73293fd..0f4be85 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -7,8 +7,7 @@
 class TransformPositionAnimation extends BaseAnimation implements AnimationInterface
 {
     public function __construct(
-        public Vec3         $initialPosition,
-        public Vec3         $finalPosition,
+        public Vec3         $modifier,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         int                 $initialDelay = 0,
         bool                $repeat = false,
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index fc8c020..f36f8a4 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -7,8 +7,7 @@
 class TransformScaleAnimation extends BaseAnimation implements AnimationInterface
 {
     public function __construct(
-        public Vec3         $initialScale,
-        public Vec3         $finalScale,
+        public Vec3         $modifier,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         int                 $initialDelay = 0,
         bool                $repeat = false,

From 98187d710012ee0e4ccd7d06714ae6d2c39752ba Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Fri, 20 Jan 2023 18:18:10 +0100
Subject: [PATCH 13/49] added some comments

---
 src/Animation/AnimationContainerType.php      | 14 ++++++++++
 src/Animation/AnimationEasingType.php         | 18 ++++++++++++
 src/Animation/AnimationSequence.php           |  3 ++
 src/Animation/AnimationType.php               | 14 ++++++++++
 src/Animation/BaseAnimation.php               | 28 +++++++++++++------
 src/Animation/BaseAnimationContainer.php      |  7 +++--
 src/Animation/ParallelAnimations.php          |  3 ++
 .../TransformOrientationAnimation.php         | 14 ++++++++++
 src/Animation/TransformPositionAnimation.php  | 14 ++++++++++
 src/Animation/TransformScaleAnimation.php     | 14 ++++++++++
 10 files changed, 118 insertions(+), 11 deletions(-)

diff --git a/src/Animation/AnimationContainerType.php b/src/Animation/AnimationContainerType.php
index 6dc521c..5d37c64 100644
--- a/src/Animation/AnimationContainerType.php
+++ b/src/Animation/AnimationContainerType.php
@@ -2,9 +2,23 @@
 
 namespace TowerDefense\Animation;
 
+/**
+ * Available animation container types.
+ */
 enum AnimationContainerType
 {
+    /**
+     * Only a single animation
+     */
     case SINGLE_ANIMATION;
+
+    /**
+     * Multiple animations, one after the other
+     */
     case SEQUENCE_OF_ANIMATIONS;
+
+    /**
+     * Parallel animations, all running at the same time
+     */
     case PARALLEL_ANIMATIONS;
 }
diff --git a/src/Animation/AnimationEasingType.php b/src/Animation/AnimationEasingType.php
index be30ce9..9c8f350 100644
--- a/src/Animation/AnimationEasingType.php
+++ b/src/Animation/AnimationEasingType.php
@@ -2,10 +2,28 @@
 
 namespace TowerDefense\Animation;
 
+/**
+ * Easing types for animations.
+ */
 enum AnimationEasingType
 {
+    /**
+     * Linear easing.
+     */
     case LINEAR;
+
+    /**
+     * Ease in easing.
+     */
     case EASE_IN;
+
+    /**
+     * Ease out easing.
+     */
     case EASE_OUT;
+
+    /**
+     * Ease in and out easing.
+     */
     case EASE_IN_OUT;
 }
diff --git a/src/Animation/AnimationSequence.php b/src/Animation/AnimationSequence.php
index 34bfb6f..ab773fe 100644
--- a/src/Animation/AnimationSequence.php
+++ b/src/Animation/AnimationSequence.php
@@ -2,6 +2,9 @@
 
 namespace TowerDefense\Animation;
 
+/**
+ * A sequence of animations, played one after the other.
+ */
 class AnimationSequence extends BaseAnimationContainer implements AnimationContainerInterface
 {
     public function getAnimationContainerType(): AnimationContainerType
diff --git a/src/Animation/AnimationType.php b/src/Animation/AnimationType.php
index b692417..1450298 100644
--- a/src/Animation/AnimationType.php
+++ b/src/Animation/AnimationType.php
@@ -2,9 +2,23 @@
 
 namespace TowerDefense\Animation;
 
+/**
+ * Animation types
+ */
 enum AnimationType
 {
+    /**
+     * Orientation animation (rotation)
+     */
     case TRANSFORM_ORIENTATION;
+
+    /**
+     * Scale animation
+     */
     case TRANSFORM_SCALE;
+
+    /**
+     * Position animation (translation)
+     */
     case TRANSFORM_POSITION;
 }
diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index 80c1678..5e5b9e6 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -2,18 +2,28 @@
 
 namespace TowerDefense\Animation;
 
+/**
+ * Base for animations
+ */
 class BaseAnimation implements AnimationContainerInterface
 {
-    public int $runCount = 0;
-    public int $repeatedCount = 0;
-    public int $reversedCount = 0;
-
-    public float $progress = 0.0;
-
-    public bool $finished = false;
-
-    public bool $running = false;
+    public int $runCount = 0; // Number of times the animation has run
+    public int $repeatedCount = 0; // Number of times the animation has repeated
+    public int $reversedCount = 0; // Number of times the animation has reversed
+    public float $progress = 0.0; // Progress of the current animation run
+    public bool $finished = false; // Whether the animation has finished
+    public bool $running = false; // Whether the animation is running
 
+    /**
+     * @param AnimationEasingType $easingType The easing type of the animation
+     * @param int $initialDelay The initial delay of the animation
+     * @param bool $repeat Whether the animation should repeat
+     * @param int $repeatCount The number of times the animation should repeat
+     * @param int $repeatDelay The delay between repeats
+     * @param bool $reverse Whether the animation should reverse
+     * @param int $reverseCount The number of times the animation should reverse
+     * @param int $reverseDelay The delay between reverses
+     */
     public function __construct(
         public AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         public int                 $initialDelay = 0,
diff --git a/src/Animation/BaseAnimationContainer.php b/src/Animation/BaseAnimationContainer.php
index 577bf23..8a6f261 100644
--- a/src/Animation/BaseAnimationContainer.php
+++ b/src/Animation/BaseAnimationContainer.php
@@ -4,10 +4,13 @@
 
 class BaseAnimationContainer
 {
-    public bool $finished = false;
+    public bool $finished = false; // Are all animations finished?
 
-    public bool $running = false;
+    public bool $running = false; // Is any animation running?
 
+    /**
+     * @param BaseAnimation[] $animations Animations to run
+     */
     public function __construct(public array $animations)
     {
     }
diff --git a/src/Animation/ParallelAnimations.php b/src/Animation/ParallelAnimations.php
index 0da68ad..b3a13e3 100644
--- a/src/Animation/ParallelAnimations.php
+++ b/src/Animation/ParallelAnimations.php
@@ -2,6 +2,9 @@
 
 namespace TowerDefense\Animation;
 
+/**
+ * Parallel animations, played at the same time.
+ */
 class ParallelAnimations extends BaseAnimationContainer implements AnimationContainerInterface
 {
     public function getAnimationContainerType(): AnimationContainerType
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 19942f0..96ff841 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -4,8 +4,22 @@
 
 use GL\Math\Quat;
 
+/**
+ * Animation that rotates an object with a given orientation
+ */
 class TransformOrientationAnimation extends BaseAnimation implements AnimationInterface
 {
+    /**
+     * @param Quat $modifier The orientation to apply
+     * @param AnimationEasingType $easingType The easing type
+     * @param int $initialDelay The initial delay
+     * @param bool $repeat Whether the animation should repeat
+     * @param int $repeatCount The number of times the animation should repeat
+     * @param int $repeatDelay The delay between each repeat
+     * @param bool $reverse Whether the animation should reverse
+     * @param int $reverseCount The number of times the animation should reverse
+     * @param int $reverseDelay The delay between each reverse
+     */
     public function __construct(
         public Quat         $modifier,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index 0f4be85..cd14f17 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -4,8 +4,22 @@
 
 use GL\Math\Vec3;
 
+/**
+ * Animation that translates an object with a given position modifier
+ */
 class TransformPositionAnimation extends BaseAnimation implements AnimationInterface
 {
+    /**
+     * @param Vec3 $modifier The position modifier to apply
+     * @param AnimationEasingType $easingType The easing type
+     * @param int $initialDelay The initial delay
+     * @param bool $repeat Whether the animation should repeat
+     * @param int $repeatCount The number of times the animation should repeat
+     * @param int $repeatDelay The delay between each repeat
+     * @param bool $reverse Whether the animation should reverse
+     * @param int $reverseCount The number of times the animation should reverse
+     * @param int $reverseDelay The delay between each reverse
+     */
     public function __construct(
         public Vec3         $modifier,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index f36f8a4..83d04db 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -4,8 +4,22 @@
 
 use GL\Math\Vec3;
 
+/**
+ * Animation that scales an object with a given scale modifier
+ */
 class TransformScaleAnimation extends BaseAnimation implements AnimationInterface
 {
+    /**
+     * @param Vec3 $modifier The scale modifier to apply
+     * @param AnimationEasingType $easingType The easing type
+     * @param int $initialDelay The initial delay
+     * @param bool $repeat Whether the animation should repeat
+     * @param int $repeatCount The number of times the animation should repeat
+     * @param int $repeatDelay The delay between each repeat
+     * @param bool $reverse Whether the animation should reverse
+     * @param int $reverseCount The number of times the animation should reverse
+     * @param int $reverseDelay The delay between each reverse
+     */
     public function __construct(
         public Vec3         $modifier,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,

From 46a7cf6196a1ffa05545edc4554200318119ab35 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Fri, 20 Jan 2023 20:29:37 +0100
Subject: [PATCH 14/49] more base code

---
 src/Animation/AnimationType.php               |   5 +
 src/Animation/BaseAnimation.php               |   7 +-
 .../TransformOrientationAnimation.php         |   2 +-
 src/Animation/TransformPositionAnimation.php  |   2 +-
 src/Animation/TransformScaleAnimation.php     |   2 +-
 src/Scene/LevelScene.php                      |   2 +-
 src/System/AnimationSystem.php                | 110 +++++++++++++++++-
 7 files changed, 124 insertions(+), 6 deletions(-)

diff --git a/src/Animation/AnimationType.php b/src/Animation/AnimationType.php
index 1450298..803645b 100644
--- a/src/Animation/AnimationType.php
+++ b/src/Animation/AnimationType.php
@@ -21,4 +21,9 @@ enum AnimationType
      * Position animation (translation)
      */
     case TRANSFORM_POSITION;
+
+    /**
+     * Unknown animation type -> that should not happen
+     */
+    case UNKNOWN;
 }
diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index 5e5b9e6..02a609f 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -5,7 +5,7 @@
 /**
  * Base for animations
  */
-class BaseAnimation implements AnimationContainerInterface
+class BaseAnimation implements AnimationContainerInterface, AnimationInterface
 {
     public int $runCount = 0; // Number of times the animation has run
     public int $repeatedCount = 0; // Number of times the animation has repeated
@@ -41,4 +41,9 @@ public function getAnimationContainerType(): AnimationContainerType
     {
         return AnimationContainerType::SINGLE_ANIMATION;
     }
+
+    public function getAnimationType(): AnimationType
+    {
+        return AnimationType::UNKNOWN;
+    }
 }
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 96ff841..8badf37 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -7,7 +7,7 @@
 /**
  * Animation that rotates an object with a given orientation
  */
-class TransformOrientationAnimation extends BaseAnimation implements AnimationInterface
+class TransformOrientationAnimation extends BaseAnimation
 {
     /**
      * @param Quat $modifier The orientation to apply
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index cd14f17..473f9d8 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -7,7 +7,7 @@
 /**
  * Animation that translates an object with a given position modifier
  */
-class TransformPositionAnimation extends BaseAnimation implements AnimationInterface
+class TransformPositionAnimation extends BaseAnimation
 {
     /**
      * @param Vec3 $modifier The position modifier to apply
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index 83d04db..81bd767 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -7,7 +7,7 @@
 /**
  * Animation that scales an object with a given scale modifier
  */
-class TransformScaleAnimation extends BaseAnimation implements AnimationInterface
+class TransformScaleAnimation extends BaseAnimation
 {
     /**
      * @param Vec3 $modifier The scale modifier to apply
diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 8ab3d29..799fb16 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -77,7 +77,7 @@ public function __construct(
             $this->container->resolveVisuDispatcher()
         );
 
-        $this->animationSystem = new AnimationSystem();
+        $this->animationSystem = new AnimationSystem(60); // @TODO get from game loop
         $this->aircraftSystem = new AircraftSystem($this->loadedObjects);
 
         // prepare the scene
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 7a2b4d8..2453964 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -2,13 +2,26 @@
 
 namespace TowerDefense\System;
 
+use TowerDefense\Animation\AnimationContainerInterface;
+use TowerDefense\Animation\AnimationContainerType;
+use TowerDefense\Animation\AnimationSequence;
+use TowerDefense\Animation\AnimationType;
+use TowerDefense\Animation\BaseAnimation;
+use TowerDefense\Animation\ParallelAnimations;
+use TowerDefense\Animation\TransformOrientationAnimation;
+use TowerDefense\Animation\TransformPositionAnimation;
+use TowerDefense\Animation\TransformScaleAnimation;
 use TowerDefense\Component\AnimationComponent;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
+use VISU\Geo\Transform;
 use VISU\Graphics\Rendering\RenderContext;
 
 class AnimationSystem implements SystemInterface
 {
+    public function __construct(private readonly int $ticksPerSecond)
+    {
+    }
 
     /**
      * @inheritDoc
@@ -32,7 +45,16 @@ public function unregister(EntitiesInterface $entities): void
      */
     public function update(EntitiesInterface $entities): void
     {
-        // TODO: Implement update() method.
+        // get alle animation components and their entities
+        foreach ($entities->view(AnimationComponent::class) as $entity => $animationComponent) {
+            /** @var AnimationComponent $animationComponent */
+            // only if the animation container is not finished
+            if (!$animationComponent->animation->finished) {
+                // get the transform of the entity
+                $transform = $entities->get($entity, Transform::class);
+                $this->handleAnimationContainer($animationComponent->animation, $transform);
+            }
+        }
     }
 
     /**
@@ -42,4 +64,90 @@ public function render(EntitiesInterface $entities, RenderContext $context): voi
     {
         // TODO: Implement render() method.
     }
+
+    /**
+     * Handles an animation container
+     *
+     * @param AnimationContainerInterface $animationContainer The animation container
+     * @param Transform $transform The transform of the entity
+     * @return void
+     */
+    private function handleAnimationContainer(AnimationContainerInterface $animationContainer, Transform $transform): void
+    {
+        if (!$animationContainer->finished) {
+            // check the AnimationContainerType of $animationContainer
+            if ($animationContainer->getAnimationContainerType() == AnimationContainerType::SINGLE_ANIMATION) {
+                /** @var BaseAnimation $animationContainer */
+                // handle the animation
+                $this->handleSingleAnimation($animationContainer, $transform);
+            } elseif ($animationContainer->getAnimationContainerType() == AnimationContainerType::SEQUENCE_OF_ANIMATIONS) {
+                /** @var AnimationSequence $animationContainer */
+                // get the current animation in line
+                $this->handleAnimationContainer(array_values($animationContainer->animations)[0], $transform);
+            } elseif ($animationContainer->getAnimationContainerType() == AnimationContainerType::PARALLEL_ANIMATIONS) {
+                /** @var ParallelAnimations $animationContainer */
+                // handle all animations "in parallel"
+                foreach ($animationContainer->animations as $animation) {
+                    $this->handleAnimationContainer($animation, $transform);
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles a single animation
+     *
+     * @param BaseAnimation $animation The animation
+     * @param Transform $transform The transform of the entity
+     * @return void
+     */
+    private function handleSingleAnimation(BaseAnimation $animation, Transform $transform): void
+    {
+        if ($animation->getAnimationType() == AnimationType::TRANSFORM_POSITION) {
+            /** @var TransformPositionAnimation $animation */
+            $this->handleTransformPositionAnimation($animation, $transform);
+        } else if ($animation->getAnimationType() == AnimationType::TRANSFORM_SCALE) {
+            /** @var TransformScaleAnimation $animation */
+            $this->handleTransformScaleAnimation($animation, $transform);
+        } else if ($animation->getAnimationType() == AnimationType::TRANSFORM_ORIENTATION) {
+            /** @var TransformOrientationAnimation $animation */
+            $this->handleTransformOrientationAnimation($animation, $transform);
+        }
+    }
+
+    /**
+     * Handles a TransformPositionAnimation
+     *
+     * @param TransformPositionAnimation $animation The animation
+     * @param Transform $transform The transform of the entity
+     * @return void
+     */
+    private function handleTransformPositionAnimation(TransformPositionAnimation $animation, Transform $transform): void
+    {
+
+    }
+
+    /**
+     * Handles a TransformScaleAnimation
+     *
+     * @param TransformScaleAnimation $animation The animation
+     * @param Transform $transform The transform of the entity
+     * @return void
+     */
+    private function handleTransformScaleAnimation(TransformScaleAnimation $animation, Transform $transform): void
+    {
+
+    }
+
+    /**
+     * Handles a TransformOrientationAnimation
+     *
+     * @param TransformOrientationAnimation $animation The animation
+     * @param Transform $transform The transform of the entity
+     * @return void
+     */
+    private function handleTransformOrientationAnimation(TransformOrientationAnimation $animation, Transform $transform): void
+    {
+
+    }
 }

From c64d4d7af3b805c947667a6e9059f4762bac439c Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Fri, 20 Jan 2023 20:36:29 +0100
Subject: [PATCH 15/49] should be way faster / use less memory

---
 src/System/AnimationSystem.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 2453964..2d98fc7 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -83,7 +83,7 @@ private function handleAnimationContainer(AnimationContainerInterface $animation
             } elseif ($animationContainer->getAnimationContainerType() == AnimationContainerType::SEQUENCE_OF_ANIMATIONS) {
                 /** @var AnimationSequence $animationContainer */
                 // get the current animation in line
-                $this->handleAnimationContainer(array_values($animationContainer->animations)[0], $transform);
+                $this->handleAnimationContainer(reset($animationContainer->animations), $transform);
             } elseif ($animationContainer->getAnimationContainerType() == AnimationContainerType::PARALLEL_ANIMATIONS) {
                 /** @var ParallelAnimations $animationContainer */
                 // handle all animations "in parallel"

From e7c9ea1593fd1353d83707a85ac5697c3cc5eb2d Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 21 Jan 2023 13:45:29 +0100
Subject: [PATCH 16/49] simplified faster looping and type checking which does
 "the same as before"

---
 src/Animation/AnimationContainerInterface.php |  8 ----
 src/Animation/AnimationContainerType.php      | 24 ----------
 src/Animation/AnimationInterface.php          |  8 ----
 src/Animation/AnimationSequence.php           |  6 +--
 src/Animation/AnimationType.php               | 29 ------------
 src/Animation/BaseAnimation.php               | 12 +----
 src/Animation/ParallelAnimations.php          |  6 +--
 .../TransformOrientationAnimation.php         |  5 --
 src/Animation/TransformPositionAnimation.php  |  5 --
 src/Animation/TransformScaleAnimation.php     |  5 --
 src/System/AnimationSystem.php                | 46 ++++++++++---------
 11 files changed, 27 insertions(+), 127 deletions(-)
 delete mode 100644 src/Animation/AnimationContainerInterface.php
 delete mode 100644 src/Animation/AnimationContainerType.php
 delete mode 100644 src/Animation/AnimationInterface.php
 delete mode 100644 src/Animation/AnimationType.php

diff --git a/src/Animation/AnimationContainerInterface.php b/src/Animation/AnimationContainerInterface.php
deleted file mode 100644
index 5eccde7..0000000
--- a/src/Animation/AnimationContainerInterface.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-interface AnimationContainerInterface
-{
-    public function getAnimationContainerType(): AnimationContainerType;
-}
diff --git a/src/Animation/AnimationContainerType.php b/src/Animation/AnimationContainerType.php
deleted file mode 100644
index 5d37c64..0000000
--- a/src/Animation/AnimationContainerType.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * Available animation container types.
- */
-enum AnimationContainerType
-{
-    /**
-     * Only a single animation
-     */
-    case SINGLE_ANIMATION;
-
-    /**
-     * Multiple animations, one after the other
-     */
-    case SEQUENCE_OF_ANIMATIONS;
-
-    /**
-     * Parallel animations, all running at the same time
-     */
-    case PARALLEL_ANIMATIONS;
-}
diff --git a/src/Animation/AnimationInterface.php b/src/Animation/AnimationInterface.php
deleted file mode 100644
index 7d26a39..0000000
--- a/src/Animation/AnimationInterface.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-interface AnimationInterface
-{
-    public function getAnimationType(): AnimationType;
-}
diff --git a/src/Animation/AnimationSequence.php b/src/Animation/AnimationSequence.php
index ab773fe..14914a0 100644
--- a/src/Animation/AnimationSequence.php
+++ b/src/Animation/AnimationSequence.php
@@ -5,10 +5,6 @@
 /**
  * A sequence of animations, played one after the other.
  */
-class AnimationSequence extends BaseAnimationContainer implements AnimationContainerInterface
+class AnimationSequence extends BaseAnimationContainer
 {
-    public function getAnimationContainerType(): AnimationContainerType
-    {
-        return AnimationContainerType::SEQUENCE_OF_ANIMATIONS;
-    }
 }
diff --git a/src/Animation/AnimationType.php b/src/Animation/AnimationType.php
deleted file mode 100644
index 803645b..0000000
--- a/src/Animation/AnimationType.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * Animation types
- */
-enum AnimationType
-{
-    /**
-     * Orientation animation (rotation)
-     */
-    case TRANSFORM_ORIENTATION;
-
-    /**
-     * Scale animation
-     */
-    case TRANSFORM_SCALE;
-
-    /**
-     * Position animation (translation)
-     */
-    case TRANSFORM_POSITION;
-
-    /**
-     * Unknown animation type -> that should not happen
-     */
-    case UNKNOWN;
-}
diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index 02a609f..b19ab2b 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -5,7 +5,7 @@
 /**
  * Base for animations
  */
-class BaseAnimation implements AnimationContainerInterface, AnimationInterface
+class BaseAnimation
 {
     public int $runCount = 0; // Number of times the animation has run
     public int $repeatedCount = 0; // Number of times the animation has repeated
@@ -36,14 +36,4 @@ public function __construct(
     )
     {
     }
-
-    public function getAnimationContainerType(): AnimationContainerType
-    {
-        return AnimationContainerType::SINGLE_ANIMATION;
-    }
-
-    public function getAnimationType(): AnimationType
-    {
-        return AnimationType::UNKNOWN;
-    }
 }
diff --git a/src/Animation/ParallelAnimations.php b/src/Animation/ParallelAnimations.php
index b3a13e3..c6842e0 100644
--- a/src/Animation/ParallelAnimations.php
+++ b/src/Animation/ParallelAnimations.php
@@ -5,10 +5,6 @@
 /**
  * Parallel animations, played at the same time.
  */
-class ParallelAnimations extends BaseAnimationContainer implements AnimationContainerInterface
+class ParallelAnimations extends BaseAnimationContainer
 {
-    public function getAnimationContainerType(): AnimationContainerType
-    {
-        return AnimationContainerType::PARALLEL_ANIMATIONS;
-    }
 }
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 8badf37..3ffede1 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -43,9 +43,4 @@ public function __construct(
             $reverseDelay,
         );
     }
-
-    public function getAnimationType(): AnimationType
-    {
-        return AnimationType::TRANSFORM_ORIENTATION;
-    }
 }
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index 473f9d8..d24ea20 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -43,9 +43,4 @@ public function __construct(
             $reverseDelay,
         );
     }
-
-    public function getAnimationType(): AnimationType
-    {
-        return AnimationType::TRANSFORM_POSITION;
-    }
 }
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index 81bd767..eafbf47 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -43,9 +43,4 @@ public function __construct(
             $reverseDelay,
         );
     }
-
-    public function getAnimationType(): AnimationType
-    {
-        return AnimationType::TRANSFORM_SCALE;
-    }
 }
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 2d98fc7..5ea3de3 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -68,26 +68,31 @@ public function render(EntitiesInterface $entities, RenderContext $context): voi
     /**
      * Handles an animation container
      *
-     * @param AnimationContainerInterface $animationContainer The animation container
+     * @param object $animationContainer The animation container
      * @param Transform $transform The transform of the entity
      * @return void
      */
-    private function handleAnimationContainer(AnimationContainerInterface $animationContainer, Transform $transform): void
+    private function handleAnimationContainer(object $animationContainer, Transform $transform): void
     {
-        if (!$animationContainer->finished) {
-            // check the AnimationContainerType of $animationContainer
-            if ($animationContainer->getAnimationContainerType() == AnimationContainerType::SINGLE_ANIMATION) {
-                /** @var BaseAnimation $animationContainer */
-                // handle the animation
-                $this->handleSingleAnimation($animationContainer, $transform);
-            } elseif ($animationContainer->getAnimationContainerType() == AnimationContainerType::SEQUENCE_OF_ANIMATIONS) {
-                /** @var AnimationSequence $animationContainer */
-                // get the current animation in line
-                $this->handleAnimationContainer(reset($animationContainer->animations), $transform);
-            } elseif ($animationContainer->getAnimationContainerType() == AnimationContainerType::PARALLEL_ANIMATIONS) {
-                /** @var ParallelAnimations $animationContainer */
-                // handle all animations "in parallel"
-                foreach ($animationContainer->animations as $animation) {
+        // check the AnimationContainerType of $animationContainer
+        if ($animationContainer instanceof BaseAnimation) {
+            // handle the animation
+            $this->handleSingleAnimation($animationContainer, $transform);
+        } elseif ($animationContainer instanceof AnimationSequence) {
+            // get the current animation in line
+            foreach ($animationContainer->animations as $animation) {
+                if (!$animation->finished) {
+                    // handle the animation
+                    $this->handleAnimationContainer($animation, $transform);
+                    break;
+                }
+            }
+            $this->handleAnimationContainer(reset($animationContainer->animations), $transform);
+        } elseif ($animationContainer instanceof ParallelAnimations) {
+            // handle all animations "in parallel"
+            foreach ($animationContainer->animations as $animation) {
+                if (!$animation->finished) {
+                    // handle the animation
                     $this->handleAnimationContainer($animation, $transform);
                 }
             }
@@ -103,14 +108,11 @@ private function handleAnimationContainer(AnimationContainerInterface $animation
      */
     private function handleSingleAnimation(BaseAnimation $animation, Transform $transform): void
     {
-        if ($animation->getAnimationType() == AnimationType::TRANSFORM_POSITION) {
-            /** @var TransformPositionAnimation $animation */
+        if ($animation instanceof TransformPositionAnimation) {
             $this->handleTransformPositionAnimation($animation, $transform);
-        } else if ($animation->getAnimationType() == AnimationType::TRANSFORM_SCALE) {
-            /** @var TransformScaleAnimation $animation */
+        } else if ($animation instanceof TransformScaleAnimation) {
             $this->handleTransformScaleAnimation($animation, $transform);
-        } else if ($animation->getAnimationType() == AnimationType::TRANSFORM_ORIENTATION) {
-            /** @var TransformOrientationAnimation $animation */
+        } else if ($animation instanceof TransformOrientationAnimation) {
             $this->handleTransformOrientationAnimation($animation, $transform);
         }
     }

From 82a51a5030cd2c6db7211e4355aa5e112a25bab4 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 21 Jan 2023 13:47:38 +0100
Subject: [PATCH 17/49] removed invalid use statements

---
 src/System/AnimationSystem.php | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 5ea3de3..c230c94 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -2,10 +2,7 @@
 
 namespace TowerDefense\System;
 
-use TowerDefense\Animation\AnimationContainerInterface;
-use TowerDefense\Animation\AnimationContainerType;
 use TowerDefense\Animation\AnimationSequence;
-use TowerDefense\Animation\AnimationType;
 use TowerDefense\Animation\BaseAnimation;
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;

From dd01725028edbfd445c3b59116afe27b8602dc84 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 21 Jan 2023 13:50:10 +0100
Subject: [PATCH 18/49] we do not need this call anymore...

---
 src/System/AnimationSystem.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index c230c94..0a3e1ca 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -84,7 +84,6 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     break;
                 }
             }
-            $this->handleAnimationContainer(reset($animationContainer->animations), $transform);
         } elseif ($animationContainer instanceof ParallelAnimations) {
             // handle all animations "in parallel"
             foreach ($animationContainer->animations as $animation) {

From 57180aadb8257620ba9c7959a69cf028cc47ebcd Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Sat, 21 Jan 2023 15:34:21 +0100
Subject: [PATCH 19/49] work in progress

---
 src/Component/AircraftComponent.php  | 10 -----
 src/Component/AnimationComponent.php |  4 +-
 src/Scene/LevelScene.php             | 12 ++---
 src/System/AircraftSystem.php        | 65 ++++++++++------------------
 4 files changed, 27 insertions(+), 64 deletions(-)
 delete mode 100644 src/Component/AircraftComponent.php

diff --git a/src/Component/AircraftComponent.php b/src/Component/AircraftComponent.php
deleted file mode 100644
index 574133b..0000000
--- a/src/Component/AircraftComponent.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace TowerDefense\Component;
-use GL\Math\Vec3;
-
-class AircraftComponent
-{
-    public Vec3 $startPosition;
-    public Vec3 $targetPosition;
-}
diff --git a/src/Component/AnimationComponent.php b/src/Component/AnimationComponent.php
index 7e816ea..8d9ab73 100644
--- a/src/Component/AnimationComponent.php
+++ b/src/Component/AnimationComponent.php
@@ -2,9 +2,7 @@
 
 namespace TowerDefense\Component;
 
-use TowerDefense\Animation\AnimationContainerInterface;
-
 class AnimationComponent
 {
-    public AnimationContainerInterface $animation;
+    public object $animation;
 }
diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 799fb16..d3b3564 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -174,17 +174,11 @@ private function prepareScene()
         $transform->position->x = 100;
         $transform->position->z = -50;
 
-        // add aircrafts
-        $initialPosition = new Vec3(-400.0, 250.0, -900.0);
+        // add aircraft
+        $initialPosition = new Vec3(0.0, 250.0, -250.0);
         $initialOrientation = new Quat();
         $initialOrientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
-        for ($i = 0; $i < 8; $i++) {
-            $initialPosition->z = -900.0 + (250.0 * $i);
-            for ($j = 0; $j < 8; $j++) {
-                $initialPosition->x = -400.0 + (250.0 * $j);
-                $this->aircraftSystem->spawnAircraft($this->entities, $initialPosition->copy(), $initialOrientation->copy());
-            }
-        }
+        $this->aircraftSystem->spawnAircraft($this->entities, $initialPosition->copy(), $initialOrientation->copy());
     }
 
     /**
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index b6346fb..fe78555 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -2,10 +2,11 @@
 
 namespace TowerDefense\System;
 
-use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
-use TowerDefense\Component\AircraftComponent;
+use TowerDefense\Animation\AnimationEasingType;
+use TowerDefense\Animation\TransformPositionAnimation;
+use TowerDefense\Component\AnimationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
@@ -14,6 +15,11 @@
 
 class AircraftSystem implements SystemInterface
 {
+    /**
+     * @var int[] $aircrafts The list of aircraft entity ids
+     */
+    private array $aircrafts = [];
+
     /**
      * Constructor
      *
@@ -27,8 +33,7 @@ public function __construct(private array &$loadedObjects)
     public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition, Quat $initialOrientation): void
     {
         $newAircraft = $entities->create();
-
-        $aircraftComponent = $entities->attach($newAircraft, new AircraftComponent);
+        $this->aircrafts[] = $newAircraft;
 
         $renderable = $entities->attach($newAircraft, new DynamicRenderableModel);
         $renderable->model = $this->loadedObjects['craft_speederA.obj'];
@@ -38,12 +43,18 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->position = $initialPosition;
         $transform->orientation = $initialOrientation;
 
-        $aircraftComponent->targetPosition = $transform->position;
-        $aircraftComponent->startPosition = $transform->position;
-        $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
-        $aircraftComponent->targetPosition += $dir * 1000.0;
-        $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
-        $aircraftComponent->targetPosition += $dir * 100.0;
+        $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
+        $animationComponent->animation = new TransformPositionAnimation(
+            new Vec3(500.0, 0.0, 0.0),
+            AnimationEasingType::LINEAR,
+            0,
+            false,
+            0,
+            0,
+            false,
+            0,
+            0,
+        );
     }
 
     /**
@@ -51,8 +62,6 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
      */
     public function register(EntitiesInterface $entities): void
     {
-        // register components
-        $entities->registerComponent(AircraftComponent::class);
     }
 
     /**
@@ -60,8 +69,8 @@ public function register(EntitiesInterface $entities): void
      */
     public function unregister(EntitiesInterface $entities): void
     {
-        foreach ($entities->view(AircraftComponent::class) as $entity => $aircraft) {
-            $entities->destory($entity);
+        foreach ($this->aircrafts as $aircraft) {
+            $entities->destory($aircraft);
         }
     }
 
@@ -70,35 +79,7 @@ public function unregister(EntitiesInterface $entities): void
      */
     public function update(EntitiesInterface $entities): void
     {
-        foreach ($entities->view(AircraftComponent::class) as $entity => $aircraft) {
-            $transform = $entities->get($entity, Transform::class);
-            $travelProgress = 1.0 - (abs($transform->position->x - $aircraft->targetPosition->x) / abs($aircraft->targetPosition->x - $aircraft->startPosition->x));
-            if ($travelProgress >= 1) {
-                $goUp = true;
-                if ($aircraft->startPosition->y < $aircraft->targetPosition->y) {
-                    $goUp = false;
-                }
 
-                $transform->orientation->rotate(GLM::radians(180.0), new Vec3(0.0, 1.0, 0.0));
-                $aircraft->targetPosition = $transform->position;
-                $aircraft->startPosition = $transform->position;
-                $dir = Quat::multiplyVec3($transform->orientation, $transform::worldBackward());
-                $aircraft->targetPosition += $dir * 1000.0;
-
-                if ($goUp) {
-                    $dir = Quat::multiplyVec3($transform->orientation, $transform::worldUp());
-                    $aircraft->targetPosition += $dir * 100.0;
-                } else {
-                    $dir = Quat::multiplyVec3($transform->orientation, $transform::worldDown());
-                    $aircraft->targetPosition += $dir * 100.0;
-                }
-
-                $travelProgress = 0.0;
-            }
-            $newTravelProgress = min($travelProgress + 0.005, 1.0);
-            $newPosition = Vec3::lerp($aircraft->startPosition, $aircraft->targetPosition, $newTravelProgress);
-            $transform->setPosition($newPosition);
-        }
     }
 
     /**

From 64781c9ea21e24fc34f695e56c1320f7ba79cd71 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Sat, 21 Jan 2023 16:20:07 +0100
Subject: [PATCH 20/49] very basic stuff

---
 src/Animation/BaseAnimation.php               | 10 +++--
 .../TransformOrientationAnimation.php         |  9 ++--
 src/Animation/TransformPositionAnimation.php  | 12 +++--
 src/Animation/TransformScaleAnimation.php     |  9 ++--
 src/System/AircraftSystem.php                 |  3 +-
 src/System/AnimationSystem.php                | 44 +++++++++++++++++--
 6 files changed, 71 insertions(+), 16 deletions(-)

diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index b19ab2b..8149278 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -13,18 +13,22 @@ class BaseAnimation
     public float $progress = 0.0; // Progress of the current animation run
     public bool $finished = false; // Whether the animation has finished
     public bool $running = false; // Whether the animation is running
+    public int $requiredTicks = 0; // Number of ticks required to complete the animation
+    public int $currentTick = 0; // Current tick of the animation
 
     /**
+     * @param int $duration The duration of the animation in milliseconds
      * @param AnimationEasingType $easingType The easing type of the animation
-     * @param int $initialDelay The initial delay of the animation
+     * @param int $initialDelay The initial delay of the animation in milliseconds
      * @param bool $repeat Whether the animation should repeat
      * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between repeats
+     * @param int $repeatDelay The delay between repeats in milliseconds
      * @param bool $reverse Whether the animation should reverse
      * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between reverses
+     * @param int $reverseDelay The delay between reverses in milliseconds
      */
     public function __construct(
+        public int                 $duration,
         public AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         public int                 $initialDelay = 0,
         public bool                $repeat = false,
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 3ffede1..9dd596e 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -11,17 +11,19 @@ class TransformOrientationAnimation extends BaseAnimation
 {
     /**
      * @param Quat $modifier The orientation to apply
+     * @param int $duration The duration of the animation in milliseconds
      * @param AnimationEasingType $easingType The easing type
-     * @param int $initialDelay The initial delay
+     * @param int $initialDelay The initial delay in milliseconds
      * @param bool $repeat Whether the animation should repeat
      * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between each repeat
+     * @param int $repeatDelay The delay between each repeat in milliseconds
      * @param bool $reverse Whether the animation should reverse
      * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between each reverse
+     * @param int $reverseDelay The delay between each reverse in milliseconds
      */
     public function __construct(
         public Quat         $modifier,
+        int                 $duration,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         int                 $initialDelay = 0,
         bool                $repeat = false,
@@ -33,6 +35,7 @@ public function __construct(
     )
     {
         parent::__construct(
+            $duration,
             $easingType,
             $initialDelay,
             $repeat,
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index d24ea20..d6c5a66 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -9,19 +9,24 @@
  */
 class TransformPositionAnimation extends BaseAnimation
 {
+    public Vec3 $initialPosition;
+    public Vec3 $targetPosition;
+
     /**
      * @param Vec3 $modifier The position modifier to apply
+     * @param int $duration The duration of the animation in milliseconds
      * @param AnimationEasingType $easingType The easing type
-     * @param int $initialDelay The initial delay
+     * @param int $initialDelay The initial delay in milliseconds
      * @param bool $repeat Whether the animation should repeat
      * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between each repeat
+     * @param int $repeatDelay The delay between each repeat in milliseconds
      * @param bool $reverse Whether the animation should reverse
      * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between each reverse
+     * @param int $reverseDelay The delay between each reverse in milliseconds
      */
     public function __construct(
         public Vec3         $modifier,
+        int                 $duration,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         int                 $initialDelay = 0,
         bool                $repeat = false,
@@ -33,6 +38,7 @@ public function __construct(
     )
     {
         parent::__construct(
+            $duration,
             $easingType,
             $initialDelay,
             $repeat,
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index eafbf47..fbb7525 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -11,17 +11,19 @@ class TransformScaleAnimation extends BaseAnimation
 {
     /**
      * @param Vec3 $modifier The scale modifier to apply
+     * @param int $duration The duration of the animation in milliseconds
      * @param AnimationEasingType $easingType The easing type
-     * @param int $initialDelay The initial delay
+     * @param int $initialDelay The initial delay in milliseconds
      * @param bool $repeat Whether the animation should repeat
      * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between each repeat
+     * @param int $repeatDelay The delay between each repeat in milliseconds
      * @param bool $reverse Whether the animation should reverse
      * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between each reverse
+     * @param int $reverseDelay The delay between each reverse in milliseconds
      */
     public function __construct(
         public Vec3         $modifier,
+        int                 $duration,
         AnimationEasingType $easingType = AnimationEasingType::LINEAR,
         int                 $initialDelay = 0,
         bool                $repeat = false,
@@ -33,6 +35,7 @@ public function __construct(
     )
     {
         parent::__construct(
+            $duration,
             $easingType,
             $initialDelay,
             $repeat,
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index fe78555..d724b2c 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -45,7 +45,8 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
 
         $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
         $animationComponent->animation = new TransformPositionAnimation(
-            new Vec3(500.0, 0.0, 0.0),
+            new Vec3(-500.0, -200.0, 0.0),
+            2000,
             AnimationEasingType::LINEAR,
             0,
             false,
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 0a3e1ca..497552f 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -2,6 +2,7 @@
 
 namespace TowerDefense\System;
 
+use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationSequence;
 use TowerDefense\Animation\BaseAnimation;
 use TowerDefense\Animation\ParallelAnimations;
@@ -113,6 +114,35 @@ private function handleSingleAnimation(BaseAnimation $animation, Transform $tran
         }
     }
 
+    /**
+     * Handles the stuff for the base animation
+     *
+     * @param BaseAnimation $animation The animation
+     * @param Transform $transform The transform of the entity
+     *
+     * @return void
+     */
+    private function handleBaseAnimation(BaseAnimation $animation, Transform $transform): void
+    {
+        if (!$animation->running) {
+            $animation->requiredTicks = ($animation->duration / 1000.0) * $this->ticksPerSecond;
+            $animation->currentTick = 0;
+        }
+        if ($animation->currentTick >= $animation->requiredTicks) {
+            $animation->finished = true;
+        } else {
+            $animation->currentTick++;
+        }
+    }
+
+    private function handleBaseAnimationFinalizer(BaseAnimation $animation, Transform $transform): void
+    {
+        if ($animation->currentTick >= $animation->requiredTicks) {
+            $animation->finished = true;
+            $animation->running = false;
+        }
+    }
+
     /**
      * Handles a TransformPositionAnimation
      *
@@ -122,7 +152,15 @@ private function handleSingleAnimation(BaseAnimation $animation, Transform $tran
      */
     private function handleTransformPositionAnimation(TransformPositionAnimation $animation, Transform $transform): void
     {
-
+        $this->handleBaseAnimation($animation, $transform);
+        if (!$animation->running) {
+            $animation->initialPosition = $transform->position->copy();
+            $animation->targetPosition = $animation->initialPosition + $animation->modifier;
+            $animation->running = true;
+        }
+        $newPosition = Vec3::lerp($animation->initialPosition, $animation->targetPosition, (1.0 / $animation->requiredTicks) * $animation->currentTick);
+        $transform->setPosition($newPosition);
+        $this->handleBaseAnimationFinalizer($animation, $transform);
     }
 
     /**
@@ -134,7 +172,7 @@ private function handleTransformPositionAnimation(TransformPositionAnimation $an
      */
     private function handleTransformScaleAnimation(TransformScaleAnimation $animation, Transform $transform): void
     {
-
+        $this->handleBaseAnimation($animation, $transform);
     }
 
     /**
@@ -146,6 +184,6 @@ private function handleTransformScaleAnimation(TransformScaleAnimation $animatio
      */
     private function handleTransformOrientationAnimation(TransformOrientationAnimation $animation, Transform $transform): void
     {
-
+        $this->handleBaseAnimation($animation, $transform);
     }
 }

From 1ab8b256126c3100fd14272f732ece7ac84a6935 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Sat, 21 Jan 2023 16:21:22 +0100
Subject: [PATCH 21/49] not needed here like that

---
 src/System/AnimationSystem.php | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 497552f..6cd20a9 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -128,11 +128,7 @@ private function handleBaseAnimation(BaseAnimation $animation, Transform $transf
             $animation->requiredTicks = ($animation->duration / 1000.0) * $this->ticksPerSecond;
             $animation->currentTick = 0;
         }
-        if ($animation->currentTick >= $animation->requiredTicks) {
-            $animation->finished = true;
-        } else {
-            $animation->currentTick++;
-        }
+        $animation->currentTick++;
     }
 
     private function handleBaseAnimationFinalizer(BaseAnimation $animation, Transform $transform): void

From 47cd7672d6d0affc4e68a00ae31bce9cf5f0c6c4 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sun, 22 Jan 2023 18:00:41 +0100
Subject: [PATCH 22/49] testing chaining

---
 src/System/AircraftSystem.php | 37 +++++++++++++++++++++++------------
 1 file changed, 25 insertions(+), 12 deletions(-)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index d724b2c..89fdb8c 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -5,6 +5,7 @@
 use GL\Math\Quat;
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
+use TowerDefense\Animation\AnimationSequence;
 use TowerDefense\Animation\TransformPositionAnimation;
 use TowerDefense\Component\AnimationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
@@ -44,18 +45,30 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->orientation = $initialOrientation;
 
         $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
-        $animationComponent->animation = new TransformPositionAnimation(
-            new Vec3(-500.0, -200.0, 0.0),
-            2000,
-            AnimationEasingType::LINEAR,
-            0,
-            false,
-            0,
-            0,
-            false,
-            0,
-            0,
-        );
+        $animationComponent->animation = new AnimationSequence([
+            new TransformPositionAnimation(
+                new Vec3(-500.0, -200.0, 0.0),
+                2000,
+                AnimationEasingType::LINEAR,
+                0,
+                false,
+                0,
+                0,
+                false,
+                0,
+                0),
+            new TransformPositionAnimation(
+                new Vec3(0.0, 500.0, 0.0),
+                2000,
+                AnimationEasingType::LINEAR,
+                0,
+                false,
+                0,
+                0,
+                false,
+                0,
+                0)
+        ]);
     }
 
     /**

From 48d754c1c2fcea8fc64b9292615dfcd9bf58181c Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sun, 22 Jan 2023 18:11:45 +0100
Subject: [PATCH 23/49] that does not quite work yet...

---
 .../TransformOrientationAnimation.php         |  3 ++
 src/System/AircraftSystem.php                 | 40 ++++++++++++++-----
 src/System/AnimationSystem.php                | 11 ++++-
 3 files changed, 42 insertions(+), 12 deletions(-)

diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 9dd596e..ec70e26 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -9,6 +9,9 @@
  */
 class TransformOrientationAnimation extends BaseAnimation
 {
+    public Quat $initialOrientation;
+    public Quat $targetOrientation;
+
     /**
      * @param Quat $modifier The orientation to apply
      * @param int $duration The duration of the animation in milliseconds
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 89fdb8c..84c2fb2 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -2,10 +2,13 @@
 
 namespace TowerDefense\System;
 
+use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
+use TowerDefense\Animation\ParallelAnimations;
+use TowerDefense\Animation\TransformOrientationAnimation;
 use TowerDefense\Animation\TransformPositionAnimation;
 use TowerDefense\Component\AnimationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
@@ -45,6 +48,8 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->orientation = $initialOrientation;
 
         $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
+        $orientation = new Quat();
+        $orientation->rotate(GLM::radians(-180.0), new Vec3(0.0, 1.0, 0.0));
         $animationComponent->animation = new AnimationSequence([
             new TransformPositionAnimation(
                 new Vec3(-500.0, -200.0, 0.0),
@@ -57,17 +62,30 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
                 false,
                 0,
                 0),
-            new TransformPositionAnimation(
-                new Vec3(0.0, 500.0, 0.0),
-                2000,
-                AnimationEasingType::LINEAR,
-                0,
-                false,
-                0,
-                0,
-                false,
-                0,
-                0)
+            new ParallelAnimations([
+                new TransformPositionAnimation(
+                    new Vec3(0.0, 500.0, 0.0),
+                    2000,
+                    AnimationEasingType::LINEAR,
+                    0,
+                    false,
+                    0,
+                    0,
+                    false,
+                    0,
+                    0),
+                new TransformOrientationAnimation(
+                    $orientation,
+                    2000,
+                    AnimationEasingType::LINEAR,
+                    0,
+                    false,
+                    0,
+                    0,
+                    false,
+                    0,
+                    0)
+            ])
         ]);
     }
 
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 6cd20a9..38db4fa 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -2,6 +2,7 @@
 
 namespace TowerDefense\System;
 
+use GL\Math\Quat;
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationSequence;
 use TowerDefense\Animation\BaseAnimation;
@@ -154,7 +155,7 @@ private function handleTransformPositionAnimation(TransformPositionAnimation $an
             $animation->targetPosition = $animation->initialPosition + $animation->modifier;
             $animation->running = true;
         }
-        $newPosition = Vec3::lerp($animation->initialPosition, $animation->targetPosition, (1.0 / $animation->requiredTicks) * $animation->currentTick);
+        $newPosition = Vec3::slerp($animation->initialPosition, $animation->targetPosition, (1.0 / $animation->requiredTicks) * $animation->currentTick);
         $transform->setPosition($newPosition);
         $this->handleBaseAnimationFinalizer($animation, $transform);
     }
@@ -181,5 +182,13 @@ private function handleTransformScaleAnimation(TransformScaleAnimation $animatio
     private function handleTransformOrientationAnimation(TransformOrientationAnimation $animation, Transform $transform): void
     {
         $this->handleBaseAnimation($animation, $transform);
+        if (!$animation->running) {
+            $animation->initialOrientation = $transform->orientation->copy();
+            $animation->targetOrientation = $animation->initialOrientation + $animation->modifier;
+            $animation->running = true;
+        }
+        $newOrientation = Quat::slerp($animation->initialOrientation, $animation->targetOrientation, (1.0 / $animation->requiredTicks) * $animation->currentTick);
+        $transform->setOrientation($newOrientation);
+        $this->handleBaseAnimationFinalizer($animation, $transform);
     }
 }

From ec9a2258b85a7a8d24e0a3f0659d29eb48428a9e Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sun, 22 Jan 2023 18:55:11 +0100
Subject: [PATCH 24/49] quats are multiplied. not "added".

---
 src/System/AnimationSystem.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 38db4fa..a699b4c 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -184,7 +184,7 @@ private function handleTransformOrientationAnimation(TransformOrientationAnimati
         $this->handleBaseAnimation($animation, $transform);
         if (!$animation->running) {
             $animation->initialOrientation = $transform->orientation->copy();
-            $animation->targetOrientation = $animation->initialOrientation + $animation->modifier;
+            $animation->targetOrientation = $animation->initialOrientation * $animation->modifier;
             $animation->running = true;
         }
         $newOrientation = Quat::slerp($animation->initialOrientation, $animation->targetOrientation, (1.0 / $animation->requiredTicks) * $animation->currentTick);

From ba2fe9bd49e93c91b4c1279b56406ba39d55602d Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sun, 22 Jan 2023 19:09:37 +0100
Subject: [PATCH 25/49] demo of parallel and sequence animations mix

---
 src/System/AircraftSystem.php  | 15 +++++++++++--
 src/System/AnimationSystem.php | 40 ++++++++++++++++++++++------------
 2 files changed, 39 insertions(+), 16 deletions(-)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 84c2fb2..37d5c17 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -76,7 +76,7 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
                     0),
                 new TransformOrientationAnimation(
                     $orientation,
-                    2000,
+                    500,
                     AnimationEasingType::LINEAR,
                     0,
                     false,
@@ -85,7 +85,18 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
                     false,
                     0,
                     0)
-            ])
+            ]),
+            new TransformPositionAnimation(
+                new Vec3(500.0, 0.0, 0.0),
+                2000,
+                AnimationEasingType::LINEAR,
+                0,
+                false,
+                0,
+                0,
+                false,
+                0,
+                0),
         ]);
     }
 
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index a699b4c..7c96a67 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -77,23 +77,35 @@ private function handleAnimationContainer(object $animationContainer, Transform
         if ($animationContainer instanceof BaseAnimation) {
             // handle the animation
             $this->handleSingleAnimation($animationContainer, $transform);
-        } elseif ($animationContainer instanceof AnimationSequence) {
-            // get the current animation in line
-            foreach ($animationContainer->animations as $animation) {
-                if (!$animation->finished) {
-                    // handle the animation
-                    $this->handleAnimationContainer($animation, $transform);
-                    break;
+        } else {
+            $finishedAnimations = 0;
+            if ($animationContainer instanceof AnimationSequence) {
+                // get the current animation in line
+                foreach ($animationContainer->animations as $animation) {
+                    if (!$animation->finished) {
+                        $animationContainer->running = true;
+                        // handle the animation
+                        $this->handleAnimationContainer($animation, $transform);
+                        break;
+                    } else {
+                        $finishedAnimations++;
+                    }
                 }
-            }
-        } elseif ($animationContainer instanceof ParallelAnimations) {
-            // handle all animations "in parallel"
-            foreach ($animationContainer->animations as $animation) {
-                if (!$animation->finished) {
-                    // handle the animation
-                    $this->handleAnimationContainer($animation, $transform);
+            } elseif ($animationContainer instanceof ParallelAnimations) {
+                // handle all animations "in parallel"
+                foreach ($animationContainer->animations as $animation) {
+                    if (!$animation->finished) {
+                        $animationContainer->running = true;
+                        // handle the animation
+                        $this->handleAnimationContainer($animation, $transform);
+                    } else {
+                        $finishedAnimations++;
+                    }
                 }
             }
+            if ($finishedAnimations == count($animationContainer->animations)) {
+                $animationContainer->finished = true;
+            }
         }
     }
 

From a6907083c150495051c114d3c33da3d38ef29574 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Mon, 23 Jan 2023 14:58:33 +0100
Subject: [PATCH 26/49] refactoring

---
 src/Animation/BaseAnimation.php               | 30 ++++++------
 src/Animation/BaseAnimationContainer.php      |  2 +-
 .../TransformOrientationAnimation.php         | 32 ++++++------
 src/Animation/TransformPositionAnimation.php  | 32 ++++++------
 src/Animation/TransformScaleAnimation.php     | 32 ++++++------
 src/System/AircraftSystem.php                 | 49 ++-----------------
 6 files changed, 68 insertions(+), 109 deletions(-)

diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index 8149278..b5c8070 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -18,25 +18,25 @@ class BaseAnimation
 
     /**
      * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType $easingType The easing type of the animation
-     * @param int $initialDelay The initial delay of the animation in milliseconds
+     * @param AnimationEasingType|null $easingType The easing type of the animation
+     * @param int|null $initialDelay The initial delay of the animation in milliseconds
      * @param bool $repeat Whether the animation should repeat
-     * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between repeats in milliseconds
+     * @param int|null $repeatCount The number of times the animation should repeat
+     * @param int|null $repeatDelay The delay between repeats in milliseconds
      * @param bool $reverse Whether the animation should reverse
-     * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between reverses in milliseconds
+     * @param int|null $reverseCount The number of times the animation should reverse
+     * @param int|null $reverseDelay The delay between reverses in milliseconds
      */
     public function __construct(
-        public int                 $duration,
-        public AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        public int                 $initialDelay = 0,
-        public bool                $repeat = false,
-        public int                 $repeatCount = 0,
-        public int                 $repeatDelay = 0,
-        public bool                $reverse = false,
-        public int                 $reverseCount = 0,
-        public int                 $reverseDelay = 0,
+        public int                  $duration,
+        public ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        public ?int                 $initialDelay = 0,
+        public ?bool                $repeat = false,
+        public ?int                 $repeatCount = 0,
+        public ?int                 $repeatDelay = 0,
+        public ?bool                $reverse = false,
+        public ?int                 $reverseCount = 0,
+        public ?int                 $reverseDelay = 0,
     )
     {
     }
diff --git a/src/Animation/BaseAnimationContainer.php b/src/Animation/BaseAnimationContainer.php
index 8a6f261..c383899 100644
--- a/src/Animation/BaseAnimationContainer.php
+++ b/src/Animation/BaseAnimationContainer.php
@@ -11,7 +11,7 @@ class BaseAnimationContainer
     /**
      * @param BaseAnimation[] $animations Animations to run
      */
-    public function __construct(public array $animations)
+    public function __construct(public ?array $animations = [])
     {
     }
 }
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index ec70e26..a0da510 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -15,26 +15,26 @@ class TransformOrientationAnimation extends BaseAnimation
     /**
      * @param Quat $modifier The orientation to apply
      * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType $easingType The easing type
-     * @param int $initialDelay The initial delay in milliseconds
+     * @param AnimationEasingType|null $easingType The easing type
+     * @param int|null $initialDelay The initial delay in milliseconds
      * @param bool $repeat Whether the animation should repeat
-     * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between each repeat in milliseconds
+     * @param int|null $repeatCount The number of times the animation should repeat
+     * @param int|null $repeatDelay The delay between each repeat in milliseconds
      * @param bool $reverse Whether the animation should reverse
-     * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between each reverse in milliseconds
+     * @param int|null $reverseCount The number of times the animation should reverse
+     * @param int|null $reverseDelay The delay between each reverse in milliseconds
      */
     public function __construct(
-        public Quat         $modifier,
-        int                 $duration,
-        AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        int                 $initialDelay = 0,
-        bool                $repeat = false,
-        int                 $repeatCount = 0,
-        int                 $repeatDelay = 0,
-        bool                $reverse = false,
-        int                 $reverseCount = 0,
-        int                 $reverseDelay = 0,
+        public Quat          $modifier,
+        int                  $duration,
+        ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        ?int                 $initialDelay = 0,
+        ?bool                $repeat = false,
+        ?int                 $repeatCount = 0,
+        ?int                 $repeatDelay = 0,
+        ?bool                $reverse = false,
+        ?int                 $reverseCount = 0,
+        ?int                 $reverseDelay = 0,
     )
     {
         parent::__construct(
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index d6c5a66..74ad68d 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -15,26 +15,26 @@ class TransformPositionAnimation extends BaseAnimation
     /**
      * @param Vec3 $modifier The position modifier to apply
      * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType $easingType The easing type
-     * @param int $initialDelay The initial delay in milliseconds
+     * @param AnimationEasingType|null $easingType The easing type
+     * @param int|null $initialDelay The initial delay in milliseconds
      * @param bool $repeat Whether the animation should repeat
-     * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between each repeat in milliseconds
+     * @param int|null $repeatCount The number of times the animation should repeat
+     * @param int|null $repeatDelay The delay between each repeat in milliseconds
      * @param bool $reverse Whether the animation should reverse
-     * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between each reverse in milliseconds
+     * @param int|null $reverseCount The number of times the animation should reverse
+     * @param int|null $reverseDelay The delay between each reverse in milliseconds
      */
     public function __construct(
-        public Vec3         $modifier,
-        int                 $duration,
-        AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        int                 $initialDelay = 0,
-        bool                $repeat = false,
-        int                 $repeatCount = 0,
-        int                 $repeatDelay = 0,
-        bool                $reverse = false,
-        int                 $reverseCount = 0,
-        int                 $reverseDelay = 0,
+        public Vec3          $modifier,
+        int                  $duration,
+        ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        ?int                 $initialDelay = 0,
+        ?bool                $repeat = false,
+        ?int                 $repeatCount = 0,
+        ?int                 $repeatDelay = 0,
+        ?bool                $reverse = false,
+        ?int                 $reverseCount = 0,
+        ?int                 $reverseDelay = 0,
     )
     {
         parent::__construct(
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index fbb7525..147b031 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -12,26 +12,26 @@ class TransformScaleAnimation extends BaseAnimation
     /**
      * @param Vec3 $modifier The scale modifier to apply
      * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType $easingType The easing type
-     * @param int $initialDelay The initial delay in milliseconds
+     * @param AnimationEasingType|null $easingType The easing type
+     * @param int|null $initialDelay The initial delay in milliseconds
      * @param bool $repeat Whether the animation should repeat
-     * @param int $repeatCount The number of times the animation should repeat
-     * @param int $repeatDelay The delay between each repeat in milliseconds
+     * @param int|null $repeatCount The number of times the animation should repeat
+     * @param int|null $repeatDelay The delay between each repeat in milliseconds
      * @param bool $reverse Whether the animation should reverse
-     * @param int $reverseCount The number of times the animation should reverse
-     * @param int $reverseDelay The delay between each reverse in milliseconds
+     * @param int|null $reverseCount The number of times the animation should reverse
+     * @param int|null $reverseDelay The delay between each reverse in milliseconds
      */
     public function __construct(
-        public Vec3         $modifier,
-        int                 $duration,
-        AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        int                 $initialDelay = 0,
-        bool                $repeat = false,
-        int                 $repeatCount = 0,
-        int                 $repeatDelay = 0,
-        bool                $reverse = false,
-        int                 $reverseCount = 0,
-        int                 $reverseDelay = 0,
+        public Vec3          $modifier,
+        int                  $duration,
+        ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
+        ?int                 $initialDelay = 0,
+        ?bool                $repeat = false,
+        ?int                 $repeatCount = 0,
+        ?int                 $repeatDelay = 0,
+        ?bool                $reverse = false,
+        ?int                 $reverseCount = 0,
+        ?int                 $reverseDelay = 0,
     )
     {
         parent::__construct(
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 37d5c17..0263f58 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -5,7 +5,6 @@
 use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
-use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
@@ -51,52 +50,12 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $orientation = new Quat();
         $orientation->rotate(GLM::radians(-180.0), new Vec3(0.0, 1.0, 0.0));
         $animationComponent->animation = new AnimationSequence([
-            new TransformPositionAnimation(
-                new Vec3(-500.0, -200.0, 0.0),
-                2000,
-                AnimationEasingType::LINEAR,
-                0,
-                false,
-                0,
-                0,
-                false,
-                0,
-                0),
+            new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000),
             new ParallelAnimations([
-                new TransformPositionAnimation(
-                    new Vec3(0.0, 500.0, 0.0),
-                    2000,
-                    AnimationEasingType::LINEAR,
-                    0,
-                    false,
-                    0,
-                    0,
-                    false,
-                    0,
-                    0),
-                new TransformOrientationAnimation(
-                    $orientation,
-                    500,
-                    AnimationEasingType::LINEAR,
-                    0,
-                    false,
-                    0,
-                    0,
-                    false,
-                    0,
-                    0)
+                new TransformPositionAnimation(new Vec3(0.0, 500.0, 0.0), 2000),
+                new TransformOrientationAnimation($orientation, 500)
             ]),
-            new TransformPositionAnimation(
-                new Vec3(500.0, 0.0, 0.0),
-                2000,
-                AnimationEasingType::LINEAR,
-                0,
-                false,
-                0,
-                0,
-                false,
-                0,
-                0),
+            new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000),
         ]);
     }
 

From 83d07667393f87c3254fa420f40f4fb32d0aa416 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Mon, 23 Jan 2023 15:05:35 +0100
Subject: [PATCH 27/49] turned it into "pugly" code... performant but ugly ;-)

---
 src/System/AnimationSystem.php | 125 ++++++++-------------------------
 1 file changed, 29 insertions(+), 96 deletions(-)

diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 7c96a67..d973dc2 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -76,7 +76,35 @@ private function handleAnimationContainer(object $animationContainer, Transform
         // check the AnimationContainerType of $animationContainer
         if ($animationContainer instanceof BaseAnimation) {
             // handle the animation
-            $this->handleSingleAnimation($animationContainer, $transform);
+            if (!$animationContainer->running) {
+                $animationContainer->requiredTicks = ($animationContainer->duration / 1000.0) * $this->ticksPerSecond;
+                $animationContainer->currentTick = 0;
+            }
+            $animationContainer->currentTick++;
+            if ($animationContainer instanceof TransformPositionAnimation) {
+                if (!$animationContainer->running) {
+                    $animationContainer->initialPosition = $transform->position->copy();
+                    $animationContainer->targetPosition = $animationContainer->initialPosition + $animationContainer->modifier;
+                    $animationContainer->running = true;
+                }
+                $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
+                $transform->setPosition($newPosition);
+            } else if ($animationContainer instanceof TransformScaleAnimation) {
+                //@TODO: implement
+            } else if ($animationContainer instanceof TransformOrientationAnimation) {
+                if (!$animationContainer->running) {
+                    $animationContainer->initialOrientation = $transform->orientation->copy();
+                    $animationContainer->targetOrientation = $animationContainer->initialOrientation * $animationContainer->modifier;
+                    $animationContainer->running = true;
+                }
+                $newOrientation = Quat::slerp($animationContainer->initialOrientation, $animationContainer->targetOrientation, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
+                $transform->setOrientation($newOrientation);
+            }
+            // check if the animation is finished
+            if ($animationContainer->currentTick >= $animationContainer->requiredTicks) {
+                $animationContainer->finished = true;
+                $animationContainer->running = false;
+            }
         } else {
             $finishedAnimations = 0;
             if ($animationContainer instanceof AnimationSequence) {
@@ -108,99 +136,4 @@ private function handleAnimationContainer(object $animationContainer, Transform
             }
         }
     }
-
-    /**
-     * Handles a single animation
-     *
-     * @param BaseAnimation $animation The animation
-     * @param Transform $transform The transform of the entity
-     * @return void
-     */
-    private function handleSingleAnimation(BaseAnimation $animation, Transform $transform): void
-    {
-        if ($animation instanceof TransformPositionAnimation) {
-            $this->handleTransformPositionAnimation($animation, $transform);
-        } else if ($animation instanceof TransformScaleAnimation) {
-            $this->handleTransformScaleAnimation($animation, $transform);
-        } else if ($animation instanceof TransformOrientationAnimation) {
-            $this->handleTransformOrientationAnimation($animation, $transform);
-        }
-    }
-
-    /**
-     * Handles the stuff for the base animation
-     *
-     * @param BaseAnimation $animation The animation
-     * @param Transform $transform The transform of the entity
-     *
-     * @return void
-     */
-    private function handleBaseAnimation(BaseAnimation $animation, Transform $transform): void
-    {
-        if (!$animation->running) {
-            $animation->requiredTicks = ($animation->duration / 1000.0) * $this->ticksPerSecond;
-            $animation->currentTick = 0;
-        }
-        $animation->currentTick++;
-    }
-
-    private function handleBaseAnimationFinalizer(BaseAnimation $animation, Transform $transform): void
-    {
-        if ($animation->currentTick >= $animation->requiredTicks) {
-            $animation->finished = true;
-            $animation->running = false;
-        }
-    }
-
-    /**
-     * Handles a TransformPositionAnimation
-     *
-     * @param TransformPositionAnimation $animation The animation
-     * @param Transform $transform The transform of the entity
-     * @return void
-     */
-    private function handleTransformPositionAnimation(TransformPositionAnimation $animation, Transform $transform): void
-    {
-        $this->handleBaseAnimation($animation, $transform);
-        if (!$animation->running) {
-            $animation->initialPosition = $transform->position->copy();
-            $animation->targetPosition = $animation->initialPosition + $animation->modifier;
-            $animation->running = true;
-        }
-        $newPosition = Vec3::slerp($animation->initialPosition, $animation->targetPosition, (1.0 / $animation->requiredTicks) * $animation->currentTick);
-        $transform->setPosition($newPosition);
-        $this->handleBaseAnimationFinalizer($animation, $transform);
-    }
-
-    /**
-     * Handles a TransformScaleAnimation
-     *
-     * @param TransformScaleAnimation $animation The animation
-     * @param Transform $transform The transform of the entity
-     * @return void
-     */
-    private function handleTransformScaleAnimation(TransformScaleAnimation $animation, Transform $transform): void
-    {
-        $this->handleBaseAnimation($animation, $transform);
-    }
-
-    /**
-     * Handles a TransformOrientationAnimation
-     *
-     * @param TransformOrientationAnimation $animation The animation
-     * @param Transform $transform The transform of the entity
-     * @return void
-     */
-    private function handleTransformOrientationAnimation(TransformOrientationAnimation $animation, Transform $transform): void
-    {
-        $this->handleBaseAnimation($animation, $transform);
-        if (!$animation->running) {
-            $animation->initialOrientation = $transform->orientation->copy();
-            $animation->targetOrientation = $animation->initialOrientation * $animation->modifier;
-            $animation->running = true;
-        }
-        $newOrientation = Quat::slerp($animation->initialOrientation, $animation->targetOrientation, (1.0 / $animation->requiredTicks) * $animation->currentTick);
-        $transform->setOrientation($newOrientation);
-        $this->handleBaseAnimationFinalizer($animation, $transform);
-    }
 }

From d5cc97fdf7f10c76d524c56e0a348a0b6ba9618c Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Mon, 23 Jan 2023 15:10:57 +0100
Subject: [PATCH 28/49] scale animation support

---
 src/Animation/TransformScaleAnimation.php | 3 +++
 src/System/AircraftSystem.php             | 7 ++++++-
 src/System/AnimationSystem.php            | 8 +++++++-
 3 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index 147b031..bec479a 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -9,6 +9,9 @@
  */
 class TransformScaleAnimation extends BaseAnimation
 {
+    public Vec3 $initialScale;
+    public Vec3 $targetScale;
+
     /**
      * @param Vec3 $modifier The scale modifier to apply
      * @param int $duration The duration of the animation in milliseconds
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 0263f58..b9fb56a 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -9,6 +9,7 @@
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
 use TowerDefense\Animation\TransformPositionAnimation;
+use TowerDefense\Animation\TransformScaleAnimation;
 use TowerDefense\Component\AnimationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
@@ -53,7 +54,11 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
             new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000),
             new ParallelAnimations([
                 new TransformPositionAnimation(new Vec3(0.0, 500.0, 0.0), 2000),
-                new TransformOrientationAnimation($orientation, 500)
+                new AnimationSequence([
+                    new TransformScaleAnimation(new Vec3(0.5, 0.5, 0.5), 500),
+                    new TransformScaleAnimation(new Vec3(2.0, 2.0, 2.0), 500)
+                ]),
+                new TransformOrientationAnimation($orientation, 1000)
             ]),
             new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000),
         ]);
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index d973dc2..d7e3060 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -90,7 +90,13 @@ private function handleAnimationContainer(object $animationContainer, Transform
                 $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
                 $transform->setPosition($newPosition);
             } else if ($animationContainer instanceof TransformScaleAnimation) {
-                //@TODO: implement
+                if (!$animationContainer->running) {
+                    $animationContainer->initialScale = $transform->scale->copy();
+                    $animationContainer->targetScale = $animationContainer->initialScale * $animationContainer->modifier;
+                    $animationContainer->running = true;
+                }
+                $newScale = Vec3::slerp($animationContainer->initialScale, $animationContainer->targetScale, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
+                $transform->setScale($newScale);
             } else if ($animationContainer instanceof TransformOrientationAnimation) {
                 if (!$animationContainer->running) {
                     $animationContainer->initialOrientation = $transform->orientation->copy();

From 682d04288db93004cf216a1a3e938afbab9d19c2 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Mon, 23 Jan 2023 18:47:15 +0100
Subject: [PATCH 29/49] basic easing curves added

---
 src/System/AircraftSystem.php  |  9 +++++----
 src/System/AnimationSystem.php | 26 +++++++++++++++++++++++---
 2 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index b9fb56a..7024a1b 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -5,6 +5,7 @@
 use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
+use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
@@ -51,16 +52,16 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $orientation = new Quat();
         $orientation->rotate(GLM::radians(-180.0), new Vec3(0.0, 1.0, 0.0));
         $animationComponent->animation = new AnimationSequence([
-            new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000),
+            new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
             new ParallelAnimations([
-                new TransformPositionAnimation(new Vec3(0.0, 500.0, 0.0), 2000),
+                new TransformPositionAnimation(new Vec3(0.0, 500.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
                 new AnimationSequence([
                     new TransformScaleAnimation(new Vec3(0.5, 0.5, 0.5), 500),
                     new TransformScaleAnimation(new Vec3(2.0, 2.0, 2.0), 500)
                 ]),
-                new TransformOrientationAnimation($orientation, 1000)
+                new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT)
             ]),
-            new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000),
+            new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
         ]);
     }
 
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index d7e3060..165b0ec 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -4,6 +4,7 @@
 
 use GL\Math\Quat;
 use GL\Math\Vec3;
+use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
 use TowerDefense\Animation\BaseAnimation;
 use TowerDefense\Animation\ParallelAnimations;
@@ -80,14 +81,33 @@ private function handleAnimationContainer(object $animationContainer, Transform
                 $animationContainer->requiredTicks = ($animationContainer->duration / 1000.0) * $this->ticksPerSecond;
                 $animationContainer->currentTick = 0;
             }
+
             $animationContainer->currentTick++;
+
+            $progress = (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick;
+
+            // apply easing curves if necessary
+            // @TODO We will add a lot more of them: https://easings.net/
+            if ($animationContainer->easingType != AnimationEasingType::LINEAR) {
+                if ($animationContainer->easingType == AnimationEasingType::EASE_OUT) {
+                    // https://easings.net/#easeOutCubic
+                    $progress = 1 - pow(1 - $progress, 3);
+                } elseif ($animationContainer->easingType == AnimationEasingType::EASE_IN_OUT) {
+                    // https://easings.net/#easeInOutCubic
+                    $progress = $progress < 0.5 ? 4 * $progress * $progress * $progress : 1 - pow(-2 * $progress + 2, 3) / 2;
+                } elseif ($animationContainer->easingType == AnimationEasingType::EASE_IN) {
+                    // https://easings.net/#easeInCubic
+                    $progress = $progress * $progress * $progress;
+                }
+            }
+
             if ($animationContainer instanceof TransformPositionAnimation) {
                 if (!$animationContainer->running) {
                     $animationContainer->initialPosition = $transform->position->copy();
                     $animationContainer->targetPosition = $animationContainer->initialPosition + $animationContainer->modifier;
                     $animationContainer->running = true;
                 }
-                $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
+                $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, $progress);
                 $transform->setPosition($newPosition);
             } else if ($animationContainer instanceof TransformScaleAnimation) {
                 if (!$animationContainer->running) {
@@ -95,7 +115,7 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     $animationContainer->targetScale = $animationContainer->initialScale * $animationContainer->modifier;
                     $animationContainer->running = true;
                 }
-                $newScale = Vec3::slerp($animationContainer->initialScale, $animationContainer->targetScale, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
+                $newScale = Vec3::slerp($animationContainer->initialScale, $animationContainer->targetScale, $progress);
                 $transform->setScale($newScale);
             } else if ($animationContainer instanceof TransformOrientationAnimation) {
                 if (!$animationContainer->running) {
@@ -103,7 +123,7 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     $animationContainer->targetOrientation = $animationContainer->initialOrientation * $animationContainer->modifier;
                     $animationContainer->running = true;
                 }
-                $newOrientation = Quat::slerp($animationContainer->initialOrientation, $animationContainer->targetOrientation, (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick);
+                $newOrientation = Quat::slerp($animationContainer->initialOrientation, $animationContainer->targetOrientation, $progress);
                 $transform->setOrientation($newOrientation);
             }
             // check if the animation is finished

From c96f55350c6d184c2118b5181cd51e08cb46a210 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Mon, 23 Jan 2023 20:18:58 +0100
Subject: [PATCH 30/49] working repeat reverse; next up: delays;

---
 src/Animation/BaseAnimation.php               |  1 +
 .../TransformOrientationAnimation.php         |  4 +-
 src/Animation/TransformPositionAnimation.php  |  4 +-
 src/Animation/TransformScaleAnimation.php     |  4 +-
 src/System/AircraftSystem.php                 | 10 +++-
 src/System/AnimationSystem.php                | 48 ++++++++++++++++---
 6 files changed, 57 insertions(+), 14 deletions(-)

diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index b5c8070..d9da454 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -13,6 +13,7 @@ class BaseAnimation
     public float $progress = 0.0; // Progress of the current animation run
     public bool $finished = false; // Whether the animation has finished
     public bool $running = false; // Whether the animation is running
+    public bool $reversing = false; // Whether the animation is reversing
     public int $requiredTicks = 0; // Number of ticks required to complete the animation
     public int $currentTick = 0; // Current tick of the animation
 
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index a0da510..1822187 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -9,8 +9,8 @@
  */
 class TransformOrientationAnimation extends BaseAnimation
 {
-    public Quat $initialOrientation;
-    public Quat $targetOrientation;
+    public ?Quat $initialOrientation;
+    public ?Quat $targetOrientation;
 
     /**
      * @param Quat $modifier The orientation to apply
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index 74ad68d..c4c32c9 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -9,8 +9,8 @@
  */
 class TransformPositionAnimation extends BaseAnimation
 {
-    public Vec3 $initialPosition;
-    public Vec3 $targetPosition;
+    public ?Vec3 $initialPosition;
+    public ?Vec3 $targetPosition;
 
     /**
      * @param Vec3 $modifier The position modifier to apply
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index bec479a..0f54a19 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -9,8 +9,8 @@
  */
 class TransformScaleAnimation extends BaseAnimation
 {
-    public Vec3 $initialScale;
-    public Vec3 $targetScale;
+    public ?Vec3 $initialScale;
+    public ?Vec3 $targetScale;
 
     /**
      * @param Vec3 $modifier The scale modifier to apply
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 7024a1b..9f616db 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -50,8 +50,8 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
 
         $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
         $orientation = new Quat();
-        $orientation->rotate(GLM::radians(-180.0), new Vec3(0.0, 1.0, 0.0));
-        $animationComponent->animation = new AnimationSequence([
+        $orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
+        /*$animationComponent->animation = new AnimationSequence([
             new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
             new ParallelAnimations([
                 new TransformPositionAnimation(new Vec3(0.0, 500.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
@@ -62,6 +62,12 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
                 new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT)
             ]),
             new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
+        ]);*/
+
+        $animationComponent->animation = new AnimationSequence([
+            new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 4, reverse: true, reverseCount: 1),
+            new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 1, reverse: true, reverseCount: 1),
+            new TransformScaleAnimation(new Vec3(0.5, 0.5, 0.5), 1000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 3, reverse: true, reverseCount: 4)
         ]);
     }
 
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 165b0ec..e1bff78 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -103,24 +103,30 @@ private function handleAnimationContainer(object $animationContainer, Transform
 
             if ($animationContainer instanceof TransformPositionAnimation) {
                 if (!$animationContainer->running) {
-                    $animationContainer->initialPosition = $transform->position->copy();
-                    $animationContainer->targetPosition = $animationContainer->initialPosition + $animationContainer->modifier;
+                    if (!$animationContainer->reversing) {
+                        $animationContainer->initialPosition = $transform->position->copy();
+                        $animationContainer->targetPosition = $animationContainer->initialPosition + $animationContainer->modifier;
+                    }
                     $animationContainer->running = true;
                 }
                 $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, $progress);
                 $transform->setPosition($newPosition);
             } else if ($animationContainer instanceof TransformScaleAnimation) {
                 if (!$animationContainer->running) {
-                    $animationContainer->initialScale = $transform->scale->copy();
-                    $animationContainer->targetScale = $animationContainer->initialScale * $animationContainer->modifier;
+                    if (!$animationContainer->reversing) {
+                        $animationContainer->initialScale = $transform->scale->copy();
+                        $animationContainer->targetScale = $animationContainer->initialScale * $animationContainer->modifier;
+                    }
                     $animationContainer->running = true;
                 }
                 $newScale = Vec3::slerp($animationContainer->initialScale, $animationContainer->targetScale, $progress);
                 $transform->setScale($newScale);
             } else if ($animationContainer instanceof TransformOrientationAnimation) {
                 if (!$animationContainer->running) {
-                    $animationContainer->initialOrientation = $transform->orientation->copy();
-                    $animationContainer->targetOrientation = $animationContainer->initialOrientation * $animationContainer->modifier;
+                    if (!$animationContainer->reversing) {
+                        $animationContainer->initialOrientation = $transform->orientation->copy();
+                        $animationContainer->targetOrientation = $animationContainer->initialOrientation * $animationContainer->modifier;
+                    }
                     $animationContainer->running = true;
                 }
                 $newOrientation = Quat::slerp($animationContainer->initialOrientation, $animationContainer->targetOrientation, $progress);
@@ -128,8 +134,38 @@ private function handleAnimationContainer(object $animationContainer, Transform
             }
             // check if the animation is finished
             if ($animationContainer->currentTick >= $animationContainer->requiredTicks) {
+                $reverseBlockedByRepeat = false;
+                if ($animationContainer->reversing) {
+                    $animationContainer->reversing = false;
+                    $animationContainer->reversedCount++;
+                    if ($animationContainer->repeat && ($animationContainer->repeatCount == 0 || $animationContainer->repeatCount > $animationContainer->repeatedCount)) {
+                        $reverseBlockedByRepeat = true;
+                    }
+                } else if ($animationContainer->runCount > 0) {
+                    $animationContainer->repeatedCount++;
+                }
                 $animationContainer->finished = true;
                 $animationContainer->running = false;
+                $animationContainer->runCount++;
+
+                if (!$reverseBlockedByRepeat && $animationContainer->reverse && ($animationContainer->reverseCount == 0 || $animationContainer->reverseCount > $animationContainer->reversedCount)) {
+                    $animationContainer->reversing = true;
+                    $animationContainer->finished = false;
+                    if ($animationContainer instanceof TransformPositionAnimation) {
+                        $animationContainer->targetPosition = $animationContainer->initialPosition->copy();
+                        $animationContainer->initialPosition = $transform->position->copy();
+                    } else if ($animationContainer instanceof TransformScaleAnimation) {
+                        $animationContainer->targetScale = $animationContainer->initialScale->copy();
+                        $animationContainer->initialScale = $transform->scale->copy();
+                    } else if ($animationContainer instanceof TransformOrientationAnimation) {
+                        $animationContainer->targetOrientation = $animationContainer->initialOrientation->copy();
+                        $animationContainer->initialOrientation = $transform->orientation->copy();
+                    }
+                } else {
+                    if ($animationContainer->repeat && ($animationContainer->repeatCount == 0 || $animationContainer->repeatCount > $animationContainer->repeatedCount)) {
+                        $animationContainer->finished = false;
+                    }
+                }
             }
         } else {
             $finishedAnimations = 0;

From 4fe60e81eec148fa46a20b2d60e2f5b0d1c43a13 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Tue, 24 Jan 2023 16:07:52 +0100
Subject: [PATCH 31/49] delay support

---
 src/Animation/BaseAnimation.php |  3 ++
 src/System/AircraftSystem.php   |  5 +--
 src/System/AnimationSystem.php  | 59 +++++++++++++++++++++++++++++++--
 3 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index d9da454..00edfa7 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -15,7 +15,10 @@ class BaseAnimation
     public bool $running = false; // Whether the animation is running
     public bool $reversing = false; // Whether the animation is reversing
     public int $requiredTicks = 0; // Number of ticks required to complete the animation
+    public int $requiredDelayTicks = 0; // Number of ticks required to delay the animation
     public int $currentTick = 0; // Current tick of the animation
+    public int $currentDelayTick = 0; // Current delay tick of the animation
+    public bool $waiting = false; // Whether the animation is waiting for the delay to finish
 
     /**
      * @param int $duration The duration of the animation in milliseconds
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 9f616db..09b1b6b 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -47,6 +47,7 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->scale = $transform->scale * 100;
         $transform->position = $initialPosition;
         $transform->orientation = $initialOrientation;
+        $transform->isDirty = true;
 
         $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
         $orientation = new Quat();
@@ -65,8 +66,8 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         ]);*/
 
         $animationComponent->animation = new AnimationSequence([
-            new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 4, reverse: true, reverseCount: 1),
-            new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 1, reverse: true, reverseCount: 1),
+            new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT, initialDelay: 0, repeat: true, repeatCount: 4, repeatDelay: 1000, reverse: true, reverseCount: 1, reverseDelay: 250),
+            new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT, initialDelay: 500, repeat: true, repeatCount: 1, repeatDelay: 500, reverse: true, reverseCount: 1, reverseDelay: 500),
             new TransformScaleAnimation(new Vec3(0.5, 0.5, 0.5), 1000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 3, reverse: true, reverseCount: 4)
         ]);
     }
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index e1bff78..21db31c 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -78,12 +78,49 @@ private function handleAnimationContainer(object $animationContainer, Transform
         if ($animationContainer instanceof BaseAnimation) {
             // handle the animation
             if (!$animationContainer->running) {
+                // set the required ticks based on the duration
                 $animationContainer->requiredTicks = ($animationContainer->duration / 1000.0) * $this->ticksPerSecond;
                 $animationContainer->currentTick = 0;
+
+                // check if the animation should wait
+                if ($animationContainer->runCount == 0 && $animationContainer->initialDelay > 0 && !$animationContainer->reversing) {
+                    // set the required delay ticks based on the initial delay
+                    $animationContainer->requiredDelayTicks = ($animationContainer->initialDelay / 1000.0) * $this->ticksPerSecond;
+                    $animationContainer->currentDelayTick = 0;
+                    $animationContainer->waiting = true;
+                    $animationContainer->running = true;
+                } else if ($animationContainer->reverseDelay > 0 && $animationContainer->reversing) {
+                    // set the required delay ticks based on the reverse delay
+                    $animationContainer->requiredDelayTicks = ($animationContainer->reverseDelay / 1000.0) * $this->ticksPerSecond;
+                    $animationContainer->currentDelayTick = 0;
+                    $animationContainer->waiting = true;
+                    $animationContainer->running = true;
+                } else if (!$animationContainer->reversing && $animationContainer->runCount > 0 && $animationContainer->repeatDelay > 0) {
+                    // set the required delay ticks based on the repeat delay
+                    $animationContainer->requiredDelayTicks = ($animationContainer->repeatDelay / 1000.0) * $this->ticksPerSecond;
+                    $animationContainer->currentDelayTick = 0;
+                    $animationContainer->waiting = true;
+                    $animationContainer->running = true;
+                }
+            }
+
+            // check if the animation is waiting
+            if ($animationContainer->waiting) {
+                // check if the delay is over
+                if ($animationContainer->currentDelayTick >= $animationContainer->requiredDelayTicks) {
+                    $animationContainer->waiting = false;
+                    $animationContainer->running = false;
+                } else {
+                    // increase the delay tick
+                    $animationContainer->currentDelayTick++;
+                    return;
+                }
             }
 
+            // increase the current tick
             $animationContainer->currentTick++;
 
+            // calculate the progress
             $progress = (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick;
 
             // apply easing curves if necessary
@@ -104,33 +141,42 @@ private function handleAnimationContainer(object $animationContainer, Transform
             if ($animationContainer instanceof TransformPositionAnimation) {
                 if (!$animationContainer->running) {
                     if (!$animationContainer->reversing) {
+                        // set the initial and target position
                         $animationContainer->initialPosition = $transform->position->copy();
                         $animationContainer->targetPosition = $animationContainer->initialPosition + $animationContainer->modifier;
                     }
                     $animationContainer->running = true;
                 }
+                // calculate the new position
                 $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, $progress);
-                $transform->setPosition($newPosition);
+                $transform->position = $newPosition;
+                $transform->isDirty = true;
             } else if ($animationContainer instanceof TransformScaleAnimation) {
                 if (!$animationContainer->running) {
                     if (!$animationContainer->reversing) {
+                        // set the initial and target scale
                         $animationContainer->initialScale = $transform->scale->copy();
                         $animationContainer->targetScale = $animationContainer->initialScale * $animationContainer->modifier;
                     }
                     $animationContainer->running = true;
                 }
+                // calculate the new scale
                 $newScale = Vec3::slerp($animationContainer->initialScale, $animationContainer->targetScale, $progress);
-                $transform->setScale($newScale);
+                $transform->scale = $newScale;
+                $transform->isDirty = true;
             } else if ($animationContainer instanceof TransformOrientationAnimation) {
                 if (!$animationContainer->running) {
                     if (!$animationContainer->reversing) {
+                        // set the initial and target orientation
                         $animationContainer->initialOrientation = $transform->orientation->copy();
                         $animationContainer->targetOrientation = $animationContainer->initialOrientation * $animationContainer->modifier;
                     }
                     $animationContainer->running = true;
                 }
+                // calculate the new orientation
                 $newOrientation = Quat::slerp($animationContainer->initialOrientation, $animationContainer->targetOrientation, $progress);
-                $transform->setOrientation($newOrientation);
+                $transform->orientation = $newOrientation;
+                $transform->isDirty = true;
             }
             // check if the animation is finished
             if ($animationContainer->currentTick >= $animationContainer->requiredTicks) {
@@ -138,6 +184,7 @@ private function handleAnimationContainer(object $animationContainer, Transform
                 if ($animationContainer->reversing) {
                     $animationContainer->reversing = false;
                     $animationContainer->reversedCount++;
+                    // check if the reverse is blocked by repeat
                     if ($animationContainer->repeat && ($animationContainer->repeatCount == 0 || $animationContainer->repeatCount > $animationContainer->repeatedCount)) {
                         $reverseBlockedByRepeat = true;
                     }
@@ -148,20 +195,25 @@ private function handleAnimationContainer(object $animationContainer, Transform
                 $animationContainer->running = false;
                 $animationContainer->runCount++;
 
+                // check if we need to reverse the animation
                 if (!$reverseBlockedByRepeat && $animationContainer->reverse && ($animationContainer->reverseCount == 0 || $animationContainer->reverseCount > $animationContainer->reversedCount)) {
                     $animationContainer->reversing = true;
                     $animationContainer->finished = false;
                     if ($animationContainer instanceof TransformPositionAnimation) {
+                        // swap the initial and target position
                         $animationContainer->targetPosition = $animationContainer->initialPosition->copy();
                         $animationContainer->initialPosition = $transform->position->copy();
                     } else if ($animationContainer instanceof TransformScaleAnimation) {
+                        // swap the initial and target scale
                         $animationContainer->targetScale = $animationContainer->initialScale->copy();
                         $animationContainer->initialScale = $transform->scale->copy();
                     } else if ($animationContainer instanceof TransformOrientationAnimation) {
+                        // swap the initial and target orientation
                         $animationContainer->targetOrientation = $animationContainer->initialOrientation->copy();
                         $animationContainer->initialOrientation = $transform->orientation->copy();
                     }
                 } else {
+                    // check if we need to repeat the animation
                     if ($animationContainer->repeat && ($animationContainer->repeatCount == 0 || $animationContainer->repeatCount > $animationContainer->repeatedCount)) {
                         $animationContainer->finished = false;
                     }
@@ -193,6 +245,7 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     }
                 }
             }
+            // check if all animations in the container are finished and set the finished flag
             if ($finishedAnimations == count($animationContainer->animations)) {
                 $animationContainer->finished = true;
             }

From 7a2783d55bb5757fa78471ccb2c44d65fd5b0df3 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Wed, 25 Jan 2023 20:07:21 +0100
Subject: [PATCH 32/49] debug output mouse click event

---
 src/Scene/LevelScene.php       | 23 +++++++++++++++++++++++
 src/System/HeightmapSystem.php |  2 +-
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 6371358..d51b028 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -23,6 +23,7 @@
 use VISU\Graphics\RenderTarget;
 use VISU\OS\Key;
 use VISU\Signals\Input\KeySignal;
+use VISU\Signals\Input\MouseClickSignal;
 use VISU\System\VISUCameraSystem;
 use VISU\System\VISULowPoly\LPObjLoader;
 use VISU\System\VISULowPoly\LPRenderingSystem as VISULowPolyRenderingSystem;
@@ -52,6 +53,12 @@ class LevelScene extends BaseScene implements DevEntityPickerDelegate
      */
     private int $keyboardHandlerId = 0;
 
+    /**
+     * Function ID for mouse click handler
+     */
+    private int $mouseClickHandlerId = 0;
+
+
     /**
      * Constructor
      */
@@ -64,6 +71,7 @@ public function __construct(
         // register key handler for debugging
         // usally a system should handle this but this is temporary
         $this->keyboardHandlerId = $this->container->resolveVisuDispatcher()->register('input.key', [$this, 'handleKeyboardEvent']);
+        $this->mouseClickHandlerId = $this->container->resolveVisuDispatcher()->register('input.mouse_click', [$this, 'handleMouseClickEvent']);
 
         // load all space kit models
         $this->objectLoader = new LPObjLoader($container->resolveGL());
@@ -265,6 +273,21 @@ public function handleKeyboardEvent(KeySignal $signal) : void
 
     }
 
+    /**
+     * Mouse click event handler
+     */
+    public function handleMouseClickEvent(MouseClickSignal $signal)
+    {
+        $p = $this->container->resolveInput()->getNormalizedCursorPosition();
+        $camera = $this->cameraSystem->getActiveCamera($this->entities);
+        $ray = $camera->getSSRay($this->container->resolveWindowMain()->getRenderTarget(), $p);
+        $o = $this->heightmapSystem->heightmap->castRay($ray);
+
+        echo "Mouse click event native: x " . $signal->position->x . " y " . $signal->position->y . PHP_EOL;
+        echo "Mouse click event normalized: x" . $p->x . " y " . $p->y . PHP_EOL;
+        echo "Mouse click event heightmap based: x " . $o->x . " y " . $o->y . " z " . $o->z . PHP_EOL;
+    }
+
     /**
      * Called when the dev entity picker has selected an entity
      *
diff --git a/src/System/HeightmapSystem.php b/src/System/HeightmapSystem.php
index 643f772..933cb05 100644
--- a/src/System/HeightmapSystem.php
+++ b/src/System/HeightmapSystem.php
@@ -20,7 +20,7 @@ class HeightmapSystem implements SystemInterface
      */
     private GPUHeightmapRenderer $heightmapRenderer;
 
-    private ?Heightmap $heightmap = null;
+    public ?Heightmap $heightmap = null;
 
     /**
      * Constructor

From 1b0b015b56316896dd93745d49f75cdad80c56d4 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Wed, 25 Jan 2023 20:50:49 +0100
Subject: [PATCH 33/49] working on it...

---
 src/Scene/LevelScene.php      |  3 +++
 src/System/AircraftSystem.php | 34 +++++++++++++++++++++++++++++++---
 2 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index d51b028..841909e 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -286,6 +286,9 @@ public function handleMouseClickEvent(MouseClickSignal $signal)
         echo "Mouse click event native: x " . $signal->position->x . " y " . $signal->position->y . PHP_EOL;
         echo "Mouse click event normalized: x" . $p->x . " y " . $p->y . PHP_EOL;
         echo "Mouse click event heightmap based: x " . $o->x . " y " . $o->y . " z " . $o->z . PHP_EOL;
+
+        // forward the click to the aircraft system
+        $this->aircraftSystem->clickAt($this->entities, $o);
     }
 
     /**
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 09b1b6b..d3a35da 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -3,6 +3,7 @@
 namespace TowerDefense\System;
 
 use GL\Math\GLM;
+use GL\Math\Mat4;
 use GL\Math\Quat;
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
@@ -49,7 +50,7 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->orientation = $initialOrientation;
         $transform->isDirty = true;
 
-        $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
+        // $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
         $orientation = new Quat();
         $orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
         /*$animationComponent->animation = new AnimationSequence([
@@ -65,11 +66,38 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
             new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
         ]);*/
 
-        $animationComponent->animation = new AnimationSequence([
+        /*$animationComponent->animation = new AnimationSequence([
             new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT, initialDelay: 0, repeat: true, repeatCount: 4, repeatDelay: 1000, reverse: true, reverseCount: 1, reverseDelay: 250),
             new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT, initialDelay: 500, repeat: true, repeatCount: 1, repeatDelay: 500, reverse: true, reverseCount: 1, reverseDelay: 500),
             new TransformScaleAnimation(new Vec3(0.5, 0.5, 0.5), 1000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 3, reverse: true, reverseCount: 4)
-        ]);
+        ]);*/
+    }
+
+    /**
+     * Handle a click event
+     *
+     * @param EntitiesInterface $entities
+     * @param Vec3 $position
+     * @return void
+     */
+    public function clickAt(EntitiesInterface $entities, Vec3 $position): void
+    {
+        // get the first aircraft
+        $aircraft = $this->aircrafts[0];
+        $transform = $entities->get($aircraft, Transform::class);
+
+        // target position $position
+        $targetPosition = $position;
+        
+        // world forward
+        $worldForward = Transform::worldForward();
+
+        // based on world forward and the target position, calculate the new orientation
+        $mat = new Mat4();
+        $newOrientation = $mat->lookAt($worldForward, $targetPosition, new Vec3(0.0, 1.0, 0.0));
+        $transform->orientation = Quat::fromMat4($mat);
+
+        $transform->markDirty();
     }
 
     /**

From 99247b79db6042e2940d6bd8b92a324ac820e60e Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Wed, 25 Jan 2023 22:46:39 +0100
Subject: [PATCH 34/49] I fucking did it!!!

---
 src/System/AircraftSystem.php | 73 ++++++++++++++++++++++++++++++-----
 1 file changed, 64 insertions(+), 9 deletions(-)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index d3a35da..e68ba54 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -73,6 +73,50 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         ]);*/
     }
 
+    /**
+     * Calculate the rotation between two vectors
+     *
+     * @param Vec3 $start
+     * @param Vec3 $dest
+     * @return Quat
+     */
+    private function rotationBetweenVectors(Vec3 $start, Vec3 $dest): Quat
+    {
+        $start->normalize();
+        $dest->normalize();
+
+        $cosTheta = Vec3::dot($start, $dest);
+
+        if ($cosTheta < -1 + 0.001) {
+            // special case when vectors in opposite directions:
+            // there is no "ideal" rotation axis
+            // So guess one; any will do as long as it's perpendicular to start
+            $rotationAxis = Vec3::cross(new Vec3(0.0, 0.0, 1.0), $start);
+            if ($rotationAxis->length() < 0.01) {
+                // bad luck, they were parallel, try again!
+                $rotationAxis = Vec3::cross(new Vec3(1.0, 0.0, 0.0), $start);
+            }
+
+            $rotationAxis->normalize();
+            $quat = new Quat();
+            $quat->rotate(GLM::radians(180.0), $rotationAxis);
+            return $quat;
+        }
+
+        $rotationAxis = Vec3::cross($start, $dest);
+
+        $s = sqrt((1 + $cosTheta) * 2);
+        $invs = 1 / $s;
+
+        return new Quat(
+            $s * 0.5,
+            $rotationAxis->x * $invs,
+            $rotationAxis->y * $invs,
+            $rotationAxis->z * $invs
+        );
+
+    }
+
     /**
      * Handle a click event
      *
@@ -86,16 +130,27 @@ public function clickAt(EntitiesInterface $entities, Vec3 $position): void
         $aircraft = $this->aircrafts[0];
         $transform = $entities->get($aircraft, Transform::class);
 
-        // target position $position
+        // set the target position to the click position
         $targetPosition = $position;
-        
-        // world forward
-        $worldForward = Transform::worldForward();
-
-        // based on world forward and the target position, calculate the new orientation
-        $mat = new Mat4();
-        $newOrientation = $mat->lookAt($worldForward, $targetPosition, new Vec3(0.0, 1.0, 0.0));
-        $transform->orientation = Quat::fromMat4($mat);
+        //$targetPosition->y = $transform->position->y; // always keep the same height
+
+        // Find the rotation between the front of the object (that we assume towards +Z,
+        // but this depends on your model) and the desired direction
+        $direction = $targetPosition - $transform->position;
+        $rot1 = $this->rotationBetweenVectors(Transform::worldBackward(), $direction);
+
+        // Recompute desiredUp so that it's perpendicular to the direction
+        // You can skip that part if you really want to force desiredUp
+        $right = Vec3::cross($direction, Transform::worldUp());
+        $desiredUp = Vec3::cross($right, $direction);
+
+        // Because of the 1rst rotation, the up is probably completely screwed up.
+        // Find the rotation between the "up" of the rotated object, and the desired up
+        $newUp = Quat::multiplyVec3($rot1, new Vec3(0.0, 1.0, 0.0));
+        $rot2 = $this->rotationBetweenVectors($newUp, $desiredUp);
+        $targetOrientation = $rot2 * $rot1; // remember, in reverse order.
+
+        $transform->orientation = $targetOrientation;
 
         $transform->markDirty();
     }

From a62b22df7c72b400f25411c5e3485ccf49031ae3 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 27 Jan 2023 08:33:37 +0100
Subject: [PATCH 35/49] Exploring sending the aircraft to a point on the map

---
 .../TransformOrientationAnimation.php         | 15 +++
 src/Animation/TransformPositionAnimation.php  | 13 +++
 src/Animation/TransformScaleAnimation.php     | 13 +++
 src/Geometry/Vec3Util.php                     | 91 +++++++++++++++++++
 src/System/AircraftSystem.php                 | 84 ++++-------------
 src/System/AnimationSystem.php                |  4 +-
 6 files changed, 150 insertions(+), 70 deletions(-)
 create mode 100644 src/Geometry/Vec3Util.php

diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 1822187..51ec457 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -49,4 +49,19 @@ public function __construct(
             $reverseDelay,
         );
     }
+
+    /**
+     * Creates a new animation from the current and target orientation
+     *
+     * @param Quat $currentOrientation
+     * @param Quat $targetOrientation
+     * @param int $duration
+     * @return static
+     */
+    public static function fromCurrentAndTargetOrientation(Quat $currentOrientation, Quat $targetOrientation, int $duration): self
+    {
+        $inverseQuat = $currentOrientation->copy();
+        $inverseQuat->inverse();
+        return new self($inverseQuat * $targetOrientation, $duration);
+    }
 }
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index c4c32c9..48418d6 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -49,4 +49,17 @@ public function __construct(
             $reverseDelay,
         );
     }
+
+    /**
+     * Creates a new animation from the current and target position
+     *
+     * @param Vec3 $currentPosition
+     * @param Vec3 $targetPosition
+     * @param int $duration
+     * @return static
+     */
+    public static function fromCurrentAndTargetPosition(Vec3 $currentPosition, Vec3 $targetPosition, int $duration): self
+    {
+        return new self($targetPosition - $currentPosition, $duration);
+    }
 }
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index 0f54a19..f14a415 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -49,4 +49,17 @@ public function __construct(
             $reverseDelay,
         );
     }
+
+    /**
+     * Creates a new animation from the current and target scale
+     *
+     * @param Vec3 $currentScale
+     * @param Vec3 $targetScale
+     * @param int $duration
+     * @return static
+     */
+    public static function fromCurrentAndTargetScale(Vec3 $currentScale, Vec3 $targetScale, int $duration): self
+    {
+        return new self($targetScale / $currentScale, $duration);
+    }
 }
diff --git a/src/Geometry/Vec3Util.php b/src/Geometry/Vec3Util.php
new file mode 100644
index 0000000..5f9e38a
--- /dev/null
+++ b/src/Geometry/Vec3Util.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace TowerDefense\Geometry;
+
+use GL\Math\GLM;
+use GL\Math\Quat;
+use GL\Math\Vec3;
+
+/**
+ * Collection of utility functions for Vec3
+ */
+class Vec3Util
+{
+    /**
+     * Calculate the rotation between two vectors
+     *
+     * @param Vec3 $start
+     * @param Vec3 $dest
+     * @return Quat
+     */
+    public static function rotationBetweenVectors(Vec3 $start, Vec3 $dest): Quat
+    {
+        $start = $start->copy();
+        $dest = $dest->copy();
+
+        $start->normalize();
+        $dest->normalize();
+
+        $cosTheta = Vec3::dot($start, $dest);
+
+        if ($cosTheta < -1 + 0.001) {
+            // special case when vectors in opposite directions:
+            // there is no "ideal" rotation axis
+            // So guess one; any will do as long as it's perpendicular to start
+            $rotationAxis = Vec3::cross(new Vec3(0.0, 0.0, 1.0), $start);
+            if ($rotationAxis->length() < 0.01) {
+                // bad luck, they were parallel, try again!
+                $rotationAxis = Vec3::cross(new Vec3(1.0, 0.0, 0.0), $start);
+            }
+
+            $rotationAxis->normalize();
+            $quat = new Quat();
+            $quat->rotate(GLM::radians(180.0), $rotationAxis);
+            return $quat;
+        }
+
+        $rotationAxis = Vec3::cross($start, $dest);
+
+        $s = sqrt((1 + $cosTheta) * 2);
+        $invs = 1 / $s;
+
+        return new Quat(
+            $s * 0.5,
+            $rotationAxis->x * $invs,
+            $rotationAxis->y * $invs,
+            $rotationAxis->z * $invs
+        );
+    }
+
+    /**
+     * Return a quaternion that will rotate the object to face forward to the target position
+     * The object is assumed to be facing forward along the forward vector
+     * The object is assumed to be facing up along the up vector
+     *
+     * @param Vec3 $position
+     * @param Vec3 $targetPosition
+     * @param Vec3 $forward
+     * @param Vec3 $up
+     * @return Quat
+     */
+    public static function quatFacingForwardTowardsTarget(Vec3 $position, Vec3 $targetPosition, Vec3 $forward, Vec3 $up): Quat
+    {
+        // Find the rotation between the front of the object (that we assume towards +Z,
+        // but this depends on your model) and the desired direction
+        $direction = $targetPosition - $position;
+        $rot1 = self::rotationBetweenVectors($forward, $direction);
+
+        // Recompute desiredUp so that it's perpendicular to the direction
+        // You can skip that part if you really want to force desiredUp
+        $right = Vec3::cross($direction, $up);
+        $desiredUp = Vec3::cross($right, $direction);
+
+        // Because of the 1rst rotation, the up is probably completely screwed up.
+        // Find the rotation between the "up" of the rotated object, and the desired up
+        $newUp = Quat::multiplyVec3($rot1, new Vec3(0.0, 1.0, 0.0));
+        $rot2 = self::rotationBetweenVectors($newUp, $desiredUp);
+        $targetOrientation = $rot2 * $rot1; // remember, in reverse order.
+
+        return $targetOrientation;
+    }
+}
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index e68ba54..a141c43 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -3,7 +3,6 @@
 namespace TowerDefense\System;
 
 use GL\Math\GLM;
-use GL\Math\Mat4;
 use GL\Math\Quat;
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
@@ -13,6 +12,7 @@
 use TowerDefense\Animation\TransformPositionAnimation;
 use TowerDefense\Animation\TransformScaleAnimation;
 use TowerDefense\Component\AnimationComponent;
+use TowerDefense\Geometry\Vec3Util;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
@@ -73,50 +73,6 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         ]);*/
     }
 
-    /**
-     * Calculate the rotation between two vectors
-     *
-     * @param Vec3 $start
-     * @param Vec3 $dest
-     * @return Quat
-     */
-    private function rotationBetweenVectors(Vec3 $start, Vec3 $dest): Quat
-    {
-        $start->normalize();
-        $dest->normalize();
-
-        $cosTheta = Vec3::dot($start, $dest);
-
-        if ($cosTheta < -1 + 0.001) {
-            // special case when vectors in opposite directions:
-            // there is no "ideal" rotation axis
-            // So guess one; any will do as long as it's perpendicular to start
-            $rotationAxis = Vec3::cross(new Vec3(0.0, 0.0, 1.0), $start);
-            if ($rotationAxis->length() < 0.01) {
-                // bad luck, they were parallel, try again!
-                $rotationAxis = Vec3::cross(new Vec3(1.0, 0.0, 0.0), $start);
-            }
-
-            $rotationAxis->normalize();
-            $quat = new Quat();
-            $quat->rotate(GLM::radians(180.0), $rotationAxis);
-            return $quat;
-        }
-
-        $rotationAxis = Vec3::cross($start, $dest);
-
-        $s = sqrt((1 + $cosTheta) * 2);
-        $invs = 1 / $s;
-
-        return new Quat(
-            $s * 0.5,
-            $rotationAxis->x * $invs,
-            $rotationAxis->y * $invs,
-            $rotationAxis->z * $invs
-        );
-
-    }
-
     /**
      * Handle a click event
      *
@@ -128,31 +84,23 @@ public function clickAt(EntitiesInterface $entities, Vec3 $position): void
     {
         // get the first aircraft
         $aircraft = $this->aircrafts[0];
-        $transform = $entities->get($aircraft, Transform::class);
-
-        // set the target position to the click position
-        $targetPosition = $position;
-        //$targetPosition->y = $transform->position->y; // always keep the same height
 
-        // Find the rotation between the front of the object (that we assume towards +Z,
-        // but this depends on your model) and the desired direction
-        $direction = $targetPosition - $transform->position;
-        $rot1 = $this->rotationBetweenVectors(Transform::worldBackward(), $direction);
-
-        // Recompute desiredUp so that it's perpendicular to the direction
-        // You can skip that part if you really want to force desiredUp
-        $right = Vec3::cross($direction, Transform::worldUp());
-        $desiredUp = Vec3::cross($right, $direction);
-
-        // Because of the 1rst rotation, the up is probably completely screwed up.
-        // Find the rotation between the "up" of the rotated object, and the desired up
-        $newUp = Quat::multiplyVec3($rot1, new Vec3(0.0, 1.0, 0.0));
-        $rot2 = $this->rotationBetweenVectors($newUp, $desiredUp);
-        $targetOrientation = $rot2 * $rot1; // remember, in reverse order.
+        // get the transform component
+        $transform = $entities->get($aircraft, Transform::class);
 
-        $transform->orientation = $targetOrientation;
+        // set the new rotated orientation
+        $position->y = $transform->position->y; // lock height to current height
+        $targetOrientation = Vec3Util::quatFacingForwardTowardsTarget($transform->position, $position, Transform::worldBackward(), Transform::worldUp());
 
-        $transform->markDirty();
+        if (!$entities->has($aircraft, AnimationComponent::class)) {
+            $animationComponent = $entities->attach($aircraft, new AnimationComponent());
+        } else {
+            $animationComponent = $entities->get($aircraft, AnimationComponent::class);
+        }
+        $animationComponent->animation = new AnimationSequence([
+            TransformOrientationAnimation::fromCurrentAndTargetOrientation($transform->orientation, $targetOrientation, 500),
+            TransformPositionAnimation::fromCurrentAndTargetPosition($transform->position, $position, 1000)
+        ]);
     }
 
     /**
@@ -168,7 +116,7 @@ public function register(EntitiesInterface $entities): void
     public function unregister(EntitiesInterface $entities): void
     {
         foreach ($this->aircrafts as $aircraft) {
-            $entities->destory($aircraft);
+            $entities->destroy($aircraft);
         }
     }
 
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 21db31c..277c0ca 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -148,7 +148,7 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     $animationContainer->running = true;
                 }
                 // calculate the new position
-                $newPosition = Vec3::slerp($animationContainer->initialPosition, $animationContainer->targetPosition, $progress);
+                $newPosition = Vec3::lerp($animationContainer->initialPosition, $animationContainer->targetPosition, $progress);
                 $transform->position = $newPosition;
                 $transform->isDirty = true;
             } else if ($animationContainer instanceof TransformScaleAnimation) {
@@ -161,7 +161,7 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     $animationContainer->running = true;
                 }
                 // calculate the new scale
-                $newScale = Vec3::slerp($animationContainer->initialScale, $animationContainer->targetScale, $progress);
+                $newScale = Vec3::lerp($animationContainer->initialScale, $animationContainer->targetScale, $progress);
                 $transform->scale = $newScale;
                 $transform->isDirty = true;
             } else if ($animationContainer instanceof TransformOrientationAnimation) {

From 5cf036c3c034eb97c269ba3df81cacd145b97f54 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 27 Jan 2023 11:41:32 +0100
Subject: [PATCH 36/49] calculate needed time

---
 src/Animation/AnimationUtil.php | 42 +++++++++++++++++++++++++++++++++
 src/System/AircraftSystem.php   | 17 +++++++++----
 src/System/AnimationSystem.php  |  8 +++----
 3 files changed, 58 insertions(+), 9 deletions(-)
 create mode 100644 src/Animation/AnimationUtil.php

diff --git a/src/Animation/AnimationUtil.php b/src/Animation/AnimationUtil.php
new file mode 100644
index 0000000..9446b4d
--- /dev/null
+++ b/src/Animation/AnimationUtil.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+use GL\Math\GLM;
+use GL\Math\Quat;
+use GL\Math\Vec3;
+
+class AnimationUtil
+{
+    /**
+     * @param Vec3 $distance The distance to travel
+     * @param int $mph The speed in miles per hour
+     * @param int $limit The minimum duration in milliseconds
+     * @return int The duration in milliseconds
+     */
+    public static function calculatePositionTransitionDuration(Vec3 $distance, int $mph, int $limit = 0): int
+    {
+        // 1 mile = 1609.344 meters
+        // 1 unit = 1 meter
+        $distanceInMiles = $distance->length() / 1609.344;
+        $timeInMinutes = ($distanceInMiles / $mph) * 60;
+        $timeInMilliseconds = $timeInMinutes * 1000;
+        return max(ceil($timeInMilliseconds), $limit);
+    }
+
+    /**
+     * Calculates the duration of a rotation animation
+     *
+     * @param Quat $orientation The orientation to rotate to
+     * @param int $degreesPerSecond The speed in degrees per second
+     * @param int $limit The minimum duration in milliseconds
+     * @return int The duration in milliseconds
+     */
+    public static function calculateOrientationTransitionDuration(Quat $orientation, int $degreesPerSecond, int $limit = 0): int
+    {
+        $normalizedOrientation = Quat::normalized($orientation);
+        $angleInDegrees = rad2deg(acos($normalizedOrientation->w) * 2);
+        $timeInMilliseconds = ($angleInDegrees / $degreesPerSecond) * 1000;
+        return max(ceil($timeInMilliseconds), $limit);
+    }
+}
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index a141c43..33b0c4f 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -7,6 +7,7 @@
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
+use TowerDefense\Animation\AnimationUtil;
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
 use TowerDefense\Animation\TransformPositionAnimation;
@@ -45,7 +46,7 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $renderable->model = $this->loadedObjects['craft_speederA.obj'];
 
         $transform = $entities->attach($newAircraft, new Transform);
-        $transform->scale = $transform->scale * 100;
+        $transform->scale = $transform->scale * 50;
         $transform->position = $initialPosition;
         $transform->orientation = $initialOrientation;
         $transform->isDirty = true;
@@ -97,10 +98,16 @@ public function clickAt(EntitiesInterface $entities, Vec3 $position): void
         } else {
             $animationComponent = $entities->get($aircraft, AnimationComponent::class);
         }
-        $animationComponent->animation = new AnimationSequence([
-            TransformOrientationAnimation::fromCurrentAndTargetOrientation($transform->orientation, $targetOrientation, 500),
-            TransformPositionAnimation::fromCurrentAndTargetPosition($transform->position, $position, 1000)
-        ]);
+
+        $orientationAnimation = TransformOrientationAnimation::fromCurrentAndTargetOrientation($transform->orientation, $targetOrientation, 500);
+        $orientationAnimation->easingType = AnimationEasingType::EASE_IN_OUT;
+        $orientationAnimation->duration = AnimationUtil::calculateOrientationTransitionDuration($orientationAnimation->modifier, 360, 250);
+
+        $positionAnimation = TransformPositionAnimation::fromCurrentAndTargetPosition($transform->position, $position, 1000);
+        $positionAnimation->easingType = AnimationEasingType::EASE_IN_OUT;
+        $positionAnimation->duration = AnimationUtil::calculatePositionTransitionDuration($positionAnimation->modifier, 25, 500);
+
+        $animationComponent->animation = new AnimationSequence([$orientationAnimation, $positionAnimation]);
     }
 
     /**
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 277c0ca..8b307d0 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -79,25 +79,25 @@ private function handleAnimationContainer(object $animationContainer, Transform
             // handle the animation
             if (!$animationContainer->running) {
                 // set the required ticks based on the duration
-                $animationContainer->requiredTicks = ($animationContainer->duration / 1000.0) * $this->ticksPerSecond;
+                $animationContainer->requiredTicks = ceil(($animationContainer->duration / 1000.0) * $this->ticksPerSecond);
                 $animationContainer->currentTick = 0;
 
                 // check if the animation should wait
                 if ($animationContainer->runCount == 0 && $animationContainer->initialDelay > 0 && !$animationContainer->reversing) {
                     // set the required delay ticks based on the initial delay
-                    $animationContainer->requiredDelayTicks = ($animationContainer->initialDelay / 1000.0) * $this->ticksPerSecond;
+                    $animationContainer->requiredDelayTicks = ceil(($animationContainer->initialDelay / 1000.0) * $this->ticksPerSecond);
                     $animationContainer->currentDelayTick = 0;
                     $animationContainer->waiting = true;
                     $animationContainer->running = true;
                 } else if ($animationContainer->reverseDelay > 0 && $animationContainer->reversing) {
                     // set the required delay ticks based on the reverse delay
-                    $animationContainer->requiredDelayTicks = ($animationContainer->reverseDelay / 1000.0) * $this->ticksPerSecond;
+                    $animationContainer->requiredDelayTicks = ceil(($animationContainer->reverseDelay / 1000.0) * $this->ticksPerSecond);
                     $animationContainer->currentDelayTick = 0;
                     $animationContainer->waiting = true;
                     $animationContainer->running = true;
                 } else if (!$animationContainer->reversing && $animationContainer->runCount > 0 && $animationContainer->repeatDelay > 0) {
                     // set the required delay ticks based on the repeat delay
-                    $animationContainer->requiredDelayTicks = ($animationContainer->repeatDelay / 1000.0) * $this->ticksPerSecond;
+                    $animationContainer->requiredDelayTicks = ceil(($animationContainer->repeatDelay / 1000.0) * $this->ticksPerSecond);
                     $animationContainer->currentDelayTick = 0;
                     $animationContainer->waiting = true;
                     $animationContainer->running = true;

From 68dc5b4c55d628ff62090a2878c751fb54fbf28e Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 27 Jan 2023 15:21:31 +0100
Subject: [PATCH 37/49] Animation Delegate support

---
 src/Animation/AnimationSystemDelegate.php     | 49 ++++++++++++
 src/Animation/BaseAnimation.php               |  2 +
 src/Animation/BaseAnimationContainer.php      |  6 +-
 .../TransformOrientationAnimation.php         |  3 +
 src/Animation/TransformPositionAnimation.php  |  3 +
 src/Animation/TransformScaleAnimation.php     |  3 +
 src/Scene/LevelScene.php                      |  1 +
 src/System/AircraftSystem.php                 | 51 ++++++++++++-
 src/System/AnimationSystem.php                | 75 +++++++++++++++++--
 9 files changed, 182 insertions(+), 11 deletions(-)
 create mode 100644 src/Animation/AnimationSystemDelegate.php

diff --git a/src/Animation/AnimationSystemDelegate.php b/src/Animation/AnimationSystemDelegate.php
new file mode 100644
index 0000000..cd80267
--- /dev/null
+++ b/src/Animation/AnimationSystemDelegate.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace TowerDefense\Animation;
+
+/**
+ * Interface for animation system delegates
+ */
+interface AnimationSystemDelegate
+{
+    /**
+     * Called when an animation starts
+     *
+     * @param BaseAnimation $animation The animation
+     * @param int $entity The entity the animation is running on
+     * @param bool $reversing Is the animation reversing?
+     * @param bool $repeating Is the animation repeating?
+     * @param bool $waiting Is the animation waiting?
+     * @return void
+     */
+    public function animationDidStart(BaseAnimation $animation, int $entity, bool $reversing, bool $repeating, bool $waiting): void;
+
+    /**
+     * Called when an animation stops
+     *
+     * @param BaseAnimation $animation
+     * @param int $entity
+     * @param bool $finished
+     * @return void
+     */
+    public function animationDidStop(BaseAnimation $animation, int $entity, bool $finished): void;
+
+    /**
+     * Called when an animation group begins
+     *
+     * @param BaseAnimationContainer $animationGroup The animation group
+     * @param int $entity The entity the animation group is running on
+     * @return void
+     */
+    public function animationGroupDidBegin(BaseAnimationContainer $animationGroup, int $entity): void;
+
+    /**
+     * Called when an animation group finishes
+     *
+     * @param BaseAnimationContainer $animationGroup The animation group
+     * @param int $entity The entity the animation group is running on
+     * @return void
+     */
+    public function animationGroupDidFinish(BaseAnimationContainer $animationGroup, int $entity): void;
+}
diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
index 00edfa7..113e7e3 100644
--- a/src/Animation/BaseAnimation.php
+++ b/src/Animation/BaseAnimation.php
@@ -30,6 +30,7 @@ class BaseAnimation
      * @param bool $reverse Whether the animation should reverse
      * @param int|null $reverseCount The number of times the animation should reverse
      * @param int|null $reverseDelay The delay between reverses in milliseconds
+     * @param int|null $tag The tag of the animation
      */
     public function __construct(
         public int                  $duration,
@@ -41,6 +42,7 @@ public function __construct(
         public ?bool                $reverse = false,
         public ?int                 $reverseCount = 0,
         public ?int                 $reverseDelay = 0,
+        public ?int                 $tag = null
     )
     {
     }
diff --git a/src/Animation/BaseAnimationContainer.php b/src/Animation/BaseAnimationContainer.php
index c383899..38d0120 100644
--- a/src/Animation/BaseAnimationContainer.php
+++ b/src/Animation/BaseAnimationContainer.php
@@ -10,8 +10,12 @@ class BaseAnimationContainer
 
     /**
      * @param BaseAnimation[] $animations Animations to run
+     * @param int|null $tag Tag to identify this animation container
      */
-    public function __construct(public ?array $animations = [])
+    public function __construct(
+        public ?array $animations = [],
+        public ?int   $tag = null
+    )
     {
     }
 }
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
index 51ec457..22eec91 100644
--- a/src/Animation/TransformOrientationAnimation.php
+++ b/src/Animation/TransformOrientationAnimation.php
@@ -23,6 +23,7 @@ class TransformOrientationAnimation extends BaseAnimation
      * @param bool $reverse Whether the animation should reverse
      * @param int|null $reverseCount The number of times the animation should reverse
      * @param int|null $reverseDelay The delay between each reverse in milliseconds
+     * @param int|null $tag The tag of the animation
      */
     public function __construct(
         public Quat          $modifier,
@@ -35,6 +36,7 @@ public function __construct(
         ?bool                $reverse = false,
         ?int                 $reverseCount = 0,
         ?int                 $reverseDelay = 0,
+        ?int                 $tag = null
     )
     {
         parent::__construct(
@@ -47,6 +49,7 @@ public function __construct(
             $reverse,
             $reverseCount,
             $reverseDelay,
+            $tag
         );
     }
 
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
index 48418d6..6f01f6c 100644
--- a/src/Animation/TransformPositionAnimation.php
+++ b/src/Animation/TransformPositionAnimation.php
@@ -23,6 +23,7 @@ class TransformPositionAnimation extends BaseAnimation
      * @param bool $reverse Whether the animation should reverse
      * @param int|null $reverseCount The number of times the animation should reverse
      * @param int|null $reverseDelay The delay between each reverse in milliseconds
+     * @param int|null $tag The tag of the animation
      */
     public function __construct(
         public Vec3          $modifier,
@@ -35,6 +36,7 @@ public function __construct(
         ?bool                $reverse = false,
         ?int                 $reverseCount = 0,
         ?int                 $reverseDelay = 0,
+        ?int                 $tag = null
     )
     {
         parent::__construct(
@@ -47,6 +49,7 @@ public function __construct(
             $reverse,
             $reverseCount,
             $reverseDelay,
+            $tag
         );
     }
 
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
index f14a415..5188340 100644
--- a/src/Animation/TransformScaleAnimation.php
+++ b/src/Animation/TransformScaleAnimation.php
@@ -23,6 +23,7 @@ class TransformScaleAnimation extends BaseAnimation
      * @param bool $reverse Whether the animation should reverse
      * @param int|null $reverseCount The number of times the animation should reverse
      * @param int|null $reverseDelay The delay between each reverse in milliseconds
+     * @param int|null $tag The tag of the animation
      */
     public function __construct(
         public Vec3          $modifier,
@@ -35,6 +36,7 @@ public function __construct(
         ?bool                $reverse = false,
         ?int                 $reverseCount = 0,
         ?int                 $reverseDelay = 0,
+        ?int                 $tag = null
     )
     {
         parent::__construct(
@@ -47,6 +49,7 @@ public function __construct(
             $reverse,
             $reverseCount,
             $reverseDelay,
+            $tag
         );
     }
 
diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index 841909e..a0a3777 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -156,6 +156,7 @@ private function prepareScene()
         $this->renderingSystem->register($this->entities);
         $this->animationSystem->register($this->entities);
         $this->aircraftSystem->register($this->entities);
+        $this->animationSystem->addAnimationDelegate($this->aircraftSystem);
 
         // create a camera
         $cameraEntity = $this->entities->create();
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 33b0c4f..81b388b 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -7,7 +7,10 @@
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
+use TowerDefense\Animation\AnimationSystemDelegate;
 use TowerDefense\Animation\AnimationUtil;
+use TowerDefense\Animation\BaseAnimation;
+use TowerDefense\Animation\BaseAnimationContainer;
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
 use TowerDefense\Animation\TransformPositionAnimation;
@@ -20,7 +23,7 @@
 use VISU\Geo\Transform;
 use VISU\Graphics\Rendering\RenderContext;
 
-class AircraftSystem implements SystemInterface
+class AircraftSystem implements SystemInterface, AnimationSystemDelegate
 {
     /**
      * @var int[] $aircrafts The list of aircraft entity ids
@@ -51,7 +54,7 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         $transform->orientation = $initialOrientation;
         $transform->isDirty = true;
 
-        // $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
+        $animationComponent = $entities->attach($newAircraft, new AnimationComponent());
         $orientation = new Quat();
         $orientation->rotate(GLM::radians(-90.0), new Vec3(0.0, 1.0, 0.0));
         /*$animationComponent->animation = new AnimationSequence([
@@ -67,11 +70,11 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
             new TransformPositionAnimation(new Vec3(500.0, 0.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT),
         ]);*/
 
-        /*$animationComponent->animation = new AnimationSequence([
+        $animationComponent->animation = new AnimationSequence([
             new TransformOrientationAnimation($orientation, 1000, AnimationEasingType::EASE_IN_OUT, initialDelay: 0, repeat: true, repeatCount: 4, repeatDelay: 1000, reverse: true, reverseCount: 1, reverseDelay: 250),
             new TransformPositionAnimation(new Vec3(-500.0, -200.0, 0.0), 2000, AnimationEasingType::EASE_IN_OUT, initialDelay: 500, repeat: true, repeatCount: 1, repeatDelay: 500, reverse: true, reverseCount: 1, reverseDelay: 500),
             new TransformScaleAnimation(new Vec3(0.5, 0.5, 0.5), 1000, AnimationEasingType::EASE_IN_OUT, repeat: true, repeatCount: 3, reverse: true, reverseCount: 4)
-        ]);*/
+        ]);
     }
 
     /**
@@ -142,4 +145,44 @@ public function render(EntitiesInterface $entities, RenderContext $context): voi
     {
         // TODO: Implement render() method.
     }
+
+    /**
+     * @inheritDoc
+     */
+    public function animationDidStart(BaseAnimation $animation, int $entity, bool $reversing, bool $repeating, bool $waiting): void
+    {
+        // TODO: Implement animationDidStart() method.
+        echo "Animation started: " . get_class($animation) . PHP_EOL;
+        echo "Reversing: " . ($reversing ? 'true' : 'false') . PHP_EOL;
+        echo "Repeating: " . ($repeating ? 'true' : 'false') . PHP_EOL;
+        echo "Waiting: " . ($waiting ? 'true' : 'false') . PHP_EOL . PHP_EOL;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function animationDidStop(BaseAnimation $animation, int $entity, bool $finished): void
+    {
+        // TODO: Implement animationDidStop() method.
+        echo "Animation stopped: " . get_class($animation) . PHP_EOL;
+        echo "Finished: " . ($finished ? 'true' : 'false') . PHP_EOL . PHP_EOL;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function animationGroupDidBegin(BaseAnimationContainer $animationGroup, int $entity): void
+    {
+        // TODO: Implement animationGroupDidBegin() method.
+        echo "Animation group began: " . get_class($animationGroup) . PHP_EOL . PHP_EOL;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function animationGroupDidFinish(BaseAnimationContainer $animationGroup, int $entity): void
+    {
+        // TODO: Implement animationGroupDidFinish() method.
+        echo "Animation group finished: " . get_class($animationGroup) . PHP_EOL . PHP_EOL;
+    }
 }
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
index 8b307d0..d7d48c9 100644
--- a/src/System/AnimationSystem.php
+++ b/src/System/AnimationSystem.php
@@ -6,7 +6,9 @@
 use GL\Math\Vec3;
 use TowerDefense\Animation\AnimationEasingType;
 use TowerDefense\Animation\AnimationSequence;
+use TowerDefense\Animation\AnimationSystemDelegate;
 use TowerDefense\Animation\BaseAnimation;
+use TowerDefense\Animation\BaseAnimationContainer;
 use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
 use TowerDefense\Animation\TransformPositionAnimation;
@@ -19,6 +21,11 @@
 
 class AnimationSystem implements SystemInterface
 {
+    /**
+     * @var AnimationSystemDelegate[] $animationDelegates The list of animation delegates
+     */
+    private $animationDelegates = [];
+
     public function __construct(private readonly int $ticksPerSecond)
     {
     }
@@ -52,7 +59,7 @@ public function update(EntitiesInterface $entities): void
             if (!$animationComponent->animation->finished) {
                 // get the transform of the entity
                 $transform = $entities->get($entity, Transform::class);
-                $this->handleAnimationContainer($animationComponent->animation, $transform);
+                $this->handleAnimationContainer($animationComponent->animation, $transform, $entity);
             }
         }
     }
@@ -65,14 +72,39 @@ public function render(EntitiesInterface $entities, RenderContext $context): voi
         // TODO: Implement render() method.
     }
 
+    /**
+     * Adds an animation delegate
+     *
+     * @param AnimationSystemDelegate $delegate
+     * @return void
+     */
+    public function addAnimationDelegate(AnimationSystemDelegate $delegate): void
+    {
+        $this->animationDelegates[] = $delegate;
+    }
+
+    /**
+     * Adds an animation delegate
+     *
+     * @param AnimationSystemDelegate $delegate
+     * @return void
+     */
+    public function removeAnimationDelegate(AnimationSystemDelegate $delegate): void
+    {
+        if (($key = array_search($delegate, $this->animationDelegates, true)) !== false) {
+            unset($this->animationDelegates[$key]);
+        }
+    }
+
     /**
      * Handles an animation container
      *
      * @param object $animationContainer The animation container
      * @param Transform $transform The transform of the entity
+     * @param int $entity The entity
      * @return void
      */
-    private function handleAnimationContainer(object $animationContainer, Transform $transform): void
+    private function handleAnimationContainer(object $animationContainer, Transform $transform, int $entity): void
     {
         // check the AnimationContainerType of $animationContainer
         if ($animationContainer instanceof BaseAnimation) {
@@ -102,6 +134,11 @@ private function handleAnimationContainer(object $animationContainer, Transform
                     $animationContainer->waiting = true;
                     $animationContainer->running = true;
                 }
+                // call the animation delegates
+                foreach ($this->animationDelegates as $animationDelegate) {
+                    // the animation did start
+                    $animationDelegate->animationDidStart($animationContainer, $entity, $animationContainer->reversing, (!$animationContainer->reversing && $animationContainer->runCount > 0), $animationContainer->waiting);
+                }
             }
 
             // check if the animation is waiting
@@ -218,6 +255,12 @@ private function handleAnimationContainer(object $animationContainer, Transform
                         $animationContainer->finished = false;
                     }
                 }
+
+                // call the animation delegates
+                foreach ($this->animationDelegates as $delegate) {
+                    // the animation did stop
+                    $delegate->animationDidStop($animationContainer, $entity, $animationContainer->finished);
+                }
             }
         } else {
             $finishedAnimations = 0;
@@ -225,9 +268,16 @@ private function handleAnimationContainer(object $animationContainer, Transform
                 // get the current animation in line
                 foreach ($animationContainer->animations as $animation) {
                     if (!$animation->finished) {
-                        $animationContainer->running = true;
+                        if (!$animationContainer->running) {
+                            $animationContainer->running = true;
+                            // call the animation delegates
+                            foreach ($this->animationDelegates as $delegate) {
+                                // the animation did start
+                                $delegate->animationGroupDidBegin($animationContainer, $entity);
+                            }
+                        }
                         // handle the animation
-                        $this->handleAnimationContainer($animation, $transform);
+                        $this->handleAnimationContainer($animation, $transform, $entity);
                         break;
                     } else {
                         $finishedAnimations++;
@@ -237,9 +287,16 @@ private function handleAnimationContainer(object $animationContainer, Transform
                 // handle all animations "in parallel"
                 foreach ($animationContainer->animations as $animation) {
                     if (!$animation->finished) {
-                        $animationContainer->running = true;
+                        if (!$animationContainer->running) {
+                            $animationContainer->running = true;
+                            // call the animation delegates
+                            foreach ($this->animationDelegates as $delegate) {
+                                // the animation group did start
+                                $delegate->animationGroupDidBegin($animationContainer, $entity);
+                            }
+                        }
                         // handle the animation
-                        $this->handleAnimationContainer($animation, $transform);
+                        $this->handleAnimationContainer($animation, $transform, $entity);
                     } else {
                         $finishedAnimations++;
                     }
@@ -247,7 +304,13 @@ private function handleAnimationContainer(object $animationContainer, Transform
             }
             // check if all animations in the container are finished and set the finished flag
             if ($finishedAnimations == count($animationContainer->animations)) {
+                /* @var $animationContainer BaseAnimationContainer */
                 $animationContainer->finished = true;
+                // call the animation delegates
+                foreach ($this->animationDelegates as $delegate) {
+                    // the animation group did start
+                    $delegate->animationGroupDidFinish($animationContainer, $entity);
+                }
             }
         }
     }

From 9b16b10f0c2dc22811d75efe7345d55a9a215414 Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 27 Jan 2023 15:33:36 +0100
Subject: [PATCH 38/49] If the angle is greater than 180 degrees, then we need
 to rotate the other shorter way

---
 src/Animation/AnimationUtil.php | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/Animation/AnimationUtil.php b/src/Animation/AnimationUtil.php
index 9446b4d..f787c3f 100644
--- a/src/Animation/AnimationUtil.php
+++ b/src/Animation/AnimationUtil.php
@@ -36,6 +36,10 @@ public static function calculateOrientationTransitionDuration(Quat $orientation,
     {
         $normalizedOrientation = Quat::normalized($orientation);
         $angleInDegrees = rad2deg(acos($normalizedOrientation->w) * 2);
+        // If the angle is greater than 180 degrees, then we need to rotate the other shorter way
+        if ($angleInDegrees > 180) {
+            $angleInDegrees = 360 - $angleInDegrees;
+        }
         $timeInMilliseconds = ($angleInDegrees / $degreesPerSecond) * 1000;
         return max(ceil($timeInMilliseconds), $limit);
     }

From f70e3b05a3bd99bf2ee53ce837d75eb871e09fce Mon Sep 17 00:00:00 2001
From: Boris Stock <boris.stock@cinergy.ch>
Date: Fri, 27 Jan 2023 17:30:34 +0100
Subject: [PATCH 39/49] refactoring

---
 src/Scene/LevelScene.php      | 43 ++++++------------------------
 src/System/AircraftSystem.php | 49 ++++++++++++++++++++++++++++-------
 2 files changed, 48 insertions(+), 44 deletions(-)

diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index ee093c9..c2dc5b0 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -6,7 +6,6 @@
 use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
-use TowerDefense\Component\HeightmapComponent;
 use TowerDefense\Debug\DebugTextOverlay;
 use TowerDefense\Renderer\TerrainRenderer;
 use TowerDefense\System\AircraftSystem;
@@ -24,7 +23,6 @@
 use VISU\Graphics\RenderTarget;
 use VISU\OS\Key;
 use VISU\Signals\Input\KeySignal;
-use VISU\Signals\Input\MouseClickSignal;
 use VISU\System\VISUCameraSystem;
 use VISU\System\VISULowPoly\LPModel;
 use VISU\System\VISULowPoly\LPObjLoader;
@@ -71,25 +69,17 @@ abstract class LevelScene extends BaseScene implements DevEntityPickerDelegate
      */
     private int $keyboardHandlerId = 0;
 
-    /**
-     * Function ID for mouse click handler
-     */
-    private int $mouseClickHandlerId = 0;
-
-
     /**
      * Constructor
      */
     public function __construct(
         protected GameContainer $container,
-    )
-    {
+    ) {
         parent::__construct($container);
 
         // register key handler for debugging
         // usally a system should handle this but this is temporary
         $this->keyboardHandlerId = $this->container->resolveVisuDispatcher()->register('input.key', [$this, 'handleKeyboardEvent']);
-        $this->mouseClickHandlerId = $this->container->resolveVisuDispatcher()->register('input.mouse_click', [$this, 'handleMouseClickEvent']);
 
         // load all space kit models
         $this->objectLoader = new LPObjLoader($container->resolveGL());
@@ -105,7 +95,7 @@ public function __construct(
             $this->container->resolveInput(),
             $this->container->resolveVisuDispatcher()
         );
-        
+
         // heightmap system (this is not the terrain)
         $this->heightmapSystem = new HeightmapSystem(
             $this->container->resolveGL(),
@@ -116,7 +106,7 @@ public function __construct(
         );
 
         $this->animationSystem = new AnimationSystem(60); // @TODO get from game loop
-        $this->aircraftSystem = new AircraftSystem($this->loadedObjects);
+        $this->aircraftSystem = new AircraftSystem($this->loadedObjects, $this->entities, $this->container->resolveVisuDispatcher(), $this->container->resolveInput());
         $this->animationSystem->addAnimationDelegate($this->aircraftSystem);
 
         // bind all systems to the scene itself
@@ -147,7 +137,7 @@ public function __destruct()
     /**
      * Constrcuts the DEV entity picker
      */
-    public function constructDevEntityPicker() : void
+    public function constructDevEntityPicker(): void
     {
         // right now using the window framebuffer as render target
         // for entity picking, we need to test if is more efficient
@@ -179,7 +169,7 @@ public function constructDevEntityPicker() : void
      * 
      * @return void 
      */
-    public function load() : void
+    public function load(): void
     {
         // load the terrain
         $this->terrainRenderer->loadTerrainFromObj(VISU_PATH_RESOURCES . '/terrain/alien_planet/alien_planet_terrain.obj');
@@ -235,7 +225,7 @@ private function prepareScene()
      * 
      * @return void 
      */
-    public function update() : void
+    public function update(): void
     {
         $this->cameraSystem->update($this->entities);
         $this->aircraftSystem->update($this->entities);
@@ -249,7 +239,7 @@ public function update() : void
      * 
      * @param RenderContext $context
      */
-    public function render(RenderContext $context) : void
+    public function render(RenderContext $context): void
     {
         DebugTextOverlay::debugString("Press 'SHIFT' + NUM for render debug: NONE=0, POS=1, VPOS=2, NORM=3, DEPTH=4, ALBEDO=5");
 
@@ -269,7 +259,7 @@ public function render(RenderContext $context) : void
     /**
      * Keyboard event handler
      */
-    public function handleKeyboardEvent(KeySignal $signal) : void
+    public function handleKeyboardEvent(KeySignal $signal): void
     {
         if ($signal->mods !== Key::MOD_SHIFT && $signal->action == GLFW_PRESS) {
             return;
@@ -292,23 +282,6 @@ public function handleKeyboardEvent(KeySignal $signal) : void
         }
     }
 
-    /**
-     * Mouse click event handler
-     */
-    public function handleMouseClickEvent(MouseClickSignal $signal)
-    {
-        $p = $this->container->resolveInput()->getNormalizedCursorPosition();
-        $heightmapComponent = $this->entities->getSingleton(HeightmapComponent::class);
-        $o = $heightmapComponent->cursorWorldPosition;
-
-        echo "Mouse click event native: x " . $signal->position->x . " y " . $signal->position->y . PHP_EOL;
-        echo "Mouse click event normalized: x" . $p->x . " y " . $p->y . PHP_EOL;
-        echo "Mouse click event heightmap based: x " . $o->x . " y " . $o->y . " z " . $o->z . PHP_EOL;
-
-        // forward the click to the aircraft system
-        $this->aircraftSystem->clickAt($this->entities, $o);
-    }
-
     /**
      * Called when the dev entity picker has selected an entity
      *
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index 81b388b..ca303a0 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -11,17 +11,20 @@
 use TowerDefense\Animation\AnimationUtil;
 use TowerDefense\Animation\BaseAnimation;
 use TowerDefense\Animation\BaseAnimationContainer;
-use TowerDefense\Animation\ParallelAnimations;
 use TowerDefense\Animation\TransformOrientationAnimation;
 use TowerDefense\Animation\TransformPositionAnimation;
 use TowerDefense\Animation\TransformScaleAnimation;
 use TowerDefense\Component\AnimationComponent;
+use TowerDefense\Component\HeightmapComponent;
 use TowerDefense\Geometry\Vec3Util;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
 use VISU\Geo\Transform;
 use VISU\Graphics\Rendering\RenderContext;
+use VISU\OS\Input;
+use VISU\Signal\Dispatcher;
+use VISU\Signals\Input\MouseClickSignal;
 
 class AircraftSystem implements SystemInterface, AnimationSystemDelegate
 {
@@ -30,14 +33,24 @@ class AircraftSystem implements SystemInterface, AnimationSystemDelegate
      */
     private array $aircrafts = [];
 
+    /**
+     * Function ID for mouse click handler
+     */
+    private int $mouseClickHandlerId = 0;
+
     /**
      * Constructor
      *
      * @param array $loadedObjects
      *
      */
-    public function __construct(private array &$loadedObjects)
-    {
+    public function __construct(
+        private array &$loadedObjects,
+        private EntitiesInterface &$entities,
+        private Dispatcher $dispatcher,
+        private Input $input
+    ) {
+        $this->mouseClickHandlerId = $this->dispatcher->register('input.mouse_click', [$this, 'handleMouseClickEvent']);
     }
 
     public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition, Quat $initialOrientation): void
@@ -77,6 +90,23 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
         ]);
     }
 
+    /**
+     * Mouse click event handler
+     */
+    public function handleMouseClickEvent(MouseClickSignal $signal)
+    {
+        $p = $this->input->getNormalizedCursorPosition();
+        $heightmapComponent = $this->entities->getSingleton(HeightmapComponent::class);
+        $o = $heightmapComponent->cursorWorldPosition;
+
+        echo "Mouse click event native: x " . $signal->position->x . " y " . $signal->position->y . PHP_EOL;
+        echo "Mouse click event normalized: x " . $p->x . " y " . $p->y . PHP_EOL;
+        echo "Mouse click event heightmap based: x " . $o->x . " y " . $o->y . " z " . $o->z . PHP_EOL . PHP_EOL;
+
+        // forward the click to the aircraft system
+        $this->clickAt($o);
+    }
+
     /**
      * Handle a click event
      *
@@ -84,22 +114,22 @@ public function spawnAircraft(EntitiesInterface $entities, Vec3 $initialPosition
      * @param Vec3 $position
      * @return void
      */
-    public function clickAt(EntitiesInterface $entities, Vec3 $position): void
+    private function clickAt(Vec3 $position): void
     {
         // get the first aircraft
         $aircraft = $this->aircrafts[0];
 
         // get the transform component
-        $transform = $entities->get($aircraft, Transform::class);
+        $transform = $this->entities->get($aircraft, Transform::class);
 
         // set the new rotated orientation
         $position->y = $transform->position->y; // lock height to current height
         $targetOrientation = Vec3Util::quatFacingForwardTowardsTarget($transform->position, $position, Transform::worldBackward(), Transform::worldUp());
 
-        if (!$entities->has($aircraft, AnimationComponent::class)) {
-            $animationComponent = $entities->attach($aircraft, new AnimationComponent());
+        if (!$this->entities->has($aircraft, AnimationComponent::class)) {
+            $animationComponent = $this->entities->attach($aircraft, new AnimationComponent());
         } else {
-            $animationComponent = $entities->get($aircraft, AnimationComponent::class);
+            $animationComponent = $this->entities->get($aircraft, AnimationComponent::class);
         }
 
         $orientationAnimation = TransformOrientationAnimation::fromCurrentAndTargetOrientation($transform->orientation, $targetOrientation, 500);
@@ -125,6 +155,8 @@ public function register(EntitiesInterface $entities): void
      */
     public function unregister(EntitiesInterface $entities): void
     {
+        $this->dispatcher->unregister('input.mouse_click', $this->mouseClickHandlerId);
+
         foreach ($this->aircrafts as $aircraft) {
             $entities->destroy($aircraft);
         }
@@ -135,7 +167,6 @@ public function unregister(EntitiesInterface $entities): void
      */
     public function update(EntitiesInterface $entities): void
     {
-
     }
 
     /**

From 2416580461654d9fc42fe06120a781f40c1fc246 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 28 Jan 2023 19:05:38 +0100
Subject: [PATCH 40/49] playing around

---
 src/System/AircraftSystem.php | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index ca303a0..e89c6e1 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -123,9 +123,13 @@ private function clickAt(Vec3 $position): void
         $transform = $this->entities->get($aircraft, Transform::class);
 
         // set the new rotated orientation
+        $height = $position->y;
         $position->y = $transform->position->y; // lock height to current height
         $targetOrientation = Vec3Util::quatFacingForwardTowardsTarget($transform->position, $position, Transform::worldBackward(), Transform::worldUp());
 
+        // set the target height of the terrain + 10 "meters"
+        $position->y = $height + 10.0;
+
         if (!$this->entities->has($aircraft, AnimationComponent::class)) {
             $animationComponent = $this->entities->attach($aircraft, new AnimationComponent());
         } else {

From 36c0c0867b68bcf53e7b541ac5e2fb8f8c4bcb35 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Mon, 6 Feb 2023 20:14:18 +0100
Subject: [PATCH 41/49] dep upgrade

---
 composer.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.lock b/composer.lock
index dc57a79..9fbcfb4 100644
--- a/composer.lock
+++ b/composer.lock
@@ -168,7 +168,7 @@
             "dist": {
                 "type": "path",
                 "url": "../visu",
-                "reference": "5ab382222d1473858e0bebe16b58c8b7c95e63d7"
+                "reference": "f7c215689198166a3a1f3beed1e1c769a413ac14"
             },
             "require": {
                 "clancats/container": "^1.3",

From 9649dbd64fae635606568413fe8b474b4268aa57 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Mon, 6 Feb 2023 20:28:10 +0100
Subject: [PATCH 42/49] Use VISU animations

---
 src/Animation/AnimationEasingType.php         |  29 --
 src/Animation/AnimationSequence.php           |  10 -
 src/Animation/AnimationSystemDelegate.php     |  49 ---
 src/Animation/AnimationUtil.php               |  46 ---
 src/Animation/BaseAnimation.php               |  49 ---
 src/Animation/BaseAnimationContainer.php      |  21 --
 src/Animation/ParallelAnimations.php          |  10 -
 .../TransformOrientationAnimation.php         |  70 ----
 src/Animation/TransformPositionAnimation.php  |  68 ----
 src/Animation/TransformScaleAnimation.php     |  68 ----
 src/Component/AnimationComponent.php          |   8 -
 src/Geometry/Vec3Util.php                     |  91 -----
 src/Scene/LevelScene.php                      |   6 +-
 src/System/AircraftSystem.php                 |  26 +-
 src/System/AnimationSystem.php                | 317 ------------------
 15 files changed, 16 insertions(+), 852 deletions(-)
 delete mode 100644 src/Animation/AnimationEasingType.php
 delete mode 100644 src/Animation/AnimationSequence.php
 delete mode 100644 src/Animation/AnimationSystemDelegate.php
 delete mode 100644 src/Animation/AnimationUtil.php
 delete mode 100644 src/Animation/BaseAnimation.php
 delete mode 100644 src/Animation/BaseAnimationContainer.php
 delete mode 100644 src/Animation/ParallelAnimations.php
 delete mode 100644 src/Animation/TransformOrientationAnimation.php
 delete mode 100644 src/Animation/TransformPositionAnimation.php
 delete mode 100644 src/Animation/TransformScaleAnimation.php
 delete mode 100644 src/Component/AnimationComponent.php
 delete mode 100644 src/Geometry/Vec3Util.php
 delete mode 100644 src/System/AnimationSystem.php

diff --git a/src/Animation/AnimationEasingType.php b/src/Animation/AnimationEasingType.php
deleted file mode 100644
index 9c8f350..0000000
--- a/src/Animation/AnimationEasingType.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * Easing types for animations.
- */
-enum AnimationEasingType
-{
-    /**
-     * Linear easing.
-     */
-    case LINEAR;
-
-    /**
-     * Ease in easing.
-     */
-    case EASE_IN;
-
-    /**
-     * Ease out easing.
-     */
-    case EASE_OUT;
-
-    /**
-     * Ease in and out easing.
-     */
-    case EASE_IN_OUT;
-}
diff --git a/src/Animation/AnimationSequence.php b/src/Animation/AnimationSequence.php
deleted file mode 100644
index 14914a0..0000000
--- a/src/Animation/AnimationSequence.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * A sequence of animations, played one after the other.
- */
-class AnimationSequence extends BaseAnimationContainer
-{
-}
diff --git a/src/Animation/AnimationSystemDelegate.php b/src/Animation/AnimationSystemDelegate.php
deleted file mode 100644
index cd80267..0000000
--- a/src/Animation/AnimationSystemDelegate.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * Interface for animation system delegates
- */
-interface AnimationSystemDelegate
-{
-    /**
-     * Called when an animation starts
-     *
-     * @param BaseAnimation $animation The animation
-     * @param int $entity The entity the animation is running on
-     * @param bool $reversing Is the animation reversing?
-     * @param bool $repeating Is the animation repeating?
-     * @param bool $waiting Is the animation waiting?
-     * @return void
-     */
-    public function animationDidStart(BaseAnimation $animation, int $entity, bool $reversing, bool $repeating, bool $waiting): void;
-
-    /**
-     * Called when an animation stops
-     *
-     * @param BaseAnimation $animation
-     * @param int $entity
-     * @param bool $finished
-     * @return void
-     */
-    public function animationDidStop(BaseAnimation $animation, int $entity, bool $finished): void;
-
-    /**
-     * Called when an animation group begins
-     *
-     * @param BaseAnimationContainer $animationGroup The animation group
-     * @param int $entity The entity the animation group is running on
-     * @return void
-     */
-    public function animationGroupDidBegin(BaseAnimationContainer $animationGroup, int $entity): void;
-
-    /**
-     * Called when an animation group finishes
-     *
-     * @param BaseAnimationContainer $animationGroup The animation group
-     * @param int $entity The entity the animation group is running on
-     * @return void
-     */
-    public function animationGroupDidFinish(BaseAnimationContainer $animationGroup, int $entity): void;
-}
diff --git a/src/Animation/AnimationUtil.php b/src/Animation/AnimationUtil.php
deleted file mode 100644
index f787c3f..0000000
--- a/src/Animation/AnimationUtil.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-use GL\Math\GLM;
-use GL\Math\Quat;
-use GL\Math\Vec3;
-
-class AnimationUtil
-{
-    /**
-     * @param Vec3 $distance The distance to travel
-     * @param int $mph The speed in miles per hour
-     * @param int $limit The minimum duration in milliseconds
-     * @return int The duration in milliseconds
-     */
-    public static function calculatePositionTransitionDuration(Vec3 $distance, int $mph, int $limit = 0): int
-    {
-        // 1 mile = 1609.344 meters
-        // 1 unit = 1 meter
-        $distanceInMiles = $distance->length() / 1609.344;
-        $timeInMinutes = ($distanceInMiles / $mph) * 60;
-        $timeInMilliseconds = $timeInMinutes * 1000;
-        return max(ceil($timeInMilliseconds), $limit);
-    }
-
-    /**
-     * Calculates the duration of a rotation animation
-     *
-     * @param Quat $orientation The orientation to rotate to
-     * @param int $degreesPerSecond The speed in degrees per second
-     * @param int $limit The minimum duration in milliseconds
-     * @return int The duration in milliseconds
-     */
-    public static function calculateOrientationTransitionDuration(Quat $orientation, int $degreesPerSecond, int $limit = 0): int
-    {
-        $normalizedOrientation = Quat::normalized($orientation);
-        $angleInDegrees = rad2deg(acos($normalizedOrientation->w) * 2);
-        // If the angle is greater than 180 degrees, then we need to rotate the other shorter way
-        if ($angleInDegrees > 180) {
-            $angleInDegrees = 360 - $angleInDegrees;
-        }
-        $timeInMilliseconds = ($angleInDegrees / $degreesPerSecond) * 1000;
-        return max(ceil($timeInMilliseconds), $limit);
-    }
-}
diff --git a/src/Animation/BaseAnimation.php b/src/Animation/BaseAnimation.php
deleted file mode 100644
index 113e7e3..0000000
--- a/src/Animation/BaseAnimation.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * Base for animations
- */
-class BaseAnimation
-{
-    public int $runCount = 0; // Number of times the animation has run
-    public int $repeatedCount = 0; // Number of times the animation has repeated
-    public int $reversedCount = 0; // Number of times the animation has reversed
-    public float $progress = 0.0; // Progress of the current animation run
-    public bool $finished = false; // Whether the animation has finished
-    public bool $running = false; // Whether the animation is running
-    public bool $reversing = false; // Whether the animation is reversing
-    public int $requiredTicks = 0; // Number of ticks required to complete the animation
-    public int $requiredDelayTicks = 0; // Number of ticks required to delay the animation
-    public int $currentTick = 0; // Current tick of the animation
-    public int $currentDelayTick = 0; // Current delay tick of the animation
-    public bool $waiting = false; // Whether the animation is waiting for the delay to finish
-
-    /**
-     * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType|null $easingType The easing type of the animation
-     * @param int|null $initialDelay The initial delay of the animation in milliseconds
-     * @param bool $repeat Whether the animation should repeat
-     * @param int|null $repeatCount The number of times the animation should repeat
-     * @param int|null $repeatDelay The delay between repeats in milliseconds
-     * @param bool $reverse Whether the animation should reverse
-     * @param int|null $reverseCount The number of times the animation should reverse
-     * @param int|null $reverseDelay The delay between reverses in milliseconds
-     * @param int|null $tag The tag of the animation
-     */
-    public function __construct(
-        public int                  $duration,
-        public ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        public ?int                 $initialDelay = 0,
-        public ?bool                $repeat = false,
-        public ?int                 $repeatCount = 0,
-        public ?int                 $repeatDelay = 0,
-        public ?bool                $reverse = false,
-        public ?int                 $reverseCount = 0,
-        public ?int                 $reverseDelay = 0,
-        public ?int                 $tag = null
-    )
-    {
-    }
-}
diff --git a/src/Animation/BaseAnimationContainer.php b/src/Animation/BaseAnimationContainer.php
deleted file mode 100644
index 38d0120..0000000
--- a/src/Animation/BaseAnimationContainer.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-class BaseAnimationContainer
-{
-    public bool $finished = false; // Are all animations finished?
-
-    public bool $running = false; // Is any animation running?
-
-    /**
-     * @param BaseAnimation[] $animations Animations to run
-     * @param int|null $tag Tag to identify this animation container
-     */
-    public function __construct(
-        public ?array $animations = [],
-        public ?int   $tag = null
-    )
-    {
-    }
-}
diff --git a/src/Animation/ParallelAnimations.php b/src/Animation/ParallelAnimations.php
deleted file mode 100644
index c6842e0..0000000
--- a/src/Animation/ParallelAnimations.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-/**
- * Parallel animations, played at the same time.
- */
-class ParallelAnimations extends BaseAnimationContainer
-{
-}
diff --git a/src/Animation/TransformOrientationAnimation.php b/src/Animation/TransformOrientationAnimation.php
deleted file mode 100644
index 22eec91..0000000
--- a/src/Animation/TransformOrientationAnimation.php
+++ /dev/null
@@ -1,70 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-use GL\Math\Quat;
-
-/**
- * Animation that rotates an object with a given orientation
- */
-class TransformOrientationAnimation extends BaseAnimation
-{
-    public ?Quat $initialOrientation;
-    public ?Quat $targetOrientation;
-
-    /**
-     * @param Quat $modifier The orientation to apply
-     * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType|null $easingType The easing type
-     * @param int|null $initialDelay The initial delay in milliseconds
-     * @param bool $repeat Whether the animation should repeat
-     * @param int|null $repeatCount The number of times the animation should repeat
-     * @param int|null $repeatDelay The delay between each repeat in milliseconds
-     * @param bool $reverse Whether the animation should reverse
-     * @param int|null $reverseCount The number of times the animation should reverse
-     * @param int|null $reverseDelay The delay between each reverse in milliseconds
-     * @param int|null $tag The tag of the animation
-     */
-    public function __construct(
-        public Quat          $modifier,
-        int                  $duration,
-        ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        ?int                 $initialDelay = 0,
-        ?bool                $repeat = false,
-        ?int                 $repeatCount = 0,
-        ?int                 $repeatDelay = 0,
-        ?bool                $reverse = false,
-        ?int                 $reverseCount = 0,
-        ?int                 $reverseDelay = 0,
-        ?int                 $tag = null
-    )
-    {
-        parent::__construct(
-            $duration,
-            $easingType,
-            $initialDelay,
-            $repeat,
-            $repeatCount,
-            $repeatDelay,
-            $reverse,
-            $reverseCount,
-            $reverseDelay,
-            $tag
-        );
-    }
-
-    /**
-     * Creates a new animation from the current and target orientation
-     *
-     * @param Quat $currentOrientation
-     * @param Quat $targetOrientation
-     * @param int $duration
-     * @return static
-     */
-    public static function fromCurrentAndTargetOrientation(Quat $currentOrientation, Quat $targetOrientation, int $duration): self
-    {
-        $inverseQuat = $currentOrientation->copy();
-        $inverseQuat->inverse();
-        return new self($inverseQuat * $targetOrientation, $duration);
-    }
-}
diff --git a/src/Animation/TransformPositionAnimation.php b/src/Animation/TransformPositionAnimation.php
deleted file mode 100644
index 6f01f6c..0000000
--- a/src/Animation/TransformPositionAnimation.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-use GL\Math\Vec3;
-
-/**
- * Animation that translates an object with a given position modifier
- */
-class TransformPositionAnimation extends BaseAnimation
-{
-    public ?Vec3 $initialPosition;
-    public ?Vec3 $targetPosition;
-
-    /**
-     * @param Vec3 $modifier The position modifier to apply
-     * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType|null $easingType The easing type
-     * @param int|null $initialDelay The initial delay in milliseconds
-     * @param bool $repeat Whether the animation should repeat
-     * @param int|null $repeatCount The number of times the animation should repeat
-     * @param int|null $repeatDelay The delay between each repeat in milliseconds
-     * @param bool $reverse Whether the animation should reverse
-     * @param int|null $reverseCount The number of times the animation should reverse
-     * @param int|null $reverseDelay The delay between each reverse in milliseconds
-     * @param int|null $tag The tag of the animation
-     */
-    public function __construct(
-        public Vec3          $modifier,
-        int                  $duration,
-        ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        ?int                 $initialDelay = 0,
-        ?bool                $repeat = false,
-        ?int                 $repeatCount = 0,
-        ?int                 $repeatDelay = 0,
-        ?bool                $reverse = false,
-        ?int                 $reverseCount = 0,
-        ?int                 $reverseDelay = 0,
-        ?int                 $tag = null
-    )
-    {
-        parent::__construct(
-            $duration,
-            $easingType,
-            $initialDelay,
-            $repeat,
-            $repeatCount,
-            $repeatDelay,
-            $reverse,
-            $reverseCount,
-            $reverseDelay,
-            $tag
-        );
-    }
-
-    /**
-     * Creates a new animation from the current and target position
-     *
-     * @param Vec3 $currentPosition
-     * @param Vec3 $targetPosition
-     * @param int $duration
-     * @return static
-     */
-    public static function fromCurrentAndTargetPosition(Vec3 $currentPosition, Vec3 $targetPosition, int $duration): self
-    {
-        return new self($targetPosition - $currentPosition, $duration);
-    }
-}
diff --git a/src/Animation/TransformScaleAnimation.php b/src/Animation/TransformScaleAnimation.php
deleted file mode 100644
index 5188340..0000000
--- a/src/Animation/TransformScaleAnimation.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace TowerDefense\Animation;
-
-use GL\Math\Vec3;
-
-/**
- * Animation that scales an object with a given scale modifier
- */
-class TransformScaleAnimation extends BaseAnimation
-{
-    public ?Vec3 $initialScale;
-    public ?Vec3 $targetScale;
-
-    /**
-     * @param Vec3 $modifier The scale modifier to apply
-     * @param int $duration The duration of the animation in milliseconds
-     * @param AnimationEasingType|null $easingType The easing type
-     * @param int|null $initialDelay The initial delay in milliseconds
-     * @param bool $repeat Whether the animation should repeat
-     * @param int|null $repeatCount The number of times the animation should repeat
-     * @param int|null $repeatDelay The delay between each repeat in milliseconds
-     * @param bool $reverse Whether the animation should reverse
-     * @param int|null $reverseCount The number of times the animation should reverse
-     * @param int|null $reverseDelay The delay between each reverse in milliseconds
-     * @param int|null $tag The tag of the animation
-     */
-    public function __construct(
-        public Vec3          $modifier,
-        int                  $duration,
-        ?AnimationEasingType $easingType = AnimationEasingType::LINEAR,
-        ?int                 $initialDelay = 0,
-        ?bool                $repeat = false,
-        ?int                 $repeatCount = 0,
-        ?int                 $repeatDelay = 0,
-        ?bool                $reverse = false,
-        ?int                 $reverseCount = 0,
-        ?int                 $reverseDelay = 0,
-        ?int                 $tag = null
-    )
-    {
-        parent::__construct(
-            $duration,
-            $easingType,
-            $initialDelay,
-            $repeat,
-            $repeatCount,
-            $repeatDelay,
-            $reverse,
-            $reverseCount,
-            $reverseDelay,
-            $tag
-        );
-    }
-
-    /**
-     * Creates a new animation from the current and target scale
-     *
-     * @param Vec3 $currentScale
-     * @param Vec3 $targetScale
-     * @param int $duration
-     * @return static
-     */
-    public static function fromCurrentAndTargetScale(Vec3 $currentScale, Vec3 $targetScale, int $duration): self
-    {
-        return new self($targetScale / $currentScale, $duration);
-    }
-}
diff --git a/src/Component/AnimationComponent.php b/src/Component/AnimationComponent.php
deleted file mode 100644
index 8d9ab73..0000000
--- a/src/Component/AnimationComponent.php
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-
-namespace TowerDefense\Component;
-
-class AnimationComponent
-{
-    public object $animation;
-}
diff --git a/src/Geometry/Vec3Util.php b/src/Geometry/Vec3Util.php
deleted file mode 100644
index 5f9e38a..0000000
--- a/src/Geometry/Vec3Util.php
+++ /dev/null
@@ -1,91 +0,0 @@
-<?php
-
-namespace TowerDefense\Geometry;
-
-use GL\Math\GLM;
-use GL\Math\Quat;
-use GL\Math\Vec3;
-
-/**
- * Collection of utility functions for Vec3
- */
-class Vec3Util
-{
-    /**
-     * Calculate the rotation between two vectors
-     *
-     * @param Vec3 $start
-     * @param Vec3 $dest
-     * @return Quat
-     */
-    public static function rotationBetweenVectors(Vec3 $start, Vec3 $dest): Quat
-    {
-        $start = $start->copy();
-        $dest = $dest->copy();
-
-        $start->normalize();
-        $dest->normalize();
-
-        $cosTheta = Vec3::dot($start, $dest);
-
-        if ($cosTheta < -1 + 0.001) {
-            // special case when vectors in opposite directions:
-            // there is no "ideal" rotation axis
-            // So guess one; any will do as long as it's perpendicular to start
-            $rotationAxis = Vec3::cross(new Vec3(0.0, 0.0, 1.0), $start);
-            if ($rotationAxis->length() < 0.01) {
-                // bad luck, they were parallel, try again!
-                $rotationAxis = Vec3::cross(new Vec3(1.0, 0.0, 0.0), $start);
-            }
-
-            $rotationAxis->normalize();
-            $quat = new Quat();
-            $quat->rotate(GLM::radians(180.0), $rotationAxis);
-            return $quat;
-        }
-
-        $rotationAxis = Vec3::cross($start, $dest);
-
-        $s = sqrt((1 + $cosTheta) * 2);
-        $invs = 1 / $s;
-
-        return new Quat(
-            $s * 0.5,
-            $rotationAxis->x * $invs,
-            $rotationAxis->y * $invs,
-            $rotationAxis->z * $invs
-        );
-    }
-
-    /**
-     * Return a quaternion that will rotate the object to face forward to the target position
-     * The object is assumed to be facing forward along the forward vector
-     * The object is assumed to be facing up along the up vector
-     *
-     * @param Vec3 $position
-     * @param Vec3 $targetPosition
-     * @param Vec3 $forward
-     * @param Vec3 $up
-     * @return Quat
-     */
-    public static function quatFacingForwardTowardsTarget(Vec3 $position, Vec3 $targetPosition, Vec3 $forward, Vec3 $up): Quat
-    {
-        // Find the rotation between the front of the object (that we assume towards +Z,
-        // but this depends on your model) and the desired direction
-        $direction = $targetPosition - $position;
-        $rot1 = self::rotationBetweenVectors($forward, $direction);
-
-        // Recompute desiredUp so that it's perpendicular to the direction
-        // You can skip that part if you really want to force desiredUp
-        $right = Vec3::cross($direction, $up);
-        $desiredUp = Vec3::cross($right, $direction);
-
-        // Because of the 1rst rotation, the up is probably completely screwed up.
-        // Find the rotation between the "up" of the rotated object, and the desired up
-        $newUp = Quat::multiplyVec3($rot1, new Vec3(0.0, 1.0, 0.0));
-        $rot2 = self::rotationBetweenVectors($newUp, $desiredUp);
-        $targetOrientation = $rot2 * $rot1; // remember, in reverse order.
-
-        return $targetOrientation;
-    }
-}
diff --git a/src/Scene/LevelScene.php b/src/Scene/LevelScene.php
index cbff1b0..ac10065 100644
--- a/src/Scene/LevelScene.php
+++ b/src/Scene/LevelScene.php
@@ -10,7 +10,6 @@
 use TowerDefense\Renderer\RoadRenderer;
 use TowerDefense\Renderer\TerrainRenderer;
 use TowerDefense\System\AircraftSystem;
-use TowerDefense\System\AnimationSystem;
 use TowerDefense\System\CameraSystem;
 use TowerDefense\System\HeightmapSystem;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
@@ -28,6 +27,7 @@
 use VISU\System\VISULowPoly\LPModel;
 use VISU\System\VISULowPoly\LPObjLoader;
 use VISU\System\VISULowPoly\LPRenderingSystem as VISULowPolyRenderingSystem;
+use VISU\System\VISUTransitionAnimationSystem;
 
 abstract class LevelScene extends BaseScene implements DevEntityPickerDelegate
 {
@@ -50,7 +50,7 @@ abstract class LevelScene extends BaseScene implements DevEntityPickerDelegate
     private CameraSystem $cameraSystem;
     private HeightmapSystem $heightmapSystem;
     private AircraftSystem $aircraftSystem;
-    private AnimationSystem $animationSystem;
+    private VISUTransitionAnimationSystem $animationSystem;
 
     /**
      * Object loader (3D models)
@@ -115,7 +115,7 @@ public function __construct(
             ]
         );
 
-        $this->animationSystem = new AnimationSystem(60); // @TODO get from game loop
+        $this->animationSystem = new VISUTransitionAnimationSystem(60); // @TODO get from game loop
         $this->aircraftSystem = new AircraftSystem($this->loadedObjects, $this->entities, $this->container->resolveVisuDispatcher(), $this->container->resolveInput());
         $this->animationSystem->addAnimationDelegate($this->aircraftSystem);
 
diff --git a/src/System/AircraftSystem.php b/src/System/AircraftSystem.php
index e89c6e1..b0cd827 100644
--- a/src/System/AircraftSystem.php
+++ b/src/System/AircraftSystem.php
@@ -5,28 +5,28 @@
 use GL\Math\GLM;
 use GL\Math\Quat;
 use GL\Math\Vec3;
-use TowerDefense\Animation\AnimationEasingType;
-use TowerDefense\Animation\AnimationSequence;
-use TowerDefense\Animation\AnimationSystemDelegate;
-use TowerDefense\Animation\AnimationUtil;
-use TowerDefense\Animation\BaseAnimation;
-use TowerDefense\Animation\BaseAnimationContainer;
-use TowerDefense\Animation\TransformOrientationAnimation;
-use TowerDefense\Animation\TransformPositionAnimation;
-use TowerDefense\Animation\TransformScaleAnimation;
-use TowerDefense\Component\AnimationComponent;
 use TowerDefense\Component\HeightmapComponent;
-use TowerDefense\Geometry\Vec3Util;
+use VISU\Animation\Transition\AnimationEasingType;
+use VISU\Animation\Transition\AnimationSequence;
+use VISU\Animation\Transition\AnimationUtil;
+use VISU\Animation\Transition\BaseAnimation;
+use VISU\Animation\Transition\BaseAnimationContainer;
+use VISU\Animation\Transition\TransformOrientationAnimation;
+use VISU\Animation\Transition\TransformPositionAnimation;
+use VISU\Animation\Transition\TransformScaleAnimation;
+use VISU\Component\AnimationComponent;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\ECS\EntitiesInterface;
 use VISU\ECS\SystemInterface;
+use VISU\Geo\Math;
 use VISU\Geo\Transform;
 use VISU\Graphics\Rendering\RenderContext;
 use VISU\OS\Input;
 use VISU\Signal\Dispatcher;
 use VISU\Signals\Input\MouseClickSignal;
+use VISU\System\VISUTransitionAnimationSystemDelegate;
 
-class AircraftSystem implements SystemInterface, AnimationSystemDelegate
+class AircraftSystem implements SystemInterface, VISUTransitionAnimationSystemDelegate
 {
     /**
      * @var int[] $aircrafts The list of aircraft entity ids
@@ -125,7 +125,7 @@ private function clickAt(Vec3 $position): void
         // set the new rotated orientation
         $height = $position->y;
         $position->y = $transform->position->y; // lock height to current height
-        $targetOrientation = Vec3Util::quatFacingForwardTowardsTarget($transform->position, $position, Transform::worldBackward(), Transform::worldUp());
+        $targetOrientation = Math::quatFacingForwardTowardsTarget($transform->position, $position, Transform::worldBackward(), Transform::worldUp());
 
         // set the target height of the terrain + 10 "meters"
         $position->y = $height + 10.0;
diff --git a/src/System/AnimationSystem.php b/src/System/AnimationSystem.php
deleted file mode 100644
index d7d48c9..0000000
--- a/src/System/AnimationSystem.php
+++ /dev/null
@@ -1,317 +0,0 @@
-<?php
-
-namespace TowerDefense\System;
-
-use GL\Math\Quat;
-use GL\Math\Vec3;
-use TowerDefense\Animation\AnimationEasingType;
-use TowerDefense\Animation\AnimationSequence;
-use TowerDefense\Animation\AnimationSystemDelegate;
-use TowerDefense\Animation\BaseAnimation;
-use TowerDefense\Animation\BaseAnimationContainer;
-use TowerDefense\Animation\ParallelAnimations;
-use TowerDefense\Animation\TransformOrientationAnimation;
-use TowerDefense\Animation\TransformPositionAnimation;
-use TowerDefense\Animation\TransformScaleAnimation;
-use TowerDefense\Component\AnimationComponent;
-use VISU\ECS\EntitiesInterface;
-use VISU\ECS\SystemInterface;
-use VISU\Geo\Transform;
-use VISU\Graphics\Rendering\RenderContext;
-
-class AnimationSystem implements SystemInterface
-{
-    /**
-     * @var AnimationSystemDelegate[] $animationDelegates The list of animation delegates
-     */
-    private $animationDelegates = [];
-
-    public function __construct(private readonly int $ticksPerSecond)
-    {
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function register(EntitiesInterface $entities): void
-    {
-        // register components
-        $entities->registerComponent(AnimationComponent::class);
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function unregister(EntitiesInterface $entities): void
-    {
-        // TODO: Implement unregister() method.
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function update(EntitiesInterface $entities): void
-    {
-        // get alle animation components and their entities
-        foreach ($entities->view(AnimationComponent::class) as $entity => $animationComponent) {
-            /** @var AnimationComponent $animationComponent */
-            // only if the animation container is not finished
-            if (!$animationComponent->animation->finished) {
-                // get the transform of the entity
-                $transform = $entities->get($entity, Transform::class);
-                $this->handleAnimationContainer($animationComponent->animation, $transform, $entity);
-            }
-        }
-    }
-
-    /**
-     * @inheritDoc
-     */
-    public function render(EntitiesInterface $entities, RenderContext $context): void
-    {
-        // TODO: Implement render() method.
-    }
-
-    /**
-     * Adds an animation delegate
-     *
-     * @param AnimationSystemDelegate $delegate
-     * @return void
-     */
-    public function addAnimationDelegate(AnimationSystemDelegate $delegate): void
-    {
-        $this->animationDelegates[] = $delegate;
-    }
-
-    /**
-     * Adds an animation delegate
-     *
-     * @param AnimationSystemDelegate $delegate
-     * @return void
-     */
-    public function removeAnimationDelegate(AnimationSystemDelegate $delegate): void
-    {
-        if (($key = array_search($delegate, $this->animationDelegates, true)) !== false) {
-            unset($this->animationDelegates[$key]);
-        }
-    }
-
-    /**
-     * Handles an animation container
-     *
-     * @param object $animationContainer The animation container
-     * @param Transform $transform The transform of the entity
-     * @param int $entity The entity
-     * @return void
-     */
-    private function handleAnimationContainer(object $animationContainer, Transform $transform, int $entity): void
-    {
-        // check the AnimationContainerType of $animationContainer
-        if ($animationContainer instanceof BaseAnimation) {
-            // handle the animation
-            if (!$animationContainer->running) {
-                // set the required ticks based on the duration
-                $animationContainer->requiredTicks = ceil(($animationContainer->duration / 1000.0) * $this->ticksPerSecond);
-                $animationContainer->currentTick = 0;
-
-                // check if the animation should wait
-                if ($animationContainer->runCount == 0 && $animationContainer->initialDelay > 0 && !$animationContainer->reversing) {
-                    // set the required delay ticks based on the initial delay
-                    $animationContainer->requiredDelayTicks = ceil(($animationContainer->initialDelay / 1000.0) * $this->ticksPerSecond);
-                    $animationContainer->currentDelayTick = 0;
-                    $animationContainer->waiting = true;
-                    $animationContainer->running = true;
-                } else if ($animationContainer->reverseDelay > 0 && $animationContainer->reversing) {
-                    // set the required delay ticks based on the reverse delay
-                    $animationContainer->requiredDelayTicks = ceil(($animationContainer->reverseDelay / 1000.0) * $this->ticksPerSecond);
-                    $animationContainer->currentDelayTick = 0;
-                    $animationContainer->waiting = true;
-                    $animationContainer->running = true;
-                } else if (!$animationContainer->reversing && $animationContainer->runCount > 0 && $animationContainer->repeatDelay > 0) {
-                    // set the required delay ticks based on the repeat delay
-                    $animationContainer->requiredDelayTicks = ceil(($animationContainer->repeatDelay / 1000.0) * $this->ticksPerSecond);
-                    $animationContainer->currentDelayTick = 0;
-                    $animationContainer->waiting = true;
-                    $animationContainer->running = true;
-                }
-                // call the animation delegates
-                foreach ($this->animationDelegates as $animationDelegate) {
-                    // the animation did start
-                    $animationDelegate->animationDidStart($animationContainer, $entity, $animationContainer->reversing, (!$animationContainer->reversing && $animationContainer->runCount > 0), $animationContainer->waiting);
-                }
-            }
-
-            // check if the animation is waiting
-            if ($animationContainer->waiting) {
-                // check if the delay is over
-                if ($animationContainer->currentDelayTick >= $animationContainer->requiredDelayTicks) {
-                    $animationContainer->waiting = false;
-                    $animationContainer->running = false;
-                } else {
-                    // increase the delay tick
-                    $animationContainer->currentDelayTick++;
-                    return;
-                }
-            }
-
-            // increase the current tick
-            $animationContainer->currentTick++;
-
-            // calculate the progress
-            $progress = (1.0 / $animationContainer->requiredTicks) * $animationContainer->currentTick;
-
-            // apply easing curves if necessary
-            // @TODO We will add a lot more of them: https://easings.net/
-            if ($animationContainer->easingType != AnimationEasingType::LINEAR) {
-                if ($animationContainer->easingType == AnimationEasingType::EASE_OUT) {
-                    // https://easings.net/#easeOutCubic
-                    $progress = 1 - pow(1 - $progress, 3);
-                } elseif ($animationContainer->easingType == AnimationEasingType::EASE_IN_OUT) {
-                    // https://easings.net/#easeInOutCubic
-                    $progress = $progress < 0.5 ? 4 * $progress * $progress * $progress : 1 - pow(-2 * $progress + 2, 3) / 2;
-                } elseif ($animationContainer->easingType == AnimationEasingType::EASE_IN) {
-                    // https://easings.net/#easeInCubic
-                    $progress = $progress * $progress * $progress;
-                }
-            }
-
-            if ($animationContainer instanceof TransformPositionAnimation) {
-                if (!$animationContainer->running) {
-                    if (!$animationContainer->reversing) {
-                        // set the initial and target position
-                        $animationContainer->initialPosition = $transform->position->copy();
-                        $animationContainer->targetPosition = $animationContainer->initialPosition + $animationContainer->modifier;
-                    }
-                    $animationContainer->running = true;
-                }
-                // calculate the new position
-                $newPosition = Vec3::lerp($animationContainer->initialPosition, $animationContainer->targetPosition, $progress);
-                $transform->position = $newPosition;
-                $transform->isDirty = true;
-            } else if ($animationContainer instanceof TransformScaleAnimation) {
-                if (!$animationContainer->running) {
-                    if (!$animationContainer->reversing) {
-                        // set the initial and target scale
-                        $animationContainer->initialScale = $transform->scale->copy();
-                        $animationContainer->targetScale = $animationContainer->initialScale * $animationContainer->modifier;
-                    }
-                    $animationContainer->running = true;
-                }
-                // calculate the new scale
-                $newScale = Vec3::lerp($animationContainer->initialScale, $animationContainer->targetScale, $progress);
-                $transform->scale = $newScale;
-                $transform->isDirty = true;
-            } else if ($animationContainer instanceof TransformOrientationAnimation) {
-                if (!$animationContainer->running) {
-                    if (!$animationContainer->reversing) {
-                        // set the initial and target orientation
-                        $animationContainer->initialOrientation = $transform->orientation->copy();
-                        $animationContainer->targetOrientation = $animationContainer->initialOrientation * $animationContainer->modifier;
-                    }
-                    $animationContainer->running = true;
-                }
-                // calculate the new orientation
-                $newOrientation = Quat::slerp($animationContainer->initialOrientation, $animationContainer->targetOrientation, $progress);
-                $transform->orientation = $newOrientation;
-                $transform->isDirty = true;
-            }
-            // check if the animation is finished
-            if ($animationContainer->currentTick >= $animationContainer->requiredTicks) {
-                $reverseBlockedByRepeat = false;
-                if ($animationContainer->reversing) {
-                    $animationContainer->reversing = false;
-                    $animationContainer->reversedCount++;
-                    // check if the reverse is blocked by repeat
-                    if ($animationContainer->repeat && ($animationContainer->repeatCount == 0 || $animationContainer->repeatCount > $animationContainer->repeatedCount)) {
-                        $reverseBlockedByRepeat = true;
-                    }
-                } else if ($animationContainer->runCount > 0) {
-                    $animationContainer->repeatedCount++;
-                }
-                $animationContainer->finished = true;
-                $animationContainer->running = false;
-                $animationContainer->runCount++;
-
-                // check if we need to reverse the animation
-                if (!$reverseBlockedByRepeat && $animationContainer->reverse && ($animationContainer->reverseCount == 0 || $animationContainer->reverseCount > $animationContainer->reversedCount)) {
-                    $animationContainer->reversing = true;
-                    $animationContainer->finished = false;
-                    if ($animationContainer instanceof TransformPositionAnimation) {
-                        // swap the initial and target position
-                        $animationContainer->targetPosition = $animationContainer->initialPosition->copy();
-                        $animationContainer->initialPosition = $transform->position->copy();
-                    } else if ($animationContainer instanceof TransformScaleAnimation) {
-                        // swap the initial and target scale
-                        $animationContainer->targetScale = $animationContainer->initialScale->copy();
-                        $animationContainer->initialScale = $transform->scale->copy();
-                    } else if ($animationContainer instanceof TransformOrientationAnimation) {
-                        // swap the initial and target orientation
-                        $animationContainer->targetOrientation = $animationContainer->initialOrientation->copy();
-                        $animationContainer->initialOrientation = $transform->orientation->copy();
-                    }
-                } else {
-                    // check if we need to repeat the animation
-                    if ($animationContainer->repeat && ($animationContainer->repeatCount == 0 || $animationContainer->repeatCount > $animationContainer->repeatedCount)) {
-                        $animationContainer->finished = false;
-                    }
-                }
-
-                // call the animation delegates
-                foreach ($this->animationDelegates as $delegate) {
-                    // the animation did stop
-                    $delegate->animationDidStop($animationContainer, $entity, $animationContainer->finished);
-                }
-            }
-        } else {
-            $finishedAnimations = 0;
-            if ($animationContainer instanceof AnimationSequence) {
-                // get the current animation in line
-                foreach ($animationContainer->animations as $animation) {
-                    if (!$animation->finished) {
-                        if (!$animationContainer->running) {
-                            $animationContainer->running = true;
-                            // call the animation delegates
-                            foreach ($this->animationDelegates as $delegate) {
-                                // the animation did start
-                                $delegate->animationGroupDidBegin($animationContainer, $entity);
-                            }
-                        }
-                        // handle the animation
-                        $this->handleAnimationContainer($animation, $transform, $entity);
-                        break;
-                    } else {
-                        $finishedAnimations++;
-                    }
-                }
-            } elseif ($animationContainer instanceof ParallelAnimations) {
-                // handle all animations "in parallel"
-                foreach ($animationContainer->animations as $animation) {
-                    if (!$animation->finished) {
-                        if (!$animationContainer->running) {
-                            $animationContainer->running = true;
-                            // call the animation delegates
-                            foreach ($this->animationDelegates as $delegate) {
-                                // the animation group did start
-                                $delegate->animationGroupDidBegin($animationContainer, $entity);
-                            }
-                        }
-                        // handle the animation
-                        $this->handleAnimationContainer($animation, $transform, $entity);
-                    } else {
-                        $finishedAnimations++;
-                    }
-                }
-            }
-            // check if all animations in the container are finished and set the finished flag
-            if ($finishedAnimations == count($animationContainer->animations)) {
-                /* @var $animationContainer BaseAnimationContainer */
-                $animationContainer->finished = true;
-                // call the animation delegates
-                foreach ($this->animationDelegates as $delegate) {
-                    // the animation group did start
-                    $delegate->animationGroupDidFinish($animationContainer, $entity);
-                }
-            }
-        }
-    }
-}

From 719d147771bd86455d60df3bc023cad00da32b78 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Fri, 10 Feb 2023 20:11:35 +0100
Subject: [PATCH 43/49] playing around with debug server / client

---
 bin/test-debug-client.php      | 22 ++++++++++
 src/Debug/DebugServer.php      | 80 ++++++++++++++++++++++++++++++++++
 src/Scene/LevelEditorScene.php | 16 +++++++
 3 files changed, 118 insertions(+)
 create mode 100644 bin/test-debug-client.php
 create mode 100644 src/Debug/DebugServer.php

diff --git a/bin/test-debug-client.php b/bin/test-debug-client.php
new file mode 100644
index 0000000..f3a6f90
--- /dev/null
+++ b/bin/test-debug-client.php
@@ -0,0 +1,22 @@
+<?php
+
+if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
+    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
+}
+
+if (socket_connect($sock, '127.0.0.1', 8145) === false) {
+    echo "socket_connect() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
+}
+
+do {
+
+    $stdin = fopen('php://stdin', 'r');
+    $msg = trim(fgets($stdin));
+    socket_write($sock, $msg, strlen($msg));
+
+    if (false === ($buf = socket_read($sock, 2048, PHP_NORMAL_READ))) {
+        echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
+    }
+
+    echo "response: $buf \n";
+} while (true);
diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
new file mode 100644
index 0000000..95bd73b
--- /dev/null
+++ b/src/Debug/DebugServer.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace TowerDefense\Debug;
+
+class DebugServer
+{
+    private string $address = '127.0.0.1';
+    private int $port = 8145;
+    private object $socket;
+    private array $clients = [];
+
+    public function __construct()
+    {
+    }
+
+    public function start(): void
+    {
+        $this->socket = socket_create(AF_INET, SOCK_STREAM, 0);
+
+        socket_bind($this->socket, $this->address, $this->port) or die('Could not bind to address');
+
+        socket_listen($this->socket);
+
+        socket_set_nonblock($this->socket);
+    }
+
+    public function stop(): void
+    {
+        foreach ($this->clients as $i => $client) {
+            socket_close($client);
+            unset($this->clients[$i]);
+        }
+
+        socket_close($this->socket);
+    }
+
+    public function process(): void
+    {
+        if ($newsock = socket_accept($this->socket)) {
+            echo "Get new socket\n";
+            socket_set_nonblock($newsock);
+            echo "New client connected\n";
+            $this->clients[] = $newsock;
+        }
+
+        if (count($this->clients) < 1) {
+            return;
+        }
+
+        $read = $this->clients;
+
+        $changed = socket_select($read, $w, $e, 0);
+
+        if (false == $changed) {
+            return;
+        }
+
+        if ($changed < 1) {
+            return;
+        }
+
+        foreach ($read as $k => $socketItem) {
+            if ($input = socket_read($socketItem, 1024)) {
+                $input = trim($input);
+
+                echo "client $k: $input\n";
+
+                $response = "Received" . "\n";
+                socket_write($socketItem, $response, strlen($response));
+
+                if ($input == 'quit') {
+                    socket_close($socketItem);
+                    unset($this->clients[$k]);
+                }
+            } else {
+                echo "Socket read error: " . socket_strerror(socket_last_error()) . "\n";
+            }
+        }
+    }
+}
diff --git a/src/Scene/LevelEditorScene.php b/src/Scene/LevelEditorScene.php
index 1ab2106..d17b919 100644
--- a/src/Scene/LevelEditorScene.php
+++ b/src/Scene/LevelEditorScene.php
@@ -5,6 +5,7 @@
 use GameContainer;
 use GL\Math\Vec3;
 use TowerDefense\Component\HeightmapComponent;
+use TowerDefense\Debug\DebugServer;
 use TowerDefense\Debug\DebugTextOverlay;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\D3D;
@@ -22,6 +23,8 @@ class LevelEditorScene extends LevelScene
      */
     private int $selectedEntity = 0;
 
+    private ?DebugServer $debugServer = null;
+
     /**
      * Returns the scenes name
      */
@@ -44,6 +47,16 @@ public function __construct(
 
         // switch to the editor input context
         $this->container->resolveInputContext()->switchTo('level_editor');
+
+        // start debug server
+        $this->debugServer = new DebugServer();
+        $this->debugServer->start();
+    }
+
+    public function __destruct()
+    {
+        // stop debug server
+        $this->debugServer->stop();
     }
 
     /**
@@ -66,6 +79,9 @@ public function update() : void
     {
         parent::update();
 
+        // process debug server
+        $this->debugServer->process();
+
         $heightmapComponent = $this->entities->getSingleton(HeightmapComponent::class);
 
         // get input context

From c5011d14f10e7a4cd49f8a8ae54a4484830ea7eb Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sun, 12 Feb 2023 11:58:08 +0100
Subject: [PATCH 44/49] some refactoring (debugserver as service with config
 from config file)

---
 app/config.ctn                 |  6 +++-
 app/core.ctn                   | 10 +++++-
 src/Debug/DebugServer.php      | 57 +++++++++++++++++++++++++++++-----
 src/Scene/LevelEditorScene.php | 54 ++++++++++++++++----------------
 4 files changed, 90 insertions(+), 37 deletions(-)

diff --git a/app/config.ctn b/app/config.ctn
index 8fca60d..555002e 100644
--- a/app/config.ctn
+++ b/app/config.ctn
@@ -8,4 +8,8 @@
 
 // defaults
 :window.default_widht: 1280
-:window.default_height: 720
\ No newline at end of file
+:window.default_height: 720
+
+// debug server
+:debug.server_ip: '127.0.0.1'
+:debug.server_port: 8145
\ No newline at end of file
diff --git a/app/core.ctn b/app/core.ctn
index 4df1821..24f1f86 100644
--- a/app/core.ctn
+++ b/app/core.ctn
@@ -30,4 +30,12 @@
 @shaders: VISU\Graphics\ShaderCollection(@GL, :visu.path.resources.shader)
     - enableVISUIncludes()
     - addVISUShaders()
-    - scanShaderDirectory(:visu.path.resources.shader)
\ No newline at end of file
+    - scanShaderDirectory(:visu.path.resources.shader)
+
+/**
+ * Debug server
+ */
+@debug.server: TowerDefense\Debug\DebugServer(
+    :debug.server_ip, 
+    :debug.server_port
+)
\ No newline at end of file
diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
index 95bd73b..00160b1 100644
--- a/src/Debug/DebugServer.php
+++ b/src/Debug/DebugServer.php
@@ -2,40 +2,77 @@
 
 namespace TowerDefense\Debug;
 
+use VISU\ECS\EntitiesInterface;
+
+/**
+ * Class DebugServer
+ * 
+ * @package TowerDefense\Debug
+ */
 class DebugServer
 {
-    private string $address = '127.0.0.1';
-    private int $port = 8145;
-    private object $socket;
-    private array $clients = [];
-
-    public function __construct()
+    private string $address = '127.0.0.1'; // localhost
+    private int $port = 8145; // default port
+    private object $socket; // the server socket
+    private array $clients = []; // the client sockets
+
+    /**
+     * DebugServer constructor.
+     * 
+     * @param string $address 
+     * @param int $port 
+     * @return void 
+     */
+    public function __construct(string $address, int $port)
     {
+        $this->address = $address;
+        $this->port = $port;
     }
 
+    /**
+     * Start the debug server
+     * @return void 
+     */
     public function start(): void
     {
+        // create the server socket
         $this->socket = socket_create(AF_INET, SOCK_STREAM, 0);
 
+        // bind the socket to the address and port
         socket_bind($this->socket, $this->address, $this->port) or die('Could not bind to address');
 
+        // start listening for connections
         socket_listen($this->socket);
 
+        // set the socket to non-blocking
         socket_set_nonblock($this->socket);
     }
 
+    /**
+     * Stop the debug server
+     * 
+     * @return void 
+     */
     public function stop(): void
     {
+        // close all clients
         foreach ($this->clients as $i => $client) {
             socket_close($client);
             unset($this->clients[$i]);
         }
 
+        // close the server socket
         socket_close($this->socket);
     }
 
-    public function process(): void
+    /**
+     * Process the debug server input and output
+     * 
+     * @return void 
+     */
+    public function process(EntitiesInterface $entities): void
     {
+        // check for new connections
         if ($newsock = socket_accept($this->socket)) {
             echo "Get new socket\n";
             socket_set_nonblock($newsock);
@@ -43,12 +80,14 @@ public function process(): void
             $this->clients[] = $newsock;
         }
 
+        // check for existing clients
         if (count($this->clients) < 1) {
             return;
         }
 
         $read = $this->clients;
 
+        // check for input
         $changed = socket_select($read, $w, $e, 0);
 
         if (false == $changed) {
@@ -59,6 +98,7 @@ public function process(): void
             return;
         }
 
+        // process input
         foreach ($read as $k => $socketItem) {
             if ($input = socket_read($socketItem, 1024)) {
                 $input = trim($input);
@@ -73,7 +113,10 @@ public function process(): void
                     unset($this->clients[$k]);
                 }
             } else {
+                // most probably the socket was closed from the client side
                 echo "Socket read error: " . socket_strerror(socket_last_error()) . "\n";
+                socket_close($socketItem);
+                unset($this->clients[$k]);
             }
         }
     }
diff --git a/src/Scene/LevelEditorScene.php b/src/Scene/LevelEditorScene.php
index d17b919..68b770e 100644
--- a/src/Scene/LevelEditorScene.php
+++ b/src/Scene/LevelEditorScene.php
@@ -5,16 +5,12 @@
 use GameContainer;
 use GL\Math\Vec3;
 use TowerDefense\Component\HeightmapComponent;
-use TowerDefense\Debug\DebugServer;
 use TowerDefense\Debug\DebugTextOverlay;
 use VISU\Component\VISULowPoly\DynamicRenderableModel;
 use VISU\D3D;
 use VISU\Geo\Transform;
 use VISU\Graphics\Rendering\RenderContext;
 use VISU\OS\Key;
-use VISU\OS\MouseButton;
-use VISU\Signals\Input\KeySignal;
-use VISU\Signals\Input\MouseClickSignal;
 
 class LevelEditorScene extends LevelScene
 {
@@ -23,12 +19,10 @@ class LevelEditorScene extends LevelScene
      */
     private int $selectedEntity = 0;
 
-    private ?DebugServer $debugServer = null;
-
     /**
      * Returns the scenes name
      */
-    public function getName() : string
+    public function getName(): string
     {
         return 'TowerDefense Level [EDITOR]';
     }
@@ -38,8 +32,7 @@ public function getName() : string
      */
     public function __construct(
         protected GameContainer $container,
-    )
-    {
+    ) {
         parent::__construct($container);
 
         // enable the dev picker
@@ -49,14 +42,19 @@ public function __construct(
         $this->container->resolveInputContext()->switchTo('level_editor');
 
         // start debug server
-        $this->debugServer = new DebugServer();
-        $this->debugServer->start();
+        if ($this->container->get('debug.server')) {
+            $debugServer = $this->container->get('debug.server');
+            $debugServer->start();
+        }
     }
 
     public function __destruct()
     {
         // stop debug server
-        $this->debugServer->stop();
+        if ($this->container->get('debug.server')) {
+            $debugServer = $this->container->get('debug.server');
+            $debugServer->stop();
+        }
     }
 
     /**
@@ -65,7 +63,7 @@ public function __destruct()
      * 
      * @return void 
      */
-    public function load() : void
+    public function load(): void
     {
         parent::load();
     }
@@ -75,25 +73,27 @@ public function load() : void
      * This is the place where you should update your game state.
      * @return void 
      */
-    public function update() : void
+    public function update(): void
     {
         parent::update();
 
         // process debug server
-        $this->debugServer->process();
+        if ($this->container->get('debug.server')) {
+            $debugServer = $this->container->get('debug.server');
+            $debugServer->process($this->entities);
+        }
 
         $heightmapComponent = $this->entities->getSingleton(HeightmapComponent::class);
 
         // get input context
         $inputContext = $this->container->resolveInputContext();
-        
+
         // always enable raycasting
         $heightmapComponent->enableRaycasting = true;
 
         // if an entity is selected and has a transform component
         // we update the position until the user clicks the mouse button again
-        if ($this->entities->valid($this->selectedEntity) && $this->entities->has($this->selectedEntity, Transform::class)) 
-        {
+        if ($this->entities->valid($this->selectedEntity) && $this->entities->has($this->selectedEntity, Transform::class)) {
             $transform = $this->entities->get($this->selectedEntity, Transform::class);
 
             // update the position to heightmap cursor position
@@ -116,7 +116,7 @@ public function update() : void
             // copy
             if ($inputContext->actions->didButtonRelease('copy')) {
                 $copy = $this->entities->create();
-                foreach($this->entities->components($this->selectedEntity) as $component) {
+                foreach ($this->entities->components($this->selectedEntity) as $component) {
                     $this->entities->attach($copy, clone $component);
                 }
                 $this->selectedEntity = $copy;
@@ -132,8 +132,7 @@ public function update() : void
             if ($inputContext->actions->didButtonRelease('place')) {
                 $this->deselectEntity();
             }
-        }
-        else {
+        } else {
             $this->deselectEntity();
         }
     }
@@ -144,7 +143,7 @@ public function update() : void
      * 
      * @param RenderContext $context
      */
-    public function render(RenderContext $context) : void
+    public function render(RenderContext $context): void
     {
         parent::render($context);
 
@@ -184,18 +183,17 @@ public function render(RenderContext $context) : void
 
         // hightlight the selected entity
         // by rendering an AABB around it
-        if ($this->entities->valid($this->selectedEntity)) 
-        {
+        if ($this->entities->valid($this->selectedEntity)) {
             DebugTextOverlay::debugString("Entity {$this->selectedEntity} selected, press '.' & ',' to rotate, scale with 'k' & 'l', copy with 'c', delete with 'backspace'");
 
             if (
-                $this->entities->has($this->selectedEntity, DynamicRenderableModel::class) && 
+                $this->entities->has($this->selectedEntity, DynamicRenderableModel::class) &&
                 $this->entities->has($this->selectedEntity, Transform::class)
             ) {
                 $renderable = $this->entities->get($this->selectedEntity, DynamicRenderableModel::class);
                 $transform = $this->entities->get($this->selectedEntity, Transform::class);
 
-                foreach($renderable->model->meshes as $mesh) {
+                foreach ($renderable->model->meshes as $mesh) {
                     D3D::aabb($transform->position, $mesh->aabb->min * $transform->scale, $mesh->aabb->max * $transform->scale, D3D::$colorGreen);
                 }
             }
@@ -228,8 +226,8 @@ public function devEntityPickerDidSelectEntity(int $entityId): void
             $this->deselectEntity();
             return;
         }
-        
+
         echo "Entity selected: $entityId\n";
         $this->selectEntity($entityId);
     }
-}
\ No newline at end of file
+}

From 09a095ef1b8c37c886b1d1372b5c5c5107fa03a4 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sun, 12 Feb 2023 12:13:30 +0100
Subject: [PATCH 45/49] some more playing around with the debug server

---
 bin/test-debug-client.php | 11 ++++++++---
 src/Debug/DebugServer.php | 41 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 46 insertions(+), 6 deletions(-)

diff --git a/bin/test-debug-client.php b/bin/test-debug-client.php
index f3a6f90..de20fdf 100644
--- a/bin/test-debug-client.php
+++ b/bin/test-debug-client.php
@@ -8,15 +8,20 @@
     echo "socket_connect() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
 }
 
+$run = true;
 do {
 
     $stdin = fopen('php://stdin', 'r');
     $msg = trim(fgets($stdin));
     socket_write($sock, $msg, strlen($msg));
 
-    if (false === ($buf = socket_read($sock, 2048, PHP_NORMAL_READ))) {
+    if (false === ($buf = socket_read($sock, 16384))) {
         echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
     }
 
-    echo "response: $buf \n";
-} while (true);
+    echo "Response:\n$buf\n";
+
+    if ($msg == 'quit') {
+        $run = false;
+    }
+} while ($run);
diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
index 00160b1..20a0097 100644
--- a/src/Debug/DebugServer.php
+++ b/src/Debug/DebugServer.php
@@ -3,6 +3,7 @@
 namespace TowerDefense\Debug;
 
 use VISU\ECS\EntitiesInterface;
+use VISU\Geo\Transform;
 
 /**
  * Class DebugServer
@@ -74,7 +75,6 @@ public function process(EntitiesInterface $entities): void
     {
         // check for new connections
         if ($newsock = socket_accept($this->socket)) {
-            echo "Get new socket\n";
             socket_set_nonblock($newsock);
             echo "New client connected\n";
             $this->clients[] = $newsock;
@@ -100,12 +100,12 @@ public function process(EntitiesInterface $entities): void
 
         // process input
         foreach ($read as $k => $socketItem) {
-            if ($input = socket_read($socketItem, 1024)) {
+            if ($input = socket_read($socketItem, 16384)) {
                 $input = trim($input);
 
                 echo "client $k: $input\n";
 
-                $response = "Received" . "\n";
+                $response = $this->handleInput($input, $entities);
                 socket_write($socketItem, $response, strlen($response));
 
                 if ($input == 'quit') {
@@ -120,4 +120,39 @@ public function process(EntitiesInterface $entities): void
             }
         }
     }
+
+    /**
+     * Handle the input command from the client
+     * 
+     * @param string $input 
+     * @param EntitiesInterface $entities 
+     * @return string 
+     */
+    private function handleInput(string $input, EntitiesInterface $entities): string
+    {
+        $response = '';
+
+        switch ($input) {
+            case 'quit':
+                $response = 'Bye!' . "\n";
+                break;
+            case 'help':
+                $response = 'Available commands:' . "\n";
+                $response .= 'quit' . "\n";
+                $response .= 'help' . "\n";
+                $response .= 'list_transform_components' . "\n";
+                break;
+            case 'list_transform_components':
+                $response = 'Entities:' . "\n";
+                foreach ($entities->view(Transform::class) as $entity => $transform) {
+                    $response .= "Entity " . $entity . " Position: " . $transform->position->__toString() . "\n";
+                }
+                break;
+            default:
+                $response = 'Unknown command' . "\n";
+                break;
+        }
+
+        return $response;
+    }
 }

From c6600fd8538671755a258f4fda484acb234ec62c Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 18 Feb 2023 08:48:51 +0100
Subject: [PATCH 46/49] working with structured JSON messages

---
 bin/test-debug-client.php | 20 ++++++--
 src/Debug/DebugServer.php | 96 ++++++++++++++++++++++++++++-----------
 2 files changed, 86 insertions(+), 30 deletions(-)

diff --git a/bin/test-debug-client.php b/bin/test-debug-client.php
index de20fdf..d14fdd8 100644
--- a/bin/test-debug-client.php
+++ b/bin/test-debug-client.php
@@ -9,19 +9,31 @@
 }
 
 $run = true;
+$msgCounter = 0;
 do {
+    echo "Command: ";
 
     $stdin = fopen('php://stdin', 'r');
+
     $msg = trim(fgets($stdin));
-    socket_write($sock, $msg, strlen($msg));
+    $msgJSON = json_encode([
+        'command' => $msg,
+        'request_id' => $msgCounter++
+    ]);
+
+    echo "\nRequest:\n$msgJSON\n\n";
+
+    socket_write($sock, $msgJSON, strlen($msgJSON));
 
     if (false === ($buf = socket_read($sock, 16384))) {
-        echo "socket_read() failed: reason: " . socket_strerror(socket_last_error($sock)) . "\n";
+        echo "socket_read() failed, reason: " . socket_strerror(socket_last_error($sock)) . "\n";
     }
 
-    echo "Response:\n$buf\n";
+    echo "\nResponse:\n";
+    print_r(json_decode($buf, true));
+    echo "\n";
 
-    if ($msg == 'quit') {
+    if ($msg == 'disconnect') {
         $run = false;
     }
 } while ($run);
diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
index 20a0097..d29f215 100644
--- a/src/Debug/DebugServer.php
+++ b/src/Debug/DebugServer.php
@@ -12,9 +12,9 @@
  */
 class DebugServer
 {
-    private string $address = '127.0.0.1'; // localhost
+    private string $address = "127.0.0.1"; // localhost
     private int $port = 8145; // default port
-    private object $socket; // the server socket
+    private ?object $socket = null; // the server socket
     private array $clients = []; // the client sockets
 
     /**
@@ -40,7 +40,13 @@ public function start(): void
         $this->socket = socket_create(AF_INET, SOCK_STREAM, 0);
 
         // bind the socket to the address and port
-        socket_bind($this->socket, $this->address, $this->port) or die('Could not bind to address');
+        if (!socket_bind($this->socket, $this->address, $this->port)) {
+            // close the server socket
+            socket_close($this->socket);
+            $this->socket = null;
+            echo "DebugServer: Could not bind to address " . $this->address . " on port " . $this->port;
+            return;
+        }
 
         // start listening for connections
         socket_listen($this->socket);
@@ -63,7 +69,9 @@ public function stop(): void
         }
 
         // close the server socket
-        socket_close($this->socket);
+        if ($this->socket !== null) {
+            socket_close($this->socket);
+        }
     }
 
     /**
@@ -73,6 +81,10 @@ public function stop(): void
      */
     public function process(EntitiesInterface $entities): void
     {
+        if ($this->socket == null) {
+            return;
+        }
+
         // check for new connections
         if ($newsock = socket_accept($this->socket)) {
             socket_set_nonblock($newsock);
@@ -85,10 +97,10 @@ public function process(EntitiesInterface $entities): void
             return;
         }
 
-        $read = $this->clients;
+        $readingFromClients = $this->clients;
 
         // check for input
-        $changed = socket_select($read, $w, $e, 0);
+        $changed = socket_select($readingFromClients, $w, $e, 0);
 
         if (false == $changed) {
             return;
@@ -99,24 +111,25 @@ public function process(EntitiesInterface $entities): void
         }
 
         // process input
-        foreach ($read as $k => $socketItem) {
+        foreach ($readingFromClients as $i => $socketItem) {
             if ($input = socket_read($socketItem, 16384)) {
                 $input = trim($input);
 
-                echo "client $k: $input\n";
+                echo "Client $i: $input\n";
 
                 $response = $this->handleInput($input, $entities);
-                socket_write($socketItem, $response, strlen($response));
+                $responseJSON = json_encode($response);
+                socket_write($socketItem, $responseJSON, strlen($responseJSON));
 
-                if ($input == 'quit') {
+                if (isset($response['request']) && $response['request'] == "disconnect") {
                     socket_close($socketItem);
-                    unset($this->clients[$k]);
+                    unset($this->clients[$i]);
                 }
             } else {
                 // most probably the socket was closed from the client side
                 echo "Socket read error: " . socket_strerror(socket_last_error()) . "\n";
                 socket_close($socketItem);
-                unset($this->clients[$k]);
+                unset($this->clients[$i]);
             }
         }
     }
@@ -126,30 +139,61 @@ public function process(EntitiesInterface $entities): void
      * 
      * @param string $input 
      * @param EntitiesInterface $entities 
-     * @return string 
+     * @return array 
      */
-    private function handleInput(string $input, EntitiesInterface $entities): string
+    private function handleInput(string $input, EntitiesInterface $entities): array
     {
-        $response = '';
+        $inputMessageJSON = json_decode($input, true);
+        if ($inputMessageJSON == null) {
+            return [
+                'error' => "Invalid JSON on input"
+            ];
+        }
+
+        if (!isset($inputMessageJSON['command'])) {
+            return [
+                'error' => "Missing command"
+            ];
+        }
+
+        if (!isset($inputMessageJSON['request_id'])) {
+            return [
+                'error' => "Missing request id for response"
+            ];
+        }
+
+        $response = [
+            'response_id' => $inputMessageJSON['request_id'],
+            'request' => $inputMessageJSON['command']
+        ];
 
-        switch ($input) {
-            case 'quit':
-                $response = 'Bye!' . "\n";
+        switch ($inputMessageJSON['command']) {
+            case 'disconnect':
+                $response['response'] = "Bye!";
                 break;
-            case 'help':
-                $response = 'Available commands:' . "\n";
-                $response .= 'quit' . "\n";
-                $response .= 'help' . "\n";
-                $response .= 'list_transform_components' . "\n";
+            case 'available_commands':
+                $response['response'] = [
+                    "disconnect",
+                    "available_commands",
+                    "list_transform_components"
+                ];
                 break;
             case 'list_transform_components':
-                $response = 'Entities:' . "\n";
+                $transformComponents = [];
                 foreach ($entities->view(Transform::class) as $entity => $transform) {
-                    $response .= "Entity " . $entity . " Position: " . $transform->position->__toString() . "\n";
+                    $transformComponents[] = [
+                        'entity_id' => $entity,
+                        'position' => [
+                            'x' => $transform->position->x,
+                            'y' => $transform->position->y,
+                            'z' => $transform->position->z
+                        ]
+                    ];
                 }
+                $response['response'] = $transformComponents;
                 break;
             default:
-                $response = 'Unknown command' . "\n";
+                $response['response'] = "Unknown command";
                 break;
         }
 

From 7abe56ad790be4c960f75b333551fdf6e48f1412 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 18 Feb 2023 08:54:07 +0100
Subject: [PATCH 47/49] added more comments

---
 bin/test-debug-client.php |  8 ++++++++
 src/Debug/DebugServer.php | 11 +++++++++++
 2 files changed, 19 insertions(+)

diff --git a/bin/test-debug-client.php b/bin/test-debug-client.php
index d14fdd8..8bdee82 100644
--- a/bin/test-debug-client.php
+++ b/bin/test-debug-client.php
@@ -1,20 +1,25 @@
 <?php
 
+// create the client socket
 if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
     echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
 }
 
+// connect to the server
 if (socket_connect($sock, '127.0.0.1', 8145) === false) {
     echo "socket_connect() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
 }
 
 $run = true;
 $msgCounter = 0;
+// client loop
 do {
     echo "Command: ";
 
+    // read the command from the console
     $stdin = fopen('php://stdin', 'r');
 
+    // prepare the json message
     $msg = trim(fgets($stdin));
     $msgJSON = json_encode([
         'command' => $msg,
@@ -23,8 +28,10 @@
 
     echo "\nRequest:\n$msgJSON\n\n";
 
+    // send the message to the server
     socket_write($sock, $msgJSON, strlen($msgJSON));
 
+    // error reading the response
     if (false === ($buf = socket_read($sock, 16384))) {
         echo "socket_read() failed, reason: " . socket_strerror(socket_last_error($sock)) . "\n";
     }
@@ -33,6 +40,7 @@
     print_r(json_decode($buf, true));
     echo "\n";
 
+    // check for disconnect
     if ($msg == 'disconnect') {
         $run = false;
     }
diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
index d29f215..56fef84 100644
--- a/src/Debug/DebugServer.php
+++ b/src/Debug/DebugServer.php
@@ -121,6 +121,7 @@ public function process(EntitiesInterface $entities): void
                 $responseJSON = json_encode($response);
                 socket_write($socketItem, $responseJSON, strlen($responseJSON));
 
+                // check for disconnect
                 if (isset($response['request']) && $response['request'] == "disconnect") {
                     socket_close($socketItem);
                     unset($this->clients[$i]);
@@ -143,35 +144,43 @@ public function process(EntitiesInterface $entities): void
      */
     private function handleInput(string $input, EntitiesInterface $entities): array
     {
+        // parse the input
         $inputMessageJSON = json_decode($input, true);
         if ($inputMessageJSON == null) {
+            // invalid JSON
             return [
                 'error' => "Invalid JSON on input"
             ];
         }
 
+        // check for the command
         if (!isset($inputMessageJSON['command'])) {
             return [
                 'error' => "Missing command"
             ];
         }
 
+        // check for the request id
         if (!isset($inputMessageJSON['request_id'])) {
             return [
                 'error' => "Missing request id for response"
             ];
         }
 
+        // prepare the response
         $response = [
             'response_id' => $inputMessageJSON['request_id'],
             'request' => $inputMessageJSON['command']
         ];
 
+        // handle the command
         switch ($inputMessageJSON['command']) {
             case 'disconnect':
+                // close the socket
                 $response['response'] = "Bye!";
                 break;
             case 'available_commands':
+                // return the available commands
                 $response['response'] = [
                     "disconnect",
                     "available_commands",
@@ -179,6 +188,7 @@ private function handleInput(string $input, EntitiesInterface $entities): array
                 ];
                 break;
             case 'list_transform_components':
+                // return the list of transform components, their connected entity ids and their positions
                 $transformComponents = [];
                 foreach ($entities->view(Transform::class) as $entity => $transform) {
                     $transformComponents[] = [
@@ -193,6 +203,7 @@ private function handleInput(string $input, EntitiesInterface $entities): array
                 $response['response'] = $transformComponents;
                 break;
             default:
+                // unknown command
                 $response['response'] = "Unknown command";
                 break;
         }

From 4d628652b1b7c91532d16340a780b79eef05a5a2 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 18 Feb 2023 08:55:34 +0100
Subject: [PATCH 48/49] this should be an error

---
 src/Debug/DebugServer.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
index 56fef84..565e827 100644
--- a/src/Debug/DebugServer.php
+++ b/src/Debug/DebugServer.php
@@ -204,7 +204,7 @@ private function handleInput(string $input, EntitiesInterface $entities): array
                 break;
             default:
                 // unknown command
-                $response['response'] = "Unknown command";
+                $response['error'] = "Unknown command";
                 break;
         }
 

From 62daec609b987f6e5f3909eb749f2ce1672749d9 Mon Sep 17 00:00:00 2001
From: Boris Stock <b.stock@bitstorm.de>
Date: Sat, 18 Feb 2023 12:07:52 +0100
Subject: [PATCH 49/49] hacked the optional debug console into the debug server
 hr hr hr

---
 app/core.ctn              |  3 ++-
 bin/test-debug-client.php |  9 +++++++--
 src/Debug/DebugServer.php | 34 ++++++++++++++++++++++++++++++++--
 src/Game.php              |  5 +++++
 4 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/app/core.ctn b/app/core.ctn
index 24f1f86..623633c 100644
--- a/app/core.ctn
+++ b/app/core.ctn
@@ -37,5 +37,6 @@
  */
 @debug.server: TowerDefense\Debug\DebugServer(
     :debug.server_ip, 
-    :debug.server_port
+    :debug.server_port,
+    @visu.dispatcher
 )
\ No newline at end of file
diff --git a/bin/test-debug-client.php b/bin/test-debug-client.php
index 8bdee82..08dded6 100644
--- a/bin/test-debug-client.php
+++ b/bin/test-debug-client.php
@@ -21,10 +21,15 @@
 
     // prepare the json message
     $msg = trim(fgets($stdin));
-    $msgJSON = json_encode([
+    $request = [
         'command' => $msg,
         'request_id' => $msgCounter++
-    ]);
+    ];
+    if (stripos($msg, 'console_command') === 0) {
+        $request['command'] = 'console_command';
+        $request['console_command'] = trim(substr($msg, 16));
+    }
+    $msgJSON = json_encode($request);
 
     echo "\nRequest:\n$msgJSON\n\n";
 
diff --git a/src/Debug/DebugServer.php b/src/Debug/DebugServer.php
index 565e827..d34840d 100644
--- a/src/Debug/DebugServer.php
+++ b/src/Debug/DebugServer.php
@@ -4,6 +4,9 @@
 
 use VISU\ECS\EntitiesInterface;
 use VISU\Geo\Transform;
+use VISU\Runtime\DebugConsole;
+use VISU\Signal\Dispatcher;
+use VISU\Signals\Runtime\ConsoleCommandSignal;
 
 /**
  * Class DebugServer
@@ -17,6 +20,9 @@ class DebugServer
     private ?object $socket = null; // the server socket
     private array $clients = []; // the client sockets
 
+    private Dispatcher $dispatcher; // the signal dispatcher
+    private ?DebugConsole $console = null; // the optional debug console
+
     /**
      * DebugServer constructor.
      * 
@@ -24,10 +30,16 @@ class DebugServer
      * @param int $port 
      * @return void 
      */
-    public function __construct(string $address, int $port)
+    public function __construct(string $address, int $port, Dispatcher $dispatcher)
     {
         $this->address = $address;
         $this->port = $port;
+        $this->dispatcher = $dispatcher;
+    }
+
+    public function setDebugConsole(DebugConsole $console): void
+    {
+        $this->console = $console;
     }
 
     /**
@@ -184,7 +196,8 @@ private function handleInput(string $input, EntitiesInterface $entities): array
                 $response['response'] = [
                     "disconnect",
                     "available_commands",
-                    "list_transform_components"
+                    "list_transform_components",
+                    "console_command"
                 ];
                 break;
             case 'list_transform_components':
@@ -202,6 +215,23 @@ private function handleInput(string $input, EntitiesInterface $entities): array
                 }
                 $response['response'] = $transformComponents;
                 break;
+            case 'console_command':
+                // trigger a console command
+                if ($this->console == null) {
+                    // debug console not available
+                    $response['error'] = "Debug console not available";
+                    break;
+                }
+                if (!isset($inputMessageJSON['console_command']) || strlen($inputMessageJSON['console_command']) < 1) {
+                    // missing console command
+                    $response['error'] = "Missing console command";
+                    break;
+                }
+                // dispatch the console command signal
+                $commandSignal = new ConsoleCommandSignal($inputMessageJSON['console_command'], $this->console);
+                $this->dispatcher->dispatch(DebugConsole::EVENT_CONSOLE_COMMAND, $commandSignal);
+                $response['response'] = "Console command triggered";
+                break;
             default:
                 // unknown command
                 $response['error'] = "Unknown command";
diff --git a/src/Game.php b/src/Game.php
index 41e908b..17e5223 100644
--- a/src/Game.php
+++ b/src/Game.php
@@ -103,6 +103,11 @@ public function __construct(GameContainer $container)
         // initialize the debug renderers
         $this->dbgText = new DebugTextOverlay($container);
         $this->dbgConsole = new DebugConsole($container->resolveGL(), $container->resolveInput(), $container->resolveVisuDispatcher());
+        if ($this->container->get('debug.server')) {
+            // set the debug console on the debug server if it is available
+            $debugServer = $this->container->get('debug.server');
+            $debugServer->setDebugConsole($this->dbgConsole);
+        }
         $this->dbg3D = new Debug3DRenderer($container->resolveGL());
         Debug3DRenderer::setGlobalInstance($this->dbg3D);
 
