Creating an Adjustable Carousel Primitive

Intro

Typically carousels are created by assembling single planes in a cylindrical configuration stacking rows as needed. But there is another way to do this. You can create a single primitive which constructs all the planes internally. Handling a single primitive requires fewer calculations to manipulate it than handling multiple planes. And you can do more with it.

To see a demo click on the image below. Try clicking on the images they are interactive.

Single Primitive Carousel

Single Primitive Carousel

Just as in the case of the cube primitive, the carousel primitive can be manipulated as a single primitive. And the number row and column numbers are adjustable.

Web Stuff

Demo: http://professionalpapervision.com/demos/web/carousel/

Download (CarouselPrimitive)
http://code.google.com/p/flex3cookbook2/downloads/list

YouTube How to Video:

How it works

The carousel constructor function shown below gives you the ability to control the horizontal and vertical gap size, number of rows and columns, height and width, segments, and material list.

public function Carousel( materials:MaterialsList, myGap:Number=0, myHGap:Number=0 ,horizNum:int=1, vertiNum:int=1, width:Number=0, height:Number=0, segmentsW:Number=0, segmentsH:Number=0, initObject:Object=null )

Primitive Code

The carousel primitive was created by duplicating the Clickable Button Panel Primitive found in this blog and renaming the class and constructor function to Carousel. Then after adding the appropriate variables, the simple equation for a cylinder was used to place the primitives according to the angle and number created by the horizNum (number of images in a circular row).

var x :Number = myX*Math.cos(myThetaIs)+myZ*Math.sin(myThetaIs);

var z :Number = -myX*Math.sin(myThetaIs)+myZ*Math.cos(myThetaIs);

var y :Number = iy * iH -(textureY+myHGap/2)*(vertiNum/2-n);

var vertex:Vertex3D = new Vertex3D();

vertex[ “x” ] = x;
vertex[ “y” ] = y;
vertex[ “z” ] = z*10;

As the different x, y, and z values are created they are put into the vertex array just as was done in creating the clickable button panel. But notice the term vertex[ “z” ] = z*10; which has a multiplier of 10. This is an issue that must be investigated in the future.

The next issue was changing how the material was named. For the clickable button panel we used a matrix numbering scheme for the material name. We change this scheme when dealing with the carousel primitive to produce a sequential numbering system. This made the images easier to work with and the code is shown below.

var myNumber:Number= horizNum*n+m;
var myMaterial:String = “myMat”+myNumber;

Main Code

Since the carousel contains many images, we do not want to have to list them individually for the material list as we did in the Button Panel case. So we put the image address in a myBitmaps array and iterate through them as shown below to create the materials list.

var materialsList:MaterialsList = new MaterialsList();

for (var i:int = 0; i<myBitmaps.length; i++)
{

var myBitmapFileMaterial:BitmapFileMaterial = new BitmapFileMaterial(myBitmaps[i]);
trace(myBitmaps[i]);
myBitmapFileMaterial.doubleSided = true;

myBitmapFileMaterial.interactive = true;
myBitmapFileMaterial.name=”myMat”+i;

myBitmapFileMaterials.push(myBitmapFileMaterial);
trace(myBitmapFileMaterial.name);

}

for (var ik:int = 0; ik<myBitmaps.length; ik++)
{

materialsList.addMaterial(myBitmapFileMaterials[ik], “myMat” + ik);

}

This is a real time/code saver and can be used to bring in items from an external data base or xml page using HTTP Services.

The Big Problem

In Papervision, pixel measurements lose their meaning due to the 1/z parameter of perspective and as result you end up doing some adjusting. The is true for the carousel. If you change the gap or height components of the Carousel function you must readjust the following parameters to get the carousel to go to the correct image when clicking on it.

var myAdjustLift:Number=35;
var myAdjustHeight:Number=70;
var myLift:Number=myAdjustLift*(plane.vertiNum-1);
var myHeight:Number=myAdjustHeight*(plane.vertiNum);

The myLift parameter adjust the place where you enter the image close up and the myHeight parameter adjusts the individual column spacings entry point. Once you set the parameters above you can change row and column numbers without further adjustments. But if you change vertical gap or image height you need to readjust the parameters above.

There is a nonlinear equation that governs this behavior, but that is the subject of another post…

To see the full Carousel class click the more button below.

package org.lively3d.objects.primitives {

import org.papervision3d.Papervision3D;
import org.papervision3d.core.geom.*;
import org.papervision3d.core.geom.renderables.Triangle3D;
import org.papervision3d.core.geom.renderables.Vertex3D;
import org.papervision3d.core.math.NumberUV;
import org.papervision3d.core.proto.*;
import org.papervision3d.materials.utils.MaterialsList;

public class Carousel extends TriangleMesh3D
{
/**
* Number of segments horizontally. Defaults to 1.
*/
public var segmentsW :Number;

/**
* Number of segments vertically. Defaults to 1.
*/
public var segmentsH :Number;

public var myGap:Number;

public var myHGap:Number;

public var horizNum:int;
public var vertiNum:int;

public var width:Number;
public var height:Number;

/**
* Default size of Plane if not texture is defined.
*/
static public var DEFAULT_SIZE :Number = 500;

/**
* Default size of Plane if not texture is defined.
*/
static public var DEFAULT_SCALE :Number = 1;

/**
* Default value of gridX if not defined. The default value of gridY is gridX.
*/
static public var DEFAULT_SEGMENTS :Number = 1;

// ___________________________________________________________________________________________________
// N E W
// NN NN EEEEEE WW WW
// NNN NN EE WW WW WW
// NNNNNN EEEE WWWWWWWW
// NN NNN EE WWW WWW
// NN NN EEEEEE WW WW

/**
* Create a new Plane object.
* <p/>
* @param material A MaterialObject3D object that contains the material properties of the object.
* <p/>
* @param width [optional] – Desired width or scaling factor if there’s bitmap texture in material and no height is supplied.
* <p/>
* @param height [optional] – Desired height.
* <p/>
* @param segmentsW [optional] – Number of segments horizontally. Defaults to 1.
* <p/>
* @param segmentsH [optional] – Number of segments vertically. Defaults to segmentsW.
* <p/>
* @param initObject [optional] – An object that contains user defined properties with which to populate the newly created GeometryObject3D.
* <p/>
* It includes x, y, z, rotationX, rotationY, rotationZ, scaleX, scaleY scaleZ and a user defined extra object.
* <p/>
* If extra is not an object, it is ignored. All properties of the extra field are copied into the new instance. The properties specified with extra are publicly available.
*/
public function Carousel( materials:MaterialsList, myGap:Number=0, myHGap:Number=0 ,horizNum:int=1, vertiNum:int=1, width:Number=0, height:Number=0, segmentsW:Number=0, segmentsH:Number=0, initObject:Object=null )
{
super( materials.getMaterialByName( “all” ), new Array(), new Array(), null, initObject );

this.materials = materials;

this.segmentsW = segmentsW || DEFAULT_SEGMENTS; // Defaults to 1
this.segmentsH = segmentsH || this.segmentsW; // Defaults to segmentsW

this.myGap=myGap;
this.myHGap=myHGap;

this.horizNum=horizNum;
this.vertiNum=vertiNum;

this.width=width;
this.height=height;

var scale :Number = DEFAULT_SCALE;

if( ! height )
{
if( width )
scale = width;

if( material && material.bitmap )
{
width = material.bitmap.width * scale;
height = material.bitmap.height * scale;
}
else
{
width = DEFAULT_SIZE * scale;
height = DEFAULT_SIZE * scale;
}
}

buildMultiPlane(myGap,horizNum,vertiNum, width, height );

}

private function buildMultiPlane(myGap:Number, horizNum:Number, vertiNum:Number, width:Number, height:Number ):void
{

for(var n:int=0; n <vertiNum; n++){

for(var m:int=0; m <horizNum; m++){
var myNumber:Number= horizNum*n+m;
var myMaterial:String = “myMat”+myNumber;

var myDegrees:Number=m*360/horizNum;

trace(“myMat”+myNumber);

buildPlane( myMaterial, myGap, m, n, horizNum, vertiNum, width, height, myDegrees) ;
trace(“makingPlane”+n);
}
}

mergeVertices();

this.geometry.ready = true;

if(Papervision3D.useRIGHTHANDED)
this.geometry.flipFaces();

}

private function buildPlane(myMaterial:String, myGap:Number, m:int, n:int, horizNum:int, vertiNum:int, width:Number, height:Number, myDegrees:Number ):void
{

var matInstance:MaterialObject3D;
if( ! (matInstance= materials.getMaterialByName( myMaterial )))
{
if(!(matInstance=materials.getMaterialByName( “all” ))){
Papervision3D.log( “MultiPlane: Required material not found in given materials list. Supported materials are: front, back, right, left, top, bottom & all.” );
return;
}
}

var gridX :Number = this.segmentsW;
var gridY :Number = this.segmentsH;
var gridX1 :Number = gridX + 1;
var gridY1 :Number = gridY + 1;

var vertices :Array = this.geometry.vertices;
var faces :Array = this.geometry.faces;
var planeVerts :Array = new Array();

var textureX :Number = width;
var textureY :Number = height;

var iW :Number = width / gridX;
var iH :Number = height / gridY;

var myRadius:Number =horizNum*(width+myGap)/(2*Math.PI);

var myThetaIs:Number = myDegrees*Math.PI/180;

//var pythRadius:Number=Math.sqrt(myRadius*myRadius+(ix * iW -(textureX/2)*(horizNum/2))*(ix * iW -(textureX/2)*(horizNum/2)));

// Vertices
for( var ix:int = 0; ix < gridX + 1; ix++ )
{
for( var iy:int = 0; iy < gridY1; iy++ )
{

//var myX:Number=ix * iW -(textureX/2)*(horizNum/2);
var myX:Number=(ix – gridX/2 )* iW ;

var myZ:Number=myRadius;

var x :Number = myX*Math.cos(myThetaIs)+myZ*Math.sin(myThetaIs);

var z :Number = -myX*Math.sin(myThetaIs)+myZ*Math.cos(myThetaIs);

//var x :Number = ix * iW -(textureX+myGap/2)*(horizNum/2-m);

trace(myDegrees);

var y :Number = iy * iH -(textureY+myHGap/2)*(vertiNum/2-n);

var vertex:Vertex3D = new Vertex3D();

vertex[ “x” ] = x;
vertex[ “y” ] = y;
vertex[ “z” ] = z*10;

vertices.push( vertex );
planeVerts.push( vertex );

/*

x = rcosthetasinphi
y = rsinthetasinphi
z = rcosphi

x = rcostheta
y = rsintheta
z = z
However, y is z and z is y

*/

}
}

// Faces
var uvA :NumberUV;
var uvC :NumberUV;
var uvB :NumberUV;

for( ix = 0; ix < gridX; ix++ )
{
for( iy= 0; iy < gridY; iy++ )
{
// Triangle A
var a:Vertex3D = planeVerts[ ix * gridY1 + iy ];
var c:Vertex3D = planeVerts[ ix * gridY1 + (iy+1) ];
var b:Vertex3D = planeVerts[ (ix+1) * gridY1 + iy ];

uvA = new NumberUV( ix / gridX, iy / gridY );
uvC = new NumberUV( ix / gridX, (iy+1) / gridY );
uvB = new NumberUV( (ix+1) / gridX, iy / gridY );

faces.push(new Triangle3D(this, [ a, b, c ], matInstance, [ uvA, uvB, uvC ] ) );

// Triangle B
a = planeVerts[ (ix+1) * gridY1 + (iy+1) ];
c = planeVerts[ (ix+1) * gridY1 + iy ];
b = planeVerts[ ix * gridY1 + (iy+1) ];

uvA = new NumberUV( (ix+1) / gridX, (iy+1) / gridY );
uvC = new NumberUV( (ix+1) / gridX, iy / gridY );
uvB = new NumberUV( ix / gridX, (iy+1) / gridY );

faces.push(new Triangle3D(this, [ a, b, c ], matInstance, [ uvA, uvB, uvC ] ) );
}
}

}
}
}

9 Responses to Creating an Adjustable Carousel Primitive

  1. saul says:

    I think you are developing some great work; your book is going to be a great success.

    Great work and thanks for the share.

  2. larry says:

    Hi Mike: I am trying to run the CP demo on another computer and it shows all black for the images. In Explorer, the .swf files are not associated with Flash, even after updating to Flash 10 on the target. Google indicates I may have issues in the AC_OETags.js file.

    Is this a common problem? It plays great on my laptop, where .swf is associated with Flash.

    TIA,

    Larry

  3. larry says:

    Yes, the PV3D is the same on all PCs. I took the whole Flex project directory and moved it to the other computer(s.) I have tried it on three systems. Two do not show Flash as the app associated with .swf; one does. In all cases, the pictures do not display. I even recoded the demo to use “./assets” instead of “assets” in the jpeg materials list. No change.

    A bigger problem/question is how to make the phtos face out from the carousel instead of in. I would like the text to be normal when they click an item, not reversed. I can reverse the images, but I’m sure it is a property of the carousel primitive, right?

    Larry

    • Mike Lively says:

      If your images are black (with purple lines), you are not referencing them correctly. Also, be careful with photoshop – certain png exports will not work in PV3D.

  4. larry says:

    I reversed the images, solving the face-in/face-out issue.

    Next Issue: Smooth scroll to clicked image vs. helter-skelter scrool. Sometimes when I click an image, it does a nice smooth minimal rotation scroll to the enlarged view. At other times, it seems to rotate the carousel 360-x degrees vs. x degrees. What controls this? When it takes the long way around it is not as nice visually.

    As always, TIA.

    Larry

  5. larry says:

    Have you experimented with .flv on the carousel?
    Good example here:
    http://actionscriptarchitect.com/lab/Video3D/

    I haven’t found source yet, so not sure how to adapt Carousel Primitive to handle a mixture of video and stills.

    Larry

  6. larry says:

    Do you have any examples of making the carousel objects interactive? E.g. when you click a picture to bring it to the front, it would be nice to be able to display buttons on the larger image. I am especially interested in displaying buttons to launch a form associated with a given still image in the carousel.

    Thanks,

    larry

Leave a reply to larry Cancel reply