Align With Normals

Blitz3D Forums/Blitz3D Programming/Align With Normals

_PJ_(Posted 2016) [#1]
Hi

I am working on a simple editor where I have a errain mesh and need to identify the angles of the normals to a selected triangle.

Triangle selection is all working well enough, and the position correctly marks the centre of a triangle.

However, when I try to align a 'marker' cylinder to show the direction vector of the normal, it doesn't appear to be correct.

here's the offending function:

	Local Surf=GetSurface(TERRAIN,1)
	
	Local v0=TriangleVertex(Surf,Tri,0)
	Local v1=TriangleVertex(Surf,Tri,1)
	Local v2=TriangleVertex(Surf,Tri,2)
	
	VertexColor Surf,v0,0,0,255
	VertexColor Surf,v1,0,0,255
	VertexColor Surf,v2,0,0,255
	
	EntityFX TERRAIN,2
	
	Local x0#=VertexX(Surf,v0)
	Local x1#=VertexX(Surf,v1)
	Local x2#=VertexX(Surf,v2)
	
	Local y0#=VertexY(Surf,v0)
	Local y1#=VertexY(Surf,v1)
	Local y2#=VertexY(Surf,v2)
	
	Local z0#=VertexZ(Surf,v0)
	Local z1#=VertexZ(Surf,v1)
	Local z2#=VertexZ(Surf,v2)
	
	Local nx0#=VertexNX(Surf,v0)
	Local nx1#=VertexNX(Surf,v1)
	Local nx2#=VertexNX(Surf,v2)
	
	Local ny0#=VertexNY(Surf,v0)
	Local ny1#=VertexNY(Surf,v1)
	Local ny2#=VertexNY(Surf,v2)
	
	Local nz0#=VertexNZ(Surf,v0)
	Local nz1#=VertexNZ(Surf,v1)
	Local nz2#=VertexNZ(Surf,v2)
	
	Local vX#=(x0+x1+x2)/3
	vX=vX+EntityX(TERRAIN,True)
	Local vY#=(y0+y1+y2)/3
	vY=vY+EntityY(TERRAIN,True)
	Local vZ#=(z0+z1+z2)/3
	vZ=vZ+EntityX(TERRAIN,True)
	
	Local nX#=(nx0+nx1+nx2)/3
	Local nY#=(ny0+ny1+ny2)/3
	Local nZ#=(nz0+nz1+nz2)/3
	
	RotateEntity NORMALMARKER,VectorPitch(nX,nY,nZ),VectorYaw(nX,nY,nZ),0
	
	PositionEntity NORMALMARKER,vX,vY,vZ,True
	UpdateNormals NORMALMARKER
	ShowEntity NORMALMARKER
End Function



Floyd(Posted 2016) [#2]
First, a mesh has only vertex normals and not surface normals. Averaging the three vertex normals should usually be a reasonable approximation. But for accuracy is it better to compute the surface normal directly.

Consider two edges of the triangle to be vectors. The cross product of these is perpendicular to both and is thus a normal for the triangle. This could point to the front or back, depending on the order in which the edges are considered. If you end up with the wrong one then swap the two edges, i.e. change (edge1 cross edge2 ) to (edge2 cross edge1).

Once you have a normal vector you can orient the marker with AlignToVector. Here is something I wrote to illustrate TFormNormal on a scaled entity. That part is irrelevant for your problem. But near the bottom of the code you can see how alignment works once you have position and normal information.

; This shows how to find vertex positions and normals with a scaled entity.
; Left/Right arrow keys move around the scaled sphere. Escape quits

Graphics3D 800, 600, 0, 2
AmbientLight 50,50,50

lt = CreateLight() : RotateEntity lt, 0, 90, 0

sph = CreateSphere( 16 )

cam = CreateCamera() : PositionEntity cam, 0,0, -10 : CameraZoom cam, 4

cone = CreateCone()
ScaleMesh cone, 0.1, .25, 0.1
PositionMesh cone, 0, 0.26, 0

EntityColor cone, 0, 100, 100

surf = GetSurface(sph,1)
maxV = CountVertices( surf ) - 1

ScaleEntity sph, 2, .8, 2

v = 150  ; start at a vertex we can easily see

While Not KeyDown(1)

	PositionMarker( sph, 1, v, cone )
	RenderWorld
	Text 340, 500, "Vertex: " + v	
	Flip

	v = v + KeyDown(205) - KeyDown(203)   :   Delay 50
	
	If v > maxV Then v = 0
	If v < 0 Then v = maxV	

Wend


Function PositionMarker( mesh, surface_index, vert, marker )

	surf = GetSurface( mesh, surface_index )

	x# = VertexX( surf, vert )
	y# = VertexY( surf, vert )
	z# = VertexZ( surf, vert )

	nx# = VertexNX( surf, vert )
	ny# = VertexNY( surf, vert )
	nz# = VertexNZ( surf, vert )
	
	; Position and normal are correct for the underlying mesh, but
	; not for the scaled entity. Here's how to transform them.
		
	TFormPoint x,y,z, mesh, 0    ; the 0 means world coordinates
	x = TFormedX()
	y = TFormedY()
	z = TFormedZ()

	TFormNormal nx,ny,nz, mesh, 0 
	nx = TFormedX()
	ny = TFormedY()
	nz = TFormedZ()

	PositionEntity marker,  x,  y,  z	
	AlignToVector  marker, nx, ny, nz, 2
	
End Function



RemiD(Posted 2016) [#3]
A method that i have used in the past :
create a mesh (for example a cube) to represent your normal :
normalmesh = createcube()
scalemesh(normalmesh,0.01/2,0.01/2,1.0/2)
positionmesh(normalmesh,0,0,1.0/2)

then once you have your picked/collided point position, position this normalmesh at this position.
then create a temporary pivot
then position the temporary pivot at this position
then move or translate (i don't remember) the temporary pivot with the normal values
then pointentity normalmesh to temporarypivot
then free the temporarypivot


_PJ_(Posted 2016) [#4]
First, a mesh has only vertex normals and not surface normals. Averaging the three vertex normals should usually be a reasonable approximation. But for accuracy is it better to compute the surface normal directly.


Thanks for this! Considering the available commands, this makes a lot more sense now!

I had just made a little progress using

AlignToVector NORMALMARKER,nX,nY,nZ,2
but still wasn't entirely accurate.

I can see from your example now, how to use the TForm commands, at least this is all a great way to learn some of these commands that I have rarely used and are a little intimidating!


then once you have your picked/collided point position, position this normalmesh at this position.
then create a temporary pivot
then position the temporary pivot at this position
then move or translate (i don't remember) the temporary pivot with the normal values
then pointentity normalmesh to temporarypivot
then free the temporarypivot


Hey RemiD, thanks for this, I THINK, if I'm following correctly, that that suggestion is in principle what I am actually trying to do, only my difficulty is with aligning the normalmesh (or pivot, whichever is responsible for determining the actual orientation) to match the normal Terrain Mesh's vector's direction

Anyhow, with my newly acquired knowledge of the TFormNormal command as above, I am on my way again :) Thanks!


Floyd(Posted 2016) [#5]
The TForm commands aren't really needed here. My original example was mainly to illustrate how to deal with scaled entities.

Copying your code and doing the cross product I get the following. It is untested, just visualizing how it should work.

	Local x0#=VertexX(Surf,v0)
	Local x1#=VertexX(Surf,v1)
	Local x2#=VertexX(Surf,v2)
	
	Local y0#=VertexY(Surf,v0)
	Local y1#=VertexY(Surf,v1)
	Local y2#=VertexY(Surf,v2)
	
	Local z0#=VertexZ(Surf,v0)
	Local z1#=VertexZ(Surf,v1)
	Local z2#=VertexZ(Surf,v2)
	
	Local xe1#, ye1#, ze1#  ; edge 1 will point from v0 to v1
	Local xe2#, ye2#, ze2#  ; edge 2 from v0 to v2
	
	xe1 = x1 - x0  :  ye1 = y1 - y0  :  ze1 = z1 - z0
	xe2 = x2 - x0  :  ye2 = y2 - y0  :  ze2 = z2 - z0
	
	; Normal vector will be edge 1 crossed with edge 2. Don't need to scale to length 1.
	; If this goes the opposite way from what is needed then interchange edge 1 and edge 2.
	
	Local nx#, ny#, nz#
	
	nx = ye1 * ze2 - ye2 * ze1
	ny = xe2 * ze1 - xe1 * ze2
	nz = xe1 * ye2 - xe2 * ye1
	
	; Position the marker and then align with 
	
	AlignToVector NORMALMARKER, nx, ny, nz, 2



Flanker(Posted 2016) [#6]
A little example with normals functions from simonh in the code archives :

If you use a mesh to draw the normal instead of a 2d line, you just have to position it in the DrawPickedTriangle() function, at point cx#,cy#,cz#, then align to vector nx#,ny#,nz#.


_PJ_(Posted 2016) [#7]
That's really great, thanks Flanker! Just what I needed.

I did manage to get something that seemed to work but the code you've provided is very clean and the TriangleN_ functions are nicely separated out.

Thanks for providing the info to convert the 2D lines too.


Kryzon(Posted 2016) [#8]
I think the order that the vertices are defined in the surface can be used to always know how to order the cross product.
For each triangle, the vertices are defined in a clockwise order --with the face of the clock pointing in the same direction as the "visible" normal of the triangle.

- A triangle with vertex one, two and three;
- With vector "a" being from vertex one to two, and vector "b" being from vertex two to three;
- The cross product should always be ordered ( a x b ) to find the normal that matches the one used for the triangle visibility.

(Based on the "right-hand rule" from Wikipedia: https://en.wikipedia.org/wiki/Cross_product#Definition)


Floyd(Posted 2016) [#9]
These things always manage to confuse me. The problem is that I learned mathematics long before computer graphics, so my brain is hardwired with mathematical conventions. That includes coordinate systems being right handed. But Direct3D is left handed.

In the early days of Blitz3D I visualized things "backward" so often that I finally gave up and adopted a lazy approach. I know that I need a x b or b x a. I don't even attempt to figure out which it should be. Just try one of them. If that fails then use the other one.


Kryzon(Posted 2016) [#10]
If i understand what you mean, the cross order should be ( b x a ) then (since it's the opposite of right-hand), under that description in my post.

It occurred to me that he's got some triangle selection happening.
How is he doing that? Because if he's using one of the Pick commands (camera, line etc.), the normal of the picked triangle is already calculated for you, given by PickedNX\NY\NZ.


Floyd(Posted 2016) [#11]
If i understand what you mean, the cross order should be ( b x a ) then (since it's the opposite of right-hand)

You were probably right the first time.

Think about the X,Y,Z axes, and imagine three unit vectors in the positive direction. In traditional mathematics those are called i,j,k but I will use the names X,Y,Z. They are mutually perpendicular.

It is also traditional that they form a right handed system, so X x Y = Z. Start with the fingers of your right hand pointing in the X direction. Curl your fingers toward Y. Your thumb will then point toward Z.

If you do this with Blitz3D it doesn't work. X points right, Y up and Z into the screen. That's left handed. Using the intuitive physical definition we should now get X x Y = -Z, or equivalently Y x X = Z.

But if you blindly calculate X x Y using the standard coordinate formulas you still get Z.

Anyway, I stopped worrying about it. Here is another example. Pitch, Yaw, Roll are rotations about the three axes. Which direction is a positive turn? If you stand at the origin and look down an axis then the traditional convention is that a positive turn ( like +10 degrees ) is counterclockwise. That's for a right handed system.

What about a left handed system? Well, something has to change. The Direct3D designers chose to flip the direction on one axis. I think it's the Y-axis, i.e. Yaw. But again I don't depend on my memory. I just try it and see.


_PJ_(Posted 2016) [#12]

It occurred to me that he's got some triangle selection happening.
How is he doing that? Because if he's using one of the Pick commands (camera, line etc.), the normal of the picked triangle is already calculated for you, given by PickedNX\NY\NZ.



I think I began trying to use these, but it wasn't working right for me- I might go back and see if I can do it now I have a better understanding, since it's got to be a better way :)

Incidentally, under-the-hood, there's no way for B3D (Or directX rather) to obtain the vertex normals WITHOUT 'knowing' the associated triangle plane angles and therefore, the triangle's vectors - so it makes more sense if I can just grab those values rather than reinvent wheels!

_________________


I think if the triangles were created the reverse direction, they would not be visible (without setting entityFX to diasable Backface culling) from the same side - Flanker's (simonh's) code seems to work for me, so the cross-product order he's using at least suits however I happen to have built my triangles :)
_________________

Thanks again!
__________________
__________________

Update, I substituted in PickedX, Y and Z values for the TriangleNX functions from Flanker's code in my actual project, and the results appear identical, so everything's working as intended/expected for now at least, and a whole lot cleaner :D


Flanker(Posted 2016) [#13]
I didn't even remember the commands PickedNX(), PickedNY(), PickedNZ(), if it suits your need then yes you'd better use them, probably faster and makes the code cleaner.

BTW, I don't understand the discussion about the cross order. When you call TriangleVertex for example, 0, 1, 2 returns the vertices used for the triangle, in the order the triangle was created. So you don't have to worry about the cross product order.


_PJ_(Posted 2016) [#14]
I think the dsicussion was just with regards to which GLOBAL direction (+ or - on the given axis) the triangle normal would be, since it would point from the 'front face' which is reversed if the order of created verts are?