One element of the handling of wreckage bits which is particularly ugly is the way they are made to diverge horizontally outward on ever more strongly curving paths as the speed slows. If a plane blows up in the air, and more so if not particularly high in altitude, the pieces will bounce along the ground for a time. For the bits that are more horizontally displaced from the average ground track, they can curve so much as they slow that their direction of travel can end up doing a full 180 degree arc! The overall pattern of the motions is very geometrically uniform, being what I call a Fleur-de-lis.
For wreckage falling from a considerable height this arcing is in effect, right from the start. The outlier pieces can end up being horizontally separated from the mean ground track by hundreds of meters. The result is a pattern of fall which is on an arc convex in the direction of travel, not much separated in distance along the direction of travel, and HUGELY elongated on its arcuate line perpendicular to the line of travel. That is, the surface distribution in the fall pattern should be elongated in the direction of travel, but instead is elongated perpendicular to the direction of travel. Most unnatural!
What's the cause of this silly curving? I include my full, current Wreckage.class below. The primary culprit is
private static float dv[] = {
-80.0F, -60.0F, -40.0F, -20.0F, 20.0F, 40.0F, 60.0F, 80.0F, 0.0F
};
Which I've scaled down considerably to
private static float dv[] = {
-20.0F, -15.0F, -10.0F, -5.0F, 5.0F, 10.0F, 15.0F, 20.0F, 0.0F
};
Acting in concert, in tick(), is the following. I've halved the magnitude of variable f1
float f = Time.tickLenFs();
float f2 = (float)v.length();
// float f1 = 10F / (f2 + 0.1F); //original; imparts more curvature at slower speed??
float f1 = 5F / (f2 + 0.1F); //half the curvature rate?
if(f1 > 1.0F)
f1 = 1.0F; //10m/s and slower (now 5m/s and slower)
f1 *= Wreckage.dv[lr] * f; //[dv] range was -80 to 80, now -20 to 20
The result is a VERY reduced tendency of curvature of motion in the horizontal plane; much more natural.
But this change by itself results in the wreckage bits remaining much more closely spaced for some time. This scheme seems to be used primarily to get the bits flying apart at a reasonable rate from the outset. To get the pieces initially flying apart I've implemented in the three relevant methods in Aircraft.class the application of an envelope of random velocity. For destruction in the air I apply an additional random velocity of up to 7m/s on each axis individually, and for destruction on the ground this envelope of dispersion is up to 30m/s (to get more 'spread' as the bits bounce across the ground).
For destruction on the ground I wanted to stop the silly looking instances of a cloud of pieces arcing upward and flying for a considerable distance before hitting the ground. For a planes crashing with a fairly high vertical velocity, the effect is of the ground having something of a rubber-like property, spring-launching the scores of parts on a long, arcing path. And so as part of the added random velocity component scheme just mentioned, for ground crashes the Z (vertical) component of velocity is ultimately limited to the range of 0.5-1.5m/s (formerly a single value of 5m/s was added) as set in setSpeed() by first making it zero in the method in Aircraft.class calling setSpeed. This has the pieces remaining more sensibly close to the ground
package com.maddox.il2.objects;
import com.maddox.JGP.*;
import com.maddox.il2.ai.RangeRandom;
import com.maddox.il2.ai.World;
import com.maddox.il2.engine.*;
import com.maddox.il2.game.Main; //NEW, for consideration of weather type
import com.maddox.il2.objects.air.Aircraft;
import com.maddox.il2.objects.effects.Explosions;
import com.maddox.il2.objects.ships.BigshipGeneric;
import com.maddox.il2.objects.ships.ShipGeneric;
import com.maddox.rts.*;
import com.maddox.sas1946.il2.util.TrueRandom; //NEW
// Referenced classes of package com.maddox.il2.objects:
// Wreck
public class Wreckage extends ActorMesh
{
static class ClipFilter
implements ActorFilter
{
public boolean isUse(Actor actor, double d)
{
return (actor instanceof BigshipGeneric) || (actor instanceof ShipGeneric);
}
ClipFilter()
{
}
}
class Interpolater extends Interpolate
{
private double deceleron(double d, float f) //d=velocity component (x, y or z), f=acceleration factor
{
if(d > 0.0D)
d -= (double)f * d * d;
else
d += (double)f * d * d;
return d;
}
public boolean tick()
{
float f = Time.tickLenFs();
float f2 = (float)v.length();
// float f1 = 10F / (f2 + 0.1F); //original; imparts more curvature at slower speed??
float f1 = 5F / (f2 + 0.1F); //half the curvature rate?
if(f1 > 1.0F)
f1 = 1.0F; //10m/s and slower (now 5m/s and slower)
f1 *= Wreckage.dv[lr] * f; //[dv] range was -80 to 80, now -20 to 20
pos.getAbs(Wreckage.p, Wreckage.o);
Wreckage.o.increment(W.z * f, W.y * f, -W.x * f);
Wreckage.oh.set(f1, 0.0F, 0.0F);
Wreckage.oh.transform(v);
Wreckage.o.setYaw(Wreckage.o.getYaw() - f1);
float f4 = A * f; //A originally = 0.02 / M; now A = M / 1,300,000^0.635
v.x = deceleron(v.x, f4);
v.y = deceleron(v.y, f4);
v.z = deceleron(v.z, f4);
v.z -= World.g() * f; //original
Wreckage.p.scaleAdd(f, v, Wreckage.p);
double d = World.land().HQ(Wreckage.p.x, Wreckage.p.y);
if(Wreckage.p.z <= d)
{
World.land().N(Wreckage.p.x, Wreckage.p.y, Wreckage.Nf);
Wreckage.N.set(Wreckage.Nf);
float f3;
if((f3 = (float)v.dot(Wreckage.N)) < 0.0F)
{
if(f3 < -40F)
f3 = -40F;
Wreckage.N.scale(2.0F * f3);
v.sub(Wreckage.N);
v.scale(0.5D);
if(World.land().isWater(Wreckage.p.x, Wreckage.p.y))
{
MsgDestroy.Post(Time.current(), actor);
Wreckage.WreckageONLYDrop_Water(Wreckage.p);
return false;
}
if(!World.land().isWater(Wreckage.p.x, Wreckage.p.y)) //NEW, for land effects (post v1.6 effects mod)
{
EffClouds effclouds = Main.cur().clouds;
if(effclouds.type() < 5 || World.cur().camouflage == World.CAMOUFLAGE_WINTER) //i.e., if NOT precipitation OR if winter, create dust/snow impact effect
Wreckage.WreckageONLYDrop_Land(Wreckage.p);
}
if(!bBoundToBeDestroyed && Math.abs(v.z) < 1.0D)
{
// MsgDestroy.Post(Time.current() + 0x13880L, actor); //original; 0x13880L = 80,000, or 80 seconds; 10 sec = 0x2710L
MsgDestroy.Post(Time.current() + 10000L, actor); //wreckage hangs about for 10 seconds after stopping
bBoundToBeDestroyed = true;
return false;
}
}
Wreckage.p.z = d;
}
pos.setAbs(Wreckage.p, Wreckage.o);
return true;
}
Interpolater()
{
}
}
public void setSpeed(Vector3d vector3d) //called by Aircraft.class and numerous plane classes
{
v.set(vector3d);
// v.z += 5D; //original
v.z += TrueRandom.nextDouble(0.5D, 1.5D);
if(v.z > 50D)
v.z = 50D;
}
private void construct(float f) //f = chunk mass
{
collide(true);
drawing(true);
getDimensions(sz);
M = f; //seems to be always zero...
float f1 = sz.x * sz.y + sz.x * sz.z + sz.z * sz.y; //f1 = 1/2 surface area
if(f1 < 0.01F)
f1 = 0.01F; //min sfc half-area (~6cm cube)
if(M < 0.001F) //important, because M is generally or always zero!
{
float f2 = sz.x * sz.y * sz.z; //f2 = volume
// if(f2 < 0.01F) //original
// f2 = 0.01F; //min volume (0.215m cube)
if(f2 < 0.001F) //lowered so as to not introduce a density turnover--keeps volume decreasing for the smallest pieces
f2 = 0.001F; //min volume (0.1m cube)
float f4 = f1 * 2.0F * 0.02F; //1/2 surface area
// M = 500F * Math.min(f2, f4); //min(vol, area), or min(8, 12*0.04) = 0.48; f4 generally smaller; typical range of M is roughly 2kg to 1400kg
M = 500F * f4;
}
//NEW; to increase by a random amount the mass of ALL pieces up to 1000kg (slight decrease for heavier); this introduces more even dispersion
float maxM = (float) Math.pow(M, 0.6) * 16F; //upper mass limit for randomizing, based on mass
M = TrueRandom.nextFloat(M, maxM);
float f3 = sz.x;
float f5 = sz.y;
float f6 = sz.z;
int i = 0;
int j = 1;
int k = 2;
if(f3 > f5)
{
float f7 = f3;
f3 = f5;
f5 = f7;
int l = i;
i = j;
j = l;
}
if(f5 > f6)
{
float f8 = f5;
f5 = f6;
f6 = f8;
int i1 = j;
j = k;
k = i1;
if(f3 > f5)
{
float f9 = f3;
f3 = f5;
f5 = f9;
int j1 = i;
i = j;
j = j1;
}
}
if(f5 * 8F < f6)
{
switch(i)
{
case 0: // '\0'
W.set(1.0F, 0.0F, 0.0F);
break;
case 1: // '\001'
W.set(0.0F, 1.0F, 0.0F);
break;
case 2: // '\002'
W.set(0.0F, 0.0F, 1.0F);
break;
}
wn = k;
} else
if(f3 * 3F < f5)
{
switch(k)
{
case 0: // '\0'
W.set(1.0F, 0.0F, 0.0F);
break;
case 1: // '\001'
W.set(0.0F, 1.0F, 0.0F);
break;
case 2: // '\002'
W.set(0.0F, 0.0F, 1.0F);
break;
}
wn = j;
} else
{
W.set(sz);
if(W.x < 1E-010F)
W.x += 1E-010F;
W.normalize();
wn = k;
}
wn = 60F + 80F / (wn + 0.2F);
W.scale(wn);
lr = World.Rnd().nextInt(0, 8);
// A = 0.02F / M; //original
A = (float) Math.pow(M / 1300000F, 0.635F); //4th experiment, in which bigger/higher mass = lower density = more rapidly slowed
interpPut(new Interpolater(), null, Time.current(), null);
MsgDestroy.Post(Time.current() + 0x1d4c0L, this); //1d4c0 = 120,000, or 120 sec
}
float DEG2RAD(float f)
{
return f * 0.01745329F;
}
float RAD2DEG(float f)
{
return f * 57.29578F;
}
public Object getSwitchListener(Message message)
{
return this;
}
public Wreckage(ActorHMesh actorhmesh, int i)
{
super(actorhmesh, i);
v = new Vector3d();
W = new Vector3f();
bBoundToBeDestroyed = false;
construct(actorhmesh.getChunkMass());
actorhmesh.hierMesh().setCurChunk(i);
if(actorhmesh instanceof Aircraft)
{
setOwner(actorhmesh, false, false, false);
netOwner = ((Aircraft)actorhmesh).netUser();
}
}
private static void WreckageONLYDrop_Water(Point3d point3d)
{
if(!Config.isUSE_RENDER())
return;
long l = Time.current();
if(l == timeLastWreckageDrop_Water)
{
double d = posLastWreckageDrop_Water.x - point3d.x;
double d1 = posLastWreckageDrop_Water.y - point3d.y;
if(d * d + d1 * d1 < 100D)
return;
}
pClipZ1.set(p);
pClipZ2.set(p);
pClipZ1.z -= 2D;
pClipZ2.z += 42D;
Actor actor = Engine.collideEnv().getLine(pClipZ2, pClipZ1, false, clipFilter, pClipRes);
if(Actor.isValid(actor))
{
return;
} else
{
timeLastWreckageDrop_Water = l;
posLastWreckageDrop_Water.set(point3d);
Explosions.WreckageDrop_Water(point3d);
return;
}
}
//NEW method, for post v1.6 effects mod; points to new method in Explosions.class
private static void WreckageONLYDrop_Land(Point3d point3d)
{
if(!Config.isUSE_RENDER())
return;
long l = Time.current();
if(l == timeLastWreckageDrop_Land)
{
double d = posLastWreckageDrop_Land.x - point3d.x;
double d1 = posLastWreckageDrop_Land.y - point3d.y;
if(d * d + d1 * d1 < 100D) //original; if d and d1 = 7.07, result is 100; 10 would be 200, 15 would be 450, 3.5 would be 25
return;
}
pClipZ1.set(p);
pClipZ2.set(p);
pClipZ1.z -= 2D;
pClipZ2.z += 42D;
Actor actor = Engine.collideEnv().getLine(pClipZ2, pClipZ1, false, clipFilter, pClipRes);
if(Actor.isValid(actor))
{
return;
} else
{
timeLastWreckageDrop_Land = l;
posLastWreckageDrop_Land.set(point3d);
Explosions.WreckageDrop_Land(point3d);
return;
}
}
public static Wreck makeWreck(ActorHMesh actorhmesh, int i)
{
HierMesh hiermesh = actorhmesh.hierMesh();
hiermesh.setCurChunk(i);
int ai[] = hiermesh.getSubTrees(hiermesh.chunkName());
if(ai == null || ai.length < 1)
return null;
double d = 0.0D;
Point3d point3d = new Point3d(0.0D, 0.0D, 0.0D);
Point3f point3f = new Point3f();
Point3f point3f1 = new Point3f();
Point3d point3d1 = new Point3d();
for(int j = 0; j < ai.length; j++)
{
hiermesh.setCurChunk(ai[j]);
if(hiermesh.isChunkVisible())
{
hiermesh.getChunkCurVisBoundBox(point3f, point3f1);
point3d1.set(point3f);
point3d1.add(point3f1.x, point3f1.y, point3f1.z);
point3d1.scale(0.5D);
hiermesh.getChunkLocObj(LO);
LO.transform(point3d1);
point3f1.sub(point3f);
double d1 = point3f1.x * point3f1.y * point3f1.z;
double d2 = 500D;
double d3 = d1 * d2;
d += d3;
point3d1.scale(d3);
point3d.add(point3d1);
}
}
if(d > 0.0001D)
{
point3d.scale(1.0D / d);
} else
{
hiermesh.setCurChunk(i);
hiermesh.getChunkLocObj(LO);
point3d.set(LO.getPoint());
}
hiermesh.setCurChunk(i);
hiermesh.getChunkLocObj(LO);
LO.getPoint().set(point3d);
HierMesh hiermesh1 = new HierMesh(hiermesh, i, LO);
Loc loc = new Loc();
actorhmesh.pos.getAbs(loc);
LO.add(loc);
return new Wreck(hiermesh1, LO);
}
public static String SMOKE = "EFFECTS/Smokes/SmokeBlack_Wreckage.eff"; //invoked in Aircraft.class, methods cut(String s) and cut_Subtrees(String s); 50% chance of occurring; lasts 3sec
public static String SMOKE_EXPLODE = "EFFECTS/Smokes/SmokeBlack_Wreckage_Explode.eff"; ;//invoked in Aircraft.class, method explode(); lasts 1 sec longer than Wreckage_Burn.eff
public static String FIRE = "EFFECTS/Explodes/Wreckage_Burn.eff"; ;//invoked in Aircraft.class, method explode()
public static String FIRE_EXPLODE_CRASH = "EFFECTS/Explodes/Wreckage_Burn_Crash.eff"; ;//NEW: invoked in Aircraft.class, method explode()
public static String SMOKE_EXPLODE_CRASH = "EFFECTS/Explodes/Wreckage_Smoke_Crash.eff"; ;//NEW; invoked in Aircraft.class, method explode()
private float M;
private float A;
private int lr;
private Vector3d v;
private Vector3f W;
private static Vector3d N = new Vector3d();
private static Vector3f Nf = new Vector3f();
private static Vector3f sz = new Vector3f();
private static Point3d p = new Point3d();
private static Orient o = new Orient();
private static Orient oh = new Orient();
private static float wn;
// private static float dv[] = { //original; imparts too much curvature in motion?
// -80.0F, -60.0F, -40.0F, -20.0F, 20.0F, 40.0F, 60.0F, 80.0F, 0.0F
// };
private static float dv[] = {
-20.0F, -15.0F, -10.0F, -5.0F, 5.0F, 10.0F, 15.0F, 20.0F, 0.0F
};
private boolean bBoundToBeDestroyed;
public NetObj netOwner;
private static long timeLastWreckageDrop_Water = 0L;
private static Point3d posLastWreckageDrop_Water = new Point3d();
private static Point3d pClipZ1 = new Point3d();
private static Point3d pClipZ2 = new Point3d();
private static Point3d pClipRes = new Point3d();
static ClipFilter clipFilter = new ClipFilter();
private static Loc LO = new Loc();
private static long timeLastWreckageDrop_Land = 0L; //NEW
private static Point3d posLastWreckageDrop_Land = new Point3d(); //NEW
}