Wirtualna kierownica do gier w oparciu o Kinecta
Spis treści
Mając chwilę czasu zastanawiam się ostatnio po jaką zabawkę sięgnąć. Ostatnim razem pokazywałem jak z wykorzystaniem Arduino i paska LED można stworzyć własny system Ambilightdo monitora, dzisiaj postanowiłem powrócić jednak do zabawy z Kinectem. Jakiś czas temu pokazałem jak skonfigurować sensor do pracy z Javą wykorzystując bibliotekę J4K i jak w prosty sposób można sterować kursorem myszy, teraz postanowiłem stworzyć wirtualną kierownicę, która pozwoli kontrolować np. samochody w grach.
Jak to działa
Założenie jest dosyć proste. Za pomocą Kinect SDK możemy odczytać położenie dłoni, łokci, czy nóg względem osi XY, która wyznaczana jest mniej więcej przez nas kręgosłup i linię przechodzącą przez nasz brzuch. Dodatkowo biblioteka J4K umożliwia konfigurację odczytu jedynie górnej partii ciała, dzięki czemu możemy stworzyć aplikację działającą w trybie siedzącym. Kierownica w grach działa najczęściej w banalny sposób i sterowanie odbywa się poprzez wciskanie strzałek w prawo, bądź lewo. Odczytując położenie dłoni możemy więc sprawdzić, która dłoń jest wyżej, a która niżej i na tej podstawie stwierdzić, w którym kierunku chce skręcić użytkownik. Jeśli dłonie są mniej więcej na jednym poziomie zakładamy, że użytkownik jedzie prosto. Idąc nieco dalej można by się pokusić o sterowanie bardziej analogowe, czyli wykrywanie tego, czy chcemy skręcać łagodnie, czy agresywnie, ale na ten moment sobie to odpuścimy.
*fot: flickr.com/photos/jamiemc/2845804445/*- Jeśli lewa ręka jest w ćwiartce 1 a prawa w 4 - skręcamy w prawo.
- Jeśli lewa ręka jest w ćwiartce 3 a prawa w 2 - skręcamy w lewo.
- Jeśli ręce znajdują się w martwej strefie pomiędzy ćwiartkami - jedziemy prosto.
Najprostszym sposobem na wykrycie odpowiedniej sytuacji jest wyliczenie różnicy pomiędzy współrzędną Y obu dłoni. W przypadku jazdy prosto można się spodziewać, że różnica ta będzie niewielka, mniej więcej w zakresie (-0.1, 0.1), a przy większej będzie to skręt w jedną ze stron.
Projekt
To jak skonfigurować projekt, aby działał on z Kinectem pokazywałem już poprzednim razem, więc nie będę tego powtarzał. Sam kod aplikacji jest stosunkowo prosty, co widać poniżej
package pl.javastart.kinect;
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import edu.ufl.digitalworlds.j4k.J4KSDK;
import edu.ufl.digitalworlds.j4k.Skeleton;
public class KinectWheel extends J4KSDK {
public static final double TURN_DIFFERENCE = 0.1;
public static final int TURN_LEFT = -1;
public static final int TURN_RIGHT = 1;
public static final int GO_STRAIGHT = 0;
private AtomicInteger direction = new AtomicInteger(GO_STRAIGHT);
private Robot robot;
private DirectionChanger directionChanger;
public KinectWheel() {
configureRobot();
trackDirection();
}
private void configureRobot() {
try {
robot = new Robot();
} catch (AWTException e) {
e.printStackTrace();
}
}
/**
* Przyciski wciskamy w osobnym wątku
*/
private void trackDirection() {
directionChanger = new DirectionChanger();
new Thread(new Runnable() {
@Override
public void run() {
try {
directionChanger.execute();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
@Override
public void onColorFrameEvent(byte[] arg0) {
}
@Override
public void onDepthFrameEvent(short[] arg0, byte[] arg1, float[] arg2,
float[] arg3) {
}
@Override
public void onSkeletonFrameEvent(boolean[] skeletons, float[] positions,
float[] orientations, byte[] state) {
Skeleton skeleton = null;
for (int i = 0; i < skeletons.length; i++) {
if (skeletons[i]) {
skeleton = Skeleton.getSkeleton(i, skeletons, positions, orientations, state, this);
}
}
if (skeleton != null) {
calculateHandDiff(skeleton);
}
}
/**
* Wyliczamy różnicę wysokości dłoni i zmieniamy kierunek
*/
private void calculateHandDiff(Skeleton skeleton) {
float rightHandY = skeleton.get3DJointY(Skeleton.HAND_RIGHT);
float leftHandY = skeleton.get3DJointY(Skeleton.HAND_LEFT);
float handDiff = rightHandY - leftHandY;
changeDirection(handDiff);
checkStopCondition(rightHandY, leftHandY);
}
private void changeDirection(float diff) {
if (diff > TURN_DIFFERENCE) {
direction.set(TURN_LEFT);
} else if (diff < -TURN_DIFFERENCE) {
direction.set(TURN_RIGHT);
} else {
direction.set(GO_STRAIGHT);
}
}
/**
* Jeśli obie ręce uniesione są wysoko kończymy działanie aplikacji
*/
private void checkStopCondition(float left, float right) {
final float stopCondition = 0.15f;
if(left > stopCondition && right > stopCondition) {
directionChanger.stop();
}
}
public static void main(String[] args) {
System.out.println("Kinect virtual wheel");
KinectWheel kinect = new KinectWheel();
kinect.start(Kinect.DEPTH | Kinect.COLOR | Kinect.SKELETON | Kinect.XYZ
| Kinect.PLAYER_INDEX);
kinect.setNearMode(true);
kinect.setSeatedSkeletonTracking(true);
System.out.println("Bye bye");
}
/**
* Symulacja ciągłego wciskania przycisku
*/
private class DirectionChanger {
private AtomicBoolean running = new AtomicBoolean(true);
public void execute() throws InterruptedException {
while (running.get()) {
switch (direction.get()) {
case GO_STRAIGHT:
robot.keyRelease(KeyEvent.VK_D);
robot.keyRelease(KeyEvent.VK_A);
break;
case TURN_RIGHT:
robot.keyRelease(KeyEvent.VK_A);
robot.keyPress(KeyEvent.VK_D);
break;
case TURN_LEFT:
robot.keyRelease(KeyEvent.VK_D);
robot.keyPress(KeyEvent.VK_A);
}
Thread.sleep(10);
}
}
public void stop() {
running.set(false);
}
}
}
Aplikacja oparta jest o dwa wątki. W wątku głównym odczytujemy położenie dłoni i odpowiednio ustawiamy wartość direction . W drugim wątku widzimy pętlę, która odpowiada za symulowanie wciskania klawiszy A lub D (najczęściej lewo lub prawo w grach) używając klasy Robot. Dodatkowo stworzona została prosta metoda checkStopCondition() , która sprawi, że aplikacja się wyłączy, gdy obie ręce uniesiemy ku górze. W metodzie main() dodaliśmy również konfigurację do obiektu J4K, gdzie poprzez metody setNearMode() i setSeatedSkeleton() wskazujemy, że siedzimy blisko sensora i śledzona ma być górna część ciała.
Prezentacja
Poniżej znajduje się krótki film obrazujący działanie powyższej aplikacji. Jak widać nie jest ona może idealna, głównie z powodu niedokładności odczytu współrzędnych, ale radość z wirtualnego kontrolera jest niemała :)
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.