add tim vram layout

This commit is contained in:
ABelliqueux 2021-04-04 23:32:34 +02:00
parent c0d2d8a3ca
commit 9ab55e1141
2 changed files with 348 additions and 55 deletions

View File

@ -111,6 +111,8 @@ You'll need to have [pngquant](https://pngquant.org/) and [img2tim](https://gith
Windows executables are provided for convenience.
For users with Imagemagick installed, there is an option to use that instead of pngquant.
On Linux, that's :
`~/.config/blender/2.79/scripts/addons`

View File

@ -20,7 +20,7 @@ import unicodedata
import subprocess
from math import radians, degrees, floor, cos, sin, sqrt
from math import radians, degrees, floor, cos, sin, sqrt, ceil
from mathutils import Vector
@ -91,7 +91,7 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
name = "Use ImageMagick",
description = "Use Image Magick's convert tool to convert PNGs to 8/4bpp",
description = "Use installed Image Magick's convert tool to convert PNGs to 8/4bpp",
default = False
)
@ -108,6 +108,18 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
def execute(self, context):
### Globals declaration
global nextTpage, freeTpage
global nextClutSlot, freeClutSlot
global tpageY
global TIMbpp
### Functions
def triangulate_object(obj):
# Triangulate an object's mesh
@ -342,7 +354,7 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
return screenPos
def convertBGtoTIM( filePathWithExt, colors = 256, bpp = 8):
def convertBGtoTIM( filePathWithExt, colors = 256, bpp = 8, timX = 640, timY = 0, clutX = 0, clutY = 480, transparency = 'alpha'):
# By default, converts a RGB to 8bpp, 256 colors indexed PNG, then to a 8bpp TIM image
@ -372,6 +384,18 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
colors = min( 16, colors )
if transparency == "alpha":
transpMethod = "-usealpha"
elif transparency == "black":
transpMethod = "-b"
elif transparency == "nonblack":
transpMethod = "-t"
# Quantization of colors with pngquant ( https://pngquant.org/ )
subprocess.call( [ "pngquant" + exe, str( colors ), filePathWithExt, "-o", filePathWithExt, "--force" ] )
@ -386,14 +410,171 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
# Convert to tim with img2tim ( https://github.com/Lameguy64/img2tim )
subprocess.call( [ "img2tim" + exe, "-t", "-bpp", str( bpp ), "-org", "320", "0", "-plt" , "0", "484","-o", filePathWithoutExt + ".tim", filePathWithExt ] )
subprocess.call( [ "img2tim" + exe, transpMethod, "-bpp", str( bpp ), "-org", str( timX ), str( timY ), "-plt" , str( clutX ), str( clutY ),"-o", filePathWithoutExt + ".tim", filePathWithExt ] )
def VramIsFull( size ):
# Returns True if not enough space in Vram for image
# Transpose bpp to bitshift value
global nextTpage, freeTpage
global nextClutSlot, freeClutSlot
global tpageY
if TIMbpp == 8:
shift = 1
elif TIMbpp == 4:
shift = 2
else:
shift = 0
# Get image width in vram
if not size:
imageWidth = size[0] >> shift
else:
imageWidth = size >> shift
# Divide by cell width ( 64 pixels )
imageWidthInTPage = ceil( imageWidth / 64 )
if ( tpageY == 0 and
nextTpage + ( imageWidthInTPage * 64 ) < 1024 and
freeTpage - imageWidthInTPage > 0
) :
return False
elif ( tpageY == 256 and
nextTpage + ( imageWidthInTPage * 64 ) < 960 and
freeTpage - imageWidthInTPage > 1
) :
return False
else:
return True
def setNextTimPos( image ):
# Sets nextTpage, freeTpage, tpageY, nextClutSlot, freeClutSlot to next free space in Vram
# Transpose bpp to bitshift value
global nextTpage, freeTpage
global nextClutSlot, freeClutSlot
global tpageY
if TIMbpp == 8:
shift = 1
elif TIMbpp == 4:
shift = 2
else:
shift = 0
# Get image width in vram
imageWidth = image.size[0] >> shift
# Divide by cell width ( 64 pixels )
imageWidthInTPage = ceil( imageWidth / 64 )
if ( tpageY == 0 and
nextTpage + ( imageWidthInTPage * 64 ) < 1024 and
freeTpage - imageWidthInTPage > 0
) :
nextTpage += imageWidthInTPage * 64
freeTpage -= imageWidthInTPage
nextClutSlot += 1
freeClutSlot -= 1
elif ( tpageY == 256 and
nextTpage + ( imageWidthInTPage * 64 ) < 960 and
freeTpage - imageWidthInTPage > 1
) :
nextTpage += imageWidthInTPage * 64
freeTpage -= imageWidthInTPage
nextClutSlot += 1
freeClutSlot -= 1
else:
tpageY = 256
nextTpage = 320
nextClutSlot += 1
freeClutSlot -= 1
### VRam Layout
nextTpage = 320
nextClutSlot = 480
freeTpage = 21
freeClutSlot = 32
tpageY = 0
# Set TIMs default bpp value
TIMbpp = 8
TIMshift = 1
if self.exp_TIMbpp:
TIMbpp = 4
TIMshift = 2
# Leave edit mode to avoid errors
bpy.ops.object.mode_set(mode='OBJECT')
@ -434,6 +615,18 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if self.exp_Precalc:
# Set rendering resolution
bpy.context.scene.render.resolution_x = 320
bpy.context.scene.render.resolution_y = 240
# Get BGs TIM size depending on mode
timSize = bpy.context.scene.render.resolution_x >> TIMshift
timSizeInCell = ceil( timSize / 64 )
# Create folder if it doesn't exist
os.makedirs(dirpath, exist_ok = 1)
@ -448,6 +641,8 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
bpy.context.scene.render.image_settings.color_depth = '8'
bpy.context.scene.render.image_settings.color_mode = 'RGB'
# Get active cam
scene = bpy.context.scene
@ -482,12 +677,35 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
# Convert PNG to TIM
convertBGtoTIM( filepath + filename + fileext , bpp = TIMbpp )
if not VramIsFull( bpy.context.scene.render.resolution_x ):
convertBGtoTIM( filepath + filename + fileext , bpp = TIMbpp, timX = nextTpage, timY = tpageY, clutY = nextClutSlot )
else:
tpageY = 256
nextTpage = 320
if not VramIsFull( bpy.context.scene.render.resolution_x ):
convertBGtoTIM( filepath + filename + fileext , bpp = TIMbpp, timX = nextTpage, timY = tpageY, clutY = nextClutSlot )
# Add camera object to camAngles
camAngles.append(o)
# Notify layout change to vars
nextTpage += timSizeInCell * 64
freeTpage -= timSizeInCell
nextClutSlot += 1
freeClutSlot -= 1
print( str(freeTpage) + " : " + str(nextTpage) + " : " + str(nextClutSlot) + " : " + str(freeClutSlot) )
### Start writing output file
@ -674,9 +892,19 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
f.write("CAMPOS camPos_" + CleanName( bpy.data.objects[ o ].name ) + " = {\n" +
"\t{" + str(round(-bpy.data.objects[o].location.x * scale)) + "," + str(round(bpy.data.objects[o].location.z * scale)) + "," +str(round(-bpy.data.objects[o].location.y * scale)) + "},\n" +
"\t{ " + str( round( -bpy.data.objects[o].location.x * scale ) ) +
"\t{" + str(round(-(degrees(bpy.data.objects[o].rotation_euler.x)-90)/360 * 4096)) + "," + str(round(degrees(bpy.data.objects[o].rotation_euler.z)/360 * 4096)) + "," + str(round(-(degrees(bpy.data.objects[o].rotation_euler.y))/360 * 4096)) + "}\n" +
"," + str( round( bpy.data.objects[o].location.z * scale ) ) +
"," + str( round( -bpy.data.objects[o].location.y * scale ) ) + " },\n" +
"\t{ " + str( round( -( degrees( bpy.data.objects[ o ].rotation_euler.x ) -90 ) / 360 * 4096 ) ) +
"," + str( round( degrees( bpy.data.objects[ o ].rotation_euler.z ) / 360 * 4096 ) ) +
"," + str( round( -( degrees( bpy.data.objects[ o ].rotation_euler.y ) ) / 360 * 4096 ) ) +
" }\n" +
"};\n\n")
@ -684,7 +912,11 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if bpy.data.objects[o].type == 'CAMERA' :
if bpy.data.objects[o].name.startswith("camPath") and not bpy.data.objects[o].data.get('isDefault'):
if ( bpy.data.objects[ o ].name.startswith( "camPath" ) and not
bpy.data.objects[ o ].data.get( 'isDefault' )
) :
camPathPoints.append(bpy.data.objects[o].name)
@ -696,9 +928,9 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
# ~ camPathPoints = list(reversed(camPathPoints))
for p in range(len(camPathPoints)):
for point in range(len(camPathPoints)):
if p == 0:
if point == 0:
f.write("CAMPATH camPath = {\n" +
@ -710,9 +942,15 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
"\t{\n")
f.write("\t\t{" + str(round(-bpy.data.objects[camPathPoints[p]].location.x * scale)) + "," + str(round(bpy.data.objects[camPathPoints[p]].location.z * scale)) + "," +str(round(-bpy.data.objects[camPathPoints[p]].location.y * scale)) + "}")
f.write( "\t\t{ " + str( round( -bpy.data.objects[ camPathPoints[ point ] ].location.x * scale ) ) +
if p != len(camPathPoints) - 1:
"," + str( round( bpy.data.objects[ camPathPoints[ point ] ].location.z * scale ) ) +
"," + str( round( -bpy.data.objects[ camPathPoints[ point ] ].location.y * scale ) ) +
" }" )
if point != len( camPathPoints ) - 1:
f.write(",\n")
@ -869,7 +1107,11 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
Zvals.append( -v.z )
f.write("\t{"+str(round(v.x*scale))+","+str(round(-v.z*scale)) + "," + str(round(v.y*scale)) +"}")
f.write("\t{ " + str( round( v.x * scale ) ) +
"," + str( round( -v.z * scale ) ) +
"," + str( round( v.y * scale ) ) + " }" )
if i != len(m.vertices) - 1:
@ -887,7 +1129,11 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
poly = m.vertices[i]
f.write("\t"+str(round(-poly.normal.x * 4096))+","+str(round(poly.normal.z * 4096))+","+str(round(-poly.normal.y * 4096))+",0")
f.write( "\t"+ str( round( -poly.normal.x * 4096 ) ) +
"," + str( round( poly.normal.z * 4096 ) ) +
"," + str( round( -poly.normal.y * 4096 ) ) + ", 0" )
if i != len(m.vertices) - 1:
@ -923,7 +1169,13 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
uy = u.y * tex_height
f.write("\t"+str(max(0, min( round(ux) , 255 )))+","+str(max(0, min(round(tex_height - uy) , 255 )))+", 0, 0") # Clamp values to 0-255 to avoid tpage overflow
# Clamp values to 0-255 to avoid tpage overflow
f.write("\t" + str( max( 0, min( round( ux ) , 255 ) ) ) +
"," + str( max( 0, min( round( tex_height - uy ) , 255 ) ) ) +
", 0, 0" )
if i != len(uv_layer) - 1:
@ -945,6 +1197,7 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
texture_image.save()
# Write vertex colors vectors
f.write("CVECTOR "+"model"+cleanName+"_color[] = {\n")
@ -957,7 +1210,11 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
for i in range(len(colors)):
f.write("\t"+str(int(colors[i].color.r*255))+","+str(int(colors[i].color.g*255))+","+str(int(colors[i].color.b*255))+", 0")
f.write("\t" + str( int( colors[ i ].color.r * 255 ) ) + "," +
str( int( colors[ i ].color.g * 255 ) ) + "," +
str( int( colors[ i ].color.b * 255 ) ) + ", 0" )
if i != len(colors) - 1:
@ -1274,7 +1531,35 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
# TODO : Add a way to arrange TIM's VM layout to avoid overlapping
# ~ convertPNGtoTIM( folder + os.sep + "TIM" + os.sep + CleanName( tex_name ) + "." + texture_image.file_format.lower() , bpp = TIMbpp )
filePathWithExt = folder + os.sep + "TIM" + os.sep + CleanName( tex_name ) + "." + texture_image.file_format.lower()
if not VramIsFull( bpy.context.scene.render.resolution_x ):
convertBGtoTIM( filePathWithExt, bpp = TIMbpp, timX = nextTpage, timY = tpageY, clutY = nextClutSlot )
setNextTimPos( texture_image )
elif VramIsFull( bpy.context.scene.render.resolution_x ) and tpageY == 0:
tpageY = 256
nextTpage = 320
if not VramIsFull( bpy.context.scene.render.resolution_x ):
convertBGtoTIM( filePathWithExt, bpp = TIMbpp, timX = nextTpage, timY = tpageY, clutY = nextClutSlot )
setNextTimPos( texture_image )
else:
self.report({'ERROR'}, "Not enough space in VRam !")
else:
self.report({'ERROR'}, "Not enough space in VRam !")
print( str(freeTpage) + " : " + str(nextTpage) + " : " + str(nextClutSlot) + " : " + str(freeClutSlot) )
# Write corresponding TIM declaration
@ -1894,13 +2179,19 @@ class ExportMyFormat(bpy.types.Operator, ExportHelper):
if checkLine(
getSepLine(p, s[0])[0],
getSepLine(p, s[0])[1],
getSepLine(p, s[0])[2],
getSepLine(p, s[0])[3],
getSepLine(op, s[1])[0],
getSepLine(op, s[1])[1],
getSepLine(op, s[1])[2],
getSepLine(op, s[1])[3]
) == 'connected' and (