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