Math problem with lines, offset

Community Forums/General Help/Math problem with lines, offset

Ian Martin(Posted 2011) [#1]


Today, let's play "Help Ian with math!" :)
See the attached picture?
I am trying to figure this out. If I have points X1,Y1 and X2,Y2, I know how to find midpoint X3,X4. What I'm trying to figure out is how to find a point a certain number of pixels away from the center at a right angle. For instance, the point at X4,Y4? I'm not sure what you would even call such an equation to look it up. Anybody know how to do this or what this is called? I know this may seem simple, but I have looked and looked at equations and I'm not seeing it. Please help if you can!


Ian Martin(Posted 2011) [#2]
This would be screen pixel locations, from 0,0 in the upper left to 1919,1079 or whatever in the bottom right.


Matty(Posted 2011) [#3]
The gradient of the 90 degree line would be the negative inverse of the line between points 1 and 2. So, if y = mx + c then your new line's gradient would be -1/m. Writing this on phone so difficult to type but that should get you started.


Kryzon(Posted 2011) [#4]
I'll give it a shot with my kindergarten math.

EDIT: Corrected.
If you know [X1,Y1], [X2,Y2] and [X3,Y3], do this:
ANGLE% = 90 ;Degrees.
LENGTH% = 50 ;Pixels.
LineAngle# = Atan2( Y2 - Y1, X2 - X1)

X4# = X3 + Cos(ANGLE + LineAngle)*LENGTH
Y4# = Y3 + Sin(ANGLE + LineAngle)*LENGTH
X3 and Y3 represent the origin from where you're trying to advance towards some point that is 'LENGTH' away and 'ANGLE' around line [x1,y1,x2,y2].

Last edited 2011


Andy_A(Posted 2011) [#5]
It's not the best way to do this but, it works.





Ian Martin(Posted 2011) [#6]
Thanks everybody! That's exactly what I was trying to figure out.

I took the code example and played around with it a bit. I think it could be a bit faster with look-up tables for the math maybe? Anyway, here's what I've got now, thanks for the code above :)

Graphics 800,600,32,2
SetBuffer BackBuffer()

SeedRnd MilliSecs()

Repeat
	
	ProfTimeA=MilliSecs()

	Cls
	;arbitrarily chosen points
	x1 = Rand (0,799)
	y1 = Rand (0,599)

	x2 = Rand (0, 799)	
	y2 = Rand (0, 599)	

	;draw the original line
	Line x1,y1, x2,y2
	
	;find the midpoint
	x3 = (x1+x2) Shr 1
	y3 = (y1+y2) Shr 1

	;find the angle of the line from x1,y1 to x2,y2 and subtract 90 degrees or 270
	If Rand (0,1)=1 Then 
		angle# = ATan2( Float(y2-y1), Float(x2-x1) ) - 270.0
	Else
		angle# = ATan2( Float(y2-y1), Float(x2-x1) ) - 90.0
	EndIf

	;use random pixeldist in this example
	pixelDist = Rand (10,250)	;was 100

	;calc the new point pixels distance at 90 degrees from mid-point
	x4 = Cos(angle) * pixelDist + x3
	y4 = Sin(angle) * pixelDist + y3
	
	;show the mid-point
	Color 255,0,0
	Oval x3-2, y3-2, 5, 5, True
	
	;draw a line from mid-point x3,y3 to x4,y4
	Color 255,255,0
	Line x3,y3, x4,y4

	;show the point 100 pixels away from mid-point x3,y3
	Color 255,0,0
	Oval x4-2, y4-2, 5, 5, True

	ProfTimeB=MilliSecs()-ProfTimeA
	Text 20,20, "Time to execute=" + ProfTimeB

	Flip
	WaitMouse()

Until KeyHit(1) ;ESC exit

End



Comment out this line to see it at speed:
WaitMouse()


Ian Martin(Posted 2011) [#7]
I will have to do this a bunch of times each frame, as I need to take the new point and make lines to it, cut those lines in half and repeat. Eventually, this will be a lightning effect in the game. Thanks again for the help!


Warpy(Posted 2011) [#8]
You can do it with just a single Sqrt call, avoiding the trig functions.


Graphics 800,600,0

dist#=50

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())

	'use scroll wheel or arrow keys to change length of secondary line
	dist:+(MouseZ()-omz+KeyDown(KEY_UP)-KeyDown(KEY_DOWN))*2
	omz=MouseZ()

	'get mouse co-ordinates
	mx = MouseX()
	my = MouseY()

	'click to change position of start of line	
	If MouseHit(1)
		x1#=mx
		y1#=my
	EndIf
	
	'end of line is at mouse cursor
	x2#=mx
	y2#=my
	
	'get the difference between the start and end points
	dx# = x2-x1
	dy# = y2-y1

	'(x3,y3) is at the midpoint of the line
	x3#=x1+dx/2
	y3#=y1+dy/2
	
	'we want to find a vector of length 1 which is at right angles to the first line	
	
	If dy=0	'then perpendicular line must be vertical
		dy=-Sgn(dx)	'whether the perpendicular goes up or down depends on the horizontal direction of the first line
		dx=0
	Else		'gradient of perpendicular is -1/gradient of line
							'gradient of line is dy/dx, so perpendicular is -dx/dy
		m# = -dx/dy*Sgn(dy)	'*Sgn(dy) is to make sure perpendicular is always on the same side of the line
		
		'the vector (1,m) is at right angles to the first line, but doesn't have length 1
		
		'find the length of the vector
		d#=Sqr(1*1+m*m)
		
		'divide by the vector's length to get a vector in the right direction and with length 1
		dx=1/d*Sgn(dy)
		dy=m/d
	EndIf
	
	'find (x4,y4) by moving <dist> units away from (x3,y3) in the direction of the perpendicular vector we just worked out
	x4#=x3+dist*dx
	y4#=y3+dist*dy
	
	'draw both lines
	DrawLine x1,y1,x2,y2
	DrawLine x3,y3,x4,y4
	
	Flip
	Cls
Wend



Floyd(Posted 2011) [#9]
Everybody is making too much of this, at least the perpendicular vector part.

We know (dx,dy) = (x2-x1,y2-y1) is a vector in the direction from point1 to point2.

How do we get a vector perpendicular to this? Just use (-dy,dx) or (dy,-dx). Either of these is perpendicular to (dx,dy) because the dot product is 0.

Then proceed as before. Divide by Sqr( dx*dx + dy*dy ) to get a vector of length one. Multiply by the desired distance from the original line and add this to the midpoint.

Last edited 2011


Warpy(Posted 2011) [#10]
*ahem*


Floyd(Posted 2011) [#11]
I was referring only to the calculation of a normal vector (nx,ny).

Here's Warpy's example with a simplified normal calculation, and drawing both perpendicular lines.

Graphics 800,600,0

dist#=50

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())

	'use scroll wheel or arrow keys to change length of secondary line
	dist:+(MouseZ()-omz+KeyDown(KEY_UP)-KeyDown(KEY_DOWN))*2
	omz=MouseZ()

	'get mouse co-ordinates
	mx = MouseX()
	my = MouseY()

	'click to change position of start of line	
	If MouseHit(1)
		x1#=mx
		y1#=my
	EndIf
	
	'end of line is at mouse cursor
	x2#=mx
	y2#=my
	
	'get the difference between the start and end points
	dx# = x2-x1
	dy# = y2-y1

	'(x3,y3) is at the midpoint of the line
	x3#=x1+dx/2
	y3#=y1+dy/2
	
	'we want to find a vector of length dist which is at right angles to the first line	

	d# = dist / Sqr( dx*dx + dy*dy )
	nx# = -dy * d
	ny# =  dx * d	

	SetColor 255,255,255 ; DrawLine x1,y1,x2,y2     ' original line drawn in White
	
 	' now add or subtract this from the midpoint
	
	SetColor 255,0,0 ; DrawLine x3,y3,x3+nx,y3+ny   ' use (-dy, dx) and draw in Red
	SetColor 0,0,255 ; DrawLine x3,y3,x3-nx,y3-ny   ' use ( dy,-dx) and draw in Blue
	
	Flip
	Cls
Wend


Last edited 2011


Ian Martin(Posted 2011) [#12]
Had to download the Blitzmax demo to try that real quick. I'm using Blitz3D :)

This is essentially what I am trying to do. I will then take the resulting offset line endpoint and the original line endpoints and subdivide them again. And then take those points/lines and subdivide and so forth. This will happen a bunch of times (32 or less) in each frame to achieve the desired effect.

So at this point I am wondering what method is the fastest to do these calculations. In my post above, the first trip through the loop is slower than the subsequent ones (4 milliseconds). Why is that? I have a very fast computer, which gets 1 millisecond for subsequent frames. Are slower computers going to have trouble doing this real time? The last post before this seems quite fast too. Is it faster than the Sin/Cos version?


Floyd(Posted 2011) [#13]
I would have thought the trig version would be acceptably fast.

But the point is that we don't need it in this case. We are rotating by +90 or -90 degrees. So the only possible values for Cos and Sin are 0, +1 and -1.

For example, the expression dx*Cos(90) - dy*Sin(90) simplifies to dx*0 - dy*1, which is just -dy.

Last edited 2011


Andy_A(Posted 2011) [#14]
Here's the fastest solution that I can come up with. Notice that the square root has been eliminated.

The funny thing is that on Win7 64 bit Home Premium with 2.9 GHz AMD dual processors it runs about 70% of what it runs on an old Dell laptop with a 2.6GHz single core.




Gabriel(Posted 2011) [#15]
The funny thing is that on Win7 64 bit Home Premium with 2.9 GHz AMD dual processors it runs about 70% of what it runs on an old Dell laptop with a 2.6GHz single core.

Is it funny? I'd have expected 64 bit registers to be slower when doing 32 bit calculations.


Andy_A(Posted 2011) [#16]
It gets even funnier. I did those timings in debug mode :\

They're now about twice as fast as the laptop.

Last edited 2011


Floyd(Posted 2011) [#17]
...Notice that the square root has been eliminated.

Notice also that it doesn't work. Try an easily checked example, a horizontal line.
The two new points should then be (midX, +distance) and (midX, -distance).

I was still curious about the speed of the test. It ran in 90 ms on my Window7 Intel i7 PC. Eliminating all calculations except the calls to Rand() left this:
	;Time only the math used to determine mid-point and new point
	For i = 1 To iterations
		x1 = Rand (0,799)
		y1 = Rand (0,599)

		x2 = Rand (0, 799)	
		y2 = Rand (0, 599)

		distance% = Rand(10,250)
		side% = Rand(0,1)
	Next
That still gave a time of 66 ms, which means nearly three fourths of all the time used originally was simply generating random numbers. That's a common failing with speed tests posted on these forums.

Last edited 2011


Andy_A(Posted 2011) [#18]
What I'm trying to figure out is how to find a point a certain number of pixels away from the center at a right angle. For instance, the point at X4,Y4?


It works for what was described.


Floyd(Posted 2011) [#19]
It gets the distance wrong because length should be Sqr(length).


Yasha(Posted 2011) [#20]
That's a common failing with speed tests posted on these forums.


You want a bit of silliness that will really blow your minds?

This:
distSqrd% = distance*distance

...will unpredictably either slow down or speed up the following code, depending on the complexity of the function and several specifics of the user's hardware (i.e. redoing the multiplication will on most machines be faster than accessing the local variable from RAM).

The first rule of optimisation has always been: Don't.

The second rule of optimisation is definitely: Don't attempt to do it in Blitz3D, because you'll probably make things worse (and if you're even trying, you probably have no idea what you're doing). Write time-critical code in C, after profiling to find out where the problems are.

[/wild tangent]

Last edited 2011


Ian Martin(Posted 2011) [#21]
Andy's code works fine for what I am trying to do. It also seems to be more than fast enough. Here's doing 1000 iterations:

Graphics 800,600,32,2
SetBuffer BackBuffer()

SeedRnd MilliSecs()
iterations% = 1000 ;iterations
;iterations% = 1 ;check to make sure it's doing what's intended

Global floaty1#
Global floaty2#
Global floaty3#

Repeat 
	;time the whole process
	Cls
	ProfTimeA=MilliSecs()
	
	For i = 1 To iterations
		
		;arbitrarily chosen points
		x1 = Rand (0,799)
		y1 = Rand (0,599)
		x2 = Rand (0, 799)	
		y2 = Rand (0, 599)
		;points will be known before routine so we can discount the random generating time
		
		Color 255,0,0
		Line x1,y1, x2,y2
		
		distance% = Rand(10,250)
		distSqrd% = distance*distance
		side% = Rand(0,1)
		dx# = (x2-x1)
		dy# = (y2-y1)
		length# = 1.0/((dx*dx+dy*dy)/distSqrd)
		;left side = 0 --- right side <> 0
		If side = 0 Then dy = -dy Else dx = -dx
		;find the mid-point of x1,y1 - x2,y2
		midX = (x1+x2) Shr 1
		midY = (y1+y2) Shr 1
		;find the perpendicular and make it 'distance' pixels from mid-point
		newX = dy * length + midX
		newY = dx * length + midY
		
		Color 255,255,0
		Line midX, midY, newX, newY
	Next

	ProfTimeB=MilliSecs()-ProfTimeA
	Color 255,255,255
	Text 20,20, "Time to execute: " + ProfTimeB
	floaty2#=ProfTimeB
	floaty3#=iterations
	floaty1#=floaty2#/floaty3#
	Text 20,40,"Time per iteration= "+(floaty1#)

	Flip
	WaitMouse()

Until KeyHit(1)

End


This includes using random numbers which I won't need as the numbers are known ahead of time (ship location, target location). It's also doing all the drawing and still very fast.

Thanks for helping everybody! I come from the Atari 8bit days and sometimes I think I need to get things more optimized than they need to be :)


Andy_A(Posted 2011) [#22]
As Floyd pointed out, the distance from the midpoint is not the correct value. And as Yasha noted, I got a little carried away in trying to optimize the code.

This is slightly slower, but gets the distance right.



Ian Martin(Posted 2011) [#23]
Ah, I see now! That works more than fast enough for what I need it for :)