Playing Video on the Sides of a Rotating Cube

By Sergey Malenkov, November 5, 2008

This sample shows how to use JavaFX technology to rotate a cube that displays video on its faces. The sample enables the user to click a cube face to play video, or drag a face to rotate the cube.

Understanding the Code

The VideoCube class creates a 3-D cube that enables the user to rotate it with the mouse and to play video on the cube faces.

The Point class is shown in Figure 1. It represents a point in 3-D space and provides rotation functionality on a coordinate basis.

Source Code
class Point {
  var x: Number;
  var y: Number;
  var z: Number;

  function rotateX(cos, sin) {
    var tmp = cos * y - sin * z;
    z = cos * z + sin * y;
    y = tmp;
  }
  function rotateY(cos, sin) {
    var tmp = cos * x + sin * z;
    z = cos * z - sin * x;
    x = tmp;
  }
  function rotateZ(cos, sin) {
    var tmp = cos * x - sin * y;
    y = cos * y + sin * x;
    x = tmp;
  }
}
  

Figure 1: Point class to support 3D

The cube points are declared as shown in Figure 2. Note that the last four points are calculated automatically.

Source Code
def ful = Point { x:-r   y:-r   z: r }
def fur = Point { x: r   y:-r   z: r }
def flr = Point { x: r   y: r   z: r }
def fll = Point { x:-r   y: r   z: r }

def bul = Point { x: bind - ful.x   y: bind - ful.y   z: bind - ful.z }
def bur = Point { x: bind - fur.x   y: bind - fur.y   z: bind - fur.z }
def blr = Point { x: bind - flr.x   y: bind - flr.y   z: bind - flr.z }
def bll = Point { x: bind - fll.x   y: bind - fll.y   z: bind - fll.z }
  

Figure 2: Initialization of Cube Points

The rotation animation is shown in Figure 3. It recalculates coordinates every 40 ms to display 25 frames per second. The ax and ay variables define angles to resolve the cube on its x and y axes accordingly. Note that only the first four points should be updated.

Source Code
var ax =  0.002;   def cx = bind Math.cos(ax);   def sx = bind Math.sin(ax);
var ay = -0.006;   def cy = bind Math.cos(ay);   def sy = bind Math.sin(ay);

def rotation = Timeline {
  repeatCount: Timeline.INDEFINITE
  keyFrames: KeyFrame {
    time: 40ms
    action: function() {
      if (ax != 0) {
        ful.rotateX(cx, sx);
        fur.rotateX(cx, sx);
        flr.rotateX(cx, sx);
        fll.rotateX(cx, sx);
      }
      if (ay != 0) {
        ful.rotateY(cy, sy);
        fur.rotateY(cy, sy);
        flr.rotateY(cy, sy);
        fll.rotateY(cy, sy);
      }
    }
  }
}
rotation.play()
  

Figure 3: Recalculating 25 Times per Second

The PerspectiveTransform class is an effect that does not support translation of mouse coordinates. The polygon based on perspective transformation provides control functions, as shown in Figure 4. When the mouse button is clicked, the Open dialog box is shown. When the mouse is dragged, the rotation angles are updated.

Source Code
Polygon {
  points: bind [
    pt.ulx, pt.uly,
    pt.urx, pt.ury,
    pt.lrx, pt.lry,
    pt.llx, pt.lly
  ]
  cursor: Cursor.HAND
  blocksMouse: true
  onMouseClicked: function (event) {
    rotation.pause();
    if (JFileChooser.APPROVE_OPTION == chooser.showOpenDialog(null)) {
      source = chooser.getSelectedFile().toURI().toString()
    }
    rotation.play();
  }
  onMouseDragged: function(event) {
    ax = if (-5 < event.dragY and event.dragY < 5) then 0 else - event.dragY / 10000;
    ay = if (-5 < event.dragX and event.dragX < 5) then 0 else   event.dragX / 10000;
  }
}
  

Figure 4: Supporting Mouse Events

If the video is not opened, the simple animation is declared as shown in Figure 5. This group of nodes is transformed by using perspective transformation.

Source Code
Group {
  effect: pt
  visible: bind error
  content: [
    Rectangle {
      x: -100   width: 200
      y: -100   height: 200
      fill:   bind background
      stroke: bind foreground
      strokeWidth: 2
    }
    Circle { radius: 85   fill: bind foreground }
    Circle { radius: 80   fill: Color.web("#c7b668") }
    Circle { radius: 70   fill: Color.web("#645f37") }
    Circle { radius: 65   fill: bind background }
    Text {
      x: -34
      y:  40
      content: bind "{number}"
      fill: bind foreground
      font: Font { size: 120   embolden: true }
    }
    Line { startX: -80   endX: 80   stroke: bind foreground   strokeWidth: 2 }
    Line { startY: -80   endY: 80   stroke: bind foreground   strokeWidth: 2 }
    Arc {
      radiusX: 80
      radiusY: 80
      startAngle: bind start
      length: bind length
      type: ArcType.ROUND
      opacity: 0.2
    }
  ]
}
  

Figure 5: Animation on the Cube Face

If the video is opened, the media view plays it as shown in Figure 6. This node is transformed by using perspective transformation. Note that the volume depends on the face position.

Source Code
MediaView {
  effect: pt
  visible: bind not error
  mediaPlayer: MediaPlayer {
    media: bind media
    autoPlay: true
    repeatCount: MediaPlayer.REPEAT_FOREVER
    volume: bind if (z > 0)
            then 0.25 * z / r
            else 0
  }
}
  

Figure 6: Playing Transformed Video

Customizing the Code

The scene creation is shown in Figure 7. For each face you can change the order of used points. The changing of the points order enables you to rotate the video on the face or to reflect it like a mirror. The source attribute notifies that the video should be loaded automatically when the sample starts.

Source Code
scene: Scene {
  content: [
    Face { number: 1   ul: ful   ur: fur   lr: flr   ll: fll   source: "{__DIR__}VideoCube.avi" }
    Face { number: 2   ul: flr   ur: fur   lr: bll   ll: bul }
    Face { number: 3   ul: bur   ur: fll   lr: flr   ll: bul }
    Face { number: 4   ul: bll   ur: fur   lr: ful   ll: blr }
    Face { number: 5   ul: bur   ur: blr   lr: ful   ll: fll }
    Face { number: 6   ul: bur   ur: bul   lr: bll   ll: blr }
  ]
}
  

Figure 7: Creating the Scene