Yes, like I said two posts up, use the script that worldlife posted. (
I'll include it into the updated tools once I have time again, but you can also use it yourself manually.
Take everything in the code box and save it as a .ms file. Then open max and in the menu bar use maxscript/run script and navigate to the saved .ms file.
Code: Select all
-- bf2mesh.ms
g_bf2doInclude "animation/skeImport.ms"
-- helper functions
fn BF2ReadString fp=(
local length = ReadLong fp #unsigned
result = ""
for i=1 to length do(
local char = bit.intaschar (ReadByte fp #unsigned)
append result char
)
return result
--return ReadString fp
)
fn BF2AddVertsToChannel obj mapChannel vertsList=(
if meshop.getMapSupport obj mapChannel != true then
meshop.setMapSupport obj mapChannel true
if obj.numverts != vertsList.count then format "ERROR in BF2AddVertsToChannel: numverts!=numtverts"
for i=1 to vertsList.count do
(
meshop.setMapVert obj mapChannel i vertsList[i]
)
)
fn BF2CreateBoneFromFile fname boneObjs s=(
local f = fopen fname "rb"
if (f != undefined) then
(
if s == undefined then s = 1.0 / BF2_GetUnitMultiplier()
local version = readlong f
format "version: %\n" version
local numBones = readlong f
local parentIDs = #()
local rightHandID = -2
for i=1 to numBones do
(
local tmpName = bf2ske.readName f
local tmpNameLC = lowercase tmpName
if tmpName == "right_ullna" then rightHandID = i - 1
if tmpName == "torus" then rightHandID = i - 1
local motherInx = readShort f
local tmpQuat = #()
for j=1 to 4 do append tmpQuat (readFloat f)
local tmpPos = [0,0,0]
tmpPos.x = (readFloat f) * s
tmpPos.z = (readFloat f) * s
tmpPos.y = (readFloat f) * s
local motherObj = undefined
if motherInx != -1 then
(
-- format " % %\n" motherInx boneObjs[motherInx+1]
if isValidNode boneObjs[motherInx+1] then
(
motherObj = boneObjs[motherInx+1]
parentIDs[i] = motherInx + 1
)
)
if (partialCompareStr tmpName "mesh") then
(
if rightHandID == motherInx then
(
local newBone = Box name:tmpName length :( 0.25) width :( 0.25) height :( 0.25)
newBone.parent = boneObjs[motherInx+1]
newBone.transform = newBone.parent.transform
continue
)
)
local newBone = bf2ske.makeBone tmpName tmpQuat tmpPos motherObj
boneObjs[i] = newBone
format "%_% (%) \t% %\n" i tmpName (motherInx+1) tmpQuat tmpPos
)
fclose f
local boneSz = 0.15
local rtNode = Dummy name :( "root_skeleton_" + (getFilenamefile fname)) size :( 0.4*s)
for i=1 to numBones do
(
if isValidNode boneObjs[i] then
(
if parentIDs[i] != undefined then
(
-- boneObjs[i].parent = boneObjs[parentIDs[i]]
)
else
(
boneObjs[i].parent = rtNode
)
)
)
return rtNode
)
return false
)
-- bounding box
-- 24 bytes
struct aabb
(
bbmin, --Point3
bbmax, --Point3
-- functions
fn Read fp=(
local vx = (ReadFloat fp)
local vy = (ReadFloat fp)
local vz = (ReadFloat fp)
bbmin = [vx,vz,vy]
local vx = (ReadFloat fp)
local vy = (ReadFloat fp)
local vz = (ReadFloat fp)
bbmax = [vx,vz,vy]
)
)
-- 4x4 transformation matrix
struct matrix4 -- 64 bytes
(
m, --BigMatrix 4*4
-- functions
fn Read fp=(
m = BigMatrix 4 4
for i=1 to 4 do (
for j=1 to 4 do m[i][j]=(ReadFloat fp)
)
),
fn convertTransform s inverseTrans:false=(
if m==undefined then return m
else(
local mat = matrix3 [m[1][1],m[1][3],m[1][2]] [m[3][1],m[3][3],m[3][2]] [m[2][1],m[2][3],m[2][2]] ([m[4][1],m[4][3],m[4][2]]*s)
if inverseTrans then return mat
else return (inverse mat)
)
)
)
--bf2 vertex structure
struct bf2vertex
(
position, --0
blendweight, --1
blendindices, --2
normal, --3
texcoord, --5
tangent, --6
uv2, --261
uv3, --517
uv4, --773
uv5, --1029
--fnList includes all functions the Read function should use
fn Read fp version attribList=(
for i=1 to attribList.count do(
--check flag
if attribList[i].flag != 0 then continue
--read data
local ret = case attribList[i].vartype of(
--D3DDECLTYPE_FLOAT1
0: ReadFloat fp
--D3DDECLTYPE_FLOAT2
1: [(ReadFloat fp),1-(ReadFloat fp),0] --this is used as UV, V is inverted, and we insert a 0 as W
--D3DDECLTYPE_FLOAT3
2: [(ReadFloat fp),(ReadFloat fp),(ReadFloat fp)]
--D3DDECLTYPE_D3DCOLOR(4 ubytes)
4: #((ReadByte fp #unsigned),(ReadByte fp #unsigned),(ReadByte fp #unsigned),(ReadByte fp #unsigned))
default: format "vartype % not supported" attribList[i].vartype
)
if ret==undefined then continue
--store data
case attribList[i].usage of(
--D3DDECLUSAGE_POSITION
0: position = [ret.x, ret.z, ret.y]--ret
--D3DDECLUSAGE_BLENDWEIGHT
1: blendweight = ret
--D3DDECLUSAGE_BLENDINDICES
2: blendindices = ret
--D3DDECLUSAGE_NORMAL
3: normal = ret
--D3DDECLUSAGE_TEXCOORD
5: texcoord = ret
--D3DDECLUSAGE_TANGENT
6: tangent = ret
--uv2
261: uv2 = ret
--uv3
517: uv3 = ret
--uv4
773: uv4 = ret
--uv5
1029: uv5 = ret
default: format "usage % not supported" attribList[i].usage
)
)
)
)
-- bf2 mesh file header
struct bf2head -- 20 bytes
(
u1, -- (uint)0
version, -- (uint)10 for most bundledmesh, 6 for some bundledmesh, 11 for staticmesh
u3, -- (uint)0
u4, -- (uint)0
u5, -- (uint)0
-- functions
fn Read fp=(
u1 = ReadLong fp #unsigned
version = ReadLong fp #unsigned
u3 = ReadLong fp #unsigned
u4 = ReadLong fp #unsigned
u5 = ReadLong fp #unsigned
)
)
-- vertex attribute table entry
struct bf2attrib -- 8 bytes
(
flag, -- (ushort)some sort of boolean flag (if true the below field are to be ignored?)
offset, -- (ushort)offset from vertex data start
vartype, -- (ushort)attribute type (vec2, vec3 etc)
usage, -- (ushort)usage ID (vertex, texcoord etc)
-- functions
fn Read fp=(
flag = ReadShort fp #unsigned
offset = ReadShort fp #unsigned
vartype = ReadShort fp #unsigned
usage = ReadShort fp #unsigned
)
-- Note: "usage" field correspond to the definition in DX SDK "Include\d3d9types.h"
-- Looks like DICE extended these for additional UV channels, these constants
-- are much larger to avoid conflict with core DX enums.
)
-- bone structure
struct bf2bone -- 68 bytes
(
id, -- (uint)4 bytes
transform, -- (matrix4)64 bytes
fn Read fp version=(
id = ReadLong fp #unsigned
transform = matrix4()
transform.Read fp
)
)
-- rig structure
struct bf2rig
(
bonenum, -- (int)
bone, -- #(bf2bone)
-- constructor/destrutor
-- functions
fn Read fp version= (
bonenum = ReadLong fp #signed
format " bonenum: %\n" bonenum
bone = #()
for i = 1 to bonenum do(
local tmpbone = bf2bone()
tmpbone.Read fp version
append bone tmpbone
format " boneid[%]: %\n" i tmpbone.id
)
)
)
-- material (aka drawcall)
struct bf2mat
(
alphamode, -- (uint)0=opaque, 1=blend, 2=alphatest
fxfile, -- (string)shader filename string
technique, -- (string)technique name
-- texture map filenames
mapnum, -- (uint)number of texture map filenames
map, -- #(string)map filename array
-- geometry info
vstart, -- (uint)vertex start offset
istart, -- (uint)index start offset
inum, -- (uint)number of indices
vnum, -- (uint)number of vertices
-- misc
u4, -- (uint)always 1?
u5, -- (ushort)always 0x34E9?
u6, -- (ushort)most often 18/19
bounds, -- (aabb)per-material bounding box (StaticMesh only)
-- constructor/destructor
-- functions
fn Read fp version isSkn=(
-- alpha flag (4 bytes)
if not isSkn do (
alphamode = ReadLong fp #unsigned
format " alphamode: %\n" alphamode
)
-- fx filename
fxfile = BF2ReadString fp
format " fxfile: %\n" fxfile
-- material name
technique = BF2ReadString fp
format " matname: %\n" technique
-- mapnum (4 bytes)
mapnum = ReadLong fp #unsigned
format " mapnum: %\n" mapnum
map = #()
for i= 1 to mapnum do(
tmpstr = (BF2ReadString fp)
append map tmpstr
format " map %: %\n" i tmpstr
)
--geometry info
vstart = ReadLong fp #unsigned
istart = ReadLong fp #unsigned
inum = ReadLong fp #unsigned
vnum = ReadLong fp #unsigned
format " vstart: %\n" vstart
format " istart: %\n" istart
format " inum: %\n" inum
format " vnum: %\n" vnum
--unknown
u4 = ReadLong fp #unsigned
u5 = ReadShort fp #unsigned
u6 = ReadShort fp #unsigned
--bounds
if not isSkn do (
if version == 11 do(
bounds = aabb()
bounds.Read fp
)
)
return true
)
)
-- lod, holds mainly a collection of materials
struct bf2lod
(
-- bounding box
bbmin, -- (Point3)
bbmax, -- (Point3)
pivot, -- (Point3)not sure this is really a pivot (only on version<=6)
-- skinning matrices (SkinnedMesh only)
rignum, -- (int)this usually corresponds to meshnum (but what was meshnum again??)
rig, -- #(bf2rig)array of rigs
-- nodes (staticmesh and bundledmesh only)
nodenum, --(int)
node, --#(matrix4)
-- material/drawcalls
matnum, -- (int)number of materials
mat, -- #(bf2mat)material array
-- constructor/destructor
-- functions
fn ReadNodeData fp version isSkn isBn isBFP4F=(--return bool
--bounds (24 bytes)
vx = (ReadFloat fp)
vy = (ReadFloat fp)
vz = (ReadFloat fp)
bbmin = [vx,vz,vy]
vx = (ReadFloat fp)
vy = (ReadFloat fp)
vz = (ReadFloat fp)
bbmax = [vx,vz,vy]
--unknown (12 bytes)
if version <= 6 do( --version 4 and 6
vx = (ReadFloat fp)
vy = (ReadFloat fp)
vz = (ReadFloat fp)
pivot = [vx,vz,vy]
)
--skinnedmesh has different rigs
if isSkn then(
rignum = ReadLong fp #signed
format " rignum: %\n" rignum
rig = #()
for i=1 to rignum do(
format " rig block % start at %\n" i (ftell fp)
local tmprig = bf2rig()
tmprig.Read fp version
append rig tmprig
--format " bone: %\n" tmprig.bone
format " rig block % end at %\n" i (ftell fp)
)
)
else(
nodenum = ReadLong fp #signed
format " nodenum: %\n" nodenum
if not isBn do(
format " node data\n"
node = #()
for i=1 to nodenum do(
local tmpnode = matrix4()
tmpnode.Read fp
format " node transform: %\n" tmpnode
append node tmpnode
)
)
--node matrices (BFP4F variant)
if isBn and isBFP4F then(
format " node data\n"
node = #()
for i=1 to nodenum do(
local tmpnode = matrix4()
tmpnode.Read fp
format " node transform: %\n" tmpnode
append node tmpnode
local tmpname = BF2ReadString fp --just discard it..
format "node matrix string: %\n" tmpname
)
)
)
return true
),
fn ReadMatData fp version isSkn=(--return bool
matnum = ReadLong fp #signed
format " matnum: %\n" matnum
mat = #()
for i=1 to matnum do(
format " mat block % start at %\n" i (ftell fp)
local tmpmat = bf2mat()
if not tmpmat.Read fp version isSkn then return false
append mat tmpmat
format " mat block % end at %\n" i (ftell fp)
)
return true
)
)
-- geom, holds a collection of LODs
struct bf2geom
(
lodnum, -- (int)number of LODs
lod, -- #(bf2lod)array of LODs
-- constructor/destructor
-- functions
fn Read fp version= ( -- return bool
lodnum = ReadLong fp #signed
format " lodnum: %\n" lodnum
lod = #()
for i=1 to lodnum do(
local tmplod = bf2lod()
append lod tmplod
)
)
)
-- BF2 mesh file structure
struct bf2mesh
(
-- header
head, --(bf2head)
-- unknown
u1, -- (ubyte)always 0?
-- geoms
geomnum, -- (int)numer of geoms
geom, -- #(bf2geom)geom array
-- vertex attribute table
vertattribnum, -- (int)number of vertex attribute table entries
vertattrib, -- #(bf2attrib)array of vertex attribute table entries
-- vertices
vertformat, -- (int)always 4? (e.g. GL_FLOAT)
vertstride, -- (int)vertex stride
vertnum, -- (int)number of vertices in buffer
vert, -- #(bf2vertex)vertex array
-- indices
indexnum, -- (int)number of indices
index, -- #(ushort)index array
-- unknown
u2, -- (int)always 8?
-- constructor/destructor
-- internal/hacks
isSkinnedMesh=false, --bool
isBundledMesh=false, --bool
isBFP4F=false, --bool
-- functions
fn Load filename= (
local fp = fopen filename "rb"
if fp == undefined then(
format "File \"%\" not found.\n" filename
return 1
)
--header
head = bf2head()
format "head start at %\n" (ftell fp)
head.Read fp
format " version: %\n" head.version
format "head end at %\n" (ftell fp)
format "\n"
--unknown (1 byte)
u1 = ReadByte fp #unsigned
--for BFP4F, the value is "1", so perhaps this is a version number as well
if u1 == 1 do isBFP4F = true
--- geom table ---------------------------------------------------------------------------
format "geom table start at %\n" (ftell fp)
--geomnum (4 bytes)
geomnum = ReadLong fp #signed
format " geomnum: %\n" geomnum
geom = #()
for i=1 to geomnum do(
tmpgeom = bf2geom()
tmpgeom.Read fp head.version
append geom tmpgeom
)
format "geom table end at %\n" (ftell fp)
format "\n"
--- vertex attribute table -------------------------------------------------------------------------------
format "attrib block at %\n" (ftell fp)
--vertattribnum (4 bytes)
vertattribnum = ReadLong fp #signed
format "vertattribnum: %\n" vertattribnum
vertattrib = #()
for i=1 to vertattribnum do(
tmpvertattrib = bf2attrib()
tmpvertattrib.Read fp
append vertattrib tmpvertattrib
format " attrib[%]: % % % %\n" i tmpvertattrib.flag \
tmpvertattrib.offset \
tmpvertattrib.vartype \
tmpvertattrib.usage
)
format "attrib block end at %\n" (ftell fp)
format "\n"
--- vertices -----------------------------------------------------------------------------
format "vertex block start at %\n" (ftell fp)
vertformat = ReadLong fp #signed
vertstride = ReadLong fp #signed
vertnum = ReadLong fp #signed
format " vertformat: %\n" vertformat
format " vertstride: %\n" vertstride
format " vertnum: %\n" vertnum
if vertformat!=4 then (
format "vertformat not supported!"
return 1
)
vert = #()
for i=1 to vertnum do(
tmpvert = bf2vertex()
tmpvert.Read fp version vertattrib
append vert tmpvert
)
format "vertex block end at %\n" (ftell fp)
--- indices ------------------------------------------------------------------------------
format "index block start at %\n" (ftell fp)
indexnum = ReadLong fp #signed
format " indexnum: %\n" indexnum
index = #()
for i=1 to indexnum do(
append index (ReadShort fp #unsigned)
)
format "index block end at %\n" (ftell fp)
format "\n"
--- rigs -------------------------------------------------------------------------------
--unknown (4 bytes)
if not isSkinnedMesh then(
u2 = ReadLong fp #signed
)
--rigs/nodes
format "nodes chunk start at %\n" (ftell fp)
for i=1 to geomnum do(
format " geom % start\n" (i-1)
for j=1 to geom[i].lodnum do(
format " lod % start\n" (j-1)
geom[i].lod[j].ReadNodeData fp head.version isSkinnedMesh isBundledMesh isBFP4F
format " lod % end\n" (j-1)
)
format " geom % end\n" (i-1)
)
format "nodes chunk end at %\n" (ftell fp)
format "\n"
--- geoms ------------------------------------------------------------------------------
format "geoms chunk start at %\n" (ftell fp)
for i=1 to geomnum do(
format " geom % start\n" (i-1)
for j=1 to geom[i].lodnum do(
format " lod % start\n" (j-1)
geom[i].lod[j].ReadMatData fp head.version isSkinnedMesh
format " lod % end\n" (j-1)
)
format " geom % end\n" (i-1)
)
format "geoms chunk end at %\n" (ftell fp)
format "\n"
--end of file
format "done reading %\n" (ftell fp)
fseek fp 0 #seek_end
format "file size is %\n" (ftell fp)
format "\n"
--close file
fclose fp
--success
return 0
),
fn createInstance forLightmap s=(
--add map search path
mapPaths.add (bf2GetSetting "outModPath")
if head == undefined then return() --not Loaded
format "%" Point() --help latter objects successfully create
local rootDummy = Point()
rootDummy.name = "root"
if isSkinnedMesh then append rootDummy.name "_skinnedmesh"
else if isBundledMesh then append rootDummy.name "_bundledmesh"
else append rootDummy.name "_staticmesh"
for i=1 to geomnum do(
format " geom % start\n" (i-1)
local geomDummy = Dummy name :( "geom"+((i-1) as string)) position:[0,0,0]
append rootDummy.children geomDummy
--preload skeleton for skinnedmesh
local skeRoot = undefined
local boneObjs = #()
if isSkinnedMesh then(
--import skeleton
local skeName = getOpenFileName caption :( "Import .ske for geom"+((i-1) as string)) types:"Skeleton .ske|*.ske|All|*.*|"
-- If the user did not cancel the file open dialog
if skeName != undefined then(
skeRoot = BF2CreateBoneFromFile skeName boneObjs s
--adjust bone positions
local boneTransforms = #()
local tmpIDs = #()
for j=1 to geom[i].lodnum do(
for k=1 to geom[i].lod[j].rignum do(
local tmprig = geom[i].lod[j].rig[k]
for l=1 to tmprig.bonenum do(
local tmpbone = tmprig.bone[l]
if boneTransforms[tmpbone.id+1] == undefined then(
--local boneObj = boneObjs[tmpbone.id+1]
append tmpIDs (tmpbone.id+1)
boneTransforms[tmpbone.id+1] = tmpbone.transform.convertTransform s
)
)
)
)
--maxid = amax tmpIDs --start from 0
--for j=1 to (maxid+1) do(
/*for j in tmpIDs do(
--if (findItem tmpIDs j) != 0 then(
local boneObj = boneObjs[j]
if boneTransforms[j]!=undefined then (
if boneObj.parent == skeRoot then (
boneObj.transform = boneTransforms[j]
)
else(
--get parent transform
local parentIndex = findItem boneObjs boneObj.parent
if boneTransforms[parentIndex] != undefined then
boneObj.transform = (inverse boneObj.parent.transform)*boneTransforms[j]--boneTransforms[parentIndex])
--boneObj.transform = inverse ((inverse boneTransforms[j])*boneObj.parent.transform)
else format "ERROR! bone % parent not found in rig transforms!\n" (j-1)
)
)
--)
)*/
--renew transform from top to bottom
fn renewTrans nodes boneObjs boneTransforms=(
while nodes.count > 0 do(
local childrens = #()
for node in nodes do(
local nodeID = findItem boneObjs node
if classof node == Pyramid and nodeID>0 and boneTransforms[nodeID]!=undefined then node.transform = boneTransforms[nodeID]
join childrens node.children
)
nodes = childrens
)
)
renewTrans skeRoot.children boneObjs boneTransforms
)
else(
skeRoot = Dummy name:"root_skeleton_debug"
--create debug bones
local tmpChildren = #()
local tmpIDs = #()
for j=1 to geom[i].lodnum do(
for k=1 to geom[i].lod[j].rignum do(
local tmprig = geom[i].lod[j].rig[k]
for l=1 to tmprig.bonenum do(
local tmpbone = tmprig.bone[l]
if boneObjs[tmpbone.id+1] == undefined then(
local bonename = (tmpbone.id as string)
if bonename.count==1 then bonename = "0"+bonename --bone id cannot exceed 99
local boneObj = Pyramid name:bonename transform :( tmpbone.transform.convertTransform s) width :( 0.01*s) depth :( 0.01*s) height :( 0.05*s)
append tmpChildren boneObj
append tmpIDs tmpbone.id
boneObjs[tmpbone.id+1] = boneObj
)
)
)
)
--add missed id bones
maxid = amax tmpIDs
for j=0 to maxid do( --id start from 0
if (findItem tmpIDs j) == 0 then(
local bonename = (j as string)
if bonename.count==1 then bonename = "0"+bonename
local boneObj = Pyramid name:bonename width :( 0.03*s) depth :( 0.03*s) height :( 0.1*s)
append tmpChildren boneObj
boneObjs[j+1] = boneObj
)
)
--sort names
fn sortByName obj1 obj2=(
if obj1.name>obj2.name then return 1
else return -1
)
qsort tmpChildren sortByName
format " debug bones: %\n" tmpChildren
for j=1 to tmpChildren.count do(
append skeRoot.children tmpChildren[j]
)
)
append geomDummy.children skeRoot
--adjust bone length
for b in boneObjs do
(
if classof b == Pyramid then
(
if b.children.count == 1 then
(
local distFromObjs = distance b b.children[1]
local dirPointing = pointingSameDir b.transform b.children[1].pos
if dirPointing == 1 then
(
b.height = distFromObjs
)
else
(
if dirPointing == -1 then
(
b.height = -distFromObjs
)
else
(
-- not pointing directly at only child!
)
)
)
)
)
)
for j=1 to geom[i].lodnum do(
format " lod % start\n" (j-1)
local lodDummy = Dummy name :( "lod"+((j-1) as string)) position:[0,0,0]
append geomDummy.children lodDummy
if isSkinnedMesh then(
--use rig info of geom[i].lod[j]
--check bone info
if skeRoot.name != "root_skeleton_debug" then(
--TODO: check bone number
)
--local mat = MultiSubMaterial numsubs:geom[i].lod[j].matnum name:"BF2Object"
-- read vert and face from bf2mat
for k=1 to geom[i].lod[j].matnum do(
local nodesInfo=#(#(),#(),#(),#(),#(),#()) -- #(#(verts),#(faces),#(blendweight),#(uvws),#(normals),#(boneIDs#(x,y)))
local tmpmat = geom[i].lod[j].mat[k]
--material(TODO: use bf2 bundledmesh material)
local stdmat = StandardMaterial name:"BF2Object"
stdmat.showInViewport = true
--mat.MaterialList[k] = stdmat
--mat.MaterialList[k].name = tmpname
for l=1 to tmpmat.mapnum do(
if (lowercase tmpmat.map[l]) == "common\\textures\\specularlut_pow36.dds" then continue -- do not include Specular_LUT
local tmpTex = BitmapTexture()
tmpTex.filename = tmpmat.map[l]
--stdmat.maps[l+1] = tmpTex
if classof stdmat.diffuseMap != BitmapTexture then(
stdmat.diffuseMap = tmpTex
)
else if classof stdmat.BumpMap != BitmapTexture then(
stdmat.BumpMap = tmpTex
)
else stdmat.SelfIllumMap = tmpTex
)
-- vertices/uv/normal/blendweight/boneid
for l=(tmpmat.vstart+1) to (tmpmat.vstart+tmpmat.vnum) do(
--local nodeIndex = vert[l].blendindices[1]+1
append nodesInfo[1] (vert[l].position*s) --scale
append nodesInfo[3] vert[l].blendweight
append nodesInfo[4] vert[l].texcoord
append nodesInfo[5] vert[l].normal
append nodesInfo[6] #(vert[l].blendindices[1]+1, vert[l].blendindices[2]+1)
)
-- faces
local tmpindex = #()
for l=(tmpmat.istart+1) to (tmpmat.istart+tmpmat.inum) do(
--format "index: % face: %\n" index[l] (index[l]-tmpmat.vstart)
append tmpindex (index[l]+1)
if tmpindex.count==3 then( --triangle
-- faces
append nodesInfo[2] [tmpindex[3],tmpindex[2],tmpindex[1]] -- inverted
tmpindex = #()
)
)
format " vertices: %\n faces: %\n blendweights: %\n UVs: %\n normals: %\n boneIDs: %\n" nodesInfo[1] nodesInfo[2] nodesInfo[3] nodesInfo[4] nodesInfo[5] nodesInfo[6]
local obj = mesh vertices:nodesInfo[1] faces:nodesInfo[2] tverts:nodesInfo[4]
obj.wirecolor = (random [0,0,0] [1,1,1]) as color
obj.name = (k-1) as string
if obj.name.count<2 then obj.name = "0"+obj.name
-- normals
for l=1 to obj.numverts do(
setNormal obj l nodesInfo[5][l]
)
-- uv mapping
--format "% %" obj.numverts obj.numtverts
buildTVFaces obj
for l=1 to obj.numfaces do(
setTVFace obj l (getFace obj l)
)
--skin modifier
addModifier obj (Skin())
local skinMod = obj.modifiers[#skin]
-- necessary for skinOps to work
max modify mode
select obj
skinMod.bone_Limit = 2 -- only 2 bones affect one vert currently in game
local tmprig = geom[i].lod[j].rig[k]
--add bones
for l=1 to tmprig.bonenum do(
local updateInt = 0
if l==tmprig.bonenum then updateInt=-1 --update modifier at the last add
skinOps.addbone skinMod boneObjs[tmprig.bone[l].id+1] updateInt
)
--add weight
for l=1 to obj.numverts do(
local boneInfo = nodesInfo[6][l]
local weightInfo
if boneInfo[1]==boneInfo[2] then(
boneInfo = boneInfo[1]
weightInfo = 1.0
)
else weightInfo = #(nodesInfo[3][l],1-nodesInfo[3][l])
skinOps.SetVertexWeights skinMod l boneInfo weightInfo
)
-- end
append lodDummy.children obj
obj.material = stdmat
)
)
else(
--use node info
if isBundledMesh then(
--use no-transform node
local nodesInfo=#(#(),#(),#(),#(),#(),#(),#(),#(),#()) -- #(#(verts),#(faces),#(matIDs),#(uvws),#(nodeIndex(for face)),#(normals),#(blendindices[1]),#(uv2),#(blendindices[4](uvtranslate)))
local indoffset = 0
local multimat = MultiSubMaterial numsubs:geom[i].lod[j].matnum name:"BF2Object"
-- read vert and face from bf2mat
for k=1 to geom[i].lod[j].matnum do(
local tmpmat = geom[i].lod[j].mat[k]
--material(TODO: use bf2 bundledmesh material)
local tmpname = tmpmat.technique
local isAnimatedUV = ((subString tmpname 1 10)=="AnimatedUV")
/*if tmpmat.alphamode!=0 then(
append tmpname ("|| alphamode=" + (tmpmat.alphamode as string))
)*/
local stdmat = StandardMaterial name:tmpname
stdmat.showInViewport = true
multimat.MaterialList[k] = stdmat
--mat.MaterialList[k].name = tmpname
for l=1 to tmpmat.mapnum do(
if (lowercase tmpmat.map[l]) == "common\\textures\\specularlut_pow36.dds" then continue -- do not include Specular_LUT
local tmpTex = BitmapTexture()
tmpTex.filename = tmpmat.map[l]
--stdmat.maps[l+1] = tmpTex
if classof stdmat.diffuseMap != BitmapTexture then(
stdmat.diffuseMap = tmpTex
)
else if classof stdmat.BumpMap != BitmapTexture then(
stdmat.BumpMap = tmpTex
)
else stdmat.SelfIllumMap = tmpTex
)
local aspectRatio
if isAnimatedUV and stdmat.diffuseMap!=undefined then(
local tmpbitmap = stdmat.diffuseMap.bitmap
if stdmat.diffuseMap.bitmap!=undefined then aspectRatio = (tmpbitmap.height as Float)/tmpbitmap.width
else messagebox ("Texture" + stdmat.diffuseMap.filename + "Not Found! Make sure you put the texture in the mod output path!")
format "bitmap % aspectRatio = %" stdmat.diffuseMap.filename aspectRatio
)
-- vertices/uv/normal
for l=(tmpmat.vstart+1) to (tmpmat.vstart+tmpmat.vnum) do(
--local nodeIndex = vert[l].blendindices[1]+1
append nodesInfo[1] (vert[l].position*s) --scale
--append nodesInfo[4] vert[l].texcoord
append nodesInfo[6] vert[l].normal
append nodesInfo[7] (vert[l].blendindices[1]+1)
append nodesInfo[9] vert[l].blendindices[4]
--if vert[l].uv2 != undefined then append nodesInfo[8] vert[l].uv2
--uvs
if vert[l].uv2 != undefined then (
if isAnimatedUV then(
if vert[l].blendindices[4]==1 or vert[l].blendindices[4]==3 then( --rotate uv
--format "Rotate UV vert %\n" l
append nodesInfo[4] (vert[l].texcoord+[vert[l].uv2.x*aspectRatio,1-vert[l].uv2.y,0])
append nodesInfo[8] vert[l].texcoord
)
else(
append nodesInfo[4] vert[l].texcoord
append nodesInfo[8] vert[l].texcoord
)
)
else(
append nodesInfo[4] vert[l].texcoord
append nodesInfo[8] vert[l].uv2
)
)
else (
append nodesInfo[4] vert[l].texcoord
--append nodesInfo[8] vert[l].texcoord
)
)
-- faces
local tmpindex = #()
for l=(tmpmat.istart+1) to (tmpmat.istart+tmpmat.inum) do(
--format "index: % face: %\n" index[l] (index[l]-tmpmat.vstart)
append tmpindex (index[l]+1)
if tmpindex.count==3 then( --triangle
--nodeIndex
local nodeIndex = vert[tmpmat.vstart+tmpindex[1]].blendindices[1]+1
--check if all 3 vertices belongs to the same node
local isBlendFace = false
for m=2 to 3 do(
if nodeIndex != vert[tmpmat.vstart+tmpindex[m]].blendindices[1]+1 then(
--format "A Blend Face: %\n" tmpindex
isBlendFace = true
)
)
if not isBlendFace then(
append nodesInfo[5] nodeIndex
-- faces
append nodesInfo[2] ([tmpindex[3],tmpindex[2],tmpindex[1]]+indoffset) -- inverted
-- matIDs
append nodesInfo[3] k
)
else(
append nodesInfo[5] 0
-- faces
append nodesInfo[2] ([tmpindex[3],tmpindex[2],tmpindex[1]]+indoffset) -- inverted
-- matIDs
append nodesInfo[3] k
)
tmpindex = #()
)
)
indoffset = indoffset + tmpmat.vnum
)
-- create node instance
--for k=1 to geom[i].lod[j].nodenum do(
format " vertices: %\n faces: %\n matIDs: %\n UVs: %\n nodeIndices: %\n normals: %\n" nodesInfo[1] nodesInfo[2] nodesInfo[3] nodesInfo[4] nodesInfo[5] nodesInfo[6]
format " vertices count: %\n faces count: %\n tverts count: %\n" nodesInfo[1].count nodesInfo[2].count nodesInfo[4].count
local obj = mesh vertices:nodesInfo[1] faces:nodesInfo[2] materialIDS:nodesInfo[3] tverts:nodesInfo[4]
--obj.name = (k-1) as string
-- normals
for k=1 to obj.numverts do(
setNormal obj k nodesInfo[6][k]
)
-- uv mapping
--format "% %" obj.numverts obj.numtverts
buildTVFaces obj
for k=1 to obj.numfaces do(
setTVFace obj k (getFace obj k)
)
--add vertex color according to blendindices[4]
/*
key: [] index value
Right Side
- treads .50 r [5]
- rotate .71 ( r and b ) [3]
- translate .60 [4]
Left Side
- treads .40 r [6]
- rotate .91 ( r and b ) [1]
- translate .80 [2]
*/
local vertColors = #()
for k=1 to nodesInfo[9].count do(
case nodesInfo[9][k] of(
1: append vertColors [0.91,0,0]
2: append vertColors [0.80,0,0]
3: append vertColors [0.71,0,0]
4: append vertColors [0.60,0,0]
5: append vertColors [0.50,0,0]
6: append vertColors [0.40,0,0]
default: append vertColors [0,0,0]
)
)
BF2AddVertsToChannel obj 0 vertColors
--add uv2 if exist
if nodesInfo[8].count>0 then(
meshop.setNumMaps obj 3 keep:true
BF2AddVertsToChannel obj 2 nodesInfo[8]
)
-- detach faces to different nodes
convertToMesh obj
local nodes = #()
local blendFaces = #()
--init nodes
for k=geom[i].lod[j].nodenum to 1 by -1 do(
nodes[k] = #()
)
--blendFaces
for k=1 to nodesInfo[5].count do(
if nodesInfo[5][k]>0 then append nodes[nodesInfo[5][k]] k
else append blendFaces nodesInfo[2][k]
)
if blendFaces.count > 0 then(
--format "Creating % blend faces...\n" blendFaces.count
--detach blendFaces
/*local detached = meshop.detachFaces obj blendFaces delete:false asMesh:true
local blendMesh = Editable_mesh()
blendMesh.mesh = detached
update blendMesh
blendMesh.name = "blendMesh"
append lodDummy.children blendMesh
blendMesh.material = multimat*/
/*
--get nodes for blendFaces, add bone and skin(skin is added on the original obj for convenience)
local nodesForBlend = #()
local vertsForBlend = #()
local vertsNotForBlend = #()
for k=1 to blendFaces.count do(
for l=1 to 3 do(
local vertIndex = blendFaces[k][l]
--format "vertIndex: %\n" vertIndex
local nodeIndex = nodesInfo[7][vertIndex]
if (findItem nodesForBlend nodeIndex) == 0 then append nodesForBlend nodeIndex
if (findItem vertsForBlend vertIndex) == 0 then append vertsForBlend vertIndex
else append vertsNotForBlend vertIndex
)
)
format "nodes for blend faces: %\n" nodesForBlend
--add bone
local boneObjs = #()
for k=1 to nodesForBlend.count do(
local bonename = "geom" + ((i-1) as string) + "lod" + ((j-1) as string) + "node" + ((nodesForBlend[k]-1) as string)
local boneObj = Pyramid name:bonename width :( 0.03*s) depth :( 0.03*s) height :( 0.1*s)
append lodDummy.children[nodesForBlend[k]].children boneObj
--boneObjs[nodesForBlend[k]] boneObj
append boneObjs boneObj
)
--add skin modifier to blendMesh
addModifier obj (Skin())
local skinMod = obj.modifiers[#skin]
-- necessary for skinOps to work
max modify mode
select obj
skinMod.bone_Limit = 1 -- only 1 bone affect one vert
skinMod.weightAllVertices = off
--add bones
for l=1 to boneObjs.count do(
if boneObjs[l]==undefined then continue
local updateInt = 0
if l==boneObjs.count then updateInt=-1 --update modifier at the last add
skinOps.addbone skinMod boneObjs[l] updateInt
)
--add weight
for l=1 to obj.numverts do(
if (findItem vertsForBlend l) == 0 then continue
local nodeIndex = nodesInfo[7][l] --blendindices[1]
local boneIndex = findItem nodesForBlend nodeIndex
if boneIndex==0 then format "Error! index % not found in nodesForBlend!" boneInfo
skinOps.SetVertexWeights skinMod l boneIndex 1.0
)
--delete unblended vertices(not sure if it works)
--modPanel.setCurrentObject obj.baseObject
--max create mode
format "vertsNotForBlend: %\n" vertsNotForBlend
meshop.deleteVerts $.baseObject vertsNotForBlend
--deleteVert obj vertsNotForBlend
update obj
--material
obj.material = multimat*/
--a basic implementation -- import the whole object and skin it to nodes...(wonder if it works when export)
--create nodes dummys and attach a bone to each
local boneObjs = #()
for k=2 to geom[i].lod[j].nodenum do(
local dummyMesh = mesh vertices:#([0.0,0.0,0.0],[0.0,0.0,0.0],[0.0,0.0,0.0]) faces:#([1,2,3]) --add dummy mesh
dummyMesh.name = (k-1) as string
if dummyMesh.name.count<2 then dummyMesh.name = "0"+dummyMesh.name
append lodDummy.children dummyMesh
dummyMesh.material = multimat
--bone
local bonename = "geom" + ((i-1) as string) + "lod" + ((j-1) as string) + "node" + ((k-1) as string)
local boneObj = Pyramid name:bonename width :( 0.03*s) depth :( 0.03*s) height :( 0.1*s)
append boneObjs boneObj
append dummyMesh.children boneObj
)
--do not skin on obj directly to avoid loop independence error
--clone obj to store skin data
local skinobj = Editable_mesh()
skinobj.mesh = obj.mesh
skinobj.name = "skindata_" + "geom" + ((i-1) as string) + "lod" + ((j-1) as string)
append lodDummy.children skinobj
--use obj itself as the base node(0)
obj.name = "00"
obj.wirecolor = (random [0,0,0] [1,1,1]) as color
obj.material = multimat
append lodDummy.children obj
--add skin modifier to skinobj
addModifier skinobj (Skin())
local skinMod = skinobj.modifiers[#skin]
-- necessary for skinOps to work
max modify mode
select skinobj
skinMod.bone_Limit = 1 -- only 1 bone affect one vert
skinMod.weightAllVertices = off
--add bones
for k=1 to boneObjs.count do(
if boneObjs[k]==undefined then continue
local updateInt = 0
if k==boneObjs.count then updateInt=-1 --update modifier at the last add
skinOps.addbone skinMod boneObjs[k] updateInt
)
--add weight
for k=1 to skinobj.numverts do(
local nodeIndex = nodesInfo[7][k] --blendindices[1]+1
--local boneIndex = findItem nodesForBlend nodeIndex
--if boneIndex==0 then format "Error! index % not found in nodesForBlend!" boneInfo
if nodeIndex>1 then skinOps.SetVertexWeights skinMod k (nodeIndex-1) 1.0
)
max create mode
)
--format " obj vertices count: %\n obj faces count: %\n obj tverts count: %\n" obj.numverts obj.numfaces obj.numtverts
else (
--detach nodes
for k=1 to geom[i].lod[j].nodenum do(
--format "nodes[%].count= %\n" k nodes[k].count
local detached = meshop.detachFaces obj nodes[k] delete:false asMesh:true
local newMesh = Editable_mesh()
newMesh.mesh = detached
newMesh.wirecolor = (random [0,0,0] [1,1,1]) as color
update newMesh
newMesh.name = (k-1) as string
if newMesh.name.count<2 then newMesh.name = "0"+newMesh.name
append lodDummy.children newMesh
--apply material
newMesh.material = multimat
--format " newMesh vertices count: %\n newMesh faces count: %\n newMesh tverts count: %\n" newMesh.numverts newMesh.numfaces newMesh.numtverts
)
delete obj
)
)
else(
--use transformed node
messagebox "Staticmeshs not supported yet!"
)
)
format " lod % end\n" (j-1)
)
format " geom % end\n" (i-1)
)
format "End importing..root:%\n" rootDummy
return rootDummy
)
)
fn bf2ImportMeshFile_new filename forLightmap s=
(
if filename == undefined then filename = getOpenFileName()
if filename == undefined then return undefined
local filetype = lowercase (getFilenameType filename)
local meshinstance = bf2mesh()
format "filetype: %\n" filetype
case filetype of(
".bundledmesh": meshinstance.isBundledMesh = true
".skinnedmesh": meshinstance.isSkinnedMesh = true
".staticmesh": format "staticmesh\n"
default: (
messagebox "Not a bf2 mesh file!"
return undefined
)
)
rollout rolWorkingStatus "Importing...." width:224 height:80
(
button btn1 "Please Wait ..." pos:[32,24] width:152 height:40 enabled:false
)
--bf2mdtOps.importMeshFile filename forLightmap s
--bf2file = fopen filename "rb"
createDialog rolWorkingStatus style:#(#style_sysmenu, #style_titlebar)
meshinstance.Load filename
local rootNode = (meshinstance.createInstance forLightmap s)
destroyDialog rolWorkingStatus
return rootNode
)
--test
bf2ImportMeshFile_new undefined 0 10