TLoad3DSMax


Croquet-Teapot

Comment:

TLoad3DSMax Notes:

There are two parts to the class - the parse routines and everything else. The actual #parse: method creates a block hierarchy - essentially a kind of parse tree, from the inputs. 3DS is somewhat regular in its construction, but there are a few things to be careful about. The tree that gets constructed is made up of a field name and a field. The field names are tokenized 3DS field names and the field is the child tree or the actual text field data - not parsed into actual numbers or anything yet. One improvement on performance might be to interleave this step with the next.

Once the tree exists, a second pass is made that converts the text fields and constructs the actual frame hierarchy. The field name of each node is matched and the appropriate #make****: routine is called on its contents. 

Once a raw mesh is set up inside the #makeGeometry: method, the #reconstruct: method is called. This does a lot of massaging and optimization of the raw vertices - including aliasing, etc. 

Anyway, start at:
	#initialize: fileName: scale: shadeAngle: textureMode: 

Based upon the field names in the tree constructed in #parse: you just make the call to the next #makeXXX:.

- Though we have all of the transforms for the heirarchy, the actual locations of the meshes is already transformed. If I can think of a good reason to un-transform the mesh elements and then add the transform to the actual frame, I will do it. For now, treat it as a solid body.

- The texture rotations are face centered. This requires an offset of 0.5, the rotation, and then putting it back. Not sure if I really want to pre-transform the texture uv coordinates. Also, not sure if this is already done, as the meshes are.

- This class will act as a template for importers, though they all seem so different that this may be easier said than done.

Hierarchy:

ProtoObject
Object
TObject
TFrame
TLoad3DSMax

Summary:

instance variables:

faces filePath frame inverseTransform matIndex materialList materialRef scale shadeAngle shadeAngleCos stringLoc textureList textureMode tfaces transform tvertices vertices

class variables:

DelimSet1 DelimSet2 DelimSet3 NumberSet WhiteSpaceSet

Pool:

OpenGLConstants

methods:

instance class
accessing convert initialize parse class initialization instance creation

Detail:

instance variables:

faces
filePath
frame
inverseTransform
matIndex
materialList
materialRef
scale
shadeAngle
shadeAngleCos
stringLoc
textureList
textureMode
tfaces
transform
tvertices
vertices

class variables:

DelimSet1
DelimSet2
DelimSet3
NumberSet
WhiteSpaceSet

instance methods:

accessing
frame


	frame class = TMesh ifTrue:[^ frame.].
	frame frameChildren ifNotNil:[
		frame frameChildren size = 1 ifTrue:[ ^ frame frameChildren at: 1. ].].
	^ frame.
scale


	^ scale.
scale: scl


	scale _ scl.
shadeAngle


	^ shadeAngle radiansToDegrees.
shadeAngle: angle


	shadeAngle _ angle degreesToRadians.
	shadeAngleCos _ shadeAngle cos.

convert
aliasIndexToArray: ai


	| alias |

	alias _ IntegerArray ofSize: ai size.
	1 to: ai size do:[ :i |
		(ai at: i) ifNil:[alias at: i put: 0.] 
				ifNotNil:[alias at: i put: (ai at: i).].
		].

	^ alias.
basicCalcFaceNormals


"Calculate the normal for each primitive (simple vertex index) face."
	| v1 v2 v count faceNormals |

	faceNormals _ B3DVector3Array  new: (faces size//3).
	count _ 1.
	1 to: faceNormals size do:[   :i |
		v1 _ (vertices at:(faces at: count)+1) - (vertices at: (faces at: count+1)+1).
		v2 _ (vertices at:(faces at: count+1)+1) - (vertices at: (faces at: count+2)+1).
		v _ v1 cross: v2.
		v length = 0.0 ifFalse:[ v_ v normalized.].
		faceNormals at: i put: v.
		count _ count+3.].

	^ faceNormals.
basicCalcVertexNormals: faceNormals


"Calculate the normal for each face vertex."
	| vertexNormals vertexCount iCount faceIndex1 faceIndex2 fv1 fv2 edges index1 |
"Initialize the vertexNormals with the owning face normal."
	vertexNormals _ B3DVector3Array  new: (faces size).
	vertexCount _ IntegerArray new: (faces size).
	iCount _ 1.
	1 to: faces size by: 3 do:[ :i |
		vertexNormals at: i put: (faceNormals at: iCount).
		vertexNormals at: i+1 put: (faceNormals at: iCount).
		vertexNormals at: i+2 put: (faceNormals at: iCount).
		iCount _ iCount+1.].
"vertexCount is set to zero at the moment. It should be 1, but we know that it is always 1 greater than whatever our end result will be below."
	"self halt."

	1 to: faces size do:[:i |
		i+1 to: faces size do:[:j |
			(faces at: i) = (faces at: j) ifTrue:[
				faceIndex1 _ 1 + ((i-1)//3).
				faceIndex2 _ 1 + ((j-1)//3).
				(self testNorm: (faceNormals at: faceIndex1) norm: (faceNormals at: faceIndex2))
					ifTrue:[
						vertexNormals at: i put: ((vertexNormals at: i) + (faceNormals at: faceIndex2)).
						vertexCount at: i put: 1+ (vertexCount at: i).
						vertexNormals at: j put: ((vertexNormals at: j) + (faceNormals at: faceIndex1)).
						vertexCount at: j put: 1+ ( vertexCount at: j).
						].
				].
			].
		].
false ifTrue:[
"Build an edge dictionary"
	edges _ Dictionary new.
	1 to: faces size by: 3 do:[ :i |
		edges at: (faces at:i)@(faces at:i+1) put: i@(i+1).
		edges at: (faces at: i+1)@(faces at: i+2) put: (i+1)@(i+2).
		edges at: (faces at: i+2)@(faces at: i) put: (i+2)@i].

"Now match edges"
	faceIndex2 _ 1.
	1 to: faces size by: 3 do:[ :i |
		index1 _ edges at: ((faces at:i+1)@(faces at:i)) ifAbsent:[ nil ].
		index1 ifNotNil:[ fv1 _ i+1. fv2 _ i.] ifNil:[
			index1 _ edges at: ((faces at: i+2)@(faces at: i+1)) ifAbsent:[ nil. ].
			index1 ifNotNil:[ fv1 _ i+2. fv2 _ i+1.] ifNil:[
				index1 _ edges at: ((faces at: i)@ (faces at: i+2)) ifAbsent:[ nil. ].
				index1 ifNotNil:[fv1 _ i. fv2 _ i+2.].
			].].

		index1 ifNotNil:[
			faceIndex1 _ 1+((index1 x-1)//3).
			(self testNorm: (faceNormals at: faceIndex1) norm:(faceNormals at: faceIndex2))
				ifTrue:[
					vertexNormals at: fv1 put: ((vertexNormals at: fv1) + (faceNormals at: faceIndex1)).
					vertexNormals at: fv2 put: ((vertexNormals at: fv2) + (faceNormals at: faceIndex1)).
					vertexCount at: fv1 put: 1+(vertexCount at: fv1).
					vertexCount at: fv2 put: 1+(vertexCount at: fv2).
					fv1 _ index1 x.
					fv2 _ index1 y.
					vertexNormals at: fv1 put: ((vertexNormals at: fv1) + (faceNormals at: faceIndex2)).
					vertexNormals at: fv2 put: ((vertexNormals at: fv2) + (faceNormals at: faceIndex2)).].
					vertexCount at: fv1 put: 1+(vertexCount at: fv1).
					vertexCount at: fv2 put: 1+(vertexCount at: fv2).
				].						
		faceIndex2 _ faceIndex2 + 1.
		].
].
" Calculate the average normal and normalize."
	1 to: vertexNormals size do:[ :i |
		(vertexNormals at: i) length = 0.0 ifFalse:[
		vertexNormals at:i put: ((vertexNormals at: i)/(1.0 + (vertexCount at: i)))normalized.].].

	^ vertexNormals.
calcFaceNormals


"Calculate the normal for each primitive (simple vertex index) face."
	| faceNormals |

	faceNormals _ B3DVector3Array  new: (faces size//3).
	^ self primCalcFaceNormals: vertices faces: faces faceNormals: faceNormals.
calcTextureVertices


" This is a somewhat naive fix to the texture transform issue. Be aware that I am not checking for shared texture coordinates. This means that if two textures share the same uv coordinate before the transform but not after, we have a problem. I don't know what 3DS Max does, so I will deal with it when I find the problem."
	| tv ml counter tv2 fvi avi ai found oc |

	ml _ materialList at: 1+ materialRef.
	ml subMaterialList ifNotNil:[ ml _ ml subMaterialList.] 
	ifNil:[
		oc _ OrderedCollection new.
		oc add: ml.
		ml _ oc.
		].
	tv _ B3DVector2Array ofSize: tfaces size.
	1 to: tfaces size do:[ :i |
		counter _ 1+(i-1//3).
		
		tv at: i put: (self textureTransform: (tvertices at: 1+(tfaces at: i)) 
			material: (ml at: 1+((matIndex at: counter)\\ml size))).
		].

	tv2 _ OrderedCollection ofSize: tvertices size.
	ai _ OrderedCollection ofSize: tvertices size.
	1 to: tfaces size do:[ :i | 
		found _ false.
		fvi _ 1+ (tfaces at: i).
		avi _ fvi.
		[ found ] whileFalse:[
			"Anything already in this slot?"
			(tv2 at: avi) ifNil:[
				tv2 at: avi put: (tv at:i).
				tfaces at: i put: avi -1.
				found _ true.].
			"Is it the same thing we already have?"
			found ifFalse:[
				(tv2 at: avi) = ( tv at: i) ifTrue:[
					tfaces at: i put: avi-1.
					found _ true.].].
			"There is something already there, but it is the wrong thing, we need an alias"
			found ifFalse:[
				(ai at: avi) ifNil:[
					ai add: nil.
					tv2 add: (tv at: i).
					tfaces at: i put: (tv2 size -1). "index of new element - 1"
					ai at: avi put: tv2 size. "pointer to new element"
					found _ true.] 
				"The alias was full, check the next alias."
				ifNotNil:[ avi _ ai at: avi.].].].].

	" Convert array."
	tvertices _ B3DVector2Array ofSize: tv2 size.
	1 to: tv2 size do:[ :i | (tv2 at: i) ifNotNil:[ tvertices at: i put: (tv2 at: i).].].
				
calcVertexNormals: faceNormals


"Calculate the normal for each face vertex."
	| vertexNormals vertexCount |
"Initialize the vertexNormals with the owning face normal."
	vertexNormals _ B3DVector3Array  new: (faces size).
	vertexCount _ IntegerArray new: (faces size).
	^ self primCalcVertexNormals: vertexNormals vertexCount: vertexCount faces: faces faceNormals: faceNormals shadeAngleCos: shadeAngleCos.
facesToFaceGroup: fcs materials: mi


	| groups count mIndex gIndex |
	
" This method creates an OrderedCollection of alternating material indices followed by the face group."

	groups _ OrderedCollection new.

	count _ 1.
	1 to: fcs size by: 3 do:[ :i |
		mIndex _ 1+ (mi at: count).
		count _ count+1.
		gIndex _ 0.
		" Which material group do I belong to?"
		1 to: groups size by: 2 do:[ :j |
			(mIndex = (groups at: j)) ifTrue:[gIndex _j].].
		"No such material group, we need to add a new one."
		gIndex = 0 ifTrue:[ 
			groups add: mIndex. 
			groups add: OrderedCollection new. 
			gIndex _ groups size -1.].
		gIndex _ gIndex+1.
		(groups at: gIndex) add: (fcs at:i).
		(groups at: gIndex) add: (fcs at: i+1).
		(groups at: gIndex) add: (fcs at: i+2).].
			
	1 to: groups size by: 2 do:[ :i |
		groups at: i+1 put:(groups at: i+1) asIntegerArray.].
	^ groups.
makeHelperObject: tree


	| field fieldName name start end |
	1 to: tree size by: 2 do:[ :i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		fieldName = #TNodeName ifTrue:[
			start _ field findDelimiters: '"' startingAt: 1.
			end _ field findDelimiters: '"' startingAt: start+1.
			name _ (field copyFrom: start+1 to: end-1).
			].
		fieldName = #TNodeTm ifTrue:[
			transform _ self makeTransform: field.
			].
		].
	^ name.
		
primCalcFaceNormals: argVertices faces: argFaces faceNormals: argFaceNormals


	<primitive: 'primitiveCalcFaceNormals' module: 'TLoad3DSMaxPlugin'>
	^ TLoad3DSMaxPlugin doPrimitive: 'primCalcFaceNormals:faces:faceNormals:' withArguments: {argVertices. argFaces. argFaceNormals}.

primCalcVertexNormals: argVertexNormals vertexCount: argVertexCount faces: argFaces faceNormals: argFaceNormals shadeAngleCos: shadeAngleCosValue



	<primitive: 'primitiveCalcVertexNormals' module: 'TLoad3DSMaxPlugin'>
	^ TLoad3DSMaxPlugin doPrimitive: 'primCalcVertexNormals:vertexCount:faces:faceNormals:shadeAngleCos:' withArguments: {argVertexNormals. argVertexCount. argFaces. argFaceNormals. shadeAngleCosValue}.

reconstruct


"This method figures out how to reconstruct the mesh based solely upon the face information - material, vertex indices, vertex normals, and texture indices. It creates a group of sub-meshes which are then properly rendered. I realize that this could and should be broken down into many smaller methods, but I don't want to deal with creating throw-away classes that would only be used to help this method. On the other hand, I am very aware that there is a limit on the actual size of a method, so I might have to anyway."

	| faceNormals vertexFaceNormals vi ai ti mi vNorm fvi tvi avi mvi counter found faceGroup mm |

" First, make the face normals and vertex normals. "

	faceNormals _ self calcFaceNormals.
	vertexFaceNormals _ self calcVertexNormals: faceNormals.
	"self halt."
	tfaces ifNotNil:[self calcTextureVertices.].

"Faces now include the following information:
	- 1 Material index - matIndex
	- 3 Vertex indices - faces
	- 3 Vertex normals - vertexFaceNormals
	- 3 texture indices - tfaces
We build a new group of submeshes.
We first look at sub-groups according to materials. Then we compare vertices to build the final face tables.
A vertex is considered equal iff the index, normal, and texture index are identical. Otherwise, it must be aliased."

vi _ OrderedCollection ofSize: vertices size.
ai _ OrderedCollection ofSize: vertices size.
ti _ OrderedCollection ofSize: vertices size.
mi _ OrderedCollection ofSize: vertices size.
vNorm _ OrderedCollection ofSize: vertices size.


1 to: faces size do:[ :i |
	found _ false.
	fvi _ 1+ (faces at: i).
	avi _ fvi.
	tfaces ifNotNil:[tvi _ 1 + (tfaces at: i).] ifNil:[tvi _ 1.].
	counter _ 1+(i-1//3).
	mvi _ matIndex at: counter.
	[found] whileFalse:[
		"Anything already in this slot?"
		(vi at: avi) ifNil:[
			vi at: avi put: fvi.
			ti at: avi put: tvi.
			vNorm at: avi put: (vertexFaceNormals at: i).
			mi at: avi put: mvi.
			found _ true.].
		"Is what is there already the same as whet we have?"
		found ifFalse:[
			((vi at: avi) = fvi and:[
				(ti at: avi) = tvi and:[
					(mi at: avi) = mvi and:[
						self compare: (vNorm at: avi) and: (vertexFaceNormals at: i) within:0.0001.]]]) ifTrue:[
							faces at: i put: (avi-1).
							found _ true.].].
		" There is something already there, but it is the wrong thing, we need an alias."
		found ifFalse:[
			(ai at: avi) ifNil:[
				ai add: nil.
				vi add: fvi.
				ti add: tvi.
				vNorm add: (vertexFaceNormals at: i).
				mi add: mvi.
				ai at: avi put: vi size.
				faces at: i put: (vi size)-1.
				found _ true.] ifNotNil:[
					avi _ ai at: avi.].].].].
"

The following commented out code fragment does the naive job of just adding all of the face information without looking for redundency. It is commented out because IT SHOULD NOT BE USED! It is here just for reference, because someday, someone will get totally lost in what is going on here and this is a quick and dirty way to simplify the problem. The results are wrong of course, but at least it is a start.
-------
1 to: faces size do:[ :i |						
	vi add: 1+ (faces at:i).
	tfaces ifNotNil:[ ti add: 1 +( tfaces at: i). ] ifNil:[ ti add: 1.].
	vNorm add: (vertexFaceNormals at: i).
	faces at: i put: i-1.].
-------"


"Now clean up the arrays and convert them. The arrays I need to construct here are:
	vertexArray - the actual 3D vertex array including aliases.
	aliasArray - the alias index array - this will be used for vector based animations
	vertexNorms - the vertex normals
	textureArray - the u,v texture coordinate array
which are all the same length, and
	faceGroup - the collection of all faceGroups - check the associated material for alphas.
	"

	vi _ self vertexIndexToArray: vi.
	ai _ self aliasIndexToArray: ai.
	vNorm _ self vertNormToArray: vNorm.
	tfaces ifNotNil:[ti _ self textureIndexToArray: ti.] ifNil:[ ti _ nil.].
	
	faceGroup _ self facesToFaceGroup: faces materials: matIndex.
	mm _ TMesh new initializeWithVertices: vi alias: ai norms: vNorm textureUV: ti faceGroups: faceGroup material: (materialRef ifNotNil:[materialList at: 1+materialRef]).    
	mm localTransform: self localTransform.
	mm objectName: self objectName.
	^ mm.
textureIndexToArray: ti

	|textureArray|

	textureArray _ B3DVector2Array ofSize: ti size.
	1 to: ti size do:[ :i |
		(ti at: i) ifNotNil:[
			textureArray at: i put: (tvertices at: (ti at:i)).]
		ifNil:[
			textureArray at: i put: B3DVector2 new.].
		].
	^ textureArray.
textureTransform: tuv material: mat


	| uv |
	uv _ tuv.
	mat texture ifNotNil:[
		"Magic mumbo jumbo goes here. Trial and error, and some intuition go a long way."
		uv _ Point x: (uv x - 0.5 - mat uvOffset x) y: (uv y - 0.5 + mat uvOffset y).
		"uv _ uv asPoint - 0.5 + mat uvOffset."
		mat uvAngle = 0.0 ifFalse:[
			uv _ uv rotateBy: mat uvAngle about: 0@0.].
		uv _ mat uvScale * uv + 0.5.
		].
	^ B3DVector2 x: uv x y: uv y.
vertNormToArray: vi


	| vertexNormals |

	vertexNormals _ B3DVector3Array ofSize: vi size.
	1 to: vi size do:[ :i |
		(vi at: i) ifNil:[
			vertexNormals at: i put: (B3DVector3 new).]
		ifNotNil:[
			vertexNormals at: i put: (vi at: i).].
		].
	^ vertexNormals.
			
		
vertexIndexToArray: vi


	| vertexArray |

	vertexArray _ B3DVector3Array ofSize: (vi size).
	1 to: vi size do:[ :i |
		(vi at: i) ifNil:[
			vertexArray at: i put: B3DVector3 new.]
		ifNotNil:[
			vertexArray at: i put: (vertices at: (vi at: i)).
			].
		].
	^ vertexArray.

initialize
initializeWithFileName: fName
 
	
	^self initializeWithFileName: fName scale: 1.0 shadeAngle: 90.1 textureMode: GLModulate
initializeWithFileName: fName scale: scl

	
	^self initializeWithFileName: fName scale: scl shadeAngle: 90.1 textureMode: GLModulate
initializeWithFileName: fName scale: scl shadeAngle: angle

	
	^self initializeWithFileName: fName scale: scl shadeAngle: angle textureMode: GLModulate.
initializeWithFileName: fName scale: scl shadeAngle: angle textureMode: txtrMd

	
	| fileStream string fieldName field parseTree mesh fileName |
	scale _ scl.
	textureMode _ txtrMd.
	"get full path name of file, and path of directory containing it"
	fileName _ CroquetData findContentFileName: fName.
	filePath _ FileDirectory dirPathFor: fileName.

	fileStream _ (CrLfFileStream readOnlyFileNamed: fileName) ascii.
	string _ fileStream next: fileStream size.
	stringLoc _ 1. 

	parseTree _ self parse: string.
	self shadeAngle: angle. "Use degrees, but convert to radians"
	frame _ TGroup new.
	1 to: parseTree size by: 2 do:[ :i |
		fieldName _ parseTree at: i.
		field _ parseTree at: i+1.
		fieldName = #T3dsmaxAsciiexport ifTrue:[ " unused "].
		fieldName = #TComment ifTrue:[" unused "].
		fieldName = #TScene ifTrue:[" unused "].
		fieldName = #TMaterialList ifTrue:[ self makeMaterials: field ].
		fieldName = #TGeomobject ifTrue:[ 
			mesh _ (self makeGeometry: field).
			frame addChild: mesh.].
		fieldName = #TGroup ifTrue:[ 
			mesh _ (self makeGroup: field). 
			frame addChild: mesh.].
		].
	frame frameChildren size = 1 ifTrue:[
		frame _ frame frameChildren at: 1.
		].
	frame prune.
	frame forceGlobalToLocal.
"	frame collapse."
	^self

parse
compare: v1 and: v2 within: epsilon


	| delta |
	delta _ v1-v2.
	delta x abs > epsilon ifTrue:[^ false].
	delta y abs > epsilon ifTrue:[^ false].
	delta z abs > epsilon ifTrue:[^ false].
	^ true.
makeGeometry: tree


	| tframe field fieldName start end children isMesh mesh |
	isMesh _ false.
	children _ OrderedCollection new.
	1 to: tree size by: 2 do:[ :i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		fieldName = #THelperobject ifTrue:[
			tframe _ TGroup new.
			tframe objectName: (self makeHelperObject: field).
			tframe localTransform: transform.
			].
		fieldName = #TNodeName ifTrue:[ 
			start _ field findDelimiters: '"' startingAt: 1.
			end _ field findDelimiters: '"' startingAt: start+1.
			self objectName: (field copyFrom: start+1 to: end-1).].
		fieldName = #TNodeTm ifTrue:[ self localTransform: (self makeTransform: field).].
		fieldName = #TMesh ifTrue:[ 
			self makeMesh: field.
			vertices size > 0 ifTrue:[isMesh _ true.].
			].
		fieldName = #TMaterialRef ifTrue:[ materialRef _ self makeNumber: field.]. ].

		isMesh ifTrue:[ mesh _ self reconstruct.] 
			    ifFalse:[ mesh _ TGroup new.
						mesh objectName: (self objectName).
						mesh localTransform: self localTransform.].
		children do:[ :c | mesh addChild: c].
		^ mesh.
		
makeGroup: tree


	| field fieldName tframe |

	tframe _ TGroup new.

	1 to: tree size by: 2 do:[ :i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		fieldName = #THelperobject ifTrue:[
			tframe objectName: (self makeHelperObject: field).
			tframe localTransform: transform.
			].
		fieldName = #TGroup ifTrue:[
			tframe addChild: (self makeGroup: field).
			].
		fieldName = #TGeomobject ifTrue:[
			tframe addChild: (self makeGeometry: field).
			].
		].
	^ tframe.
		
makeMap: tree


	| txtr fieldName field fname uOffset vOffset uTiling vTiling angle start end name extend |
" Create a texture based upon information found in the material field."
	extend _ nil.
	uOffset _ 0.0.
	vOffset _ 0.0.
	uTiling _ 1.0.
	vTiling _ 1.0.
	angle _ 0.0.
	1 to: tree size by: 2 do:[:i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		fieldName = #TBitmap ifTrue:[ 
			start _ field findDelimiters:'"' startingAt: 1.
			end _ field findDelimiters: '"' startingAt: start+1.
			fname _ field copyFrom: start+1 to: end-1.].
		fieldName = #TUvwUOffset ifTrue:[ uOffset _ self makeNumber: field.].
		fieldName = #TUvwVOffset ifTrue:[ vOffset _ self makeNumber: field.].
		fieldName = #TUvwUTiling ifTrue:[ uTiling _ self makeNumber: field.].
		fieldName = #TUvwVTiling ifTrue:[ vTiling _ self makeNumber: field.].
		fieldName = #TUvwAngle ifTrue:[ angle _ self makeNumber: field.].
		fieldName = #TMapName ifTrue:[ name _ field. 
				start _ name findDelimiters:'$' startingAt: 1.
				start < (name size-1) ifTrue:[
					(name at:(start+1))=$c ifTrue:[extend _ #colorKey.].
					(name at:(start+1))=$a ifTrue:[extend _ #alpha].
					(name at:(start+1))=$f ifTrue:[extend _ #fullBright].
				].].
		].
	"get file name from full path name - assumes file is a Windows file name"
	fname _ fname reverse.
	end _ fname findDelimiters:'\:'startingAt: 1.
	fname _ (fname copyFrom: 1 to: end-1) reverse.

	fname ifNil:[^fname].
	txtr _ TTexture new initializeWithFileName: (FileDirectory pathFrom: { filePath . fname }) mipmap: true shrinkFit: true extension: extend.
	txtr uvAngle: angle.
	txtr uvOffset: uOffset@vOffset.
	txtr uvScale: uTiling@vTiling.
	txtr objectName: name.
	^ txtr.
	


	
makeMaterial: tree


	| fieldName field material ambient diffuse specular shininess alpha txtr subMaterialList twoSided |

	alpha _ 1.0.
	shininess _ 50.0.
	material _ TMaterial new.
	twoSided _ false.
	1 to: tree size by: 2 do:[:i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		txtr _ nil.
		fieldName = #TMaterialName ifTrue:[material objectName: field.].
		fieldName = #TMaterialClass ifTrue:["unused"].
		fieldName = #TMaterialAmbient ifTrue:[ambient _ self makeNumberArray: field.].
		fieldName = #TMaterialDiffuse ifTrue:[diffuse _ self makeNumberArray: field].
		fieldName = #TMaterialSpecular ifTrue:[specular _ self makeNumberArray: field].
		fieldName = #TMaterialShine ifTrue:[shininess _ self makeNumber: field].
		fieldName = #TMaterialShinestrength ifTrue:["unused"].
		fieldName = #TMaterialTransparency ifTrue:[alpha _ 1.0 - (self makeNumber: field)].
		fieldName = #TMaterialWiresize ifTrue:["unused"].
		fieldName = #TMaterialShading ifTrue:["unused"].
		fieldName = #TMaterialXpFalloff ifTrue:["unused"].
		fieldName = #TMaterialSoften ifTrue:["unused"].
		fieldName = #TMaterialXpType ifTrue:["unused"].
		fieldName = #TMapDiffuse ifTrue:[ txtr _ self makeMap: field.].
		fieldName = #TNumsubmtls ifTrue:[ subMaterialList _ OrderedCollection new.].
		fieldName = #TSubmaterial ifTrue:[ subMaterialList add: (self makeMaterial:field) ].
		fieldName = #TMaterialTwosided ifTrue:[ twoSided _ true. ].
		].
	ambient ifNotNil:[
		ambient add: alpha.
		material ambientColor: ambient asFloatArray.
		].
	diffuse ifNotNil:[
		diffuse add: alpha.
		material diffuseColor: diffuse asFloatArray.
		].
	specular ifNotNil:[
		specular add: 1.0.
		material specularColor: specular asFloatArray.].
	material shininess: shininess.
	txtr ifNotNil:[
		material texture: txtr.
		txtr extension = #fullBright ifTrue:[material textureMode: GLReplace.]
			ifFalse:[ material textureMode: textureMode.].
		(txtr extension = #colorKey or:[txtr extension = #alpha])ifTrue:[
			material hasAlpha ifFalse:[ 
				material ambientColor:(material ambientColor * 0.99)]].

		material uvAngle: txtr uvAngle.
		material uvOffset: txtr uvOffset.
		material uvScale: txtr uvScale.
		].

	material subMaterialList: subMaterialList.
	twoSided ifTrue:[ material cullFace: false].
	^ material.
makeMaterials: tree


	| count |

	count _ 0.
	((tree at: 1) = #TMaterialCount) ifTrue:[ count _ (tree at: 2) asNumber].
	count <= 0 ifTrue:[^self].
	materialList _ OrderedCollection new.

	1 to: count do:
		[ :i | 
			(tree at: 1+(i * 2)) = #TMaterial ifTrue:[ 
				materialList add: (self makeMaterial: (tree at: 2+(i*2))).].].
makeMesh: tree


	| fieldName field numVertices numFaces numTVertex numTVFaces tarray count matCount tv  |

	tvertices _ nil.
	tfaces _ nil.
	1 to: tree size by: 2 do:[:i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		
		fieldName = #TMeshNumvertex ifTrue:[ 
			numVertices _ self makeNumber: field.
			vertices _ B3DVector3Array new: numVertices. ].
		fieldName = #TMeshNumfaces ifTrue:[
			numFaces _ self makeNumber: field.
			faces _ IntegerArray ofSize: (numFaces*3).
			matIndex _ IntegerArray ofSize: numFaces.].
		fieldName = #TMeshVertexList ifTrue:[ 
			field ifNotNil:[
				count _ 1.
				1 to: field size by: 2 do:[ :j |
					tarray _ self makeNumberArray: (field at:(j+1)).
					tarray _ (B3DVector3 x:(tarray at: 2) y: (tarray at: 4) z:(tarray at: 3)negated)*scale.
					vertices at: count put: tarray.
					count _ count+1.
					].
				].
			.].
		fieldName = #TMeshFaceList ifTrue:[
			count _ 1.
			matCount _ 1.
			1 to: field size by: 6 do:[:j|
				tarray _ self makeNumberArray: (field at: (j+1)).
				faces at: count  put: (tarray at: 2).
				faces at: count+1 put: (tarray at: 3).
				faces at: count+2 put: (tarray at: 4).
				count _ count+3.
				matIndex at: matCount put:(self makeNumber: (field at: (j+5))).
				matCount _ matCount + 1.
				.].
			].
		fieldName = #TMeshNumtvertex ifTrue:[
			numTVertex _ self makeNumber:field.
			tvertices _ B3DVector2Array new: numTVertex. ]. 
		fieldName = #TMeshTvertlist ifTrue:[
			count _ 1.
			1 to: field size by: 2 do:[ :j |
				tarray _ self makeNumberArray: (field at:(j+1)).
				tv _ (B3DVector2 x:(tarray at:2) y:1-(tarray at:3)).
				tvertices at: count put: tv.
				count _ count+1.
				].
			].
		fieldName = #TMeshNumtvfaces ifTrue:[
			numTVFaces _ self makeNumber: field.
			tfaces _ IntegerArray ofSize:(numTVFaces*3). ].
		fieldName = #TMeshTfacelist ifTrue:[
			count _ 1.
			1 to: field size by:2 do: [ :j |
				tarray _ self makeNumberArray: (field at: (j+1)).
				tfaces at: count put: (tarray at: 2).
				tfaces at: count+1 put: (tarray at: 3).
				tfaces at: count+2 put: (tarray at: 4).
				count _ count+3.
				].
			].
		].
makeNodeName: tree


	| field fieldName name start end |
	1 to: tree size by: 2 do:[ :i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		fieldName = #TNodeName ifTrue:[
			start _ field findDelimiters: '"' startingAt: 1.
			end _ field findDelimiters: '"' startingAt: start+1.
			name _ (field copyFrom: start+1 to: end-1).
			].
		].
	^ name.
		
makeNumber: string


	| start end substr|
	
	start _ 1.
	end _ 1.
	
	start _ string findDelimiters: '1234567890-.' startingAt: end.
	end _ string findDelimiters: (' ',Character tab asString) startingAt: start.
	substr _ string copyFrom:start to: end-1.
	^ substr asNumber.
makeNumberArray: string


	| start farray parseStream |
	parseStream := ReadStream on: string.	
	farray _ OrderedCollection new.
	"For rapidly finding the numeric entries"
	"set1 := ByteArray new: 256.
	'1234567890-.' do:[:ch| set1 at: ch asciiValue+1 put: 1].
	set2 := ByteArray new: 256.
	set2 at: Character space asciiValue+1 put: 1.
	set2 at: Character tab asciiValue+1 put: 1."
	start := 0.
	[start _ string indexOfAnyOf: NumberSet startingAt: start+1 ifAbsent: [0].

"start := String findFirstInString: string inSet: set1 startingAt: start+1."
	start = 0] whileFalse:[
		parseStream position: start-1.
		farray add: (Number readFrom: parseStream).
		start := parseStream position+1.
	].
	^ farray.
makeTransform: tree


	| field fieldName matrix v mOffset start end m90 |
	matrix _ B3DMatrix4x4 identity.
	1 to: tree size by: 2 do:[ :i |
		fieldName _ tree at: i.
		field _ tree at: i+1.
		fieldName = #TNodeName ifTrue:[ "I put this here because groups don't give me a node name."
			start _ field findDelimiters: '"' startingAt: 1.
			end _ field findDelimiters: '"' startingAt: start+1.
			self objectName: (field copyFrom: start+1 to: end-1).].
		fieldName = #TTmRow0 ifTrue:[ 
			v _ self makeNumberArray: field. 
			matrix a11: (v at: 1).
			matrix a21: (v at: 2).
			matrix a31: (v at: 3).].
		fieldName = #TTmRow1 ifTrue:[			
			v _ self makeNumberArray: field. 
			matrix a12: (v at: 1).
			matrix a22: (v at: 2).
			matrix a32: (v at: 3).].
		fieldName = #TTmRow2 ifTrue:[
			v _ self makeNumberArray: field. 
			matrix a13: (v at: 1).
			matrix a23: (v at: 2).
			matrix a33: (v at: 3).].
		fieldName = #TTmRow3 ifTrue:[ "********TRANSLATION FIELD******"
			v _ self makeNumberArray: field. 
			matrix a14: (v at: 1) * scale.
			matrix a24: (v at: 2) * scale.
			matrix a34: (v at: 3) * scale .].
		fieldName = #TTmScale ifTrue:[ "*********SCALE FIELD************"
			v _ self makeNumberArray: field.
			v at: 1 put:(1.0/v at:1).
			v at: 2 put:(1.0/v at:2).
			v at: 3 put:(1.0/v at:3).
			matrix a11: matrix a11*(v at:1).
			matrix a21: matrix a21*(v at:1).
			matrix a31: matrix a31*(v at:1).

			matrix a12: matrix a12*(v at:2).
			matrix a22: matrix a22*(v at:2).
			matrix a32: matrix a32*(v at:2).

			matrix a13: matrix a13*(v at:3).
			matrix a23: matrix a23*(v at:3).
			matrix a33: matrix a33*(v at:3).
			].
" I am not sure if I need this next matrix or not."
		fieldName = #TNodeObjectoffset ifTrue:[ 
			mOffset _ self makeTransform: field.
			].].

	m90 _ B3DMatrix4x4 identity rotationAroundX:-90.
	matrix _ (m90 composeWith: matrix) composeWith: m90 orthoNormInverse.

"	^ B3DMatrix4x4 identity translation: (matrix translation)."
	^ matrix.
parse: string


	| tloc nloc tn name tree |
	tree _ nil. "just to show this is accidental"
	tloc _ string indexOfAnyOf: DelimSet1 startingAt: stringLoc ifAbsent:[string size+1].
" ---- Is this a label?  If not, then we are poorly formed. Bail as best you can."
	(string at: tloc) = $* ifTrue:[
		tree _ OrderedCollection new.
		stringLoc _ tloc+1.


" ---- Loop Here ---- "
		[stringLoc < string size] whileTrue:[

" ---- Find the field name or end of block or end of file."
			tloc _ string indexOfAnyOf: DelimSet2 startingAt: stringLoc ifAbsent:[string size+1].
			(string at: tloc) = $} ifTrue:[ stringLoc _ tloc + 1. ^tree. ].
			tloc >= string size ifTrue:[^tree.].
			(string at: tloc) = ${ ifTrue:[^tree.].
			tn _ string copyFrom: stringLoc to: (tloc-1).
			name _ 'T'. "Because this is Tea and because some field start with numbers."
			(tn findTokens: '_')do:[ :tkn | name _ name, (tkn asLowercase capitalized)].
			name _ name asSymbol.
			tree add: name.
" ---- Find the data."
			stringLoc _ tloc+1.
			tloc _ string indexOfAnyOf: DelimSet1 startingAt: stringLoc ifAbsent:[string size+1].

" ---- End of file."
			tloc > string size ifTrue:[ 
				tree add: (string copyFrom:stringLoc to: (tloc-1)). 
				stringLoc _ tloc. 
				^tree.].

" ---- Found a new name or end of block- everything in between the last one and this must be useful."
			((string at: tloc) = $* or:[(string at: tloc)= $}])ifTrue:[ 
				nloc _ string indexOf: Character cr startingAt: stringLoc ifAbsent:[string size+1].
				nloc > tloc ifTrue:[nloc _ tloc].
				stringLoc <= (nloc-1) ifTrue:[
					tree add: (string copyFrom: stringLoc to: (nloc-1)).]
				ifFalse: [
					tree add:''].
				(string at: tloc)=$} ifTrue:[stringLoc _ tloc. ^tree].
				stringLoc _ tloc+1.
				].

" ---- Found a block - recurse."
			(string at: tloc) = ${ ifTrue: [
				stringLoc _ tloc+1. 
				tree add: (self parse: string).
	" ---- Find the next name."
				stringLoc _ string indexOfAnyOf: DelimSet3 startingAt: stringLoc+1 ifAbsent:[string size+1].
				stringLoc > string size ifTrue:[^tree].
				(string at: stringLoc) = $} ifTrue:[stringLoc _ stringLoc+1. ^tree ].
				stringLoc _ stringLoc+1.
				].
			].
		].
	^tree.

showTree: tree


	self showTree: tree depth: 0.
showTree: tree depth: depth


	| label |
	tree class = OrderedCollection ifTrue:[
	1 to: tree size by: 2 do:[:i |
		1 to: depth do:[ :j | Transcript show:'---|'.].
		label _ tree at: i.
		Transcript show: (tree at: i) asString; cr.
		label = #TMeshVertexList ifFalse:[
		label = #TMeshFaceList ifFalse:[
		label = #TMeshTvertlist ifFalse:[
		label = #TMeshTfacelist ifFalse:[
		label = #TSubmaterial ifFalse:[
		self showTree: (tree at:(i+1)) depth: depth+1.].].].].].].].
testNorm: n1 norm: n2
 

	" Test to see if the angle between the surfaces of n1 and n2 is greater than the shade angle, which is equivalent to being less than the cos of the shade angle. This means that we look at the negative of the dot product because the cos between the normals is the negative of the cos of the angle between the surfaces."
	^ (n1 dot: n2) negated < shadeAngleCos.

class methods:

class initialization
initialize

	"TLoad3DSMax initialize"
	DelimSet1 := CharacterSet newFrom: '*{}'.
	DelimSet2 := CharacterSet newFrom: ' {}	',Character cr asString.
	DelimSet3 := CharacterSet newFrom: '*}'.
	NumberSet _ CharacterSet newFrom: '1234567890-.'.
	WhiteSpaceSet _  CharacterSet newFrom: (Array with: Character space with: Character tab).

^top


- made by Dandelion -