vBF2/PR 3dsMax Tools - Import/Export/Lightmaps/Animation - Max 9 and newer!
-
Harmonikater
- PR:BF2 Contributor
- Posts: 44
- Joined: 2017-06-11 20:23
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Well now it's a different issue, but one I cannot replicate.
Could you possibly send the offending maxfile?
Could you possibly send the offending maxfile?
- Mineral
- Retired PR Developer
- Posts: 8534
- Joined: 2012-01-02 12:37
- Location: Belgium
-
Harmonikater
- PR:BF2 Contributor
- Posts: 44
- Joined: 2017-06-11 20:23
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
This is acually a import bug (at least in the scene above), somehow a material is being given information that neither the bf2 materials nor the exporter can handle. (Somehow there's a bump texture with a map that has no filename, hence the string errors.)
Don't know why yet.
Export worked fine for me after replacing the offending material (the one with only objects\vehicles\common\textures\ger_vehicleglass_c.dds). with a new one (with the same texture map) from scratch.
I did find a bug regarding alpha materials that resulted in much more materials being created
than necessary, but it doesn't seem to be related. (But good that I found it in the process of looking for the other one, so thanks for that)
I can't reproduce the same import with the 341 or gazelle thats in the PR files. And either i'm blind or the 342 isn't the files yet. I'd need those as well to check why the import is mucking up.
Export worked fine for me after replacing the offending material (the one with only objects\vehicles\common\textures\ger_vehicleglass_c.dds). with a new one (with the same texture map) from scratch.
I did find a bug regarding alpha materials that resulted in much more materials being created
than necessary, but it doesn't seem to be related. (But good that I found it in the process of looking for the other one, so thanks for that)
I can't reproduce the same import with the 341 or gazelle thats in the PR files. And either i'm blind or the 342 isn't the files yet. I'd need those as well to check why the import is mucking up.
- Mineral
- Retired PR Developer
- Posts: 8534
- Joined: 2012-01-02 12:37
- Location: Belgium
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Yeah 342 is something new we are trying to get ingame
I noticed the duplicate alpha materials as well. Wasn't sure if it was worth reporting. The ger_vehicleglass material is something I made I think. Wasn't by import. Was trying to replace the current glass material with a new one. So maybe something to do with a new BF2 material being made with that faulty parameter? Or when making a copy of another material and removing the bump map? Just speculating what might have happened.
-
Psyrus
- Retired PR Developer
- Posts: 3841
- Joined: 2006-06-19 17:10
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Incredible work! I'm floored by this, I never thought anyone would go to the effort of recreating (never mind improving!) all this functionality for the newer versions.
-
Outlawz7
- Retired PR Developer
- Posts: 17261
- Joined: 2007-02-17 14:59
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
I haven't had the chance to check this out yet but did anyone try the OG lod tool mentioned in OP?
Harmonikater, can you also add these scripts to the tools menu?
meshcheck.ms - Checks mesh for thin triangles. Use this to pinpoint custom lightmap UV errors.
objexp.ms - A minimal Wavefront OBJ exporter that doesn't suck. Use this for BF2 undergrowth.
Battlefield Mod Tools
As said, being able to import and lightmap destroyables would be (even more) awesome since until now that's something 3ds Max LM tools couldn't do.
Also while at the moment we can export static objects with custom LM UVs, we still need to use BFMeshView to generate samples for them, any way to add generating custom LM UV samples into exporter?
Harmonikater, can you also add these scripts to the tools menu?
meshcheck.ms - Checks mesh for thin triangles. Use this to pinpoint custom lightmap UV errors.
objexp.ms - A minimal Wavefront OBJ exporter that doesn't suck. Use this for BF2 undergrowth.
Battlefield Mod Tools
As said, being able to import and lightmap destroyables would be (even more) awesome since until now that's something 3ds Max LM tools couldn't do.
Also while at the moment we can export static objects with custom LM UVs, we still need to use BFMeshView to generate samples for them, any way to add generating custom LM UV samples into exporter?

-
Harmonikater
- PR:BF2 Contributor
- Posts: 44
- Joined: 2017-06-11 20:23
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
This is actually a very good point.[R-DEV]Outlawz7 wrote:Also while at the moment we can export static objects with custom LM UVs, we still need to use BFMeshView to generate samples for them, any way to add generating custom LM UV samples into exporter?
Bad news is that I can't touch the actual sample generation since that's done in the SceneParse binary.
However, I generally assume that BfMeshView generates better samples anyway. So I went ahead and packed the relevant BfMeshView code into a small command line tool that generates samples based on a couple of input parameters, in the same way that BfMeshView does.
Therefore any staticmesh export will automatically have BfMeshView-like samples by default. (Though the old SceneParse samples can still be picked by adjusting a setting in the Setup window.)
Consequently, custom LM resolutions are possible with the next version:
-
worldlife
- Posts: 15
- Joined: 2016-06-27 00:07
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Hi Harmonikater,
I've been working on improving this 3dsmax tool this summer, and when I see this post.. OMG You have done almost everything I want to do/have done, and many things I didn't consider to do! The alternative import, support for higher versions, what a coincidence isn't it! Excellent work!
Anyway, I'll just talk about what I've done that you haven't done.
1.My importer(also based on Remdul's meshman code) supports skinnedmesh import with skin modifier. The skin info can be inferred from the blend weight part in vertex info. Also it supports bundlemesh import with animated UV setup and skin modifier, which info can be inferred from the blend indices part and uv2 part in vertex info.
By the way, did you solve the smoothgroup problem? Like when you import and export vBF2 RUTNK_T90, the main barrel's smoothgroup is destroyed.
2.About lightmapping destroyable object. Destroyableobjects lightmaps itself and do not cast shadows, so I use exclusion in light objects' shadow settings(or maybe global shadow settings is better). Just add this in StaticObjects_Import.ms
and add object type check in bf2LoadObjCon, and name them with a "noshadow_" prefix. And when importing the sun, add those objects with "noshadow_" prefix to the excludeList of the light and set inclExclType 2(shadow exclude).
3.Changed some rollout codes to provide Localization support. This is done by changing the caption of the rollout objects on open. Like this:
LOC_EXPORT_btnExport is a global string value stored in a localization file. Choose file to include according to the language setting.
Feel free to contact me if you need more details.
I've been working on improving this 3dsmax tool this summer, and when I see this post.. OMG You have done almost everything I want to do/have done, and many things I didn't consider to do! The alternative import, support for higher versions, what a coincidence isn't it! Excellent work!
Anyway, I'll just talk about what I've done that you haven't done.
1.My importer(also based on Remdul's meshman code) supports skinnedmesh import with skin modifier. The skin info can be inferred from the blend weight part in vertex info. Also it supports bundlemesh import with animated UV setup and skin modifier, which info can be inferred from the blend indices part and uv2 part in vertex info.
By the way, did you solve the smoothgroup problem? Like when you import and export vBF2 RUTNK_T90, the main barrel's smoothgroup is destroyed.
2.About lightmapping destroyable object. Destroyableobjects lightmaps itself and do not cast shadows, so I use exclusion in light objects' shadow settings(or maybe global shadow settings is better). Just add this in StaticObjects_Import.ms
Code: Select all
global g_bf2Level_SupportedObjTypes_Noshadow = #("destroyableobject")3.Changed some rollout codes to provide Localization support. This is done by changing the caption of the rollout objects on open. Like this:
Code: Select all
on rollbf2test open do
(
--localization
btnExport.caption = LOC_EXPORT_btnExport
chkPause.caption = LOC_EXPORT_chkPause
...
)Feel free to contact me if you need more details.
-
AfterDune
- Retired PR Developer
- Posts: 17094
- Joined: 2007-02-08 07:19
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Harmonikator, I don't know where you suddenly come from, but your work is sooo appreciated!! 

-
Harmonikater
- PR:BF2 Contributor
- Posts: 44
- Joined: 2017-06-11 20:23
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
That's something I've wanted to include but haven't had the time yet. It wasn't super high priority for me, since skinnedmesh imports tend to be not very useful imo.worldlife wrote: 1.My importer(also based on Remdul's meshman code) supports skinnedmesh import with skin modifier. The skin info can be inferred from the blend weight part in vertex info. Also it supports bundlemesh import with animated UV setup and skin modifier, which info can be inferred from the blend indices part and uv2 part in vertex info.
By the way, did you solve the smoothgroup problem? Like when you import and export vBF2 RUTNK_T90, the main barrel's smoothgroup is destroyed.![]()
If you're going to change the actual mesh the skin modifier will likely go to hell anyway.
I guess if you want to change just the weights on some parts, that could work, but you'd still have the additional vertices where the exporter detached due to smoothing group changes.
Unless one were to manually weld verts during import while keeping the smoothing groups intact and then applying the imported skin weights based on vertex position or something like that. But I'm afraid that could become fairly complex and resource-intensive pretty quickly.
Of course it would also benefit normal non-weighted meshes in the end as well.
But in my experience just letting max weld and autosmooth leads to too many errors.
Think of a small cylinder whose ends the player doesn't see, there's no problem in making it 4-sided with one smoothing group around. That create obvious problems for autosmooth.
Then again I'm doing everything in maxscript, if this were done in SDK the entire import would probably a hell of a lot quicker. Unfortunately I have no experience with max SDK, but maybe you do? (You didn't mention how you implemented your importer)
In any case, would you mind sharing your code here?
I've already done something like this for Object children that shouldn't cast shadows (also on a per-object basis), like RotationalBundles, i.e. for FH2 mostly windmill sails.worldlife wrote: 2.About lightmapping destroyable object. Destroyableobjects lightmaps itself and do not cast shadows, so I use exclusion in light objects' shadow settings(or maybe global shadow settings is better). Just add this in StaticObjects_Import.msand add object type check in bf2LoadObjCon, and name them with a "noshadow_" prefix. And when importing the sun, add those objects with "noshadow_" prefix to the excludeList of the light and set inclExclType 2(shadow exclude).Code: Select all
global g_bf2Level_SupportedObjTypes_Noshadow = #("destroyableobject")
I guess with destroyableObjects it's again a matter of what's the least bad option, since there is no perfect one. Do we want there to be no shadows cast at all onto terrain and surrounding statics? (In FH2 there's a big destroyable Anti-Tank wall which stands in line with other Or assume that the Object will spend more time being alive than destroyed and have only geom0 cast shadows? (In which case we'd have to make sure that geom0 isn't casting shadows onto its own geom1) What's the usual opinion on this? (Again don't have a mapping background, so don't know how this is commonly handled)
Thanks! I've mostly been lurking on the PR forums for the Tutorials and whatnot, (especially while I was starting out), since it's a much more comprehensive collection than over at FH2.[R-DEV]AfterDune wrote:Harmonikator, I don't know where you suddenly come from, but your work is sooo appreciated!!![]()
-
Rhino
- Retired PR Developer
- Posts: 47909
- Joined: 2005-12-13 20:00
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Nice, but is that for generating samples off of custom LM UVs or just for automatic LM UVs?Harmonikater wrote:Consequently, custom LM resolutions are possible with the next version:
http://fh2.cmp-gaming.com/cmp_file_shar ... s/BfLM.JPG
If it is for custom LM UVs which would be really nice, there are no options for each LOD are there?
-
Heavy Death
- Posts: 1303
- Joined: 2012-10-21 10:51
-
Harmonikater
- PR:BF2 Contributor
- Posts: 44
- Joined: 2017-06-11 20:23
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
[R-DEV]Rhino wrote:Nice, but is that for generating samples off of custom LM UVs or just for automatic LM UVs?
If it is for custom LM UVs which would be really nice, there are no options for each LOD are there?
Alright we're getting closer. What I've done now is include a "User" option in the dropdown list. I assume that if one wants custom sizes for each lod, they sort of know what they're doing. Well, enough to trust them to rename lod nodes themselves.
So in case "User" selected, one can add the requested size to each lod node (including lods under possible different geoms, e.g. if we're talking destroyableObjects) such:
Can either be single values (-> square textures) or two values separated by *
This applies for both automatic and custom LM UVs.
One caveat I found is that different nodes underneath the same lod cannot differ regarding the LM UV channel, as in if one node has custom UVs, all other nodes need custom UVs as well. However I think that's a fairly rare case, considering StaticMeshes are mostly just a single node, and that somebody who makes custom UVs probably does them for all nodes anyway.
-
Rhino
- Retired PR Developer
- Posts: 47909
- Joined: 2005-12-13 20:00
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Awesome, that's perfect! 
-
worldlife
- Posts: 15
- Joined: 2016-06-27 00:07
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
About welding vertices, I planned to group all vertices that have the same position together before creating the mesh and hence creating a mesh that have no duplicated vertices. But yeah that's a lot of work -- UV vertices must be considered too, and I'm a little confused about uv vertices in 3dsmax.Harmonikater wrote:That's something I've wanted to include but haven't had the time yet. It wasn't super high priority for me, since skinnedmesh imports tend to be not very useful imo.
If you're going to change the actual mesh the skin modifier will likely go to hell anyway.
I guess if you want to change just the weights on some parts, that could work, but you'd still have the additional vertices where the exporter detached due to smoothing group changes.
Unless one were to manually weld verts during import while keeping the smoothing groups intact and then applying the imported skin weights based on vertex position or something like that. But I'm afraid that could become fairly complex and resource-intensive pretty quickly.
Of course it would also benefit normal non-weighted meshes in the end as well.
But in my experience just letting max weld and autosmooth leads to too many errors.
Think of a small cylinder whose ends the player doesn't see, there's no problem in making it 4-sided with one smoothing group around. That create obvious problems for autosmooth.
I implement it in maxscript as well (I hate c++:rollThen again I'm doing everything in maxscript, if this were done in SDK the entire import would probably a hell of a lot quicker. Unfortunately I have no experience with max SDK, but maybe you do? (You didn't mention how you implemented your importer)
Sure. The code's a bit long though. Uncomment the last line to have a test.In any case, would you mind sharing your code here?
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
-
Harmonikater
- PR:BF2 Contributor
- Posts: 44
- Joined: 2017-06-11 20:23
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
Hm, regarding the BundledMesh Skin import, I'm actually not sure anymore it's possible to always get it correctly, without having everything remain one single mesh (as you're doing right now). Since really all the skin mod does on export is change which node the vertex belongs to, leaving no information which it belonged to originally in the scene.
I guess maybe if we do weld identical vertices and then afterwards split off only those vertices belonging to a node other then the main node that represent an independent element. Assuming of course that skin is only ever applied to nodes that are connected to the main mesh, which should mostly be the case for tank threads.
However mostly not the case for dampers on retractable landing gears on aircraft, I don't think we can get those correctly at all...
Also, skinnedmesh import seems to work great! I'll try to integrate that somehow, since I think it will be useful for 3P animating.
I guess maybe if we do weld identical vertices and then afterwards split off only those vertices belonging to a node other then the main node that represent an independent element. Assuming of course that skin is only ever applied to nodes that are connected to the main mesh, which should mostly be the case for tank threads.
However mostly not the case for dampers on retractable landing gears on aircraft, I don't think we can get those correctly at all...
Also, skinnedmesh import seems to work great! I'll try to integrate that somehow, since I think it will be useful for 3P animating.
-
Outlawz7
- Retired PR Developer
- Posts: 17261
- Joined: 2007-02-17 14:59
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
I thought of a small feature request. In Rhino's LM tutorial there's a section where staticobjects.con is imported but you have to move the folders out so 3ds Max doesn't import actual meshes, just positions as helpers. Could you add that as a script, importing positions only from staticobjects.con? I know you improved importing OG for LMing here, but I found other uses for importing positions.

-
FalkeS
- Posts: 51
- Joined: 2016-02-12 09:31
- Mats391
- PR:BF2 Lead Developer
- Posts: 7643
- Joined: 2010-08-06 18:06
Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.
The new animation tools are really nice. I am no animator myself, but since we currently have none I often have to do minor adjustments and fixes for new weapons and such. The new tools will help a lot with that especially having the soldier mesh in 3p, something our past animators never bothered to add to their scenes 
I have one issue tho, I cannot create templates.

How do I create the first template?
I have one issue tho, I cannot create templates.

How do I create the first template?

Mineral: TIL that Wire-guided missiles actually use wire


