Recipe 5.3: Helicopter

    Helicopters and other flying vehicles are very popular in Second Life. This recipe shows how to create a helicopter. The helicopter is capable of carrying up to four avatars at once. The helicopter is flown very similarly to how an avatar is flown.

Figure 5.7: A Helicopter

A Helicopter

    The boat shares some characteristics with the car. However, there are some important differences.

  • The helicopter should roll slightly when it turns.
  • The helicopter hovers in the air.
  • The helicopter spins its rotors whenever a driver is seated.
  • The helicopter uses different sounds.
  • The helicopter has no wheels to turn.
  • The helicopter can leave the ground.

    The main script for the helicopter is located in the root prim. This script is shown in Listing 5.6.

Listing 5.6: Helicopter Script (Helicopter.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/

float forward_power = 15; //Power used to go forward (1 to 30)
float reverse_power = -15; //Power ued to go reverse (-1 to -30)
float turning_ratio = 2.0; //How sharply the vehicle turns. Less is more sharply. (.1 to 10)
string sit_message = "Ride"; //Sit message
string not_owner_message = "You are not the owner of this vehicle ..."; //Not owner message
float VERTICAL_THRUST = 7;
float ROTATION_RATE = 2.0;      //  Rate of turning  

resetY()
{
    rotation rot = llGetRot();
    llSetRot(rot);
}

default
{
    state_entry()
    {
        llSetSitText(sit_message);
        // forward-back,left-right,updown
        llSitTarget(<0.2,0,0.45>, ZERO_ROTATION );
        
        llSetCameraEyeOffset(<-8, 0.0, 5.0>);
        llSetCameraAtOffset(<1.0, 0.0, 2.0>);
        
        llPreloadSound("helicopter_run");
        
        //car
       llSetVehicleType(VEHICLE_TYPE_AIRPLANE);

       llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.1);
       llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.1);
       llSetVehicleFloatParam(VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 10);
       llSetVehicleFloatParam(VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 10);

       llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_TIMESCALE, 0.2);
       llSetVehicleFloatParam(VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 10);
       llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.2);
       llSetVehicleFloatParam(VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.1);

       llSetVehicleVectorParam(VEHICLE_LINEAR_FRICTION_TIMESCALE, <1,1,1>);
       llSetVehicleVectorParam(VEHICLE_ANGULAR_FRICTION_TIMESCALE, <1,1000,1000>);

       llSetVehicleFloatParam(VEHICLE_BUOYANCY, 0.9);

        llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 1 );
        llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 2 );

        llSetVehicleFloatParam( VEHICLE_BANKING_EFFICIENCY, 1 );
        llSetVehicleFloatParam( VEHICLE_BANKING_MIX, 0.5 );
        llSetVehicleFloatParam( VEHICLE_BANKING_TIMESCALE, .5 );
        
        
    }
    
    changed(integer change)
    {
        
        
        if (change & CHANGED_LINK)
        {
            
            key agent = llAvatarOnSitTarget();
            if (agent)
            {                
                if (agent != llGetOwner())
                {
                    llSay(0, not_owner_message);
                    llUnSit(agent);
                    llPushObject(agent, <0,0,50>, ZERO_VECTOR, FALSE);
                }
                else
                {
                    llMessageLinked(LINK_ALL_CHILDREN , 0, "start", NULL_KEY);
                    
                    llSleep(.4);
                    llSetStatus(STATUS_PHYSICS, TRUE);
                    llSetStatus(STATUS_ROTATE_Y,TRUE);
                    llSleep(.1);
                    llRequestPermissions(agent, PERMISSION_TRIGGER_ANIMATION | PERMISSION_TAKE_CONTROLS);

                    llLoopSound("helicopter_run",1);
                }
            }
            else
            {
                llStopSound();
                llMessageLinked(LINK_ALL_CHILDREN , 0, "stop", NULL_KEY);
                
                llSetStatus(STATUS_PHYSICS, FALSE);
                llSleep(.4);
                llReleaseControls();
                llTargetOmega(<0,0,0>,PI,0);
                
                llResetScript();
            }
        }
        
    }
    
    run_time_permissions(integer perm)
    {
        if (perm) {
            llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_RIGHT | CONTROL_LEFT | CONTROL_ROT_RIGHT | CONTROL_ROT_LEFT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE);
        }
    }
    
    control(key id, integer level, integer edge)
    {
        vector angular_motor;
        

        // going forward, or stop going forward
        if(level & CONTROL_FWD)
        {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <forward_power,0,0>);
        } else if(edge & CONTROL_FWD)
        {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
        }
        
        
        // going back, or stop going back
        if(level & CONTROL_BACK)
        {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <reverse_power,0,0>);
        }
        else if(edge & CONTROL_BACK)
        {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
        }
        
        // turning
        if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
        {
            angular_motor.x += 25;
        }
        
        if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT))
        {
            angular_motor.x -= 25;
        }
        
        
        // going up or stop going up
        if(level & CONTROL_UP) {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,VERTICAL_THRUST>);
        } else if (edge & CONTROL_UP) {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
        }
        
        // going down or stop going down
        
        if(level & CONTROL_DOWN) {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,-VERTICAL_THRUST>);
        } else if (edge & CONTROL_DOWN) {
            llSetVehicleVectorParam(VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
        }

        angular_motor.y = 0;
        llSetVehicleVectorParam(VEHICLE_ANGULAR_MOTOR_DIRECTION, angular_motor);
        


    } //end control   
    
    
    
} //end default

    The next few sections will examine helicopter script in detail.

Initializing the Helicopter

    The helicopter, like the car, is initialized in the state_entry event handler. First, the vehicle type is set to airplane. Airplane includes all air-based vehicles. For more information on airplane parameters refer to Tables 5.1, 5.2 and 5.3.

llSetVehicleType(VEHICLE_TYPE_AIRPLANE);

    Angular deflection is the tendency of a vehicle to move in certain directions. The angular deflection efficiency determines how effective angular deflection is. A value of 0.1 specifies angular deflection at 10%. This allows the helicopter to turn fairly easily.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_DEFLECTION_EFFICIENCY, 0.1);

    A value of 0.1 specifies that linear deflection has 10% power. This means it takes little effort for the helicopter to change its linear velocity.

llSetVehicleFloatParam(
VEHICLE_LINEAR_DEFLECTION_EFFICIENCY, 0.1);

    It will take ten seconds for angular deflection to reach full effect.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_DEFLECTION_TIMESCALE, 10);
llSetVehicleFloatParam(
VEHICLE_LINEAR_DEFLECTION_TIMESCALE, 10);

    It will take one fifth of a second for the linear motor to reach full power.

llSetVehicleFloatParam(
VEHICLE_LINEAR_MOTOR_TIMESCALE, 0.2);

    It takes ten seconds for the linear motor to drop from full power. However, the controls for the helicopter will stop the linear motor if the direction of the helicopter is changed. This makes the helicopter easier to control. Because of this, the linear motor may not always have a full ten seconds to slow down.

llSetVehicleFloatParam(
VEHICLE_LINEAR_MOTOR_DECAY_TIMESCALE, 10);

    It takes one fifth of a second for the angular motor to reach full power.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_MOTOR_TIMESCALE, 0.2);

    It takes one tenth of a second for the angular motor's power to drop off. This causes the helicopter to stop turning quickly.

llSetVehicleFloatParam(
VEHICLE_ANGULAR_MOTOR_DECAY_TIMESCALE, 0.1);

    There is little friction in any of the three directions.

llSetVehicleVectorParam(
VEHICLE_LINEAR_FRICTION_TIMESCALE, <1,1,1>);

    There is little angular friction in the x and z coordinate systems.

llSetVehicleVectorParam(
VEHICLE_ANGULAR_FRICTION_TIMESCALE, <1,1000,1000>);

    The helicopter is slightly heavier than air. It will sink very slowly when hovering. It will also be able to sit on the ground without floating away.

llSetVehicleFloatParam(
VEHICLE_BUOYANCY, 0.9);

    The helicopter should stay right-side up. The following vertical attraction values ensure this.

llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_EFFICIENCY, 1 );
llSetVehicleFloatParam( VEHICLE_VERTICAL_ATTRACTION_TIMESCALE, 2 );

    The helicopter will turn quickly once banked.

llSetVehicleFloatParam( 
VEHICLE_BANKING_EFFICIENCY, 1 );

    The helicopter banking mix is 50% realistic. This means that the helicopter does not necessarily need to be in motion to turn.

llSetVehicleFloatParam( 
VEHICLE_BANKING_MIX, 0.5 );

    It takes half a second for the bank to translate into a turn.

llSetVehicleFloatParam( 
VEHICLE_BANKING_TIMESCALE, .5 );

    These values produce a helicopter that is steady and easy to control.

Controlling the Helicopter

    The helicopter is controlled differently to the car. To begin with, the helicopter must bank to turn. The car simply turns. Also, the helicopter must detect when the up/down and forward/backward controls are released. Otherwise, the helicopter would continue in those directions and be difficult to control. Finally, the helicopter can go up and down, whereas the car stays on the ground.

    The control event handler begins by checking to see whether the user is applying forward pressure to the helicopter controls. If this is the case, apply power to the linear motor to go forward.

// going forward, or stop going forward
if(level & CONTROL_FWD)
{
	llSetVehicleVectorParam(
	VEHICLE_LINEAR_MOTOR_DIRECTION, <forward_power,0,0>);

    If the user recently ceased applying forward pressure to the helicopter controls, stop the linear motor.

} else if(edge & CONTROL_FWD)
{
	llSetVehicleVectorParam(
	VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
}        

    Next, the control event handler checks to see whether the user is applying backward pressure to the helicopter controls. If so, apply backward power to make the linear motor go backward.

// going back, or stop going back
if(level & CONTROL_BACK)
{
	llSetVehicleVectorParam(
	VEHICLE_LINEAR_MOTOR_DIRECTION, <reverse_power,0,0>);
}

    If the user recently ceased applying backward pressure to the helicopter controls, stop the linear motor.

else if(edge & CONTROL_BACK)
{
	llSetVehicleVectorParam(
	VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
}

    If the user is turning to the right, apply positive power to the angular motor in the x coordinate system. This rolls the helicopter to the right.

// turning
if(level & (CONTROL_RIGHT|CONTROL_ROT_RIGHT))
{
	angular_motor.x += 25;
}

    If the user is turning to the left, apply negative power to the angular motor in the x coordinate system. This rolls the helicopter to the left.

if(level & (CONTROL_LEFT|CONTROL_ROT_LEFT))
{
	angular_motor.x -= 25;
}

    Next, the control event handler checks to see whether the user is applying upward pressure to the helicopter controls. If so, apply upward power to the linear motor to make it go upwards.

// going up or stop going up
if(level & CONTROL_UP) 
{
	llSetVehicleVectorParam(
VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,VERTICAL_THRUST>);

    If the user recently ceased applying upward pressure to the helicopter controls, stop the linear motor.

} else if (edge & CONTROL_UP) {
	llSetVehicleVectorParam(
VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
}
        

    Next the control event handler checks to see whether the user is applying downward pressure to the helicopter controls. If so, apply downward power to the linear motor to make it go downwards.

// going down or stop going down
        
if(level & CONTROL_DOWN) 
{
	llSetVehicleVectorParam(
VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,-VERTICAL_THRUST>);

    If the user has recently ceased applying upward pressure to the helicopter controls, stop the linear motor.

} else if (edge & CONTROL_DOWN) {
	llSetVehicleVectorParam(
VEHICLE_LINEAR_MOTOR_DIRECTION, <0,0,0>);
}

    Finally, apply the correct power to the angular motor.

angular_motor.y = 0;
llSetVehicleVectorParam(
VEHICLE_ANGULAR_MOTOR_DIRECTION, angular_motor);

    The control system for an air vehicle is different to a land or sea vehicle.

Spinning the Rotors

    The helicopter has a spinning rotor, similar to a real helicopter. This blade is turned on and off by the main helicopter script's changed event handler. To start the rotor, use this command.

llMessageLinked(LINK_ALL_CHILDREN , 0, "start", NULL_KEY);

    To stop the rotor, use this command.

llMessageLinked(LINK_ALL_CHILDREN , 0, "stop", NULL_KEY);

    The script that drives the rotor is shown in Listing 5.7.

Listing 5.7: Helicopter Rotors (Blade.lsl)

// From the book:
//
// Scripting Recipes for Second Life
// by Jeff Heaton (Encog Dod in SL)
// ISBN: 160439000X
// Copyright 2007 by Heaton Research, Inc.
//
// This script may be freely copied and modified so long as this header
// remains unmodified.
//
// For more information about this book visit the following web site:
//
// http://www.heatonresearch.com/articles/series/22/

float rad = 0.0;
float radinc = 0.05;
float time_inc = .2;
float rotspeed = 3.2;

default
{    

    state_entry()
    {
        llSetTextureAnim(0, ALL_SIDES, 0, 0, 0, 0, 0);
    }
    
    link_message(integer sender_num, integer num, string str, key id)
    {
        if(str=="stop")
        {
            llSetTextureAnim(0, ALL_SIDES, 0, 0, 0, 0, 0);
        }
        if(str=="start")
        {
            llSetTextureAnim(ANIM_ON | ROTATE | LOOP | SMOOTH, ALL_SIDES, 0, 0, 0, 100, 20);
        }
    }
    
}

    The rotor script does most of its work inside of the link_message event handler.

link_message(integer sender_num, integer num, string str, key id)
{
	if(str=="stop")
	{
		llSetTextureAnim(0, ALL_SIDES, 0, 0, 0, 0, 0);
	}
	if(str=="start")
	{
		llSetTextureAnim(ANIM_ON | ROTATE | LOOP | 
SMOOTH, ALL_SIDES, 0, 0, 0, 100, 20);
	}
}

    When the event handler receives the message “stop”, the rotor animation is stopped. When the event handler receives the message “start” the animation starts.


Copyright 2005 - 2010 by Heaton Research, Inc.. Heaton Research™ and Encog™ are trademarks of Heaton Research. Click here for copyright and trademark information.