Simulated boat physics


This thread is different from Grea$emonkey's thread. I didn't want to hijack it with my question(s).

Alright, now that we got that out of the way; Me and Royzo are working on a gamemode and we’re doing fine coding/art wise, but now that I tried to tackle one of the biggest challenges in our project (boat physics) things have been going downhill. The basic premise is that each player is invisible and that every player has his own little boat he can control. The level is basically a giant brush o’ water.


What I want it to do and what it doesn't do:

Turning left/right
Holding IN_LEFT or IN_RIGHT will cause your ship to turn relative to your current speed. Aka if you’re standing still you won’t turn left or right and if you’re sailing fast you’ll turn left and right fast.
Sailing forward/backward
Holding IN_FORWARD or IN_BACKWARD will cause your ship to accelerate forward or backward. (Note: backpedaling on a ship isn’t actually possible irl) Currently my ship slowly bends to the right when moving forward and wobbles intensely when moving backward.
Staying upright
I tried a keep upright constraint but that was way too stiff and glitchy and I’m kinda out of ideas now. :v:

Here’s a little image to further explain what I mean:

http://dl.dropbox.com/u/2372797/illustration.png

The red arrow indicates ideal forward movement and the pink arrow indicates current forward movement.
The yellow arrow indicates ideal backward movement and the orange arrow indicates current backward movement.
The green arrows indicate ideal rotation.


My current code

(It has gone through several revisions, this being mostly stolen from Garry’s scripted vehicle base)


ENT.Type 		= "anim"

ENT.PrintName	= "Nincompoop Ship"
ENT.Author		= "Daimao"
ENT.Contact		= "die"

ENT.Spawnable			= false
ENT.AdminSpawnable		= false
ENT.RenderGroup 		= RENDERGROUP_OPAQUE

AddCSLuaFile( "shared.lua" )

AccessorFunc( ENT, "m_pPlayer", 		"Player" )

/*---------------------------------------------------------
   Name: Initialize
---------------------------------------------------------*/
function ENT:Initialize()

	if ( SERVER ) then
		self:SetModel( "models/props_nincompoop/pirate_ship.mdl" )
		self:SetMaterial( "models/debug/debugwhite" )

		self:SetMoveType( MOVETYPE_CUSTOM )
		self:PhysicsInit( SOLID_VPHYSICS )
		
		local phys = self:GetPhysicsObject()
	
		if (phys:IsValid()) then
			phys:Wake()
			phys:SetBuoyancyRatio( 1 )
			phys:SetMaterial( "ice" )
		end
		
		local max = self:OBBMaxs()
		local min = self:OBBMins()
		
		self:StartMotionController()
		
	end
	    
end

/*---------------------------------------------------------
   Name: Initialize
---------------------------------------------------------*/
function ENT:Think()

	if ( SERVER ) then
	
		if ( !IsValid(self.m_pPlayer) ) then self:Remove() return end
	
		self:GetPhysicsObject():Wake()
		
		self.m_pPlayer:SetPos( self:GetPos() )
	
	end

end

function ENT:GetForwardAcceleration( driver, phys, ForwardVel )

	if (!driver || !driver:IsValid()) then return 0 end
	
	if ( driver:KeyDown( IN_FORWARD ) ) then return 50 end
	if ( driver:KeyDown( IN_BACK ) ) then return -50 end

	return 0
	
end

function ENT:GetTurnYaw( driver, phys, ForwardVel )

	if ( !driver || !driver:IsValid() ) then return 0 end
	
	if ( driver:KeyDown( IN_MOVELEFT ) ) then return 10 end
	if ( driver:KeyDown( IN_MOVERIGHT ) ) then return -10 end
	
	return 0
	
end

/*---------------------------------------------------------
   Name: Simulate
---------------------------------------------------------*/
function ENT:PhysicsSimulate( phys, deltatime )

	--local angles = self:GetAngles()
	--self:SetAngles( Angle( angles.p, angles.y, 0) )

	--phys:AddAngleVelocity( Angle(0,-phys:GetAngleVelocity().y,-phys:GetAngleVelocity().r) )

	local up = phys:GetAngle():Up()
	if ( up.z < 0.33 ) then
		return SIM_NOTHING
	end

	local driver = self.m_pPlayer
	
	local forward = 0
	local right = 0
	
	local Velocity = phys:GetVelocity()
	local ForwardVel = phys:GetAngle():Forward():Dot( Velocity )
	local RightVel = phys:GetAngle():Right():Dot( Velocity )
	
	if ( driver ) then
	
		forward = self:GetForwardAcceleration( driver, phys, ForwardVel )
		yaw		= self:GetTurnYaw( driver, phys, ForwardVel )
		
	end
	
	right = RightVel
	
	forward = forward - ForwardVel * 0.01
	
	local Linear = ( Vector( forward, right, 0 ) ) * deltatime * 1000;
	
	local AngleVel = phys:GetAngleVelocity()

	local AngleFriction = AngleVel * -0.1
	
	local Angular = (AngleFriction + Vector( 0, 0, yaw )) * deltatime * 1000;
	
	return Angular, Linear, SIM_LOCAL_ACCELERATION
	
end



Final words

If you’re able to help me I’ll be eternally grateful and will help you with anything model/skin/effect related for your Fretta gamemode. Thanks in advance! :love:

EDIT:
I fixed the spazzy keep upright thing by lowering the keep upright value. The thing is; it occasionally just crashes out of nowhere and I don’t really want that. For some reason it stopped wobbling and now it just goes in circles whenever I press forward or backward.



 	
ENT.Type 		= "anim"

ENT.PrintName	= "Nincompoop Ship"
ENT.Author		= "Daimao"
ENT.Contact		= "die"

ENT.Spawnable			= false
ENT.AdminSpawnable		= false
ENT.RenderGroup 		= RENDERGROUP_OPAQUE

AddCSLuaFile( "shared.lua" )

AccessorFunc( ENT, "m_pPlayer", 		"Player" )

/*---------------------------------------------------------
   Name: Initialize
---------------------------------------------------------*/
function ENT:Initialize()

	if ( SERVER ) then
		self:SetModel( "models/props_nincompoop/pirate_ship.mdl" )
		self:SetMaterial( "models/debug/debugwhite" )

		self:SetMoveType( MOVETYPE_CUSTOM )
		self:PhysicsInit( SOLID_VPHYSICS )
		
		local phys = self:GetPhysicsObject()
	
		if (phys:IsValid()) then
			phys:Wake()
			phys:SetMass( 20 )
			phys:SetBuoyancyRatio( 1 )
			phys:SetMaterial( "ice" )
		end
		
		local max = self:OBBMaxs()
		local min = self:OBBMins()
		
		local Constraint = ents.Create( "phys_keepupright" )
        Constraint:SetAngles( self:GetAngles() )
        Constraint:SetKeyValue( "angularlimit", 100 )
        Constraint:SetPhysConstraintObjects( phys, phys )
        Constraint:Spawn()
        Constraint:Activate()
		
		self:StartMotionController()
		
	end
	    
end

/*---------------------------------------------------------
   Name: Initialize
---------------------------------------------------------*/
function ENT:Think()

	if ( SERVER ) then
	
		if ( !IsValid(self.m_pPlayer) ) then self:Remove() return end
	
		self:GetPhysicsObject():Wake()
		
		self.m_pPlayer:SetPos( self:GetPos() )
	
	end

end

function ENT:GetForwardAcceleration( driver, phys, ForwardVel )

	if (!driver || !driver:IsValid()) then return 0 end
	
	if ( driver:KeyDown( IN_FORWARD ) ) then return 50 end
	if ( driver:KeyDown( IN_BACK ) ) then return -50 end

	return 0
	
end

function ENT:GetTurnYaw( driver, phys, ForwardVel )

	if ( !driver || !driver:IsValid() ) then return 0 end
	
	if ( driver:KeyDown( IN_MOVELEFT ) ) then return 10 end
	if ( driver:KeyDown( IN_MOVERIGHT ) ) then return -10 end
	
	return 0
	
end

/*---------------------------------------------------------
   Name: Simulate
---------------------------------------------------------*/
function ENT:PhysicsSimulate( phys, deltatime )

	--local angles = self:GetAngles()
	--self:SetAngles( Angle( angles.p, angles.y, 0) )

	--phys:AddAngleVelocity( Angle(0,-phys:GetAngleVelocity().y,-phys:GetAngleVelocity().r) )

	local up = phys:GetAngle():Up()
	if ( up.z < 0.33 ) then
		return SIM_NOTHING
	end

	local driver = self.m_pPlayer
	
	local forward = 0
	local right = 0
	
	local Velocity = phys:GetVelocity()
	local ForwardVel = phys:GetAngle():Forward():Dot( Velocity )
	local RightVel = phys:GetAngle():Right():Dot( Velocity )
	
	if ( driver ) then
	
		forward = self:GetForwardAcceleration( driver, phys, ForwardVel )
		yaw		= self:GetTurnYaw( driver, phys, ForwardVel )
		
	end
	
	right = RightVel
	
	forward = forward - ForwardVel * 0.01
	
	local Linear = ( Vector( forward, right, 0 ) ) * deltatime * 1000;
	
	local AngleVel = phys:GetAngleVelocity()

	local AngleFriction = AngleVel * -0.1
	
	local Angular = (AngleFriction + Vector( 0, 0, yaw )) * deltatime * 1000;
	
	return Angular, Linear, SIM_LOCAL_ACCELERATION
	
end



It’s not a big deal if the reverse doesn’t work, it could be left out for realism’s sake anyway.

I tried replacing the keep upright constraint with an advanced ballsocket constraint but that pretty much comes with the same issues.

Stealing my thunder! :v:

I have no idea. When I figure mine out I’ll try and help.

I don’t see how. Ballsocket it to the world with -360,360 on the z axis and -90,90 on the other axies.
The water itself will keep whatever you are having floating. Parent it to a box with the plastic barrel physprop material.

The reason your boat is spinning is because that’s how the water works. There is no fin physics in this game.
Fake it by taking the angular speeds of the boat and counter attack them with force. Since i’m no lua-coder i can’t help you more on this matter but this is the only way i can think off. Another way might be to simply apply rotational force so that it stays in it’s current direction unless the A or D keys is pressed or something?

I’m just used to e2 so this rotational force and shit might not exist. But yeah. Uh. Regular force though should though. Set velocity or something.

A way of cheathing could be making a very heavy object that is axised to the ground and simply adv-ballsocket the boat to it? So that when the heavy thing turns the boat turns too? Just make it heavier than the boat and it should work.

I have toyed with boat physics. I know more about planes but I can give you some tips on how I do vehicle stuff.

One second I will just write some code.

I personally like to use SIM_GLOBAL_ACCELERATION. It probably involves a lot more maths but IIRC I couldn’t get local working without it spazzing into a giant mess.

For the left and right thing:

[lua]
local ctrl = math.Clamp((math.abs(<FORWARD_VELOCIY> ^ 2) / <EXPECTED MAX VELOCITY SQUARED>) * 10,0,12) //How much influence the controls have
[/lua]

Here ctrl will give you a number between 0 and 12. You can then use this to multiply your left turning by a certain amount.

For turning itself you should do something like this:

[lua]
local forcelinear, forceangular = p:CalculateForceOffset(self:GetRight() * <YOUR Y VALUE> * ctrl, self:GetPos() + self:GetForward() * -50) //The rudder on boats is at the back. So force it from the back.
//Then add these onto your main forcelinear and angular bear in mind these are global vectors.
[/lua]

I would also recommend simulating keep upright yourself. Gives you a lot more control on everything. I use this:

[lua]
local rollforce = (self:GetRight() * (self:GetAngles().r + <YOUR Y VALUE>)) * -ctrl * 5
local _, forceangular = p:CalculateForceOffset(rollforce, self:GetPos() + (self:GetUp() * 50))

[/lua]

The reason the Y value (which way the ship is turning I presume) is here is because it will make the ship lean when turning.

p:CalculateForceOffset is a powerful function. Basically the same as applyforceoffset, but it seems to work a lot better. I kinda rushed this but hopefully thats helped you a little. Also I should emphasize vehicles take a unholy amount of time to tweak, I am not sure about boats but with my planes it took me about 2 months just to get them working right. If you want to talk to me on Steam I can help you more, I have got a lot of experience in vehicle physics.

Thanks for the tips! I’ll be sure to fiddle around with your suggestions.

I’ll add you on Steam, thanks a bunch!