Page 2 of 10

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-09 15:49
by Harmonikater
Well now it's a different issue, but one I cannot replicate.
Could you possibly send the offending maxfile?

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-09 16:01
by Mineral

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-09 18:19
by Harmonikater
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.) Image 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.

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-09 18:24
by Mineral
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.

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-10 02:25
by Psyrus
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.

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-10 05:50
by Outlawz7
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?

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-10 20:49
by Harmonikater
[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?
This is actually a very good point.

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:
Image

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 02:57
by worldlife
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. :confused:

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")
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:

Code: Select all

on rollbf2test open do
(
	--localization
	btnExport.caption = LOC_EXPORT_btnExport
	chkPause.caption = LOC_EXPORT_chkPause
	...
)
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. :)

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 04:42
by AfterDune
Harmonikator, I don't know where you suddenly come from, but your work is sooo appreciated!! :D

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 06:26
by Harmonikater
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. :confused:
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.

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?
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.ms

Code: Select all

global g_bf2Level_SupportedObjTypes_Noshadow = #("destroyableobject")
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).
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.
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)
[R-DEV]AfterDune wrote:Harmonikator, I don't know where you suddenly come from, but your work is sooo appreciated!! :D
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.

Posted: 2017-08-11 08:31
by Mineral
General rule for destroyables is to have geom0 terrain shadows unless it's a small prop like a cone or sign or something. But those should be removed by the mapper anyway from the on scene

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 08:41
by Rhino
Harmonikater wrote:Consequently, custom LM resolutions are possible with the next version:
http://fh2.cmp-gaming.com/cmp_file_shar ... s/BfLM.JPG
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?

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 11:23
by Heavy Death
And now we can get 1.5 in a matter of days!

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 16:35
by Harmonikater
[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?
Image

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:

Image

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.

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 17:17
by Rhino
Awesome, that's perfect! :D

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-11 17:23
by worldlife
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.
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.
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)
I implement it in maxscript as well (I hate c++:roll :) .
In any case, would you mind sharing your code here?
Sure. The code's a bit long though. Uncomment the last line to have a test.

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

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-12 07:36
by Harmonikater
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.

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-12 08:37
by Outlawz7
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.

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-12 15:16
by FalkeS
This is great! :D

Re: Experimental updated 3dsMax Tools. Newer Max versions support and more.

Posted: 2017-08-14 10:34
by Mats391
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 :p
I have one issue tho, I cannot create templates.
Image
How do I create the first template?