SWEP: Draw line between two points in 3D space

Hi all!

For a gamemode I’m working on I’m making a SWEP that lets you attach things to a vehicle via a rope. If the player is pointing at a towable object and they are within range of a towing vehicle, I’d like to render a preview rope.

This is causing me more difficulty than I had anticipated.

The code I’d like to have looks a bit like this:



local rope = Material("cable/rope")
function SWEP:ViewModelDrawn()
	local trc = self:OwnerTrace(self.RopeLength)
	print(trc.Entity)
	if self:ValidTarget(trc.Entity) then
		print("drawing")
		local towPoint = self:FindClosestTowPoint(trc.HitPos)
		if towPoint then
			render.SetMaterial(rope)
			render.DrawBeam(towPoint:GetPos(), trc.HitPos, 1, 0, 0, Color(255, 255, 255, 255))
		end
	end
end


Unfortunately there are two issues here:

  1. Render functions in the ViewModelDrawn hook is drawn in slightly the wrong location. Additionally, Vector:ToScreen() returns slightly incorrect coordinates. These imperfections are more noticeable the further the player is from the points that are being rendered.
  2. Stuff drawn in the ViewModelDrawn hook is displayed on top of all world geometry and doesn’t respect z index.

I have tried a bunch of other hooks, some documented in the wiki and others undocumented but mentioned in weird places on the internet:

SWEP:DrawWorldModel
SWEP:DrawWorldModelTranslucent
SWEP:Draw

Interestingly enough, according to some hastily inserted print(“test”) lines, none of these are actually ever executed. My SWEP has both a world model and a view model.

The closest I’ve managed to get is:



function SWEP:DrawHUD()
	local trc = self:OwnerTrace(self.RopeLength)
	print(trc.Entity)
	if self:ValidTarget(trc.Entity) then
		print("drawing")
		local towPoint = self:FindClosestTowPoint(trc.HitPos)
		if towPoint then
			local start = towPoint:GetPos():ToScreen()
			local endpos = trc.HitPos:ToScreen()
			print(start.x, start.y, "", endpos.x, endpos.y)
			cam.Start2D();
			surface.SetDrawColor(Color(100, 255, 100))
			surface.DrawLine(start.x, start.y, endpos.x, endpos.y)
			cam.End2D();
		end
	end
end


This displays a green line between the correct positions, but is not a desirable solution for the following reasons:

  1. The line doesn’t take into account perspective. I could theoretically fix this by drawing a polygon, but that seems like overkill
  2. The line is a solid colour rather than textured. Again, polygons could solve this.
  3. The line draws on top of all world geometry and does not respect z indexes.

Hopefully you geniuses will be able to help me out, I hope I’ve explained the situation clearly.

Thanks,

Yoshie

Here’s an example: https://dl.dropboxusercontent.com/u/26074909/tutoring/render_lasers_rendering_cubes.lua.html

Make sure you use a render-hook. If you use a draw-hook you’ll need to use a cam Start3D2D to convert the 3D render function to something the 2D draw hook will understand. You can also convert a vector ToScreen which does something similar ( if you’re using a surface.DrawLine function but want it to appear “in” the world ).

Thanks for your help Acecool.

I consider this not a very nice solution considering the draw hook is no longer ‘owned’ by the SWEP, but the following code works:



local rope = Material("cable/rope")
hook.Add("PostDrawTranslucentRenderables", "DrawPreviewRope", function(_isDepth, _isSkybox)
	if _isSkybox then return end
	
	local ply = LocalPlayer()
	local wep = ply:GetActiveWeapon()
	
	if not IsValid(wep) or wep:GetClass() ~= "weapon_scrap_tow" then return end
	
	local trc = wep:OwnerTrace(wep.RopeLength)
	print(trc.Entity)
	if wep:ValidTarget(trc.Entity) then
		print("drawing")
		local towPoint = wep:FindClosestTowPoint(trc.HitPos)
		if towPoint then
			render.SetMaterial(rope)
			render.DrawBeam(towPoint:GetPos(), trc.HitPos, 1, 0, 0, Color(255, 255, 255, 255))
		end
	end
end)


One final question: Any idea why the texture is all messed up? cable/rope looks like a rope, but the DrawBeam just looks like a brown smear.

Thanks,

Yoshie

Look at the arguments… render.DrawBeam( _pos1, _pos2, _size, _textureStart, _textureEnd, COLOR_WHITE )

Play with _textureStart and _textureEnd; it is how you control the texturing of the beam. http://wiki.garrysmod.com/page/render/DrawBeam

Texture should repeat; so set the end to the width/length or the texture and see what happens. You can grab the texture width and height by using:
local _w = _material:Width( );
local _h = _material:Height( );

Cheers, the textureStart and textureEnd arguments aren’t well documented at all. In case anyone else finds this thread, set texture start to 0 and change texture end to control how stretched your texture is. If texture start and texture end are the same, one single row of pixels will be stretched across the whole of the rope. I’m not actually sure what the specific values mean still, but I found that for “cable/rope” an end value of 4 works nicely.