Please bear with me I´m a newbie in programming and I´m trying to learn how to rotate a 3D point (XYZ) around 0,0,0 and later I´ll try to improve my code to allow rotation around an arbitrary point (XYZ).
I´m starting with VB and after extensive searches here and in the Web, I could not find an explanation for my problem. I´m an almost 40 years old trying to learn math and programming, so please bear with me because it will take time for me to digest all the math side for these problems.
Basically, I´m trying to write an algorithm to rotate a 3D point, however, while it seems that with some angles my code works, with some others I just get weird funky numbers that are probable correct in some aspect, but I can´t find the flaw in the code. I´ve been looking into this for days and tried multiple approaches, but I´m just not being able to spot the error.
This is the UI for my little app. The original coordinates are entered in the top of the form, and in the bottom of the form I show the rotated coordinates.
Notice that in the image below, a simple rotation of a coordinate of Y10.0 around Z axis by 90 degrees return a correct X value (-10), but Y shows a funky number (6.1230...)... However if I change the rotation angle around Z to 45, the results seems to be correct...
I don´t know what I´m doing wrong to get this weird Y. Because of this error, I´m not trusting in the results of this algorithm at all but I´m currently in a blindspot...
This is the code of the calculate button:
Private Sub BtnCompute_Click(sender As Object, e As EventArgs) Handles BtnCompute.Click
'Capture the values from the text boxes and parse then to doubles
ValidateAllFieldsWithDoubleValues()
'Rotate the coordinates
RotateXYZCoordinates(dblOriginalCoordX, dblOriginalCoordY, dblOriginalCoordZ, dblCurrentRotationAroundX, dblCurrentRotationAroundY, dblCurrentRotationAroundZ)
'Update the text boxes for the rotated coordinates for XYZ
txtResultX.Text = dblResultX.ToString
txtResultY.Text = dblResultY.ToString
txtResultZ.Text = dblResultZ.ToString
End Sub
And this is the code of the function that calculates the rotations:
Private Function RotateXYZCoordinates(ByVal XCoord As Double, ByVal YCoord As Double, ByVal ZCoord As Double, ByVal Pitch As Double, ByVal Roll As Double, ByVal Yaw As Double)
'X Rotation
Dim RadPitch As Double = 0
Dim CosPitch As Double = 0
Dim SinPitch As Double = 0
Dim XRotatedAroundX As Double = 0
Dim YRotatedAroundX As Double = 0
Dim ZRotatedAroundX As Double = 0
RadPitch = Pitch * Math.PI / 180
CosPitch = Math.Cos(RadPitch)
SinPitch = Math.Sin(RadPitch)
XRotatedAroundX = XCoord
YRotatedAroundX = YCoord * CosPitch - ZCoord * SinPitch
ZRotatedAroundX = YCoord * SinPitch + ZCoord * CosPitch
'Y Rotation
Dim RadRoll As Double = 0
Dim CosRoll As Double = 0
Dim SinRoll As Double = 0
Dim XRotatedAroundY As Double = 0
Dim YRotatedAroundY As Double = 0
Dim ZRotatedAroundY As Double = 0
RadRoll = Roll * Math.PI / 180
CosRoll = Math.Cos(RadRoll)
SinRoll = Math.Sin(RadRoll)
XRotatedAroundY = ZRotatedAroundX * CosRoll - XRotatedAroundX * SinRoll
YRotatedAroundY = YRotatedAroundX
ZRotatedAroundY = ZRotatedAroundX * SinRoll + XRotatedAroundX * CosRoll
'Z Rotation
Dim RadYaw As Double = 0
Dim CosYaw As Double = 0
Dim SinYaw As Double = 0
Dim XRotatedAroundZ As Double = 0
Dim YRotatedAroundZ As Double = 0
Dim ZRotatedAroundZ As Double = 0
RadYaw = Yaw * Math.PI / 180
CosYaw = Math.Cos(RadYaw)
SinYaw = Math.Sin(RadYaw)
XRotatedAroundZ = XRotatedAroundY * CosYaw - YRotatedAroundY * SinYaw
YRotatedAroundZ = XRotatedAroundY * SinYaw + YRotatedAroundY * CosYaw
ZRotatedAroundZ = ZRotatedAroundY
'Final result
dblResultX = XRotatedAroundZ
dblResultY = YRotatedAroundZ
dblResultZ = ZRotatedAroundZ
Return Nothing
End Function
I know this is not an elegant code but it is what I can code for now... I´d appreciate if someone could take a look at this and point me to the source of error... I´ve been watching videos and did an extensive search in this website before I posted... But it seems some things are still very advanced to me for now... I´m not lazy and I´m willing to learn if someone point me towards something I could digest for now...
If someone could share a hint about how to make this rotate function to support rotation around a point other than 0,0,0 I´d appreciate.
Thank you,
Daniel
The answer is correct. Due to double precision math and a 90 degree rotation there is a limit to the accuracy. The answer is really 6.12303176911189E-16 or .000000000000000612303176911189. Round the number off to a realistic value of decimal points. This is also why 1+1 is not equal to 2 but 1.999999999999999999999999999999 in floating point math.
Related
I've done some research and the general advice is changing the datatype of the variable holding the expression to Long or Ulong but in either case, I'm still getting the same error. I also tried enclosing the expression with CInt() (or CLong()) to force it to cut out it's decimal portion to reduce the length of the output of the expression but neither is working. It's all pretty confusing. Any help will be deeply appreciated. The code block triggering the error is as follows;
Vsf(i) = CInt((((0.91544 - 0.00166 * Angle(i) - 0.000002 * W - 0.054248 *
Superelevation(i) - Sidefrictionfactor) / 0.013939) * Radius(i)) ^ 0.5)
Vro(i) = CInt((((1.04136 - 0.004528 * Angle(i) - 0.000004 * W - 0.338711 * Superelevation(i) - rolloverthreshold) / 0.014578) * Radius(i)) ^ 0.5)
Vmin(i) = CInt(Math.Min(Vsf(i), Vro(i)))
I declared VSf(), Vro() and Vmin() all as integer arrays. I even enclosed the computation expression in a CInt() in hopes that it would convert the result of Vro (which was triggering the arithmetic overflow error) to an integer and hence not have to deal with decimals which would lead to more digits. Yet when I hover over Vro(i), I see a 4 digit integer with decimals. Not sure why that's happening.
I broke the formula down to try and see where the problem was occurring. Everything buzzed along until the very end. If the value to be raised to .5 is a negative number, the square root does not produce a number. I would perform the steps of the formula without the ^.5 and then check for a negative number.
Private Sub OPCode()
Dim Angle = 90.2
Dim Radius = 100.2
Dim Superelevation = 37.8
Dim Sidefrictionfactor = 0.003
Dim W = 0.00325
Dim Vsf As Double
Dim AngleMultiplication = 0.00166 * Angle
Dim WMultiplication = 0.000002 * W
Dim SuperelavationMultiplication = 0.054248 * Superelevation
Dim SubtractResult = 0.91544 - AngleMultiplication - WMultiplication - SuperelavationMultiplication - Sidefrictionfactor
Dim DivisionResult = SubtractResult / 0.013939
Dim MultiplyByRadius = DivisionResult * Radius
Debug.Print(MultiplyByRadius.ToString) 'With my made up values I get a negative number
Vsf = MultiplyByRadius ^ 0.5 'You cannot get the square root of a negative number
Debug.Print(Vsf.ToString) 'NaN
Dim intVsf = CInt(Vsf) 'The Arithematic overflow error occurs here
End Sub
This is my first post on stackoverflow so sorry in advance for anything I may have messed up, but for a project I am doing a simulator for projectile motion. I am only a beginner programmer and dont have much experience with charts, but there aren't any youtube videos that I find helpful.
The chart is plotting vertical displacement against horizontal displacement, and should look like an inverted parabola but it doesn't look anything like what it should.
I am struggling to set axis intervals correctly, each time the line should start from the origin but the x axis contains negative values for displacement, something which should definitely not be occurring. Each time a different simulation is done the axis should change so that each interval in the x axis is something along the lines of a tenth of the total range of the projectile.
Anything that could point me in the right direction would be incredibly useful.
Code:
Public Sub CreateDiagram(ByVal flightRange As Double, ByVal totalTime As Double, ByVal velocity
As Double, ByVal angle As Double, ByVal elevation As Double, ByVal heightOfProjectile As Double)
Chart1.Titles.Add("Projectile Motion")
Chart1.ChartAreas.Clear()
Chart1.ChartAreas.Add("Default")
Dim xAxisInterval As Integer = CInt(totalTime / 10)
Dim yAxisInterval As Integer = CInt(heightOfProjectile / 10)
With Chart1.ChartAreas("Default")
.AxisX.Interval() = xAxisInterval
.AxisY.Interval() = yAxisInterval
End With
Chart1.Series.Add("projection")
Chart1.Series("projection").Color = Color.Black
Chart1.Series("projection").ChartType = DataVisualization.Charting.SeriesChartType.Line
For x = 0 To totalTime Step 0.1
Dim xPos As Double = findXLocation(velocity, angle, x)
Dim yPos As Double = findYLocation(velocity, angle, x, elevation)
Chart1.Series("projection").Points.AddXY(xPos, yPos)
Next
End Sub
EDIT: findXLocation and findYLocation code:
Public Function findYLocation(ByVal velocity As Double, ByVal angle As Double, ByVal time As Double, ByVal elevation As Double) As Double
Dim y As Double
y = elevation + findVerticalVelocity(velocity, angle) - ((0.5 * gConstant) * (time ^ 2))
y = Math.Round(y, 1)
If y < 0 Then
y = 0
End If
Return y
End Function
Public Function findXLocation(ByVal velocity As Double, ByVal angle As Double, ByVal time As Double) As Double
Dim x As Double
x = findHorizontalVelocity(velocity, angle) * time
x = Math.Round(x, 1)
If x < 0 Then
x = 0
End If
Return x
End Function
If you want to explicitly set the minimum value of the X Axis you can use Axis.Minimum. In your case you would use it as Chart1.ChartAreas("Default").AxisX.Minimum = SomeValue
Note that when you set the minimum in this way the axis will stop automatically rescaling.
If you want the minimum to always be zero when all x-values are positive, have a look at Axis.IsStartedFromZero
I hope i understood your question correctly because you mention intervals, but seem to be talking about the minimum value of your x axis.
As a further comment on your code unrelated to the question; it is unnecessary and inefficient to remove the ChartArea every time you want to update the graph, same goes for the Series. Doing this will result in having to re-format the chart on every update (like you are in fact doing with the series).
It suffices to call YourSeries.Points.Clear() and to then add the new points. This way you don't have to re-specify the series color and chartype on each update.
EDIT:
I also see that you round the x position to one decimal, presumably to prevent the x-axis from having long ugly numbers on it. However, a better way to do this is to specify a format for the axis:
chart1.ChartAreas.FirstOrDefault.AxisX.LabelStyle.Format = "{0:0.0}" in your case. This way your series will still store the exact value, but only one decimal will be displayed.
I have this code I sorta put together from a few different sites.
It works perfect at creating the bitangent... but for what ever reason. the tangent is thrashed.. In other words and to make this more clear, the code is generating BAD tangents!!
The tangent is usually pointing down, kinda opposite the normal. I'm expecting it to be aligned with the V axis of the texture coordinate..
The strange thing is, I use the cross of the normal and tangent to get the bitangent, which is fine.
I have tried flipping the coordinates, the UVs.. just about everything..
Im not great in math so.. can some one take a look at my code and see if it looks "legit"? Thank you!
Private Sub ComputeTangentBasis(ByRef p0 As vertex_data, ByRef p1 As vertex_data, ByRef p2 As vertex_data)
Dim tangent, bitangent As Vector3D
Dim n As Vector3D
n.X = p0.nx
n.Y = p0.ny
n.Z = p0.nz
'convert to vector3d type... they are WAY easier to do complex math with!!
Dim v0 = convert_vector3d(p0)
Dim v1 = convert_vector3d(p1)
Dim v2 = convert_vector3d(p2)
Dim edge1 = v1 - v2
Dim edge2 = v2 - v0
Dim deltaU1 = (p1.u - p0.u) * -0.1
Dim deltaU2 = (p2.u - p0.u) * -0.1
Dim deltaV1 = (p1.v - p0.v) * -0.1
Dim deltaV2 = (p2.v - p0.v) * -0.1
Dim dividend = (deltaU1 * deltaV2) - (deltaU2 - deltaV1)
Dim f As Double
If dividend = 0.0 Then
f = 1.0
End If
tangent.X = f * ((deltaV2 * edge1.X) - (deltaV1 * edge2.X))
tangent.Y = f * ((deltaV2 * edge1.Y) - (deltaV1 * edge2.Y))
tangent.Z = f * ((deltaV2 * edge1.Z) - (deltaV1 * edge2.Z))
bitangent = Vector3D.CrossProduct(tangent, n)
'
p0.t.x = tangent.X
p0.t.y = tangent.Y
p0.t.z = tangent.Z
p0.bt.x = bitangent.X
p0.bt.y = bitangent.Y
p0.bt.z = bitangent.Z
'
p1.t.x = tangent.X
p1.t.y = tangent.Y
p1.t.z = tangent.Z
p1.bt.x = bitangent.X
p1.bt.y = bitangent.Y
p1.bt.z = bitangent.Z
'
p2.t.x = tangent.X
p2.t.y = tangent.Y
p2.t.z = tangent.Z
p2.bt.x = bitangent.X
p2.bt.y = bitangent.Y
p2.bt.z = bitangent.Z
End Sub
Here's a pic of the problem.. Norms are red.. Tangents Green... BiTangents.. blueish.
You will likely need to recalculate the tangent from cross-product of the normal and bitangent. This ensures that the normal, tangent and bitangent are orthagonal to one another.
In most implementations I've seen, the first calculation for a perpendicular axis to the normal is a placeholder as there are an infinite number of possible axes to choose from, and as such requires it to be recalculated to be orthagonal.
In your code you don't take dividend into the calculus, except for the case is 0.
Any two points in a triangle form a tangent or a bitangent. There are infinite possibilities. If you want to align T and B with some texture coordinates you need more maths.
With little effort you would have found this: How to calculate Tangent and Binormal?
Ok... I found 2 problems..
Dim edge1 = v1 - v2 should be Dim edge1 = v1 - v0
I looked in to what Ripi2 commented about.. I watched the video I got this from over again.. I messed a part where he changed the code.. He removed dividend and the if statement and replaced it with this line:
Dim f As Single = 1.0! / ((deltaU1 * deltaV2) - (deltaU2 * deltaV1))
Now it creates prefect Tangents and BiTangents.
I added normalization using these 2 lines.
tangent /= tangent.Length
bitangent /= bitangent.Length
I hope this helps anyone out there.
I'm reading the following page about steering AI:
http://rocketmandevelopment.com/blog/steering-behaviors-wander/
At the bottom is some code which I'm trying to covert for use in VB.NET + XNA
My code looks like this:
Sub AI()
Dim circleRadius As Single = 6.0F
Dim wanderAngle As Single = 0.0F
Dim wanderChange As Single = 1.0F
Dim enemySpeed As Single = 0.3F
Dim enemyPosistion As Vector2 = (1,1)
Dim circleMiddle As Vector2 = enemyPosistion
circleMiddle.Normalize()
circleMiddle = Vector2.Multiply(circleMiddle, circleRadius)
Dim wanderForce As New Vector2
wanderForce = Vector2.Normalize(wanderForce) * 3 ' Set length of vector
wanderForce = AngleToVector(wanderAngle)
Randomize()
wanderAngle += Rnd() * wanderChange - wanderChange * 0.5
Dim force As New Vector2
force = Vector2.Add(circleMiddle, wanderForce)
enemyPosistion += force * enemySpeed
End Sub
Private Function AngleToVector(angle As Single) As Vector2
Return New Vector2(CSng(Math.Sin(angle)), -CSng(Math.Cos(angle)))
End Function
I realised I made a simple mistake by setting enemyPosistion to Vector2.Zero, instead I set it to (1,1) and it makes the enemy fly up and to the right. I have included a video:
https://www.youtube.com/watch?v=UZubNaEA9W0
This is more along the lines of what it should do:
https://www.youtube.com/watch?v=1wfgPCMdW2U
Can anybody tell me what I'm doing wrong?
You need to preserve some state across frames. At least the wanderAngle and a velocity vector. Call Randomize() only once at the beginning of the game. circleMiddle should be velocity * circleRadius. velocity should be updated at the end with circleMiddle + wanderForce and enemyPosition += velocity * enemySpeed. Honestly, this whole code looks a bit strange, mixing up some physical concepts. Forces affect a body's acceleration (together with mass), acceleration affects its velocity (together with a time step) and the velocity affects its position (together with a time step). - Nico Schertler
VB.NET
My data is coming from an exported DXF file from various cad packages. When a circular arc (defined as a part of a true circle) is exported it is sometimes exported as a bunch of line segments rather than a circular arc.
I have a list of points and I am trying to guess if they were derived from the same circle. Basically I iterate through all the points and use a method to find the center point of a circle from three points. My intention was to compare all the calculated center points generated and make a determination if they are close to each other.
My first thought was that I could check to see if the center points are all equal but they have slight differences between them because of rounding and the underlying estimating routine that generates the point in the first place (I have no control over that).
MY second was to check the standard deviation of the x and y values of the circumference points and compare that against the standard deviation of the x,y of the centers and make some judgement from that. VB.net does not seem to have a native stdev function and I am sometimes a bit lazy.
Does anybody have a simple idea on how to determine if a list of points are all from the same circle?
Here are my functions:
To determine the center of a circle given three points:
Public Function getCenter(p1 As Point2D, p2 As Point2D, p3 As Point2D) As Point2D
Dim yDelta_a As Double = p2.Y - p1.Y
Dim xDelta_a As Double = p2.X - p1.X
Dim yDelta_b As Double = p3.Y - p2.Y
Dim xDelta_b = p3.X - p2.X
Dim center As New Point2D
Dim aSlope As Double = yDelta_a / xDelta_a
Dim bSlope As Double = yDelta_b / xDelta_b
center.X = (aSlope * bSlope * (p1.Y - p3.Y) + bSlope * (p1.X + p2.X) - aSlope * (p2.X + p3.X)) / (2 * (bSlope - aSlope))
center.Y = -1 * (center.X - (p1.X + p2.X) / 2) / aSlope + (p1.Y + p2.Y) / 2
Return center
End Function
And then to iterate the list of points and get a collection of centers. FYI...This function received a list of lines that have endpoints that are points so I do a bit of iterating to get all the correct points.
Public Function MakesCircle(lines As List(Of Line))
Dim points As New List(Of Point2D)
If lines.Count < 2 Then
Return False
Else
//Get points from lines
For i As Int16 = 0 To lines.Count - 2
points.Add(lines(i).StartPoint)
Next
points.Add(lines.Last.StartPoint)
End If
//"Prime the pump" for the center calculation loop
Dim centers As New List(Of Point2D)
Dim a As Point2D = points(0)
Dim b As Point2D = points(1)
Dim c As Point2D = points(2)
//Calc all the centers
For i As Int16 = 3 To lines.Count - 1
centers.Add(getCenter(a, b, c))
a = b
b = c
c = points(i)
Next
//This is where I need logic to determine if the points all actually belong to the same circle
Return True
End Function
You can use a GraphicsPath object to find this out. -not tested-
I figured you would be able to construct a Rectangle Structure based on the data coming in (x,y,w,h) then this make-shift algorithm would do for you.
Private Function areAllPointsInEllipsis(ellipsis As Rectangle, pts() As Point) As Boolean
Dim result As Boolean
Using gp As New System.Drawing.Drawing2D.GraphicsPath
gp.AddEllipsis(ellispsis)
result = pts.All(Function(pt) gp.IsVisible(pt))
End Using
Return result
End Function
What I did was generate all the distances from the average center point to each of the points that make up the circumference. Find the max and min distances. They you can use a threshold of a percentage of the radius - a kind of eccentricity measurement. If the difference between the max and min falls below 1% of the radius then I call it a circle.