Code archives/3D Graphics - Misc/YAL improvments
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| Lights now have types, POINT_LIGHT (normal lights), and a new one : SUN_LIGHT | |||||
;
; YAL - Yet Another Lightmapper
; Version: 1.2
;
; 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)
; - An weld(mesh) function that re-weld the vertices that have the same values
; - Other light types, such as directional and spot
;
;
; History:
; 1.0 (28/11/2002) - Initial version (marcelo@greenlandstudios.com)
; 1.1 (30/11/2002) - Generate surfaces with more than one triangle (marcelo)
; Per light attenuation and brightness (marcelo)
; Functions to apply, save and load the lightmap (marcelo)
; Lightmap sharing (starfox)
;Modifications made by O.Arguimbau :
;
; 1.2 (9/12/2002) - Bug correction in surface detection
; - Lights now have types, POINT_LIGHT (normal lights), and a new one : SUN_LIGHT (a directional light without distances considerations)
; - A little bug correction in loadlightmap
; - Modification of the exemple code to show lightmapterrain in action
; - Added the LightMapParams function to modify the light parameters (maybe usefull)
;
;I recommend to calculate objects lightmapping BEFORE adding a Sun_Light because it's VERY slow : Createlmlights(point_lights)..... LightmapMeshes... CreateLmlight(sun_light) Lightmapterrain
;but if you have a powerfull PC...you can try it.
;PS : I choose to upgrade the version to 1.2, maybe the original author have better ideas ??? So Marcelo, it's up to you.
; Call example
LMExample()
Const FLT_MAX = 65535
Const SUN_LIGHT = 0
Const POINT_LIGHT = 1
; Set to True to draw the triangle edges on the texture
Const LM_DRAWTRIS = False
; Max polys per surface
Const LM_SURFTRIS = 256
; Num verts per poly
Const LM_VERTS = 2
; Angle between normals tolerance
Const LM_NORMAL_EPSILON# = 1.0
; Vertex distance tolerance
Const LM_VERTPOS_EPSILON# = 0.01
; 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 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#
; True if the triangle is part of a surface
Field Linked
; Original surface pointer
Field Surf
End Type
Type LMSurface
; Triangle list
Field Tris.LMTriangle[LM_SURFTRIS]
Field NTris%
; 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 TypeLight
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#, bright# = 10.0, att0# = 0, att1# = 1, att2# = 0, typelight = POINT_LIGHT)
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\TypeLight = typelight
l\Att[0] = att0
l\Att[1] = att1
l\Att[2] = att2
Return l
End Function
; Apply an lightmap created with LightMapMesh or LightMapTerrain
Function ApplyLightMap(mesh, tex, layer = 1)
If Not tex
Return False
EndIf
EntityFX(mesh, 1)
EntityTexture(mesh, tex, 0, layer)
FreeTexture(tex)
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 = 1)
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)
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)
SetBuffer(BackBuffer())
Cls
sMsg$="Creating lightmap object "+EntityName(mesh)+"..."
font=LoadFont("Arial",20,True)
SetFont font
Color 250,250,150
Text GraphicsWidth()/2,(GraphicsHeight()/2),sMsg$,True,True
;progress bar
progW=400
progX=(GraphicsWidth()/2)-(progW/2)
progY=(GraphicsHeight()/2)+50
progH=20
Color 0,0,200
Rect progX-4,progY-4,progW+8,progH+8
Flip
UnWeld(mesh)
NbSurf = CountSurfaces(mesh)
; Run thru all surfaces & triangles storing the info into LMTriangle
For surfcount = 1 To NbSurf
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\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
; Create the surfaces
SurfaceCount = 0
While True
LMSurf.LMSurface = New LMSurface
SurfaceCount = SurfaceCount + 1
; Find the first unlinked triangle
For Tri.LMTriangle = Each LMTriangle
If Not Tri\Linked
Exit
EndIf
Next
; No more unlinked tris
If Tri = Null
Exit
EndIf
Tri\Linked = True
LMSurf\Tris[LMSurf\NTris] = Tri
LMSurf\NTris = LMSurf\NTris + 1
; Search for adjacent tri's with the same caracteristics and append to list
; Three waves to assure that all the poly's will be get
For Wave = 1 To 3
For STri.LMTriangle = Each LMTriangle
If Not STri\Linked
; Compare the triangle normal
;Bug ???
; Ang# = Abs((STri\NX * Tri\NX) + (STri\NY * Tri\NY) + (STri\NZ * Tri\NZ))
Ang# = ((STri\NX * Tri\NX) + (STri\NY * Tri\NY) + (STri\NZ * Tri\NZ))
If ACos(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\Linked = True
LMSurf\Tris[LMSurf\NTris] = STri
LMSurf\NTris = LMSurf\NTris + 1
If LMSurf\NTris > LM_SURFTRIS
Exit
EndIf
EndIf
EndIf
EndIf
Next
If LMSurf\NTris > LM_SURFTRIS
Exit
EndIf
Next
LMSetupSurface(LMSurf, lumelsize)
Wend
CurrentCount = 0
For LMSurf.LMSurface = Each LMSurface
CurrentCount = CurrentCount + 1
If KeyHit(1) Then End
; Create the light texture
LMLightSurface(LMSurf, lumelsize)
; Blur resulting image
If blurradius > 0
LMBlurImage(LMSurf\Image, blurradius)
EndIf
;Progression
SetBuffer BackBuffer()
Color 0,0,200
Cls
Rect progX-4,progY-4,progW+8,progH+8
Color 255,0,0
Rect progX,progY,progW/Float(SurfaceCount)*Float(CurrentCount),progH
Color 250,250,150
Text GraphicsWidth()/2,(GraphicsHeight()/2),sMsg$,True,True
Flip
Next
; 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
Return Tex
End Function
;
; Same as the lightmapmesh, but for terrains. detail% is the texture map size
;
Function LightMapTerrain(terrain, detail% = 0, blurradius% = 1)
SetBuffer(BackBuffer())
Cls
sMsg$="Creating terrain lightmap..."
font=LoadFont("Arial",20,True)
SetFont font
Color 250,250,150
Text GraphicsWidth()/2,(GraphicsHeight()/2),sMsg$+" 1",True,True
;progress bar
progW=400
progX=(GraphicsWidth()/2)-(progW/2)
progY=(GraphicsHeight()/2)+50
progH=20
Color 0,0,200
Rect progX-4,progY-4,progW+8,progH+8
Flip
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)
cptlight = 0
For Light.LMLight = Each LMLight
cptlight = cptlight + 1
PositionEntity(LightPivot, Light\X, Light\Y, Light\Z)
;Added by O.Arguimbau : Determination of the light influence (aproximation) with a simple vertical projection (NEED OPTIMISATION/CORRECTION ?)
If (Light\TypeLight = POINT_LIGHT)
zmin = (LMSize-1)-Ceil(((Light\Z+Light\Range)-zpos)/zscale) - 2
zmax = (LMSize-1)-Floor(((Light\Z-Light\Range)-zpos)/zscale) + 2
xmin = Floor(((Light\X-Light\Range)-xpos)/xscale) - 2
xmax = Ceil(((Light\X+Light\Range)-xpos)/xscale) + 2
If zmin < 0 Then zmin = 0
If zmax > LMSize-1 Then zmax = LMSize-1
If xmin < 0 Then xmin = 0
If xmax > LMSize-1 Then xmax = LMSize-1
Else
xmin = 0 : zmin = 0
xmax = LMSize-1 : zmax = LMSize-1
EndIf
For z% = zmin To zmax
If KeyHit(1) Then End
For x% = xmin To xmax
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)
Select Light\TypeLight
;Added by O.Arguimbau : Sun light is a directionnal light, all rays are parallels
Case SUN_LIGHT
;Check for visibility
xi# = EntityX(LumelPivot) : yi# = EntityY(LumelPivot) : zi# = EntityZ(LumelPivot)
PositionEntity LightPivot, Light\X + xi, Light\Y + yi, Light\Z + zi
If EntityVisible(LumelPivot,LightPivot)
Intensity# = Light\Bright
ARGB = ReadPixelFast(x, z) 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, z, RGB)
EndIf
Case POINT_LIGHT
; 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)
EndIf ; Dist < Light\Range
End Select
Next ; x
;Progression
UnlockBuffer(ImgBuf)
SetBuffer(BackBuffer())
ClsColor 0,0,0:Cls
Color 0,0,200
Rect progX-4,progY-4,progW+8,progH+8
Color 255,0,0
Rect progX,progY,progW/Float(zmax)*Float(z+1),progH
Color 250,250,150
Text GraphicsWidth()/2,(GraphicsHeight()/2),sMsg$ + " " + cptLight,True,True
Flip
SetBuffer (ImgBuf)
LockBuffer(ImgBuf)
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))
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)
CopyRect(0, 0, IW, IH, Img\X1, Img\Y1, ImageBuffer(LMSurf\Image), TextureBuffer(Tex))
; 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 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))
rgb2 = ReadPixelFast(x, y, ImageBuffer(img2))
If rgb1 <> rgb2
UnlockBuffer(ImageBuffer(img1))
UnlockBuffer(ImageBuffer(img2))
Return 0
EndIf
Next
Next
UnlockBuffer(ImageBuffer(img1))
UnlockBuffer(ImageBuffer(img2))
Return True
End Function
;
; Setup the surface
;
Function LMSetupSurface.LMSurface(LMSurf.LMSurface, lumelsize#)
; 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)
; 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
DT# = lumelsize
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#
Return LMSurf
End Function
;
; Create the lightmap texture
;
Function LMLightSurface(LMSurf.LMSurface, lumelsize#)
; 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
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(LumelPivot, 1)
EntityRadius(LumelPivot, 0.625) ; Found by trial and error
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
u# = Float(x) / Float(LMSizeX)
v# = Float(y) / Float(LMSizeY)
; 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)
Dist# = EntityDistance(LightPivot, LumelPivot)
Select Light\TypeLight
Case SUN_LIGHT
; Normal vector between lumel and light
NX# = (-Light\X);/Dist ;Sun_light is not affected by distance, maybe i can add a directionnal light that is influenced by distance...
NY# = (-Light\Y);/Dist ;Directionnal_light type maybe :)
NZ# = (-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
NHits = 0
; Center pick
dx# = - Light\X
dy# = - Light\Y
dz# = - Light\Z
If LinePick(Light\X+LumX, Light\Y+LumY, Light\Z+LumZ, dx*FLT_MAX, dy*FLT_MAX, dz*FLT_MAX, 0) = LumelPivot
NHits = NHits + 1
EndIf
If NHits > 0
;
; Add the incident light the pixel
;
; Lambert + Attenuation + Shadow
Intensity# = (Light\Bright * CosAngle)
If Intensity < 0.0 Then Intensity = 0.0
If Intensity > 1.0 Then Intensity = 1.0
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 ; Visible
EndIf ; CosAngle > 0
Case POINT_LIGHT
; 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)
EndIf
EndIf ; Dist < Light\Range
End Select
Next ; x
Next ; y
Next ;Light
UnlockBuffer(ImgBuf)
SetBuffer(BackBuffer())
FreeEntity(LightPivot)
FreeEntity(LumelPivot)
End Function
Function LMLightProcess(x%, y%, Light.LMLight, LumX#, LumY#, LumZ#, Dist#, CosAngle#, LumelPivot)
NHits = 0
; Center pick
dx# = LumX - Light\X
dy# = LumY - Light\Y
dz# = LumZ - Light\Z
If LinePick(Light\X, Light\Y, Light\Z, dx, dy, dz, 0) = LumelPivot
NHits = NHits + 1
EndIf
If NHits > 0
;
; Add the incident light the pixel
;
; Measure attenuation
Att# = 1 / (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
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 ; Visible
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 medium value
r = r / num
g = g / num
b = b / num
; Clamp
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
;
; Starfox's functions (unmodified)
;
Type tris
Field x#[3],y#[3],z#[3]
Field surf,index,mesh
Field ver[3],u#[3],v#[3]
Field tex,size#
End Type
Function CreateLUVs(mesh,filename$,coordset=1)
file = WriteFile(filename)
For surfcount = 1 To CountSurfaces(mesh)
surf = GetSurface(mesh,surfcount)
For vercount = 0 To CountVertices(surf)-1
WriteFloat(file,VertexU(surf,vercount,coordset))
WriteFloat(file,VertexV(surf,vercount,coordset))
Next
Next
CloseFile file
End Function
Function LoadLUVs(mesh,filename$,coordset=1)
file = ReadFile(filename)
For surfcount = 1 To CountSurfaces(mesh)
surf = GetSurface(mesh,surfcount)
For vercount = 0 To CountVertices(surf)-1
u# = ReadFloat(file)
v# = ReadFloat(file)
VertexTexCoords surf,vercount,u,v,0,coordset
Next
Next
CloseFile file
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)
For tricount = 0 To CountTriangles(surf)-1
t.tris = New tris
t\surf = surf : t\mesh = mesh
t\index = tricount
in = TriangleVertex(t\surf,t\index,0)
t\x[1] = VertexX(surf,in) : t\y[1] = VertexY(surf,in)
t\z[1] = VertexZ(surf,in) : t\ver[1] = in
t\u[1] = VertexU(surf,in) : t\v[1] = VertexV(surf,in)
in = TriangleVertex(t\surf,t\index,1)
t\x[2] = VertexX(surf,in) : t\y[2] = VertexY(surf,in)
t\z[2] = VertexZ(surf,in) : t\ver[2] = in
t\u[2] = VertexU(surf,in) : t\v[2] = VertexV(surf,in)
in = TriangleVertex(t\surf,t\index,2)
t\x[3] = VertexX(surf,in) : t\y[3] = VertexY(surf,in)
t\z[3] = VertexZ(surf,in) : t\ver[3] = in
t\u[3] = VertexU(surf,in) : t\v[3] = VertexV(surf,in)
Next
ClearSurface(surf,True,True)
For t.tris = Each tris
t\ver[1] = AddVertex(t\surf,t\x[1],t\y[1],t\z[1],t\u[1],t\v[1])
t\ver[2] = AddVertex(t\surf,t\x[2],t\y[2],t\z[2],t\u[2],t\v[2])
t\ver[3] = AddVertex(t\surf,t\x[3],t\y[3],t\z[3],t\u[3],t\v[3])
AddTriangle(t\surf,t\ver[1],t\ver[2],t\ver[3])
Delete t
Next
Next
UpdateNormals mesh
Return mesh
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 lightmaps
Function LMExample()
Graphics3D(640, 480)
SetBuffer(BackBuffer())
; Create some stuff in the world
camera = CreateCamera()
PositionEntity(camera, 17, 18, 18)
terr=CreateTerrain(512) : PositionEntity terr, -512,0,-512: ScaleEntity terr, 4, 1, 4 : EntityColor terr, 128, 64 , 0
sun=CreateSphere(): PositionEntity sun, -50,60,0
cube1 = CreateCube() : EntityColor cube1, 64,20,20
PositionMesh(cube1, -2, 1.0, 0)
ScaleEntity(cube1, 20, 5, 20)
EntityPickMode(cube1, 2)
NameEntity(cube1, "cube1")
PointEntity(camera, cube1) ; Look at the cube
cube2 = CreateCube() : EntityColor cube2, 20,64,20
PositionMesh(cube2, 0, 1.0, 0)
ScaleEntity(cube2, 2, 2, 2)
EntityPickMode(cube2, 2)
NameEntity(cube2, "cube2")
AmbientLight(50, 50, 50)
light = CreateLight(1)
RotateEntity(light, 45, 30, 0)
; 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
BeginLightMap(40, 40, 40)
CreateLMLight( -50, 60, 0, 255, 255, 10, 1, 1,0,1,0,SUN_LIGHT)
CreateLMLight( 8, 3, 3, 255, 255, 219, 40, 2,0,1,0,POINT_LIGHT)
EntityPickMode(terr, 2)
tex=lightmapterrain(terr)
ApplyLightmap(terr, tex)
EntityPickMode(terr, 0)
tex = LightMapMesh(ent, 0.5)
If tex
SaveLightMap(ent, tex, EntityName(ent) + "_lm.bmp", EntityName(ent) + ".luv")
ApplyLightMap(ent, tex)
EndIf
EndLightMap()
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()
Flip()
Wend
EndGraphics()
End Function |
Comments
None.
Code Archives Forum