Code archives/3D Graphics - Misc/Yet Another Lightmapper Improvement
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| This update to YAL fixes numerous graphical glitches which many lightmappers have. Primarily, it corrects the black edges issue. It does this by determining if the lumels can see the lights rather than if the lights can see the lumels. This means that areas within solid geometry are lit. This correction however caused light to bleed under walls. Which though far less noticeable than the black edges issue, was still annoying. I corrected this issue by illuminating only those lumels which have all four corners in light, rather than just checking the center of each. This increases the lightmap processing time by 400% but results in a more solid feeling world. Another issue was that levels with improperly constructed goemetry, such as a cube sitting on the floor, would have dark edges around said geometry because the bottom face of the cube would block the view of the light for the floor beneath, which is similar to the dark edges problem. To correct this, I offset the position of each lumel from it's surface a little bit using the normal of the surface, and this raises the lumels up just enough to be above gometry sitting flush against floors walls or ceilings. This version of YAL requires the Ray Intersect functions you'll find just below this code archives entry. These functions replace the calls the EntityVisible, so that you don't have to make geometry pickable to lightmap it. It also allows me to add future improvments like light sources that have area and cast soft shadows, and may be somewhat faster than using EntityVisible calls. | |||||
; ID: 514
; Author: Marcelo
; Date: 2002-11-28 19:49:40
; Title: YAL - Yet Another LightMapper (update 1.4 )
; Description: Based on starfox's portable lightmapper
;
; YAL - Yet Another Lightmapper
; Version: 1.4
;
; Please post any code improvement into blitz basic main site www.blitzbasic.co.nz, and include your data into history
; Thanks to David Dawkins (startfox) and elias_t, that produced the base code for this file.
;
; References:
; http://members.net-tech.com.au/alaneb/lightmapping_tutorial.html (lightmap tutorial)
; http://polygone.flipcode.com/tut_lightmap.htm (lightmap tutorial)
; http://www.blackpawn.com/texts/lightmaps/default.html (lightmap packing)
;
;
;
; To Do:
; - Terrain lightmap precision (seems that the shadows are one or two lumels offset from the correct position)
; - Other light types, such as directional and spot
; - Test with complex meshes to see if the surface self shadowing is working with no problems
; - Merge with the Olive's 1.2 version new features (directional light, etc)
; - Easy way to work out the light coefficients
; - Show the percentage statistics on the terrain too
;
;
; History:
; 1.0 (28/11/2002) - Initial version (marcelo@greenlandstudios.com)
;
; 1.1 (30/11/2002) - Generate surfaces with more than one triangle
; Per light attenuation and brightness
; Functions to apply, save and load the lightmap
; Lightmap sharing (starfox)
;
; 1.3b1(23/12/2002) - Different shadow ray checking, now it generates more precise shadows.
; New LM_DRAWSURFS const to paint the surfaces for debbuging purposes
; SaveLightMap() and LoadLightMap() should work in multiple surface meshes now. (surface fingerprint)
; Weld() the mesh to reduce the number of verts (Peter Scheutz and Terabit)
; Shows the percentage, elapsed and approximate remaining time
; Bug fixes (thanks to Olive), optimizations, etc.
;
; 1.4 (21/6/2003) - Bug fixes and extensive checking made this a stable version
; Compresses the lightmaps based on the contrast (thanks again to elias for the idea)
Include "..\..\myprojects\functions\ray_intersect.bb"
; Call example
LMExample()
; Set to True to draw the triangle edges on the texture
Const LM_DRAWTRIS = False
; True to color each surface
Const LM_DRAWSURFS = False
Const LM_DISABLESELFSHADOW = False
; Max polys per surface
Const LM_SURFTRIS = 256
Const LM_SURFADJTRIS = 256
; Num verts per poly
Const LM_VERTS = 2
; Angle between normals tolerance
Const LM_NORMAL_EPSILON# = 0.997
Const LM_NORMAL_EPSILON2# = 0.984
; This moves the lumel pivot away from the surface in the direction of it's normal by this distance.
; Increasing this value will prevent objects which are flush against the floor from darkening lumels around
; their base.
Const LM_LUMEL_PULL_INWARD# = 0.001
; Vertex distance tolerance
Const LM_VERTPOS_EPSILON# = 0.01
Const LM_VERTPOS_EPSILON2# = 0.05
; If the intensity is less that it ignore
Const LM_INTENSITY_EPSILON# = 0.9999 / 255.0
; Mapping plane
Const LMPLANE_XY = 0
Const LMPLANE_XZ = 1
Const LMPLANE_YZ = 2
; Mininum texture size
Const LM_MINTEXSIZE = 2
; Types
Type LMTriangle
; Vertex info
Field OX#[LM_VERTS], OY#[LM_VERTS], OZ#[LM_VERTS]
Field X#[LM_VERTS], Y#[LM_VERTS], Z#[LM_VERTS]
Field U#[LM_VERTS], V#[LM_VERTS]
Field VertIndex[LM_VERTS]
; Normal
Field NX#, NY#, NZ#
; Original surface pointer
Field Surf
; Surface that owns this triangle
Field LMSurf.LMSurface
End Type
Type LMSurface
; Triangle list
Field Tris.LMTriangle[LM_SURFTRIS]
Field NTris%
Field AdjTris.LMTriangle[LM_SURFADJTRIS]
Field NAdjTris%
; Plane
Field NX#, NY#, NZ#
Field Plane%
; UV Bound Box
Field UMin#, UMax#, UDelta#
Field VMin#, VMax#, VDelta#
; UV to worldspace transformations
Field UEdgeX#, UEdgeY#, UEdgeZ#
Field VEdgeX#, VEdgeY#, VEdgeZ#
Field OriginX#, OriginY#, OriginZ#
; Misc
Field Image
Field ImageSize
End Type
; Wrapper to sort the surfaces
Type LMSortedSurface
Field Surf.LMSurface
End Type
; Node for the packer
Type LMImgNode
Field Child.LMImgNode[1]
Field Surf.LMSurface
Field X1%, Y1%
Field X2%, Y2%
End Type
; Global parameters
Type LMParams
Field AmbR, AmbG, AmbB
End Type
; Light
Type LMLight
Field X#, Y#, Z#
Field R#, G#, B#
Field Range#
Field Att#[2]
Field Bright#
Field CastShadows
End Type
; This list contains all entities which should obscure light.
Type LMObscurer
Field Entity
End Type
; Store global parameters
Global g_LMParams.LMParams = Null
; *****************
;
; Public functions
;
; *****************
; Create and setup global parameters
; AmbR, AmbG, AmbB is the ambient light color
Function BeginLightMap(AmbR = 0, AmbG = 0, AmbB = 0)
g_LMParams = New LMParams
g_LMParams\AmbR = AmbR
g_LMParams\AmbG = AmbG
g_LMParams\AmbB = AmbB
End Function
Function LightMapParams(AmbR = 0, AmbG = 0, AmbB = 0)
g_LMParams\AmbR = AmbR
g_LMParams\AmbG = AmbG
g_LMParams\AmbB = AmbB
End Function
; Free parameters and stuff
Function EndLightMap()
If g_LMParams <> Null
; Delete all lights
For Light.LMLight = Each LMLight
Delete Light
Next
Delete g_LMParams
g_LMParams = Null
EndIf
End Function
; Create a new Light for lightmapping, only point lights until now
; x, y, z - world space coordinates
; r, g, b - Red, Green and Blue amounts (0..255)
; range - Maximum distance that the light will affect
; (only clamps the distance, If you want a falloff effect use the attenuation coefficients)
;
; bright - Light brightness
;
; att0, att1, att2 - Coefficients for light attenuation (control the falloff curve)
; lumel attenuation# = 1.0 / (att0 + (att1 * dist) + (att2 * dist^2)
; where dist is the distance from light source to lumel
Function CreateLMLight.LMLight(x#, y#, z#, r#, g#, b#, range# = 0, castshadows = True, bright# = 10.0, att0# = 0, att1# = 1, att2# = 0, Area#=0, AreaQuality=0)
l.LMLight = New LMLight
l\X# = x#
l\Y# = y#
l\Z# = z#
l\R# = r#
l\G# = g#
l\B# = b#
l\Range# = range#
l\Bright# = bright#
l\Att#[0] = att0#
l\Att#[1] = att1#
l\Att#[2] = att2#
l\CastShadows = castshadows
If l\Range# = 0
l\Range# = 9999999.0
EndIf
Return l
End Function
; Apply an lightmap created with LightMapMesh or LightMapTerrain
Function ApplyLightMap(mesh, tex, layer = 4)
If Not tex
Return False
EndIf
EntityFX(mesh, 1)
EntityTexture(mesh, tex, 0, layer)
FreeTexture(tex)
Weld(mesh)
Return True
End Function
; Save to a bmp file and a luv file the information about a lightmapped entity
Function SaveLightMap(mesh, tex, imgfile$, luvfile$)
If Not tex
Return False
EndIf
SaveBuffer(TextureBuffer(tex), imgfile$)
CreateLUVs(mesh, luvfile$, 1)
End Function
; Load an image file and the luv file into the entity
Function LoadLightMap(mesh, imgfile$, luvfile$, layer = 4)
Unweld(mesh)
If FileType(luvfile$)
LoadLUVs(mesh, luvfile$)
EndIf
tex = LoadTexture(imgfile$)
If tex
EntityFX(mesh, 1)
TextureCoords(tex, 1)
EntityTexture(mesh, tex, 0, layer)
Weld(mesh)
Return tex;FreeTexture(tex)
EndIf
End Function
; Assigns a 2nd channel planar mapping coordinates to the mesh and returns a packed texture that can be applied for lightmapping
;
; NOTES:
;
; - The world objects must have EntityPickMode() set to produce shadows
; - The mesh is changed in the process (unwelded)
; - Lumel is the equivalent of an texel, but for lightmaps
; - lumelsize# is the size of the lumel in the world units to control the resolution of the lightmap
; Example: If you use the metric system, a 0.2 lumelsize will create a lumel at each 20 centimeters
; - maxmapsize : maximum texture size that the lightmapper can pack (only used if needed)
; - blurradius : blur the resul image by this radius
;
Function LightMapMesh(mesh, lumelsize# = 0.5, maxmapsize = 1024, blurradius = 1, TotalInfo$ = "")
UnWeld(mesh)
; Run thru all surfaces & triangles storing the info into LMTriangle
For surfcount = 1 To CountSurfaces(mesh)
surf = GetSurface(mesh, surfcount)
For tricount = 0 To CountTriangles(surf) - 1
Tri.LMTriangle = New LMTriangle
For i = 0 To LM_VERTS
vertn = TriangleVertex(surf, tricount, i)
TFormPoint(VertexX(surf, vertn), VertexY(surf, vertn), VertexZ(surf, vertn), mesh, 0)
Tri\X[i] = TFormedX() : Tri\Y[i] = TFormedY() : Tri\Z[i] = TFormedZ()
Tri\OX[i] = VertexX(surf, vertn) : Tri\OY[i] = VertexY(surf, vertn) : Tri\OZ[i] = VertexZ(surf, vertn)
Tri\VertIndex[i] = vertn
Next
Tri\Surf = Surf
GetTriangleNormal(Tri\X[0], Tri\Y[0], Tri\Z[0], Tri\X[1], Tri\Y[1], Tri\Z[1], Tri\X[2], Tri\Y[2], Tri\Z[2])
Tri\NX = TriangleNormalX() : Tri\NY = TriangleNormalY() : Tri\NZ = TriangleNormalZ()
Next
Next
LumelCount = 0
; Create the surfaces
While True
; Find the first unlinked triangle
For Tri.LMTriangle = Each LMTriangle
If Tri\LMSurf = Null
Exit
EndIf
Next
; No more unlinked tris
If Tri = Null
Exit
EndIf
LMSurf.LMSurface = New LMSurface
Tri\LMSurf = LMSurf
LMSurf\Tris[LMSurf\NTris] = Tri
LMSurf\NTris = LMSurf\NTris + 1
; Search for adjacent tri's with the same caracteristics and append to list
; Loop while no poly's get added
While True
bNewPoly = False
For STri.LMTriangle = Each LMTriangle
If STri\LMSurf = Null
; Compare the triangle normal
Ang# = ((STri\NX * Tri\NX) + (STri\NY * Tri\NY) + (STri\NZ * Tri\NZ))
If Ang >= LM_NORMAL_EPSILON
NSharedVerts = 0
; Check if it shares vertices with one of the current surface triangles
For i = 0 To LMSurf\NTris-1
VTri.LMTriangle = LMSurf\Tris[i]
For j = 0 To LM_VERTS
For k = 0 To LM_VERTS
DX# = STri\X[j] - VTri\X[k]
DY# = STri\Y[j] - VTri\Y[k]
DZ# = STri\Z[j] - VTri\Z[k]
Dist# = Sqr(DX*DX + DY*DY + DZ*DZ)
If Dist <= LM_VERTPOS_EPSILON
NSharedVerts = NSharedVerts + 1
Exit
EndIf
Next
Next
Next
If NSharedVerts > 0
STri\LMSurf = LMSurf
LMSurf\Tris[LMSurf\NTris] = STri
LMSurf\NTris = LMSurf\NTris + 1
bNewPoly = True
If LMSurf\NTris > LM_SURFTRIS
Exit
EndIf
EndIf
EndIf
EndIf
Next
If Not bNewPoly
Exit
EndIf
If LMSurf\NTris > LM_SURFTRIS
Exit
EndIf
Wend
; Get the averaged normal
NX# = 0 : NY# = 0 : NZ# = 0
For i = 0 To LMSurf\NTris-1
GetTriangleNormal(LMSurf\Tris[i]\X[0], LMSurf\Tris[i]\Y[0], LMSurf\Tris[i]\Z[0], LMSurf\Tris[i]\X[1], LMSurf\Tris[i]\Y[1], LMSurf\Tris[i]\Z[1], LMSurf\Tris[i]\X[2], LMSurf\Tris[i]\Y[2], LMSurf\Tris[i]\Z[2])
NX = NX + TriangleNormalX()
NY = NY + TriangleNormalY()
NZ = NZ + TriangleNormalZ()
Next
LMSurf\NX = NX / Float(LMSurf\NTris)
LMSurf\NY = NY / Float(LMSurf\NTris)
LMSurf\NZ = NZ / Float(LMSurf\NTris)
; Search for directly adjacent triangles (that can be hidden on self shadow check)
If LM_DISABLESELFSHADOW
For STri.LMTriangle = Each LMTriangle
If STri\LMSurf <> LMSurf
; Compare the triangle normal
Ang# = ((STri\NX * LMSurf\NX) + (STri\NY * LMSurf\NY) + (STri\NZ * LMSurf\NZ))
If Ang >= LM_NORMAL_EPSILON2
NSharedVerts = 0
; Check if it shares vertices with one of the current surface triangles
For i = 0 To LMSurf\NTris-1
VTri.LMTriangle = LMSurf\Tris[i]
For j = 0 To LM_VERTS
For k = 0 To LM_VERTS
DX# = STri\X[j] - VTri\X[k]
DY# = STri\Y[j] - VTri\Y[k]
DZ# = STri\Z[j] - VTri\Z[k]
Dist# = Sqr(DX*DX + DY*DY + DZ*DZ)
If Dist <= LM_VERTPOS_EPSILON2
NSharedVerts = NSharedVerts + 1
Exit
EndIf
Next
Next
Next
If NSharedVerts > 0
LMSurf\AdjTris[LMSurf\NAdjTris] = STri
LMSurf\NAdjTris = LMSurf\NAdjTris + 1
If LMSurf\NAdjTris > LM_SURFADJTRIS
Exit
EndIf
EndIf
EndIf
EndIf
Next
EndIf
LMSetupSurface(LMSurf, lumelsize, blurradius)
LumelCount = LumelCount + LMSurf\ImageSize
Wend
lcount = 0
count = 0
SpdSum# = 0
InitialTime = MilliSecs()
If Not LM_DRAWSURFS
ClsColor(0, 0, 0)
Cls()
Print(TotalInfo$)
Print("Percentage : 0%")
Print("Time : 0s (0s to go)")
Flip()
For LMSurf.LMSurface = Each LMSurface
Time = MilliSecs()
; Create the light texture
LMLightSurface(LMSurf, lumelsize)
; Blur resulting image
If blurradius > 0
LMBlurImage(LMSurf\Image, blurradius)
EndIf
lcount = lcount + LMSurf\ImageSize
count = count + 1
Now = MilliSecs()
Elapsed = Now - Time
If Elapsed > 0
Spd# = Float(LMSurf\ImageSize) / Float(Elapsed) * 1000
SpdSum# = SpdSum# + Spd
EndIf
AvgSpd# = SpdSum / Float(count)
Est = Float(LumelCount - lcount) / AvgSpd#
; Display status
ClsColor(0, 0, 0)
Cls()
Print(TotalInfo$)
Print("Percentage : " + (Float(lcount) / Float(LumelCount) * 100) + "%")
Print("Time : " + ((Now - InitialTime)/1000) + "s (" + Est + "s to go)")
Flip()
Next
Else
SeedRnd(MilliSecs())
EndIf
; First sort it by image size, larger images enter first
For LMSurf.LMSurface = Each LMSurface
; Search for a lower image size
For SLMSurf.LMSortedSurface = Each LMSortedSurface
If SLMSurf\Surf\ImageSize <= LMSurf\ImageSize
Exit
EndIf
Next
NLMSurf.LMSortedSurface = New LMSortedSurface
NLMSurf\Surf = LMSurf
If SLMSurf <> Null
Insert NLMSurf Before SLMSurf
EndIf
Next
; Get the mininum map size possible
lmapsize% = LMPacker_FitTexSize(maxmapsize)
; Pack into a big texture
Tex = LMPacker_Pack(lmapsize%)
; Free temporary stuff
For LMSurf.LMSurface = Each LMSurface
FreeImage(LMSurf\Image)
Delete LMSurf
Next
Delete Each LMSortedSurface
Delete Each LMTriangle
SetBuffer(BackBuffer())
Return Tex
End Function
;
; Same as the lightmapmesh, but for terrains. detail% is the texture map size
;
Function LightMapTerrain(terrain, detail% = 0, blurradius% = 1)
TSize# = TerrainSize(terrain)
If detail = 0
detail = TSize
EndIf
; Get the entity scale
vx# = GetMatElement(terrain, 0, 0)
vy# = GetMatElement(terrain, 0, 1)
vz# = GetMatElement(terrain, 0, 2)
XScale# = Sqr(vx*vx + vy*vy + vz*vz)
vx# = GetMatElement(terrain, 1, 0)
vy# = GetMatElement(terrain, 1, 1)
vz# = GetMatElement(terrain, 1, 2)
YScale# = Sqr(vx*vx + vy*vy + vz*vz)
vx# = GetMatElement(terrain, 2, 0)
vy# = GetMatElement(terrain, 2, 1)
vz# = GetMatElement(terrain, 2, 2)
ZScale# = Sqr(vx*vx + vy*vy + vz*vz)
; Relation between detail and texture size
Scale# = 1
If detail < TSize
Scale# = Float(detail)/Float(TSize)
EndIf
LMSize = detail
Img = CreateImage(LMSize, LMSize)
ImgBuf = ImageBuffer(Img)
SetBuffer(ImgBuf)
; Set the ambient light
ClsColor(g_LMParams\AmbR, g_LMParams\AmbG, g_LMParams\AmbB)
Cls()
ClsColor(0, 0, 0)
LockBuffer(ImgBuf)
LightPivot = CreatePivot()
LumelPivot = CreatePivot()
EntityPickMode(LumelPivot, 1)
EntityRadius(LumelPivot, 0.625)
xpos# = EntityX(terrain) : ypos# = EntityY(terrain) : zpos# = EntityZ(terrain)
For Light.LMLight = Each LMLight
PositionEntity(LightPivot, Light\X, Light\Y, Light\Z)
For z% = 0 To LMSize-1
For x% = 0 To LMSize-1
zp% = TSize - z
y# = TerrainHeight(terrain, x+1, zp)
LumX# = (xpos + Float(x) * XScale) / Scale
LumY# = (ypos + Float(y) * YScale) / Scale
LumZ# = (zpos + Float(zp) * ZScale) / Scale
PositionEntity(LumelPivot, LumX, LumY, LumZ)
Dist# = EntityDistance(LightPivot, LumelPivot)
; If this light can light this lumel
If (Dist <= Light\Range) And (Dist > 0)
LMLightProcess(x, z, Light, LumX, LumY, LumZ, Dist, 1.0, LumelPivot, LightPivot)
EndIf
Next ; x
Next ; z
Next
UnlockBuffer(ImgBuf)
; Blur resulting image
If blurradius > 0
LMBlurImage(Img, blurradius)
EndIf
Tex = CreateTexture(LMSize, LMSize, 512)
CopyRect(0, 0, LMSize, LMSize, 0, 0, ImageBuffer(Img), TextureBuffer(Tex))
TextureCoords(Tex, 1)
ScaleTexture(Tex, TSize, TSize)
FreeImage(Img)
SetBuffer(BackBuffer())
FreeEntity(LightPivot)
FreeEntity(LumelPivot)
Return Tex
End Function
; ******************
;
; Private functions
;
; ******************
; Lightmap packing functions
Function LMPacker_Pack(lmapsize)
Tex = CreateTexture(lmapsize, lmapsize, 512)
SetBuffer(TextureBuffer(Tex))
; Set the ambient light
ClsColor(g_LMParams\AmbR, g_LMParams\AmbG, g_LMParams\AmbB)
Cls()
ClsColor(0, 0, 0)
LMRoot.LMImgNode = New LMImgNode
LMRoot\X1 = 0 : LMRoot\Y1 = 0
LMRoot\X2 = lmapsize : LMRoot\Y2 = lmapsize
LMRoot\Surf = Null
SurfCnt = 0
For SLMSurf.LMSortedSurface = Each LMSortedSurface
; Insert in the best location
Img.LMImgNode = LMPacker_Insert(LMRoot, SLMSurf\Surf)
If Img <> Null
LMSurf.LMSurface = Img\Surf
IW = ImageWidth(LMSurf\Image)
IH = ImageHeight(LMSurf\Image)
If LM_DRAWSURFS
Color(Rand(0,220), Rand(0,220), Rand(0,220))
Rect(Img\X1, Img\Y1, IW, IH, True)
Color(0, 0, 0)
Text(Img\X1 + IW/2, Img\Y1 + IH/2, Handle(LMSurf), True, True)
Else
CopyRect(0, 0, IW, IH, Img\X1, Img\Y1, ImageBuffer(LMSurf\Image), TextureBuffer(Tex))
EndIf
; Scale the original UV's to the new position and scale
DX# = Float(Img\X1) / Float(lmapsize)
DY# = Float(Img\Y1) / Float(lmapsize)
ScaleU# = Float(IW) / Float(lmapsize)
ScaleV# = Float(IH) / Float(lmapsize)
For i = 0 To LMSurf\NTris-1
For j = 0 To LM_VERTS
LMSurf\Tris[i]\U[j] = (LMSurf\Tris[i]\U[j] * ScaleU) + DX
LMSurf\Tris[i]\V[j] = (LMSurf\Tris[i]\V[j] * ScaleV) + DY
VertexTexCoords(LMSurf\Tris[i]\Surf, LMSurf\Tris[i]\VertIndex[j], LMSurf\Tris[i]\U[j], LMSurf\Tris[i]\V[j], 0, 1)
Next
Next
; Draw debug stuff if needed
If LM_DRAWTRIS
; Triangles
Color(255, 255, 255)
For i = 0 To LMSurf\NTris-1
x1% = LMSurf\Tris[i]\U[0] * Float(lmapsize)
y1% = LMSurf\Tris[i]\V[0] * Float(lmapsize)
x2% = LMSurf\Tris[i]\U[1] * Float(lmapsize)
y2% = LMSurf\Tris[i]\V[1] * Float(lmapsize)
Line(x1, y1, x2, y2)
x1% = LMSurf\Tris[i]\U[1] * Float(lmapsize)
y1% = LMSurf\Tris[i]\V[1] * Float(lmapsize)
x2% = LMSurf\Tris[i]\U[2] * Float(lmapsize)
y2% = LMSurf\Tris[i]\V[2] * Float(lmapsize)
Line(x1, y1, x2, y2)
x1% = LMSurf\Tris[i]\U[2] * Float(lmapsize)
y1% = LMSurf\Tris[i]\V[2] * Float(lmapsize)
x2% = LMSurf\Tris[i]\U[0] * Float(lmapsize)
y2% = LMSurf\Tris[i]\V[0] * Float(lmapsize)
Line(x1, y1, x2, y2)
Next
EndIf
SurfCnt = SurfCnt + 1
Else
DebugLog("Lightmap doesn't fit into the maxmapsize, increase the lumelsize or increase the maxmapsize")
Exit
EndIf
Next
TextureCoords(Tex, 1)
SetBuffer(BackBuffer())
For LMNode.LMImgNode = Each LMImgNode
Delete LMNode
Next
Return Tex
End Function
;
; Find of the minimum texture size up to maxmapsize% that will fit all the lightmap images
;
Function LMPacker_FitTexSize%(maxmapsize%)
lmapsize = LM_MINTEXSIZE
While lmapsize <= maxmapsize
LMRoot.LMImgNode = New LMImgNode
LMRoot\X1 = 0 : LMRoot\Y1 = 0
LMRoot\X2 = lmapsize : LMRoot\Y2 = lmapsize
LMRoot\Surf = Null
bFit = True
For SLMSurf.LMSortedSurface = Each LMSortedSurface
Img.LMImgNode = LMPacker_Insert(LMRoot, SLMSurf\Surf)
If Img = Null
bFit = False
Exit
EndIf
Next
For LMNode.LMImgNode = Each LMImgNode
Delete LMNode
Next
If bFit
Return lmapsize
EndIf
lmapsize = lmapsize * 2
Wend
Return maxmapsize
End Function
;
; Recursive function to pack the lightmaps
;
Function LMPacker_Insert.LMImgNode(Node.LMImgNode, LMSurf.LMSurface)
; We are not in a leaf
If (Node\Child[0] <> Null) And (Node\Child[1] <> Null)
; Try first child
NewNode.LMImgNode = LMPacker_Insert(Node\Child[0], LMSurf)
If NewNode <> Null Return NewNode
; No room, use the second
Return LMPacker_Insert(Node\Child[1], LMSurf)
Else
; Already have a lightmap here
If Node\Surf <> Null
If LM_DRAWSURFS
Return Null
EndIf
; If the lightmap is the same image use it
If LMImageAlike(Node\Surf\Image, LMSurf\Image)
Node\Surf = LMSurf
Return Node
Else
Return Null
EndIf
EndIf
IW% = ImageWidth(LMSurf\Image)
IH% = ImageHeight(LMSurf\Image)
NW% = Node\X2 - Node\X1
NH% = Node\Y2 - Node\Y1
; Check if image doesn't fit this node
If (IW > NW) Or (IH > NH)
Return Null
EndIf
; If it fits perfectly
If (IW = NW) And (IH = NH)
Node\Surf = LMSurf
Return Node
EndIf
; We need to spit the node
Node\Child[0] = New LMImgNode
Node\Child[1] = New LMImgNode
DW% = NW - IW
DH% = NH - IH
; Choose the best axis to split
If DW > DH
Node\Child[0]\X1 = Node\X1
Node\Child[0]\Y1 = Node\Y1
Node\Child[0]\X2 = Node\X1 + IW
Node\Child[0]\Y2 = Node\Y2
Node\Child[1]\X1 = Node\X1 + IW
Node\Child[1]\Y1 = Node\Y1
Node\Child[1]\X2 = Node\X2
Node\Child[1]\Y2 = Node\Y2
Else
Node\Child[0]\X1 = Node\X1
Node\Child[0]\Y1 = Node\Y1
Node\Child[0]\X2 = Node\X2
Node\Child[0]\Y2 = Node\Y1 + IH
Node\Child[1]\X1 = Node\X1
Node\Child[1]\Y1 = Node\Y1 + IH
Node\Child[1]\X2 = Node\X2
Node\Child[1]\Y2 = Node\Y2
EndIf
Return LMPacker_Insert(Node\Child[0], LMSurf)
EndIf
End Function
Function LMImageAlike(img1, img2)
;Check if imagess are congruent
width1 = ImageWidth(img1)
width2 = ImageWidth(img2)
If width1 <> width2 Then Return False
height1 = ImageHeight(img1)
height2 = ImageHeight(img2)
If height1 <> height2 Then Return 0
LockBuffer(ImageBuffer(img1))
LockBuffer(ImageBuffer(img2))
For y = 0 To height1-1
For x = 0 To width1-1
rgb1 = ReadPixelFast(x, y, ImageBuffer(img1)) And $FFFFFF
rgb2 = ReadPixelFast(x, y, ImageBuffer(img2)) And $FFFFFF
If rgb1 <> rgb2
UnlockBuffer(ImageBuffer(img1))
UnlockBuffer(ImageBuffer(img2))
Return 0
EndIf
Next
Next
UnlockBuffer(ImageBuffer(img1))
UnlockBuffer(ImageBuffer(img2))
Return True
End Function
Function LMImageMeasureContrast%(img)
;minvalue = 255
;maxvalue = 0
minvalue_r = 255
minvalue_g = 255
minvalue_b = 255
width = ImageWidth(img)
height = ImageHeight(img)
LockBuffer(ImageBuffer(img))
For y = 0 To height-1
For x = 0 To width-1
rgb1 = ReadPixelFast(x, y, ImageBuffer(img)) And $FFFFFF
r1 = (rgb1 Shr 16 And %11111111)
g1 = (rgb1 Shr 8 And %11111111)
b1 = (rgb1 And %11111111)
; If r1 > maxvalue Then maxvalue = r1
; If g1 > maxvalue Then maxvalue = g1
; If b1 > maxvalue Then maxvalue = b1
; If r1 < minvalue Then minvalue = r1
; If g1 < minvalue Then minvalue = g1
; If b1 < minvalue Then minvalue = b1
; (sswift)
; What you really want to measure is the contrast of each channel, and then select the channel with the
; max contrast. What you're doing above would assume that an image that is pure red has a lot of contrast
; because blue and green are 0 and red is 255.
If r1 > maxvalue_r Then maxvalue_r = r1
If g1 > maxvalue_g Then maxvalue_g = g1
If b1 > maxvalue_b Then maxvalue_b = b1
If r1 < minvalue_r Then minvalue_r = r1
If g1 < minvalue_g Then minvalue_g = g1
If b1 < minvalue_b Then minvalue_b = b1
Next
Next
UnlockBuffer(ImageBuffer(img))
;(sswift)
contrast_r = maxvalue_r - minvalue_r
contrast_g = maxvalue_g - minvalue_g
contrast_b = maxvalue_b - minvalue_b
If (contrast_r > contrast_g) And (contrast_r > contrast_b) Then Return contrast_r
If (contrast_g > contrast_r) And (contrast_g > contrast_b) Then Return contrast_g
Return contrast_b
;Return maxvalue - minvalue
End Function
; Setup the surface
; (Map the surface's UV's to a plane aligned with a world axis.)
Function LMSetupSurface.LMSurface(LMSurf.LMSurface, lumelsize#, blurradius#)
; Find out the best plane to map on (which have the largest normal)
NX# = Abs(LMSurf\NX) : NY# = Abs(LMSurf\NY) : NZ# = Abs(LMSurf\NZ)
If (NZ > NX) And (NZ > NY)
LMSurf\Plane = LMPLANE_XY
Else If (NY > NX) And (NY > NZ)
LMSurf\Plane = LMPLANE_XZ
Else
LMSurf\Plane = LMPLANE_YZ
EndIf
Select LMSurf\Plane
Case LMPLANE_XY
For i = 0 To LMSurf\NTris-1
For j = 0 To LM_VERTS
LMSurf\Tris[i]\U#[j] = LMSurf\Tris[i]\X#[j]
LMSurf\Tris[i]\V#[j] = LMSurf\Tris[i]\Y#[j]
Next
Next
Case LMPLANE_XZ
For i = 0 To LMSurf\NTris-1
For j = 0 To LM_VERTS
LMSurf\Tris[i]\U#[j] = LMSurf\Tris[i]\X#[j]
LMSurf\Tris[i]\V#[j] = LMSurf\Tris[i]\Z#[j]
Next
Next
Case LMPLANE_YZ
For i = 0 To LMSurf\NTris-1
For j = 0 To LM_VERTS
LMSurf\Tris[i]\U#[j] = LMSurf\Tris[i]\Y#[j]
LMSurf\Tris[i]\V#[j] = LMSurf\Tris[i]\Z#[j]
Next
Next
End Select
; Measure the UV bound box
LMSurf\UMin = LMSurf\Tris[0]\U[0] : LMSurf\UMax = LMSurf\Tris[0]\U[0]
LMSurf\VMin = LMSurf\Tris[0]\V[0] : LMSurf\VMax = LMSurf\Tris[0]\V[0]
For i = 0 To LMSurf\NTris-1
For j = 0 To LM_VERTS
If LMSurf\Tris[i]\U[j] < LMSurf\UMin Then LMSurf\UMin = LMSurf\Tris[i]\U[j]
If LMSurf\Tris[i]\U[j] > LMSurf\UMax Then LMSurf\UMax = LMSurf\Tris[i]\U[j]
If LMSurf\Tris[i]\V[j] < LMSurf\VMin Then LMSurf\VMin = LMSurf\Tris[i]\V[j]
If LMSurf\Tris[i]\V[j] > LMSurf\VMax Then LMSurf\VMax = LMSurf\Tris[i]\V[j]
Next
Next
; Reduce black borders
; (sswift)
; Multiplying by 3.0 eradicates the light bleeding from adjacent lightmaps, but I don't know why, or what
; effect changing the lumel size will have.
;DT# = lumelsize * Float(blurradius + 1)
;DT# = LumelSize# * Float(BlurRadius# + 1.0) * 3.0
DT# = LumelSize# * Float(BlurRadius# + 5.0)
LMSurf\UMax# = LMSurf\UMax# + DT#
LMSurf\VMax# = LMSurf\VMax# + DT#
LMSurf\UMin# = LMSurf\UMin# - DT#
LMSurf\VMin# = LMSurf\VMin# - DT#
; Bound Box size
LMSurf\UDelta# = LMSurf\UMax# - LMSurf\UMin#
LMSurf\VDelta# = LMSurf\VMax# - LMSurf\VMin#
; Normalize the UV's, making it range from 0.0 to 1.0
For i = 0 To LMSurf\NTris-1
For j = 0 To LM_VERTS
; Translate it to the origin
LMSurf\Tris[i]\U[j] = LMSurf\Tris[i]\U[j] - LMSurf\UMin#
LMSurf\Tris[i]\V[j] = LMSurf\Tris[i]\V[j] - LMSurf\VMin#
; Normalize
LMSurf\Tris[i]\U[j] = LMSurf\Tris[i]\U[j] / LMSurf\UDelta#
LMSurf\Tris[i]\V[j] = LMSurf\Tris[i]\V[j] / LMSurf\VDelta#
Next
Next
;
; Calculate the UV space to world space equations
;
; Distance of the plane
Dist# = -(LMSurf\NX * LMSurf\Tris[0]\X[0] + LMSurf\NY * LMSurf\Tris[0]\Y[0] + LMSurf\NZ * LMSurf\Tris[0]\Z[0])
Local UVX#, UVY#, UVZ#
Local V1X#, V1Y#, V1Z#
Local V2X#, V2Y#, V2Z#
; Messy stuff based on the plane equation: Ax + By + Cz + D = 0
Select LMSurf\Plane
Case LMPLANE_XY
Z# = -(LMSurf\NX * LMSurf\UMin + LMSurf\NY * LMSurf\VMin + Dist) / LMSurf\NZ
UVX# = LMSurf\UMin : UVY# = LMSurf\VMin : UVZ# = Z
Z# = -(LMSurf\NX * LMSurf\UMax + LMSurf\NY * LMSurf\VMin + Dist) / LMSurf\NZ
V1X# = LMSurf\UMax : V1Y# = LMSurf\VMin : V1Z# = Z
Z# = -(LMSurf\NX * LMSurf\UMin + LMSurf\NY * LMSurf\VMax + Dist) / LMSurf\NZ
V2X# = LMSurf\UMin : V2Y# = LMSurf\VMax : V2Z# = Z
Case LMPLANE_XZ
Y# = -(LMSurf\NX * LMSurf\UMin + LMSurf\NZ * LMSurf\VMin + Dist) / LMSurf\NY
UVX# = LMSurf\UMin : UVY# = Y : UVZ# = LMSurf\VMin
Y# = -(LMSurf\NX * LMSurf\UMax + LMSurf\NZ * LMSurf\VMin + Dist) / LMSurf\NY
V1X# = LMSurf\UMax : V1Y# = Y : V1Z# = LMSurf\VMin
Y# = -(LMSurf\NX * LMSurf\UMin + LMSurf\NZ * LMSurf\VMax + Dist) / LMSurf\NY
V2X# = LMSurf\UMin : V2Y# = Y : V2Z# = LMSurf\VMax
Case LMPLANE_YZ
X# = -(LMSurf\NY * LMSurf\UMin + LMSurf\NZ * LMSurf\VMin + Dist) / LMSurf\NX
UVX# = X : UVY# = LMSurf\UMin : UVZ# = LMSurf\VMin
X# = -(LMSurf\NY * LMSurf\UMax + LMSurf\NZ * LMSurf\VMin + Dist) / LMSurf\NX
V1X# = X : V1Y# = LMSurf\UMax : V1Z# = LMSurf\VMin
X# = -(LMSurf\NY * LMSurf\UMin + LMSurf\NZ * LMSurf\VMax + Dist) / LMSurf\NX
V2X# = X : V2Y# = LMSurf\UMin : V2Z# = LMSurf\VMax
End Select
LMSurf\UEdgeX = V1X - UVX : LMSurf\UEdgeY = V1Y - UVY : LMSurf\UEdgeZ = V1Z - UVZ
LMSurf\VEdgeX = V2X - UVX : LMSurf\VEdgeY = V2Y - UVY : LMSurf\VEdgeZ = V2Z - UVZ
LMSurf\OriginX = UVX# : LMSurf\OriginY = UVY# : LMSurf\OriginZ = UVZ#
; Create image size based on the lumel density
LMSizeX% = (LMSurf\UDelta / lumelsize)
LMSizeY% = (LMSurf\VDelta / lumelsize)
; Mininum texture size
If LMSizeX < LM_MINTEXSIZE Then LMSizeX = LM_MINTEXSIZE
If LMSizeY < LM_MINTEXSIZE Then LMSizeY = LM_MINTEXSIZE
LMSurf\Image = CreateImage(LMSizeX, LMSizeY)
LMSurf\ImageSize = LMSizeX * LMSizeY
Return LMSurf
End Function
;
; Create the lightmap texture
;
Function LMLightSurface(LMSurf.LMSurface, lumelsize#)
; Move poly to far away
; (sswift)
; This is unneccessary. Moving the lumel slightly away from the surface prevents any possiblity of the
; polygons in the surface self shadowing themselves, and you WANT the other polygons in a surface to shadow
; the others if you have a large enough angle for polygon combining.
; For i = 0 To LMSurf\NTris-1
; For j = 0 To LM_VERTS
; VertexCoords(LMSurf\Tris[i]\Surf, LMSurf\Tris[i]\VertIndex[j], 99999, 99999, 99999)
; Next
; Next
; For i = 0 To LMSurf\NAdjTris-1
; For j = 0 To LM_VERTS
; VertexCoords(LMSurf\AdjTris[i]\Surf, LMSurf\AdjTris[i]\VertIndex[j], 99999, 99999, 99999)
; Next
; Next
LMSizeX% = ImageWidth(LMSurf\Image)
LMSizeY% = ImageHeight(LMSurf\Image)
ImgBuf = ImageBuffer(LMSurf\Image)
SetBuffer(ImgBuf)
; Set the ambient light
ClsColor(g_LMParams\AmbR, g_LMParams\AmbG, g_LMParams\AmbB)
Cls()
ClsColor(0, 0, 0)
LockBuffer(ImgBuf)
LightPivot = CreatePivot()
LumelPivot = CreatePivot()
;EntityPickMode(LightPivot, 1, False)
;EntityRadius(LightPivot, 0.625) ; Found by trial and error
;EntityRadius(LightPivot, 10.0)
;EntityPickMode(LumelPivot, 1)
;EntityRadius(LumelPivot, 0.625) ; Found by trial and error
;EntityRadius(LumelPivot, 0.3)
;EntityRadius(LumelPivot, lumelsize*2)
; (sswift)
; Calculate one half the width of a lumel, in UV coordinates.
centeruvoffset# = (1.0 / Float(LMSizeX)) / 2.0
For Light.LMLight = Each LMLight
PositionEntity(LightPivot, Light\X, Light\Y, Light\Z)
For Y = 0 To LMSizeY-1
For X = 0 To LMSizeX-1
; Find the UV
; (sswift) Of the TOP LEFT corner of the lumel! Not the center!
U# = Float(x) / Float(LMSizeX)
V# = Float(y) / Float(LMSizeY)
; (sswift)
; Offset the UV coordinates so that we are at the center of the lumel.
U# = U# + CenterUVOffset#
V# = V# + CenterUVOffset#
; Transform to world coordinates
N_UEdgeX# = LMSurf\UEdgeX# * u# : N_UEdgeY# = LMSurf\UEdgeY# * u# : N_UEdgeZ# = LMSurf\UEdgeZ# * u#
N_VEdgeX# = LMSurf\VEdgeX# * v# : N_VEdgeY# = LMSurf\VEdgeY# * v# : N_VEdgeZ# = LMSurf\VEdgeZ# * v#
LumX# = (LMSurf\OriginX# + N_UEdgeX# + N_VEdgeX#)
LumY# = (LMSurf\OriginY# + N_UEdgeY# + N_VEdgeY#)
LumZ# = (LMSurf\OriginZ# + N_UEdgeZ# + N_VEdgeZ#)
PositionEntity(LumelPivot, LumX#, LumY#, LumZ#)
RotateEntity(LumelPivot, 0, 0, 0)
AlignToVector(LumelPivot, LMSurf\UEdgeX#, LMSurf\UEdgeY#, LMSurf\UEdgeZ#, 1)
AlignToVector(LumelPivot, LMSurf\VEdgeX#, LMSurf\VEdgeY#, LMSurf\VEdgeZ#, 3)
Dist# = EntityDistance(LightPivot, LumelPivot)
; If this light can light this lumel
If (Dist# <= Light\Range#) And (Dist# > 0)
; Normal vector between lumel and light
NX# = (LumX# - Light\X#) / Dist#
NY# = (LumY# - Light\Y#) / Dist#
NZ# = (LumZ# - Light\Z#) / Dist#
; Dot product to find the cosine angle between the surface normal and incident light normal
CosAngle# = (NX# * LMSurf\NX#) + (NY# * LMSurf\NY#) + (NZ# * LMSurf\NZ#)
; Poly face front of the light
If CosAngle# > 0
LMLightProcess(x, y, Light, LumX#, LumY#, LumZ#, Dist#, CosAngle#, LumelPivot, LightPivot, LumelSize#)
EndIf
EndIf ; Dist < Light\Range
Next ; x
Next ; y
Next ;Light
UnlockBuffer(ImgBuf)
SetBuffer(BackBuffer())
; Move it back
; For i = 0 To LMSurf\NTris-1
; For j = 0 To LM_VERTS
; VertexCoords(LMSurf\Tris[i]\Surf, LMSurf\Tris[i]\VertIndex[j], LMSurf\Tris[i]\OX[j], LMSurf\Tris[i]\OY[j], LMSurf\Tris[i]\OZ[j])
; Next
; Next
; For i = 0 To LMSurf\NAdjTris-1
; For j = 0 To LM_VERTS
; VertexCoords(LMSurf\AdjTris[i]\Surf, LMSurf\AdjTris[i]\VertIndex[j], LMSurf\AdjTris[i]\OX[j], LMSurf\AdjTris[i]\OY[j], LMSurf\AdjTris[i]\OZ[j])
; Next
; Next
FreeEntity(LightPivot)
FreeEntity(LumelPivot)
If (LMSizeX > 2) And (LMSizeY > 2)
TFormFilter(True)
Contrast = LMImageMeasureContrast(LMSurf\Image)
NSizeX = LMSizeX
NSizeY = LMSizeY
Select True
Case (Contrast <= 4)
NSizeX = 2 : NSizeY = 2
Case (Contrast > 4) And (Contrast <= 20)
NSizeX = NSizeX / 4
NSizeY = NSizeY / 4
Case (Contrast > 20) And (Contrast <= 80)
NSizeX = NSizeX / 2
NSizeY = NSizeY / 2
End Select
If NSizeX < 2 Then NSizeX = 2
If NSizeY < 2 Then NSizeY = 2
If (NSizeX <> LMSizeX) Or (NSizeY <> LMSizeY)
ResizeImage(LMSurf\Image, NSizeX, NSizeY)
EndIf
EndIf
LMSurf\ImageSize = ImageWidth(LMSurf\Image) * ImageHeight(LMSurf\Image)
End Function
; LumelRadius# is in world units, not texture UV.
Function LMLightProcess(x%, y%, Light.LMLight, LumX#, LumY#, LumZ#, Dist#, CosAngle#, LumelPivot, LightPivot, LumelRadius#=0)
; Measure attenuation
Att# = 1.0 / (Light\Att#[0] + (Light\Att#[1] * Dist#) + (Light\Att#[2] * Dist# * Dist#))
; Lambert + attenuation
Intensity# = (Light\Bright# * CosAngle#) * Att#
If (Intensity# < 0.0) Then Intensity# = 0.0
If (Intensity# > 1.0) Then Intensity# = 1.0
If Intensity# > LM_INTENSITY_EPSILON#
NHits = 0
NFired = 0
If Light\CastShadows
NFired = NFired + 1
Obscured = False
For LumelCorner = 1 To 4
Select LumelCorner
; Top left
Case 1
TFormPoint -LumelRadius#, LM_LUMEL_PULL_INWARD#, -LumelRadius#, LumelPivot, 0
; Top right
Case 2
TFormPoint LumelRadius#, LM_LUMEL_PULL_INWARD#, -LumelRadius#, LumelPivot, 0
; Bottom left
Case 3
TFormPoint -LumelRadius#, LM_LUMEL_PULL_INWARD#, LumelRadius#, LumelPivot, 0
; Bottom right
Case 4
TFormPoint LumelRadius#, LM_LUMEL_PULL_INWARD#, LumelRadius#, LumelPivot, 0
End Select
; Get the location of this corner.
Px# = TFormedX#()
Py# = TFormedY#()
Pz# = TFormedZ#()
; Calculate the vector between this corner and the light.
Dx# = Light\X# - Px#
Dy# = Light\Y# - Py#
Dz# = Light\Z# - Pz#
; Check each obscuring object to see if it is blocking the light.
; Exit early if one is found.
; Might get some additional speed with a lot of obscurers by sorting them so that those
; nearest the light source or the lumel are examined first.
For ThisObscurer.LMObscurer = Each LMObscurer
If Ray_Intersect_Mesh(ThisObscurer\Entity, Px#, Py#, Pz#, Dx#, Dy#, Dz#, False, True)
Obscured = True
Exit
EndIf
Next
If Obscured Then Exit
Next
If Not Obscured Then NHits = NHits + 1
Else
; This light does not cast shadows.
NHits = 1
NFired = 1
EndIf
; If this lumel is illuminated...
If (NHits > 0)
Intensity# = Intensity# * Float(NHits) / Float(NFired)
; Add the incident light the pixel
ARGB = ReadPixelFast(x, y) And $FFFFFF
R = (ARGB Shr 16 And %11111111)
G = (ARGB Shr 8 And %11111111)
B = (ARGB And %11111111)
R = R + (Light\R * Intensity)
G = G + (Light\G * Intensity)
B = B + (Light\B * Intensity)
If R > 255 Then R = 255
If G > 255 Then G = 255
If B > 255 Then B = 255
RGB = B Or (G Shl 8) Or (R Shl 16)
WritePixelFast(x, y, RGB)
EndIf
EndIf
End Function
;
; Blur an image using radius
;
Function LMBlurImage(Image, radius = 1)
TmpImg = CopyImage(Image)
TmpBuf = ImageBuffer(TmpImg)
ImgBuf = ImageBuffer(Image)
LockBuffer(ImgBuf)
LockBuffer(TmpBuf)
W% = ImageWidth(Image)
H% = ImageHeight(Image)
; Go thru all the pixels
For y% = 0 To H-1
For x% = 0 To W-1
; Measure the box to get the pixel samples from
ix1 = x - radius
iy1 = y - radius
ix2 = x + radius
iy2 = y + radius
; Prevent it going out of bound
If ix1 < 0 Then ix1 = 0
If iy1 < 0 Then iy1 = 0
If ix2 > W-1 Then ix2 = W-1
If iy2 > H-1 Then iy2 = H-1
r = 0 : g = 0 : b = 0
num = 0
; Run thru all the sampled box
For y2% = iy1 To iy2
For x2% = ix1 To ix2
; Sum the sampled pixel
argb = ReadPixelFast(x2, y2, TmpBuf) And $FFFFFF
ar = (argb Shr 16 And %11111111)
ag = (argb Shr 8 And %11111111)
ab = (argb And %11111111)
r = r + ar
g = g + ag
b = b + ab
num = num + 1
Next
Next
; Get the average value
r = r / num
g = g / num
b = b / num
; Clamp
; (sswift: Impossible to get RGB value greater than 255 with averaging!)
;If r > 255 Then r = 255
;If g > 255 Then g = 255
;If b > 255 Then b = 255
rgb = b Or (g Shl 8) Or (r Shl 16)
WritePixelFast(x, y, rgb, ImgBuf)
Next
Next
UnlockBuffer(TmpBuf)
UnlockBuffer(ImgBuf)
FreeImage(TmpBuf)
End Function
;
; Helper functions
;
Global g_TriNormalX#, g_TriNormalY#, g_TriNormalZ#
Function GetTriangleNormal(x1#, y1#, z1#, x2#, y2#, z2#, x3#, y3#, z3#)
ux# = x1# - x2#
uy# = y1# - y2#
uz# = z1# - z2#
vx# = x3# - x2#
vy# = y3# - y2#
vz# = z3# - z2#
nx# = (uy# * vz#) - (vy# * uz#)
ny# = (uz# * vx#) - (vz# * ux#)
nz# = (ux# * vy#) - (vx# * uy#)
; Normalize it
NormLen# = Sqr((nx*nx) + (ny*ny) + (nz*nz))
If NormLen > 0
nx = nx/NormLen : ny = ny/NormLen: nz = nz/NormLen
Else
nx = 0 : ny = 0 : nz = 1
EndIf
g_TriNormalX = nx
g_TriNormalY = ny
g_TriNormalZ = nz
End Function
Function TriangleNormalX#()
Return g_TriNormalX
End Function
Function TriangleNormalY#()
Return g_TriNormalY
End Function
Function TriangleNormalZ#()
Return g_TriNormalZ
End Function
Function CreateLUVs(mesh,filename$,coordset=1)
file = WriteFile(filename)
WriteInt(file, CountSurfaces(mesh))
For surfcount = 1 To CountSurfaces(mesh)
surf = GetSurface(mesh,surfcount)
fprint = SurfaceFingerPrint(mesh, surf)
WriteInt(file, fprint)
count = CountVertices(surf)
WriteInt(file, count)
For vercount = 0 To count-1
WriteFloat(file,VertexU(surf,vercount,coordset))
WriteFloat(file,VertexV(surf,vercount,coordset))
Next
Next
WriteInt(file, 0)
CloseFile file
End Function
Function LoadLUVs(mesh,filename$,coordset=1)
file = ReadFile(filename)
surfcount = ReadInt(File)
If surfcount <> CountSurfaces(mesh)
DebugLog "Wrong number of surfaces"
CloseFile(file)
Return False
EndIf
fprint = ReadInt(file)
While fprint
surf = FindSurfFingerPrint(mesh, fprint)
If surf
count = ReadInt(file)
For vercount = 0 To count-1
u# = ReadFloat(file)
v# = ReadFloat(file)
VertexTexCoords(surf,vercount,u,v,0,coordset)
Next
Else
DebugLog "Surface fingerprint " + fprint + " not found"
count = ReadInt(file)
For vercount = 0 To count-1
ReadFloat(file):ReadFloat(file)
Next
EndIf
fprint = ReadInt(file)
Wend
CloseFile file
End Function
Function FindSurfFingerPrint(mesh, fingerprint)
For surfcount = 1 To CountSurfaces(mesh)
surf = GetSurface(mesh, surfcount)
If SurfaceFingerPrint(mesh, surf) = fingerprint
Return surf
EndIf
Next
Return 0
End Function
Function SurfaceFingerPrint%(mesh, surf)
tricount = CountTriangles(surf)
CoordSum = 0
For tri = 0 To tricount - 1
For i = 0 To 2
in = TriangleVertex(surf, tri, i)
s$ = VertexX(surf, in)
Pos = Instr(s, ".")
If Pos <> 0
x# = Left(s$, pos + 3)
Else
x# = s$
EndIf
s$ = VertexY(surf, in)
Pos = Instr(s, ".")
If Pos <> 0
y# = Left(s$, pos + 3)
Else
y# = s$
EndIf
s$ = VertexZ(surf, in)
Pos = Instr(s, ".")
If Pos <> 0
z# = Left(s$, pos + 3)
Else
z# = s$
EndIf
CoordSum = CoordSum + Abs(x * 3 * (i+1))
CoordSum = CoordSum + Abs(y * 2 * (i+1))
CoordSum = CoordSum + Abs(z * 1 * (i+1))
Next
Next
Return CoordSum
End Function
Function Unweld(mesh)
;Unweld a mesh, retaining all of its textures coords and textures
For surfcount = 1 To CountSurfaces(mesh)
surf = GetSurface(mesh,surfcount)
count = CountTriangles(surf)
bank = CreateBank((15*count)*4)
For tricount = 0 To count-1
off = (tricount*15)*4
in = TriangleVertex(surf,tricount,0)
x# = VertexX(surf,in):y#=VertexY(surf,in):z#=VertexZ(surf,in)
u# = VertexU(surf,in):v#=VertexV(surf,in)
PokeFloat(bank,off,x)
PokeFloat(bank,off+4,y)
PokeFloat(bank,off+8,z)
PokeFloat(bank,off+12,u)
PokeFloat(bank,off+16,v)
in = TriangleVertex(surf,tricount,1)
x# = VertexX(surf,in):y#=VertexY(surf,in):z#=VertexZ(surf,in)
u# = VertexU(surf,in):v#=VertexV(surf,in)
PokeFloat(bank,off+20,x)
PokeFloat(bank,off+24,y)
PokeFloat(bank,off+28,z)
PokeFloat(bank,off+32,u)
PokeFloat(bank,off+36,v)
in = TriangleVertex(surf,tricount,2)
x# = VertexX(surf,in):y#=VertexY(surf,in):z#=VertexZ(surf,in)
u# = VertexU(surf,in):v#=VertexV(surf,in)
PokeFloat(bank,off+40,x)
PokeFloat(bank,off+44,y)
PokeFloat(bank,off+48,z)
PokeFloat(bank,off+52,u)
PokeFloat(bank,off+56,v)
Next
ClearSurface(surf,True,True)
For tricount = 0 To count-1
off = (tricount*15)*4
x# = PeekFloat(bank,off)
y# = PeekFloat(bank,off+4)
z# = PeekFloat(bank,off+8)
u# = PeekFloat(bank,off+12)
v# = PeekFloat(bank,off+16)
a = AddVertex(surf,x,y,z,u,v)
x# = PeekFloat(bank,off+20)
y# = PeekFloat(bank,off+24)
z# = PeekFloat(bank,off+28)
u# = PeekFloat(bank,off+32)
v# = PeekFloat(bank,off+36)
b = AddVertex(surf,x,y,z,u,v)
x# = PeekFloat(bank,off+40)
y# = PeekFloat(bank,off+44)
z# = PeekFloat(bank,off+48)
u# = PeekFloat(bank,off+52)
v# = PeekFloat(bank,off+56)
c = AddVertex(surf,x,y,z,u,v)
AddTriangle(surf,a,b,c)
Next
FreeBank bank
Next
UpdateNormals mesh
Return mesh
End Function
Dim txv(3)
Type TRIS
Field x0#
Field y0#
Field z0#
Field u0#
Field v0#
Field U20#
Field V20#
Field x1#
Field y1#
Field z1#
Field u1#
Field v1#
Field U21#
Field V21#
Field x2#
Field y2#
Field z2#
Field u2#
Field v2#
Field U22#
Field V22#
Field surface
End Type
Function Weld(mish)
Dim txv(3)
For nsurf = 1 To CountSurfaces(mish)
su=GetSurface(mish,nsurf)
For tq = 0 To CountTriangles(su)-1
txv(0) = TriangleVertex(su,tq,0)
txv(1) = TriangleVertex(su,tq,1)
txv(2) = TriangleVertex(su,tq,2)
vq.TRIS = New TRIS
vq\x0# = VertexX(su,txv(0))
vq\y0# = VertexY(su,txv(0))
vq\z0# = VertexZ(su,txv(0))
vq\u0# = VertexU(su,txv(0),0)
vq\v0# = VertexV(su,txv(0),0)
vq\u20# = VertexU(su,txv(0),1)
vq\v20# = VertexV(su,txv(0),1)
vq\x1# = VertexX(su,txv(1))
vq\y1# = VertexY(su,txv(1))
vq\z1# = VertexZ(su,txv(1))
vq\u1# = VertexU(su,txv(1),0)
vq\v1# = VertexV(su,txv(1),0)
vq\u21# = VertexU(su,txv(1),1)
vq\v21# = VertexV(su,txv(1),1)
vq\x2# = VertexX(su,txv(2))
vq\y2# = VertexY(su,txv(2))
vq\z2# = VertexZ(su,txv(2))
vq\u2# = VertexU(su,txv(2),0)
vq\v2# = VertexV(su,txv(2),0)
vq\u22# = VertexU(su,txv(2),1)
vq\v22# = VertexV(su,txv(2),1)
Next
ClearSurface su
For vq.tris = Each tris
vt1=findvert(su,vq\x0#,vq\y0#,vq\z0#,vq\u0#,vq\v0#,vq\u20#,vq\v20#)
If vt1=-1 Then
vt1=AddVertex(su,vq\x0#,vq\y0#,vq\z0#,vq\u0#,vq\v0#)
VertexTexCoords su,mycount,vq\u20#,vq\v20#,0,1
vt1 = mycount
mycount = mycount +1
EndIf
vt2=findvert(su,vq\x1#,vq\y1#,vq\z1#,vq\u1#,vq\v1#,vq\u21#,vq\v21#)
If Vt2=-1 Then
vt2=AddVertex( su,vq\x1#,vq\y1#,vq\z1#,vq\u1#,vq\v1#)
VertexTexCoords su,mycount,vq\u21#,vq\v21#,0,1
vt2 = mycount
mycount = mycount +1
EndIf
vt3=findvert(su,vq\x2#,vq\y2#,vq\z2#,vq\u2#,vq\v2#,vq\u22#,vq\v22#)
If vt3=-1 Then
vt3=AddVertex(su,vq\x2#,vq\y2#,vq\z2#,vq\u2#,vq\v2#)
VertexTexCoords su,mycount,vq\u22#,vq\v22#,0,1
vt3 = mycount
mycount = mycount +1
EndIf
AddTriangle su,vt1,vt2,vt3
Next
Delete Each tris
mycount=0
Next
End Function
Function findvert(su,x2#,y2#,z2#,u2#,v2#,u22#,v22#)
Local thresh# =0.001
For t=0 To CountVertices(su)-1
If Abs(VertexX(su,t)-x2#)<thresh# Then
If Abs(VertexY(su,t)-y2#)<thresh# Then
If Abs(VertexZ(su,t)-z2#)<thresh# Then
If Abs(VertexU(su,t,0)-u2#)<thresh# Then
If Abs(VertexV(su,t,0)-v2#)<thresh# Then
If Abs(VertexU(su,t,1)-u22#)<thresh# Then
If Abs(VertexV(su,t,1)-v22#)<thresh# Then
Return t
EndIf
EndIf
EndIf
EndIf
EndIf
EndIf
EndIf
Next
Return -1
End Function
;
; Example function
;
; Hold 2th mouse button do turn the cam
; Use the arrrows to move
; Click on a object to lightmap it
; Press F2 to load the saved lightmap
Function LMExample()
Graphics3D 1024, 768, 32, 2
SetBuffer(BackBuffer())
; Create some stuff in the world
camera = CreateCamera()
PositionEntity(camera, 17, 18, 18)
cube1 = CreateCube()
FlipMesh(cube1)
PositionMesh(cube1, 0, 1.0, 0)
ScaleEntity(cube1, 20, 5, 20)
EntityPickMode(cube1, 2)
NameEntity(cube1, "cube1")
PointEntity(camera, cube1) ; Look at the cube
cube2 = CreateCube()
;PositionMesh(cube2, 0, 2, 0)
PositionMesh(cube2, 0, 1, 0)
;PositionMesh(cube2, 0, 0.5, 0)
ScaleEntity(cube2, 2, 2, 2)
EntityPickMode(cube2, 2)
NameEntity(cube2, "cube2")
AmbientLight(50, 50, 50)
light = CreateLight(1)
RotateEntity(light, 45, 30, 0)
ThisObscurer.LMObscurer = New LMObscurer
ThisObscurer\Entity = Cube1
ThisObscurer.LMObscurer = New LMObscurer
ThisObscurer\Entity = Cube2
; Timing control
OldTime% = MilliSecs()
While Not KeyHit(1)
; Time elapsed between last frame
Time% = MilliSecs()
DeltaTime# = Float(Time - OldTime) / 1000 ; in seconds
OldTime% = Time
; Camera movement
CamSpd# = 10 * DeltaTime
MoveEntity(camera, Float(KeyDown(205) - KeyDown(203)) * CamSpd, 0, Float(KeyDown(200) - KeyDown(208)) * CamSpd)
If MouseDown(2)
TurnSpeed# = 0.8
TurnEntity(camera, Float(MouseYSpeed()) * TurnSpeed#, 0, 0, False)
TurnEntity(camera, 0, -Float(MouseXSpeed()) * TurnSpeed#, 0, True)
Else
MouseXSpeed() : MouseYSpeed()
EndIf
; Lightmap the picked entity
If MouseHit(1)
ent = CameraPick(camera, MouseX(), MouseY())
If ent
;EntityPickMode(cube1, 0)
BeginLightMap(40, 40, 40)
CreateLMLight( -8, 3, -8, 219, 219, 255, 0, True, 3)
CreateLMLight( 8, 3, 3, 255, 255, 219, 0, True, 3)
;(mesh, lumelsize# = 0.5, maxmapsize = 1024, blurradius = 1, TotalInfo$ = "")
;tex = LightMapMesh(ent, 0.25, 1024, 1, "Lightmapping " + EntityName(ent))
tex = LightMapMesh(ent, 0.25, 1024, 1, "Lightmapping " + EntityName(ent))
If tex
SaveLightMap(ent, tex, EntityName(ent) + "_lm.bmp", EntityName(ent) + ".luv")
ApplyLightMap(ent, tex)
EndIf
EndLightMap()
;EntityPickMode(cube1, 2)
EndIf
EndIf
If KeyHit(60) ; F2 key
ent = CameraPick(camera, MouseX(), MouseY())
If ent
LoadLightMap(ent, EntityName(ent) + "_lm.bmp", EntityName(ent) + ".luv")
EndIf
EndIf
UpdateWorld()
RenderWorld()
ent = CameraPick(camera, MouseX(), MouseY())
If ent Then Text 0,0, EntityName$(ent)
Flip()
Wend
EndGraphics()
End Function |
Comments
| ||
| cant find the include file |
Code Archives Forum