package leapv2; import com.leapmotion.leap.Arm; import com.leapmotion.leap.Bone; import com.leapmotion.leap.Controller; import com.leapmotion.leap.Vector; import java.util.List; import javafx.application.Application; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.geometry.Bounds; import javafx.geometry.Point3D; import javafx.scene.Group; import javafx.scene.PerspectiveCamera; import javafx.scene.PointLight; import javafx.scene.Scene; import javafx.scene.SceneAntialiasing; import javafx.scene.SubScene; import javafx.scene.image.Image; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.paint.Material; import javafx.scene.paint.PhongMaterial; import javafx.scene.shape.CullFace; import javafx.scene.shape.Cylinder; import javafx.scene.shape.DrawMode; import javafx.scene.shape.MeshView; import javafx.scene.shape.Shape3D; import javafx.scene.shape.Sphere; import javafx.scene.shape.TriangleMesh; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; import javafx.stage.Stage; /** * * @author Jose Pereda - June 2014 - @JPeredaDnr */ public class LeapV2 extends Application { private LeapListener listener = null; private Controller controller = null; private final Rotate cameraXRotate = new Rotate(0,0,0,0,Rotate.X_AXIS); private final Rotate cameraYRotate = new Rotate(0,0,0,0,Rotate.Y_AXIS); private final Translate cameraPosition = new Translate(-100,-550,-200); private Shape3D[] meshView=null; private final Group root=new Group(); private double dragStartX, dragStartY, dragStartRotateX, dragStartRotateY; private Bounds sphereBounds; private PhongMaterial material, sphereMaterial; private BooleanProperty touch = new SimpleBooleanProperty(false); @Override public void start(Stage primaryStage) { listener = new LeapListener(); controller = new Controller(); controller.addListener(listener); AnchorPane pane=new AnchorPane(); Scene scene = new Scene(pane, 800, 600, Color.BEIGE); final PerspectiveCamera camera = new PerspectiveCamera(); camera.setFieldOfView(60); camera.getTransforms().addAll(cameraXRotate,cameraYRotate,cameraPosition); material = new PhongMaterial(); material.setDiffuseColor(Color.GAINSBORO); material.setSpecularColor(Color.rgb(30, 30, 30)); sphereMaterial = new PhongMaterial(); sphereMaterial.setDiffuseColor(Color.GAINSBORO); sphereMaterial.setSpecularColor(Color.rgb(30, 30, 30)); sphereMaterial.setSelfIlluminationMap(new Image(getClass().getResource("red.png").toExternalForm())); meshView = new Shape3D[3]; meshView[0]=new MeshView(buildTriangleMesh(20, 20, 30,true)); meshView[1]=new MeshView(buildTriangleMesh(20, 20, 30,false)); for (int i=0; i<2; i++) { meshView[i].setMaterial(material); meshView[i].setTranslateX(300); meshView[i].setTranslateY(i==0?-300:0); meshView[i].setTranslateZ(i==0?0:-300); meshView[i].setDrawMode(DrawMode.LINE); meshView[i].setCullFace(CullFace.NONE); } meshView[2]=new Sphere(60); meshView[2].setMaterial(material); meshView[2].setDrawMode(DrawMode.FILL); meshView[2].setCullFace(CullFace.BACK); meshView[2].setTranslateX(160); meshView[2].setTranslateY(-60); meshView[2].setTranslateZ(-160); sphereBounds = meshView[2].localToScene(meshView[2].getBoundsInLocal()); System.out.println("s0: "+meshView[2].getBoundsInLocal()); System.out.println("s: "+sphereBounds); final Group parent = new Group(meshView); root.getChildren().addAll(parent); final PointLight pointLight = new PointLight(Color.ANTIQUEWHITE); pointLight.setTranslateX(0); pointLight.setTranslateY(-800); pointLight.setTranslateZ(-600); Cylinder axisX=new Cylinder(5,700); axisX.setRotationAxis(Rotate.Z_AXIS); axisX.setRotate(90d); axisX.setTranslateX(250d); root.getChildren().add(axisX); Cylinder axisY=new Cylinder(5,700); axisY.setTranslateY(-250d); root.getChildren().add(axisY); Cylinder axisZ=new Cylinder(5,700); axisZ.setRotationAxis(Rotate.X_AXIS); axisZ.setRotate(90d); axisZ.setTranslateZ(-250d); root.getChildren().add(axisZ); root.getChildren().addAll(pointLight); Group root3D=new Group(); root3D.getChildren().addAll(camera, root); SubScene subScene = new SubScene(root3D, 800, 600,true,SceneAntialiasing.BALANCED); subScene.setCamera(camera); pane.getChildren().addAll(subScene); final int minRoot=root.getChildren().size(); final PhongMaterial materialFinger = new PhongMaterial(); materialFinger.setDiffuseColor(Color.GOLDENROD); materialFinger.setSpecularColor(Color.rgb(50, 50, 50)); final PhongMaterial materialArm = new PhongMaterial(); materialArm.setDiffuseColor(Color.CORNSILK); materialArm.setSpecularColor(Color.rgb(30, 30, 30)); listener.doneListProperty().addListener((ov,b,b1)->{ if(b1){ // First, get a fresh copy of the bones, arms & joints collection List<Bone> bones=listener.getBones(); List<Arm> arms=listener.getArms(); List<Pair> joints=listener.getJoints(); touch.set(false); Platform.runLater(()->{ // Now, on the JavaFX thread if(root.getChildren().size()>minRoot){ // clean old bones root.getChildren().remove(minRoot,root.getChildren().size()-1); } // Iterate over the list adding the bones to the scene // If the collection changes there won't be any concurrent exception // as we are iterating over its copy. bones.stream() .filter((bone) -> (bone.isValid() && bone.length()>0)) .forEach((bone) -> { final Vector p=bone.center(); // create bone as a vertical cylinder and locate it at its center position Cylinder c=new Cylinder(bone.width()/2,bone.length()); c.setMaterial(materialFinger); // translate and rotate the cylinder towards its direction final Vector v=bone.direction(); Vector cross = (new Vector(v.getX(),-v.getY(),-v.getZ())).cross(new Vector(0,-1,0)); double ang=(new Vector(v.getX(),-v.getY(),-v.getZ())).angleTo(new Vector(0,-1,0)); c.getTransforms().addAll( new Translate(200+p.getX(),-p.getY(),-p.getZ()), new Rotate(-Math.toDegrees(ang), 0, 0, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())) ); // add bone to scene root.getChildren().add(c); Sphere s=new Sphere(bone.width()/2f*1.2); s.setMaterial(materialFinger); s.getTransforms().addAll( new Translate(200+p.getX(),-p.getY()+bone.length()/2d,-p.getZ()), new Rotate(-Math.toDegrees(ang), 0, -bone.length()/2d, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ()))); // add bone to scene root.getChildren().add(s); Sphere s2=new Sphere(bone.width()/2f*1.2); s2.setMaterial(materialFinger); s2.getTransforms().addAll( new Translate(200+p.getX(),-p.getY()-bone.length()/2d,-p.getZ()), new Rotate(-Math.toDegrees(ang), 0, bone.length()/2d, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ()))); // add bone to scene root.getChildren().add(s2); // intersection if(sphereBounds.intersects(s.localToScene(s.getBoundsInLocal())) || sphereBounds.intersects(s.localToScene(s2.getBoundsInLocal()))){ touch.set(true); } }); if(touch.get()){ meshView[2].setMaterial(sphereMaterial); } else if(((PhongMaterial)meshView[2].getMaterial()).getSelfIlluminationMap()!=null){ meshView[2].setMaterial(material); } arms.stream() .filter(arm->arm.isValid()) .forEach(arm->{ final Vector p=arm.center(); // create bone as a cylinder and locate it at its center position Cylinder c=new Cylinder(arm.width()/2,arm.elbowPosition().minus(arm.wristPosition()).magnitude()); c.setMaterial(materialArm); // rotate the cylinder towards its direction final Vector v=arm.direction(); Vector cross = (new Vector(v.getX(),-v.getY(),-v.getZ())).cross(new Vector(0,-1,0)); double ang=(new Vector(v.getX(),-v.getY(),-v.getZ())).angleTo(new Vector(0,-1,0)); c.getTransforms().addAll( new Translate(200+p.getX(),-p.getY(),-p.getZ()), new Rotate(-Math.toDegrees(ang), 0, 0, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())) ); // add arm to scene root.getChildren().add(c); }); joints.stream() .forEach(joint->{ double length=joint.getV0().distanceTo(joint.getV1()); Cylinder c=new Cylinder(bones.get(0).width()/4,length); c.setMaterial(materialArm); final Vector p=joint.getCenter(); final Vector v=joint.getDirection(); Vector cross = (new Vector(v.getX(),-v.getY(),-v.getZ())).cross(new Vector(0,-1,0)); double ang=(new Vector(v.getX(),-v.getY(),-v.getZ())).angleTo(new Vector(0,-1,0)); c.getTransforms().addAll( new Translate(200+p.getX(),-p.getY(),-p.getZ()), new Rotate(-Math.toDegrees(ang), 0, 0, 0, new Point3D(cross.getX(),-cross.getY(),cross.getZ())) ); // add joint to scene root.getChildren().add(c); }); }); } }); scene.addEventHandler(MouseEvent.ANY, event -> { if (event.getEventType() == MouseEvent.MOUSE_PRESSED) { dragStartX = event.getSceneX(); dragStartY = event.getSceneY(); dragStartRotateX = cameraXRotate.getAngle(); dragStartRotateY = cameraYRotate.getAngle(); } else if (event.getEventType() == MouseEvent.MOUSE_DRAGGED) { double xDelta = event.getSceneX() - dragStartX; double yDelta = event.getSceneY() - dragStartY; cameraXRotate.setAngle(dragStartRotateX - (yDelta*0.7)); cameraYRotate.setAngle(dragStartRotateY + (xDelta*0.7)); } }); primaryStage.setTitle("Skeletal Tracking with Leap Motion v2 and JavaFX 3D"); primaryStage.setScene(scene); primaryStage.show(); } @Override public void stop(){ controller.removeListener(listener); } public static void main(String[] args) { launch(args); } final static float minX = -10; final static float minY = -10; final static float maxX = 10; final static float maxY = 10; public TriangleMesh buildTriangleMesh(int subDivX, int subDivY, float scale, boolean planeXY) { final int pointSize = 3; final int texCoordSize = 2; // 3 point indices and 3 texCoord indices per triangle final int faceSize = 6; int numDivX = subDivX + 1; int numVerts = (subDivY + 1) * numDivX; float points[] = new float[numVerts * pointSize]; float texCoords[] = new float[numVerts * texCoordSize]; int faceCount = subDivX * subDivY * 2; int faces[] = new int[faceCount * faceSize]; // Create points and texCoords for (int y = 0; y <= subDivY; y++) { float dy = (float) y / subDivY; double fy = (1 - dy) * minY + dy * maxY; for (int x = 0; x <= subDivX; x++) { float dx = (float) x / subDivX; double fx = (1 - dx) * minX + dx * maxX; int index = y * numDivX * pointSize + (x * pointSize); points[index] = (float) fx * scale; if(planeXY){ points[index + 1] = (float) fy * scale; points[index + 2] = 0.0f; } else { points[index + 2] = (float) fy * scale; points[index + 1] = 0.0f; } index = y * numDivX * texCoordSize + (x * texCoordSize); texCoords[index] = dx; texCoords[index + 1] = dy; } } // Create faces for (int y = 0; y < subDivY; y++) { for (int x = 0; x < subDivX; x++) { int p00 = y * numDivX + x; int p01 = p00 + 1; int p10 = p00 + numDivX; int p11 = p10 + 1; int tc00 = y * numDivX + x; int tc01 = tc00 + 1; int tc10 = tc00 + numDivX; int tc11 = tc10 + 1; int index = (y * subDivX * faceSize + (x * faceSize)) * 2; faces[index + 0] = p00; faces[index + 1] = tc00; if(planeXY){ faces[index + 2] = p10; faces[index + 3] = tc10; faces[index + 4] = p11; faces[index + 5] = tc11; } else { faces[index + 2] = p11; faces[index + 3] = tc11; faces[index + 4] = p10; faces[index + 5] = tc10; } index += faceSize; faces[index + 0] = p11; faces[index + 1] = tc11; if(planeXY){ faces[index + 2] = p01; faces[index + 3] = tc01; faces[index + 4] = p00; faces[index + 5] = tc00; } else { faces[index + 2] = p00; faces[index + 3] = tc00; faces[index + 4] = p01; faces[index + 5] = tc01; } } } TriangleMesh triangleMesh = new TriangleMesh(); triangleMesh.getPoints().addAll(points); triangleMesh.getTexCoords().addAll(texCoords); triangleMesh.getFaces().addAll(faces); return triangleMesh; } }