Mostrando entradas con la etiqueta script. Mostrar todas las entradas
Mostrando entradas con la etiqueta script. Mostrar todas las entradas

jueves, 19 de marzo de 2009

Timelines de duración variable y el at(..)


Uno de los inconvenientes de trabajar en un ambientea tan nuevo como el JavaFX script, es que no abundan las fuentes de información.

Una de las cosas que no tenía claras (seguramente porque no investigué lo suficiente, o no miré mejor los ejemplos), era cómo se hace que un timeline tenga duración variable.

Bueno, ahora descubrí que diablos es el at(..) en los timeline y cómo se hace para que la duración sea variable.

Primera cosa, cuando uno usa at(), en realidad está usando una "abreviatura" en la manera de definir un timeline. La idea es que sólo sea una opción breve para escribir el código, pero por lo mismo, no es la forma más "potente".

Tomemos este ejemplo:

var x:Number;
Timeline {
repeatCount: 1
keyFrames: [
at (0s) { x => 0.0}
at (3s) { x => 100.0}
]
}.play();

En él, definimos una variable x, que al segundo 0s -de iniciado el timeline- asume un valor de 0.0, luego, x asumirá gradualmente los valores entre 0.0 y 100.0, alcanzando el máximo valor (100.0) en 3s segundos.

El asunto que me faltaba comprender era... ¿como cambiaba el tiempo en at(3s)?, porque no acepta ni variables ni bind dentro de los paréntesis.

Pues resulta que con at(..) no lo vas a conseguir (por lo menos eso entiendo en este momento), la forma correcta y potente de usar las lineas de tiempo es esta:


var dur=3s;
var x:Number;
Timeline {
repeatCount: 1
keyFrames : [
KeyFrame {
time : 0s
values: [x=>0]
},
KeyFrame {
time: bind dur
values: [x=>100]
}
]
}.play();

De hecho, esta forma de definir un timeline permite anidar timelines o agregar un action: en cada KeyFrame, de manera que una acción sea ejecutada cada vez que una etapa de la animación o transición se completa.


var dur=3s;
var dur2=5s;
var x:Number;
Timeline {
repeatCount: 1
keyFrames : [
KeyFrame {
time : 0s
values: [x=>0]
},
KeyFrame {
time: bind dur
canSkip: true
values: [x=>50]
action: function() {
println("completo etapa 1");
};
},
KeyFrame {
time: bind dur2
canSkip: true
values: [x=>200]
action: function() {
println("completo etapa 2");
};
}
]
}.play();

miércoles, 18 de marzo de 2009

Un "acordeón" de imágenes


Este componente aun no está terminado, pero ya lo pueden probar usando el link de más abajo.

Lanzar demo Java Web Start, CLICK AQUI

Código fuente componente y demo, CLICK AQUI


El código para crear un "Acordeón de imágenes" es el siguiente:

var accordion = ImagesAccordion{
width: 650,
height: 350,
lineWidth: 1.0,
lineColor: Color.WHITE
images: [
ImageItem {
id: "moais",
caption: "Moais"
image: Image { url: "{__DIR__}moais.jpg"
}
message: "Easter Island (Rapa Nui) is a Polynesian island in the "
"southeastern Pacific Ocean, The island is a special "
"territory of Chile. Easter Island is famous for its "
"monumental statues, called moai."
messageArea: Rectangle2D {
minX: 30,
minY: 253,
width: 350,
height: 87
}
call: click
},
ImageItem {
id: "anakena",
caption: "Anakena"
image: Image { url: "{__DIR__}anakena.jpg"
}
message: "‘Anakena is a white coral sand beach in Rapa Nui..."
messageArea: Rectangle2D {
minX: 20,
minY: 233,
width: 360,
height: 107
}
messageFont: Font { size: 12
}
call: click
},
ImageItem {
id: "glacier",
caption: "Grey glacier"
image: Image { url: "{__DIR__}grey.jpg"
}
message: "The Glacier is in the south end..."
messageArea: Rectangle2D {
minX: 30,
minY: 270,
width: 320,
height: 70
}
call: click
},
ImageItem {
id: "torres",
caption: "Torres del Paine"
image: Image { url: "{__DIR__}paine.jpg"
}
message: "Spectacular mountain group in Torres del Paine..."
messageArea: Rectangle2D {
minX: 30,
minY: 270,
width: 320,
height: 70
}
call: click
},
ImageItem {
id: "salar",
caption: "Atacama desert"
image: Image { url: "{__DIR__}salar.jpg"
}
message: "The Atacama Desert is a virtually rainless plateau in Chile..."
messageArea: Rectangle2D {
minX: 20,
minY: 237,
width: 380,
height: 103
}
// messageFont: Font { size: 12 }
call: click
},
/* ImageItem {
id: "crayons",
caption: "Crayones"
message: "Recuerdos de la niñez"
image: Image { url: "{__DIR__}crayons.jpg"}
call: click
},*/
ImageItem {
id: "chaiten",
caption: "Chaiten"
image: Image { url: "{__DIR__}chaiten.jpg"
}
message: "Hell on Earth. Chaitén is a volcanic caldera..."
messageArea: Rectangle2D {
minX: 20,
minY: 270,
width: 420,
height: 70
}
call: click
}
]
effect: Reflection { fraction: 0.1,
}

};

function click(image:ImageItem) {
println(image.id);
}

lunes, 16 de marzo de 2009

¿Comó aplicar más de un efecto a un nodo?


Si por ejemplo, se desea hacer difusa una imagen y al mismo tiempo reducir su brillo.

El "truco" está en que casi todos los efectos tienen un INPUT. Eso permite anidar los efectos.


var img = ImageView {
image: Image { url: "{__DIR__}crayons.jpg"},
effect:
GaussianBlur {
input:
ColorAdjust{
brightness: -0.5
}
}
};

lunes, 23 de febrero de 2009

Cómo crear una aplicación móvil con JavaFX y Netbeans 6.5


Este video muestra lo sencillo que es crear y probar aplicaciones móviles desarrolladas en JavaFX script con Netbeans 6.5.

Básicamente, sólo tienes que crear una aplicación y luego indicar que la vas a ejecutar con el emulador.

miércoles, 4 de febrero de 2009

Curso gratuito de JavaFX Script, de 15 semanas de duración


A partir del 30 de Enero pasado, se inició un curso gratuito por internet, para aprender JavaFX Script. Una vez por semana, los autores (Jim Weaver** & Sang Shin) publican el material necesario para experimentar y estudiar el lenguaje.

Solo tienen que mirar una vez por semana este sitio, al final del curso los participantes recibirán un diploma por su participación (aunque simbólico mas que nada, porque no es respaldado por ninguna institución):

JavaFX: un detalle importante sobre como se retornan valores


Si se han fijado, muchas veces nos encontramos con cosas como esta en el código de una aplicación JavaFX:

function figura():Node {
Group {
content: Line { startX:0,
startY:0, endX:100,
endY:100 }
}
};


Ahora, según la definición de la función, esta debe retornar un NODO (un objeto que puede ser una figura geométrica o una imagen, por ejemplo). Pero ese código, nunca ejecuta "explícitamente" un return.

Lo que sucede, es que JavaFX Script asume (salvo que exista explícitamente una instrucción RETURN), que la última expresión evaluada es lo que se retorna. En el caso del código (arriba), retornaría el Group.

El código escrito de manera más "formal" podría ser este:

function figura():Node {
var objeto = Group {
content: Line { startX:0,
startY:0, endX:100,
endY:100 }
};
return objeto;
};


Ahora, esta flexibilidad no deja de tener su encanto al poder por ejemplo escribir algo como:

var resultado:Number = if (a==b)
then { a+b/c } else { d/e+f };


... o poder decribir el cuerpo de una función como:

function calculo(a:Number,
b:Number):Number {

a + 1000 / b

};


En realidad, es un detalle que no deja de ser comodo y que hace más comprensible y breve el código... quizas más natural (siendo siempre opcional, y teniendo siempre la posibilidad de ser mas "formal" al codificar).

Ojalá esta explicación ayude a que comprendan mejor el código de algunos scripts.

lunes, 2 de febrero de 2009

JavaFX: Menú flotante (popup menu)


Este código utiliza un menú flotante que implemente yo. Es un trabajo en progreso, así que tiene un par de detalles pendientes... pero, por ahora, quizás le puede servir a alguien para su aplicación o para aprender como funciona...



Como he dicho, estoy aprendiendo JavaFX script, así que es probable que haga cosas de maneras inapropiadas, no se crean que soy un experto! ;) ... aun... en todo caso, implementar el menú flotante me tomó sólo un par de horas desde que comencé (de cero), para que vean lo productivo que puede ser este lenguaje.

Por cierto, se pueden crear menús flotantes sin tanta configuración, sólo son necesarias las opciones (el texto de la opción [text:] y la función a las que están asociadas [call:]), las demás configuraciones son opcionales... y ojo, que hay bastantes opciones por probar y combinar.... por cierto, JavaFX script permite hacer combinaciones como el Timeline que agregué para que cambie el color del borde del menú flotante. Quizás puedes experimentar agregándole un Interpolador de resorte para que se vea más simpático al aparecer o elegir una opción, o que el menú aparezca rotando cuando es lanzado, etc.

Aquí está el proyecto Netbeans 6.5 completo (Download the complete Netbeans project here, alpha version).

package popupmenu;

import javafx.stage.Stage;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.animation.*;
import javafx.scene.input.*;


var screenWidth=500.0;
var screenHeight=500.0;

// Primer menu flotante
var popupmenu=popupMenu{

corner:20
padding:8
borderWidth:4
borderColor:bind dynamicColor

// opciones del menu
content: [
menuItem { text:"Say Hello!", call: hello },
menuItem { text:"Again, say Hello!", call: again },

//menuSeparator { },
menuItem { text:"Say Bye!", call: bye }
]

};

// Timeline anima el borde del primer menu flotante
var dynamicColor:Color;
Timeline {
repeatCount:Timeline.INDEFINITE
autoReverse:true
keyFrames: [
at (0s) {dynamicColor => Color.YELLOW }
at (0.5s) {dynamicColor => Color.DARKBLUE}
at (1s) {dynamicColor => Color.LIGHTSALMON}
]
}.play();


// Segundo menu flotante
var popupCircle=popupMenu{

font: Font { size:20 };
gradient: LinearGradient { proportional: false
startX: 125.0, startY: 0.0, endX: 225.0, endY: 0.0
stops: [ Stop { offset: 0.0 color: Color.BLACK },
Stop { offset: 1.0 color: Color.RED } ] }
fill:Color.TRANSPARENT
stroke:Color.WHITE
highlight: Color.YELLOW
verticalSpacing:10

// opciones del menu
content: [
menuItem { text:"Multiline options\n"
"for your menu!", call: helloCircle },
menuItem { text:"This is a second\n"
"multiline option!", call: helloCircle },
]

};

// Tercer menu flotante
var popupmenu2=popupMenu{

font: Font { size: 18, name:"Verdana", embolden:true };
gradient: LinearGradient { proportional: true
stops: [ Stop { offset: 0.0 color: Color.GRAY },
Stop { offset: 1.0 color: Color.WHITE } ] }
fill:Color.TRANSPARENT
corner:20
padding:16
verticalSpacing:6
rotation:-25
highlight:Color.WHITE
highlightStroke:Color.RED

// opciones del menu
content: [
menuItem { text:"Say Hello!", call: hello },
menuItem { text:"Again, say Hello!", call: again },
menuItem { text:"Say Bye!", call: bye }
]

};


// funciones asignada a menus
function hello(e:MouseEvent):Void {
println("hello {e.x},{e.y}");
};

function helloCircle(e:MouseEvent):Void {
println("hello Circle {e.x},{e.y}");
};

function again(e:MouseEvent):Void {
println("hello, again {e.x},{e.y}");
};

function bye(e:MouseEvent):Void {
println("bye! {e.x},{e.y}");
};


Stage {
title: "Application title"
width: bind screenWidth with inverse
height: bind screenHeight with inverse
scene: Scene {
content: [
// agrega un fondo a la ventana
Rectangle {
fill: Color.BLUE
width: bind screenWidth
height: bind screenHeight
// le asigna un menu flotante
onMousePressed: function(e) {
popupmenu.event=e;
}
},
// crea un circulo
Circle {
// bloquea el click para que no dispare el menu flotante del fondo
blocksMouse:true
centerX: 100,
centerY: 100
radius: 40
fill: Color.RED
// le asigna un menu flotante
onMousePressed: function(e) {
popupCircle.event=e;
}
},
// crea un segundo circulo
Circle {
// bloquea el click para que no dispare el menu flotante del fondo
blocksMouse:true
centerX: 300,
centerY: 300
radius: 80
fill: Color.YELLOW
// le asigna un menu flotante
onMousePressed: function(e) {
popupmenu2.event=e;
}
},
// incluye los menus flotantes
popupmenu,
popupmenu2,
popupCircle
]
}
}

miércoles, 28 de enero de 2009

Mi primer juego en JavaFX: Alunizaje


Al juego le falta un par de detalles, como que la nave aparezca inicialmente en cualquier area de la pantalla. Por ahora aparece al centro a una velocidad y dirección aleatoria. También me falta agregar que la "base" de alunizaje tenga una posición aleatoria y que detecte cuando aterriza fuera de ella. Son detalles menores que quizas hasta puedes terminar tú.



El juego usa 4 teclas. La barra de espacio, para encender el motor principal. Las teclas de flecha hacia izquierda y derecha, que encienden los motos auxiliares para inclinar el módulo, y la tecla con la flecha hacia arriba, que enciende el piloto automático, para frenar la rotación del módulo (automáticamente).

Está bastante realista, los retrocohetes se encienden cuando corresponde y dejan de operar cuando no hay mas combustible. También incluye alarmas visuales y sonoras para cuando se excede la velocidad de aterrizaje, la inclinación del módulo es incorrecta cuando esta cerca de alunizar o cuando el combustible baja de 1/3 del estanque.

Me resultó entretenido hacer este juego y también descubrir LO INCREIBLEMENTE SIMPLE que es desarrollar cosas bastante decentes en tan poco tiempo (el grueso estaba operando en un par de horas... ¡y eso que estoy aprendiendo JavaFX script!).

La facilidad de incluir gráficos desde Adobe Illustrator sin duda es una ventaja inmensa. Especialmente, porque puede ser reemplazada por el trabajo de un diseñador más experto, sin necesidad de tocar el código de JavaFX script ya desarrollado. De hecho, con poco trabajo adicional podría agregarse multiples "skins" o apariencias al juego... escenarios de distintos planetas y naves por ejemplo (pudiendo relocalizar las alarmas, botones, formas de la nave, motores, entorno, etc).

El proyecto Netbeans 6.5 completo está disponible aquí (código fuente, gráfica, sonidos, etc).

El código requiere que esté incluida la libreria JavaFX FXD 1.0 en el proyecto.

sábado, 6 de diciembre de 2008

JavaFX: La rueda de la fortuna


Este ejemplo genera una "rueda" de colores de distinta intensidad y que gira... el ejemplo original viene incluido con Netbeans 6.5, yo sólo le agregué el giro. Pueden modificar la velocidad del giro cambiando el tiempo en el Timeline (0.5s, o medio segundo para una vuelta completa).

Básicamente, lo único que agregué fue un rotate: bind angle; asociado a la rueda ya generada incluida en la escena (Scene) y una "linea de tiempo" (Timeline) que incrementa el ángulo entre 0º y 360º, en el tiempo especificado. Una vez que llega a 360º, comienza por el ángulo 0º. Así queda la impresión que la ruega gira indefinidamente.



package color;

import javafx.scene.Group;
import javafx.scene.CustomNode;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.animation.Timeline;
import javafx.lang.FX;

class ColorWheelNode extends CustomNode {

var segments : Number = 12;
var steps : Number = 6;
var radius : Number = 95;
var stripes : Arc[];

override function create() : Node {
return Group {
content: bind stripes
};
}

init {
// Set radius
var r = radius;
var rStep = radius / steps;
var colors : Color[];
for( i in [1..
segments + 1] ) {
insert Color.rgb( 255, 255, 0 ) into colors;
}
for( i in [0..
steps - 1] ) {
colors[1] = Color.rgb( 255 - ( 255 / steps * i ), 255 - ( 255 / steps * i ), 0 );
colors[2] = Color.rgb( 255 - ( 255 / steps ) * i, ( 255 / 1.5 ) - (( 255 / 1.5 ) / steps ) * i, 0 );
colors[3] = Color.rgb( 255 - ( 255 / steps ) * i, ( 255 / 2 ) - ( ( 255 / 2 ) / steps ) * i, 0 );
colors[4] = Color.rgb( 255 - ( 255 / steps ) * i, ( 255 / 2.5 ) - (( 255 / 2.5 ) / steps ) * i, 0 );
colors[5] = Color.rgb( 255 - ( 255 / steps ) * i, 0, 0 );
colors[6] = Color.rgb( 255 - ( 255 / steps ) * i, 0, ( 255 / 2 ) - (( 255 / 2 ) / steps ) * i );
colors[7] = Color.rgb( 255 - ( 255 / steps ) * i, 0, 255 - ( 255 / steps ) * i );
colors[8] = Color.rgb(( 255 / 2 ) - (( 255 / 2 ) / steps ) * i, 0, 255 - ( 255 / steps ) * i );
colors[9] = Color.rgb( 0, 0, 255 - ( 255 / steps ) * i );
colors[10] = Color.rgb( 0, 255 - ( 255 / steps ) * i, ( 255 / 2.5 ) - (( 255 / 2.5 ) / steps ) * i );
colors[11] = Color.rgb( 0 , 255 - ( 255 / steps ) * i, 0 );
colors[12] = Color.rgb(( 255 / 2 ) - (( 255 / 2 ) / steps ) * i, 255 - ( 255 / steps ) * i, 0 );
for( j in [1..segments] ) {
var c = colors[
j.intValue()];
insert Arc {
centerX: 100,
centerY: 100
radiusX: r ,
radiusY: r
startAngle: 360 / segments * ( segments - j - 0.5)
length: 360 / segments
fill: c
type: ArcType.ROUND
} into stripes;
}
r -= rStep;
}
}
}


var angle:Integer;

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: false
keyFrames: [
at (0s) {angle => 0},
at (0.5s) {angle => 360},
]
}.play();



Stage {
scene: Scene {
fill: Color.GRAY
content: ColorWheelNode {
rotate: bind angle;
}
}

width: 216
height: 232
title: "Color Wheel"
}

JavaFX: Pelota 3D con sombra rebotando sobre video


Ok, solo para que vayan descubriendo el poder de JavaFX. Ayer yo no sabía NADA de JavaFX script. Luego de jugar unas pocas horas, ya tengo este ejemplo: Una "habitación" que tiene en el piso un VIDEO y sobre el cual rebota una bola de apariencia 3D, que incluso se aleja del frente de la pantalla y que proyecta una sombra que varia tanto en intensidad como tamaño dependiendo de la altura a la que se encuentra la bola.



Por ahora sólo les dejo el código. Más adelante postearé otro artículo con la explicación del mismo.

Atención: este ejemplo requiere el mediacomponent.jar entre los Libraries del proyecto. Si crean un proyecto demo basado en el SimpleMediaPlayer (incluido con Netbeans 6.5) y luego reemplanzan el código de Main.fx, la biblioteca mediacomponent.jar quedará incluida en el proyecto automáticamente. Si no, tienen que buscar donde se encuentra en su disco la biblioteca e incluirla entre los libraries del proyecto haciendo click con el botón derecho del mouse en Libraries (a la izquierda en Netbeans) y seleccionado Add Library... (ahí tienen que decirle donde está el archivo). Pero como les digo, lo más fácil es comenzar con un proyecto de ejemplo de mediaplayer y reemplazar el código de Main.fx por el código que incluyo a continuación:

package simplevideoplayer;

import javafx.stage.Stage;
import javafx.scene.Scene;
import com.sun.fxmediacomponent.*;
import javafx.scene.Group;
import javafx.scene.shape.Circle;
import javafx.scene.paint.Color;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.paint.Stop;
import javafx.scene.effect.PerspectiveTransform;
import javafx.scene.shape.Ellipse;
import javafx.scene.paint.*;
import javafx.scene.effect.*;
import javafx.scene.transform.*;
import javafx.scene.shape.Line;


var mediaUrl:String = "http://sun.edgeboss.net/download/sun/media/1460825906/1460825906_2956241001_big-buck-bunny-640x360.flv";
if(FX.getArgument("mediaURL")!=null) {
mediaUrl = FX.getArgument("mediaURL").toString();
}

def vidWidth = 1024;
def vidHeight = 576;

var X=0;
var Y=1;
var Z=0;
var VX=230; // perspectiva horizontal para video
var VY=376; // perspectiva vertical para video
var S:Number;

Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {X => 100},
at (2.3s) {X => 924 tween Interpolator.LINEAR},
]
}.play();


Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {Y => 100},
at (0.5s) {Y => 476 tween Interpolator.EASEIN},
]
}.play();


Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {Z => 40},
at (10s) {Z => 100 tween Interpolator.LINEAR},
]
}.play();


Timeline {
repeatCount: Timeline.INDEFINITE
autoReverse: true
keyFrames: [
at (0s) {S => 0.5},
at (0.5s) {S => 1 tween Interpolator.EASEIN},
]
}.play();


var mediaBox:MediaComponent = MediaComponent {
// set the media and make the component visible
mediaSourceURL : mediaUrl
visible:true
// the position and size of the media on screen
translateX: 0
translateY: 0
mediaViewWidth : vidWidth
mediaViewHeight: vidHeight
// determines if the control bar is below the media or on top with a fade in
staticControlBar: false
// make the movie play as soon as it's loaded
mediaPlayerAutoPlay: true
// set the volume
volume: 1.0

effect: PerspectiveTransform {
ulx: bind VX uly: bind VY
urx: bind 1024-VX ury: bind VY
lrx: 1024 lry: 576
llx: 0 lly: 576
}
};


// Currently these variables have to be set after the component is created

// Description to be displayed when the mouse is over the component
mediaBox.mediaDescriptionStr = "A well-tempered rabbit finds his day spoiled by the rude actions of the...";
// Duration to be displayed when the mouse is over the component
mediaBox.mediaDurationStr = "9:56";
// Title to be displayed when the mouse is over the component
mediaBox.mediaTitleStr = "Big Buck Bunny";


Stage {

title: "Simple Media Player"

scene: Scene{

width: vidWidth
height: vidHeight

stylesheets: [
MediaComponent.css_skins
]

content: [

mediaBox,

Line {
strokeWidth: 3.0
stroke: Color.BLACK
startX: bind VX
startY: bind VY
endX: 0
endY: 576
}

Line {
strokeWidth: 3.0
stroke: Color.BLACK
startX: bind VX
startY: bind VY
endX: bind VX
endY: 0
}

Line {
strokeWidth: 3.0
stroke: Color.BLACK
startX: bind VX
startY: bind VY
endX: bind 1024-VX
endY: bind VY
}

Line {
strokeWidth: 3.0
stroke: Color.BLACK
startX: bind 1024-VX
startY: bind VY
endX: bind 1024-VX
endY: 0
}

Line {
strokeWidth: 3.0
stroke: Color.BLACK
startX: bind 1024-VX
startY: bind VY
endX: 1024
endY: 576
}


Ellipse {
centerX: 0, centerY: 0 radiusX: 100 radiusY: 30
fill: Color.BLACK
opacity: bind S - 0.2
fill: RadialGradient {
proportional: false radius: 100
centerX: 0 centerY: 0
focusX: 0 focusY: 0
stops: [
Stop { offset: 0.0 color: Color.web("#333333", 1.0) },
Stop { offset: 0.4 color: Color.web("#444444", 1.0) },
Stop { offset: 1.0 color: Color.web("#FFFFFF", 0.0) }
]
}
transforms: bind [
Transform.translate(X , 476 + Z - 100 + Z / 2)
Transform.scale(S * Z / 100.0, S * Z / 100.0)
]
}

Circle {
centerX: 0 centerY: 0 radius: 100
stroke: Color.BLACK
fill: RadialGradient {
proportional: false radius: 100
centerX: 0 centerY: 0
focusX: 20 focusY: -50
stops: [
Stop { offset: 0.0 color: Color.WHITE },
Stop { offset: 0.85 color: Color.RED },
Stop { offset: 1.0 color: Color.BLACK }
]
}
transforms: bind [
Transform.translate(X , Y + Z - 100)
Transform.scale(Z/100.0, Z/100.0)
]

}

]

}

}