Tuesday, October 23, 2007

Tornados in Cardboard Land

I started working on a wind area trigger within torquescript, and ended up creating a tornado because that is a fairly interesting effect to recreate. As this is a fairly short script, I'll just paste it here with all my comments and possibly add some stuff.
The flow is this: I create a trigger of my defined type, which should call the onAdd callback to initialise my tornado shape and particle emitter. However, somehow the onAdd isn't being called, so I'm just doing it explicitly. There are two main features of this script, a loop that runs every 50 ms when a player is intersecting my trigger, which takes the difference in positions between the player and trigger, then uses that information to build up some impulse vectors to pull the player in, and spin him around the z axis of my trigger. The second loop is independent of intersection, and is used to move the object around in a random manner. I created a momentum vector for the XY plane to keep the thing from just jumping around, and devised an "adjustment" vector to pull it towards the initial position if we stray too far from there.

Here are the two important functions:

function WindTrigger::onTickTrigger(%this,%trigger) {
%max = %trigger.getNumObjects();
for (%n=0;%n<%max;%n++) {
%obj = %trigger.getObject(%n);
if (%obj.getClassName() $= "Player") {
//find XY distance to player
%pos = VectorSub(%obj.getPosition(),%trigger.getWorldBoxCenter());
%posX = getWord(%pos,0);
%posY = getWord(%pos,1);
%dist = VectorLen(VectorNormalize(setWord(%pos, 2, "0.0")));
//calculate circular tangent vector and apply centripetal force to maintain circular movement
%windX = -%posY - 5*%posX;//mSin(%posX)*mCos(%posY);
%windY = %posX - 5*%posY;//mSin(%posY)*mCos(%posZ);
%windZ = 10;//mSin(%posZ)*mCos(%posX);
%obj.applyImpulse(%obj.getPosition(),VectorScale(%windX SPC %windY SPC %windZ,100*(1.5-%dist)));
}
}
Parent::onTickTrigger(%this,%trigger);
}

function WindTrigger::moveMe(%this,%trigger) {
//get our scale
%scale = %trigger.getScale();
%scaleX = getWord(%scale,0);
%scaleY = getWord(%scale,1);

//compute an adjustment vector to apply on top of
//our movement to keep within some distance of original position
%pos = %trigger.getPosition();
%adjustment = VectorSub(%trigger.startPos,%pos);
%adjDist = VectorLen(%adjustment);
%adjustment = VectorNormalize(%adjustment);
%adjX = 0;
%adjY = 0;
//stay within 25 units of original position (or try to)
if (%adjDist > 25) {
%adjX = getWord(%adjustment,0);
%adjY = getWord(%adjustment,1);
}

//random(-.25,.25) with a step of .01
%trigger.mvX += getRandom(-25,25)/100.0 + %adjX;
%trigger.mvY += getRandom(-25,25)/100.0 + %adjY;
//cap movement to [-1,1]
if (%trigger.mvX <= -1)
%trigger.mvX = -1;
if (%trigger.mvX >= 1)
%trigger.mvX = 1;

if (%trigger.mvY <= -1)
%trigger.mvY = -1;
if (%trigger.mvY >= 1)
%trigger.mvY = 1;

//find our new position
%posX = getWord(%pos,0)+%trigger.mvX;
%posY = getWord(%pos,1)+%trigger.mvY;
%posZ = getTerrainHeight(%posX+5 SPC %posY-5);

//move trigger
%trigger.position = (%posX) SPC (%posY) SPC %posZ;
%trigger.setScale(%trigger.getScale());

//adjust to center for inner objects
%posX += %scaleX/2;
%posY -= %scaleY/2;
%pos = %posX SPC %posY SPC %posZ;

//update StaticShape and ParticleEmitter positions
%trigger.myTornado.position = %pos;
%trigger.myTornado.setScale(%trigger.myTornado.getScale());
%trigger.myParticles.position = %pos;
%trigger.myParticles.setScale(%trigger.myParticles.getScale());

//make sure we get called again
%this.schedule(100,"moveMe",%trigger);
}

It works pretty well, but I haven't added anything to turn off all this simulation when the player is not near the tornado area. I think I may just put a large trigger around the area to enable/disable the tornado set when the player enters and leaves the area. I'll have to play around with this to see if it is sufficiently useable, otherwise I might need to turn down the delays so it doesn't use up as much resources.

No comments: