Playing Video on the Sides of a Rotating Cube
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.
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.
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.
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.
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.
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.
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.
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
Sergey Malenkov