-- dependencies
if not FSM_LUA     then dofile("fsm.lua")     end
if not SCENE_LUA   then dofile("scene.lua")   end
if not GAMEFSM_LUA then dofile("gamefsm.lua") end

MOBILE_LUA = true

STATE_MOBILE_PRERACE  = "PRERACE"
STATE_MOBILE_RACING   = "RACING"
STATE_MOBILE_OH       = "OVERHEAT"
STATE_MOBILE_OOB      = "OOB"
STATE_MOBILE_OOC      = "OOC"
STATE_MOBILE_RESPAWN  = "RESPAWN"
STATE_MOBILE_POSTRACE = "POSTRACE"

THROT_SOUND_FILE1 = "data/sounds/engine/1000rpm.ogg"
THROT_SOUND_FILE2 = "data/sounds/engine/idle.ogg"
RAIL_SOUND_FILE   = "data/sounds/rail.ogg"
HIT1_SOUND_FILE   = "data/sounds/mob_thump1.ogg"
HIT2_SOUND_FILE   = "data/sounds/mob_thump2.ogg"
HIT3_SOUND_FILE   = "data/sounds/mob_thump3.ogg"
HIT4_SOUND_FILE   = "data/sounds/mob_thump4.ogg"
BOX1_SOUND_FILE   = "data/sounds/box_thump1.ogg"
BOX2_SOUND_FILE   = "data/sounds/box_thump2.ogg"
WSH1_SOUND_FILE   = "data/sounds/whoosh.ogg"
WSH2_SOUND_FILE   = "data/sounds/whoosh2.ogg"
MOB_SOUND_FILE    = "data/sounds/crash.ogg"
WIND_SOUND_FILE    = "data/sounds/wind.ogg"

mobile = FSM:instantiate{ state = STATE_NONE, tostr = "MOBILE_FSM" }

-- mobile constants
mobile.turn_max     = 10
mobile.torque_max   = 200
mobile.accel_low    = 300
mobile.accel_high   = 400
mobile.accel_boost  = 500
mobile.accel_mud    = 300
mobile.veloc_max_l  = 70
mobile.veloc_max_h  = 100
mobile.veloc_max_b  = 150
mobile.veloc_rev    = -30
mobile.veloc_mud    = 15
mobile.break_force  = 150
mobile.friction     = 30
mobile.max_rate     = 4000  -- experimentally determined
mobile.max_vel      = 50
mobile.boost_length = 4

-- need to figure out a way to set this prior to OOC
-- so that respawn occurs at a resonable place
mobile.crash_z      = 0

-- mobile constants: modeling
mobile.body_obj     = "data/snowMobile_wCharacter.obj"
mobile.wheel_obj    = "data/wheel.obj"
mobile.skid_obj     = "data/skid.obj"

mobile.body_y_rot   = 90      -- rot of veh body about y axis
mobile.body_scale   = 1.5     -- scale of veh body (model)
mobile.body_ofs_x   = 0       -- body origin offsets
mobile.body_ofs_y   = 0
mobile.body_ofs_z   = 0

mobile.wheel_y_rot  = 90      -- rot of wheel about y axis
mobile.wheel_scale  = 1       -- scale of wheels (model)
mobile.wheel_ofs_x  = 0       -- wheel origin offsets
mobile.wheel_ofs_y  = 0.1
mobile.wheel_ofs_z  = 1.5

mobile.skid_y_rot   = 0       -- rot of skids about y axis
mobile.skid_scale   = 1       -- scale of skids (model)
mobile.skid_left    = -1      -- skid origin offsets
mobile.skid_rght    = 1
mobile.skid_ofs_z   = -1.65
mobile.skid_ofs_y   = 0.1

-- mob constants: geom props
mobile.base_ofs_y   = -0.05   -- base height
mobile.base_width   = 2
mobile.base_height  = .05
mobile.base_length  = 4

mobile.frame_width  = 2
mobile.frame_height = 2
mobile.frame_length = 4

mobile.skid_width   = 1
mobile.skid_length  = 2
mobile.skid_height  = 1

mobile.wheel_rad    = 0.5     -- wheel radius
mobile.wheel_width  = 0.1

-- mobile constants: physics
mobile.baseweight   = 10
mobile.tireweight   = 2
mobile.skidweight   = 3
mobile.frameweight  = 1

mobile.frame_fric   = 10 
mobile.tire_fric    = 250
mobile.skid_fric    = 5

mobile.cor_fac_rx   = 0.0    -- correction factor; error to correct per frame
mobile.cor_fac_ry   = .1
mobile.cor_fac_rz   = 0.1

-- mobile constants: rendering
mobile.viewgeoms    = false
mobile.alpha        = 1
mobile.scale        = 1

function mobile:create ( px, py, pz, ry, parent )

    -- initialize control/state variables
    self.opaque      = true
    self.breaking    = false
    self.turn        = 0
    self.targ_head   = 0
    self.lastpos     = 0
    self.rate        = 0

    self.really_high = false
    self.on_ground   = false
    self.on_rail     = false
    self.in_pwrup    = false
    self.hit_haz     = false
    self.hit_mob     = false
    self.hit_obs     = false
    self.on_surf     = false
    self.surf_type   = "NUL"
    self.ground_time = 0
    self.rail_time   = 0
    self.haz_time    = 0
    self.mob_time    = 0
    self.obs_time    = 0
    self.run_time    = 0
    self.pwrup_time  = 0
    self.surf_time   = 0
    self.boost_time  = -10

    self.throt       = mobile.throt:instantiate{ mob = self }
    self.throt.state = STATE_THROT_IDLE
    self.throt.heat  = 0
--    self.lean_last   = 0
    self.lean_x      = 0
    
    self.lap         = 1

    -- init physics properties
    self.px, self.py, self.pz = 0, 0, 0  -- position
    self.vx, self.vy, self.vz = 0, 0, 0  -- velocity
    self.rx, self.ry, self.rz = 0, 0, 0  -- rotation
    self.sx, self.sy, self.sz = 0, 0, 0  -- ang velo
    self.xx, self.xy, self.xz = 0, 0, 0  -- x vector
    self.yx, self.yy, self.yz = 0, 0, 0  -- y vector
    self.zx, self.zy, self.zz = 0, 0, 0  -- z vector
    self.x0, self.y0, self.z0 = 0, 0, 0  -- smooth k

    -- assign relative positions of mobile elements

    local pos = {
	{ self.wheel_ofs_x, self.wheel_ofs_y, self.wheel_ofs_z }, -- tire
	{ self.skid_rght,   self.skid_ofs_y , self.skid_ofs_z  }, -- skid 1
	{ self.skid_left,   self.skid_ofs_y , self.skid_ofs_z  }, -- skid 2
	{ 0,                self.base_ofs_y , 0                }, -- base

	{ 0, self.base_ofs_y + self.frame_height/2, 0          }, -- frame

	{ self.body_ofs_x, self.body_ofs_y, self.body_ofs_z}, -- body

	{ 0, 0,                   - self.skid_length / 2}, -- skid bumpers
	{ 0, 0,                   self.skid_length   / 2}, -- skid bumpers
    }

    -- adjust all positions by mobile scale
    for i = 1, 5 do
	pos[i][1] = pos[i][1] * self.scale
	pos[i][2] = pos[i][2] * self.scale
	pos[i][3] = pos[i][3] * self.scale
    end
    local sbs = self.body_scale
    local sws = self.wheel_scale * self.scale
    
    -- adjust the wheel positions to account for rotation
    for i=1, 3 do
	local d = math.sqrt(pos[i][1]*pos[i][1] + pos[i][3]*pos[i][3])
	local t = math.deg(math.atan(pos[i][1] / pos[i][3]))
	if pos[i][3] < 0 then t = t + 180 end
	t = t + ry
	pos[i][1] = d * math.sin(math.rad(t))
	pos[i][3] = d * math.cos(math.rad(t))
    end

    -- offset all positions by location to place mobile
    for i = 1, 5   do
	pos[i][1] = pos[i][1] + px
	pos[i][2] = pos[i][2] + py
	pos[i][3] = pos[i][3] + pz
    end

    -- create entities for all bike parts.

    self.base   = E.create_pivot()
--    self.body   = E.create_object(self.body_obj)
    self.body   = E.create_pivot()
    self.tire1  = E.create_pivot()
    self.skid1  = E.create_pivot()
    self.skid2  = E.create_pivot()
    self.frame  = E.create_pivot()
    self.shock1 = E.create_pivot()
    self.shock2 = E.create_pivot()
    self.skd1f  = E.create_pivot()
    self.skd2f  = E.create_pivot()
    self.skd1b  = E.create_pivot()
    self.skd2b  = E.create_pivot()

    -- setup animation frames
    local frames = {"sit", "in_air"}
    self.anim = {}
    for i, line in ipairs(frames) do
	local file = string.format("data/anim/mob%d_%s.obj",
				   self.id,line)
	self.anim[line] = E.create_object(file) 
	E.parent_entity(self.anim[line], self.body)
    end
    self:set_anim_frame("sit")

    -- set up body and frame

    E.parent_entity(self.body,  self.base)
    E.parent_entity(self.skd1f, self.skid1)
    E.parent_entity(self.skd2f, self.skid2)
    E.parent_entity(self.skd1b, self.skid1)
    E.parent_entity(self.skd2b, self.skid2)

    E.set_entity_rotation(self.body, 0, self.body_y_rot,  0)

    E.set_entity_scale(self.body, sbs, sbs, sbs)

    E.set_entity_position(self.frame, pos[5][1], pos[5][2], pos[5][3])
    E.set_entity_position(self.body,  pos[6][1], pos[6][2], pos[6][3])
    E.set_entity_position(self.skd1f, pos[7][1], pos[7][2], pos[7][3])
    E.set_entity_position(self.skd2f, pos[7][1], pos[7][2], pos[7][3])
    E.set_entity_position(self.skd1b, pos[8][1], pos[8][2], pos[8][3])
    E.set_entity_position(self.skd2b, pos[8][1], pos[8][2], pos[8][3])

    -- set up tires and base

    E.parent_entity(self.base,  parent)
    E.parent_entity(self.frame, parent)
    E.parent_entity(self.tire1, parent)
    E.parent_entity(self.skid1, parent)
    E.parent_entity(self.skid2, parent)
    E.parent_entity(self.shock1, parent)
    E.parent_entity(self.shock2, parent)

    E.set_entity_rotation(self.base, 0, ry, 0)
    E.set_entity_rotation(self.frame, 0, ry, 0)

    E.set_entity_scale(self.base, self.scale, self.scale, self.scale)

    E.set_entity_rotation(self.tire1, 0, self.wheel_y_rot + ry, 0)
    E.set_entity_rotation(self.skid1, 0, self.skid_y_rot + ry, 0)
    E.set_entity_rotation(self.skid2, 0, self.skid_y_rot + ry, 0)

    E.set_entity_scale(self.tire1, sws, sws, sws)
    E.set_entity_scale(self.skid1, sws, sws, sws)
    E.set_entity_scale(self.skid2, sws, sws, sws)

    E.set_entity_position(self.tire1,  pos[1][1], pos[1][2], pos[1][3])
    E.set_entity_position(self.skid1,  pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_position(self.skid2,  pos[3][1], pos[3][2], pos[3][3])
    E.set_entity_position(self.shock1, pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_position(self.shock2, pos[3][1], pos[3][2], pos[3][3])
    E.set_entity_position(self.base,   pos[4][1], pos[4][2], pos[4][3])


    -- Mark all entities as rigid bodies.
 
    E.set_entity_body_type(self.base,   true)
    E.set_entity_body_type(self.frame,  true)
    E.set_entity_body_type(self.tire1,  true)
    E.set_entity_body_type(self.skid1,  true)
    E.set_entity_body_type(self.skid2,  true)
    E.set_entity_body_type(self.shock1, true)
    E.set_entity_body_type(self.shock2, true)

    -- view structures
    E.set_entity_flags(self.base,   E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.frame,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.tire1,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.skid1,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.skid2,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.shock1, E.entity_flag_visible_body, self.viewgeoms)
    E.set_entity_flags(self.shock2, E.entity_flag_visible_body, self.viewgeoms)
    E.set_entity_flags(self.skd1f,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.skd2f,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.skd1b,  E.entity_flag_visible_geom, self.viewgeoms)
    E.set_entity_flags(self.skd2b,  E.entity_flag_visible_geom, self.viewgeoms)

    -- Define the geoms of all colliding bodies.

    E.set_entity_geom_type(self.base,  E.geom_type_box, 
			   self.base_width, self.base_height, self.base_length)
    E.set_entity_geom_type(self.frame, E.geom_type_box, 
			   self.frame_width, 
			   self.frame_height, 
			   self.frame_length)

---[[
    E.set_entity_geom_type(self.skid1, E.geom_type_box, 
			   self.skid_width, self.skid_height, self.skid_length)
    E.set_entity_geom_type(self.skid2, E.geom_type_box, 
			   self.skid_width, self.skid_height, self.skid_length)
--]]
--[[
    E.set_entity_geom_type(self.skid1, E.geom_type_capsule, 
			   self.skid_width / 2,  self.skid_length)
    E.set_entity_geom_type(self.skid2, E.geom_type_capsule, 
			   self.skid_width / 2,  self.skid_length)
--]]
    E.set_entity_geom_type(self.tire1, E.geom_type_capsule, 
			   self.wheel_rad, self.wheel_width)
    E.set_entity_geom_type(self.skd1f, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)
    E.set_entity_geom_type(self.skd2f, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)
    E.set_entity_geom_type(self.skd1b, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)
    E.set_entity_geom_type(self.skd2b, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)

    -- ADD WEIGHTS

    E.set_entity_geom_attr(self.base,  E.geom_attr_mass, self.baseweight)
    E.set_entity_geom_attr(self.frame, E.geom_attr_mass, self.frameweight)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_mass, self.tireweight)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_mass, self.skidweight)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_mass, self.skidweight)

    -- Attach the wheels.
    E.set_entity_joint_type(self.base,   self.frame,  E.joint_type_slider)
    E.set_entity_joint_type(self.base,   self.tire1,  E.joint_type_hinge_2)
    E.set_entity_joint_type(self.base,   self.shock1, E.joint_type_slider)
    E.set_entity_joint_type(self.base,   self.shock2, E.joint_type_slider)
    E.set_entity_joint_type(self.shock1, self.skid1,  E.joint_type_hinge)
    E.set_entity_joint_type(self.shock2, self.skid2,  E.joint_type_hinge)

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_anchor,
			    pos[1][1], pos[1][2], pos[1][3])
    E.set_entity_joint_attr(self.shock1, self.skid1, E.joint_attr_anchor,
			    pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_joint_attr(self.shock2, self.skid2, E.joint_attr_anchor,
			    pos[3][1], pos[3][2], pos[3][3])

    E.set_entity_joint_attr(self.base, self.tire1, 
			    E.joint_attr_axis_1, 0, 1, 0)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_axis_1, 0, 1, 0)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_axis_1, 0, 1, 0)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_axis_1, 0, 1, 0)

    E.set_entity_joint_attr(self.base, self.tire1, 
			    E.joint_attr_axis_2, 1, 0, 0)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_axis_1, 1, 0, 0)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_axis_1, 1, 0, 0)

    -- Soften the suspension a little.

    local erp = 0.80
    local cfm = 0.02

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_susp_erp, erp)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_susp_cfm, cfm)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_cfm, cfm)

    erp, cfm = 0.99, 0.001
    E.set_entity_joint_attr(self.base, self.shock1, E.joint_attr_cfm, cfm)
    E.set_entity_joint_attr(self.base, self.shock2, E.joint_attr_cfm, cfm)
    E.set_entity_joint_attr(self.shock1, self.skid1, E.joint_attr_cfm, cfm)
    E.set_entity_joint_attr(self.shock2, self.skid2, E.joint_attr_cfm, cfm)

    -- Prevent the wheels from steering on their own.

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_stop_cfm, 0.05)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_stop_erp, 0.50)

    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_stop_cfm, 0.001)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_stop_erp, 0.999)

    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_stop_cfm, 0.001)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_stop_erp, 0.99)

    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_stop_cfm, 0.001)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_stop_erp, 0.99)

    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_stop_cfm, 0.02)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_stop_erp, 0.80)

    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_stop_cfm, 0.02)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_stop_erp, 0.80)


    -- Assign categories to prevent the wheel from fouling in the base.

    E.set_entity_geom_attr(self.base,  E.geom_attr_category, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_category, category_body)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_category, category_tire)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_category, category_skid)
 

    E.set_entity_geom_attr(self.base,  E.geom_attr_collider, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_collider, 
			   category_body + category_world)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_collider, category_rail)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_collider, category_rail)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_collider, category_world)

    E.set_entity_geom_attr(self.base,  E.geom_attr_response, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_response, 
			   category_body + category_world)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_response, category_rail)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_response, category_rail)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_response, category_world)

    E.set_entity_geom_attr(self.base,  E.geom_attr_callback, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_callback, 
			   category_body + category_world)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_callback, category_none)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_callback, category_none)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_callback, category_world)


    -- Let everything slide a little on the road.

    E.set_entity_geom_attr(self.frame, E.geom_attr_friction, self.frame_fric)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_friction, self.tire_fric)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_friction, self.skid_fric)


    -- load sound streams
    self.throt.stream1  = E.load_sound(THROT_SOUND_FILE1)
    self.throt.stream2  = E.load_sound(THROT_SOUND_FILE2)
    self.sounds     = {}
    self.sounds.hit = {}
    self.sounds.hit[1] = E.load_sound(HIT1_SOUND_FILE)
    self.sounds.hit[2] = E.load_sound(HIT2_SOUND_FILE)
    self.sounds.hit[3] = E.load_sound(HIT3_SOUND_FILE)
    self.sounds.hit[4] = E.load_sound(HIT4_SOUND_FILE)
    self.sounds.box = {}
    self.sounds.box[1] = E.load_sound(BOX1_SOUND_FILE)
    self.sounds.box[2] = E.load_sound(BOX2_SOUND_FILE)
    self.sounds.wsh = {}
    self.sounds.wsh[1] = E.load_sound(WSH1_SOUND_FILE)
    self.sounds.wsh[2] = E.load_sound(WSH2_SOUND_FILE)
    self.sounds.rail   = E.load_sound(RAIL_SOUND_FILE)
    self.sounds.mob    = E.load_sound(MOB_SOUND_FILE)
    self.sounds.wind   = E.load_sound(WIND_SOUND_FILE)

    E.set_sound_amplitude(self.sounds.wind, 1)

    E.set_sound_emitter(self.throt.stream1,  self.base)
    E.set_sound_amplitude(self.throt.stream1, 0.25)
 
    E.set_sound_emitter(self.throt.stream2,  self.base)
    E.set_sound_amplitude(self.throt.stream2, 0.25)
    
    E.set_sound_emitter(self.sounds.rail,    self.base)
    E.set_sound_amplitude(self.sounds.rail, 0.5)

    E.set_sound_emitter(self.sounds.mob,    self.base)
    E.set_sound_amplitude(self.sounds.mob, 0.5)

    table.foreachi(self.sounds.hit, function (i, v) 
					E.set_sound_emitter(v, self.base)
					E.set_sound_amplitude(v, 1)
				    end)
    table.foreachi(self.sounds.box, function (i, v) 
					E.set_sound_emitter(v, self.base)
					E.set_sound_amplitude(v, 3)
				    end)
    table.foreachi(self.sounds.wsh, function (i, v) 
					E.set_sound_emitter(v, self.base)
					E.set_sound_amplitude(v, 1)
				    end)



    E.loop_sound(self.throt.stream1)
    E.loop_sound(self.throt.stream2)


    -- create shadow
    local img = E.create_image("data/shadow.png")
    local brush = E.create_brush()
    E.set_brush_image(brush, img)
    self.shadow = E.create_sprite(brush)
    E.set_entity_rotation(self.shadow, -90, 0, 0)
    E.set_entity_scale(self.shadow, 20/256, 20/256, 1)
    E.set_entity_alpha(self.shadow, .5)
    E.parent_entity(self.shadow, parent)
end

function mobile:destroy()
    -- since delete_entity recursively deletes heirarchies,
    --   only delete the root nodes.
    E.delete_entity(self.base)
    E.delete_entity(self.tire1)
    E.delete_entity(self.skid1)
    E.delete_entity(self.skid2)
    E.delete_entity(self.shock1)
    E.delete_entity(self.shock2)

end

function mobile:reset( px, py, pz, ry, lap )

    self.throt:set_state(STATE_THROT_IDLE)
    E.set_sound_frequency(self.throt.stream1, 1)

    table.foreachi(self.sounds.hit, function (i, v) E.stop_sound(v) end)
    table.foreachi(self.sounds.box, function (i, v) E.stop_sound(v) end)
    table.foreachi(self.sounds.wsh, function (i, v) E.stop_sound(v) end)
    E.stop_sound(self.sounds.rail)
    E.stop_sound(self.sounds.mob)
    E.stop_sound(self.sounds.wind)

    -- initialize control/state variables
    self.opaque      = true
    self.breaking    = false
    self.turn        = 0
    self.targ_head   = 0
    self.lastpos     = 0
    self.rate        = 0

    self.really_high = false
    self.on_ground   = false
    self.on_rail     = false
    self.in_pwrup    = false
    self.hit_haz     = false
    self.hit_mob     = false
    self.hit_obs     = false
    self.on_surf     = false
    self.surf_type   = "NUL"
    self.ground_time = 0
    self.rail_time   = 0
    self.haz_time    = 0
    self.mob_time    = 0
    self.obs_time    = 0
    self.run_time    = 0
    self.pwrup_time  = 0
    self.surf_time   = 0
    self.boost_time  = -10

    if lap then self.lap = lap end

    self.throt.heat  = 0

    -- init physics properties
    self.px, self.py, self.pz = 0, 0, 0  -- position
    self.vx, self.vy, self.vz = 0, 0, 0  -- velocity
    self.rx, self.ry, self.rz = 0, 0, 0  -- rotation
    self.sx, self.sy, self.sz = 0, 0, 0  -- ang velo
    self.xx, self.xy, self.xz = 0, 0, 0  -- x vector
    self.yx, self.yy, self.yz = 0, 0, 0  -- y vector
    self.zx, self.zy, self.zz = 0, 0, 0  -- z vector
    self.x0, self.y0, self.z0 = 0, 0, 0  -- smooth k

    -- assign relative positions of mobile elements
    
    local pos = {
	{ self.wheel_ofs_x, self.wheel_ofs_y, self.wheel_ofs_z }, -- tire
	{ self.skid_rght,   self.skid_ofs_y,  self.skid_ofs_z  }, -- skid 1
	{ self.skid_left,   self.skid_ofs_y,  self.skid_ofs_z  }, -- skid 2
	{ 0,                self.base_ofs_y,  0                }, -- base
	{ 0, self.base_ofs_y + self.frame_height/2, 0          }, -- frame
    }

    -- adjust all positions by mobile scale
    for i = 1, 5 do
	pos[i][1] = pos[i][1] * self.scale
	pos[i][2] = pos[i][2] * self.scale
	pos[i][3] = pos[i][3] * self.scale
    end
    
    -- adjust the wheel positions to account for rotation
    for i=1, 3 do
	local d = math.sqrt(pos[i][1]*pos[i][1] + pos[i][3]*pos[i][3])
	local t = math.deg(math.atan(pos[i][1] / pos[i][3]))
	if pos[i][3] < 0 then t = t + 180 end
	t = t + ry
	pos[i][1] = d * math.sin(math.rad(t))
	pos[i][3] = d * math.cos(math.rad(t))
    end

    -- offset all positions by location to place mobile
    for i = 1, 5   do
	pos[i][1] = pos[i][1] + px
	pos[i][2] = pos[i][2] + py
	pos[i][3] = pos[i][3] + pz
    end

    -- Mark all entities as rigid bodies.
 
    E.set_entity_body_type(self.base,   false)
    E.set_entity_body_type(self.frame,  false)
    E.set_entity_body_type(self.tire1,  false)
    E.set_entity_body_type(self.skid1,  false)
    E.set_entity_body_type(self.skid2,  false)
    E.set_entity_body_type(self.shock1, false)
    E.set_entity_body_type(self.shock2, false)

    E.set_entity_rotation(self.base, 0, ry, 0)
    E.set_entity_rotation(self.frame, 0, ry, 0)

    E.set_entity_rotation(self.tire1,  0, self.wheel_y_rot + ry, 0)
    E.set_entity_rotation(self.skid1,  0, self.skid_y_rot  + ry, 0)
    E.set_entity_rotation(self.skid2,  0, self.skid_y_rot  + ry, 0)
    E.set_entity_rotation(self.shock1, 0, self.skid_y_rot  + ry, 0)
    E.set_entity_rotation(self.shock2, 0, self.skid_y_rot  + ry, 0)

    E.set_entity_position(self.tire1,  pos[1][1], pos[1][2], pos[1][3])
    E.set_entity_position(self.skid1,  pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_position(self.skid2,  pos[3][1], pos[3][2], pos[3][3])
    E.set_entity_position(self.shock1, pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_position(self.shock2, pos[3][1], pos[3][2], pos[3][3])
    E.set_entity_position(self.base,   pos[4][1], pos[4][2], pos[4][3])
    E.set_entity_position(self.frame,  pos[5][1], pos[5][2], pos[5][3])

    -- Mark all entities as rigid bodies.
 
    E.set_entity_body_type(self.base,   true)
    E.set_entity_body_type(self.frame,  true)
    E.set_entity_body_type(self.tire1,  true)
    E.set_entity_body_type(self.skid1,  true)
    E.set_entity_body_type(self.skid2,  true)
    E.set_entity_body_type(self.shock1, true)
    E.set_entity_body_type(self.shock2, true)


    -- Define the geoms of all colliding bodies.

    E.set_entity_geom_type(self.base,  E.geom_type_box, 
			   self.base_width, self.base_height, self.base_length)
    E.set_entity_geom_type(self.frame, E.geom_type_box, 
			   self.frame_width, 
			   self.frame_height, 
			   self.frame_length)
---[[
    E.set_entity_geom_type(self.skid1, E.geom_type_box, 
			   self.skid_width, self.skid_height, self.skid_length)
    E.set_entity_geom_type(self.skid2, E.geom_type_box, 
			   self.skid_width, self.skid_height, self.skid_length)
--]]
--[[
    E.set_entity_geom_type(self.skid1, E.geom_type_capsule, 
			   self.skid_width / 2,  self.skid_length)
    E.set_entity_geom_type(self.skid2, E.geom_type_capsule, 
			   self.skid_width / 2,  self.skid_length)
--]]
    E.set_entity_geom_type(self.tire1, E.geom_type_capsule, 
			   self.wheel_rad, self.wheel_width)
    E.set_entity_geom_type(self.skd1f, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)
    E.set_entity_geom_type(self.skd2f, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)
    E.set_entity_geom_type(self.skd1b, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)
    E.set_entity_geom_type(self.skd2b, E.geom_type_sphere, 
			   self.skid_height / 2 + 0.05)

    -- ADD WEIGHTS

    E.set_entity_geom_attr(self.base,  E.geom_attr_mass, self.baseweight)
    E.set_entity_geom_attr(self.frame, E.geom_attr_mass, self.frameweight)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_mass, self.tireweight)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_mass, self.skidweight)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_mass, self.skidweight)

    -- Attach the wheels.

    E.set_entity_joint_type(self.base, self.frame,   E.joint_type_slider)
    E.set_entity_joint_type(self.base, self.tire1,   E.joint_type_hinge_2)
    E.set_entity_joint_type(self.base, self.shock1,  E.joint_type_slider)
    E.set_entity_joint_type(self.base, self.shock2,  E.joint_type_slider)
    E.set_entity_joint_type(self.shock1, self.skid1, E.joint_type_hinge)
    E.set_entity_joint_type(self.shock2, self.skid2, E.joint_type_hinge)

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_anchor,
			    pos[1][1], pos[1][2], pos[1][3])
    E.set_entity_joint_attr(self.shock1, self.skid1, E.joint_attr_anchor,
			    pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_joint_attr(self.shock2, self.skid2, E.joint_attr_anchor,
			    pos[3][1], pos[3][2], pos[3][3])

    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_axis_1, 0, 1, 0)
    E.set_entity_joint_attr(self.base, self.tire1, 
			    E.joint_attr_axis_1, 0, 1, 0)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_axis_1, 0, 1, 0)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_axis_1, 0, 1, 0)

    E.set_entity_joint_attr(self.base, self.tire1, 
			    E.joint_attr_axis_2, 1, 0, 0)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_axis_1, 1, 0, 0)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_axis_1, 1, 0, 0)

    -- Soften the suspension a little.

    local erp = 0.80
    local cfm = 0.02

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_susp_erp, erp)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_susp_cfm, cfm)

    E.set_entity_joint_attr(self.base, self.skid1, E.joint_attr_susp_erp, erp)
    E.set_entity_joint_attr(self.base, self.skid1, E.joint_attr_susp_cfm, cfm)
    E.set_entity_joint_attr(self.base, self.skid2, E.joint_attr_susp_erp, erp)
    E.set_entity_joint_attr(self.base, self.skid2, E.joint_attr_susp_cfm, cfm)

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_cfm, cfm)
    E.set_entity_joint_attr(self.base, self.skid1, E.joint_attr_cfm, cfm)
    E.set_entity_joint_attr(self.base, self.skid2, E.joint_attr_cfm, cfm)

    -- Prevent the wheels from steering on their own.

    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_stop_cfm, 0.05)
    E.set_entity_joint_attr(self.base, self.tire1, E.joint_attr_stop_erp, 0.50)

    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_stop_cfm, 0.001)
    E.set_entity_joint_attr(self.base, self.frame, 
			    E.joint_attr_stop_erp, 0.999)

    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_stop_cfm, 0.2)
    E.set_entity_joint_attr(self.shock1, self.skid1, 
			    E.joint_attr_stop_erp, 0.80)

    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_stop_cfm, 0.2)
    E.set_entity_joint_attr(self.shock2, self.skid2, 
			    E.joint_attr_stop_erp, 0.80)

    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_stop_cfm, 0.05)
    E.set_entity_joint_attr(self.base, self.shock1, 
			    E.joint_attr_stop_erp, 0.80)

    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_lo_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_hi_stop,  0.00)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_stop_cfm, 0.05)
    E.set_entity_joint_attr(self.base, self.shock2, 
			    E.joint_attr_stop_erp, 0.80)

    -- Assign categories to prevent the wheel from fouling in the base.

    E.set_entity_geom_attr(self.base,  E.geom_attr_category, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_category, category_body)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_category, category_tire)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_category, category_skid)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_category, category_skid)
 

    E.set_entity_geom_attr(self.base,  E.geom_attr_collider, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_collider, 
			   category_body + category_world)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_collider, category_rail)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_collider, category_rail)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_collider, category_world)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_collider, category_world)

    E.set_entity_geom_attr(self.base,  E.geom_attr_response, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_response, 
			   category_body + category_world)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_response, category_rail)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_response, category_rail)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_response, category_world)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_response, category_world)

    E.set_entity_geom_attr(self.base,  E.geom_attr_callback, category_none)
    E.set_entity_geom_attr(self.frame, E.geom_attr_callback, 
			   category_body + category_world)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_callback, category_rail)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_callback, category_rail)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_callback, category_world)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_callback, category_world)


    -- Let everything slide a little on the road.

    E.set_entity_geom_attr(self.frame, E.geom_attr_friction, self.frame_fric)
    E.set_entity_geom_attr(self.tire1, E.geom_attr_friction, self.tire_fric)
    E.set_entity_geom_attr(self.skid1, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skid2, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_friction, self.skid_fric)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_friction, self.skid_fric)

end

-------------------------------------------------------------------------------
-- entity manip functions

function mobile:set_anim_frame( frame )
    for k, v in pairs(self.anim) do
	E.set_entity_flags(v, E.entity_flag_hidden, not (frame == k))
    end	    
end

function mobile:set_alpha( alpha )
    E.set_entity_alpha(self.base,  alpha)
    E.set_entity_alpha(self.tire1, alpha)
    E.set_entity_alpha(self.skid1, alpha)
    E.set_entity_alpha(self.skid2, alpha)
end

function mobile:hide( hidden )

    -- view models
    E.set_entity_flags(self.body,   E.entity_flag_hidden, hidden)
    E.set_entity_flags(self.tire1,  E.entity_flag_hidden, hidden)
    E.set_entity_flags(self.skid1,  E.entity_flag_hidden, hidden)
    E.set_entity_flags(self.skid2,  E.entity_flag_hidden, hidden)

    local viewstruc = self.viewgeoms and not hidden

    -- view structures
    E.set_entity_flags(self.base,   E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.frame,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.tire1,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.skid1,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.skid2,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.shock1, E.entity_flag_visible_body, viewstruc)
    E.set_entity_flags(self.shock2, E.entity_flag_visible_body, viewstruc)
    E.set_entity_flags(self.skd1f,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.skd2f,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.skd1b,  E.entity_flag_visible_geom, viewstruc)
    E.set_entity_flags(self.skd2b,  E.entity_flag_visible_geom, viewstruc)

end

function mobile:set_position( px, py, pz, ry )

    -- assign relative positions of mobile elements
    local pos = {
	{ self.wheel_ofs_x, self.wheel_ofs_y, self.wheel_ofs_z }, -- tire
	{ self.skid_rght,   self.skid_ofs_y,  self.skid_ofs_z  }, -- skid 1
	{ self.skid_left,   self.skid_ofs_y,  self.skid_ofs_z  }, -- skid 2
	{ 0,                self.base_ofs_y,  0                }, -- base
    }

    -- adjust all positions by mobile scale
    for i = 1, 4 do
	pos[i][1] = pos[i][1] * self.scale
	pos[i][2] = pos[i][2] * self.scale
	pos[i][3] = pos[i][3] * self.scale
    end
    
    -- adjust the wheel positions to account for rotation
    for i=1, 3 do
	local d = math.sqrt(pos[i][1]*pos[i][1] + pos[i][3]*pos[i][3])
	local t = math.deg(math.atan(pos[i][1] / pos[i][3]))
	if pos[i][3] < 0 then t = t + 180 end
	t = t + ry
	pos[i][1] = d * math.sin(math.rad(t))
	pos[i][3] = d * math.cos(math.rad(t))
    end

    -- offset all positions by location to place mobile
    for i = 1, 4   do
	pos[i][1] = pos[i][1] + px
	pos[i][2] = pos[i][2] + py
	pos[i][3] = pos[i][3] + pz
    end

    E.set_entity_rotation(self.base,   0, ry,                    0)
    E.set_entity_rotation(self.tire1,  0, self.wheel_y_rot + ry, 0)
    E.set_entity_rotation(self.skid1,  0, self.skid_y_rot  + ry, 0)
    E.set_entity_rotation(self.skid2,  0, self.skid_y_rot  + ry, 0)
    E.set_entity_rotation(self.shock1, 0, self.skid_y_rot  + ry, 0)
    E.set_entity_rotation(self.shock2, 0, self.skid_y_rot  + ry, 0)

    E.set_entity_position(self.tire1,  pos[1][1], pos[1][2], pos[1][3])
    E.set_entity_position(self.skid1,  pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_position(self.skid2,  pos[3][1], pos[3][2], pos[3][3])
    E.set_entity_position(self.shock1, pos[2][1], pos[2][2], pos[2][3])
    E.set_entity_position(self.shock2, pos[3][1], pos[3][2], pos[3][3])
    E.set_entity_position(self.base,   pos[4][1], pos[4][2], pos[4][3])
end

function mobile:move(dx, dy, dz)
    --[[
    E.move_entity( self.base  , dx, dy, dz )
    E.move_entity( self.tire1 , -dz, dy, dx )
    E.move_entity( self.skid1 , dx, dy, dz )
    E.move_entity( self.skid2 , dx, dy, dz )
    E.move_entity( self.shock1, dx, dy, dz )
    E.move_entity( self.shock2, dx, dy, dz )
    --]]
--    self.vz / self.max_vel

    local k1, k2 = 1, 2

    if self.on_surf then
	if self.surf_type == "ICE" then
	    k1 = 100
	elseif self.surf_type == "MUD" then
	    k1 = 50
	    k2 = 2
	end
    end
    
    dx = (self.x0 * (k1-1) + dx)/(k1 * k2)
    dy = (self.y0 * (k1-1) + dy)/(k1 * k2)
    dz = (self.z0 * (k1-1) + dz)/(k1 * k2)

    local px, py, pz
    px, py, pz = E.get_entity_position( self.base )
    E.set_entity_position( self.base,   px + dx, py + dy, pz + dz )
    px, py, pz = E.get_entity_position( self.tire1 )
    E.set_entity_position( self.tire1,  px + dx, py + dy, pz + dz )
    px, py, pz = E.get_entity_position( self.skid1 )
    E.set_entity_position( self.skid1,  px + dx, py + dy, pz + dz )
    px, py, pz = E.get_entity_position( self.skid2 )
    E.set_entity_position( self.skid2,  px + dx, py + dy, pz + dz )
    px, py, pz = E.get_entity_position( self.shock1 )
    E.set_entity_position( self.shock1, px + dx, py + dy, pz + dz )
    px, py, pz = E.get_entity_position( self.shock2 )
    E.set_entity_position( self.shock2, px + dx, py + dy, pz + dz )

    self.x0, self.y0, self.z0 = dx, dy, dz
end

function mobile:lean(dx, dy, dz)
    if not self.on_ground then
	--E.add_entity_torque(self.base, dx, dy, dz)
	E.turn_entity(self.base, dx, dy, dz)
    end
end

-------------------------------------------------------------------------------
-- physics functions

function mobile:in_control()
    return (self.state == STATE_MOBILE_RACING) or 
	(self.state == STATE_MOBILE_RESPAWN)
end

function mobile:set_throttle(low, high, rev, boost)
    if self.throt.state == STATE_THROT_OH then
	return
    end
    
    local boost_pwrup = (self.run_time - self.boost_time) < self.boost_length

    if boost or boost_pwrup then
	self.throt:set_state(STATE_THROT_BOOST)
    elseif high then 
	self.throt:set_state(STATE_THROT_HIGH)
    elseif low then
	self.throt:set_state(STATE_THROT_LOW)
    elseif rev then
	self.throt:set_state(STATE_THROT_REV)
    else
	self.throt:set_state(STATE_THROT_IDLE)
    end
end

function mobile:calc_physics ( dt )

    tpx, tpy, tpz = E.get_entity_position(self.base)
    dpx, dpy, dpz = tpx - self.px, tpy - self.py, tpz - self.pz
    tvx, tvy, tvz = dpx / dt,      dpy / dt,      dpz / dt

    txx, txy, txz = E.get_entity_x_vector(self.base)
    tyx, tyy, tyz = E.get_entity_y_vector(self.base)
    tzx, tzy, tzz = E.get_entity_z_vector(self.base)

    -- computes angular values in radians
    trx, try, trz = math.asin(tyz), math.asin(tzx), math.asin(txy)
    drx, dry, drz = trx - self.rx,  try - self.ry,  trz - self.rz
    tsx, tsy, tsz = drx / dt,       dry / dt,       drz / dt
    
    self.px, self.py, self.pz = tpx, tpy, tpz
    self.vx, self.vy, self.vz = tvx, tvy, tvz
    self.xx, self.xy, self.xz = txx, txy, txz
    self.yx, self.yy, self.yz = tyx, tyy, tyz
    self.zx, self.zy, self.zz = tzx, tzy, tzz
    self.rx, self.ry, self.rz = trx, try, trz
    self.sx, self.sy, self.sz = tsx, tsy, tsz

    local tire_rate = E.get_entity_joint_attr(self.base, self.tire1, 
						E.joint_attr_rate_2)
    self.rate = tire_rate/self.max_rate
    if self.rate < 0 then  self.rate = 0 end
    
end

function mobile:apply_joints ( )

    -- turn
    if self.on_ground then
	local vfac = self.vz / self.max_vel
	local tx   = self.yx * self.turn * self.torque_max --* vfac
	local ty   = self.yy * self.turn * self.torque_max --* vfac
	local tz   = self.yz * self.turn * self.torque_max --* vfac

	self.targ_head = self.turn * self.turn_max

	local latforce = -self.turn * self.torque_max
	E.add_entity_torque(self.base,  tx,       ty, tz )
	E.add_entity_force (self.base,  latforce, 0,  0  )
	E.add_entity_force (self.tire1, latforce, 0,  0  )
	self:move( -self.turn, 0, 0)
    end
    
    -- Apply power to the rear wheels.
    local accel, veloc = self.accel_high, 0

    if self.throt.state == STATE_THROT_LOW then
	veloc = self.veloc_max_l
	accel = self.accel_low
    elseif self.throt.state == STATE_THROT_HIGH then
	veloc = self.veloc_max_h
	accel = self.accel_high
    elseif self.throt.state == STATE_THROT_BOOST then
	veloc = self.veloc_max_b
	accel = self.accel_boost
    end

    if self.on_surf and self.surf_type == "MUD" and veloc > 0 then
	veloc = self.veloc_mud
	accel = self.accel_mud
    end


--    accel = accel - self.friction

--    if accel < 0 then veloc = 0 end
--    accel = math.abs(accel)

    E.set_entity_joint_attr(self.base, self.tire1, 
			    E.joint_attr_velocity_2,  veloc)
    E.set_entity_joint_attr(self.base, self.tire1, 
			    E.joint_attr_force_max_2, accel)

--    if DEBUG_MODE then
--	E.print_console("veloc: "..veloc.." accel: "..accel.."\n")
--    end
    
end

function mobile:stabilize( dt )

    local ax, ay, az = 0, 0, 0

    if not self.on_ground then
	ax = ax - self.sx/dt
	ay = ay - self.sy/dt
	az = az - self.sz/dt
	ay = ay - self.ry/(dt * dt) * self.cor_fac_ry 
	az = az - self.rz/(dt * dt) * self.cor_fac_rz 
    end
    if self.on_ground then
	local yadj = math.rad(self.targ_head) - self.ry
	ax = ax - self.rx / (dt * dt) * self.cor_fac_rx 
	ay = ay + yadj    / (dt * dt) * self.cor_fac_ry 
	az = az - self.rz / (dt * dt) * self.cor_fac_rz 
    end
    E.add_entity_torque(self.base, ax + self.lean_x * 250, ay, az)
end

function mobile:set_surf_affect( type )
    local tire_f, fram_f, skid_f

    if not type then
	tire_f = self.tire_fric
	fram_f = self.frame_fric
	skid_f = self.skid_fric
	self.surf_type = "NUL"
	--E.print_console(string.format("%d: set friction %s\n", self.id, "NUL"))
    else
	if type == "MUD" then
	    
	    tire_f = 250
	    fram_f = 100
	    skid_f = 100
	
	elseif type == "ICE" then
	    tire_f = 30
	    fram_f = 0
	    skid_f = 0
	end
	self.surf_type = type
	--E.print_console(string.format("%d: set friction %s\n", self.id, type))
    end


    E.set_entity_geom_attr(self.tire1, E.geom_attr_friction, tire_f)
    E.set_entity_geom_attr(self.frame, E.geom_attr_friction, fram_f)
    E.set_entity_geom_attr(self.skd1f, E.geom_attr_friction, skid_f)
    E.set_entity_geom_attr(self.skd2f, E.geom_attr_friction, skid_f)
    E.set_entity_geom_attr(self.skd1b, E.geom_attr_friction, skid_f)
    E.set_entity_geom_attr(self.skd2b, E.geom_attr_friction, skid_f)
end

-------------------------------------------------------------------------------
-- contact functions

function mobile:change_contact_obs ( contact )
    if contact then
	if self.vz < -30 then
	    E.play_sound(self.sounds.wsh[math.random(1,2)])
	end
    end
end

function mobile:change_contact_surf ( contact, type )
    if contact then
	self:set_surf_affect( type )
    else
	self:set_surf_affect()
    end
end

function mobile:change_contact_mob ( contact )
    if contact then
	E.play_sound(self.sounds.mob)
	--E.loop_sound(self.sounds.rail)
    else
	--E.stop_sound(self.sounds.rail)
    end
end

function mobile:contact_pwrup ( type )
    if     type == "BOOST" and self:in_control() then
	self.boost_time = self.run_time
	if self.id == chasetarg then
	    E.play_sound(sounds.boost)
	end
    elseif type == "COOL"  and self:in_control() then
	self.throt.heat = self.throt.heat - 0.5
	if self.id == chasetarg then
	    E.play_sound(sounds.chill)
	end
    end
end

function mobile:change_contact_rail ( contact, px, py, pz, nx, ny, nz, mag )
    if contact and self.py < 5 then
	E.loop_sound(self.sounds.rail)
    else
        E.stop_sound(self.sounds.rail)
    end
end

function mobile:change_contact_grnd ( contact, px, py, pz, nx, ny, nz, mag )
    if contact then

	if mag >= 0.04 then
	elseif mag >= 0.001 then
	end
	
	E.play_sound(self.sounds.hit[math.random(1, 4)])

	self:set_anim_frame("sit")
    else
	self:set_anim_frame("in_air")
    end
end

function mobile:is_owner( entity )

    if     entity == self.tire1 then
	return "TIRE"

    elseif entity == self.skid1 or
	   entity == self.skd1f or
	   entity == self.skd2f or
	   entity == self.skd1b or
	   entity == self.skd2b or
	   entity == self.skid2 then
	return "SKID"

    elseif entity == self.frame then
	return "FRAME"
	
    elseif entity == self.body  then
	return "BODY"
	
    elseif entity == self.base  then
	return "BASE"
	
    else
	return nil
    end
end

function mobile:do_contact( part, obj_cat, type, px, py, pz, nx, ny, nz, mag )

    if (obj_cat == category_rail) and (-self.vz > 7) then
	self.rail_time = self.run_time
	if not self.on_rail then 
	    self:change_contact_rail( true, px, py, pz, nx, ny, nz, mag )
	    self.on_rail = true
	end
    elseif (obj_cat == category_grnd) or (obj_cat == category_obst) then
	self.ground_time = self.run_time
	if not self.on_ground then
	    self:change_contact_grnd( true, px, py, pz, nx, ny, nz, mag )
	    self.on_ground = true
	end
    elseif (obj_cat == category_pwrup) then
	self.pwrup_time = self.run_time
	if not self.in_pwrup then
	    self:contact_pwrup( type )
	    self.in_pwrup = true
	end
    elseif (obj_cat == category_srfc) then
	self.surf_time = self.run_time
	if not self.on_surf then
	    self:change_contact_surf( true, type )
	    self.on_surf = true
	end
    elseif check_flag(category_bike, obj_cat) then
	self.mob_time = self.run_time
	if not self.hit_mob then
	    self:change_contact_mob( true )
	    self.hit_mob = true
	end
    elseif (obj_cat == category_haz) then
	self.haz_time = self.run_time
	if not self.hit_haz then
	    E.play_sound(self.sounds.box[math.random(1,2)])
	    self.hit_haz = true
	end
    end

    if (obj_cat == category_obst) then
	self.obs_time = self.run_time
	if not self.hit_obs then
	    self:change_contact_obs( true, px, py, pz, nx, ny, nz, mag )
	    self.hit_obs = true
	end
    end

end

function mobile:check_contact( run_time )

    if self.ground_time < self.run_time then
	if self.on_ground then
	    self:change_contact_grnd ( false )
	    self.on_ground = false
	end
    end
    
    if self.obs_time < self.run_time then
	if self.hit_obs then
	    self:change_contact_obs ( false )
	    self.hit_obs = false
	end
    end
    
    if self.rail_time < self.run_time then
	if self.on_rail then
	    self:change_contact_rail ( false )
	    self.on_rail = false
	end
    end
    
    if self.pwrup_time < self.run_time then
	if self.in_pwrup then
	    self.in_pwrup = false
	end
    end

    if self.surf_time < self.run_time then
	if self.on_surf then
	    self:change_contact_surf ( false )
	    self.on_surf = false
	end
    end
    if self.mob_time < self.run_time then
	if self.hit_mob then
	    self:change_contact_mob ( false )
	    self.hit_mob = false
	end
    end

    if self.haz_time < self.run_time then
	self.hit_haz = false
    end
end

-------------------------------------------------------------------------------

function mobile:do_idle( dt )
    self.state_time = self.state_time + dt

    self:calc_physics (dt)
    
    local shadow_y = 0.01 * self.id + shadow_layer
    E.set_entity_position(self.shadow, self.px-.5, shadow_y, self.pz-.5)

    self.throt:do_idle( dt )

    self:check_contact(run_time)
    self.run_time = self.run_time + dt

    if not self.really_high and (self.py > 13) and (self.id == chasetarg) then
	self.really_high = true
	E.loop_sound(self.sounds.wind)
    end
    if self.really_high and (self.py < 5) and (self.id == chasetarg) then
	self.really_high = false
	E.stop_sound(self.sounds.wind)
    end

    if self:in_control() then 

	-- check for out of control condition
	if not (self.yy > 0) and self.on_ground then 
	    self:set_state(STATE_MOBILE_OOC)
	end

	self:stabilize( dt ) 
    else
	self.throt:set_state(STATE_THROT_IDLE)
    end

    self:apply_joints()

    -- adjust one of the engine noises to match the speed
    E.set_sound_frequency(self.throt.stream1, 1+self.rate)
    E.set_sound_amplitude(self.throt.stream1, 0.25 + 0.3*self.rate)

    -- flash while respawning
    if self.state == STATE_MOBILE_RESPAWN then
	
	--[[
	local e = math.floor(self.state_time * 10) 
	if math.mod(e, 5) == 0 and 
	    not (math.floor(e / 5) == self.blink_time) then
	    
	    self.blink_time = math.floor(e/5)
	    local alpha = 0
	    if self.opaque then
		alpha = .3 * self.alpha
		self.opaque = false
	    else
		alpha = self.alpha
		self.opaque = true
	    end
	    self:set_alpha(alpha)
	    
	end
	--]]
	-- check to see if respawn period is over
	if self.state_time > 4 then
	    --self:set_alpha(self.alpha)
	    self:set_state(STATE_MOBILE_RACING)
	end

    elseif (self.state == STATE_MOBILE_OOC) or 
 	   (self.state == STATE_MOBILE_OOB) then

	-- if the mobile has been out of control
	-- long enough, then respawn

	if self.state_time > 3 then
	    self:set_state(STATE_MOBILE_RESPAWN) 
	end


    elseif self.state == STATE_MOBILE_RESPAWN then
    elseif self.state == STATE_MOBILE_POSTRACE then
	
    end


end


function mobile:do_change_state( pstate, nstate )
--    E.print_console(pstate.."->"..nstate.."\n")
    if pstate == STATE_MOBILE_RACING and nstate == STATE_MOBILE_OOB then
	self.throt:set_state(STATE_THROT_IDLE)
	self.turn = 0
	self.breaking = true
	self:apply_joints()

    elseif pstate == STATE_MOBILE_RACING and nstate == STATE_MOBILE_OOC then
	self.throt:set_state(STATE_THROT_IDLE)
	self.turn = 0
	self.breaking = true
	self:apply_joints()
	if self.id == chasetarg then
	    gamefsm:flash("wipeout", 1.4)
	end

    elseif nstate == STATE_MOBILE_RESPAWN then
	local x, y, z = E.get_entity_position(self.base)
	self:reset( 0, 8, z, 0 )
	self.run_time, self.ground_time = 0, 0
	if scene.chase == self.base then
	    scene:set_state(STATE_SCENE_SIDE)
	end

	if self.id == chasetarg then
	    E.play_sound(sounds.lap)
	end
    end
	
end

-------------------------------------------------------------------------------
-- throt_fsm: Throttle finite state machine
-------------------------------------------------------------------------------

STATE_THROT_IDLE    = "IDLE"
STATE_THROT_LOW     = "LOW"
STATE_THROT_HIGH    = "HIGH"
STATE_THROT_BOOST   = "BOOST"
STATE_THROT_REV     = "REV"
STATE_THROT_OH      = "OH"

mobile.throt = FSM:instantiate{state = STATE_NONE, tostr = "MOBILE_THROT_FSM"}

mobile.throt.low_pitch  = 1.5
mobile.throt.high_pitch = 2
mobile.throt.rev_pitch  = 2.5
mobile.throt.oh_time    = 16 -- numsecs to overheat with full throt 

function mobile.throt:do_change_state ( prev_state, new_state )
    if not (prev_state == new_state) then

	if (prev_state == STATE_THROT_BOOST) and 
	   (self.mob.base == scene.chase) then
	    scene:set_state(STATE_SCENE_SIDE)
	end

	if prev_state == STATE_THROT_OH then
	    E.stop_sound(sounds.overheat)
	    --E.close_console()
	end

	if new_state == STATE_THROT_IDLE then
	    E.set_sound_amplitude(self.stream2, 0.25)
	    E.set_sound_frequency(self.stream2, 1)
	elseif new_state == STATE_THROT_LOW then
	    E.set_sound_amplitude(self.stream2, 0.35)
	    E.set_sound_frequency(self.stream2, self.low_pitch)
	elseif new_state == STATE_THROT_HIGH then
	    E.set_sound_amplitude(self.stream2, 0.45)
	    E.set_sound_frequency(self.stream2, self.high_pitch)
	elseif new_state == STATE_THROT_BOOST then
	    E.set_sound_amplitude(self.stream2, 0.55)
	    E.set_sound_frequency(self.stream2, self.high_pitch)
	    if (self.mob.base == scene.chase) then
		scene:set_state(STATE_SCENE_BOOST)
	    end
	elseif new_state == STATE_THROT_OH then
	    E.loop_sound(sounds.overheat)
	    gamefsm:flash("overheat", 1.6, true)
	    E.set_sound_frequency(self.stream2, 1)
	    E.set_sound_amplitude(self.stream2, 0.25)
	    --E.print_console(self.tostr..": "..new_state.."\n")
	end
    end
end

function mobile.throt:do_idle( dt )
    self.state_time = self.state_time + dt

    if self.mob.id > 1 then return end

    if     self.state == STATE_THROT_IDLE then
	self.heat = self.heat - dt/(self.oh_time * 2)
	if self.heat < 0   then self.heat = 0   end
    elseif self.state == STATE_THROT_LOW then
	if self.heat < 0.5 then 
	    self.heat = self.heat + dt/self.oh_time
	else
	    self.heat = self.heat - dt/(self.oh_time * 2)
	end
    elseif self.state == STATE_THROT_HIGH then
	self.heat = self.heat + dt/self.oh_time
	if self.heat > 1   then self:set_state(STATE_THROT_OH) end	
    elseif self.state == STATE_THROT_BOOST then
	-- do not alter heat during boost
	--self.heat = self.heat - dt/(self.oh_time * 2)
	--if self.heat < 0   then self.heat = 0   end
    elseif self.state == STATE_THROT_OH then
	if self.state_time > 5 then self:set_state(STATE_THROT_IDLE)
	elseif self.state_time > 2.5 then
	    self.heat = self.heat - dt/(self.oh_time * 2)
	end
    end

end

-------------------------------------------------------------------------------
-- sound: mobile sound finite state machine
-------------------------------------------------------------------------------

--mobile.sound = FSM:new{state = 0, tostr = "MOBILE_SOUND_FSM"}
--mobile.sound.streams = {}

--function mobile.sound:do_change_state ( prev_state, new_state )
--    if not (prev_state == new_state) then
--	E.print_console("setting sound: ".. new_state .. "\n")
--	E.stop_sound(self.streams[prev_state])
--	E.loop_sound(self.streams[new_state])
--   end
--end
