Code archives/File Utilities/SaveImageDDS function
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
| Code from my SaveImageDDS thread. This function supports saving to dxt1, dxt3 and dxt5 as well as 24-bit uncompressed. It also seems to work for dxt2 and dxt4, although probably not quite right. It uses an optional alpha image map to determine the alpha texels. This allows you to have full control over things. If you don't want to use an alpha map you can just omit the alpha parameter. Updated by Tom C. to support mipmaps! | |||||
;SaveImageDDS example code
Graphics3D 640,480,0,2 ;Setup the scene
AppTitle "Save Image DDS"
SetBuffer BackBuffer()
camera=CreateCamera()
light=CreateLight()
RotateEntity light,45,45,0
image=MakeTestImage(128,128) ;Create a test image
;image=LoadImage("test.bmp") ;Alternatively load a bmp
;image=LoadImage("test.png") ;Alternatively load a png
;image=LoadImage("test.jpg") ;Alternatively load a jpg
alpha=MakeTestAlpha(ImageWidth(image),ImageHeight(image)) ;A test alpha
filename$="test.dds" ;DDS Filename
format=5 ;Set DDS format
;alpha=0 ;If we don't want an alpha channel set "alpha" to 0
ok=SaveImageDDS(image,filename$,format,alpha)
cube=CreateCube() ;Create a cube to texture
PositionEntity cube,0,0,4
If format=1 Then flags=1+4 Else flags=1+2 ;Set either masked/alpha flags
tex=LoadTexture(filename$,flags) ;Now load the new DDS file
If format=1 Or format=3 Or format=5
EntityTexture cube,tex,0,0 ;Only texture cube if valid format
EndIf
cube2=CreateCube() ;Create a cube behind the first cube
PositionEntity cube2,0,2,8
While Not KeyHit(1) ;Main loop
RenderWorld()
TurnEntity cube,0.4,0.3,0.2 ;Rotate the cubes
TurnEntity cube2,-0.4,-0.3,-0.2
If space ;If space key was hit
DrawImage image,50,50 ;Draw image
If alpha DrawImage alpha,ImageWidth(image)+100,50 ;Draw alpha image
EndIf
If KeyHit(57) Then space=Not space ;Space key
Text 0,0,"Hit space to show image and alpha maps"
Text 0,20,"ok="+ok+" file="+filename+" format="+format+" alpha="+alpha
Flip
Wend
Function MakeTestImage(width,height)
Local image,x,y,rgb
image=CreateImage(width,height)
LockBuffer(ImageBuffer(image))
For y=0 To ImageHeight(image)-1
For x=0 To ImageWidth(image)-1
rgb=y+(y*256)+(x*256^2) ;Gradient color
WritePixelFast x,y,rgb,ImageBuffer(image)
Next
Next
UnlockBuffer(ImageBuffer(image))
SetBuffer ImageBuffer(image)
Color 255,255,255 : Oval 40,40,30,30
Color 0,0,0 : Text 50,50,"DXTC" : Color 255,255,255
SetBuffer BackBuffer()
Return image
End Function
Function MakeTestAlpha(width,height)
Local alpha,x,y,rgb
alpha=CreateImage(width,height)
LockBuffer(ImageBuffer(alpha))
For y=0 To ImageHeight(alpha)-1
For x=0 To ImageWidth(alpha)-1
rgb=(y*2)+((y*2)*256)+((y*2)*256^2) ;Grayscale
If Not x Mod 8 Then rgb=$7F7F7F ;Grid lines
If Not y Mod 8 Then rgb=$7F7F7F
If Not x Mod 16 Then rgb=$FFFFFF
If Not y Mod 16 Then rgb=$FFFFFF
WritePixel x,y,rgb,ImageBuffer(alpha)
Next
Next
UnlockBuffer(ImageBuffer(alpha))
Return alpha
End Function
;SaveImageDDS, by markcw on 13 Sep 06
;MipMap support, by Tom C. on 12 Jul 07
;Description:
;This function works like SaveImage except it saves DDS files.
;Set the dxt mode with the format parameter 1-5 (or 0 for uncompressed RGB only)
;the alphaimg parameter is for an alpha image.
;If you omit it (or set it to 0) you get no alpha.
;Of course you can use the same image handle for alpha image
;the last optional parameter indicates a mipmap generation,
;0 for all possible mipmaps, 1 for only the main image (default)
;Return value is False if fails, True if succeeds.
Function SaveImageDDS(image,filename$,format,alphaimg=0,mipmaps=1)
;image=image handle, filename$, format=optional compression format 0..5
;alphaimg=optional alpha image handle, zero if no alpha required
;mipmaps=count of mipmaps, if given the main picture will scaled down and saved to the file
;Uses MakeEmptyAlpha, MAX, MakeFourCC, ColorHighest, ColorTotal, Color565
Local width=ImageWidth(image)
Local height=ImageHeight(image)
Local imagebuf,alphabuf
;DXT3/5, if no alpha image then create empty white
If alphaimg=0 And format<>1 Then
alphaimg=MakeEmptyAlpha(width,height)
alphaimg_created=True
EndIf
;Determine mipmap count
If mipmaps=0 Then
c=MAX(width,height)
mipmaps=Log(c)/Log(2)+1
EndIf
Local dwwidth,dwheight,flags1,flags2,caps1,caps2,bpp,pitch,sizebytes,w,h,mipsize
Local bsize,bindex,fourcc,hdds,i,x,y,offset,mipoffset,ix,iy,argb
Local color0,color1,color2,color3,color4,color5,color6,color7
Local d0,d1,d2,d3,d4,d5,d6,d7,texel,file
Local sclX#,sclY#
;dwFlags constants
Local DDSD_CAPS=$00000001,DDSD_HEIGHT=$00000002,DDSD_WIDTH=$00000004
Local DDSD_PITCH=$00000008,DDSD_PIXELFORMAT=$00001000
Local DDSD_MIPMAPCOUNT=$00020000,DDSD_LINEARSIZE=$00080000
Local DDSD_DEPTH=$00800000,DDPF_ALPHAPIXELS=$00000001
Local DDPF_FOURCC=$00000004,DDPF_RGB=$00000040
;dwCaps1 constants
Local DDSCAPS_COMPLEX=$00000008,DDSCAPS_TEXTURE=$00001000
Local DDSCAPS_MIPMAP=$00400000
;dwCaps2 constants
Local DDSCAPS2_CUBEMAP=$00000200,DDSCAPS2_CUBEMAP_POSITIVEX=$00000400
Local DDSCAPS2_CUBEMAP_NEGATIVEX=$00000800
Local DDSCAPS2_CUBEMAP_POSITIVEY=$00001000
Local DDSCAPS2_CUBEMAP_NEGATIVEY=$00002000
Local DDSCAPS2_CUBEMAP_POSITIVEZ=$00004000
Local DDSCAPS2_CUBEMAP_NEGATIVEZ=$00008000,DDSCAPS2_VOLUME=$00200000
;Calculate DWORD-aligned width and height, multiple of 4
dwwidth=(width+3)/4*4
dwheight=(height+3)/4*4
;Default flags for all formats
flags1=DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH Or DDSD_PIXELFORMAT
caps1=DDSCAPS_TEXTURE
If format<=0 Or format>5 ;Uncompressed
flags1=flags1 Or DDSD_PITCH
flags2=DDPF_RGB
bpp=24
;Determine Size of Bytes for each mipmap and add
w=dwwidth:h=dwheight
For i=1 To mipmaps
mipsize=MAX(1,w)*(bpp/8) ;DWORD-aligned scanline
sizebytes=sizebytes+mipsize*h
w=MAX(1,w/2):h=MAX(1,h/2)
;pitch
If i=1 Then pitch=mipsize
Next
Else ;Compressed
flags1=flags1 Or DDSD_LINEARSIZE
flags2=DDPF_FOURCC
;Determine Size of Bytes for each mipmap and add
If format>1 Then bytesC=16 Else bytesC=8
w=dwwidth:h=dwheight
For i=1 To mipmaps
mipsize=(MAX(1,w/4)*MAX(1,h/4))*bytesC
sizebytes=sizebytes+mipsize
w=MAX(1,w/2):h=MAX(1,h/2)
;Linear size
If i=1 Then pitch=mipsize
Next
;pitch=sizebytes
bsize=2 : bindex=0 : If format>1 Then bsize=4 : bindex=8 ;block values
If format=1 Then fourcc=MakeFourCC("D","X","T","1")
If format=2 Then fourcc=MakeFourCC("D","X","T","2")
If format=3 Then fourcc=MakeFourCC("D","X","T","3")
If format=4 Then fourcc=MakeFourCC("D","X","T","4")
If format=5 Then fourcc=MakeFourCC("D","X","T","5")
EndIf
hdds=CreateBank(128+sizebytes) ;Bank to store DDS
;Magic Value, DWORD
PokeInt hdds,0,MakeFourCC("D","D","S"," ") ;dwMagic, "DDS "
;Surface Format Header, DDSURFACEDESC2 structure
PokeInt hdds,4,124 ;dwSize, sizeof(DDSURFACEDESC2)
PokeInt hdds,8,flags1 ;dwFlags, flags to indicate valid fields
PokeInt hdds,12,dwheight ;dwHeight, image height in pixels
PokeInt hdds,16,dwwidth ;dwWidth, image width in pixels
PokeInt hdds,20,pitch ;dwPitchOrLinearSize, pitch or linear size
PokeInt hdds,24,0 ;dwDepth, volume textures not supported until DX 8.0
PokeInt hdds,28,mipmaps ;dwMipMapCount, for items with mipmap levels
For i=1 To 11
PokeInt hdds,(i*4)+28,0 ;dwReserved[11]
Next
;DDPIXELFORMAT structure
PokeInt hdds,76,32 ;dwSize, sizeof(DDPIXELFORMAT)
PokeInt hdds,80,flags2 ;dwFlags, flags to indicate valid fields
PokeInt hdds,84,fourcc ;dwFourCC
PokeInt hdds,88,bpp ;dwRGBBitCount
PokeInt hdds,92,$00FF0000 ;dwRBitMask
PokeInt hdds,96,$0000FF00 ;dwGBitMask
PokeInt hdds,100,$000000FF ;dwBBitMask
PokeInt hdds,104,$FF000000 ;dwRGBAlphaBitMask
;DDCAPS2 structure
PokeInt hdds,108,caps1 ;dwCaps1, flags to indicate valid fields
PokeInt hdds,112,caps2 ;dwCaps2, flags to indicate valid fields
For i=1 To 2
PokeInt hdds,(i*4)+112,0 ;dwReserved[2]
Next
PokeInt hdds,124,0 ;dwReserved2
mipoffset=128 ;default Offset after Header
;Main Surface Data, BYTE bData1[] and Attached Surfaces Data for MIPMAPS
If format<=0 Or format>5 ;uncompressed, 24-bit
For mips=1 To mipmaps ;Loop of optional mipmap count
If mips>1 Then
;Store offset
mipoffset=offset+pitch
;Half of dimension
w=MAX(1,width/2):h=MAX(1,height/2)
sclX#=Float#(w)/Float#(width):sclY#=Float#(h)/Float#(height)
ScaleImage image,sclX#,sclY#
width=w:height=h
pitch=width*(bpp/8)
EndIf
;Create Buffer
imagebuf=ImageBuffer(image)
LockBuffer(imagebuf)
For y=0 To height-1
offset=mipoffset+pitch*y ;next DWORD-aligned scanline
For x=0 To width-1
argb=ReadPixelFast(x,y,imagebuf)
PokeByte hdds,offset+(x*3),argb And $000000FF ;b
PokeByte hdds,offset+(x*3)+1,(argb And $0000FF00) Shr 8 ;g
PokeByte hdds,offset+(x*3)+2,(argb And $00FF0000) Shr 16 ;r
Next
Next
UnlockBuffer(imagebuf)
Next
Else ;Compressed
For mips=1 To mipmaps ;Loop of optional mipmap count
If mips>1 Then
;Store offset
mipoffset=offset+dwwidth*bsize
;Half of dimension
w=MAX(1,width/2):h=MAX(1,height/2)
sclX#=Float#(w)/Float#(width):sclY#=Float#(h)/Float#(height)
ScaleImage image,sclX#,sclY#
;If not the same image-handle scale the alpha image too
If image<>alphaimg And alphaimg>0 Then ScaleImage alphaimg,sclX#,sclY#
width=w:height=h
;Calculate DWORD-aligned width and height, multiple of 4
dwwidth=(width+3)/4*4:dwheight=(height+3)/4*4
EndIf
;Create Buffer
imagebuf=ImageBuffer(image)
If alphaimg Then alphabuf=ImageBuffer(alphaimg)
LockBuffer(imagebuf)
LockBuffer(alphabuf)
For y=0 To dwheight-1 Step 4
offset=mipoffset+MAX(1,dwwidth/4)*(y*bsize) ;Next block-aligned scanline
For x=0 To dwwidth-1 Step 4
If format=2 Or format=3 ;DXT2,DXT3
;Find color in alpha block and set each alpha texel
For iy=0 To 3
For ix=0 To 3
If x+ix<width And y+iy<height ;Not out of bounds
argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
Else
argb=0 ;Black
EndIf
i=ColorHighest(argb)/17 : If i>15 Then i=15 ;Alpha color 0..15
texel=PeekShort(hdds,offset+(x*bsize)+(iy*2)) Or (i Shl ix*4)
PokeShort hdds,offset+(x*bsize)+(iy*2),texel ;wAlphaTexels[4]
Next
Next
EndIf
If format=4 Or format=5 ;DXT4,DXT5
;Find highest and lowest colors in alpha block
color0=0 : color1=$FFFFFFFF ;color0 highest
For iy=0 To 3
For ix=0 To 3
If x+ix<width And y+iy<height ;Not out of bounds
argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
If ColorHighest(argb)>ColorHighest(color0) Then color0=argb
If ColorHighest(argb)<ColorHighest(color1) Then color1=argb
EndIf
Next
Next
;Make sure color0 is the highest
If color1>color0 Then
i=color0 : color0=color1 : color1=i ;Switch order
EndIf
PokeByte hdds,offset+(x*bsize),ColorHighest(color0) ;bAlpha0
PokeByte hdds,offset+(x*bsize)+1,ColorHighest(color1) ;bAlpha1
;Set each alpha texel in block to closest alpha
color0=ColorHighest(color0) : color1=ColorHighest(color1)
For iy=0 To 3
For ix=0 To 3
If x+ix<width And y+iy<height ;Not out of bounds
argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
Else
argb=0 ;black
EndIf
If color0>color1 ;8-alpha block
color2=((6*color0)+color1)/7
color3=((5*color0)+(2*color1))/7
color4=((4*color0)+(3*color1))/7
color5=((3*color0)+(4*color1))/7
color6=((2*color0)+(5*color1))/7
color7=(color0+(6*color1))/7
Else ;6-alpha block
color2=((4*color0)+color1)/5
color3=((3*color0)+(2*color1))/5
color4=((2*color0)+(3*color1))/5
color5=(color0+(4*color1))/5
color6=0
color7=255
EndIf
d0=Abs(color0-ColorHighest(argb)) ;Get differences
d1=Abs(color1-ColorHighest(argb))
d2=Abs(color2-ColorHighest(argb))
d3=Abs(color3-ColorHighest(argb))
d4=Abs(color4-ColorHighest(argb))
d5=Abs(color5-ColorHighest(argb))
d6=Abs(color6-ColorHighest(argb))
d7=Abs(color7-ColorHighest(argb))
i=0 : If d1<d0 Then d0=d1 : i=1 ;Find closest color
If d2<d0 Then d0=d2 : i=2
If d3<d0 Then d0=d3 : i=3
If d4<d0 Then d0=d4 : i=4
If d5<d0 Then d0=d5 : i=5
If d6<d0 Then d0=d6 : i=6
If d7<d0 Then d0=d7 : i=7
If iy<2 ;Upper 24bit-block
texel=PeekInt(hdds,offset+(x*bsize)+2) And $00FFFFFF
If iy=0 Then texel=texel Or (i Shl (ix*3))
If iy=1 Then texel=texel Or (i Shl ((ix*3)+12))
PokeInt hdds,offset+(x*bsize)+2,texel And $00FFFFFF
Else ;Lower 24bit-block
texel=PeekInt(hdds,offset+(x*bsize)+5) And $00FFFFFF
If iy=2 Then texel=texel Or (i Shl (ix*3))
If iy=3 Then texel=texel Or (i Shl ((ix*3)+12))
PokeInt hdds,offset+(x*bsize)+5,texel And $00FFFFFF
EndIf
Next
Next
EndIf
;Find highest and lowest colors in texel block
;Better algorithm might be to find the most common highest/lowest colors
color0=0 : color1=$FFFFFFFF ;color0 highest
For iy=0 To 3
For ix=0 To 3
If x+ix<width And y+iy<height ;Not out of bounds
argb=ReadPixelFast(ix+x,iy+y,imagebuf)
If ColorTotal(argb)>ColorTotal(color0) Then color0=argb
If ColorTotal(argb)<ColorTotal(color1) Then color1=argb
EndIf
Next
Next
;Make sure color0 is the highest
If color1>color0 Then
i=color0 : color0=color1 : color1=i ;Switch order
EndIf
;Switch order, color1 highest to indicate DXT1a
If format=1 And alphaimg<>0 ;DXT1a, using alpha bit
i=color0 : color0=color1 : color1=i
EndIf
PokeShort hdds,offset+(x*bsize)+bindex,Color565(color0) ;wColor0
PokeShort hdds,offset+(x*bsize)+bindex+2,Color565(color1) ;wColor1
;Set each texel in block to closest color
color0=ColorTotal(color0) : color1=ColorTotal(color1)
For iy=0 To 3
For ix=0 To 3
If x+ix<width And y+iy<height ;Not out of bounds
argb=ReadPixelFast(ix+x,iy+y,imagebuf)
Else
argb=0 ;Black
EndIf
If color0>color1 ;Four-color block
color2=((2*color0)+color1)/3
color3=(color0+(2*color1))/3
Else ;Three-color block
color2=(color0+color1)/2
color3=3*16 ;Max transparent color
EndIf
d0=Abs(color0-ColorTotal(argb)) ;Get differences
d1=Abs(color1-ColorTotal(argb))
d2=Abs(color2-ColorTotal(argb))
d3=Abs(color3-ColorTotal(argb))
i=0 : If d1<d0 Then d0=d1 : i=1 ;Find closest color
If d2<d0 Then d0=d2 : i=2
If d3<d0 Then d0=d3 : i=3
If color0>color1 And Abs(color2-color3)<8
If i=3 Then i=2 ;Close and wrong order so use color2
EndIf
If format=1 And alphaimg<>0 ;DXT1a, using alpha bit
If i=3 Then i=2 ;No color3 so use color2
EndIf
If x+ix>width-1 Or y+iy>height-1 ;Out of bounds
If color0<color1 Then i=0 Else i=1 ;Find lowest color
EndIf
texel=PeekByte(hdds,offset+(x*bsize)+bindex+4+iy) Or (i Shl ix*2)
PokeByte hdds,offset+(x*bsize)+bindex+4+iy,texel ;bTexels[4]
Next
Next
;Find color in texel block and set each alpha texel
If format=1 And alphaimg<>0 ;DXT1a, using alpha bit
For iy=0 To 3
For ix=0 To 3
If x+ix<width And y+iy<height ;Not out of bounds
argb=ReadPixelFast(ix+x,iy+y,alphabuf) ;Use alpha map
color3=3*16 ;Max transparent color
If ColorTotal(argb)<color3 ;Set alpha texel
texel=PeekByte(hdds,offset+(x*bsize)+bindex+4+iy) Or (3 Shl ix*2)
PokeByte hdds,offset+(x*bsize)+bindex+4+iy,texel ;bTexels[4]
EndIf
EndIf
Next
Next
EndIf
Next
Next
UnlockBuffer(imagebuf)
UnlockBuffer(alphabuf)
Next
;Attached Surfaces Data, BYTE bData2[]
;Complex
EndIf
;Write DDS bank to file
file=WriteFile(filename$)
If Not file FreeBank hdds : Return False ;Fail code
WriteBytes hdds,file,0,128+sizebytes
CloseFile file
FreeBank hdds
;If an alpha image was created
If alphaimg_created=True Then FreeImage alphaimg
Return True ;Success code
End Function
Function MakeEmptyAlpha(width,height)
Local alpha,x,y,rgb
alpha=CreateImage(width,height)
LockBuffer(ImageBuffer(alpha))
For y=0 To ImageHeight(alpha)-1
For x=0 To ImageWidth(alpha)-1
rgb=$FFFFFF
WritePixel x,y,rgb,ImageBuffer(alpha)
Next
Next
UnlockBuffer(ImageBuffer(alpha))
Return alpha
End Function
Function MAX(a,b)
If a=>b Then Return a Else Return b
End Function
Function MakeFourCC(c0$,c1$,c2$,c3$)
Return (Asc(c0$)+(Asc(c1$) Shl 8)+(Asc(c2$) Shl 16)+(Asc(c3$) Shl 24))
End Function
Function ColorHighest(argb)
Local r,g,b,a
r=(argb And $00FF0000) Shr 16
g=(argb And $0000FF00) Shr 8
b=(argb And $000000FF)
If r>g Then a=r Else a=g
If b>a Then a=b
Return a
End Function
Function ColorTotal(argb)
Local r,g,b
r=(argb And $00FF0000) Shr 16
g=(argb And $0000FF00) Shr 8
b=(argb And $000000FF)
Return (r+g+b) ;0..255*3
End Function
Function Color565(argb)
Local r,g,b
r=(argb And $00FF0000) Shr 16 : r=(r*31/255) Shl 11 ;Bits 11..15
g=(argb And $0000FF00) Shr 8 : g=(g*63/255) Shl 5 ;Bits 5..10
b=(argb And $000000FF) : b=b*31/255 ;Bits 0..4
Return (r+g+b)
End Function |
Comments
| ||
| Sweet! |
| ||
| I converted the code to BlitzMax. There seems to be a little color inaccuracy, compared to DDS files saved by ATI's Compressonator tool. That tool includes some color weighting values that make no sense to me. I think it must be part of a high-pass filter, but their exported DDS files more closely match the original image's colors: |
| ||
| So the code works but the colors are a little off? I think that is due to my algorithm, so the Blitz3D version should do exactly the same thing. I wrote this code by reading the DX7 Programmer's Reference, there was no info about what algorithm to use so I came up with my own. If you can find the correct algorithm that ATI use then we could improve it, otherwise I have no idea what their algorithm is. |
| ||
| If I set the color weights in ATI's Compressonator to 0 the output looks just like yours. So I think that is what makes the difference. The default values are this: Red: 0.3086 Green: 0.6094 Blue: 0.082 I have no idea how these are being used. They looks like values to convert RGB to grayscale. |
| ||
Ha! It was just a rounding problem. Your code is also about ten times faster than nvdxt.Function Color565:Int(argb) Local r,g,b r=(argb & $00FF0000) Shr 16 ; r=Int((r*31.0/255.0+0.5)) Shl 11 'Bits 11..15 g=(argb & $0000FF00) Shr 8 ; g=Int((g*63.0/255.0+0.5)) Shl 5 'Bits 5..10 b=(argb & $000000FF) ; b=Int(b*31/255.0+0.5) 'Bits 0..4 Return (r+g+b) EndFunction The output is slightly grainy compared to NVidia and ATI tools, but it is hardly different. |
| ||
| You fixed it? Great job! I am relieved. :) I had a look at color weighting and that seems to be the process of choosing the most used color in a block of pixels. I'm not sure but I think maybe Color565() should be dividing by 256. Yeah, the grainyness is probably due to the algorithm used. |
| ||
| I found it was necessary to clear the created bank, or errors would result after multiple saves. |
| ||
| There is an error in your calculation here, at least with BlitzMax integer handling: mipmaps=Log(c)/Log(2)+1 It should be this: mipmaps=Int(Log(c)/Log(2)+1.0+0.5) The wrong mipmap count will be calculated with a texture of size 64, without this fix. |
| ||
| Doesn't work in Blitz3D. The problem is that Int() behaves differently in BMax to B3D, in Max it floors the value whereas in B3D it rounds it to the nearest int. I'd actually consider this a bug in Max, but you could say it is a matter of preference. Edit: well looking at it again, Int() in Max is a type cast so it's not a bug but there's no function to do the equivalent, such as Round(). |
| ||
| Edit: well looking at it again, Int() in Max is a type cast so it's not a bug but there's no function to do the equivalent, such as Round(). Floor()? |
| ||
| No, Int() in b3d isn't like Floor(). With floor (or int type cast in max) 2.99=2, with int in b3d (or round in max) 2.99=3. Edit: in fact, Leadwerks' MathEx module has a Round(). |
| ||
Round%(fFoat#)=Abs(Floor#(Float#+(Sgn(fFloat#))*0.5))) Woudn't this do the trick? |
Code Archives Forum