Posted: 17 Sep 2007, 20:49
by Argh
I can make it use case behaviour, no need for a new class for the beam caps but it should not trigger without the modder invoking it since beamcaps work well for the default graphics.
No, because that will involve 1-3 IF statements, slowing down that code. It'd be faster, better code if LaserBolt was a separate weapon type.
And I didn't just remove the caps, I removed the second copy of the texture, which was being drawn 4 times, that made the center "color" in order to emulate OTA stuff for backwards-compatibility, because with ColorMap, you don't need to have them anyhow. What I gave you was a completely new animal- using 8 fewer polygons, 1 fewer textures, and a lot less code-per-frame. Just build a new case in WeaponDefHandler, looking for "beamBolt" in the TDF section, it'll keep everybody happier. What I proposed was a completely new projectile type, and I'd rather have it perform like I wrote it (er, aside from that mistake with deleteme, oops) than make LaserCannonProjectile even longer and slower.
We need to quit doing that to Spring's weapons- it's better to have 50 different classes than 1 that does everything, because each of the 50, while they will be limited in certain ways, will run sooooo much faster than 1 will that the overall cost savings will win every time. Even if an IF returns immediately because the value returned is FALSE, that's an extra step we don't want in something as performance-crucial as weapons code. Look at the horrible performance of missiles in general, due to both the really horrible smoketrail code and the messy, option-filled series of IF-trees that are a part of it. It's exactly what we don't want. It'd be better to have different missile types (dancing, wobbling, gravity-arced, non-guided, etc.) than to have what we actually have, which is one projectile that can do all of those things- and must waste many cycles every time such a projectile is created on irrelevant code.
When I get the chance to sit down with it, I also have some other weapons I'd like to add as entirely new classes, such as a weapon that can behave like poison gas / fire / radiation / whatever (it's very easy, just needs a new type of explosion behavior)... and we should finally get rid of the default explosion code entirely, and simply point towards default CustomExplosionGenerators that perform the same functions (easy to do, and would remove more IFs from Spring's code).
Spring never decides to return true for you, only a "return true" statement will.
No, but Spring supplies the variable values for (heading, pitch), the two vars populated every time AimWeapon is called. Try your example code out, using some simple NanoBlobs aiming code or a OTA tank, I suspect you will start to see why I'm getting all grumpy about this issue
As for a fix... the algorithm needs to be on the COBHandler side. Pseudocode:
Unit.cpp (I
think that's where this function call occurs): CALL COBHandler AimWeapon
COBHandler: CALL AimWeapon, GET value of Tolerance and pitchTolerance (again, those should both work).
AimWeapon: IF COB contains wait-for-turn AND heading OR pitch (
I think COBhandler just looks for the first two var assignments within that function, you cannot define heading, pitch inversely, or as third / fourth variables, IIRC from testing), then:
Return TRUE IF piecenum (normalized vector) / target (normalized vector) <= Tolerance AND <= pitchTolerance. That's all that needs to happen. A little nasty, but I doubt if it includes a lot more logic than is already in that code.
Posted: 17 Sep 2007, 22:35
by Argh
Zeroing tolerance would force a constant reaim.
Which is how it used to work. So the default value, in the source, was 500, IIRC.
And FLOZi, show me another way to do that, I'm open to another approach, but the only other way I can think of to do this is to check the AimFromWeapon(piecenum) and IF piecenum (normalized vector) / piecenum centroid to target (vector) <= tolerance THEN return TRUE.
Any other ideas?
Personally I think we should unify the weapons and leave only an instahitweapon and a projectileweapon type behind, the current fragmentation is already a maintenance nightmare and limits flexibility, leading to issues like lightning not damaging shields or missiles not obeying accuracy. If you want to be the weapon maintainer of 50+ classes be my guest but I doubt you're going to become the primary dev for these things.
You're right, I am not likely to become the primary dev for these things.
A, because my C++ skills overall are only so-so at best. I know it, I'm ok with that.
B, because I haven't even bothered trying to compile since the whole move to MingW, etc., because I only ever got it to compile in MS C++ 2005, and I haven't felt like wasting several days of my life, AGAIN, just to be able to compile something that I do not have commit access to. I have a game to build, and that's a higher priority, frankly. I just thought that the BeamBolt was a good idea, and since you've been messing with this area of the code, I thought that you might get it implemented.
C, because there are very few areas of Spring that I am sufficiently interested in and understand well enough to poke my nose in. I wouldn't touch the pathfinder code, even though I know it has problems- I know I'm not qualified to mess with it, and I don't want to screw it up in some subtle way trying, either.
Weapons, however, are (for the most part) rather simple code. Moreover, they directly affect game design itself, which is my primary interest. So, any changes to them, that I am aware of naturally attracts my interest...
Now, as to your specific proposal:
If you actually build your omni-weapon, and get the Spring devs to let you commit that, and make it mandatory, and I will immediately take a very, very keen interest in building a separate trunk of Spring, which will not include that
I can tell you, with absolute 100% certainty, that your idea is a bad one.
If you wanna build something like that as an OPTIONAL THING, for people who don't mind terrifically-slow code for every weapon particle... be my guest. There's no harm in watching you prove me right
"Hard to maintain" to me sounds like, "I am too lazy to write a proper #Include for all functions that should be common to all properly-separated code segments, and document why that code is #include for commonality and
true ease of maintenance".
If you want to fix the whole collideFriendly mess, the best way to do that is to write up a new bit of code, ProjectileCollisions.cpp... put the relevant code for validating avoid / collide Friendly / Feature into that, and then have every single weapon class #include that code and call that function, not to build a ridiculous bloat of IF trees
Posted: 17 Sep 2007, 22:50
by Argh
A single if statement to check a single value is not a major performance hog.
But it gets called any time any weapon particle is created. With rapid-fire weapons, and lots of Units all firing, that may mean 10, 20, 30 all being updated, and running that if statement. Is that if in the middle of a function that runs every sync cycle, or is it just called once? An if is not an if... frequency is the issue. I learned that when writing the suspension simulation code for vehicles in PURE, which at this point only 3 people have seen besides me- frequency is the bane we want to avoid in sync code, and whatever can be unsynced and run through the stack between sync cycles should do so.
If you have 50 weapons, all using 50 different pieces of code, you're right, the performance advantages are fairly small. However, most Spring games aren't going to even use all 50 weapon classes (assuming there were 50, which there aren't), and some classes run much faster than others, but are called less often, etc.
Then the advantages of smaller, faster, tighter classes become more and more obvious.
Look, people... before I read
one more stupid, condescending, Argh-is-an-idiot post in this thread, I want you to go build yourselves a Missile weapon that burst-fires 50 missiles, with smoke trails and heightMod=1.0 and with a burst delay of 0.1.
Go do that, equip a Unit with it, turn on shadow rendering, and watch Spring become a slideshow when that weapon fires.
This is what one, "I am trying to be a single weapon that does many things" weapon does, people (that, and the horrible drawing code for the smoketrails)...
Then do that with a plasma weapon, which uses some of the shortest weapons code in Spring.
Then come back to this conversation.
Posted: 17 Sep 2007, 23:14
by Argh
C++ <> LUA. You don't really have inheritance. But you can use sub-functions and includes to achieve exactly the same thing.
Here, for all of you people who are coders, but too lazy to go look at why ol' Argh is on his milk-crate and grumpy about these issues... here is the current guided missile function, in a small font:
#include "StdAfx.h"
#include "Game/Camera.h"
#include "Game/GameHelper.h"
#include "LogOutput.h"
#include "Map/Ground.h"
#include "Matrix44f.h"
#include "MissileProjectile.h"
#include "myMath.h"
#include "Sim/Projectiles/ProjectileHandler.h"
#include "Rendering/GL/myGL.h"
#include "Rendering/GL/VertexArray.h"
#include "Rendering/UnitModels/3DModelParser.h"
#include "Rendering/UnitModels/UnitDrawer.h"
#include "Sim/Misc/GeometricObjects.h"
#include "Sim/Projectiles/Unsynced/SmokeTrailProjectile.h"
#include "Sim/Units/Unit.h"
#include "Sim/Weapons/WeaponDefHandler.h"
#include "Sync/SyncTracer.h"
#include "mmgr.h"
static const float Smoke_Time=60;
CR_BIND_DERIVED(CMissileProjectile, CWeaponProjectile, (float3(0,0,0),float3(0,0,0),NULL,0,0,0,NULL,NULL,float3(0,0,0)));
CR_REG_METADATA(CMissileProjectile,(
CR_MEMBER(dir),
CR_MEMBER(maxSpeed),
CR_MEMBER(curSpeed),
CR_MEMBER(ttl),
CR_MEMBER(areaOfEffect),
CR_MEMBER(age),
CR_MEMBER(oldSmoke),
CR_MEMBER(oldDir),
CR_MEMBER(target),
CR_MEMBER(decoyTarget),
CR_MEMBER(drawTrail),
CR_MEMBER(numParts),
CR_MEMBER(targPos),
CR_MEMBER(isWobbling),
CR_MEMBER(wobbleDir),
CR_MEMBER(wobbleTime),
CR_MEMBER(wobbleDif),
CR_MEMBER(danceMove),
CR_MEMBER(danceCenter),
CR_MEMBER(danceTime),
CR_MEMBER(isDancing),
CR_MEMBER(extraHeight),
CR_MEMBER(extraHeightDecay),
CR_MEMBER(extraHeightTime),
CR_RESERVED(16)
));
CMissileProjectile::CMissileProjectile(const float3& pos, const float3& speed, CUnit* owner, float areaOfEffect, float maxSpeed, int ttl, CUnit* target, const WeaponDef *weaponDef, float3 targetPos)
: CWeaponProjectile(pos, speed, owner, target, targetPos, weaponDef, 0, true),
ttl(ttl),
maxSpeed(maxSpeed),
target(target),
dir(speed),
oldSmoke(pos),
age(0),
drawTrail(true),
numParts(0),
areaOfEffect(areaOfEffect),
decoyTarget(0),
targPos(targetPos),
wobbleTime(1),
wobbleDir(0, 0, 0),
wobbleDif(0, 0, 0),
danceMove(0, 0, 0),
danceCenter(0, 0, 0),
danceTime(1),
isDancing(weaponDef? (weaponDef->dance > 0): false),
isWobbling(weaponDef? (weaponDef->wobble > 0): false),
extraHeightTime(0)
{
curSpeed=speed.Length();
dir.Normalize();
oldDir=dir;
if(target)
AddDeathDependence(target);
SetRadius(0.0f);
if((weaponDef)&&(!weaponDef->visuals.modelName.empty())){
S3DOModel* model = modelParser->Load3DO(string("objects3d/")+weaponDef->visuals.modelName,1,colorTeam);
if(model){
SetRadius(model->radius);
}
}
drawRadius=radius+maxSpeed*8;
ENTER_MIXED;
float3 camDir=(pos-camera->pos).Normalize();
if(camera->pos.distance(pos)*0.2f+(1-fabs(camDir.dot(dir)))*3000 < 200)
drawTrail=false;
ENTER_SYNCED;
castShadow=true;
#ifdef TRACE_SYNC
tracefile << "New missile: ";
tracefile << pos.x << " " << pos.y << " " << pos.z << " " << speed.x << " " << speed.y << " " << speed.z << "\n";
#endif
if(target)
target->IncomingMissile(this);
if((weaponDef)&&(weaponDef->trajectoryHeight>0)){
float dist=pos.distance(targPos);
extraHeight=dist*weaponDef->trajectoryHeight;
if(dist<maxSpeed)
dist=maxSpeed;
extraHeightTime=(int)(dist/*+pos.distance(targPos+UpVector*dist))*0.5f*//maxSpeed);
extraHeightDecay=extraHeight/extraHeightTime;
}
}
CMissileProjectile::~CMissileProjectile(void)
{
}
void CMissileProjectile::DependentDied(CObject* o)
{
if(o==target)
target=0;
if(o==decoyTarget)
decoyTarget=0;
CWeaponProjectile::DependentDied(o);
}
void CMissileProjectile::Collision()
{
float h=ground->GetHeight2(pos.x,pos.z);
if (weaponDef->waterweapon && h < pos.y) return; //let waterweapons travel in water
if(h>pos.y && fabs(speed.y)>0.001f)
pos-=speed*std::min((float)1,float((h-pos.y)/fabs(speed.y)));
if (weaponDef->visuals.smokeTrail)
SAFE_NEW CSmokeTrailProjectile(pos,oldSmoke,dir,oldDir,owner,false,true,7,Smoke_Time,0.6f,drawTrail);
//helper->Explosion(pos,damages,areaOfEffect,owner);
CWeaponProjectile::Collision();
oldSmoke=pos;
}
void CMissileProjectile::Collision(CUnit *unit)
{
if (weaponDef->visuals.smokeTrail)
SAFE_NEW CSmokeTrailProjectile(pos,oldSmoke,dir,oldDir,owner,false,true,7,Smoke_Time,0.6f,drawTrail);
// unit->DoDamage(damages,owner);
//helper->Explosion(pos,damages,areaOfEffect,owner);
CWeaponProjectile::Collision(unit);
oldSmoke=pos;
}
void CMissileProjectile::Update(void)
{
ttl--;
if(ttl>0){
if(curSpeed<maxSpeed)
curSpeed+=weaponDef->weaponacceleration;
float3 targSpeed(0,0,0);
if(weaponDef->tracks && (decoyTarget || target)){
if(decoyTarget){
targPos=decoyTarget->pos;
targSpeed=decoyTarget->speed;
} else {
targSpeed=target->speed;
if((target->physicalState==CSolidObject::Flying && (target->midPos-pos).SqLength()<150*150) || !owner)
targPos=target->midPos;
else
targPos=helper->GetUnitErrorPos(target,owner->allyteam);
}
}
if (isWobbling) {
--wobbleTime;
if (wobbleTime == 0) {
float3 newWob = gs->randVector();
wobbleDif = (newWob - wobbleDir) * (1.0f / 16);
wobbleTime = 16;
}
wobbleDir += wobbleDif;
dir += wobbleDir * weaponDef->wobble * (owner? (1 - owner->limExperience * 0.5f): 1);
dir.Normalize();
}
if (isDancing) {
--danceTime;
if (danceTime <= 0) {
danceMove = gs->randVector() * weaponDef->dance - danceCenter;
danceCenter += danceMove;
danceTime = 8;
}
pos += danceMove;
}
float3 orgTargPos(targPos);
float dist=targPos.distance(pos);
if(dist==0)
dist=0.1f;
if(extraHeightTime){
extraHeight-=extraHeightDecay;
--extraHeightTime;
targPos.y+=extraHeight;
dir.y-=extraHeightDecay/dist;
//geometricObjects->AddLine(pos,targPos,3,1,1);
}
float3 dif(targPos + targSpeed*(dist/maxSpeed)*0.7f - pos);
dif.Normalize();
float3 dif2=dif-dir;
float tracking=weaponDef->turnrate;
if(dif2.Length()<tracking){
dir=dif;
} else {
dif2-=dir*(dif2.dot(dir));
dif2.Normalize();
dir+=dif2*tracking;
dir.Normalize();
}
speed=dir*curSpeed;
targPos=orgTargPos;
} else {
speed*=0.995f;
speed.y+=gs->gravity;
dir=speed;
dir.Normalize();
}
pos+=speed;
age++;
numParts++;
if(weaponDef->visuals.smokeTrail && !(age&7)){
CSmokeTrailProjectile* tp=SAFE_NEW CSmokeTrailProjectile(pos,oldSmoke,dir,oldDir,owner,age==8,false,7,Smoke_Time,0.6f,drawTrail);
oldSmoke=pos;
oldDir=dir;
numParts=0;
useAirLos=tp->useAirLos;
if(!drawTrail){
ENTER_MIXED;
float3 camDir=(pos-camera->pos).Normalize();
if(camera->pos.distance(pos)*0.2f+(1-fabs(camDir.dot(dir)))*3000 > 300)
drawTrail=true;
ENTER_SYNCED;
}
}
//CWeaponProjectile::Update();
}
void CMissileProjectile::Draw(void)
{
float3 interPos=pos+speed*gu->timeOffset;
inArray=true;
float age2=(age&7)+gu->timeOffset;
float color=0.6f;
unsigned char col[4];
if (weaponDef->visuals.smokeTrail)
if(drawTrail){ //draw the trail as a single quad
float3 dif(interPos-camera->pos);
dif.Normalize();
float3 dir1(dif.cross(dir));
dir1.Normalize();
float3 dif2(oldSmoke-camera->pos);
dif2.Normalize();
float3 dir2(dif2.cross(oldDir));
dir2.Normalize();
float a1=(1-float(0)/(Smoke_Time))*255;
a1*=0.7f+fabs(dif.dot(dir));
float alpha=min((float)255,max(float(0),a1));
col[0]=(unsigned char) (color*alpha);
col[1]=(unsigned char) (color*alpha);
col[2]=(unsigned char) (color*alpha);
col[3]=(unsigned char) alpha;
unsigned char col2[4];
float a2=(1-float(age2)/(Smoke_Time))*255;
if(age<8)
a2=0;
a2*=0.7f+fabs(dif2.dot(oldDir));
alpha=min((float)255,max((float)0,a2));
col2[0]=(unsigned char) (color*alpha);
col2[1]=(unsigned char) (color*alpha);
col2[2]=(unsigned char) (color*alpha);
col2[3]=(unsigned char) alpha;
float size=(1);
float size2=(1+(age2*(1/Smoke_Time))*7);
float txs=weaponDef->visuals.texture2->xend - (weaponDef->visuals.texture2->xend-weaponDef->visuals.texture2->xstart)*(age2/8.0f);//(1-age2/8.0f);
va->AddVertexTC(interPos-dir1*size, txs, weaponDef->visuals.texture2->ystart, col);
va->AddVertexTC(interPos+dir1*size, txs, weaponDef->visuals.texture2->yend, col);
va->AddVertexTC(oldSmoke+dir2*size2, weaponDef->visuals.texture2->xend, weaponDef->visuals.texture2->yend, col2);
va->AddVertexTC(oldSmoke-dir2*size2, weaponDef->visuals.texture2->xend, weaponDef->visuals.texture2->ystart, col2);
} else { //draw the trail as particles
float dist=pos.distance(oldSmoke);
float3 dirpos1=pos-dir*dist*0.33f;
float3 dirpos2=oldSmoke+oldDir*dist*0.33f;
for(int a=0;a<numParts;++a){
//float a1=1-float(a)/Smoke_Time;
float alpha=255;
col[0]=(unsigned char) (color*alpha);
col[1]=(unsigned char) (color*alpha);
col[2]=(unsigned char) (color*alpha);
col[3]=(unsigned char)alpha;//min(255,max(0,a1*255));
float size=(1+(a*(1/Smoke_Time))*7);
float3 pos1=CalcBeizer(float(a)/(numParts),pos,dirpos1,dirpos2,oldSmoke);
va->AddVertexTC(pos1+( camera->up+camera->right)*size, ph->smoketex[0].xstart, ph->smoketex[0].ystart, col);
va->AddVertexTC(pos1+( camera->up-camera->right)*size, ph->smoketex[0].xend, ph->smoketex[0].ystart, col);
va->AddVertexTC(pos1+(-camera->up-camera->right)*size, ph->smoketex[0].xend, ph->smoketex[0].ystart, col);
va->AddVertexTC(pos1+(-camera->up+camera->right)*size, ph->smoketex[0].xstart, ph->smoketex[0].ystart, col);
}
}
//rita flaren
col[0]=255;
col[1]=210;
col[2]=180;
col[3]=1;
float fsize = radius*0.4f;
va->AddVertexTC(interPos-camera->right*fsize-camera->up*fsize,weaponDef->visuals.texture1->xstart,weaponDef->visuals.texture1->ystart,col);
va->AddVertexTC(interPos+camera->right*fsize-camera->up*fsize,weaponDef->visuals.texture1->xend,weaponDef->visuals.texture1->ystart,col);
va->AddVertexTC(interPos+camera->right*fsize+camera->up*fsize,weaponDef->visuals.texture1->xend,weaponDef->visuals.texture1->yend,col);
va->AddVertexTC(interPos-camera->right*fsize+camera->up*fsize,weaponDef->visuals.texture1->xstart,weaponDef->visuals.texture1->yend,col);
/* col[0]=200;
col[1]=200;
col[2]=200;
col[3]=255;
float3 r=dir.cross(UpVector);
r.Normalize();
float3 u=dir.cross(r);
va->AddVertexTC(interPos+r*1.0f,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos-r*1.0f,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos+dir*9,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos+dir*9,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos+u*1.0f,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos-u*1.0f,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos+dir*9,1.0f/16,1.0f/16,col);
va->AddVertexTC(interPos+dir*9,1.0f/16,1.0f/16,col);*/
}
void CMissileProjectile::DrawUnitPart(void)
{
float3 interPos=pos+speed*gu->timeOffset;
glPushMatrix();
float3 rightdir;
if(dir.y!=1)
rightdir=dir.cross(UpVector);
else
rightdir=float3(1,0,0);
rightdir.Normalize();
float3 updir=rightdir.cross(dir);
CMatrix44f transMatrix;
transMatrix[0]=-rightdir.x;
transMatrix[1]=-rightdir.y;
transMatrix[2]=-rightdir.z;
transMatrix[4]=updir.x;
transMatrix[5]=updir.y;
transMatrix[6]=updir.z;
transMatrix[8]=dir.x;
transMatrix[9]=dir.y;
transMatrix[10]=dir.z;
transMatrix[12]=interPos.x+dir.x*radius*0.9f;
transMatrix[13]=interPos.y+dir.y*radius*0.9f;
transMatrix[14]=interPos.z+dir.z*radius*0.9f;
glMultMatrixf(&transMatrix[0]);
glCallList(modelDispList);
glPopMatrix();
}
int CMissileProjectile::ShieldRepulse(CPlasmaRepulser* shield,float3 shieldPos, float shieldForce, float shieldMaxSpeed)
{
float3 sdir=pos-shieldPos;
sdir.Normalize();
if(ttl > 0){
float3 dif2=sdir-dir;
float tracking=max(shieldForce*0.05f,weaponDef->turnrate*2); //steer away twice as fast as we can steer toward target
if(dif2.Length()<tracking){
dir=sdir;
} else {
dif2-=dir*(dif2.dot(dir));
dif2.Normalize();
dir+=dif2*tracking;
dir.Normalize();
}
return 2;
}
return 0;
}
void CMissileProjectile::DrawS3O(void)
{
unitDrawer->SetS3OTeamColour(colorTeam);
DrawUnitPart();
}
Do we really want to add every other possible weapon to this horrible bloat? Does anybody really doubt that therein lies a nightmare? Does anybody really think this will get easier to maintain, when it gets larger and larger?
Or should we figure out what properties should be common to all weapons, which properties should be common to a large sub-set of all weapons, make these properties into one, relatively-short include, and be in a better position to maintain and add new classes?