Code archives/3D Graphics - Misc/YAL - Yet Another LightMapper (update 1.5 )
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
Another lightmapper, based on starfox's portable lightmapper. :) Features: - Uniform lumel density (specify the lumel size in world units) - Improved lightmap packing (use every free space in the texture) - Smooth shadows (image blurring) - Calculates optimal texture size automatically - Uses vertex normal to smooth the lightmaps ![]() | |||||
; ; YAL - Yet Another Lightmapper ; Version: 1.5 ; ; 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) ; ; 1.5 (15/9/2003) - Fixed the math bug that offset's the lumel wrongly ; - Code to create a one pixel border (to prevent bilinear filtering from catching neighboor pixels) ; - 5 ray's checking per lumel reduces the need for image blurring (softer shadows) ; - Uses vertex normals to smooth the result ; 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 ; Max polys per surface Const LM_SURFTRIS = 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 ; Vertex distance tolerance Const LM_VERTPOS_EPSILON# = 0.01 Const LM_VERTPOS_EPSILON2# = 0.05 Const LM_NORMALOFFSET# = 0.00001 ; If the intensity is less that it ignore Const LM_INTENSITY_EPSILON# = 0.9999 / 255 ; 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 VNX#[LM_VERTS], VNY#[LM_VERTS], VNZ#[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% ; 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 Field ExtraCheck End Type ; Light Type LMLight Field X#, Y#, Z# Field R#, G#, B# Field Pitch#, Yaw#, Roll# Field Range# Field Att#[2] Field Bright# Field Directional Field CastShadows End Type Type LMEntity Field Invisible Field Entity ; Box sizes Field Width#, Height#, Depth# End Type Function AddLMEntity(Entity, Invisible = False) Ent.LMEntity = New LMEntity Ent\Entity = Entity Ent\Invisible = Invisible Ent\Width = MeshWidth(Entity) Ent\Height = MeshHeight(Entity) Ent\Depth = MeshDepth(Entity) Return True End Function Function LMEntityVisible(Entity1, Entity2) End Function ; 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, ExtraCheck = False) g_LMParams = New LMParams g_LMParams\AmbR = AmbR g_LMParams\AmbG = AmbG g_LMParams\AmbB = AmbB g_LMParams\ExtraCheck = ExtraCheck 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 Delete Each LMEntity 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) 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\Directional = False l\CastShadows = castshadows If l\Range = 0 l\Range = 9999999.0 EndIf Return l End Function Function CreateLMLight2.LMLight(x#, y#, z#, pitch#, yaw#, roll#, r#, g#, b#, castshadows = True, bright# = 10.0) l.LMLight = New LMLight l\X = x : l\Y = y : l\Z = z l\R = r : l\G = g : l\B =b l\pitch = pitch : l\yaw = yaw: l\roll = roll l\Bright = bright l\Att[0] = 1 l\Att[1] = 0 l\Att[2] = 0 l\Directional = True 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, imgfile2$ = "") If Not tex Return False EndIf If EntityClass(mesh) = "Terrain" ; Try to blend secondary texture tex2 = LoadTexture(imgfile2$) If tex2 BlendTextureMultiply(tex, tex2) FreeTexture(tex2) EndIf EndIf EntityFX(mesh, 1) EntityTexture(mesh, tex, 0, layer) FreeTexture(tex) If EntityClass(mesh) = "Mesh" Weld(mesh) EndIf 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$) If luvfile$ <> "" CreateLUVs(mesh, luvfile$, 1) EndIf End Function Function BlendTextureMultiply(Tex1, Tex2) w = TextureWidth(Tex1) h = TextureHeight(Tex1) If (w <> TextureWidth(Tex2)) Or (h <> TextureHeight(Tex2)) Return False EndIf buf1 = TextureBuffer(Tex1) buf2 = TextureBuffer(Tex2) LockBuffer(buf1) LockBuffer(buf2) For y = 0 To h - 1 For x = 0 To w - 1 argb1 = ReadPixelFast(x, y, buf1) r1 = (argb1 Shr 16 And %11111111) g1 = (argb1 Shr 8 And %11111111) b1 = (argb1 And %11111111) argb2 = ReadPixelFast(x, y, buf2) r2 = (argb2 Shr 16 And %11111111) g2 = (argb2 Shr 8 And %11111111) b2 = (argb2 And %11111111) ; Multiply fr = Float(r1) * (Float(r2) / 255) fg = Float(g1) * (Float(g2) / 255) fb = Float(b1) * (Float(b2) / 255) ; Write back on tex1 buffer frgb = fb Or (fg Shl 8) Or (fr Shl 16) WritePixelFast(x, y, frgb, buf1) Next Next UnlockBuffer(buf1) UnlockBuffer(buf2) Return Tex1 End Function ; Load an image file and the luv file into the entity Function LoadLightMap(mesh, imgfile$, luvfile$, layer = 4) If EntityClass(mesh) = "Mesh" 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 EndIf EndIf Return False End Function Function LoadTerrainLightmap(mesh, imgfile$, imgfile2$, layer = 4) If EntityClass(mesh) = "Terrain" tex1 = LoadTexture(imgfile$) If tex1 ; Try to blend secondary texture tex2 = LoadTexture(imgfile2$) If tex2 BlendTextureMultiply(tex1, tex2) FreeTexture(tex2) EndIf TSize = TerrainSize(mesh) ScaleTexture(tex1, TSize, TSize) If layer >= 0 EntityFX(mesh, 1) EntityTexture(mesh, tex1, 0, layer) EndIf Return tex1 EndIf EndIf Return False 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\VNX[i] = VertexNX(surf, vertn) : Tri\VNY[i] = VertexNY(surf, vertn) : Tri\VNZ[i] = VertexNZ(surf, vertn) TFormNormal(VertexNX(surf, vertn), VertexNY(surf, vertn), VertexNZ(surf, vertn), mesh, 0) Tri\VNX[i] = TFormedX() : Tri\VNY[i] = TFormedY() : Tri\VNZ[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 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) 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, texsize% = 0, blurradius% = 1, selfshadow = False, TotalInfo$) ClsColor(0, 0, 0) Cls() Print(TotalInfo$) Print("Percentage : 0%") Print("Time : 0s (0s to go)") Flip() TSize# = TerrainSize(terrain) If texsize = 0 texsize = TSize EndIf ; Get the entity scale vx# = GetMatElement(terrain, 0, 0) vy# = GetMatElement(terrain, 0, 1) vz# = GetMatElement(terrain, 0, 2) XSize# = Sqr(vx*vx + vy*vy + vz*vz) * TSize vx# = GetMatElement(terrain, 1, 0) vy# = GetMatElement(terrain, 1, 1) vz# = GetMatElement(terrain, 1, 2) YSize# = Sqr(vx*vx + vy*vy + vz*vz) vx# = GetMatElement(terrain, 2, 0) vy# = GetMatElement(terrain, 2, 1) vz# = GetMatElement(terrain, 2, 2) ZSize# = Sqr(vx*vx + vy*vy + vz*vz) * TSize Img = CreateImage(texsize, texsize) 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() lumelsize# = XSize / Float(texsize) halflumel# = lumelsize/2 xpos# = EntityX(terrain, 1) : ypos# = EntityY(terrain, 1) : zpos# = EntityZ(terrain, 1) scale# = Float(TSize) / Float(texsize) NLights = 0 For Light.LMLight = Each LMLight NLights = NLights + 1 Next LumelCount = (texsize * texsize) * NLights count = 0 : lcount = 0 SpdSum# = 0 InitialTime = MilliSecs() For Light.LMLight = Each LMLight For z% = 0 To texsize-1 Time = MilliSecs() For x% = 0 To texsize-1 tx# = Float(x) * scale tz# = Float(z) * scale y# = TerrainHeight(terrain, tx, tz) LumY# = (ypos + y * YSize) + LM_NORMALOFFSET LumX# = (xpos + Float(x) * lumelsize) + halflumel LumZ# = (zpos + Float(z) * lumelsize) + halflumel PositionEntity(LumelPivot, LumX, LumY, LumZ) lx# = Light\X : ly# = Light\Y : lz# = Light\Z RotateEntity(LightPivot, Light\Pitch, Light\Yaw, Light\Roll) If Light\Directional TFormVector(0, 0, -1, LightPivot, 0) lx# = LumX + TFormedX() * 9999 ly# = LumY + TFormedY() * 9999 lz# = LumZ + TFormedZ() * 9999 Dist# = 0.0001 EndIf PositionEntity(LightPivot, lx, ly, lz) If Not Light\Directional Dist# = EntityDistance(LightPivot, LumelPivot) EndIf ; If this light can light this lumel If (Dist <= Light\Range) And (Dist > 0) If Not selfshadow LMLightProcess(x, z, Light, Dist, 1.0, LumelPivot, LightPivot, lumelsize) Else If LinePick(LumX, LumY + 0.2, LumZ, 0, -1.0, 0, 0) ; Normal vector between lumel and light NX# = (lx - LumX) / Dist NY# = (ly - LumY) / Dist NZ# = (lz - LumZ) / Dist CosAngle# = (NX * PickedNX()) + (NY * PickedNY()) + (NZ * PickedNZ()) If CosAngle > 0 LMLightProcess(x, z, Light, Dist, CosAngle, LumelPivot, LightPivot, lumelsize) EndIf EndIf EndIf Next ; x lcount = lcount + texsize count = count + 1 Now = MilliSecs() Elapsed = Now - Time If Elapsed > 0 Spd# = texsize / Float(Elapsed) * 1000 SpdSum# = SpdSum# + Spd EndIf AvgSpd# = SpdSum / Float(count) Est = Float(LumelCount - lcount) / AvgSpd# ; Display status SetBuffer(BackBuffer()) 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() SetBuffer(ImgBuf) Next ; z Next UnlockBuffer(ImgBuf) ; Blur resulting image If blurradius > 0 LMBlurImage(Img, blurradius) EndIf ScaleImage (Img, 1, -1) HandleImage(Img, 0, 0) Tex = CreateTexture(texsize, texsize, 512) CopyRect(0, 0, texsize, texsize, 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+1, Img\Y1+1, IW-2, IH-2, 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)+1.5) / Float(lmapsize) DY# = (Float(Img\Y1)+1.5) / Float(lmapsize) ScaleU# = (Float(IW)-3) / Float(lmapsize) ScaleV# = (Float(IH)-3) / 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% = Int((LMSurf\Tris[i]\U[0]) * Float(lmapsize)) y1% = Int((LMSurf\Tris[i]\V[0]) * Float(lmapsize)) x2% = Int((LMSurf\Tris[i]\U[1]) * Float(lmapsize)) y2% = Int((LMSurf\Tris[i]\V[1]) * Float(lmapsize)) Line(x1, y1, x2, y2) x1% = Int((LMSurf\Tris[i]\U[1]) * Float(lmapsize)) y1% = Int((LMSurf\Tris[i]\V[1]) * Float(lmapsize)) x2% = Int((LMSurf\Tris[i]\U[2]) * Float(lmapsize)) y2% = Int((LMSurf\Tris[i]\V[2]) * Float(lmapsize)) Line(x1, y1, x2, y2) x1% = Int((LMSurf\Tris[i]\U[2]) * Float(lmapsize)) y1% = Int((LMSurf\Tris[i]\V[2]) * Float(lmapsize)) x2% = Int((LMSurf\Tris[i]\U[0]) * Float(lmapsize)) y2% = Int((LMSurf\Tris[i]\V[0]) * Float(lmapsize)) Line(x1, y1, x2, y2) ;Rect(Img\X1+1, Img\Y1+1, IW-2, IH-2, False) 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_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_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)) 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 End Function ; ; Setup the surface ; 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 ; 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) ; Minimum 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 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 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() CenterU# = (1.0 / Float(LMSizeX)) / 2 CenterV# = (1.0 / Float(LMSizeY)) / 2 For Light.LMLight = Each LMLight For y% = 0 To LMSizeY-1 For x% = 0 To LMSizeX-1 ; Find the UV u# = Float(x) / Float(LMSizeX) + CenterU v# = Float(y) / Float(LMSizeY) + CenterV ; 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) lx# = Light\X : ly# = Light\Y : lz# = Light\Z RotateEntity(LightPivot, Light\Pitch, Light\Yaw, Light\Roll) If Light\Directional TFormVector(0, 0, -1, LightPivot, 0) lx# = LumX + TFormedX() * 9999 ly# = LumY + TFormedY() * 9999 lz# = LumZ + TFormedZ() * 9999 Dist# = 0.0001 EndIf PositionEntity(LightPivot, lx, ly, lz) If Not Light\Directional Dist# = EntityDistance(LightPivot, LumelPivot) EndIf ; If this light can light this lumel If (Dist <= Light\Range) And (Dist > 0) DX# = LumX - lx DY# = LumY - ly DZ# = LumZ - lz TriFound = False For i = 0 To LMSurf\NTris-1 Tri.LMTriangle = LMSurf\Tris[i] x1# = Tri\X[0] : y1# = Tri\Y[0] : z1# = Tri\Z[0] x2# = Tri\X[1] : y2# = Tri\Y[1] : z2# = Tri\Z[1] x3# = Tri\X[2] : y3# = Tri\Y[2] : z3# = Tri\Z[2] If Ray_Intersect_Triangle(lx, ly, lz, DX, DY, DZ, x1, y1, z1, x2, y2, z2, x3, y3, z3, True, False) px# = LumX py# = LumY pz# = LumZ a# = GetTriangleArea(x1, y1, z1, x2, y2, z2, x3, y3, z3) bu# = GetTriangleArea(x2, y2, z2, x3, y3, z3, px, py, pz) / a bv# = GetTriangleArea(x3, y3, z3, x1, y1, z1, px, py, pz) / a bw# = GetTriangleArea(x1, y1, z1, x2, y2, z2, px, py, pz) / a INX# = ((bu * Tri\VNX[0]) + (bv * Tri\VNX[1]) + (bw * Tri\VNX[2])) INY# = ((bu * Tri\VNY[0]) + (bv * Tri\VNY[1]) + (bw * Tri\VNY[2])) INZ# = ((bu * Tri\VNZ[0]) + (bv * Tri\VNZ[1]) + (bw * Tri\VNZ[2])) size# = Sqr(INX*INX + INY*INY + INZ*INZ) INX = INX / size INY = INY / size INZ = INZ / size TriFound = True Exit EndIf Next If Not TriFound INX# = LMSurf\NX INY# = LMSurf\NY INZ# = LMSurf\NZ EndIf ; Normal vector between lumel and light NX# = (lx-LumX) / Dist NY# = (ly-LumY) / Dist NZ# = (lz-LumZ) / Dist ; Dot product to find the cosine angle between the surface normal and incident light normal CosAngle# = (NX * INX) + (NY * INY) + (NZ * INZ) ; Lumel face front of the light If CosAngle > 0 LMLightProcess(x, y, Light, Dist, CosAngle, LumelPivot, LightPivot, lumelsize) EndIf EndIf ; Dist < Light\Range Next ; x Next ; y Next ;Light ; 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 UnlockBuffer(ImgBuf) 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 ; Create one pixel border IW = ImageWidth(LMSurf\Image) IH = ImageHeight(LMSurf\Image) img2 = CreateImage(IW + 2, IH + 2) SetBuffer(ImageBuffer(img2)) ClsColor(255, 0, 255) Cls() CopyRect(0, 0, IW, IH, 1, 1, ImageBuffer(LMSurf\Image), ImageBuffer(img2)) FreeImage(LMSurf\Image) LMSurf\Image = img2 ; Extend the color to the edges LockBuffer() IW = ImageWidth(LMSurf\Image) IH = ImageHeight(LMSurf\Image) For x = 0 To IW - 1 ARGB = ReadPixelFast(x, 1) WritePixelFast(x, 0, ARGB) ARGB = ReadPixelFast(x, IH-2) WritePixelFast(x, IH-1, ARGB) Next For y = 0 To IH - 1 ARGB = ReadPixelFast(1, y) WritePixelFast(0, y, ARGB) ARGB = ReadPixelFast(IW-2, y) WritePixelFast(IW-1, y, ARGB) Next UnlockBuffer() LMSurf\ImageSize = IW * IH SetBuffer(BackBuffer()) End Function Function LMLightProcess(x%, y%, Light.LMLight, Dist#, CosAngle#, LumelPivot, LightPivot, lumelsize#) ; 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 If Intensity > LM_INTENSITY_EPSILON NHits = 0 NFired = 0 If Light\CastShadows ; Center pick NFired = NFired + 1 If EntityVisible(LightPivot, LumelPivot) NHits = NHits + 1 EndIf If g_LMParams\ExtraCheck ; Left up NFired = NFired + 1 MoveEntity(LumelPivot, -lumelsize/3, 0, -lumelsize/3) If EntityVisible(LightPivot, LumelPivot) NHits = NHits + 1 EndIf ; Right up NFired = NFired + 1 MoveEntity(LumelPivot, lumelsize/1.5, 0, 0) If EntityVisible(LightPivot, LumelPivot) NHits = NHits + 1 EndIf ; Right bottom NFired = NFired + 1 MoveEntity(LumelPivot, 0, 0, lumelsize/1.5) If EntityVisible(LightPivot, LumelPivot) NHits = NHits + 1 EndIf ; Left bottom NFired = NFired + 1 MoveEntity(LumelPivot, -lumelsize/1.5, 0, 0) If EntityVisible(LightPivot, LumelPivot) NHits = NHits + 1 EndIf EndIf Else NHits = 1 NFired = 1 EndIf 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 ; Visible 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 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 ; Function GetTriangleArea#(x1#, y1#, z1#, x2#, y2#, z2#, x3#, y3#, z3#) ux# = x2# - x1# uy# = y2# - y1# uz# = z2# - z1# vx# = x3# - x1# vy# = y3# - y1# vz# = z3# - z1# nx# = (uy# * vz#) - (vy# * uz#) ny# = (uz# * vx#) - (vz# * ux#) nz# = (ux# * vy#) - (vx# * uy#) Return Sqr(nx*nx + ny*ny + nz*nz) * 0.5 End Function Global g_TriNormalX#, g_TriNormalY#, g_TriNormalZ# Function GetTriangleNormal(x1#, y1#, z1#, x2#, y2#, z2#, x3#, y3#, z3#) ux# = x2# - x1# uy# = y2# - y1# uz# = z2# - z1# vx# = x3# - x1# vy# = y3# - y1# vz# = z3# - z1# 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) WriteString(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 WriteString(file, "") 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$ = ReadString(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 = ReadString(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) fingerprint$ = "" brush = GetSurfaceBrush(surf) If brush tex = GetBrushTexture(brush) If tex fingerprint = StripPath(TextureName(tex)) + "|" FreeTexture(tex) EndIf FreeBrush(brush) EndIf fingerprint = fingerprint + CountTriangles(surf) + "," +CountVertices(surf) Return fingerprint 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 ;Include "waterplane.bb" ; ; 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(800, 600) SetBuffer(BackBuffer()) ; Create some stuff in the world camera = CreateCamera() PositionEntity(camera, 17, 18, 18) ;wplane.TWaterPlane = CreateWaterPlane("plane.b3d", 256, 2, 2) ;MoveEntity(wplane\Entity, 0, 2, 0) cube1 = CreateCube() FlipMesh(cube1) PositionMesh(cube1, 0, 1.0, 0) ScaleMesh(cube1, 20, 15, 20) EntityPickMode(cube1, 2) NameEntity(cube1, "cube1") PointEntity(camera, cube1) ; Look at the cube cube2 = CreateSphere(10) PositionMesh(cube2, 0, 4, 0) ScaleMesh(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( -8.1, 3, -8, 219, 219, 255, 0, True, 3) CreateLMLight( 8, 3, 3.1, 255, 255, 219, 0, True, 3) If EntityName(ent) = "cube1" tex = LightMapMesh(ent, 0.2, 1024, 1, "Lightmapping " + EntityName(ent)) Else tex = LightMapMesh(ent, 0.02, 1024, 1, "Lightmapping " + EntityName(ent)) EndIf 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 ;UpdateWaterPlane(wplane) UpdateWorld() RenderWorld() Flip() ;RenderWaterPlane(wplane, camera) Wend EndGraphics() End Function ; ------------------------------------------------------------------------------------------------------------------- ; This function returns TRUE if a ray intersects a triangle. ; It also calculates the UV coordinates of said colision as part of the intersection test, ; but does not return them. ; ; V0xyz, V1xyz, and V2xyz are the locations of the three vertices of the triangle. ; ; These vertices should be wound in CLOCKWISE order. By clockwise I mean that if you face the front side of the ; trianngle, the vertcies go around the trangle from V0 to V1 to V2 in a clockwise direction. This is the same ; as Blitz, so just pass the vertcies for a triangle in the same order that Blitz does. ; ; The UV's generated by the function are set up as follows: ; V0 is the location of UV(0,0) ; V1 is the location of UV(0,1) ; V2 is the location of UV(1,0) ; ; This is useful to know if you want to know the exact location in texture space of the collision. ; You can easily modify the function to return the values of T#, U#, and V#. ; ; Pxyz is a the start of the line. ; Triangles which are "behind" this point are ignored. ; "Behind" is defined as the direction opposite that which Dxyz points in. ; ; Dxyz is a vector providing the slope of the line. ; Dxyz does not have to be normalized. ; ; If Extend_To_Infinity is set to false, then the length of Dxyz is how far the ray extends. ; So if you want an endpoint on your ray beyond which no triangles will be detected, subtract the position ; of Pxyz from your endpoint's position, and pass that ato the function as Dxyz. Ie: (Dx = P2x-P1x) ; ; If Cull_Backfaces is set to true, then if the specified ray passes through the triangle from it's back side, then ; it will not register that it hit that triangle. ; ------------------------------------------------------------------------------------------------------------------- Function Ray_Intersect_Triangle(Px#, Py#, Pz#, Dx#, Dy#, Dz#, V0x#, V0y#, V0z#, V1x#, V1y#, V1z#, V2x#, V2y#, V2z#, Extend_To_Infinity=True, Cull_Backfaces=False) ; crossproduct(b,c) = ; ax = (by * cz) - (cy * bz) ; ay = (bz * cx) - (cz * bx) ; az = (bx * cy) - (cx * by) ; dotproduct(v,q) = ; (vx * qx) + (vy * qy) + (vz * qz) ; DP = 1 = Vectors point in same direction. ( 0 degrees of seperation) ; DP = 0 = Vectors are perpendicular to one another. ( 90 degrees of seperation) ; DP = -1 = Vectors point in opposite directions. (180 degrees of seperation) ; ; The dot product is also reffered to as "the determinant" or "the inner product" ; Calculate the vector that represents the first side of the triangle. E1x# = V2x# - V0x# E1y# = V2y# - V0y# E1z# = V2z# - V0z# ; Calculate the vector that represents the second side of the triangle. E2x# = V1x# - V0x# E2y# = V1y# - V0y# E2z# = V1z# - V0z# ; Calculate a vector which is perpendicular to the vector between point 0 and point 1, ; and the direction vector for the ray. ; Hxyz = Crossproduct(Dxyz, E2xyz) Hx# = (Dy# * E2z#) - (E2y# * Dz#) Hy# = (Dz# * E2x#) - (E2z# * Dx#) Hz# = (Dx# * E2y#) - (E2x# * Dy#) ; Calculate the dot product of the above vector and the vector between point 0 and point 2. A# = (E1x# * Hx#) + (E1y# * Hy#) + (E1z# * Hz#) ; If we should ignore triangles the ray passes through the back side of, ; and the ray points in the same direction as the normal of the plane, ; then the ray passed through the back side of the plane, ; and the ray does not intersect the plane the triangle lies in. If (Cull_Backfaces = True) And (A# >= 0) Then Return False ; If the ray is almost parralel to the plane, ; then the ray does not intersect the plane the triangle lies in. If (A# > -0.00001) And (A# < 0.00001) Then Return False ; Inverse Determinant. (Dot Product) ; (Scaling factor for UV's?) F# = 1.0 / A# ; Calculate a vector between the starting point of our ray, and the first point of the triangle, ; which is at UV(0,0) Sx# = Px# - V0x# Sy# = Py# - V0y# Sz# = Pz# - V0z# ; Calculate the U coordinate of the intersection point. ; ; Sxyz is the vector between the start of our ray and the first point of the triangle. ; Hxyz is the normal of our triangle. ; ; U# = F# * (DotProduct(Sxyz, Hxyz)) U# = F# * ((Sx# * Hx#) + (Sy# * Hy#) + (Sz# * Hz#)) ; Is the U coordinate outside the range of values inside the triangle? If (U# < 0.0) Or (U# > 1.0) ; The ray has intersected the plane outside the triangle. Return False EndIf ; Not sure what this is, but it's definitely NOT the intersection point. ; ; Sxyz is the vector from the starting point of the ray to the first corner of the triangle. ; E1xyz is the vector which represents the first side of the triangle. ; The crossproduct of these two would be a vector which is perpendicular to both. ; ; Qxyz = CrossProduct(Sxyz, E1xyz) Qx# = (Sy# * E1z#) - (E1y# * Sz#) Qy# = (Sz# * E1x#) - (E1z# * Sx#) Qz# = (Sx# * E1y#) - (E1x# * Sy#) ; Calculate the V coordinate of the intersection point. ; ; Dxyz is the vector which represents the direction the ray is pointing in. ; Qxyz is the intersection point I think? ; ; V# = F# * DotProduct(Dxyz, Qxyz) V# = F# * ((Dx# * Qx#) + (Dy# * Qy#) + (Dz# * Qz#)) ; Is the V coordinate outside the range of values inside the triangle? ; Does U+V exceed 1.0? If (V# < 0.0) Or ((U# + V#) > 1.0) ; The ray has intersected the plane outside the triangle. Return False ; The reason we check U+V is because if you imagine the triangle as half a square, U=1 V=1 would ; be in the lower left hand corner which would be in the lower left triangle making up the square. ; We are looking for the upper right triangle, and if you think about it, U+V will always be less ; than or equal to 1.0 if the point is in the upper right of the triangle. EndIf ; Calculate the distance of the intersection point from the starting point of the ray, Pxyz. ; This distance is scaled so that at Pxyz, the start of the ray, T=0, and at Dxyz, the end of the ray, T=1. ; If the intersection point is behind Pxyz, then T will be negative, and if the intersection point is ; beyond Dxyz then T will be greater than 1. T# = F# * ((E2x# * Qx#) + (E2y# * Qy#) + (E2z# * Qz#)) ; If the triangle is behind Pxyz, ignore this intersection. ; We want a directional ray, which only intersects triangles in the direction it points. If (T# < 0) Then Return False ; If the plane is beyond Dxyz, amd we do not want the ray to extend to infinity, then ignore this intersection. If (Extend_To_Infinity = False) And (T# > 1) Return False ; The ray intersects the triangle! Return True End Function ; Returns the file from the specifed path Function StripPath$(path$) For a = Len(path$) To 1 Step -1 byte$ = Mid(path$,a,1) If byte$ = "\" Return Right(path$,Len(path$)-a) EndIf Next Return path$ End Function |
Comments
None.
Code Archives Forum