Papervision3D cube face orientation - actionscript-3

I have a question about Papervision3D, or perhaps bitmapData, I am unsure where the problem is. I have constructed a program that takes 4 banners and splits them into pieces and then applies those pieces to cubes so that I can make a banner rotator. So after I run my program I have 10 cubes with a piece of a banner on 4 faces(front, top, back, bottom) of each cube. The problem is that some of the faces are oriented incorrectly(spun 180 degrees). Is there a way in Papervision3D to spin a cube face? The other place I think the problem may be is when I create the bitmapData that will be applied to the cube faces. Is there some way to explicitly define orientation during bitmapData creation? Any help or ideas would be greatly appreciated. Thanks!
-------Edit----------
Here is the code for my CubeMaker.as class. This is the class that takes the image pieces and applies them to the cubes.
package {
import away3d.events.MaterialEvent;
import away3d.materials.BitmapMaterial;
import away3d.materials.ColorMaterial;
import away3d.materials.TransformBitmapMaterial;
import away3d.primitives.Cube;
import away3d.primitives.data.CubeMaterialsData;
import CubeEvent;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
public class CubeMaker extends Sprite {
private var _colorMaterial:ColorMaterial = new ColorMaterial(0x000000);
private var _dcRef;
private var _banners:Array;
private var _cubeArray:Array = [];
private var _cubeCoung:int = 0;
private var _numberOfPieces:int;
private var _width:Number;
private var _depth:Number;
private var _height:Number;
public function CubeMaker(banners:Array, numberOfPieces:int) {
_dcRef = DocumentClass._base;
_banners = banners;
_numberOfPieces = numberOfPieces;
}
public function makeCubes() {
//loop through the cubes
for (var i = 0; i < _numberOfPieces; i++) {
var faceBitmapArray:Array;
//fill array with four faces for current cube
faceBitmapArray = fillFace(i);
//get width and height from a piece instance
var width:Number;
var height:Number;
var tempArray:Array;
tempArray = _banners[0];
_width = tempArray[0].width;
_height = tempArray[0].height;
_depth = tempArray[0].height;
//create four materials from bitmapData
var myFront:TransformBitmapMaterial = new TransformBitmapMaterial(faceBitmapArray[0], {rotation:0} );
var myTop:TransformBitmapMaterial = new TransformBitmapMaterial(faceBitmapArray[1], {rotation:0.5} );
var myBack:TransformBitmapMaterial = new TransformBitmapMaterial(faceBitmapArray[2], {rotation:0} );
var myBottom:TransformBitmapMaterial = new TransformBitmapMaterial(faceBitmapArray[3], {rotation:0} );
//create two materians from color
var myLeft:ColorMaterial = new ColorMaterial(0x000000);
var myRight:ColorMaterial = new ColorMaterial(0x000000);
//create a CubeMatrialsData from the materials created above
var myMaterials:CubeMaterialsData = new CubeMaterialsData( { front:myFront, back:myBack, top:myTop, bottom:myBottom, left:myLeft, right:myRight} );
//listen for material change
myMaterials.addOnMaterialChange(materialChanged);
//create a new cube with the CubeMaterialsData created earlier
var _cube = new Cube({width:_width, height:_height, depth:_depth, cubeMaterials:myMaterials});
//the created cube is put into the _cubeArray
_cubeArray[i] = _cube;
if (i == (_numberOfPieces - 1)) cubesMade();
}
}
private function fillFace(i:int):Array {
var faceBitmapArray:Array = [];
for (var j = 0; j < 4; j++) {
//tempBannerArray filled with one banner in pieces
var tempBannerArray:Array = _banners[j];
//batmapData created and sized to current banner piece
var bitmapData:BitmapData = new BitmapData(tempBannerArray[i].width, tempBannerArray[i].height);
//bitmapData filled with current banner piece bitmap data
bitmapData = tempBannerArray[i].bitmapData;
bitmapData.lock();
//array is filled with bitmap data
faceBitmapArray[j] = bitmapData;
}
return faceBitmapArray;
}
private function cubesMade() {
//dispatch event to notify of cube making completion
dispatchEvent(new CubeEvent(CubeEvent.CUBES_MADE, _cubeArray.length));
}
private function materialChanged(e:MaterialEvent):void {
trace("Warning! Warning! Material changed!");
}
public function get cubes():Array {
return _cubeArray;
}
public function get cubeWidth():Number {
return _width;
}
public function get cubeHeight():Number {
return _height;
}
public function get cubeDepth():Number {
return _depth;
}
}
}

Preface: Papervision3D is close to unsupported / closed as a library, so I highly recommend using Away3D or another actionscript 3D library. Though the work done on PV3D was groundbreaking and incredible from a flash perspective so if you're looking to build a 3D experience with it, it's quite good. Just know from the get-go that future dev + support are unlikely.
Preface aside, take a look at the "Cube" primitive class in the library. You can create Materials and attach them to the MaterialsList instance that will be added to the cube instance. Assuming you're using BitmapMaterial, it has a "rotation" property, that you can set. Just determine which materials are upside-down and make their rotation = 180. Pros: quick; Cons: hard-coded.
The other option is to hit the problem on the "asset" end and flip your images. Pros: quick; Cons: non-scalable or puts the issue on the designer.
The best option, when loading the data / assets, is to have a "rotate" property that you can use to set which images should be flipped. It's a cube, so like Ender says, up is relative.

Related

Looping Cubes & Adding Material (Away3D)

I'm trying to create a loop of cubes and apply a ColorMaterial to the surface. The main actionscript class has the following method for creating the loop. There is an external class file called Building which is responsible for the shape and ColorMaterial. Unfortunately, I seem to be doing something wrong. Flash Builder is not showing any warnings or errors. But when I run a test, all I get is black screen.
private function buildCity():void
{
var citySize:int = 1800;
var buildingSize:int = 100;
var roadSize:int = 50;
// Loop across in the x direction and again in the z direction
for(var cityX:int=citySize/2;cityX<citySize/2;cityX+=buildingSize+roadSize)
{
for(var cityZ:int=citySize/2;cityZ<citySize/2;cityZ+=buildingSize+roadSize)
{
// Create buildings
var building:Building = new Building();
// Position it
building.x = cityX;
building.z = cityZ;
// Add to the scene
view.scene.addChild(building);
}
}
}
The Building class responsible for the shape and material
package
{
import away3d.entities.Mesh;
import away3d.materials.ColorMaterial;
import away3d.primitives.CubeGeometry;
public class Building extends Mesh
{
public function Building()
{
super(new CubeGeometry(50,100,50));
// Make a material
material = new ColorMaterial(Math.random()*0xFFFFFF);
// Offset the y position based on height
y = 50;
}
}
}
Check your loop parameters. For example, in your code above, you set cityX to citySize / 2, then let it iterate while cityX < citySize / 2.
Since cityX >= citySize / 2 from the start, the loop is never executed.
The same goes for the inner loop.

Get global coordinate of a dynamically created nested component?

In the code below, I have created 4 sprites (picn) inside a sprite (noteholder). How can I get the absolute values of the picn instances that I create? I know about the localToGlobal function, but I can't figure out how to use that in this case. At this point, I think I need the container because I need to be able to move the sprites after creation. Any help is greatly appreciated. Thanks!
package
{
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
import Main;
public class Notes extends Sprite
{
private var speed:int = 14;
[Embed(source="../lib/Dodgethis.jpg")]
private var picn:Class;
private var noteholder:Sprite = new Sprite();
public function appear() {
trace ("appear ran")
var arr1:Array = new Array;
var numnotes:Number = 4;
Main.StageRef.addChild(noteholder);
trace (noteholder.x, noteholder.y);
for (var i = 0; i < numnotes; i++)
{
//trace (i);
var nbm:Bitmap = new picn;
noteholder.addChild(nbm);
nbm.y = i * 50;
arr1.push(nbm);
You need to hold a reference to the display objects you dynamically create — you don't need a "name" which is just an abstraction of instance variables when creating visual assets in the Flash IDE.
I've updated your code to demonstrate the concept:
package
{
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.display.Stage; // added this
import flash.events.Event;
import flash.geom.Point;
import flash.geom.Rectangle; // added this (see below)
import Main;
public class Notes extends Sprite
{
private var speed:int = 14;
[Embed(source="../lib/Dodgethis.jpg")]
private var NoteBMP:Class;
private var display:Sprite = new Sprite();
private var notes:Array = []; // make notes an instance variable, so it's accessible throughout the class
// I renamed appear to 'build' because I'm a pedantic moron
public function build():void
{
trace ("build ran");
var noteCount:int = 4;
Main.StageRef.addChild(display);
for (var i:int = 0; i < noteCount; i++)
{
var nbm:Bitmap = new NoteBMP;
display.addChild(nbm);
nbm.y = i * 50;
notes.push(nbm); // by adding the display objects to an array you can reference them later
then at some later point in the Notes class you can reference your bitmaps by looping through the notes array
// initialize variables outside the loop
var localPos:Point
var globalPos:Point;
var bounds:Rectangle;
var n:DisplayObject;
var stage:Stage = Main.StageRef;
for (var i:int = 0; i < notes.length; i++)
{
trace( 'note' + i );
n = notes[i] as DisplayObject; // cast during assignment since array items have ambiguous type
// getBounds() returns a Rectangle representing the bounding box of the display object
bounds = n.getBounds(stage);
trace(
'getBounds:',
bounds.top, bounds.left, bounds.bottom, bounds.right
);
// localToGlobal() returns a Point which is just the position
localPos = new Point( n.x, n.y );
globalPos = display.localToGlobal(localPos)
trace(
'localToGlobal:',
globalPos.x, globalPos.y
);
}
A few points:
I refactored some names to make the intention more clear
I wonder why your Notes class extends Sprite, since it adds display (what you called "noteholder") to the stage. Assuming that's correct just make Notes a generic object i.e. doesn't seem to need to extend another class.
You can use getBounds about getBounds
// call this after added to stage
if (stage) {
var r:Rectangle = nbm.getBounds(stage);// r's x and y is the global pos
}
And I think localToGlobal should work in your case. Move the sprites don't have effect on localToGlobal

How can I use a document class in a single AS3 movie clip?

I have a confetti generator that I am tyring to add to a single movie clip within my flash file. The clip is masked and I want to have some graphics and text appear above the confetti (which will be above a background layer as well).
I purchased a decent script and have modified it to work with some original confetti artwork but I can't figure out how to use this class (or change it for use) in just the one movie clip. Pasting the class below. I've been stressing about this for a couple of hours now, any help would be greatly appreciated.
package com.pixeljunkyard
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import caurina.transitions.*;
import fl.motion.Color;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
public class Main extends Sprite
{
//Create Heart Instance
private var hearts:Heart;
//Amount of hearts
private var totalHearts:Number = 30;
//Falling Speed
private var speed:Number = 1.5;
//Constructor
public function Main()
{
//Align top left for screen aspect ratio
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
//Loop through the amount of heart to be created
for (var i = 0; i < totalHearts; i++)
{
//Create new heart
var heart = new Heart();
//Set Random value
var randScale:Number = randRange(50, 100);
var randRotation:Number = randRange( -180, 180);
var randRotationY:Number = randRange( -360, 360);
//Random position and scale
heart.x = randRange(0, stage.stageWidth);
heart.y = randRange( -stage.stageHeight, stage.stageHeight);
heart.scaleX = randScale/100;
heart.scaleY = randScale/100;
//Name each heart with the number of creation
heart.name = "heart" + i;
var Low : int = 1;
var High : int = 8;
var myRandomNumber:int = Math.floor(Math.random()*(1+High-Low))+Low;
heart.gotoAndStop(myRandomNumber);
//Add eventlisteners for interactions
heart.addEventListener(MouseEvent.ROLL_OVER, hit_heart);
heart.addEventListener(Event.ENTER_FRAME, change_shade);
//Initial Animation
Tweener.addTween(heart, {time:randRange(1,5)/speed, rotation:randRotation,rotationY:randRotationY,y:stage.stageHeight+(heart.height/2)+20, transition:"linear", onComplete:rebirth,onCompleteParams:[heart]} );
//Add to Stage
addChildAt(heart, i);
}
}
//Change shade to give lighting effect
private function change_shade(e:Event):void
{
//New color instance
var c:Color = new Color();
//Set properties
c.brightness = e.target.rotation / 300;
//Apply color to heart
e.target.transform.colorTransform = c;
}
//Random Function
private function randRange(min:Number, max:Number):Number
{
var randomNum:Number = Math.floor(Math.random() * (max - min + 1)) + min;
return randomNum;
}
//Interactive animation
private function hit_heart(e:Event):void
{
Tweener.addTween(e.target, { time:randRange(1,3), rotationY:e.target.rotationY+180 } );
}
//Reset heart to top of the screen once fallen
private function rebirth($heart:Heart):void
{
$heart.x = randRange(0, stage.stageWidth);
$heart.y = -$heart.height;
Tweener.addTween($heart, {time:randRange(1,5)/speed, rotation:randRange(-180,180),y:stage.stageHeight+($heart.height/2)+20, transition:"linear", onComplete:rebirth,onCompleteParams:[$heart]} );
}
}
}
Now I understand your problem.
First of all, I suggest to never write code on the timeline, except simple stuff like stop() or gotoAndPlay("loop").
The easiest way to achieve what you want is to do the following:
Make a blank MovieClip in Flash IDE Ctrl + F8
Give it a linkage like this:
Then click the edit button (marked with a red rectangle)
Open in Flash Professional if asked
Save the file in your .FLA directory and copy the contents of your Main.as file into this file
Remove the package name ("com.pixeljunkyard")
Change the public class Main extends Sprite to public class ConfettiContainer extends MovieClip and import flash.display.MovieClip
Now you have a class ConfettiContainer which does the same stuff that you Main.as file did. Don't forget to copy anything that this Main.as class uses from stage to your ConfettiContainer MovieClip.
You can now create and use it like this:
var confetti:ConfettiContainer = new ConfettiContainer();
addChild(confetti);
P.S. If you can't see Export for Actionscript option when creating a Symbol in Flash, click Advanced.

Box2D Walls - same code: the left one works, the right one doesn't

I'm making a simple Box2D game ( http://iwanttobeacircle.com ), where you start off as a triangle and bounce off bigger shapes to gain sides.
I'm having a bizarre bug with my walls... both are created from the same class, yet the left one works and the right doesn't. If I only add the right one, then it works, but for some reason adding them both seems to be causing a problem somewhere.
The WallSegment class is below:
package com.carmstrong.iwanttobeacircle {
import flash.display.DisplayObjectContainer;
import flash.display.Sprite;
import flash.display.Shape;
import Box2D.Collision.Shapes.*;
import Box2D.Dynamics.*;
import Box2D.Common.Math.b2Vec2;
import com.carmstrong.iwanttobeacircle.Config;
public class WallSegment extends Actor {
private var _shape:Sprite;
private var _shapeBody:b2Body;
private var _colour:uint = 0x666666;
private var prevEdge:int;
private var thisEdge:int;
private var side:Number;
private var name:String;
private var _pathWidth:int;
private var _parent:DisplayObjectContainer;
public function WallSegment(Width:int, Position:String, Parent:DisplayObjectContainer) {
name = "Wall";
_pathWidth = Width;
_parent = Parent;
if(Position == "left") {
side = 1;
prevEdge = (Config.WIDTH - Config.PREV_WIDTH)/2;
thisEdge = (Config.WIDTH-_pathWidth)/2;
} else {
side = -1;
prevEdge = Config.WIDTH-(Config.WIDTH - Config.PREV_WIDTH)/2;
thisEdge = Config.WIDTH-(Config.WIDTH-_pathWidth)/2;
}// check if its left or right wall
//Create the costume
drawShape();
drawBody();
super(_shapeBody, _shape);
}//DynamicWall
private function drawShape():void {
//Draw visual
_shape = new Sprite();
var left:Sprite = new Sprite();
left.graphics.beginFill(_colour, 0.5);
left.graphics.moveTo(prevEdge, Config.HEIGHT/2);
left.graphics.lineTo(prevEdge-Config.WIDTH*side, Config.HEIGHT/2);
left.graphics.lineTo(thisEdge-Config.WIDTH*side, -Config.HEIGHT/2);
left.graphics.lineTo(thisEdge, -Config.HEIGHT/2);
left.graphics.endFill();
_shape.addChild(left);
_parent.addChild(_shape);
}//drawShape
private function drawBody():void {
//Create the shape definition
var shapeDef:b2PolygonDef = new b2PolygonDef();
shapeDef.vertexCount = 4;
b2Vec2(shapeDef.vertices[0]).Set(prevEdge/Config.RATIO, Config.HEIGHT/2/Config.RATIO);
b2Vec2(shapeDef.vertices[1]).Set((prevEdge-Config.WIDTH*side)/Config.RATIO, Config.HEIGHT/2/Config.RATIO);
b2Vec2(shapeDef.vertices[2]).Set((thisEdge-Config.WIDTH*side)/Config.RATIO, -Config.HEIGHT/2/Config.RATIO);
b2Vec2(shapeDef.vertices[3]).Set(thisEdge/Config.RATIO, -Config.HEIGHT/2/Config.RATIO);
shapeDef.density = 0;
shapeDef.friction = 10;
shapeDef.restitution = 0.45;
//Create the body definition (specify location here)
var shapeBodyDef:b2BodyDef = new b2BodyDef();
shapeBodyDef.position.Set(0, -Config.HEIGHT*(Config.CURRENT_SEGMENT+1)/Config.RATIO);
//Create the body
_shapeBody = Config.world.CreateBody(shapeBodyDef);
//Create the shape
_shapeBody.CreateShape(shapeDef);
}//drawBody
}//class
}//package
To keep the level dynamic, the walls are drawn just ahead of the player object each time in the main class, using the following code:
private function addWall(Width:int) {
Config.CURRENT_SEGMENT++;
//addWalls
var leftWall:WallSegment = new WallSegment(Width, "left",camera);
var rightWall:WallSegment = new WallSegment(Width, "right",camera);
Config.PREV_WIDTH = Width;
}//addWall
I'm getting this error:
TypeError: Error #1010: A term is
undefined and has no properties. at
com.carmstrong.iwanttobeacircle::GameContactListener/Add()
at
Box2D.Dynamics.Contacts::b2CircleContact/Evaluate()
at
Box2D.Dynamics.Contacts::b2Contact/Update()
at
Box2D.Dynamics::b2ContactManager/Collide()
at Box2D.Dynamics::b2World/Step() at
com.carmstrong.iwanttobeacircle::IWantToBeACircle/everyFrame()
Which refers to the GameContactListener class, shown below (the add function is at the bottom):
package com.carmstrong.iwanttobeacircle {
import Box2D.Collision.b2ContactPoint;
import Box2D.Dynamics.b2ContactListener;
public class GameContactListener extends b2ContactListener {
public function GameContactListener() {
}//GameContactListener
override public function Add(point:b2ContactPoint):void {
if (point.shape1.GetBody().GetUserData() is ShapeActor && point.shape2.GetBody().GetUserData() is ShapeActor) {
//trace("Two shapes collided: Shape 1 has "+ point.shape1.GetBody().GetUserData().sides + " sides and Shape 2 has " + point.shape2.GetBody().GetUserData().sides + " sides");
if (point.shape1.GetBody().GetUserData().sides > point.shape2.GetBody().GetUserData().sides) {
//remove side from shape 1 and add side to shape 2
point.shape1.GetBody().GetUserData().sides--;
point.shape2.GetBody().GetUserData().sides++;
//point.shape2.GetBody().GetUserData().updateColour;
} else if (point.shape1.GetBody().GetUserData().sides < point.shape2.GetBody().GetUserData().sides) {
//remove side from shape 2 and add side to shape 1
point.shape1.GetBody().GetUserData().sides++;
point.shape2.GetBody().GetUserData().sides--;
//point.shape2.GetBody().GetUserData().updateColour;
}// add side to smaller shape and take away from larger shape
if(point.shape1.GetBody().GetUserData().name == "player" || point.shape2.GetBody().GetUserData().name == "player") {
if(point.shape1.GetBody().GetUserData().name == "player" && point.shape2.GetBody().GetUserData().sides <= point.shape1.GetBody().GetUserData().sides) {
Config.FULFILLMENT++;
Config.SOUNDS[3+Math.ceil(Math.random()*5)][1].play();
trace(Config.FULFILLMENT);
} else if (point.shape2.GetBody().GetUserData().name == "player" && point.shape1.GetBody().GetUserData().sides <= point.shape2.GetBody().GetUserData().sides) {
Config.FULFILLMENT++;
Config.SOUNDS[Math.ceil(Math.random()*5)][1].play();
trace(Config.FULFILLMENT);
} else {
Config.SOUNDS[Math.ceil(Math.random()*3)][1].play();
Config.FULFILLMENT = int(Config.FULFILLMENT - 5);
trace(Config.FULFILLMENT);
}//if other shape is less than or equal to player
}//if one of the shapes is player
}// if both collider objects are shapes
super.Add(point);
}// override Add
}//class
}//package
I would appreciate any thoughts or ideas. Also, this is my first go at Box2D so would appreciate any tips on how to make my code more efficient.
Thanks in advance
You're asking two questions here
Why is the left and right wall collision detection not working?
Why is there an error on the GameContactListener/Add() method?
It looks like they may have the same root problem. I'm seeing you're adding the wall segments as children of the "camera" (had to trace where this add was happening... you pass a reference of "camera" into the constructor of the object.)
i.e. var leftWall:WallSegment = new WallSegment(Width, "left",camera);
Within there, you add the leftWall as a child of that object. But I'm not sure what "camera" is... Is this part of your game world object?
Also, what are your trace statements for point.shape1 and point.shape2?
What 2 objects are colliding?

Papervision3D; rotate child objects within camera view

It's my first time with Papervision3D and I have created a slide show of images that is skewed on the y-axis. Smallest photos on the left, and they increase in size going to the right. So they zoom from left to right, smallest to biggest.
I have a tooltip that pops up when you hovers over the photo, but the tooltip also gets skewed proportionate to the camera view (slanted). I want the tooltip's angle to be independent of the entire camera view.
Any idea how to rotate objects independent of the parent's camera angle?
Thanks!
my_obj = new DisplayObject3D();
my_plane = my_obj.addChild(new Plane(bla bla));
my_obj.lookAt(camera);
The 'lookAt' bit is what you need.
Why not draw the tooltips in 2d? You can get the on-screen position of the images and then just draw a regular Sprite like so:
package
{
import flash.display.Sprite;
import flash.text.TextField;
import org.papervision3d.events.InteractiveScene3DEvent;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.typography.Text3D;
import org.papervision3d.view.BasicView;
public class PVTest extends Sprite
{
private var world:BasicView;
private var text:Text3D;
private var text2d:TextField;
public function PVTest()
{
world = new BasicView(stage.width, stage.height, true, true);
var colorMat:ColorMaterial = new ColorMaterial();
colorMat.interactive = true;
var planeContainer:DisplayObject3D = new DisplayObject3D();
var plane:Plane;
for(var i:int = 0; i < 11; i++)
{
plane= new Plane(
colorMat,
100, 100,
10);
plane.x = (i * 105) - 500;
plane.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, handleMouseOver);
plane.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, handleMouseOut);
planeContainer.addChild(plane);
}
planeContainer.rotationY = 10;
world.scene.addChild(planeContainer);
world.camera.z = -500;
addChild(world);
world.startRendering();
}
private function handleMouseOver(event:InteractiveScene3DEvent):void
{
var plane:Plane = Plane(event.displayObject3D);
plane.calculateScreenCoords(world.camera);
const OFFSET_X:int = -20;
const OFFSET_Y:int = 30;
text2d = new TextField();
text2d.text = "toolTip";
text2d.x = plane.screen.x + (stage.width/2) + OFFSET_X;
text2d.y = plane.screen.y + (stage.height/2) + OFFSET_Y;
addChild(text2d);
}
private function handleMouseOut(event:InteractiveScene3DEvent):void
{
removeChild(text2d);
}
}
}
Even for this example you'd have to offset the y position of the tooltip based on the objects scale but it may be easier than working out the rotations and is the best way to get a consistent looking result.