阿赵的MaxScript学习笔记分享十三《导出Obj模型》

文章介绍了如何使用MaxScript编写脚本来导出3DsMax中的Obj格式模型,包括顶点、法线、纹理坐标等信息,并解释了为何自定义导出以及代码中的关键部分,如坐标轴转换、保留小数位数和处理多个对象的索引。此外,还讨论了Obj文件结构和MaxScript中处理数据的一些注意事项,如添加修改器、删除无用数据等。
摘要由CSDN通过智能技术生成

MaxScript学习笔记目录
大家好,我是阿赵。
之前分享了很多MaxScript的学习笔记,但很多细节,我觉得只有在实际应用里面才能讲明白,因为MaxScript包含很多命令,没有办法一一列举,其实也没必要一一列举,因为MaxScript官网上是有帮助手册的,只是手册里面内容太多,反而不好查找,所以我才写了一部分常用的学习笔记。
这次用MaxScript写一个导出Obj格式模型的完整例子。可以把很多内容整合在一起运用一下。
3DsMax本身有导出Obj格式的功能,为什么我还要自己写一个能?这是因为之后我会介绍一些需要配合着模型数据来导出的技术,如果通过3DsMax自带的导出功能,可能会导致某些数据的索引和我需要的不一致。
其实我很多工具都是一时兴起的时候写的,可能写的时候并没有什么用,但说不定在某个时候会觉得还是挺好用的,所以也无所谓了。如果觉得没什么实际用途,也可以当做是写脚本的案例来查看一下

一、完整代码

(
local ShowTips
local FloorNum
local FloorVector
local VectorToString
local VectorPosToString
local GetOneSaveObjContent
local CheckObjHasMeshInfo
local ExportObjFun
local ExportSelectedFun
local ExportAllFun
--习惯加一个工具版本号,可以在出问题的时候检查是哪个版本工具导出的
local toolVerStr = "1.0.0"

--弹窗提示	
fn ShowTips content = 
(
	messagebox content
)

--保留小数位数
--val1:原始数字
--val2:保留小数位数
fn FloorNum val1 val2 = 
(
	local val3 = pow 10 val2
	local val4 = (floor (val1*val3))/val3
	return val4
)

--保留Point的小数位数(比起point,我还是更习惯叫他vector)
--val1:原始的point
--val2:保留小数位数
fn FloorVector val1 val2 = 
(
	local val3 = [(FloorNum val1.x val2),(FloorNum val1.y val2),(FloorNum val1.z val2)]
	return val3
)

--获得Point的保存字符串
fn VectorToString vect = 
(
	local str = (vect.x as string)+" "+(vect.y as string)+" "+(vect.z as string)
	return str
	
)

--获得Point在转换坐标轴之后的保存字符串
--所以y和z要调转,并且y要乘以-1
fn VectorPosToString vect = 
(
	local str = (vect.x as string)+" "+((vect.z) as string)+" "+((vect.y*-1) as string)
	return str
)

--获得一个物体需要保存的数据字符串
--如果同时导出多个物体,顶点坐标、法线、uv等是多个物体的数据排在一起的
--所以要知道当前导出的物体的顶点、法线、uv的开始的索引,以便接着上一个物体的索引
fn GetOneSaveObjContent obj posStartIndex normalStartIndex uvStartIndex = 
(
	select obj
	
	--每个单独物体都加一个对象名称,便于阅读
	local saveContent = "#\n"
	saveContent += ("#  Object "+obj.name+"\n")
	saveContent +="#\n"
	
	--一开始添加了Edit_Mesh和Edit_Normals两个修改器
	--这是因为我们收集的是三角面,所以转成Editable Mesh会方便获取三角面的索引
	--然后为了收集准确的法线数据,所以用Edit_Normal来收集
	--注意两个修改器的添加顺序
	--Edit_Mesh只要在堆栈里面,就可以使用meshop的方法
	--但Edit_Normals一定要在堆栈顶部,才能用相关的方法获取
	local addModNum = 2
	if (getNumTVerts obj.mesh) == 0 then
	(
		--假如物体没有展过UV,下面获取UV信息时会报错,所以强制给加一个Unwrap_UVW修改器
		modPanel.addModToSelection (Unwrap_UVW ()) ui:on
		addModNum = 3
	)
	modPanel.addModToSelection (Edit_Mesh ()) ui:on
	modPanel.addModToSelection (Edit_Normals()) ui:on
	local modN = obj.modifiers[1]

	--收集顶点坐标
	local vertexNum = GetNumVerts obj.mesh
	local vertPosArr = #()
	local vertPosList = #()
	for i in 1 to vertexNum do
	(
		local vertPos = meshop.getvert obj i
		vertPosArr[i] = vertPos
		local findIndex = findItem vertPosList vertPos
		if findIndex == 0 then
		(
			append vertPosList vertPos
		)
	)
	for i in 1 to vertPosList.count do
	(
		saveContent += "v  "
		saveContent += VectorPosToString(vertPosList[i])
		saveContent +="\n"
	)
	saveContent+= "# "+(vertPosList.count as string)+" vertices"
	saveContent+="\n"
	
	--收集法线数据
	--这部分之前的文章介绍过,不再重复介绍
	local myVerts = #{1..vertexNum}
	local myNormals = #{}
	modN.ConvertVertexSelection &myVerts &myNormals
	local normalArr = #()
	local normalList = #()
	for i in myNormals do
	(
		--由于获取的法线向量有很长的小数位数,这里只保留4位小数
		local normalPos = FloorVector (modN.GetNormal i) 4
		normalArr[i] = normalPos
		--由于可能出现很多重复的法线向量,所以这里只保存不重复的
		local findIndex = findItem normalList normalPos
		if findIndex == 0 then
		(
			append normalList normalPos			
		)
	)
	
	saveContent +="\n"
	for i in 1 to normalList.count do
	(
		saveContent += "vn "
		saveContent += VectorPosToString(normalList[i])
		saveContent += "\n"
	)
	saveContent += "# "+(normalList.count as string)+" vertex normals"
	saveContent +="\n"

	--收集UV信息
	local uvNum = getNumTVerts obj.mesh
	local uvArr = #()
	local uvList = #()
	for i in 1 to uvNum do
	(
		local uvPos = getTVert obj.mesh i
		uvArr[i] = uvPos
		--由于可能出现很多重复的UV坐标,所以这里只保存不重复的
		local findIndex = findItem uvList uvPos
		if findIndex == 0 then
		(
			append uvList uvPos
		)
	)
	saveContent +="\n"
	for i in 1 to uvList.count do
	(
		saveContent +="vt "
		saveContent += VectorToString(uvList[i])
		saveContent += "\n"
	)
	saveContent += "# "+(uvList.count as string)+" texture coords"
	saveContent +="\n"
	
	
	
	--收集物体名称
	saveContent +="\n"
	saveContent += "o "+obj.name+"\n"
	saveContent += "g "+obj.name+"\n\n"
	
	--收集三角面数据
	local faceNum = modN.GetNumFaces()

	local faceNormalIdList = #()
	for i in 1 to faceNum do
	(

		local corners = modN.GetFaceDegree i

		local tempFaceNormalList = #()
		local tempVertIndexList = #()
		for j in 1 to corners do
		(
			local norInd = modN.GetNormalID i j
			append tempFaceNormalList norInd
		)
		faceNormalIdList[i] = tempFaceNormalList

	)	

	local faceNum = getNumfaces obj.mesh
	local faceDataList = #()
	for i in 1 to faceNum do
	(
		local posIndexArr = getface obj.mesh i
		local uvIndexArr = getTVFace obj.mesh i
		local normalIndexArr = faceNormalIdList[i]
		local faceStr = ""
		--由于已经添加了Edit_Mesh修改器,所以这里获取的都是三角面,可以默认每个面的顶点数是3
		for j in 1 to 3 do
		(
			--一个面的数据构成:顶点索引/UV索引/法线索引
			local tempPos = vertPosArr[posIndexArr[j] as Integer]
			local posIndex = findItem vertPosList tempPos
			faceStr+=((posIndex as Integer + posStartIndex)  as string)
			faceStr+="/"
			local uvTempData = uvArr[uvIndexArr[j]]
			local uvIndex = findItem uvList uvTempData
			faceStr+=((uvIndex + uvStartIndex)as string)
			faceStr+="/"
			local normalTempData = normalArr[normalIndexArr[j]]
			local normalIndex = findItem normalList normalTempData
			faceStr+=((normalIndex + normalStartIndex)  as string)
			faceStr+=" "
		)
		faceDataList[i] = faceStr
	)
	
	for i in 1 to faceNum do
	(
		saveContent += "f "
		saveContent += faceDataList[i]
		saveContent += "\n"
	)
	saveContent += "# "+(faceNum as string)+" faces \n\n"
	
	--刚才添加了2个修改器,这里用完了,删除它们
	for i in 1 to addModNum do
		deleteModifier obj 1

	
	--由于要返回的内容比较多,所以用了一个数组,分别记录了保存的文本、当前物体顶点数、法线数、uv数
	local objData = #()
	objData[1] = saveContent
	objData[2] = vertPosList.count
	objData[3] = normalList.count
	objData[4] = uvList.count
	return objData
)

--导出一个数组的物体
fn ExportObjFun objs savePath = 
(

	--在文件开头写入工具版本号,以便以后查错
	local saveContent = "#  Export by azhao's Tool\n"
	saveContent += "#  tool version:"+toolVerStr+"\n\n"
	local posStartIndex = 0
	local normalStartIndex = 0
	local uvStartIndex = 0
	for i in objs do
	(
		local objData = GetOneSaveObjContent i posStartIndex normalStartIndex uvStartIndex
		saveContent += objData[1]
		posStartIndex += objData[2]
		normalStartIndex += objData[3]
		uvStartIndex += objData[4]
	)
	
	
	f = createFile savePath
	format saveContent to:f
	close f
	print ("save file to :"+savePath)
)

--打开选择保存路径的窗口
fn GetSaveFilePathFun = 
(
	local savePath = getSaveFileName types:"Obj(*.obj)|*obj"
	if savePath == undefined then
	(
		return undefined
	)
	if (matchPattern savePath pattern:"*.obj") == false then
	(
		savePath+=".obj"
	)
	return savePath
)

--判断一个物体是不是多边形类型的模型,如果不是,则不能导出数据
fn CheckObjHasMeshInfo obj = 
(
	--如果不是GeometryClass,肯定不带网格信息
	if (isKindOf obj GeometryClass) == false then
	(	
		return false
	)
	local objType = classOf obj
	--骨骼也会有mesh信息,这里我不希望导出骨骼所以排除了这两种类型
	--但如果在骨骼上面加Edit_Mesh、Edit_Poly之类的修改器,或者直接塌陷了模型,那么还是可以导出的
	if objType == BoneGeometry or objType == Biped_Object then
		return false
	else
		return true
	
)

--导出选择的物体
fn ExportSelectedFun = 
(
	local selObjs = getcurrentSelection()
	
	if selObjs == undefined or selObjs.count == 0 then
	(
		showTips "No Object Selected!"
		return 0
	)
	local canExportObjs = #()
	for i in 1 to selObjs.count do
	(
		if (CheckObjHasMeshInfo selObjs[i]) == true then
		(
			append canExportObjs selObjs[i]
		)
		
	)
	if canExportObjs.count == 0 then
	(
		showTips "No Mesh Object Selected!"
		return 0
	)		
	
	local savePath = GetSaveFilePathFun()
	if savePath == undefined then
	(
		return 0
	)

	
	ExportObjFun canExportObjs savePath
	showTips ("Success!File Path:"+savePath)
	
)

--导出整个场景所有物体
fn ExportAllFun = 
(
	if objects.count == 0 then
	(
		showTips "No Object"
		return 0
	)
	
	local canExportObjs = #()
	for i in 1 to objects.count do
	(
		if (CheckObjHasMeshInfo objects[i]) == true then
		(
			append canExportObjs objects[i]
		)
		
	)
	if canExportObjs.count == 0 then
	(
		showTips "No Mesh Object Selected!"
		return 0
	)	
	
	local savePath = GetSaveFilePathFun()
	if savePath == undefined then
	(
		return 0
	)
	
	ExportObjFun canExportObjs savePath
	showTips ("Success!File Path:"+savePath)
)


rollout exportObjWin "Export Obj" width:257 height:199
(
	button 'exportSelectBtn' "Export Selected Obj" pos:[43,29] width:161 height:57 align:#left
	button 'exportAllBtn' "Export All" pos:[43,113] width:161 height:57 align:#left
	on exportSelectBtn pressed do
		ExportSelectedFun()
	on exportAllBtn pressed do
		ExportAllFun()
)
createDialog exportObjWin

)

运行脚本,会打开一个窗口:
在这里插入图片描述

我在场景里面乱摆了很多不同类型的对象
在这里插入图片描述

点击ExportAll按钮,会打开保存文件窗口
在这里插入图片描述

选择保存路径之后,会得到一个obj文件
在这里插入图片描述

可以看到,只有网格模型被导出来了

二、Obj格式说明

用3DsMax自带的Obj导出功能,导出一个Box,看看结构:

# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 22.02.2023 14:10:08

mtllib box.mtl

#
# object Box001
#

v  12.5000 0.0000 -12.5000
v  12.5000 0.0000 12.5000
v  -12.5000 0.0000 12.5000
v  -12.5000 0.0000 -12.5000
v  12.5000 25.0000 -12.5000
v  -12.5000 25.0000 -12.5000
v  -12.5000 25.0000 12.5000
v  12.5000 25.0000 12.5000
# 8 vertices

vn 0.0000 -1.0000 -0.0000
vn 0.0000 1.0000 -0.0000
vn 0.0000 0.0000 1.0000
vn 1.0000 0.0000 -0.0000
vn 0.0000 0.0000 -1.0000
vn -1.0000 0.0000 -0.0000
# 6 vertex normals

vt 0.9995 0.0005 0.0000
vt 0.9995 0.3323 0.0000
vt 0.6677 0.3323 0.0000
vt 0.6677 0.0005 0.0000
vt 0.6659 0.3323 0.0000
vt 0.3341 0.3323 0.0000
vt 0.3341 0.0005 0.0000
vt 0.6659 0.0005 0.0000
vt 0.3323 0.9995 0.0000
vt 0.0005 0.9995 0.0000
vt 0.0005 0.6677 0.0000
vt 0.3323 0.6677 0.0000
vt 0.6659 0.6740 0.0000
vt 0.3341 0.6740 0.0000
vt 0.3341 0.3422 0.0000
vt 0.6659 0.3422 0.0000
vt 0.3323 0.6659 0.0000
vt 0.0005 0.6659 0.0000
vt 0.0005 0.3341 0.0000
vt 0.3323 0.3341 0.0000
vt 0.3323 0.3323 0.0000
vt 0.0005 0.3323 0.0000
vt 0.0005 0.0005 0.0000
vt 0.3323 0.0005 0.0000
# 24 texture coords

o Box001
g Box001
usemtl wire_225143087
s 2
f 1/1/1 2/2/1 3/3/1 
f 3/3/1 4/4/1 1/1/1 
s 4
f 5/5/2 6/6/2 7/7/2 
f 7/7/2 8/8/2 5/5/2 
s 8
f 8/9/3 7/10/3 3/11/3 
f 3/11/3 2/12/3 8/9/3 
s 16
f 5/13/4 8/14/4 2/15/4 
f 2/15/4 1/16/4 5/13/4 
s 32
f 6/17/5 5/18/5 1/19/5 
f 1/19/5 4/20/5 6/17/5 
s 64
f 7/21/6 6/22/6 4/23/6 
f 4/23/6 3/24/6 7/21/6 
# 12 faces

1、#开头的行,都是注释,只是起到说明的作用,所以是否导出,怎样导出,按自己喜欢就行
2、mtllib 开头的行,是材质的引用。在3DsMax自带的Obj导出功能,是可以选择是否导出材质的,如果选择了导出材质,那么在导出的Obj文件旁边,会多一个mtl文件。而这个mtllib 就是引用导出来的材质。
但我一直都非常不喜欢模型带着材质导出的,因为在3DsMax里面的材质通用性并不强,到了游戏引擎里面一般都用不上。像fbx之类的格式,如果选择带着材质导出,还会把贴图之类嵌入到fbx文件里面,这样在导入游戏引擎之后,会额外多生成一套默认的材质和贴图,反而不好管理。所以我导出模型,都是拒绝带着材质导出的。既然是这样,那么mtllib 开头的内容,我也不会导出。
3、v开头的行,是顶点坐标,从上往下按顺序索引
4、vn开头的行,是法线向量,是经过去重之后的结果,从上往下按顺序索引
5、vt开头的行,是uv坐标,也是经过去重之后的结果,从上往下按顺序索引
6、需要注意的是,如果一个obj文件里面有多个网格对象,导出的时候会有多套v、vn、vt,看着好像是互相独立的网格数据,实际上它们是连续的,分隔开多个网格对象组显示,只是为了方便查阅而已,一个obj文件里面,假如不按对象分隔,把所有v、vn、vt都写在一起,按顺序从上往下排列,也是一样的。
7、o和g开头的行,代表了Object对象和组,他们会连着下面的f开头的行在一起,这个就不能搞错顺序,因为一个对象下面的f开头的行,代表了这个对象包含的面的信息
8、s开头的行代表光滑组,但由于我们已经按照面来导出了法线信息,其实这个光滑组的作用并不大,可以不用导出也行
9、f开头的行代表一个面,他的后面跟着组成这个面的顶点的信息,一个顶点的数据结构是:顶点索引/UV索引/法线索引,这些用斜杠分割的都是索引,是根据索引编号读取上面的以v、vt、vn开头的行的数据

明白了obj文件结构之后,就可以回头去看我的MaxScript代码,里面的注释也比较的详细,可以看出每个部分的数据是怎样读取的。

三、代码里面需要注意的地方

虽然代码里面注释得比较详细了,但有些细节的地方,还是可以再说一下

1、前后加括号的原因

从代码可以看出,整段代码的开始和结束是被一对小括号括起来的。为什么要这么做呢?
之前介绍过,MaxScript的函数声明是有先后顺序的,如果在函数没有声明之前就调用了,是会报错的。但我们写代码的时候,经常会有A函数里面调用B,同样B函数里面也有调用A的情况。如果必须按顺序声明,那就没办法写下去了。
解决的办法就是在文件开头的时候,先声明一下函数名。当然,可以在文件开头用global来声明所有函数名称,这样函数就变成全局了。但把内部函数变成全局函数来使用并不是一个好习惯,所以我把文件内使用的函数都声明为了local的。
这里又有另外一个问题了,在文件开头,是不允许使用local来声明变量的,不然在编译的时候就会报错。为了解决这个问题,所以我把整个文件用小括号括起来,作为一个整体,那么在括号里面使用local,是允许的。

2、添加工具版本号的习惯

在文件的一开始,我用一个toolVerStr局部变量来定义了一个工具版本号。然后在导出obj文件的一开头,就把是使用哪个工具导出,工具的版本号通过#号注释的方式写到obj文件。
为什么要这么做呢?
既然是写代码,难免有出错或者兼容性不好的时候。当我们拿到一个数据有问题的obj文件的时候,如果我们可以通过文件头备注,知道是什么工具的哪个版本导出的,那么我们就有可能去查,后续版本的工具是否解决了这个问题,或者如果不能升级工具版本,也可以去git或者svn上面找到对应的版本的工具代码,进行局部的修改。
我认为这是一个比较好的习惯。

3、选择保存文件路径的问题

在导出的时候,有一步是通过getSaveFileName方法来打开一个选择保存路径的窗口。
类似的方法还有很多,比如:
选择保存路径的方法:getSavePath
选择打开文件的方法:getOpenFileName

这里我不是想介绍这些方法的具体用途,而是想说一下,getSaveFileName方法带着一个types参数,比如我上面的代码里面,为了让用户只能保存obj格式,我是这么指定的:

local savePath = getSaveFileName types:"Obj(*.obj)|*obj"

如果我需要可以选择obj和fbx文件,或者不指定类型,那么就应该是这么写:

local savePath = getSaveFileName types:"Obj(*.obj)|*obj|Fbx(*.fbx)|*fbx|All|*.*"

通过这个代码,我们可以正常打开窗口,并在下面的类型里面选择
在这里插入图片描述

到这里都没有问题,我们如果选择了一种格式,在上面的窗口,会对文件进行过滤,只显示对应的扩展名的文件。但当返回结果时,问题来了,比如我们下面选择了fbx,上面fileName输入了一个aaa的文件名,按正常人的理解,返回值应该是aaa.fbx才对?但实际上,这个方法只返回了aaa这个文件名,并没有把扩展名带上……
所以下面我自己做了一个判断,如果玩家没有在上面的文件名输入时带上扩展名,我就自动帮他补上:

if (matchPattern savePath pattern:"*.obj") == false then
	(
		savePath+=".obj"
	)
这里是比较需要

注意的地方。

4、过滤场景里面需要导出的物体

我上面的例子里面,场景上除了放了球、圆柱体、盒子以外,还放了二维线条、灯光、摄像机、骨骼、辅助体等的对象。
我们通过getcurrentSelection()方法获取当前选择的物体,或者通过objects获得场景中所有的物体时,并不是所有的对象都应该导出的。
比如二维线条、灯光、摄像机、帮助体这些类型的物体对象,身上并没有网格模型数据,是根本不需要导出的。
骨骼对象有点特殊,他身上是带着网格数据的。但我这里只是想导出模型本身的网格模型作为渲染的对象,明显骨骼是不需要参与实际的渲染的,而Obj文件也并不能导出蒙皮数据,所以在导出Obj文件的时候,骨骼也不应该导出网格数据。
这里我通过了判断物体对象的类型来判断对象是否应该导出
这里需要知道几个方法:
1.isKindOf
判断一个物体对象是否属于某种类型
2.classOf
获取一个物体对象当前的类型
3.superClassOf
获取一个物体对象的父类型
当前类型指的是box、Sphere、Editable_mesh、Circle、freeSpot这些在堆栈最顶端的具体的类型
查询MaxScript的帮助文档,可以看到这个关系:
在这里插入图片描述

于是我可以知道,通过isKindOf obj GeometryClass可以过滤掉一些类型,因为父类不是GeometryClass的,肯定没有网格信息
但骨骼的类型也属于GeometryClass,所以再用classOf获取物体当前的类型,判断它不是BoneGeometry和Biped_Object这些骨骼类型。
但物体的类型是会改变的,比如二维线条,如果塌陷为可编辑网格,他就会变成GeometryClass,骨骼上面加Edit_Mesh、Edit_Poly之类的修改器,或者直接塌陷了模型,那么他的类型也会相应的变成Editable_mesh,那么还是可以导出的,因为既然塌陷了,那么证明这个模型网格是需要渲染的。

5、保留小数的问题

在代码里面有这么一个保留小数的方法:

--保留小数位数
--val1:原始数字
--val2:保留小数位数
fn FloorNum val1 val2 = 
(
	local val3 = pow 10 val2
	local val4 = (floor (val1*val3))/val3
	return val4
)

这种方式其实是因为我没找到可以直接保留小数位数的函数,所以采用了先乘再取整再除的形式去保留小数位数。
比如要保留3位小数,那么就把原来的数乘以1000后取整,再除以1000.
这里还有另外的一个问题,MaxScript自带的取整函数,只有向下取整的floor和向上取整的ceil,没有四舍五入的round。如果实在想用round,可以考虑调用.Net的方法。

6、为什么要转换坐标轴

在保存顶点坐标和法线向量的时候,我做了一个调换轴的处理,把y和z对调,并对y乘以-1。
为什么要这么操作呢?这是因为我这个obj文件是打算用于Unity引擎的。但3DMAX中使用的是右手坐标系,而且以Z轴向上,但是Unity则使用的左手坐标系,而且以Y轴向上。所以这里必须做一个转换。其实自带的导出工具里面,也会有这些坐标轴变换的选项。
在这里插入图片描述

7、为什么要添加几个修改器

一开始添加了Edit_Mesh和Edit_Normals两个修改器,这是因为我们收集的是三角面,所以转成Editable Mesh会方便获取三角面的索引
然后为了收集准确的法线数据,所以用Edit_Normal来收集
注意两个修改器的添加顺序,Edit_Mesh只要在堆栈里面,就可以使用meshop的方法,但Edit_Normals一定要在堆栈顶部,才能用相关的方法获取
假如物体没有展过UV,下面获取UV信息时会报错,所以如果判断UV点数量为0时,强制给加一个Unwrap_UVW修改器,等于是自动展一次UV,不过由于Edit_Normal需要在堆栈最上面,所以Unwrap_UVW要加在前面。
最后,根据前面加了多少个修改器,后面就把他们全删掉,因为我们只是为了获取数据,获取完之后,这些修改器就不需要了,可以还原成没添加之前的状态。

8、通过数组作为函数返回值

在GetOneSaveObjContent函数的最后,我返回了一个objData的数组。我在写这个函数的时候,本来只是想获得单个物体对象保存时的数据的字符串的,但由于同一个Obj文件里面如果有多个对象,他们的顶点和法线都是排在一起的,比如第一个物体有8个顶点,那么第二个物体的顶点序号就应该是从9开始算的。所以我必须返回当前物体的顶点、法线、UV的索引,好让下一个物体接着序号去排序。
如果是其他语言,我可以构造一个数据对象,然后把所有信息都赋值给这个对象然后返回。但在MaxScript里面我暂时没有找到类似的做法,幸好MaxScript的数组不是单一类型对象的,可以在同一个数组里面放入不同类型的对象。所以,我就用一个数组模拟了一个数据结构,通过不同下标来赋值和读取对应想要的数据了。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值