Biarc Rendering - vb.net

I am creating a DXF parser for use by an industrial robot. The robot can only move in lines and arcs, so I've had to decompose ellipse entities into a series of biarcs (thanks to Keeper for the arc function). The conversion works perfectly, but when I try to draw the arcs as a path on the canvas, some are misaligned.
I know that these arcs are really cotangent as otherwise my contour grouping algorithm would have made them separate paths, so it's definitely a display issue (I've also checked the Startpoints and Endpoints manually and have confirmed this). I've also exported a series of biarcs using the same method but by hand out of SolidWorks and have encountered the same problem.
Here is an image of the output on the form, I've circled where the biarcs are misaligned:
Here is the ellipse conversion code for reference, it simply samples the ellipse segment 2n times, where n is the number of arcs wanted. Then using the 3 point arc function by Keeper it draws arcs for every set of 3 points.
Public Function Ellipse2DToArcs(ByVal CenterPoint As XYZPoint, ByVal MajorRadius As Double, ByVal MinorRadius As Double, ByVal StartAngle As Double, ByVal EndAngle As Double, ByVal OffsetAngle As Double)
Dim PointList As New List(Of XYZPoint)
Dim ArcList As New List(Of ShapeClasses.ArcClass)
For i = StartAngle To EndAngle Step (EndAngle - StartAngle) / (My.Settings.EllipseApprox * 2)
PointList.Add(New XYZPoint With {.X = CenterPoint.X + MajorRadius * Math.Cos(i) * Math.Cos(OffsetAngle) - MinorRadius * Math.Sin(i) * Math.Sin(OffsetAngle), .Y = CenterPoint.Y + MajorRadius * Math.Cos(i) * Math.Sin(OffsetAngle) + MinorRadius * Math.Sin(i) * Math.Cos(OffsetAngle)})
Next
For i As UInteger = 1 To PointList.Count - 2 Step 2
Dim D As Double = 2 * (PointList(i - 1).X - PointList(i + 1).X) * (PointList(i + 1).Y - PointList(i).Y) + 2 * (PointList(i).X - PointList(i + 1).X) * (PointList(i - 1).Y - PointList(i + 1).Y)
Dim M1 As Double = ((PointList(i - 1).X ^ 2) - (PointList(i + 1).X ^ 2) + (PointList(i - 1).Y ^ 2) - (PointList(i + 1).Y ^ 2))
Dim M2 As Double = ((PointList(i + 1).X ^ 2) - (PointList(i).X ^ 2) + (PointList(i + 1).Y ^ 2) - (PointList(i).Y ^ 2))
Dim NX As Double = M1 * (PointList(i + 1).Y - PointList(i).Y) + M2 * (PointList(i + 1).Y - PointList(i - 1).Y)
Dim NY As Double = M1 * (PointList(i).X - PointList(i + 1).X) + M2 * (PointList(i - 1).X - PointList(i + 1).X)
Dim CX As Double = NX / D
Dim CY As Double = NY / D
ArcList.Add(New ShapeClasses.ArcClass With {.Radius = Math.Sqrt((CX - PointList(i + 1).X) ^ 2 + (CY - PointList(i + 1).Y) ^ 2), .CenterPoint = New XYZPoint With {.X = CX, .Y = CY}, .StartPoint = PointList(i - 1), .EndPoint = PointList(i + 1)})
Next
Return ArcList.ToArray
End Function
This is the code that converts the Arc Object to a graphic (it's stored as a startpoint, endpoint, centerpoint and radius):
Public Function Arc2DToDraw(ByVal Arc As ShapeClasses.ArcClass)
'calculate start angle
Dim StartAngle As Single = Math.Atan2(Arc.StartPoint.Y - Arc.CenterPoint.Y, Arc.StartPoint.X - Arc.CenterPoint.X) * (180 / Math.PI)
'calculate end angle
Dim EndAngle As Single = Math.Atan2(Arc.EndPoint.Y - Arc.CenterPoint.Y, Arc.EndPoint.X - Arc.CenterPoint.X) * (180 / Math.PI)
If StartAngle = EndAngle Then 'is a circle
'359.99 is a kludge to prevent a chord forming between 0 and 270 (why I have no idea)
Return {New System.Drawing.Rectangle(Arc.CenterPoint.X - Arc.Radius, Arc.CenterPoint.Y - Arc.Radius, Arc.Radius * 2, Arc.Radius * 2), StartAngle, CSng(359.99)}
Else
Return {New System.Drawing.Rectangle(Arc.CenterPoint.X - Arc.Radius, Arc.CenterPoint.Y - Arc.Radius, Arc.Radius * 2, Arc.Radius * 2), StartAngle, -(StartAngle - EndAngle)}
End If
End Function
Is there a solution to this problem, or is it an inherent display issue?

SOLVED:
The issue was that I was using the integer based Rectangle rather than float based RectangleF which was invoking a narrowing conversion when drawing the arc.
New System.Drawing.Rectangle(Arc.CenterPoint.X - Arc.Radius, Arc.CenterPoint.Y - Arc.Radius, Arc.Radius * 2, Arc.Radius * 2)
Should be changed to:
New System.Drawing.RectangleF(Arc.CenterPoint.X - Arc.Radius, Arc.CenterPoint.Y - Arc.Radius, Arc.Radius * 2, Arc.Radius * 2)

Related

Using VBA to open Chrome and fill out a form

I constantly use the website below to track air miles round trip. Recently, the website stopped working in IE, so my code did as well. Since I use this on a work computer, I cannot download many of the other solutions that I have found in my searches and I cannot use another website without going through a lengthy process to get the site approved. Is there a way to perform the same task here in Chrome without any other downloads?
Dim ele As Object
Dim IE As New InternetExplorer
IE.Visible = True
IE.navigate "http://www.distancefromto.net"
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
'step 1
With IE
.document.getElementsByName("distance")(0).Value = Range("B2").Value
.document.getElementsByName("distance")(1).Value = Range("B3").Value & Range("E3").Value
.document.getElementById("hae").Click
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Application.Wait (Now + TimeValue("0:00:02"))
Dim a As String
a = Trim(.document.getElementById("totaldistancemiles").Value)
Dim aa As Variant
aa = Split(a, " ")
Range("C2").Value = aa(0)
'step 2
.document.getElementsByName("distance")(0).Value = Range("B4").Value & Range("E4").Value
.document.getElementById("hae").Click
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Application.Wait (Now + TimeValue("0:00:02"))
Dim b As String
b = Trim(.document.getElementById("totaldistancemiles").Value)
Dim bb As Variant
bb = Split(b, " ")
Range("C3").Value = bb(0)
'step 3
.document.getElementsByName("distance")(1).Value = Range("B2").Value
.document.getElementById("hae").Click
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Application.Wait (Now + TimeValue("0:00:02"))
Dim c As String
c = Trim(.document.getElementById("totaldistancemiles").Value)
Dim cc As Variant
cc = Split(c, " ")
Range("C4").Value = cc(0)
End With
IE.Quit
Any help, even a definitive "no, it's not possible" would be greatly appriciated
Thanks
Chrome:
To use Chrome - no. You would need to download selenium basic or use a different programming language e.g python.
Different site:
You could switch to using a different site (appreciate there may some minor differences on your prior figures due to the website though technically the distances shouldn’t have changed that much!). I note you say that this would be problematic. At the risk of sounding stalkerish, you have used freemaptools before so that might be an acceptable choice?
API:
If you find a site offering an API service you might be able to ditch all the above and issue an XMLHTTP request. I couldn't see your site offering an API service otherwise that would have been the obvious next choice.
#RahulChalwa mentions "[the site OP is using is itself using a wrapper around google maps API: https://maps.googleapis.com/maps/api/js/GeocodeService.Search. User can register for API and do a POST request]"; so that might be the way forward. Main documentation here.
E.g. API site: Personal and small scale use API - wheretocredit.com
Current set-up debug:
Ascertain the reason for IE no longer working in your current set-up would also be advisable, perhaps by contacting the site developers and raising your issue.
Perform the calculation (as the site does) using the Vincenty's formula or, as other sites do, using Haversine formula:
Haversine:
VBA haversine formula
Vicenty's (including sample code):
How to Calculate Distance in Excel
Vicenty's code from Contextures. I have attributed but if this should not be included here I will remove.
'*************************************************************
Private Const PI = 3.14159265358979
Private Const EPSILON As Double = 0.000000000001
Public Function distVincenty(ByVal lat1 As Double, ByVal lon1 As Double, _
ByVal lat2 As Double, ByVal lon2 As Double) As Double
'INPUTS: Latitude and Longitude of initial and
' destination points in decimal format.
'OUTPUT: Distance between the two points in Meters.
'
'======================================
' Calculate geodesic distance (in m) between two points specified by
' latitude/longitude (in numeric [decimal] degrees)
' using Vincenty inverse formula for ellipsoids
'======================================
' Code has been ported by lost_species from www.aliencoffee.co.uk to VBA
' from javascript published at:
' https://www.movable-type.co.uk/scripts/latlong-vincenty.html
' * from: Vincenty inverse formula - T Vincenty, "Direct and Inverse Solutions
' * of Geodesics on the Ellipsoid with application
' * of nested equations", Survey Review, vol XXII no 176, 1975
' * https://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
'Additional Reference: https://en.wikipedia.org/wiki/Vincenty%27s_formulae
'======================================
' Copyright lost_species 2008 LGPL
' https://www.fsf.org/licensing/licenses/lgpl.html
'======================================
' Code modifications to prevent "Formula Too Complex" errors
' in Excel (2010) VBA implementation
' provided by Jerry Latham, Microsoft MVP Excel Group, 2005-2011
' July 23 2011
'======================================
Dim low_a As Double
Dim low_b As Double
Dim f As Double
Dim L As Double
Dim U1 As Double
Dim U2 As Double
Dim sinU1 As Double
Dim sinU2 As Double
Dim cosU1 As Double
Dim cosU2 As Double
Dim lambda As Double
Dim lambdaP As Double
Dim iterLimit As Integer
Dim sinLambda As Double
Dim cosLambda As Double
Dim sinSigma As Double
Dim cosSigma As Double
Dim sigma As Double
Dim sinAlpha As Double
Dim cosSqAlpha As Double
Dim cos2SigmaM As Double
Dim C As Double
Dim uSq As Double
Dim upper_A As Double
Dim upper_B As Double
Dim deltaSigma As Double
Dim s As Double ' final result, will be returned rounded to 3 decimals (mm).
'added by JLatham to break up "Too Complex" formulas
'into pieces to properly calculate those formulas as noted below
'and to prevent overflow errors when using
'Excel 2010 x64 on Windows 7 x64 systems
Dim P1 As Double ' used to calculate a portion of a complex formula
Dim P2 As Double ' used to calculate a portion of a complex formula
Dim P3 As Double ' used to calculate a portion of a complex formula
'See https://en.wikipedia.org/wiki/World_Geodetic_System
'for information on various Ellipsoid parameters for other standards.
'low_a and low_b in meters
' === GRS-80 ===
' low_a = 6378137
' low_b = 6356752.314245
' f = 1 / 298.257223563
'
' === Airy 1830 === Reported best accuracy for England and Northern Europe.
' low_a = 6377563.396
' low_b = 6356256.910
' f = 1 / 299.3249646
'
' === International 1924 ===
' low_a = 6378388
' low_b = 6356911.946
' f = 1 / 297
'
' === Clarke Model 1880 ===
' low_a = 6378249.145
' low_b = 6356514.86955
' f = 1 / 293.465
'
' === GRS-67 ===
' low_a = 6378160
' low_b = 6356774.719
' f = 1 / 298.247167
'=== WGS-84 Ellipsoid Parameters ===
low_a = 6378137 ' +/- 2m
low_b = 6356752.3142
f = 1 / 298.257223563
'====================================
L = toRad(lon2 - lon1)
U1 = Atn((1 - f) * Tan(toRad(lat1)))
U2 = Atn((1 - f) * Tan(toRad(lat2)))
sinU1 = Sin(U1)
cosU1 = Cos(U1)
sinU2 = Sin(U2)
cosU2 = Cos(U2)
lambda = L
lambdaP = 2 * PI
iterLimit = 100 ' can be set as low as 20 if desired.
While (Abs(lambda - lambdaP) > EPSILON) And (iterLimit > 0)
iterLimit = iterLimit - 1
sinLambda = Sin(lambda)
cosLambda = Cos(lambda)
sinSigma = Sqr(((cosU2 * sinLambda) ^ 2) + _
((cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ^ 2))
If sinSigma = 0 Then
distVincenty = 0 'co-incident points
Exit Function
End If
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda
sigma = Atan2(cosSigma, sinSigma)
sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma
cosSqAlpha = 1 - sinAlpha * sinAlpha
If cosSqAlpha = 0 Then 'check for a divide by zero
cos2SigmaM = 0 '2 points on the equator
Else
cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha
End If
C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha))
lambdaP = lambda
'the original calculation is "Too Complex" for Excel VBA to deal with
'so it is broken into segments to calculate without that issue
'the original implementation to calculate lambda
'lambda = L + (1 - C) * f * sinAlpha * _
(sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * _
(-1 + 2 * (cos2SigmaM ^ 2))))
'calculate portions
P1 = -1 + 2 * (cos2SigmaM ^ 2)
P2 = (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * P1))
'complete the calculation
lambda = L + (1 - C) * f * sinAlpha * P2
Wend
If iterLimit < 1 Then
MsgBox "iteration limit has been reached, something didn't work."
Exit Function
End If
uSq = cosSqAlpha * (low_a ^ 2 - low_b ^ 2) / (low_b ^ 2)
'the original calculation is "Too Complex" for Excel VBA to deal with
'so it is broken into segments to calculate without that issue
'the original implementation to calculate upper_A
'upper_A = 1 + uSq / 16384 * (4096 + uSq * _
(-768 + uSq * (320 - 175 * uSq)))
'calculate one piece of the equation
P1 = (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)))
'complete the calculation
upper_A = 1 + uSq / 16384 * P1
'oddly enough, upper_B calculates without any issues - JLatham
upper_B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)))
'the original calculation is "Too Complex" for Excel VBA to deal with
'so it is broken into segments to calculate without that issue
'the original implementation to calculate deltaSigma
'deltaSigma = upper_B * sinSigma * (cos2SigmaM + upper_B / 4 * _
(cosSigma * (-1 + 2 * cos2SigmaM ^ 2) _
- upper_B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ^ 2) * _
(-3 + 4 * cos2SigmaM ^ 2)))
'calculate pieces of the deltaSigma formula
'broken into 3 pieces to prevent overflow error that may occur in
'Excel 2010 64-bit version.
P1 = (-3 + 4 * sinSigma ^ 2) * (-3 + 4 * cos2SigmaM ^ 2)
P2 = upper_B * sinSigma
P3 = (cos2SigmaM + upper_B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ^ 2) _
- upper_B / 6 * cos2SigmaM * P1))
'complete the deltaSigma calculation
deltaSigma = P2 * P3
'calculate the distance
s = low_b * upper_A * (sigma - deltaSigma)
'round distance to millimeters
distVincenty = Round(s, 3)
End Function
Function SignIt(Degree_Dec As String) As Double
'Input: a string representation of a lat or long in the
' format of 10° 27' 36" S/N or 10~ 27' 36" E/W
'OUTPUT: signed decimal value ready to convert to radians
'
Dim decimalValue As Double
Dim tempString As String
tempString = UCase(Trim(Degree_Dec))
decimalValue = Convert_Decimal(tempString)
If Right(tempString, 1) = "S" Or Right(tempString, 1) = "W" Then
decimalValue = decimalValue * -1
End If
SignIt = decimalValue
End Function
Function Convert_Degree(Decimal_Deg) As Variant
'source: https://support.microsoft.com/kb/213449
'
'converts a decimal degree representation to deg min sec
'as 10.46 returns 10° 27' 36"
'
Dim degrees As Variant
Dim minutes As Variant
Dim seconds As Variant
With Application
'Set degree to Integer of Argument Passed
degrees = Int(Decimal_Deg)
'Set minutes to 60 times the number to the right
'of the decimal for the variable Decimal_Deg
minutes = (Decimal_Deg - degrees) * 60
'Set seconds to 60 times the number to the right of the
'decimal for the variable Minute
seconds = Format(((minutes - Int(minutes)) * 60), "0")
'Returns the Result of degree conversion
'(for example, 10.46 = 10° 27' 36")
Convert_Degree = " " & degrees & "° " & Int(minutes) & "' " _
& seconds + Chr(34)
End With
End Function
Function Convert_Decimal(Degree_Deg As String) As Double
'source: https://support.microsoft.com/kb/213449
' Declare the variables to be double precision floating-point.
' Converts text angular entry to decimal equivalent, as:
' 10° 27' 36" returns 10.46
' alternative to ° is permitted: Use ~ instead, as:
' 10~ 27' 36" also returns 10.46
Dim degrees As Double
Dim minutes As Double
Dim seconds As Double
'
'modification by JLatham
'allow the user to use the ~ symbol instead of ° to denote degrees
'since ~ is available from the keyboard and ° has to be entered
'through [Alt] [0] [1] [7] [6] on the number pad.
Degree_Deg = Replace(Degree_Deg, "~", "°")
' Set degree to value before "°" of Argument Passed.
degrees = Val(Left(Degree_Deg, InStr(1, Degree_Deg, "°") - 1))
' Set minutes to the value between the "°" and the "'"
' of the text string for the variable Degree_Deg divided by
' 60. The Val function converts the text string to a number.
minutes = Val(Mid(Degree_Deg, InStr(1, Degree_Deg, "°") + 2, _
InStr(1, Degree_Deg, "'") - InStr(1, Degree_Deg, "°") - 2)) / 60
' Set seconds to the number to the right of "'" that is
' converted to a value and then divided by 3600.
seconds = Val(Mid(Degree_Deg, InStr(1, Degree_Deg, "'") + _
2, Len(Degree_Deg) - InStr(1, Degree_Deg, "'") - 2)) / 3600
Convert_Decimal = degrees + minutes + seconds
End Function
Private Function toRad(ByVal degrees As Double) As Double
toRad = degrees * (PI / 180)
End Function
Private Function Atan2(ByVal X As Double, ByVal Y As Double) As Double
' code nicked from:
' https://en.wikibooks.org/wiki/Programming:Visual_Basic_Classic
' /Simple_Arithmetic#Trigonometrical_Functions
' If you re-use this watch out: the x and y have been reversed from typical use.
If Y > 0 Then
If X >= Y Then
Atan2 = Atn(Y / X)
ElseIf X <= -Y Then
Atan2 = Atn(Y / X) + PI
Else
Atan2 = PI / 2 - Atn(X / Y)
End If
Else
If X >= -Y Then
Atan2 = Atn(Y / X)
ElseIf X <= Y Then
Atan2 = Atn(Y / X) - PI
Else
Atan2 = -Atn(X / Y) - PI / 2
End If
End If
End Function
'======================================

OpenGL - Rotating camera around Lookat point WITHOUT using GLU functions

Background
I think I have the basics down for creating my OpenGL environment. I can render simple objects, can handle lighting, etc. I am trying to create a test environment that I can move around in similar to the FPS game style. I want to be able to "walk" forward and backwards in the direction I am looking at.
How my other methods are done
I have developed a walk forward and backwards function but am having a bit of difficulty with rotating my camera. I have a few variables that seem to work. I have cam.lookat which is the point to which my camera looks at, cam.position which is the point my camera is at, and distance which is a calculation between the two (which in my case I keep constant).
So I may be going at this completely wrong but here is what I have done so far:
My setupviewport sub that is called on initial load:
Private Sub SetupViewport()
Dim w As Integer = GLcontrol1.Width
Dim h As Integer = GLcontrol1.Height
Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, GLcontrol1.Width / CSng(GLcontrol1.Height), 0.1F, 2000.0F)
GL.MatrixMode(MatrixMode.Projection)
GL.LoadIdentity()
GL.Ortho(0, w, h, 0, -1, 1)
GL.LoadMatrix(perspective1)
GL.MatrixMode(MatrixMode.Modelview)
GL.LoadIdentity()
GL.Viewport(0, 0, w, h)
GL.Enable(EnableCap.DepthTest)
GL.DepthFunc(DepthFunction.Less)
End Sub
Here is my Paint event that is called per frame change:
Private Sub GlControl1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs)
GL.Clear(ClearBufferMask.ColorBufferBit)
GL.Clear(ClearBufferMask.DepthBufferBit)
GL.DepthMask(True)
GL.Enable(EnableCap.DepthTest)
GL.ClearDepth(1.0F)
GL.MatrixMode(MatrixMode.Modelview)
GL.LoadIdentity()
Dim lightColor0 As Single() = {light_intensity, light_intensity, light_intensity, 1.0F}
Dim lightPos0 As Single() = {cam.Position.X, cam.Position.Y, cam.Position.Z, 1.0F}
GL.Light(LightName.Light0, LightParameter.Diffuse, lightColor0)
GL.Light(LightName.Light0, LightParameter.Position, lightPos0)
GL.Enable(EnableCap.Light0)
Dim mat_specular As Single() = {1.0F, 1.0F, 1.0F, 1.0F}
Dim mat_shininess As Single() = {50.0F}
GL.Material(MaterialFace.Front, MaterialParameter.Specular, mat_specular)
GL.Material(MaterialFace.Front, MaterialParameter.Shininess, mat_shininess)
draw_extras()
GL.Flush()
GLcontrol1.SwapBuffers()
End Sub
The way I have constructed my "move forward and backwards" sub is as follows:
Private Sub move_forward_and_back(ByVal forward As Boolean, ByVal delta As Single)
'change the distance between camera and lookat
'distance = old distance
Dim curdistance As Single = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
'curdistance = distance
Dim deltadistance As Single = 0.1
Dim newdistance As Single = deltadistance
'The formula to use for the new point is Xnew = +/-((X2-X1)/Dold)*Dnew+X2
'This formula results with two possible points because of the +/-
'Both points are calculated and then evaluated
Dim newcamx As Single = 0
Dim newcamy As Single = 0
Dim newcamz As Single = 0
Dim newlookatx As Single = 0
Dim newlookaty As Single = 0
Dim newlookatz As Single = 0
Dim newx1 As Single = (((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
Dim newy1 As Single = (((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
Dim newz1 As Single = (((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
Dim newdistance1 As Single = Math.Sqrt((newx1 - cam.lookat.X) ^ 2 + (newy1 - cam.lookat.Y) ^ 2 + (newz1 - cam.lookat.Z) ^ 2)
Dim newx2 As Single = (-((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
Dim newy2 As Single = (-((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
Dim newz2 As Single = (-((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
Dim newdistance2 As Single = Math.Sqrt((newx2 - cam.lookat.X) ^ 2 + (newy2 - cam.lookat.Y) ^ 2 + (newz2 - cam.lookat.Z) ^ 2)
'The one with the greater distance is considered the one to use for "moving away"
'The one with the less distance is considered the one to use for "moving towards"
If forward = True Then 'use one with less distance
If newdistance1 > newdistance2 Then
newcamx = newx2
newcamy = newy2
newcamz = newz2
Else
newcamx = newx1
newcamy = newy1
newcamz = newz1
End If
Else 'use one with greater distance
If newdistance1 < newdistance2 Then
newcamx = newx2
newcamy = newy2
newcamz = newz2
Else
newcamx = newx1
newcamy = newy1
newcamz = newz1
End If
End If
'newcamx, newcamy, and newcamz are calculated for where the camera needs to move to
'need to move lookat the same distance in the same direction
If forward = True Then
newdistance = curdistance + delta
Else
newdistance = curdistance - delta
End If
newx1 = (((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
newy1 = (((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
newz1 = (((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
newdistance1 = Math.Sqrt((newx1 - cam.lookat.X) ^ 2 + (newy1 - cam.lookat.Y) ^ 2 + (newz1 - cam.lookat.Z) ^ 2)
newx2 = (-((cam.Position.X - cam.lookat.X) / curdistance) * newdistance) + cam.Position.X
newy2 = (-((cam.Position.Y - cam.lookat.Y) / curdistance) * newdistance) + cam.Position.Y
newz2 = (-((cam.Position.Z - cam.lookat.Z) / curdistance) * newdistance) + cam.Position.Z
newdistance2 = Math.Sqrt((newx2 - cam.lookat.X) ^ 2 + (newy2 - cam.lookat.Y) ^ 2 + (newz2 - cam.lookat.Z) ^ 2)
If forward = True Then 'we want the one that is smaller
If newdistance1 < newdistance2 Then
newlookatx = newx1
newlookaty = newy1
newlookatz = newz1
Else
newlookatx = newx2
newlookaty = newy2
newlookatz = newz2
End If
Else
If newdistance1 < newdistance2 Then
newlookatx = newx1
newlookaty = newy1
newlookatz = newz1
Else
newlookatx = newx2
newlookaty = newy2
newlookatz = newz2
End If
End If
'now simply assign values
cam.Position.X = newcamx
cam.Position.Y = newcamy
cam.Position.Z = newcamz
cam.lookat.X = newlookatx
cam.lookat.Y = newlookaty
cam.lookat.Z = newlookatz
newdistance = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
End Sub
I am just calculating the camera position points based on a constant distance and knowing the camera lookat. This is done as a calculation instead of matrices which hopefully is good practice.
Now all of this works fine so far but am providing it as background for how I am going about my environment
Problem
I am trying to create a rotate camera function based on two angles. It should keep the distance and cam.position constant while only changing the cam.lookat point. What I have so far:
Private Sub rotate_camera(ByVal deltaangle1 As Single, ByVal deltaangle2 As Single)
Dim curdistance As Single = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
angle1 += deltaangle1
angle2 += deltaangle2
If angle1 >= 360 Then
angle1 = angle1 - 360
End If
If angle2 >= 360 Then
angle2 = angle2 - 360
End If
If angle1 < 0 Then
angle1 = angle1 + 360
End If
If angle2 < 0 Then
angle2 = angle2 + 360
End If
deltax = distance * Sin(deltaangle1 * (PI / 180)) * Cos(deltaangle2 * (PI / 180))
deltay = distance * Sin(deltaangle1 * (PI / 180)) * Sin(deltaangle2 * (PI / 180))
deltaz = distance * Cos(deltaangle1 * (PI / 180))
deltaz = Sqrt((distance ^ 2) - (deltax ^ 2) - (deltay) ^ 2) * zsign
'now simply assign values
cam.lookat.X = cam.lookat.X + deltax
cam.lookat.Y = cam.lookat.Y + deltay
cam.lookat.Z = cam.lookat.Z + deltaz
'distance = Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)
End Sub
So I have three problems:
My deltay is always positive. My formulas do not have direction How do I include in my formulas whether or not the delta should be positive or negative?
On similar lines, there appears to be a reflection point at 180 degrees where the direction of rotation reverses. How should I take into account this reflection?
On a slightly different note, my camera appears to have its positive X axis in line with the negative world X axis. How do I fix this? How can I have maintain my cam.position and cam.lookat, while rotating the camera?
I know there is a lot to read here but I do hope the questions are basic. I provided enough detail as I am open to adjusting other methods if I am totally off in left field.
Update per questions and answer below
So thank you for all of the help so far! I am mostly there I think. I still have this line which uses the final matrix:
Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, GLcontrol1.Width / CSng(GLcontrol1.Height), 0.1F, 2000.0F)
but I have changed the cam.getviewmatrix function using advice to this:
Public Function GetViewMatrix() As Matrix4
Dim myforwardvector As Vector3
Dim rotational_matrix_y As Matrix3
Dim rotational_matrix_x As Matrix3
Dim rotational_matrix_z As Matrix3
Dim upvector As Vector3
If invert_z = False Then
upvector = Vector3.UnitZ
Else
upvector = -Vector3.UnitZ
End If
rotational_matrix_x.M11 = 1
rotational_matrix_x.M12 = 0
rotational_matrix_x.M13 = 0
rotational_matrix_x.M21 = 0
rotational_matrix_x.M22 = Cos(theida * (PI / 180))
rotational_matrix_x.M23 = -Sin(theida * (PI / 180))
rotational_matrix_x.M31 = 0
rotational_matrix_x.M32 = Sin(theida * (PI / 180))
rotational_matrix_x.M33 = Cos(theida * (PI / 180))
rotational_matrix_y.M11 = Cos(theida * (PI / 180))
rotational_matrix_y.M12 = 0
rotational_matrix_y.M13 = Sin(theida * (PI / 180))
rotational_matrix_y.M21 = 0
rotational_matrix_y.M22 = 1
rotational_matrix_y.M23 = 0
rotational_matrix_y.M31 = -Sin(theida * (PI / 180))
rotational_matrix_y.M32 = 0
rotational_matrix_y.M33 = Cos(theida * (PI / 180))
rotational_matrix_z.M11 = Cos(theida * (PI / 180))
rotational_matrix_z.M12 = -Sin(theida * (PI / 180))
rotational_matrix_z.M13 = 0
rotational_matrix_z.M21 = Sin(theida * (PI / 180))
rotational_matrix_z.M22 = Cos(theida * (PI / 180))
rotational_matrix_z.M23 = 0
rotational_matrix_z.M31 = 0
rotational_matrix_z.M32 = 0
rotational_matrix_z.M33 = 1
Dim rotational_matrix As Matrix3
rotational_matrix = Matrix3.Mult(Matrix3.Mult(rotational_matrix_x, rotational_matrix_y), rotational_matrix_z)
myforwardvector = multiply_matrix3_by_vector3(rotational_matrix, myforwardvector)
lookat = multiply_vector3_by_scalar(myforwardvector, distance)
Return Matrix4.LookAt(Position, lookat, upvector)
End Function
The loading of the environment works but nothing changes in my environment when I change theida. I do call my SetupViewPort to refresh the matrix and repaint it like normal. Am I missing something in my matrix creation?
This is what happens currently when holding down the button that increases my yaw only (just changing the X matrix). Keep in mind that one sphere is located at (0,0,0):
Improvement based on second answer
My new ViewMatrix function is as follows:
Dim view_matrix As Matrix4 = Matrix4.LookAt(Position, lookat, up_vector)
'transform to x axis first
view_matrix = view_matrix * Get_Transform_Matrix(-Position.X, 0, 0)
'rotate around x axis
view_matrix = view_matrix * Get_Rotational_Matrix("x", yaw)
'trnsform back
view_matrix = view_matrix * Get_Transform_Matrix(Position.X, 0, 0)
'transform to y axis first
view_matrix = view_matrix * Get_Transform_Matrix(0, -Position.Y, 0)
'rotate around y axis
view_matrix = view_matrix * Get_Rotational_Matrix("y", pitch)
'trnsform back
view_matrix = view_matrix * Get_Transform_Matrix(Position.Y, 0, 0)
'transform to z axis first
view_matrix = view_matrix * Get_Transform_Matrix(0, -Position.Z, 0)
'rotate around z axis
view_matrix = view_matrix * Get_Rotational_Matrix("z", roll)
'trnsform back
view_matrix = view_matrix * Get_Transform_Matrix(Position.Z, 0, 0)
Return view_matrix
My Get_Rotational_Matrix function that retrieves the correct matrix to use for the given rotation:
Public Function Get_Rotational_Matrix(ByVal matrix_name As String, ByVal angle As Single) As OpenTK.Matrix4
'yaw = x, pitch = y, z = roll
Dim rotational_matrix_ As Matrix4
Select Case matrix_name
Case "x"
rotational_matrix_.M11 = 1
rotational_matrix_.M12 = 0
rotational_matrix_.M13 = 0
rotational_matrix_.M14 = 0
rotational_matrix_.M21 = 0
rotational_matrix_.M22 = Cos(angle * (PI / 180))
rotational_matrix_.M23 = -Sin(angle * (PI / 180))
rotational_matrix_.M24 = 0
rotational_matrix_.M31 = 0
rotational_matrix_.M32 = Sin(angle * (PI / 180))
rotational_matrix_.M33 = Cos(angle * (PI / 180))
rotational_matrix_.M34 = 0
rotational_matrix_.M41 = 0
rotational_matrix_.M42 = 0
rotational_matrix_.M43 = 0
rotational_matrix_.M44 = 1
Case "y"
rotational_matrix_.M11 = Cos(angle * (PI / 180))
rotational_matrix_.M12 = 0
rotational_matrix_.M13 = Sin(angle * (PI / 180))
rotational_matrix_.M14 = 0
rotational_matrix_.M21 = 0
rotational_matrix_.M22 = 1
rotational_matrix_.M23 = 0
rotational_matrix_.M24 = 0
rotational_matrix_.M31 = -Sin(angle * (PI / 180))
rotational_matrix_.M32 = 0
rotational_matrix_.M33 = Cos(angle * (PI / 180))
rotational_matrix_.M34 = 0
rotational_matrix_.M41 = 0
rotational_matrix_.M42 = 0
rotational_matrix_.M43 = 0
rotational_matrix_.M44 = 1
Case "z"
rotational_matrix_.M11 = Cos(angle * (PI / 180))
rotational_matrix_.M12 = -Sin(angle * (PI / 180))
rotational_matrix_.M13 = 0
rotational_matrix_.M14 = 0
rotational_matrix_.M21 = Sin(angle * (PI / 180))
rotational_matrix_.M22 = Cos(angle * (PI / 180))
rotational_matrix_.M23 = 0
rotational_matrix_.M24 = 0
rotational_matrix_.M31 = 0
rotational_matrix_.M32 = 0
rotational_matrix_.M33 = 1
rotational_matrix_.M34 = 0
rotational_matrix_.M41 = 0
rotational_matrix_.M42 = 0
rotational_matrix_.M43 = 0
rotational_matrix_.M44 = 1
End Select
'
Return rotational_matrix_
End Function
This seems to work great! Last question, how do I apply the rotations to the up_vector so I can keep track of it? Notice how my new method never changes that.
I think you should have a private member where you store your forward vector. The direction pointing in front of your camera. At start you should look in the z direction so for example it could be myForwardvector = vec3(0,0,1).
You also should store the distance to the point you are aiming.
So the lookAt position should become something like:
vec3 position = myForwardVector * radius;
Now when when you want to rotate your forward vector depending on your input, your mouse etc ...
you can use spherical coordinates
where theta is the angle when you rotate left/right (yaw) and Phi top/bottom (pitch). Roll is not used in a fps.
If you want to use matrice you can start by a litlle example and just implementing the yaw movement.
this is what thoose matrices look like:
Use the one from the middle and replace theta by your yaw angle.
Now each time you made a movement you change your forwardVector:
myForwardVector = Rotationmatrice * myForwardVector;
myLookAtMatrice = glm::lookat(myCameraPos, myFowardVector * radius, upVector)
Hope it helps,
note that you will update your upVector using the same method.
#DraykoonD answer is good. But It seems you're a bit confused with geometry. So, here are some explanations:
If your objects don't move, you just have to steps to go: Camera and Projection.
Camera
Transforming the camera means translating and/or rotating it. Both movements are usually expressed by an only matrix, usually called "lookAt" matrix.
If you work with matrices, then C= R*T is the operation (lookAt C = Rotation * Translation, in this order, not T*R)
Translation
While T is pretty simple, perhaps you want to replace matrices operation with some code on your own. Something like:
camera.x = camera.x + deltaX
camera.y = camera.x + deltaY
camera.z = camera.x + deltaZ
Note that the deltas are not the necessary the same, as the user may whish to move in 1/2/3 axis at once.
If, instead of the above, you want to move the camera by stepSizein the current direction (that it aims to) then get a unit-vector in that direction and calculate deltas and use them as before:
vx = target.x - camera.x
vy = target.y - camera.y
vz = target.x - camera.z
modulus = Sqrt(vx*vx + vy*vy + vz*vz)
' components of the unit vector
dx = vx / modulus
dy = vy / modulus
dz = vz / modulus
' stepSize is a signed value, forward or backwards
deltaX = dx * stepSize
deltaY = dy * stepSize
deltaZ = dz * stepSize
Rotation
Fist of all: Things rotate by an angle around an axis, not around a point.
Read the last sentence again. It means that you can not simply give two angles and get the rotation, you also need the order in which those two rotations occurs, because the result is not the same if you change the order.
Now read it once more. It means you need an axis for each rotation.
What confuses people is that rotation needs an origin. Think of the nail of a compass you use to draw an arc.
So: an axis, an angle, and an origin.
OK. Let's explain rotations in a FPS-game-like. We will use for the origin of the rotation the camera position. Again, don't call this "rotating around a point".
Let's say you have your axis X+ to right, Y+ deep into the screen, Z+ up. This is a right-handled system: if you calculate the cross product of two unit vectors aligned with the axis you get the third unit-vector. Cross product, as matrices multiplication, is not commutative; so pay attention to the order, or you'll get a vector in the opposite direction.
This axis system is easy to understand if you use it as the world coordinates system.
A left-handled system is the same, but with some sign changed, for example Z= Y x X, instead of Z= X x Y
If your first rotation is over Z axis then, applying the Rz matrix shown in the Wikipedia Rotation to the current X-vector and Y-vector we get the new X',Y' vectors, Z' is the same as Z. If in the first place the camera target was (0, d, 0) then post-multiply it by Rz to get the new target (tx, ty, 0)
"Pre" or "post" matrix multiplication depends on if the matrix is column or row mayor order. Typically in OpenGL and many graphics APIs column mayor order is used, which is the same as that Wikipedia link.
Now suppose the user wants a down-up rotation. The axis needed is the X' we obtained before, after Z-axis rotation. This is a "local" axis. If we apply the Rx matrix we get a local rotation. Right. But what we need is a "global" direction, so as the objects are located well.
Here's when matrix stuff shines: just apply R2= Rx*Rz instead of R2= Rx. If you apply R2 to (0, d, 0) you get the new2 target (t2x, t2y, t2z)
It's so simple as "stacking" the transformations: Cn= Cn-1 * ... *C5*C4*C3*C2*C1 where Ck is the column-mayor-order matrix for the 'k' transformation (translation or rotation or whatever). Just be aware of the order; row-mayor-order reverses it.
Once you have your first "camera matrix" the following matrices are just a matter of stacking.
Note: translations can not be represented by a 3x3 matrix. But a 4x4 does. Using 4x4 matrices allows stacking any transformation.
Now the user wants a Z-axis (right-left) rotation again. Mmmm, let me see... When we do a rotation the whole axis-system changes. That means that now, after those two previous rotations, the Z-local-axis, where the "up-camera" vector is, is not the same as the Z-global. But the user does want a Z-global rotation, not a local one (user wants X-local but Z-global)
We've seen before that each Ck matrix represents a local transformation. What we need now is the Z-global axis expressed in local system. That is: Zv= R2 * (0,0,1)
OK, but I only know of Rx,Ry,Rz matrices and now Z-global is not the same as none of my local axis. True. We need the matrix rotation around any axis. This link shows it.
All right. But... how is the "first camera matrix" built? This lookAt link shows it. The parameters needed (camera position, target and up-camera) depends on what you initially want to "see".
If you are curious about the deduction of the matrix, I tell you that a transform matrix row is the new axis vector expressed in old axis vectors. And that you can get vectors using the cross product and normalizing them.
Good. Instead of playing with stacked matrices (I know they degenerate after many transformations due to numerical issues) I want to get "camera.position", "camera.target" and "camera.up" (important: all of them in global coordinates) and use that lookAtmatrix on my own, for every camera movement. Then you must calculate those three vectors every time.
The translation is showed at the beggining of this answer. You must translate both camera.position and camera.target. camera.up is a vector, not a point; thus, translating a vector has no sense.
For rotations recall we use the position of the camera as the origin of any rotation. That means that before rotating the target transform it's global coordinates to the origin, then rotate, then undo the translation, Something like this:
target.x = target.x - camera.position.x
target.y = target.y - camera.position.y
target.z = target.z - camera.position.z
target = RotateGeneric(axis, angle, target)
target.x = target.x + camera.position.x
target.y = target.y + camera.position.y
target.z = target.z + camera.position.z
I think it's good you have a RotateGeneric function that uses the matrix in the link I wrote some lines ago.
To rotate the up-camera vector you can use the same function (without translations, it's a vector, right?)
You are doing all calculations on global system. Z-axis rotation is simple. But X-local-axis, at any moment... how do we get it? Well, there are two options:
Keep track of its current (x,y,z) direction. In other words, rotate
it also as you do for up-camera.
Use matrix magic again. Every
rotation can be undone. The matrix needed is the inverse of the
current matrix, IC= C-1. So if you have the X-local xv=(1,0,0)
and want its components in global coordinates then xg= IC * xv. Of course, you need to update and store the current C matrix.
If you also rotate around Y-axis, the process is the same as for X-axis.
Projection
Using matrix stack, or lookAt matrix at every movement, what you get is an axis-system such as X and Y axis are aligned with OpenGL device axis-system.
Now two jobs left: projecting and dealing with Z-axis orientation.
As you correctly suspect, matrix is again here.
I will not write here about orthographic nor perspective matrix representations, that's not the subject of the question.
Just point out that those matrices have a -1 in a proper site that reverses the sign of Z, so OpenGL left-handed device system is happy. Well, that mostly always used value is not strictly needed; but if +1 is set, then you should change the depth compare function to tell OpenGL to use a right-handed system instead.
Stack the projection matrix "after" the camera matrix M= P*C and that's all.

Open TK determine sphere (quadstrips) normals?

Introduction
I am somewhat new to using Open GL / Open TK. I have learned how to draw basic shapes, use matrices, lighting, shadowing, etc. I have a function that draws a sphere:
Private Sub drawSphere(r As Double, lats As Integer, longs As Integer)
Dim i As Integer, j As Integer
For i = 0 To lats
Dim lat0 As Double = PI * (-0.5 + CDbl(i - 1) / lats)
Dim z0 As Double = Sin(lat0)
Dim zr0 As Double = Cos(lat0)
Dim lat1 As Double = PI * (-0.5 + CDbl(i) / lats)
Dim z1 As Double = Sin(lat1)
Dim zr1 As Double = Cos(lat1)
GL.Begin(PrimitiveType.QuadStrip)
For j = 0 To longs
Dim lng As Double = 2 * PI * CDbl(j - 1) / longs
Dim x As Double = Cos(lng)
Dim y As Double = Sin(lng)
GL.Normal3(x * zr0 * r, y * zr0 * r, z0 * r)
GL.Vertex3(x * zr0 * r, y * zr0 * r, z0 * r)
GL.Normal3(x * zr1 * r, y * zr1 * r, z1 * r)
GL.Vertex3(x * zr1 * r, y * zr1 * r, z1 * r)
Next
GL.End()
Next
End Sub
I have other code that sets up the lights. I know the other code works because I have a separate function for drawing an STL object:
Dim texture As UInteger() = New UInteger(0) {}
Dim i As Integer = 0
If stl_table.Items.Count > 0 Then
find_center_of_part()
GL.Begin(PrimitiveType.Triangles)
GL.Color3(part_color.R, part_color.G, part_color.B)
Do Until i + 4 >= stl_table.Items.Count
GL.Normal3(Convert.ToSingle(stl_table.Items.Item(i).SubItems(0).Text), Convert.ToSingle(stl_table.Items.Item(i).SubItems(1).Text), Convert.ToSingle(stl_table.Items.Item(i).SubItems(2).Text))
GL.Vertex3(stl_table.Items.Item(i + 1).SubItems(0).Text - avgx, stl_table.Items.Item(i + 1).SubItems(1).Text - avgy, stl_table.Items.Item(i + 1).SubItems(2).Text - avgz)
GL.Vertex3(stl_table.Items.Item(i + 2).SubItems(0).Text - avgx, stl_table.Items.Item(i + 2).SubItems(1).Text - avgy, stl_table.Items.Item(i + 2).SubItems(2).Text - avgz)
GL.Vertex3(stl_table.Items.Item(i + 3).SubItems(0).Text - avgx, stl_table.Items.Item(i + 3).SubItems(1).Text - avgy, stl_table.Items.Item(i + 3).SubItems(2).Text - avgz)
i = i + 4
Loop
GL.End()
End If
This second function basically imports a CAD STL file and draws it as triangles. The normal vectors are simply an input from the CAD file (so they are already computed). This method's lighting works perfectly fine which makes me know my lighting code is correct.
Problem
The problem is that my sphere is not getting light correctly. I know through testing that this is due to my normal vectors.
With the current code, my sphere looks like this:
There is a "spot" of light which makes me think that is simply one of the quadstrips having the normal correct.
Does anybody have any suggestions on setting up the normal vectors correctly inside my function? Also before anybody suggests it, I can't use GLU or GLUT for what I am trying to accomplish, which is why I need the sphere function.

How to draw very specific annular sectors using visual basic .NET 2

This question furthers a previous question. In that question valter provided me with a very good answer that used two lines of code to perform all the math. I'm hoping he would like to try his hand at this problem also. Even though it looks similar I have not been able to get any math to work.
I need to now draw exactly the same annular sectors accept that the OuterRadius in that question becomes a Rectangle. I include this image to explain what I mean. You will notice the red line that represents the Rectangle plugged into the function. The image is actually a screen capture of what the code I tried up to now produces. Naturally most them are is wrong, but the top left annular sector for instance is correct. All the annular sectors should terminate on the rectangle edge, whilst compensating for the gap. Corner cases would probably need 3 points.
I have decided to post the code I have so far, so the puritans must please close their eyes, because it is pretty brutal:
<Extension()> Friend Sub AddAnnularSector(
ByVal aGraphicsPath As GraphicsPath,
ByVal aCenterPoint As PointR,
ByVal aInnerRadius As Double,
ByVal aOuterRectangle As RectangleF,
ByVal aStartAngle As Double,
ByVal aSweepAngle As Double,
ByVal aStartGap As Double,
ByVal aEndGap As Double)
'Declare local variables...
Dim tInnerStartOffset As Double = (Math.Asin(aStartGap / aInnerRadius) * 180.0R) / Math.PI
Dim tInnerEndOffset As Double = (Math.Asin(aEndGap / aInnerRadius) * 180.0R) / Math.PI
Dim tTestAngle1 As Double = aStartAngle + aSweepAngle - tInnerEndOffset
If tTestAngle1 > 360.0R Then tTestAngle1 -= 360.0R
If tTestAngle1 > 270.0R Then tTestAngle1 = 360.0R - tTestAngle1
If tTestAngle1 > 180.0R Then tTestAngle1 -= 180.0R
If tTestAngle1 > 90.0R Then tTestAngle1 = 180.0R - tTestAngle1
Dim tOuterEndLength As Double = (Math.Min(aOuterRectangle.Width, aOuterRectangle.Height) / 2) / Math.Sin(tTestAngle1.ToRadians)
Dim tTestAngle2 As Double = aStartAngle + tInnerStartOffset
If tTestAngle2 > 360.0R Then tTestAngle2 -= 360.0R
If tTestAngle2 > 270.0R Then tTestAngle2 = 360.0R - tTestAngle2
If tTestAngle2 > 180.0R Then tTestAngle2 -= 180.0R
If tTestAngle2 > 90.0R Then tTestAngle2 = 180.0R - tTestAngle2
Dim tOuterStartLength As Double = (Math.Min(aOuterRectangle.Width, aOuterRectangle.Height) / 2) / Math.Sin(tTestAngle2.ToRadians)
'Add the annular sector to the figure...
aGraphicsPath.StartFigure()
aGraphicsPath.AddArc(CSng(aCenterPoint.X - aInnerRadius), CSng(aCenterPoint.Y - aInnerRadius), CSng(aInnerRadius * 2.0R), CSng(aInnerRadius * 2.0R), CSng(aStartAngle + tInnerStartOffset), CSng(aSweepAngle - (tInnerStartOffset + tInnerEndOffset)))
aGraphicsPath.AddLines(New PointF() {
New PointF(CSng((aCenterPoint.X) + (Math.Cos((aStartAngle + aSweepAngle - tInnerEndOffset).ToRadians) * tOuterEndLength)), CSng((aCenterPoint.Y) + (Math.Sin((aStartAngle + aSweepAngle - tInnerEndOffset).ToRadians) * tOuterEndLength))),
New PointF(CSng((aCenterPoint.X) + (Math.Cos((aStartAngle + tInnerStartOffset).ToRadians) * tOuterStartLength)), CSng((aCenterPoint.Y) + (Math.Sin((aStartAngle + tInnerStartOffset).ToRadians) * tOuterStartLength)))
})
aGraphicsPath.CloseFigure()
Return
End Sub
You will notice how OuterRadius of the previous question changed to aOuterRectangle. Also the inner arc is drawn exactly like the previous outer arc was drawn.
EDIT 1: Just chopped the code off a bit to make it more legible.
EDIT 2: Here is an image of what is actually required unlike the above image that just shows the current result.
Light Cyan - Figure that will actually be added to the GraphicsPath.
Dark Cyan - The part of the figure that is cut away by the OuterRectangle.
Green - The OuterRectangle's dimensions including its center point.
Yellow - The InnerRadius's dimensions including its center point.
Pink - Figure added in a previous call to the function with different inputs just for reference.
EDIT 3: This image shows what I thought would be a quick mathematical solution.
Thanks
drifter
For wdthRect = 250, hgtRect = 200, innerR = 65, startA = 280.0, angle = 30.0, gap = 10.0R
Private Sub DrawAnnular2(ByVal pntC As Point, ByVal wdthRect As Integer, ByVal hgtRect As Integer, ByVal innerR As Integer, ByVal startA As Single, ByVal angle As Single, ByVal gap As Double)
Dim g As Graphics
Dim pth As New GraphicsPath
Dim pthRct As New GraphicsPath
Dim pthCrclIn As New GraphicsPath
Dim pthCrclOut As New GraphicsPath
Dim fe, theta, dbl As Double
Dim outerR, wdth As Integer
Dim rect As Rectangle
wdth = Math.Min(wdthRect, hgtRect)
outerR = CInt(Math.Sqrt(2.0R * (CDbl(wdth) / 2.0R) * (CDbl(wdth) / 2.0R))) 'πυθαγόρειο θεώρημα
rect.X = CInt(CDbl(pntC.X) - CDbl(wdth) / 2.0R)
rect.Y = CInt(CDbl(pntC.Y) - CDbl(wdth) / 2.0R)
rect.Width = wdth
rect.Height = wdth
pthCrclOut.AddEllipse(pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR)
pthCrclIn.AddEllipse(rect)
pthRct.AddRectangle(rect)
'////// The same as annular 1 //////////////////////////////////////////////////
g = Me.CreateGraphics
g.SmoothingMode = SmoothingMode.AntiAlias
gap /= 2.0R
dbl = gap / CDbl(outerR)
theta = Math.Asin(dbl) * 180.0R / Math.PI
fe = theta
pth.AddArc(pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR, startA + CSng(fe), angle - CSng(2.0R * fe)) 'Outer
dbl = gap / CDbl(innerR)
theta = Math.Asin(dbl) * 180.0R / Math.PI
fe = theta
pth.AddArc(pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR, startA + angle - CSng(fe), -(angle - CSng(2.0R * fe))) 'Inner
'////////////////////////////////////////////////////////////
Dim Reg1 As New Region(pth)
Dim Reg2 As New Region(pthRct)
Reg2.Intersect(Reg1)
g.FillRegion(Brushes.Aqua, Reg2) 'This is the actual annular 2.
g.DrawPath(Pens.Green, pthCrclIn)
g.DrawPath(Pens.Green, pthCrclOut)
g.DrawPath(Pens.Red, pthRct)
Reg1.Dispose()
Reg2.Dispose()
pthRct.Dispose()
pthCrclOut.Dispose()
pthCrclIn.Dispose()
pth.Dispose()
g.Dispose()
End Sub
This line of code:
Reg2.Intersect(Reg1)
is actually the intersect between the blue and the red
EDIT
Private Function DrawAnnular2(ByVal pntC As Point, ByVal wdthRect As Integer, ByVal hgtRect As Integer, ByVal innerR As Integer, ByVal startA As Single, ByVal angle As Single, ByVal gap As Double) As GraphicsPath
Dim g As Graphics
Dim pth As New GraphicsPath
Dim pthRct As New GraphicsPath
Dim pthFinal As New GraphicsPath
Dim fe, theta, dbl As Double
Dim outerR, wdth As Integer
Dim rect As Rectangle
Dim lst1 As New List(Of Integer)
Dim lst2 As New List(Of Integer)
Dim lst3 As New List(Of Integer)
Dim lst4 As New List(Of Integer)
Dim i As Integer
Dim lstBl(3) As Boolean
Dim position As Integer
lstBl(0) = False
lstBl(1) = False
lstBl(2) = False
lstBl(3) = False
wdth = Math.Min(wdthRect, hgtRect)
outerR = CInt(Math.Sqrt(2.0R * (CDbl(wdth) / 2.0R) * (CDbl(wdth) / 2.0R))) 'πυθαγόρειο θεώρημα
rect.X = CInt(CDbl(pntC.X) - CDbl(wdth) / 2.0R)
rect.Y = CInt(CDbl(pntC.Y) - CDbl(wdth) / 2.0R)
rect.Width = wdth
rect.Height = wdth
pthRct.AddRectangle(rect)
'////////////////////////////////////////////////////////
g = Me.CreateGraphics
g.SmoothingMode = SmoothingMode.AntiAlias
gap /= 2.0R
dbl = gap / CDbl(outerR)
theta = Math.Asin(dbl) * 180.0R / Math.PI
fe = theta
If CDbl(angle) - 2.0R * fe >= 360.0R Then
pthFinal.AddEllipse(pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR)
pthFinal.AddRectangle(rect)
g.FillPath(Brushes.Aqua, pthFinal)
g.DrawPath(Pens.Red, pthRct)
pthRct.Dispose()
pth.Dispose()
g.Dispose()
Return pthFinal
End If
pth.AddArc(pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR, startA + CSng(fe), angle - CSng(2.0R * fe)) 'Outer
dbl = gap / CDbl(innerR)
theta = Math.Asin(dbl) * 180.0R / Math.PI
fe = theta
pth.AddArc(pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR, startA + angle - CSng(fe), -(angle - CSng(2.0R * fe))) 'Inner
'////////////////////////////////////////////////////////////
For i = rect.X To rect.X + wdth
If pth.IsVisible(i, rect.Y) Then
If lst1.Count <> 0 Then
If i <> lst1(lst1.Count - 1) + 1 Then
lstBl(0) = True
position = lst1.Count
End If
End If
lst1.Add(i)
End If
Next
For i = rect.Y To rect.Y + wdth
If pth.IsVisible(rect.X + wdth, i) Then
If lst2.Count <> 0 Then
If i <> lst2(lst2.Count - 1) + 1 Then
lstBl(1) = True
position = lst2.Count
End If
End If
lst2.Add(i)
End If
Next
For i = rect.X To rect.X + wdth
If pth.IsVisible(i, rect.Y + wdth) Then
If lst3.Count <> 0 Then
If i <> lst3(lst3.Count - 1) + 1 Then
lstBl(2) = True
position = lst3.Count
End If
End If
lst3.Add(i)
End If
Next
For i = rect.Y To rect.Y + wdth
If pth.IsVisible(rect.X, i) Then
If lst4.Count <> 0 Then
If i <> lst4(lst4.Count - 1) + 1 Then
lstBl(3) = True
position = lst4.Count
End If
End If
lst4.Add(i)
End If
Next
'If lstBl(0) = True Or lstBl(1) = True Or lstBl(2) = True Or lstBl(3) = True Then
'It is a rare case that i have to work on, when angle is too large
'MsgBox(lstBl(0).ToString + " " + lstBl(1).ToString + " " + lstBl(2).ToString + " " + lstBl(3).ToString + " ")
'Application.Exit()
'End If
'TextBox1.Text = lst1.Count.ToString + " " + lst2.Count.ToString + " " + lst3.Count.ToString + " " + " " + lst4.Count.ToString
pthFinal.AddArc(pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR, startA + angle - CSng(fe), -(angle - CSng(2.0R * fe))) 'Inner
If CDbl(startA) + fe >= 225.0R And CDbl(startA) + fe <= 315.0R Then '1
If lst1.Count <> 0 Then
If lstBl(0) = True Then
pthFinal.AddLine(lst1(position), rect.Y, lst1(lst1.Count - 1), rect.Y)
Else
pthFinal.AddLine(lst1(0), rect.Y, lst1(lst1.Count - 1), rect.Y)
End If
End If
If lst2.Count <> 0 Then
pthFinal.AddLine(lst1(lst1.Count - 1), rect.Y, rect.X + wdth, lst2(lst2.Count - 1))
End If
If lst3.Count <> 0 Then
pthFinal.AddLine(rect.X + wdth, lst2(lst2.Count - 1), lst3(0), rect.Y + wdth)
End If
If lst4.Count <> 0 Then
pthFinal.AddLine(lst3(0), rect.Y + wdth, rect.X, lst4(0))
End If
If lstBl(0) = True Then
pthFinal.AddLine(rect.X, lst4(0), lst1(position - 1), rect.Y)
End If
ElseIf (CDbl(startA) + fe > 315.0R And CDbl(startA) + fe <= 360.0R) Or _
(CDbl(startA) + fe >= 0.0R And CDbl(startA) + fe <= 45.0R) Then '2
If lst2.Count <> 0 Then
If lstBl(1) = True Then
pthFinal.AddLine(rect.X + wdth, lst2(position), rect.X + wdth, lst2(lst2.Count - 1))
Else
pthFinal.AddLine(rect.X + wdth, lst2(0), rect.X + wdth, lst2(lst2.Count - 1))
End If
End If
If lst3.Count <> 0 Then
pthFinal.AddLine(rect.X + wdth, lst2(lst2.Count - 1), lst3(0), rect.Y + wdth)
End If
If lst4.Count <> 0 Then
pthFinal.AddLine(lst3(0), rect.Y + wdth, rect.X, lst4(0))
End If
If lst1.Count <> 0 Then
pthFinal.AddLine(rect.X, lst4(0), lst1(lst1.Count - 1), rect.Y)
End If
If lstBl(1) = True Then
pthFinal.AddLine(lst1(lst1.Count - 1), rect.Y, rect.X + wdth, lst2(position - 1))
End If
ElseIf CDbl(startA) + fe > 45.0R And CDbl(startA) + fe <= 135.0R Then '3
If lst3.Count <> 0 Then
If lstBl(2) = True Then
pthFinal.AddLine(lst3(position - 1), rect.Y + wdth, lst3(0), rect.Y + wdth)
Else
pthFinal.AddLine(lst3(lst3.Count - 1), rect.Y + wdth, lst3(0), rect.Y + wdth)
End If
End If
If lst4.Count <> 0 Then
pthFinal.AddLine(lst3(0), rect.Y + wdth, rect.X, lst3(0))
End If
If lst1.Count <> 0 Then
pthFinal.AddLine(rect.X, lst3(0), lst1(lst1.Count - 1), rect.Y)
End If
If lst2.Count <> 0 Then
pthFinal.AddLine(lst1(lst1.Count - 1), rect.Y, rect.X + wdth, lst2(lst2.Count - 1))
End If
If lstBl(2) = True Then
pthFinal.AddLine(rect.X + wdth, lst2(lst2.Count - 1), lst3(position), rect.Y + wdth)
End If
Else '4
If lst4.Count <> 0 Then
If lstBl(3) = True Then
pthFinal.AddLine(rect.X, lst4(position - 1), rect.X, lst4(0))
Else
pthFinal.AddLine(rect.X, lst4(lst4.Count - 1), rect.X, lst4(0))
End If
End If
If lst1.Count <> 0 Then
pthFinal.AddLine(rect.X, lst4(0), lst1(lst1.Count - 1), rect.Y)
End If
If lst2.Count <> 0 Then
pthFinal.AddLine(lst1(lst1.Count - 1), rect.Y, rect.X + wdth, lst2(lst2.Count - 1))
End If
If lst3.Count <> 0 Then
pthFinal.AddLine(rect.X + wdth, lst2(lst2.Count - 1), lst3(0), rect.Y + wdth)
End If
If lstBl(3) = True Then
pthFinal.AddLine(lst3(0), rect.Y + wdth, rect.X, lst4(position))
End If
End If
'g.FillPath(Brushes.Blue, pth)
g.FillPath(Brushes.Aqua, pthFinal)
g.DrawPath(Pens.Red, pthRct)
pthRct.Dispose()
pth.Dispose()
g.Dispose()
Return pthFinal
End Function
Your values:

How to draw very specific annular sectors using visual basic .NET

Here goes... I need to draw several annular sectors in VisualBasic.NET using a GraphicsPath.
I would for instance enter the annular sector's outer and inner diameter/radius and a starting and ending degree/radiant. The function must then return a GraphicsPath that can simply be added to another GraphicsPath as a closed shape. This seems very easy to do, but I need it to keep a 5 pixel gap between the annular sectors.
I will explain more... If for instance I enter 0 degrees as starting point and 360 degrees as ending point it must return a GraphicsPath with an annular sector that starts at X degrees (where X is 2.5 pixels offset from 0 degrees) and ends at Y degrees (where Y is -2.5 pixels offset from 360 degrees). Both the outer and inner curves of the annular sector must adhere to the above. It basically must be as if I drew two parallel lines 5 pixels apart through the circle's center at the degrees entered and used the lines as starting and ending points for the arcs. This must hold true for any Degrees entered.
This post: SVG donut slice as path element (annular sector) Almost solves the problem, but it draws all annular sectors from the center point (not unlike Pie slices) outwards causing consecutive outer rings to have bigger and bigger gaps in between if you split them with 1 Degree each time (plus it is for SVG).
I include an image explaining what I am trying to say better: https://onedrive.live.com/redir?resid=79292E5BC057FE03!112&authkey=!AMnkq0bjbsH4BwU&v=3&ithint=photo%2cjpg
This question was solved by valter. Scroll down to EDIT 2 in his answer for the code.
This image: (https://onedrive.live.com/redir?resid=79292E5BC057FE03!113&authkey=!AGxrnpf8MiiEX48&v=3&ithint=photo%2cjpg) shows what can be achieved with his solution.
The following code was used to create the GraphicsPath used to draw the pie and annular sectors in the image. At the time of writing this negative sweepA (sweep angle) was not supported.
Dim p As GraphicsPath = New GraphicsPath()
p.AddPie(160.0F, 160.0F, 280.0F, 280.0F, 300.0F, 90.0F)
p.AddPath(DrawAnnular(New Point(300I, 300I), 100I, 70I, 300.0F, 90.0F, 5.0R), False)
p.AddPath(DrawAnnular(New Point(300I, 300I), 100I, 70I, 30.0F, 80.0F, 5.0R), False)
p.AddPath(DrawAnnular(New Point(300I, 300I), 135I, 105I, 300.0F, 90.0F, 5.0R), False)
p.AddPath(DrawAnnular(New Point(300I, 300I), 135I, 105I, 30.0F, 80.0F, 5.0R), False)
Thanks
drifter
pntC is the center. outerR and innerR are the radius. startA and endA are the angles. The angles are measured from x axis and clockwise
Private Sub DrawAnnular(ByVal pntC As Point, ByVal outerR As Integer, ByVal innerR As Integer, ByVal startA As Integer, ByVal endA As Integer)
Dim g As Graphics
Dim mypen As New Pen(Color.FromKnownColor(KnownColor.Control), 1) 'the form back color
Dim mybrush As New SolidBrush(Color.FromKnownColor(KnownColor.Control)) 'the form back color
g = Me.CreateGraphics
g.SmoothingMode = SmoothingMode.AntiAlias
g.FillPie(Brushes.Black, pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR, startA, -startA + endA) 'Outer
g.FillPie(mybrush, pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR, startA, -startA + endA) 'Inner
g.DrawPie(mypen, pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR, startA, -startA + endA) 'Outer
g.DrawPie(mypen, pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR, startA, -startA + endA) 'Inner
g.Dispose()
End Sub
EDIT
This is an example of how to find the GraphicPath with the gap.
Dim pth As New GraphicsPath
pth.AddArc(outArc)
pth.AddLine(D, C)
pth.AddArc(innArc)
pth.AddLine(A, B)
and there is your path. Lets find A, B (the same logic applies to C, D):
ε1 : y = a * x + b
ε1': y = a * x + (b +- 2,5) we keep the minus in this example
a is known (the angle of ε1 with x axis) and b can be calculated (pntC belongs to ε1):
pntC.Υ = a * pntC.X + b
Now we have to find the intercept points of ε1' with outer circle(B) and with inner circle(A):
Solve these equations for x, y (point B)
circle: (x - pntC.X) * (x - pntC.X) + (y - pntC.Y) * (y - pntC.Y) = outerR * outerR
ε1' : y = a * x + (b - 2,5)
Solve these equations for x, y (point A)
circle: (x - pntC.X) * (x - pntC.X) + (y - pntC.Y) * (y - pntC.Y) = innerR * innerR
ε1' : y = a * x + (b - 2,5)
Now we need to valculate outArc, innArc. outArc (the same logic applies for innerArc):
pth.AddArc(outArc): pth.AddArc(pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR, startA + φ, endA - startA - 2 * φ)
Calculating φ (OBB' triangle):
sinφ = OB'/OB => sinφ = 2,5 / outerR
One last thing. Because Form coordinates are opposite from real ones (only for Y axis), before using pntC for your calculation, convert to real one :
real: pntC.Yr = (Form client Height) - pntC.Y
and when you calculate A, B, C, D, transform to Form ones (the r means real):
A.Y = (Form client Height) - A.Yr
B.Y = (Form client Height) - B.Yr
C.Y = (Form client Height) - C.Yr
D.Y = (Form client Height) - D.Yr
EDIT 2
With the gap:
Private Sub DrawAnnular(ByVal pntC As Point, ByVal outerR As Integer, ByVal innerR As Integer, ByVal startA As Single, ByVal sweepA As Single, ByVal gap As Double)
Dim g As Graphics
Dim pth As New GraphicsPath
Dim fe, dbl As Double 'fe is "φ"
gap = gap / 2.0R
g = Me.CreateGraphics
g.SmoothingMode = SmoothingMode.AntiAlias
dbl = gap / CDbl(outerR)
fe = Math.Asin(dbl) * 180.0R / Math.PI
pth.AddArc(pntC.X - outerR, pntC.Y - outerR, 2 * outerR, 2 * outerR, startA + CSng(fe), sweepA - CSng(2.0R * fe)) 'Outer
dbl = gap / CDbl(innerR)
fe= Math.Asin(dbl) * 180.0R / Math.PI
pth.AddArc(pntC.X - innerR, pntC.Y - innerR, 2 * innerR, 2 * innerR, startA + sweepA - CSng(fe), -(sweepA - CSng(2.0R * fe))) 'Inner
g.FillPath(Brushes.Black, pth)
pth.Dispose()
g.Dispose()
End Sub
valter