JavaRTS/entity/drone.java
Pan 132fa5ebc0 Upgrade the screen resolution of considerably, it gives the user wider
view of the back ground. Also the screen resolution and the shadowmap
resolution are no longer hard coded everywhere. They are defined in
mainThread.java.
2021-08-16 09:33:33 +12:00

682 lines
21 KiB
Java

package entity;
import java.awt.Rectangle;
import core.*;
//small flying unit capable of repairing tanks
public class drone extends solidObject{
public vector iDirectionBody, jDirectionBody, kDirectionBody;
public vector bodyCenter;
public static polygon3D[] polys;
public static vector engine1Center, engine2Center;
public int fan1Angle, fan2Angle;
public final static Rectangle visibleBoundary = new Rectangle(-100,-150,screen_width + 300, screen_height + 300);
//a screen space boundary which is used to test if the entire object is within the screen
public final static Rectangle screenBoundary = new Rectangle(40,40,screen_width - 90, screen_height-80);
//screen space boundary which is used to test if the shadow of the object is within the screen
public final static Rectangle shadowBoundary1 = new Rectangle(0,0,screen_width, screen_height);
public int bodyAngle, destinationAngle;
//index of the tiles to check when the drone is idle
public static int[] tileCheckList;
public factory myFactory;
public static int numOfPolygons;
public float heightVariance;
public int randomNumber;
public solidObject targetUnit;
public vector idlePosition;
public static final int returnToIdlePosition = 0;
public static final int healUnit = 1;
public static final int turnRate = 5;
public static final float maxSpeed = 0.04f;
public float serviceRadius;
public static vector armCenter, armDirection;
public vector armCenterClone, armDirectionClone;
public int returnToIdlePositionCountdown;
public drone(vector origin, int bodyAngle, factory myFactory){
//register itself in factory and find out idle location
idlePosition = new vector(0,0,0);
serviceRadius = 3.5f;
for(int i = 0; i < 3; i++){
if(myFactory.myDrones[i] == null){
myFactory.myDrones[i] = this;
if(i == 0){
idlePosition.set(myFactory.centre.x, 0, myFactory.centre.z+0.15f);
}else if(i == 1){
idlePosition.set(myFactory.centre.x - 0.3f, 0, myFactory.centre.z-0.25f);
}else{
idlePosition.set(myFactory.centre.x + 0.2f, 0, myFactory.centre.z-0.25f);
}
break;
}
}
speed = 0f;
currentHP = 20;
start = origin.myClone();
centre = origin.myClone();
tempCentre = origin.myClone();
bodyCenter = origin.myClone();
this.bodyAngle = 360 -bodyAngle;
this.immediateDestinationAngle = bodyAngle;
destinationAngle = bodyAngle;
teamNo = myFactory.teamNo;
this.myFactory = myFactory;
//drone does't have any collision boundary, and its unselectable
type = 5;
isSelectable = false;
height = centre.y + 0.5f; //?
theAssetManager = mainThread.theAssetManager;
movement = new vector(0,0,0);
//create main axis in object space
iDirection = new vector(1f,0,0);
jDirection = new vector(0,1f,0);
kDirection = new vector(0,0,1f);
iDirection.rotate_XZ(360-bodyAngle);
jDirection.rotate_XZ(360-bodyAngle);
kDirection.rotate_XZ(360-bodyAngle);
//create polygons
makePolygons();
if(tileCheckList == null){
tileCheckList = generateTileCheckList(13f);
}
randomNumber = (int)(Math.random()*360);
}
public void makePolygons(){
if(polys == null){
polys = new polygon3D[171];
vector[] v;
engine1Center = new vector(0.041f, 0, 0);
engine2Center = new vector(-0.041f, 0, 0);
//create engine fan
start.reset();
float delta = (float)Math.PI/4;
v = new vector[]{
put(0.027*Math.cos(1*delta), -0.01, 0.027*Math.sin(1*delta)),
put(0.027*Math.cos((0)*delta), -0.01, 0.027*Math.sin((0)*delta)),
put(0.00001*Math.cos((0)*delta), -0.01, 0.00001*Math.sin((0)*delta)),
put(0.00001*Math.cos(1*delta), -0.01, 0.00001*Math.sin(1*delta)),
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{
put(0.027*Math.cos(5*delta), -0.01, 0.027*Math.sin(5*delta)),
put(0.027*Math.cos((4)*delta), -0.01, 0.027*Math.sin((4)*delta)),
put(0.00001*Math.cos((4)*delta), -0.01, 0.00001*Math.sin((4)*delta)),
put(0.00001*Math.cos(5*delta), -0.01, 0.00001*Math.sin(5*delta)),
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{
put(0.027*Math.cos(3*delta), -0.01, 0.027*Math.sin(3*delta)),
put(0.027*Math.cos((2)*delta), -0.01, 0.027*Math.sin((2)*delta)),
put(0.00001*Math.cos((2)*delta), -0.01, 0.00001*Math.sin((2)*delta)),
put(0.00001*Math.cos(3*delta), -0.01, 0.00001*Math.sin(3*delta)),
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{
put(0.027*Math.cos(7*delta), -0.01, 0.027*Math.sin(7*delta)),
put(0.027*Math.cos((6)*delta), -0.01, 0.027*Math.sin((6)*delta)),
put(0.00001*Math.cos((6)*delta), -0.01, 0.00001*Math.sin((6)*delta)),
put(0.00001*Math.cos(7*delta), -0.01, 0.00001*Math.sin(7*delta)),
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
//create main body section
v = new vector[]{ put(-0.016, 0, -0.006),put(-0.016, 0, 0.006), put(-0.015, 0, 0.013), put(0.015, 0, 0.013), put(0.016, 0, 0.006), put(0.016, 0, -0.006), put(0.015, 0, -0.013),put(-0.015, 0, -0.013)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{put(-0.016, 0, -0.006), put(-0.015, 0, -0.013), put(-0.015, -0.015, -0.013), put(-0.016, -0.015, -0.006)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{ put(0.016, -0.015, -0.006), put(0.015, -0.015, -0.013), put(0.015, 0, -0.013),put(0.016, 0, -0.006)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{put(-0.016, -0.015, 0.006), put(-0.015, -0.015, 0.013), put(-0.015, 0, 0.013), put(-0.016, 0, 0.006)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{put(0.016, 0, 0.006), put(0.015, 0, 0.013), put(0.015, -0.015, 0.013), put(0.016, -0.015, 0.006)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{put(0.015, 0, 0.013), put(-0.015, 0, 0.013), put(-0.015, -0.015, 0.013), put(0.015, -0.015, 0.013)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
v = new vector[]{put(0.015, -0.015, -0.013), put(-0.015, -0.015, -0.013), put(-0.015, 0, -0.013), put(0.015, 0, -0.013)};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
//left engine
float r = 0.03f;
delta = (float)Math.PI/8;
start.x-=0.041f;
for(int i = 0; i < 16; i++){
v = new vector[]{put(r*Math.cos(i*delta), 0.01, r*Math.sin(i*delta)),
put(r*Math.cos((i+1)*delta), 0.01, r*Math.sin((i+1)*delta)),
put(r*Math.cos((i+1)*delta), -0.013, r*Math.sin((i+1)*delta)),
put(r*Math.cos(i*delta), -0.013, r*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
float r2 = 0.026f;
for(int i = 0; i < 16; i++){
v = new vector[]{put(r2*Math.cos(i*delta), -0.013, r2*Math.sin(i*delta)),
put(r2*Math.cos((i+1)*delta), -0.013, r2*Math.sin((i+1)*delta)),
put(r2*Math.cos((i+1)*delta), 0.01, r2*Math.sin((i+1)*delta)),
put(r2*Math.cos(i*delta), 0.01, r2*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
for(int i = 0; i < 16; i++){
v = new vector[]{
put(r2*Math.cos(i*delta), 0.01, r2*Math.sin(i*delta)),
put(r2*Math.cos((i+1)*delta), 0.01, r2*Math.sin((i+1)*delta)),
put(r*Math.cos((i+1)*delta), 0.01, r*Math.sin((i+1)*delta)),
put(r*Math.cos(i*delta), 0.01, r*Math.sin(i*delta)),
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
float r3 = 0.005f;
float r4 = 0.0001f;
for(int i = 0; i < 16; i++){
v = new vector[]{put(r4*Math.cos(i*delta), 0.01, r4*Math.sin(i*delta)),
put(r4*Math.cos((i+1)*delta), 0.01, r4*Math.sin((i+1)*delta)),
put(r3*Math.cos((i+1)*delta), -0.01, r3*Math.sin((i+1)*delta)),
put(r3*Math.cos(i*delta), -0.01, r3*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
//right engine
start.x+=0.082f;
for(int i = 0; i < 16; i++){
v = new vector[]{put(r*Math.cos(i*delta), 0.01, r*Math.sin(i*delta)),
put(r*Math.cos((i+1)*delta), 0.01, r*Math.sin((i+1)*delta)),
put(r*Math.cos((i+1)*delta), -0.013, r*Math.sin((i+1)*delta)),
put(r*Math.cos(i*delta), -0.013, r*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
for(int i = 0; i < 16; i++){
v = new vector[]{put(r2*Math.cos(i*delta), -0.013, r2*Math.sin(i*delta)),
put(r2*Math.cos((i+1)*delta), -0.013, r2*Math.sin((i+1)*delta)),
put(r2*Math.cos((i+1)*delta), 0.01, r2*Math.sin((i+1)*delta)),
put(r2*Math.cos(i*delta), 0.01, r2*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
for(int i = 0; i < 16; i++){
v = new vector[]{
put(r2*Math.cos(i*delta), 0.01, r2*Math.sin(i*delta)),
put(r2*Math.cos((i+1)*delta), 0.01, r2*Math.sin((i+1)*delta)),
put(r*Math.cos((i+1)*delta), 0.01, r*Math.sin((i+1)*delta)),
put(r*Math.cos(i*delta), 0.01, r*Math.sin(i*delta)),
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
for(int i = 0; i < 16; i++){
v = new vector[]{put(r4*Math.cos(i*delta), 0.01, r4*Math.sin(i*delta)),
put(r4*Math.cos((i+1)*delta), 0.01, r4*Math.sin((i+1)*delta)),
put(r3*Math.cos((i+1)*delta), -0.01, r3*Math.sin((i+1)*delta)),
put(r3*Math.cos(i*delta), -0.01, r3*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
//repair arm
start.x-=0.041f;
r2 = 0.008f;
for(int i = 0; i < 16; i++){
v = new vector[]{put(r2*Math.cos(i*delta), 0, r2*Math.sin(i*delta)),
put(r2*Math.cos((i+1)*delta), 0, r2*Math.sin((i+1)*delta)),
put(r2*Math.cos((i+1)*delta), -0.08, r2*Math.sin((i+1)*delta)),
put(r2*Math.cos(i*delta), -0.08, r2*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
iDirection.rotate_YZ(340);
jDirection.rotate_YZ(340);
kDirection.rotate_YZ(340);
start.y-=0.075f;
r3 = 0.007f;
for(int i = 0; i < 16; i++){
v = new vector[]{put(r2*Math.cos(i*delta), 0, r2*Math.sin(i*delta)),
put(r2*Math.cos((i+1)*delta), 0, r2*Math.sin((i+1)*delta)),
put(r3*Math.cos((i+1)*delta), -0.04, r3*Math.sin((i+1)*delta)),
put(r3*Math.cos(i*delta), -0.04, r3*Math.sin(i*delta))
};
addPolygon(polys, new polygon3D(v, v[0], v[1], v[3], mainThread.textures[26], 1f,1f,1));
}
armCenter = put(0,-0.04, 0);
armDirection = jDirection.myClone();
armDirection.y*=-1;
}
polygons = clonePolygons(polys, false);
armCenterClone = armCenter.myClone();
armDirectionClone = armDirection.myClone();
}
//update the model
public void update(){
if(returnToIdlePositionCountdown > 0)
returnToIdlePositionCountdown--;
//check if factory where the drone is spawned has been destroyed
if(myFactory.currentHP <= 0){
if(targetUnit != null)
targetUnit.myHealer = null;
//spawn an explosion when the object is destroyed
float[] tempFloat = theAssetManager.explosionInfo[theAssetManager.explosionCount];
tempFloat[0] = centre.x;
tempFloat[1] = centre.y;
tempFloat[2] = centre.z;
tempFloat[3] = 0.99f;
tempFloat[4] = 1;
tempFloat[5] = 0;
tempFloat[6] = 7;
tempFloat[7] = this.height;
theAssetManager.explosionCount++;
theAssetManager.removeObject(this);
return;
}
//handle AI
if(!disableUnitLevelAI){
if(currentCommand == returnToIdlePosition){
if(centre.y <=0.05){
centre.y+=0.005f;
}
if((idlePosition.x != centre.x || idlePosition.z != centre.z) && returnToIdlePositionCountdown == 0){
double distanceToDestination = Math.sqrt((idlePosition.x - centre.x)* (idlePosition.x - centre.x) + (idlePosition.z - centre.z)*(idlePosition.z - centre.z));
if(distanceToDestination >= 0.2f){
if(speed <= maxSpeed)
speed+=0.002f;
}
if(distanceToDestination < 0.4f){
if(speed >= 0.03f)
speed-=0.002f;
}
if(distanceToDestination < 0.25f){
if(speed >= 0.02f)
speed-=0.002f;
}
if(distanceToDestination < 0.1f){
if(speed >= 0.01f)
speed-=0.002f;
}
//move to idle position
if(distanceToDestination <= speed){
centre.x = idlePosition.x;
centre.z = idlePosition.z;
speed = 0;
}else{
movement.set(idlePosition.x - centre.x, 0, idlePosition.z - centre.z);
movement.unit();
movement.scale(speed);
centre.add(movement);
}
//face idle position
destinationAngle = geometry.findAngle(centre.x, centre.z, idlePosition.x, idlePosition.z);
int angleDelta = 360 - (geometry.findAngleDelta(bodyAngle, destinationAngle, turnRate) + 360)%360;
bodyAngle= (bodyAngle - angleDelta + 360)%360;
if(Math.abs(bodyAngle - destinationAngle) <= turnRate)
bodyAngle = destinationAngle;
}
//scan for nearby damaged unit
currentOccupiedTile = (int)(centre.x*64)/16 + (127 - (int)(centre.z*64)/16)*128;
for(int i = 0; i < tileCheckList.length; i++){
if(targetUnit != null)
break;
if(tileCheckList[i] != Integer.MAX_VALUE){
int index = currentOccupiedTile + tileCheckList[i];
if(index < 0 || index >= 16384)
continue;
tile = mainThread.gridMap.tiles[index];
for(int j = 0; j < 4; j++){
if(tile[j] != null){
if(tile[j].teamNo == teamNo && tile[j].currentHP < tile[j].getMaxHp() && getDistance(myFactory, tile[j]) < serviceRadius && tile[j].myHealer == null){
currentCommand = healUnit;
targetUnit = tile[j];
targetUnit.myHealer = this;
break;
}
}
}
}
}
}else if(currentCommand == healUnit){
returnToIdlePositionCountdown = 60;
if(targetUnit.currentHP <=0 || getDistance(myFactory, targetUnit) >= serviceRadius){
targetUnit.myHealer = null;
targetUnit = null;
currentCommand = returnToIdlePosition;
}else{
tempVector.set(targetUnit.centre);
if(tempVector.x != centre.x || tempVector.z != centre.z){
double distanceToDestination = Math.sqrt((tempVector.x - centre.x)* (tempVector.x - centre.x) + (tempVector.z - centre.z)*(tempVector.z - centre.z));
if(distanceToDestination >= 0.2f){
if(speed <= maxSpeed)
speed+=0.002f;
if(centre.y <=0.1){
centre.y+=0.01f;
}
}else{
if(centre.y > - 0.15){
centre.y-=0.01f;
}
}
if(distanceToDestination < 0.4f){
if(speed >= 0.03f)
speed-=0.002f;
//heal unit
if(targetUnit.currentHP < targetUnit.getMaxHp() || targetUnit.underAttackCountDown > 60){
if(mainThread.gameFrame%5 == 1 && centre.y <=-0.1){
targetUnit.currentHP+=5;
if(targetUnit.currentHP > targetUnit.getMaxHp())
targetUnit.currentHP = targetUnit.getMaxHp();
}
if(mainThread.gameFrame%2==0 && centre.y <=-0.15){
//spawn a healing steam particle
float[] tempFloat = theAssetManager.smokeEmmiterList[theAssetManager.smokeEmmiterCount];
tempFloat[0] = armCenterClone.x + (float)(Math.random()/20) - 0.025f;
tempFloat[1] = armCenterClone.y;
tempFloat[2] = armCenterClone.z + (float)(Math.random()/20) - 0.025f;
tempFloat[3] = 0.8f;
tempFloat[4] = 1;
tempFloat[5] = 11;
tempFloat[6] = this.height;
theAssetManager.smokeEmmiterCount++;
}
}else{
targetUnit.myHealer = null;
targetUnit = null;
currentCommand = returnToIdlePosition;
}
}
if(distanceToDestination < 0.25f){
if(speed >= 0.02f)
speed-=0.002f;
}
if(distanceToDestination < 0.1f){
if(speed >= 0.01f)
speed-=0.002f;
}
if(distanceToDestination >= 0.06f){
movement.set(tempVector.x - centre.x, 0, tempVector.z - centre.z);
movement.unit();
movement.scale(speed);
centre.add(movement);
}
//face idle position
destinationAngle = geometry.findAngle(centre.x, centre.z, tempVector.x, tempVector.z);
int angleDelta = 360 - (geometry.findAngleDelta(bodyAngle, destinationAngle, turnRate) + 360)%360;
bodyAngle= (bodyAngle - angleDelta + 360)%360;
if(Math.abs(bodyAngle - destinationAngle) <= turnRate)
bodyAngle = destinationAngle;
}
}
}
}
fan1Angle+=60;
fan2Angle+=300;
fan1Angle = fan1Angle%360;
fan2Angle = fan2Angle%360;
heightVariance = gameData.sin[((mainThread.gameFrame+randomNumber)*5)%360] * 0.01f;
//update center in camera coordinate
tempCentre.set(centre);
tempCentre.subtract(camera.position);
tempCentre.rotate_XZ(camera.XZ_angle);
tempCentre.rotate_YZ(camera.YZ_angle);
tempCentre.updateLocation();
//check if the tank object is visible in mini map
visible_minimap = theAssetManager.minimapBitmap[(int)(centre.x*64)/16 + (127 - (int)(centre.z*64)/16)*128];
//test if the object is visible in camera point of view
if(visibleBoundary.contains(tempCentre.screenX, tempCentre.screenY) && myFactory.isRevealed && visible_minimap){
visible = true;
for(int i = 0; i < numOfPolygons; i++){
polygons[i].update_lightspace();
}
//update centre
updateGeometry();
if(screenBoundary.contains(tempCentre.screenX, tempCentre.screenY))
withinViewScreen = true;
else
withinViewScreen = false;
}else{
visible = false;
}
}
public void updateGeometry(){
int bodyAngle_ = 360 - bodyAngle;
//rotate fans
for(int j = 0; j < polygons[0].vertex3D.length; j++){
polygons[0].vertex3D[j].set(polys[0].vertex3D[j]);
polygons[0].vertex3D[j].rotate_XZ(fan1Angle);
polygons[0].vertex3D[j].add(engine1Center);
polygons[0].vertex3D[j].rotate_XZ(bodyAngle_);
polygons[0].vertex3D[j].add(centre);
polygons[0].vertex3D[j].y+=heightVariance;
polygons[1].vertex3D[j].set(polys[1].vertex3D[j]);
polygons[1].vertex3D[j].rotate_XZ(fan1Angle);
polygons[1].vertex3D[j].add(engine1Center);
polygons[1].vertex3D[j].rotate_XZ(bodyAngle_);
polygons[1].vertex3D[j].add(centre);
polygons[1].vertex3D[j].y+=heightVariance;
polygons[2].vertex3D[j].set(polys[2].vertex3D[j]);
polygons[2].vertex3D[j].rotate_XZ(fan2Angle);
polygons[2].vertex3D[j].add(engine2Center);
polygons[2].vertex3D[j].rotate_XZ(bodyAngle_);
polygons[2].vertex3D[j].add(centre);
polygons[2].vertex3D[j].y+=heightVariance;
polygons[3].vertex3D[j].set(polys[3].vertex3D[j]);
polygons[3].vertex3D[j].rotate_XZ(fan2Angle);
polygons[3].vertex3D[j].add(engine2Center);
polygons[3].vertex3D[j].rotate_XZ(bodyAngle_);
polygons[3].vertex3D[j].add(centre);
polygons[3].vertex3D[j].y+=heightVariance;
}
//update main body
for(int i =4; i < numOfPolygons; i++){
for(int j = 0; j < polygons[i].vertex3D.length; j++){
polygons[i].vertex3D[j].set(polys[i].vertex3D[j]);
polygons[i].normal.set(polys[i].normal);
polygons[i].normal.rotate_XZ(bodyAngle_);
polygons[i].vertex3D[j].rotate_XZ(bodyAngle_);
polygons[i].vertex3D[j].add(centre);
polygons[i].vertex3D[j].y+=heightVariance;
polygons[i].findDiffuse();
}
}
//update arm
armCenterClone.set(armCenter);
armCenterClone.rotate_XZ(bodyAngle_);
armCenterClone.add(centre);
armCenterClone.y+=heightVariance;
armDirectionClone.set(armDirection);
armDirectionClone.rotate_XZ(bodyAngle_);
}
public void draw(){
if(!visible)
return;
for(int i = 0; i < numOfPolygons; i++){
polygons[i].update();
polygons[i].draw();
}
}
public int addPolygon(polygon3D[] polys, polygon3D poly){
for(int i = 0; i < polys.length; i++){
if(polys[i] == null){
polys[i] = poly;
numOfPolygons++;
return i;
}
}
return -1;
}
public float getDistance(solidObject o1, solidObject o2){
return (float)Math.sqrt((o1.centre.x - o2.centre.x)*(o1.centre.x - o2.centre.x) + (o1.centre.z - o2.centre.z)*(o1.centre.z - o2.centre.z));
}
}