Ab heute mache ich wieder einen kleinen Exkurs in die „Virtuelle Realität“. Genauer in die Steuerung eben dieser.

Jeder der schonmal eine VIVE oder Oculus auf der Nase hatte und nicht über Hightech-Ausrüstung wie Laufbänder oder Bodysuits oder taktile Handschuhe verfügte, musste sich mit der Steuerung beschäftigen. Welcher Knopf ist für was und wie halte ich die Dinger am besten.

Ein anderer Weg als über Kontroller mit der virtuellen Umgebung zu interagieren, ist der Einsatz von Sensoren, zum tracken von Körperteilen mittels Kameras oder Infrarot. Der Platzhirsch seit Jahren auf dem Gebiet – und bis auf die Kinect von MS – fast alternativlos ist die Leap Motion. Und genau damit beschäftige ich mich im momentan in einem Kunstprojekt, an dem ich mitwirken darf.

Ganz kurz gesagt und ohne zu viele Worte über die Experience, die irgendwann im April fertig sein soll (mehr dann dazu Ende April) zu verlieren, geht es darum mit den Händen in natürlicher Pose – wie aus einem Fantasy-Roman – Energy aus einem Objekt aufzunehmen. Die kumulierte Energie kann dann später von der Handfläche aus in Richtung der Fingerspitzen ausgesendet werden. Ein bisschen wie das wirken eines Feuerballzaubers in Advanced Dungeons & Dragons.

Aktueller Stand vor dem Einbauen in die eigentliche Umgebung

Prinzipiell kein Hexenwerk. Nur hat Leap Motion die Gesten aus dem VR-SDK (Orion) in der letzten Version leider gestrichen. D.d. die Gesten werden nicht mehr „reported“ wie es in der aktuellen Dokumentation an den entsprechenden Stellen geschrieben steht. Vielleicht auch besser so, dem Kommentar eines Leap Motion Mitarbeiters im Forum nach zu urteilen:

„We removed the four built-in gestures in Orion. This was for a variety of reasons, but mostly because they never really worked well enough and worked even less well in VR.“ (forums.leapmotion)

Was also tun?

Genau! Wir programmieren unsere Gesten selbst. Wir haben ja noch die ganzen Informationen die uns das „Leap Motion“-Handobjekt liefert. Zu jeder Zeit (d.h. zu jedem Frame) können wir abfragen in welche Richtung welcher Teil der Hand gerade zeigt und in welcher Geschwindigkeit sich welcher Teil gerade in welche Richtung bewegt. Dann muss man für das was wir brauchen nur noch einen kleinen Observer schreiben.

Freundlicherweise liefert Leap Motion schon ein paar basale Skripte im Core-Modul mit, mit deren Hilfe man z.B. erkennen/detektieren kann, in welche Richtung eine Hand zeigt oder ob sich ein Target Objekt in der Nähe befindet.

Das Ganze sieht dann in z.B. wie folgt aus:

Als Target fungiert hier ein Zylinder, der später in der Experience durch einen Baum ausgetauscht wird. Der Detector ruft also die Funktion OnActivateO auf, wenn sich die Handfläche der linken Hand in einem Winkel < 30 Grad zum Target befindet und OnDeactivateO wenn die Handfläche einen Winkel von > 45 Grad zum Target einnimmt. Statt eines Targets kann man auch die Blickrichtung der Kamera oder den Horizont als Referenz für die Berechnung des Winkels nehmen.

Die anderen mitgelieferten Detektoren funktionieren auf eine ähnliche Weise und sind weitgehend selbsterklärend.

Kann man benutzen.

Man merkt dann aber relativ schnell, dass dieses Vorgehen mit der ein oder anderen Einschränkung verbunden ist. Z.B. ist die Benutzung des gleichen Skript auf der selben Hand (Handfläche oben, ja, nein? Handfläche unten, ja, nein?) irgendwie unhandlich. Außerdem funktioniert das auch nicht wirklich besonders zuverlässig, da sich die Skripte irgendwie in die Quere kommen.

Da ich aber gerade am Testen bin, hab ich die Detektoren einfach mal benutzt um zu checken ob die beiden Hände mit der Handfläche auf das Target gerichtet sind, die Hände offen sind und sich zudem in einem vorher (auch in einem Detektor) definierten Radius um das Target befinden. Mit dem Logic Gate kann ich nun bestimmen, ob die Energie aufgeladen wird, d.h, der Ball größer wird.

An dieser Stelle habe ich dann beschlossen, den Rest, selbst zu schreiben.

Das Aufnehmen des Energieballs in eine der beiden Hände, wenn die Handfläche nach oben gedreht wird (Skript 1) und das Senden der Energie, wenn die Beschleunigung der zweiten Hand mit der Handfläche nach vorne gerichtet einen Grenzwert überschreitet. (Welches ganz ähnlich aussieht, nur dass als Referenz für die Berechnung des Winkels nicht der Up-Vector, sondern der Forward-Vector herangezogen wird.

Skript 1: 1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

private void checkUPPositionSingleHand ( )

{

Frame frame = provider . CurrentFrame ;



for ( int i = 0 ; i & lt ; frame . Hands . Count ; i ++ )

{

Hand hand = frame . Hands [ i ] ; // cannot apply indexing





if ( hand . IsLeft )

{

//Where the Energy flows

Vector direction = hand . Direction ;

leftHandDirection = direction . ToVector3 ( ) ;



//Check Angle of Palm to UP-Vector

Vector3 normal = hand . PalmNormal . ToVector3 ( ) ;

float angleTo = Vector3 . Angle ( normal, Camera . main . transform . TransformDirection ( Vector3 ( 0 , 0 , 1 ) ) ) ;

GameObject EBubble = Energyball ;



if ( angleTo & lt ; UpAngleOn & amp ;& amp ; EBubble . transform . parent != rightHand . transform ) { Debug . Log ( "left:" + angleTo ) ; EBubble . transform . parent = leftHand . transform ; EBubble . transform . localPosition = ( - 0 . 05f, 0 . 05f, 0 ) ; leftHandInUpPosition = true ; } else if ( angleTo & gt ; UpAngleOff & amp ;& amp ; EBubble . transform . parent != rightHand . transform )

{





EBubble . transform . parent = cameraToAttach . transform ;

EBubble . transform . localPosition = ( 0 , 0 , 0 . 13f ) ;

leftHandInUpPosition = false ;



}



}

if ( hand . IsRight )

{

//Where the Energy flows

Vector direction = hand . Direction ;

rightHandDirection = direction . ToVector3 ( ) ;

//Check Angle of Palm to UP-Vector

Vector3 normal = hand . PalmNormal . ToVector3 ( ) ;

float angleTo = Vector3 . Angle ( normal, Camera . main . transform . TransformDirection ( UPDirection ) ) ;



GameObject EBubble = Energyball ;

if ( angleTo & lt ; UpAngleOn & amp ;& amp ; EBubble . transform . parent != leftHand . transform ) { Debug . Log ( "right:" + angleTo ) ; EBubble . transform . parent = rightHand . transform ; EBubble . transform . localPosition = ( 0 . 05f, - 0 . 05f, 0 ) ; rightHandInUpPosition = true ; } else if ( angleTo & gt ; UpAngleOff & amp ;& amp ; EBubble . transform . parent != leftHand . transform )

{



EBubble . transform . parent = cameraToAttach . transform ;

EBubble . transform . localPosition = ( 0 , 0 , 0 . 13f ) ;

rightHandInUpPosition = false ;



}

}



}

} checkUPPositionSingleHandFrame frameproviderltframeHand handframehandVector directionhandleftHandDirectiondirectionVector3 normalhandangleToVector3normal, Camera new Vector3GameObject EBubbleEnergyballangleToltUpAngleOnampampEBubblerightHandDebugangleToEBubbleleftHandEBubble new Vector305f, 005f,leftHandInUpPositionangleTogtUpAngleOffampampEBubblerightHandEBubblecameraToAttachEBubble new Vector3, 013fleftHandInUpPositionhandVector directionhandrightHandDirectiondirectionVector3 normalhandangleToVector3normal, CameraUPDirectionGameObject EBubbleEnergyballangleToltUpAngleOnampampEBubbleleftHandDebugangleToEBubblerightHandEBubble new Vector305f,05f,rightHandInUpPositionangleTogtUpAngleOffampampEBubbleleftHandEBubblecameraToAttachEBubble new Vector3, 013frightHandInUpPosition

Und so haben wir nun einen simplen Mix aus vorgefertigten Skripten und Eigenproduktionen, die ein ganz nettes Ergebnis liefern. Bin gespannt wie es in der echten virtuellen Welt mit den richtigen Effekten und Partikelsystemen funktioniert und aussieht.

tbc.