Tutorial: Dynamische Objektformationen in MiniScript – Eine Fallstudie

1. Einleitung

Dieses Tutorial behandelt die Implementierung dynamischer Objektformationen in der MiniScript-Umgebung mit XRXplorer. Ziel ist es, die Transformation einer Menge von 3D-Objekten (Würfeln) von einer initialen Gitterstruktur in eine sphärische Anordnung und zurück zu demonstrieren. Dabei werden grundlegende Konzepte der 3D-Grafikprogrammierung, wie Transformationen, Zeitmanagement und mathematische Interpolation, vertieft.

MiniScript ist eine leichtgewichtige Skriptsprache, die für eingebettete Anwendungen konzipiert wurde. Sie bietet eine klare Syntax und grundlegende mathematische Funktionen, die für die Manipulation von Objekten in einer 3D-Umgebung essenziell sind.

2. Grundlagen der 3D-Transformationen

In 3D-Umgebungen werden Objekte durch Transformationen im Raum positioniert, ausgerichtet und skaliert. Jedes Objekt besitzt eine sogenannte “Transformationsmatrix”, die seine Position (Translation), Rotation und Skalierung im Weltraum definiert.

  • Position (Translation): Beschreibt den Ort eines Objekts im 3D-Raum (X, Y, Z-Koordinaten).
  • Skalierung: Definiert die Größe eines Objekts entlang seiner Achsen.
  • Rotation: Beschreibt die Ausrichtung eines Objekts im Raum.

In XRXPlorer werden diese Transformationen durch spezifische Befehle wie moveObject und scaleObject direkt angewendet. Das Verständnis, ob diese Befehle im globalen Weltkoordinatensystem oder in einem lokalen, hierarchischen System (Parenting) operieren, ist entscheidend. In dieser Fallstudie gehen wir davon aus, dass moveObject Objekte direkt im globalen Weltraum positioniert.

3. Das Konzept der Interpolation (Lerp)

Um einen fließenden Übergang zwischen zwei Zuständen (hier: Gitterformation und Kugelformation) zu realisieren, wird die lineare Interpolation (oft als “Lerp” bezeichnet) verwendet. Lerp berechnet einen Zwischenwert zwischen zwei gegebenen Werten basierend auf einem Interpolationsfaktor, der typischerweise zwischen 0 und 1 liegt.

Die Formel für die lineare Interpolation zwischen zwei Werten A und B mit einem Faktor t ist: Value=Acdot(1−t)+Bcdott

  • Wenn t=0, ist der Value=A.
  • Wenn t=1, ist der Value=B.
  • Für 0\<t\<1 liegt der Value proportional zwischen A und B.

Dies kann auf 3D-Vektoren (Positionen) angewendet werden, indem jede Komponente (X, Y, Z) separat interpoliert wird.

4. Mathematische Grundlagen der Kugelverteilung

Um 100 Würfel annähernd gleichmäßig auf der Oberfläche einer Kugel zu verteilen, wird eine Methode verwendet, die auf dem Goldenen Winkel basiert. Dies ist eine effiziente Methode, um Punkte spiralförmig auf einer Kugeloberfläche zu verteilen, ohne komplexe Algorithmen zur exakten Kugelpackung zu benötigen.

Die Formeln für die Position eines Punktes (x, y, z) auf einer Kugel mit Radius R für den i-ten Punkt (von N Punkten) sind:

  • Goldener Winkel (phi): phi=picdot(3−sqrt5)
  • Y-Koordinate: y=1−(i/(N−1))cdot2 (verteilt y von 1 bis −1)
  • Radius im XY-Plane (r_xy): r_xy=sqrt1−y2cdotR
  • Winkel um die Y-Achse (alpha): alpha=phicdoti
  • X-Koordinate: x=r_xycdotcos(alpha)
  • Z-Koordinate: z=r_xycdotsin(alpha)

Diese Berechnungen ermöglichen es, die Würfel in einer ansprechenden sphärischen Anordnung zu platzieren.

5. Analyse des MiniScript-Codes

Betrachten wir nun den MiniScript-Code für die “Würfel-Kugel-Formation-Animation” im Detail:

numCubes = 100
cubeBaseName = "Cube"
cubeScale = 0.25 // Einheitliche Skalierung für alle Würfel

// Gitter-Parameter (Start-/Endposition)
gridSize = 10 // 10x10 Gitter
initialSpacing = 0.5 // Abstand zwischen den Würfeln im Gitter
gridYPos = 0.5 // Feste Y-Position des Gitters

// Kugel-Parameter (Ziel-Formation)
sphereRadius = 4 // Radius der Kugel-Formation
sphereYPos = 2   // Y-Position der Kugel-Formation

// Animations-Parameter
animationDuration = 5 // Dauer einer Animationsphase (Gitter zu Kugel oder Kugel zu Gitter)
animationSpeed = 1    // Geschwindigkeit der gesamten Animation

cubeNames = []
for i in range(1, numCubes)
    cubeNames.push(cubeBaseName + i)
end for

// Initiale Skalierung aller Würfel
for i in range(0, numCubes - 1)
    currentCubeName = cubeNames[i]
    scaleObject currentCubeName, cubeScale, cubeScale, cubeScale
end for

// Hauptanimationsschleife für die Kugel-Formation
while true
    currentTime = time

    // Phasen-Berechnung: Wechsel zwischen Gitter -> Kugel und Kugel -> Gitter
    // Modulo 2 * animationDuration, um die Zeit in Phasen zu unterteilen
    phaseTime = currentTime * animationSpeed % (2 * animationDuration)

    // Interpolationsfaktor (von 0 bis 1 und zurück)
    // Wenn phaseTime < animationDuration: Gitter zu Kugel (factor von 0 nach 1)
    // Wenn phaseTime >= animationDuration: Kugel zu Gitter (factor von 1 nach 0)
    interpFactor = 0
    if phaseTime < animationDuration then
        interpFactor = phaseTime / animationDuration
    else
        interpFactor = 1 - ((phaseTime - animationDuration) / animationDuration)
    end if

    for i in range(0, numCubes - 1)
        currentCubeName = cubeNames[i]

        // 1. Gitter-Startposition (unabhängig von Expansion)
        row = floor(i / gridSize)
        col = i % gridSize
        gridPosX = (col - (gridSize - 1) / 2) * initialSpacing
        gridPosZ = (row - (gridSize - 1) / 2) * initialSpacing

        // 2. Kugel-Zielposition
        // Gleichmässige Verteilung der Würfel auf einer Kugeloberfläche
        // (Annäherung: Fibonacci-Gitter oder spiralförmige Anordnung auf Kugel)
        // Hier eine einfache spiralförmige Anordnung für 100 Punkte auf einer Kugel
        goldenAngle = pi * (3 - sqrt(5)) // Goldener Winkel in Radianten
        y = 1 - (i / (numCubes - 1)) * 2 // Y von 1 bis -1
        radiusAtY = sqrt(1 - y*y) * sphereRadius
        angle = goldenAngle * i

        spherePosX = radiusAtY * cos(angle)
        spherePosY = y * sphereRadius
        spherePosZ = radiusAtY * sin(angle)

        // Interpolation zwischen Gitter- und Kugelposition
        // lerp = linear interpolation
        finalPosX = gridPosX * (1 - interpFactor) + spherePosX * interpFactor
        finalPosY = gridYPos * (1 - interpFactor) + spherePosY * interpFactor
        finalPosZ = gridPosZ * (1 - interpFactor) + spherePosZ * interpFactor

        moveObject currentCubeName, finalPosX, finalPosY, finalPosZ
    end for

    // yield // Dieses Statement bleibt entfernt, wie gewünscht
end while

5.1. Initialisierung und Parameter (Zeilen 1-28)

  • numCubes, cubeBaseName, cubeScale: Definieren die Anzahl, den Namenspräfix und die Größe der Würfel.
  • gridSize, initialSpacing, gridYPos: Legen die Dimensionen und die Y-Position der initialen Gitterformation fest.
    • gridSize = 10 bedeutet ein 10×10 Gitter für 100 Würfel.
    • initialSpacing ist der Abstand zwischen den Würfeln im Gitter.
  • sphereRadius, sphereYPos: Bestimmen den Radius und die Y-Position der Ziel-Kugelformation.
  • animationDuration, animationSpeed: Steuern die Dauer einer einzelnen Transformationsphase (z.B. Gitter zu Kugel) und die Gesamtgeschwindigkeit der Animation.
  • Die Schleifen zur Befüllung von cubeNames und zur initialen scaleObject-Anwendung sind Standardprozeduren.

5.2. Hauptanimationsschleife und Phasenberechnung (Zeilen 31-44)

  • while true: Eine Endlosschleife, die die Animation kontinuierlich ablaufen lässt.
  • currentTime = time: Ruft die aktuelle Systemzeit ab. time ist eine intrinsische MiniScript-Funktion, die die Sekunden seit Programmstart zurückgibt.
  • phaseTime = currentTime * animationSpeed % (2 * animationDuration):
    • currentTime * animationSpeed: Skaliert die Zeit, um die Gesamtgeschwindigkeit der Animation zu steuern.
    • % (2 * animationDuration): Der Modulo-Operator sorgt dafür, dass phaseTime immer im Bereich von 0 bis 2 * animationDuration bleibt. Dies teilt die Animation in wiederkehrende Zyklen (z.B. 0-10 Sekunden für einen Zyklus, wenn animationDuration 5 ist).
  • Interpolationsfaktor (interpFactor):
    • Dieser Faktor steuert den Übergang zwischen der Gitter- und der Kugelformation.
    • Wenn phaseTime kleiner als animationDuration ist (erste Hälfte des Zyklus: Gitter zu Kugel), steigt interpFactor linear von 0 auf 1.
    • Wenn phaseTime größer oder gleich animationDuration ist (zweite Hälfte des Zyklus: Kugel zu Gitter), fällt interpFactor linear von 1 auf 0.

5.3. Positionsberechnung für Gitter und Kugel (Zeilen 46-70)

Innerhalb der for-Schleife für jeden Würfel werden zwei Zielpositionen berechnet:

  • Gitter-Position (gridPosX, gridPosZ):
    • row = floor(i / gridSize) und col = i % gridSize: Berechnen die Reihe und Spalte des aktuellen Würfels im Gitter.
    • Die Formeln (col - (gridSize - 1) / 2) * initialSpacing und (row - (gridSize - 1) / 2) * initialSpacing zentrieren das Gitter um den Ursprung (0,0) der XZ-Ebene.
  • Kugel-Position (spherePosX, spherePosY, spherePosZ):
    • goldenAngle = pi * (3 - sqrt(5)): Definiert den Goldenen Winkel für die spiralförmige Verteilung.
    • y = 1 - (i / (numCubes - 1)) * 2: Berechnet die Y-Koordinate des Punktes auf der Kugel, die von 1 bis -1 linear über die Anzahl der Würfel verteilt wird.
    • radiusAtY = sqrt(1 - y*y) * sphereRadius: Berechnet den Radius des Kreises auf der Y-Ebene, der für die XZ-Position des Punktes auf der Kugel verwendet wird.
    • angle = goldenAngle * i: Berechnet den Winkel um die Y-Achse für den aktuellen Punkt in der Spirale.
    • spherePosX = radiusAtY * cos(angle) und spherePosZ = radiusAtY * sin(angle): Berechnen die X- und Z-Koordinaten auf dem Kreis bei der jeweiligen Y-Ebene.
    • spherePosY = y * sphereRadius: Skaliert die Y-Koordinate auf den Kugelradius.

5.4. Interpolation und Anwendung der Transformation (Zeilen 72-78)

  • finalPosX = gridPosX * (1 - interpFactor) + spherePosX * interpFactor (und analog für Y und Z):
    • Dies ist die Anwendung der linearen Interpolation. Die endgültige Position jedes Würfels ist eine Mischung aus seiner Gitter-Position und seiner Kugel-Position, gesteuert durch den interpFactor.
    • Beachten Sie, dass finalPosY auch zwischen gridYPos und spherePosY interpoliert wird, um den Y-Übergang zu steuern.
  • moveObject currentCubeName, finalPosX, finalPosY, finalPosZ: Verschiebt den Würfel an die berechnete interpolierte Position.

6. Implementierung und Anpassung

  1. Vorbereitung in der Galaxie: Stelle sicher, dass 100 Würfel in deiner Szene vorhanden sind und exakt von Cube1 bis Cube100 benannt sind. Ihre initiale Position ist für dieses Skript nicht kritisch, da sie sofort neu positioniert werden.
  2. Skript einfügen: Kopiere den gesamten MiniScript-Code in den dafür vorgesehenen Editor deiner Galaxie.
  3. Anpassung der Parameter: Experimentiere mit den Werten der Variablen im oberen Bereich des Skripts (gridSize, initialSpacing, sphereRadius, animationDuration, animationSpeed). Kleinere animationDuration-Werte führen zu schnelleren Übergängen, höhere Werte zu langsameren.
  4. Ausführung: Starte das Skript in deiner Galaxie. Die Würfel sollten sich nun kontinuierlich zwischen der Gitter- und der Kugelformation hin und her bewegen.

7. Fazit

Dieses Tutorial hat demonstriert, wie komplexe dynamische Objektformationen in MiniScript durch die Kombination von mathematischen Prinzipien (Sinus, Kosinus, Goldener Winkel) und dem Konzept der linearen Interpolation realisiert werden können. So kannst du Effekte durch präzise mathematische Berechnungen im globalen Raum erreichen. Das Verständnis der zugrunde liegenden Algorithmen ist entscheidend für die Erstellung überzeugender und dynamischer 3D-Szenen.

8. Download

Beispiel: formation.txt

Scroll to Top