The normal has x,y and z components...on a flat section of terrain it will be 0,1.0,0 but it will vary depending on hills. i set all 3 components as they should be set.
Take a look at the GenerateHeightmapNormals() function, its somewhere in the code below. I can post a full example project showing how to use it if you wanted.
// Generate heightmap from image function
// scale applies to the width and depth of the heightmap (a scale of 1.0 gives a heightmap 1.0 wide and 1.0 deep)
// height scales the maximum height value
// Smoothing applies some smoothing to the hills
function CreateObjectFromHeightMapImage(ImageId as Integer, scale as float, height as float, smoothing as integer)
// Create a memblock from the image and get its dimensions
imblk = CreateMemblockFromImage(ImageId)
imx = GetMemblockInt( imblk, 0 ) // image x dimension
imy = GetMemblockInt( imblk, 4 ) // image y dimension
// Image data starts at offset 12 and is in 4byte blocks per pixel RGBA
// Calculate the number of triangles in the heightmap mesh
NumVertices = imx * imy
NumTriangles = 2* (imx-1) * (imy-1)
NumIndices = 3* NumTriangles
VertexSize = 8 * 4 // Size of a vertex = 8 floats x 4 bytes = 32 bytes
// calculate the size of memblock we will need
VertexOffset = 24 + 36
IndexOffset = VertexOffset + (NumVertices * VertexSize)
MemSize = IndexOffset + (NumIndices * 4)
// create the memblock
msblk = CreateMemblock( MemSize )
//debug
//print("Mesh Memblock ID:" + str(msblk))
//print("Mesh Memblock Size:" + str(MemSize))
// Start filling it up with relevant info
SetMemblockInt( msblk, 0, NumVertices )
SetMemblockInt( msblk, 4, NumIndices )
SetMemblockInt( msblk, 8, 3 ) // number if attributes (3)
SetMemblockInt( msblk, 12, VertexSize ) // Bytes per vertex (32)
SetMemblockInt( msblk, 16, VertexOffset ) // should be (60)
SetMemblockInt( msblk, 20, IndexOffset ) // varies
// debug
//print("NumVertices:" + str(NumVertices))
//print("NumTriangles:" + str(NumTriangles))
//print("VertexSize:" + str(VertexSize))
//print("VertexOffset:" + str(VertexOffset))
//print("IndexOffset:" + str(IndexOffset))
// Set the attribute Data
// VERTEX ATTRIBUTES
SetMemblockByte( msblk, 24, 0 ) // float
SetMemblockByte( msblk, 25, 3 ) // component count
SetMemblockByte( msblk, 26, 0 ) // normalize
SetMemblockByte( msblk, 27, 12 ) // string length
SetMemblockString( msblk, 28, "position" )
// NORMALS ATTRIBUTES
SetMemblockByte( msblk, 40, 0 ) // float
SetMemblockByte( msblk, 41, 3 ) // component count
SetMemblockByte( msblk, 42, 0 ) // normalize
SetMemblockByte( msblk, 43, 8 ) // string length
SetMemblockString( msblk, 44, "normal" )
// UV ATTRIBUTES
SetMemblockByte( msblk, 52, 0 ) // float
SetMemblockByte( msblk, 53, 2 ) // component count
SetMemblockByte( msblk, 54, 0 ) // normalize
SetMemblockByte( msblk, 55, 4 ) // string length
SetMemblockString( msblk, 56, "uv" )
// Vertex data begins at offset 60
// Copy the vertex data into memblock
vertexIndex = 0
x# = 0
y# = 0
z# = 0
u# = 0
v# = 0
// Go through all the vertices and set heights, location, UV's and a preliminary normal
for iy# = 0 to imy-1
for ix# = 0 to imx-1
// create uv's (0.0-1.0)
u#=ix#/(imx-1)
v#=iy#/(imy-1)
// create positions
x#= u#
y#= GetMemblockByte( imblk, 12+(vertexIndex*4))/255.0// get the red value only
z#= 1.0 - v#
// Scale the x,y,z (x &Z by scale...Y is the height)
x# = x# * scale
z# = z# * scale
y# = y# * height
// Save these values
SetMeshMemblockVertexPosition( msblk, vertexIndex, x#, y#, z# )
SetMeshMemblockVertexNormal( msblk, vertexIndex, 0.0, 1.0, 0.0 ) // set a default normal to straight up
SetMeshMemblockVertexUV( msblk, vertexIndex, u#, v# )
// increment the vertex index
vertexIndex = vertexIndex+1
next ix#
next iy#
//print("Vertices Added:" + str(vertexIndex))
// Set the indices in the memblock
// 0,1,1+imx
// 0,1+imx,0+imx repeat this - clockwise order??
IndOffset = IndexOffset
//print("Indices Start at:" + str(IndOffset))
Index = 0
for ix = 0 to imx-2
for iy = 0 to imy-2
Index = ix + (iy*imx) // top left
// First index
SetMemblockInt( msblk, IndOffset, Index ) // top left
IndOffset = IndOffset + 4
// third index
SetMemblockInt( msblk, IndOffset, Index + 1 + imx) // bottom right
IndOffset = IndOffset + 4
// second index
SetMemblockInt( msblk, IndOffset, Index + 1 ) // top right
IndOffset = IndOffset + 4
// First index
SetMemblockInt( msblk, IndOffset, Index ) // top left
IndOffset = IndOffset + 4
// Third index
SetMemblockInt( msblk, IndOffset, Index + imx) // bottom left
IndOffset = IndOffset + 4
// Second index
SetMemblockInt( msblk, IndOffset, Index + 1 + imx ) // bottom right
IndOffset = IndOffset + 4
next iy
next ix
// Smooth the heightmap (can be re-entered multiple times)
while smoothing>0
SmoothHeightmap(msblk,imx,imy)
smoothing = smoothing - 1
endwhile
// generate the normals
GenerateHeightmapNormals(msblk) //,imx,imy)
// copy data into the membuffer
newobj = CreateObjectFromMeshMemblock( msblk )
// Delete the image memblock
DeleteMemblock( imblk )
// delete the MeshMemblock
DeleteMemblock( msblk )
// return the newly created mesh object :)
endfunction newobj
// Performs smoothing on the heightmap
// x is the width of the heightmap
// y is the depth of the heightmap
function SmoothHeightmap(msblk as integer, x as integer, y as integer)
//VertexSize = GetMemblockInt( msblk, 12) // Bytes per vertex (should be 32)
//VertexOffset = GetMemblockInt( msblk, 16 ) // (Normally 60)
// Applies a smoothing function to the mesh
// loop through all the vertices
for iy = 1 to y-2 // intentionally stays one pixel inside the heightmap
for ix = 1 to x-2 // intentionally stays one pixel inside the heightmap
// index of this vertex is
ind = ix + (iy*x)
xp# = GetMeshMemblockVertexX( msblk, ind )
yp# = GetMeshMemblockVertexY( msblk, ind )
zp# = GetMeshMemblockVertexZ( msblk, ind )
// get height of vertex to the left
yl# = GetMeshMemblockVertexY( msblk, ind-1 )
// get height of vertex to the right
yr# = GetMeshMemblockVertexY( msblk, ind+1 )
// get height of vertex below
yb# = GetMeshMemblockVertexY( msblk, ind+x )
// get height of vertex above
ya# = GetMeshMemblockVertexY( msblk, ind-x )
// calculate a new value of height
filt# = 0.6
newy# = (filt#*yp#) + ((1-filt#)*(yr# + yb# + ya# + yl#)/4)
// Set the newheight
SetMeshMemblockVertexPosition( msblk, ind, xp#, newy#, zp# )
next ix
next iy
endfunction
// Generate normals for the mesh
// msblk is a meshmemblock which holds the mesh ypou want normals calculating for
// The mesh must be an indexed triangle mesh - see alternate function for non indexed
function GenerateHeightmapNormals(msblk as integer)
// Start by getting the relevant info
NumVertices = GetMemblockInt( msblk, 0)
NumIndices = GetMemblockInt( msblk, 4)
NumTriangles = NumIndices/3
//NumAttributes = GetMemblockInt( msblk, 8) // number if attributes ( should be 3 for a heightmap)
//VertexSize = GetMemblockInt( msblk, 12) // Bytes per vertex (should be 32)
//VertexOffset = GetMemblockInt( msblk, 16 ) // (Normally 60)
IndicesOffset = GetMemblockInt( msblk, 20 )
// How to generate surface normals the proper way
//1) Normal vectors must all be zero before start!!
//2) Iterate through the triangles and get the normal on each triangle then ADD that to each of the 3 vertices normal. Other triangles will add to these values if they share indices.
//3) Loop through all the vertices and normalize the normals
// Fill all the vertex normals with 0,0,0
for ind =0 to NumVertices-1
SetMeshMemblockVertexNormal(msblk, ind, 0 , 0 , 0)
next ind
// loop through all the triangles
Offset = IndicesOffset
for it = 0 to NumTriangles-1
// Get the indices
inda = GetMemblockInt(msblk, Offset)
indb = GetMemblockInt(msblk, Offset+4)
indc = GetMemblockInt(msblk, Offset+8)
// Get the first vector (P2-P1)
Vx# = GetMeshMemblockVertexX(msblk,indb) - GetMeshMemblockVertexX(msblk,inda)
Vy# = GetMeshMemblockVertexY(msblk,indb) - GetMeshMemblockVertexY(msblk,inda)
Vz# = GetMeshMemblockVertexZ(msblk,indb) - GetMeshMemblockVertexZ(msblk,inda)
// Get the second vector (P3-P1)
Wx# = GetMeshMemblockVertexX(msblk,indc) - GetMeshMemblockVertexX(msblk,indb)
Wy# = GetMeshMemblockVertexY(msblk,indc) - GetMeshMemblockVertexY(msblk,indb)
Wz# = GetMeshMemblockVertexZ(msblk,indc) - GetMeshMemblockVertexZ(msblk,indb)
// Cross product them to get the normal
Nx#=(Vz#*Wy#)-(Vy#*Wz#)
Ny#=(Vx#*Wz#)-(Vz#*Wx#)
Nz#=(Vy#*Wx#)-(Vx#*Wy#)
// add the calculated normal to each of the vertices in this triangle
px# = GetMeshMemblockVertexNormalX(msblk, inda)
py# = GetMeshMemblockVertexNormalY(msblk, inda)
pz# = GetMeshMemblockVertexNormalZ(msblk, inda)
SetMeshMemblockVertexNormal(msblk, inda, px# + Nx#, py# + Ny#, pz# + Nz#)
px# = GetMeshMemblockVertexNormalX(msblk, indb)
py# = GetMeshMemblockVertexNormalY(msblk, indb)
pz# = GetMeshMemblockVertexNormalZ(msblk, indb)
SetMeshMemblockVertexNormal(msblk, indb, px# + Nx#, py# + Ny#, pz# + Nz#)
px# = GetMeshMemblockVertexNormalX(msblk, indc)
py# = GetMeshMemblockVertexNormalY(msblk, indc)
pz# = GetMeshMemblockVertexNormalZ(msblk, indc)
SetMeshMemblockVertexNormal(msblk, indc, px# + Nx#, py# + Ny#, pz# + Nz#)
// Move the offset to the next triangle
Offset = Offset + 12
next it
// Normalise all the vertex normals
for ind =0 to NumVertices-1
// Get the normal
nx# = GetMeshMemblockVertexNormalX(msblk, ind)
ny# = GetMeshMemblockVertexNormalY(msblk, ind)
nz# = GetMeshMemblockVertexNormalZ(msblk, ind)
// normalise the normal
len# = sqrt((nx#*nx#) + (ny#*ny#) + (nz#*nz#))
nx# = nx#/len#
ny# = ny#/len#
nz# = nz#/len#
// Set the new normal
SetMeshMemblockVertexNormal( msblk, ind, nx#, ny#, nz# )
next ind
endfunction
// Get float array from heightmap
// gets an array of floats which contains the y data in the heightmap memblock
// data is the array that gets filled with the heights in the heightmap
// x is the width of the heightmap
// y is the depth of the heightmap
function GetFloatArrayFromHeightmap(msblk as integer, data ref as float[][],x as integer, y as integer)
// loop through all the vertices
for iy = 0 to y-1
for ix = 0 to x-1
// index of this vertex is
ind = ix + (iy*x)
// Get this vertex
yp# = GetMeshMemblockVertexY( msblk, ind )
// Stick it into the array
data[y,x] = yp# // should this be [x,y] ?? hmm whats the convention in basic?
// todo - check this convention?
next ix
next iy
// TODO - this would be much quicker with in incrementing offset and direct access to the float Y value using GetMemblockFloat(offset) etc...
// TODO - implement and test that
endfunction
// This Saves the whole memblock to a text readable file - mainly for debugging code
// saves in the standard default AGK save location (HARD TO FIND!!)
Function SaveMeshMemBlockAsText(msblk as integer, Filename as string)
//SetFolder("media")
// open a file
fid = OpenToWrite(Filename)
// Start saving the relevant info
WriteLine( fid, "Num Vertices:" + str(GetMemblockInt( msblk, 0)))
WriteLine( fid, "Num Indices:" + str(GetMemblockInt( msblk, 4)))
WriteLine( fid, "Num Attributes:" + str(GetMemblockInt( msblk, 8)))
WriteLine( fid, "Vertex Size:" + str(GetMemblockInt( msblk, 12)))
WriteLine( fid, "Vertex Data Offset:" + str(GetMemblockInt( msblk, 16)))
WriteLine( fid, "Vertex Data Size:" + str(GetMemblockInt( msblk, 12) * GetMemblockInt( msblk, 0)))
WriteLine( fid, "Index Data Offset:" + str(GetMemblockInt( msblk, 20)))
WriteLine( fid, "Index Data Size:" + str(GetMemblockInt( msblk, 4)*4))
WriteLine( fid, "Total Memblock Size:" + str(GetMemblockSize( msblk)))
offset = 24
// Write out the attributes
for Att = 1 to GetMemblockInt( msblk, 8)
WriteLine( fid, "Attribute" + str(Att) +": " + str(GetMemblockByte( msblk, offset))+ str(GetMemblockByte( msblk, offset+1))+ str(GetMemblockByte( msblk, offset+2))+ str(GetMemblockByte( msblk, offset+3)))
WriteLine( fid, GetMemblockString( msblk, offset+4, GetMemblockByte( msblk, offset+3) ))
// Move the offset
offset = offset + 4 + GetMemblockByte( msblk, offset+3)
next Att
// Write out the indices in 3's
offset = GetMemblockInt( msblk, 20)
WriteLine( fid, "Indices:")
for ind = 1 to GetMemblockInt( msblk, 4)
WriteLine( fid, str(GetMemblockInt( msblk, offset)) + " " + str(GetMemblockInt( msblk, offset+4)) + " " + str(GetMemblockInt( msblk, offset+8)))
offset = offset + 12
next ind
// Write out the Vertices
offset = GetMemblockInt( msblk, 16)
WriteLine( fid, "Vertices:")
for Ver = 1 to GetMemblockInt( msblk, 0)
WriteLine( fid, str(GetMemblockFloat( msblk, offset)) + " " + str(GetMemblockfloat( msblk, offset+4)) + " " + str(GetMemblockFloat( msblk, offset+8)))
offset = offset + GetMemblockInt( msblk, 12)
next Ver
// close the output file
CloseFile(fid)
Endfunction