Introduction
The vehicle script, Vehicle control v<x.xx>, can be used to propel any vehicle-like object, either by sitting in it and using the arrow keys (or WASD) to move, or by clicking on an external, circular touchpad to control speed and direction. It was originally designed to work with either ODE or BulletSim physics, although more recently the BulletSim side has not been kept up due to issues with that software.
Construction
Root prim
For stability, the vehicle should be enclosed in an invisible cuboid prim that projects slightly below the lowest point (where the wheels/tracks/etc touch the ground), and slightly outside all parts horizontally. The top should ideally be higher than any point on the vehicle, but in practice should be as high as possible to allow access to the touchpad and any other controls that need to be accessed while the vehicle is running.
When at a standstill, the root prim will be sliced by the script to 0.0 - 0.02 to allow access to doors, etc; the script sets the slice end to 1.0 when in motion.
The reason for this is to simplify the vehicle from a physics point of view; without the enclosing prim, the behaviour of the vehicle becomes erratic, especially under the current implementation of OpenSim.
For a vehicle to be rezzed by RezMela, the root prim will contain the WorldObject Root script. This overrides any alpha setting for the root's texture, so invisibility should be achieved by setting an invisible texture.
Size limitation
It appears that no part of a vehicle should exceed 10m in size on any axis, at least on the ODE engine. This limitation is apparently fixed in BulletSim. It has been found that exceeding that size can cause erratic behaviour, although at least one exception (a bus model) has been found. Like many issues with OpenSim physics, it is hard to know exactly what is the case.
Dust
It is possible for the vehicle to make dust trails from its wheels when in motion. These will be emitted from child prims with the name "Dust maker", which should be invisible and placed just above where the wheels make contact with the ground (although ensure that no part of the prim protrudes below the root prim bottom). In addition "Dust" should be set to True in the parameters (see below).
Parameters
Certain parameters to the vehicle script can be provided in a notecard called "Vehicle config" in the root prim's contents. This has a .ini type layout, and an example follows:
DriverPosition = <0.43, 0.52, 0.19>
DriverRotation = <0, -10, 0>
DriverOutsidePos = <-0.5, 2.0, 2.0>
SitText = "Drive"
Animation = "Vehicle sit"
AnimationMoving = "Vehicle drive"
CameraOffset = <0, -5, -5>
Dust = "Dust cloud", 8, 0.5, <255, 230, 190> // dust cloud texture name, density, alpha, color
SliceRoot = True
PowerForward = 15 // Auto control
PowerReverse = 11 // Auto control
DriverControlAccelerate = 6.0 // acceleration (both forward and backward)
DriverControlDecelerate = 0.1 // rate of slowing down when keys released
DriverControlMinImpulse= 12.0 // starting impulse when moving from rest
DriverControlMaxImpulse = 20.0 // maximum impulse (equivalent to max speed)
DriverTurnMax = 1.4 // Maximum amount of turn
DriverTurnMin = 0.2 // Minimum turn - ie amount turned when A/D first pressed
DriverTurnRate = 0.1 // Amount of increase as A/D held
DriverTurnCenterRate = 0.1 // Rate of return to centre when A/D released
DriverDirectionChangeDelay = 2 // Number of 0.5-second increments in delay when changing direction with W/S
SurvivorRegion = "Heaven"
SurvivorPos = <128, 128, 22>
SurvivorRot = <0, 0, 270>
SteeringWheelRot = <180, -15, 90> // Normal local rotation of steering wheel prim
SteeringWheelTurnAxis = <0, 1, 0> // Local rotational axis of steering wheel
SteeringWheelTurnAngle = 40 // Angle of turn of steering wheel (degrees)
SpeedoMaxSpeed = 20 // Maximum speed on speedometer (ref DriverControlMaxImpulse)
SpeedoRot = <257.45, 356.65, 14.65> // Local rotation of speedo
SpeedoTurnAxis = <1, 0, 0> // Local rotational axis of speedo
SpeedoMaxAngle = 205 // Amount of rotation when speedo is at max (degrees)
SpeedoPrecision = 1 // Speed changes smaller than this are ignored
BrakeLightFullBright = True // Use fullbright for brake lights?
BrakeLightGlow = 1 // Amount of glow to use for brake lights (float, 0 to 1)
The meaning of the parameters is as follows:
DriverPosition, DriverRotation - Driver's "sit target" parameters. Rotation is an Euler vector in degrees
DriverOutsidePos - Position (relative to root) where driver is sent when standing up from driving position
Animation - Name of animation in root prim contents to play for driver. If omitted, default avatar sit is used
AnimationMoving - Name of animation in root prim contents to play for driver while vehicle is in motion. If omitted, value for Animation is used.
CameraOffset - Relative position of driver player's camera while seated (omit for default)
Dust - CSV for dust cloud texture name, density, alpha, color
SitText - Text to display on context (right-click menu) instead of "Sit Here". If omitted, "Sit Here" will be used
SliceRoot - (boolean) May be set to False to suppress the slicing of the root prim. This no longer has a known useful purpose.
PowerForward, PowerReverse - [External control only] Amount of impulse to provide (ie speed) in forward and reverse. This is an arbitrary unit and its effect varies with vehicle weight.
Driver* - see "Driver controls implementation" section below
Survivor* - Region (omit for current region), position and rotation (omit for zero rotation) to teleport driver to when vehicle destroyed (optional feature, omit all for none)
SteeringWheel* - Steering wheel parameters (see below)
Speedo* - Speedometer parameters (see below)
BrakeLight* - Brake light parameters (see below)
Touchpad
Construction
On the top of the vehicle should be a touchpad consisting of two prims with specific names. One, called "Touchpad" (case sensitive) should be a disk have a specific texture consisting of a green outer circle with a blue segment at the bottom and a red inner circle. Another, called "Stop button", should be a smaller, concentric disk slightly above the touchpad, and should have a blank texture with the colour <243, 100, 101> (red); this should be sized to just cover the red portion of the touchpad (1/3 of the diameter). Both these prims should have Full Bright set.
Operation
If the user clicks on the stop prim, any movement will be stopped. A click on the touchpad while move the vehicle in the direction indicated (relative to the centre of that prim), and with a speed proportional to the distance from the edge of the prim. That is, a click near the edge will cause the vehicle to move slowly, and a click nearer the centre more quickly.
Turning is achieved by an immediate rotation in the direction specified (rather than the slower turn while moving achieved when the vehicle is driven).
While the vehicle is running, the touchpad prim is made slightly transparent to reveal details it would otherwise hide. This doesn't apply to the stop button - because semi-transparent prims can occasionally fail to detect clicks, an opaque solid button means you can always stop the vehicle.
Driver controls
When a user is sitting in the vehicle (actually on the root prim, rather than any child prim), they have control of the vehicle though the following keys (arrow keys/WASD):
Up/W: move forward (or slow reverse movement)
Down/S: move back (or slow forward movement)
Left/A: turn to the left
Right/D: turn to the right
Page Down/C: brake suddenly
Page Up/E: (dependent on context)
Also, the player's camera is positioned above the vehicle and behind, and follows the vehicle with "lerping" for smooth movement.
Driver controls implementation
The most complex part of the script has to do with the way the vehicle is controlled when driven by an avatar (as opposed to external control through the disc). This section attempts to describe how this is implemented.
The following parameters are used:
The way these values are used, and the basic logic in the code, is described below. WASD control is assumed, but the same may be achieved using the arrow/page keys.
a. Physics state
When the vehicle is at rest, it is not a physical object, and is phantom. This enables occupants to stand without colliding with the structure of the object. As soon as there is any forward or reverse movement, the vehicle is given a physics state and is set to VEHICLE_TYPE_CAR. Thereon, all control of the vehicle is using the LSL vehicle subsystem (llSetVehicle*Param(), etc). Note that it is not advisable to mix vehicle and non-vehicle physics commands.
b. Vehicle parameter use while in motion
Control of the vehicle is achieved primarily by altering the values of three different parameters:
VEHICLE_LINEAR_MOTOR_DIRECTION - forward and reverse power
VEHICLE_LINEAR_FRICTION_TIMESCALE - braking
VEHICLE_ANGULAR_MOTOR_DIRECTION - turning
c. Events and performance
Most of the control is performed in two events: the timer() and control() events. The control() event fires roughly 40-50 times per second, but only when a control key is pressed, whereas the timer() event fires every 0.5 seconds regardless of key states. Because of the rapidity of the control() event, as much processing as possible is delegated to the timer() event. The rationale behind this is that a complex calculation may cause sim performance problems if processed that rapidly (especially if there are many vehicles running simultaneously). In addition, if the control event takes longer to execute than its timeframe, this will lead to filling of the event queue for the script and potentially dropped events. On the other hand, processing in the timer() event incurs a delay of average 0.25s and is not consistent (the delay can be 0.5s or nearly absent).
The philosophy taken was one of great caution, because it is very difficult during development to know how things will behave when the sim is under heavy load, in particular when multiple vehicles are in use. In addition, the absence of profiling or any other method of identifying bottlenecks, critical paths, etc in LSL means that a lot of the performance tuning is inevitably a case of informed guesswork.
In summary, code executed in the control() event should be only that which (i) requires data from the control() events parameters; or (ii) has to have faster response than 0.25s average. Often, values such as key presses are stored there for reason (i) but accessed in the timer() event. Turning code, however is in the control() event because of reason (ii) - while it may be acceptable for a short delay when pressing the accelerator or brake, a delay when turning the wheel is not tolerable.
d. Structure of control() and timer() events
For linear motion, depending on what combinations of key down/key up/key held events are current, these are resolved into a single state of NONE, BRAKE, FORWARD or BACK. This state is picked up by the timer event and translated into modifications to the VEHICLE_LINEAR_MOTOR_DIRECTION and VEHICLE_LINEAR_FRICTION_TIMESCALE parameters. For angular (turning) motion, a similar approach is used but contained entirely in the control() event.
e. Acceleration
Control event: When the vehicle is at a standstill and the driver presses W or S, the vehicle is set physical and as an LSL vehicle. The linear motion variable (DriverControlDirection) is set to indicate forward motion. It keeps that value while W or S (ie the same key) is held down.
Timer event: If the vehicle is currently at rest, the forward impulse is set to DriverControlMinImpulse. Otherwise it is incremented by DriverControlAccelerate, but is capped at DriverControlMaxImpulse. The impulse is applied to VEHICLE_LINEAR_MOTOR_DIRECTION.
The processing is identical for reverse movement, but with negative impulse.
f. Deceleration
When the W/S key is released, no processing occurs during the control phase; the linear motion variable DriverControlDirection is set to NONE. Consequently, the impulse is decremented by DriverControlDecelerate and the vehicle decelerates progressively until the impulse reaches zero. When zero impulse is reached, the vehicle will come to a stop.
g. Braking
Pressing C (or PgDn) causes the emergency (hand) brake to be applied and the vehicle is immediately stopped. There is no intermediate stage, and impulse is set directly to zero.
h. Reverse braking
If the S key is pressed while the vehicle is in forward motion, or W while in reverse motion, reverse braking" logic supersedes the above. Initially, impulse is set to zero and the vehicle brought to a halt at the next timer event - this introduces a delay of between 0 and 0.5 seconds. If the S key continues to be held, a counter is set to DriverDirectionChangeDelay and decremented by 1 on each 0.5s timer event. When the timer reaches zero, processing continues as if S or W had been pressed while the vehicle is at rest. In human terms, this means that pressing S while travelling forward will near-immediately stop the vehicle dead. If S continues to be held, the vehicle will start moving backwards. Obviously, if the key is released once the vehicle is at rest, there is no further motion.
Sitting and standing
To drive the vehicle, open the drivers door (if necessary) by clicking it, and then click on the steering wheel. You will be automatically seated in the driving seat, and the car will be ready to go.
When you're finished, click the "Stand" button in the viewer HUD. The driver's door will open and the driver avatar will be teleported to the relative position given by DriverOutsidePos in the config file.
The script knows which door to open by virtue of its door type being "driver" (see the door script documentation for further details).
Steering wheel
Optionally, a steering wheel prim can be linked to the vehicle which rotates as the vehicle is steered by the driver. The following parameters in the vehicle configuration file control this feature:
SteeringWheelRot - (vector, Euler degrees) the local rotation of the steering wheel when centred
SteeringWheelTurnAxis - (vector normal, eg <1, 0, 0>) the local axis of rotation of the steering wheel
SteeringWheelTurnAngle - (float, degrees) the amount of turn between centred and either extreme
To obtain the value for SteeringWheelRot, an easy method is to rotate the whole vehicle to <0, 0, 0>, then edit the wheel using "edit linked parts" and copy the rotatation X, Y and Z values.
The steering wheel prim must be named "SteeringWheel". Only one such prim will be controlled.
Speedometer
This feature allows a speedometer needle prim to rotate according to the vehicle's speed. The following parameters are supported:
SpeedoRot - (vector, Euler degrees) the local rotation of the needle when speed is zero
SpeedoTurnAxis - (vector normal, eg <1, 0, 0>) the local axis of rotation of the needle prim
SpeedoMaxAngle - (float, degrees) the angle of rotation for the needle to turn to its maximum position
SpeedoMaxSpeed - (float, m/s) the speed at which the needle should be at maximum (needle is "pegged" at that position for higher speeds)
SpeedoPrecision - (float, m/s) the difference in speed below which no movement should take place (to save calculations)
The speedometer needle prim must be named "Speedometer". Only one such prim will be controlled.
Brake lights
Any prim in the linkset with the name "BrakeLight" is lit by the script when the vehicle brakes. The following parameters affect this behaviour:
BrakeLightFullBright - (text boolean) whether full-bright should be applied on braking
BrakeLightGlow - (float) the amount of glow to be applied when braking (or 0 for no glow)
The script supports multiple brake light prims.
One technique that works well is for the "brake light" to be a 100% transparent, untextured prim placed over the existing textured brake light of the model. This gives the appearance of the texture lighting up.
Sounds
For the vehicle to have sounds, it is necessary for the "Vehicle Sounds" script to be in the root prim, together with a configuration file notecard called "Sounds". In addition, sound files may be placed in the root prim contents and referenced by name (as usual with sounds, the alternative is to reference them by asset UUID, in which case they need not themselves be included).
The format of the "Sounds" config file is a series of lines of CSV, with optional comments preceded by "//". The following types of lines are allowed:
Start, <start sound file>, <volume>, <length>
Stop, <stop sound file>, <volume>, <length>
Idle, <idle sound file>, <volume>
Moving, <max speed>, <sound file>, <volume>
Only one line of types Start, Stop and Idle is allowed, but multiple Moving lines can be used to give different sound at different speeds. The "start" sound is played once when the vehicle engine starts, the "idle" sounds loops while the engine is running but the vehicle is stationary, the "moving" sounds play at different speeds when in motion and the "stop" sound plays once when the engine is turned off. (The engine turns on and off when the driver sits down and stands up).
Because Start and Stop are played only once, and llPlaySound() is asynchronous, it is necessary to specify the length of the samples so that they're not cut short by other sounds and there is not gap - practically, this is only important for the Start sound because no sounds are likely to follow Stop.
The sound <volume> figure is expressed as a decimal fraction (ie "0" to "1", with "0.5" as 50% volume).
An example of multiple Moving lines:
Moving, 15, Slow sound, 0.7
Moving, 17, Medium sound, 0.75
Moving, 0, Fast sound, 0.8
With this setup, "Slow sound" while be played when the vehicle is travelling at up to 15m/s; "Medium sound" from 15-17m/s; "Fast sound" is played at speeds higher than 17m/s (since a speed of "0" specifies infinity). Unlike the other types of line, the sequence of these "moving" entries is critical.
Note that door opening/closing sounds, gunfire, etc are not handled by the vehicle script but by the door scripts, gun scripts, etc, which should be in different prims (only one sound can play per prim).
Integration with gun script
The vehicle script is designed to work well alongside the gun script, for vehicles that have guns (tanks, etc). A linked message is sent from the gun script to indicate when the object is destroyed, and the vehicle script responds to destruction by stopping the vehicle, unseating the driver and immobilising the vehicle until the gun script is "un-destroyed" (in practice, this probably never happens).
The Page Up key (or E) will cause the gun to fire.