1--------------------------------------------------------------------
2-- Quake III MD3 Importer for gmax
3-- Copyright (c) Chris Cookson, 2002
4--
5-- cjcookson@hotmail.com
6--
7-- Thanx to Acrid
8--
9-- All bug reports & feature requests to cjcookson@hotmail.com
10--
11-- For gmax Scripts, News, Articles and More check out:
12--
13-- http://gmax.digitalarenas.com
14--
15-- Version history:
16-- 	beta0.1 Public beta - unsupported test version
17--
18-- Known issues:
19--	* Auto-attach doesn't always work. Open upper.md3 first for best results
20--	* Freeze when animated objects selected under Win98?
21--  * Slow for huge models, but hey, it's only a script!
22--------------------------------------------------------------------
23
24
25utility MD3Import "Quake3 MD3 Import" silentErrors:false
26(
27	-- Useful model file information
28	local g_modelFilename
29	local g_modelName
30	local g_modelPath
31
32	-- Quake3 Image Extensions
33	local g_q3ImageExts = #( ".tga", ".jpg", ".jpeg" )
34
35	local appName = "Quake3 MD3 Import"
36
37	-- Texture Cache
38	local g_textureNames = #()
39	local g_textures = #()
40
41
42	--------------------
43	-- User Interface --
44	--------------------
45
46	group "About:"
47	(
48		label titleLabel "Quake III MD3 Import"
49		label betaLabel "Public Beta v0.1"
50		HyperLink addy "by Chris Cookson" align:#center address:"mailto:cjcookson@hotmail.com" color:(color 0 0 170) hoverColor:(color 170 0 0)
51	)
52
53	group "Import:"
54	(
55		radiobuttons tagAction "Tags:" labels:#("Ignore", "Load all", "Auto attach") default:1
56		checkbox loadAnimations "Load Animations" checked:true
57		checkbox promptForMissing "Prompt for missing textures" checked:false
58
59		button importButton "Import MD3..."	tooltip:"Import a Quake III .MD3 model"
60	)
61
62	group "Tools:"
63	(
64		button applySkin "Apply Skin..." tooltip:"Apply a .skin file to models in the scene"
65		button createTag "Create New Tag" tooltip:"Create a new tag triangle"
66	)
67
68	-----------------------------------------------------
69	-- Recent file list (sadly I can't save to an INI) --
70	-----------------------------------------------------
71
72	local recentFile
73
74	fn AddRecentFile fname=
75	(
76		recentFile = fname
77		ok
78	)
79
80	fn GetMostRecentPath=
81	(
82		if recentFile != undefined then
83		(
84			return getFileNamePath recentFile
85		) else if g_gameDataPath != undefined then
86		(
87			return g_gameDataPath + "\\models\\"
88		) else
89		(
90			return ""
91		)
92	)
93
94	---------------
95	-- Utilities --
96	---------------
97
98	-- Replacement for doesFileExist which seems to give false positives
99	fn DoesFileReallyExist filename=
100	(
101		local temp = fopen filename "rb"
102		if temp != undefined then
103		(
104			fclose temp
105			true
106		)
107		else
108		(
109			false
110		)
111	)
112
113	-- Error message box
114	fn ShowError msg=
115	(
116		format "*** Error: %\n" msg
117		messageBox msg title:appName;
118		ok
119	)
120
121	-- Error message box and exception
122	fn FatalError msg=
123	(
124		ShowError msg;
125		throw (appName + ":" + msg)
126	)
127
128	-- Quick check, error if fail
129	fn Check condition msg =
130	(
131		if not condition do
132		(
133			if msg == unsupplied do msg = "Check failed"
134			format "*** Check failed: %\n" msg
135			FatalError msg
136		)
137	)
138
139	-- Binstream tools
140
141	-- Skip over a number of bytes
142	fn SkipBytes bstream count=
143	(
144		local unknown
145		case count of
146		(
147			2: unknown = ReadShort bstream #unsigned
148			4: unknown = ReadLong bstream #unsigned
149			default:
150			(
151				for i = 1 to count do
152				(
153					unknown = ReadByte bstream #unsigned
154				)
155			)
156		)
157	)
158
159	-- Read a string of fixedLen chars
160	fn ReadFixedString bstream fixedLen=
161	(
162		local str = ""
163		for i = 1 to fixedLen do
164		(
165			local ch = ReadByte bstream #unsigned
166			str += bit.intAsChar ch
167			if ch == 0 then
168			(
169				-- Ignore junk
170				SkipBytes bstream (fixedLen - i)
171				exit
172			)
173		)
174		str
175	)
176
177	-- String utils
178
179	-- Get the string a long represents
180	fn LongToString num=
181	(
182		local str = ""
183		for i = 1 to 4 do
184		(
185			str += bit.intAsChar (bit.and num 0xff)
186			-- num = bit.shift num -8
187			num /= 256
188		)
189		str
190	)
191
192	-- Convert to lowercase
193	fn ToLowerCase instr=
194	(
195		local outstr = copy instr
196		local upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
197		local lower = "abcdefghijklmnopqrstuvwxyz"
198
199		for i =1 to outstr.count do
200		(
201			local pos = findString upper outstr[i]
202			if pos != undefined do outstr[i] = lower[pos]
203		)
204		outstr
205	)
206
207
208	-- Convert a forward-slash style PK3 path to a windows one
209	fn ToWindowsPath pakPath=
210	(
211		local winPath = "\\"
212		for i = 1 to pakPath.count do
213		(
214			if pakPath[i] == "/" then
215			(
216				winPath += "\\"
217			) else
218			(
219				winPath += pakPath[i]
220			)
221		)
222		winPath
223	)
224
225	-- Swap a file's extension
226	fn ChangeExt filename newExt=
227	(
228		(getFilenamePath filename) + (getFilenameFile filename) + newExt
229	)
230
231	-- Get the game data path set by the user, or a default one
232	fn GetDataPath=
233	(
234		if g_gameDataPath != undefined then
235		(
236			g_gameDataPath
237		)
238		else
239		(
240			g_defaultGameDataPath
241		)
242	)
243
244	-- Extract the filename of a data file from a PK3 filename
245	fn GetDataPathFilename filename=
246	(
247		local splitPath = filterString filename "\\/"
248
249		if splitPath.count > 0 then
250		(
251			splitPath[splitPath.count]
252		)
253		else
254		(
255			""
256		)
257	)
258
259	-------------------
260	-- Texture Cache --
261	-------------------
262
263	-- Clear the texture cache
264	fn ClearTextureCache=
265	(
266		g_textureNames = #()
267		g_textures = #()
268	)
269
270	-- Add a texture to the cache and return the loaded bitmaptexture
271	fn AddTextureToCache name fname=
272	(
273		local bmpTex = bitmapTexture filename:fname
274		-- Don't add invalid names
275		if name != "" then
276		(
277			append g_textureNames name
278			append g_textures bmpTex
279		)
280		bmpTex
281	)
282
283	-- Find a texture given a PK3 filename
284	fn FindTexture filename=
285	(
286		-- First of all, check the cache
287		local index = findItem g_textureNames filename
288		if index != 0 then
289		(
290			return g_textures[index]
291		)
292
293		-- Not in cache so we'll try searching in the model's directory
294		local localFilename = g_modelPath + (GetDataPathFilename filename)
295
296		-- Thanks to the Discreet/Tempest guys for the for ext... bit!
297		for ext in g_q3ImageExts do
298		(
299			local imgName = ChangeExt localFilename ext
300
301			if DoesFileReallyExist imgName then
302			(
303				return (AddTextureToCache filename imgName)
304			)
305			format "Failed on %\n" imgName
306		)
307
308		-- We're out of luck so ask the user to find it for us!
309		local foundFilename
310		if promptForMissing.checked do
311		(
312			messageBox ("Can't find texture " + filename + "\nPlease click OK and locate it.")
313
314			foundFilename = getOpenFileName caption:("Find Texture " + filename) filename:localFilename \
315						types:"All supported (*.tga;*.jpg;*.jpeg)|*.tga;*.jpg;*.jpeg|All Files (*.*)|*.*|"
316		)
317
318		if foundFilename != undefined then
319		(
320			return (AddTextureToCache filename foundFilename)
321		)
322		else
323		(
324			format "Warning: Could not find % or any variant!\n" filename
325			append g_textureNames filename
326			append g_textures undefined
327		)
328		undefined
329	)
330
331
332	----------------------
333	-- Skin file reader --
334	----------------------
335	struct Tokenizer
336	(
337		tokens = #(),
338
339		fn SetString str=
340		(
341			tokens = filterString str "\t ,"
342		),
343
344		fn ReadToken=
345		(
346			if tokens.count > 0 then
347			(
348				local tok = tokens[1]
349				deleteItem tokens 1
350				tok
351			) else
352			(
353				undefined
354			)
355		),
356
357		fn PeekToken=
358		(
359			if tokens.count > 0 then tokens[1] else undefined
360		)
361	)
362
363	struct SkinDef
364	(
365		name,
366		imgFile
367	)
368
369	local g_skinDefs = #()
370
371	-- Strip off the _1, _2 level of detail suffixes
372	function StripLodSuffix str=
373	(
374		if str[str.count-1] == "_" then
375		(
376			return subString str 1 (str.count-2)
377		)
378		str
379	)
380
381	function GetDefaultSkinFile=
382	(
383		-- Strip LOD suffix from model name
384		local strippedName = StripLodSuffix g_modelName
385
386		g_modelPath + strippedName + "_default.skin"
387	)
388
389	function LoadSkinFile fname=
390	(
391		try
392		(
393			g_skinDefs = #()
394
395			--format "Attempting to load skin file: %\n" fname
396			local fp = openFile fname mode:"r"
397
398			if fp == undefined then
399			(
400				format "Skin file % not found\n" fname
401				return false
402			)
403
404			local tok = Tokenizer()
405
406			while not (eof fp) do
407			(
408				local curLine = readLine fp
409
410				--format "%\n" curLine
411
412				tok.SetString curLine
413
414				if tok.PeekToken() != undefined then
415				(
416					local surfName = tok.ReadToken()
417					surfName = ToLowerCase surfName
418					local imageFile = tok.ReadToken()
419					local newSkinDef = SkinDef name:surfName imgFile:imageFile
420					append g_skinDefs newSkinDef
421					--format "Got skindef: (%, %)\n" (newSkinDef.name) (newSkinDef.imgFile)
422				)
423			)
424
425			close fp
426		)
427		catch ()
428		ok
429	)
430
431	function GetSkinForSurface surfName=
432	(
433		--format "Looking for skin for surface %..." surfName
434		surfName = ToLowerCase surfName
435
436		for def in g_skinDefs do
437		(
438			if (def.name == surfName) or (def.name == (StripLodSuffix surfname)) then
439			(
440				--format "Found %\n" (def.imgFile)
441				return def.imgFile
442			)
443		)
444		--format "Not found\n"
445		undefined
446	)
447
448
449
450	--------------------------------------------------------------------
451	-- MD3 Importer
452	--------------------------------------------------------------------
453
454	-- Surfaces are a group of polys with the same rendering style
455	struct MD3_Surface
456	(
457		ident,
458		name,
459		flags,
460		numFrames,
461		numShaders,
462		numVertices,
463		numTriangles,
464		offsetTriangles,
465		offsetShaders,
466		offsetTexCoords,
467		offsetVertices,
468		offsetEnd,
469
470		-- Internal
471		offsetStart,	-- Set when LoadFromStream called
472
473		fn LoadFromStream bstream=
474		(
475			offsetStart = ftell bstream
476
477			ident					= ReadLong bstream #unsigned
478			name					= ReadFixedString bstream 64
479			flags					= ReadLong bstream #unsigned
480			numFrames				= ReadLong bstream #unsigned
481			numShaders				= ReadLong bstream #unsigned
482			numVertices				= ReadLong bstream #unsigned
483			numTriangles			= ReadLong bstream #unsigned
484			offsetTriangles			= ReadLong bstream #unsigned
485			offsetShaders			= ReadLong bstream #unsigned
486			offsetTexCoords			= ReadLong bstream #unsigned
487			offsetVertices			= ReadLong bstream #unsigned
488			offsetEnd				= ReadLong bstream #unsigned
489		),
490
491		fn IsValid=
492		(
493			/*
494			if ident != 0x07 then
495			(
496				return false
497			)
498			*/
499			true
500		),
501
502		fn DebugOut=
503		(
504			format "MD3_Surface\n-----------\n"
505			format "ident = %, name = %, flags = %\n" ident name flags
506			format "numFrames = %, numShaders = %\n" numFrames numShaders
507			format "numVertices = %, numTriangles = %\n" numVertices numTriangles
508			format "ofsTris = %, ofsShaders = %, ofsTexCoords = %, ofsVertices = %\n" \
509				offsetTriangles offsetShaders offsetTexCoords offsetVertices
510		)
511	)
512
513	-- Triangle defined as 3 indices
514	struct MD3_Triangle
515	(
516		indices,
517
518		fn LoadFromStream bstream=
519		(
520			local v1 = ReadLong bstream #unsigned
521			local v2 = ReadLong bstream #unsigned
522			local v3 = ReadLong bstream #unsigned
523			indices = [v3 + 1, v2 + 1, v1 + 1]
524		)
525	)
526
527
528	-- Vertex definition
529	struct MD3_Vertex
530	(
531		position,
532		normal,
533
534		fn LoadFromStream bstream=
535		(
536			local x = ReadShort bstream #signed
537			local y = ReadShort bstream #signed
538			local z = ReadShort bstream #signed
539			position = [x as float, y as float, z as float] * (1.0 / 64.0)
540			normal = ReadShort bstream #unsigned
541		),
542
543		fn GetNormal=
544		(
545			local unpackAngle = 360.0 / 255.0
546
547			local longitude = ((normal / 256) as float) * unpackAngle
548			local latitude = ((bit.and normal 0xff) as float) * unpackAngle
549
550			local norm = [0,0,0]
551			norm.x = cos( longitude ) * sin( latitude )
552			norm.y = sin( longitude ) * sin( latitude )
553			norm.z = cos( latitude )
554
555			norm
556		),
557
558		fn GetSize= 8
559	)
560
561	-- Texture co-ordinate
562	struct MD3_TexCoord
563	(
564		stu,
565
566		fn LoadFromStream bstream=
567		(
568			local s = ReadFloat bstream
569			local t = ReadFloat bstream
570			-- gmax needs the t co-ordinate reversed
571			stu = [s, -t, 0.0]
572		)
573	)
574
575	-- Shader definition
576	struct MD3_Shader
577	(
578		name,
579		shaderIndex,	-- Internal to Q3
580
581		fn LoadFromStream bstream=
582		(
583			name = ReadFixedString bstream 64
584			shaderIndex = ReadLong bstream #unsigned
585		),
586
587		fn DebugOut=
588		(
589			format "MD3_Shader: %\n" name
590		)
591	)
592
593	-- Tag
594	struct MD3_Tag
595	(
596		name,
597		origin,
598		axisRow1,
599		axisRow2,
600		axisRow3,
601		tagMatrix,
602
603		fn LoadFromStream bstream=
604		(
605			name = ReadFixedString bstream 64
606			local x = ReadFloat bstream
607			local y = ReadFloat bstream
608			local z = ReadFloat bstream
609			origin = [x, y, z]
610			x = ReadFloat bstream
611			y = ReadFloat bstream
612			z = ReadFloat bstream
613			axisRow1 = [x, y, z]
614			x = ReadFloat bstream
615			y = ReadFloat bstream
616			z = ReadFloat bstream
617			axisRow2 = [x, y, z]
618			x = ReadFloat bstream
619			y = ReadFloat bstream
620			z = ReadFloat bstream
621			axisRow3 = [x, y, z]
622
623			tagMatrix = matrix3 axisRow1 axisRow2 axisRow3 origin
624		),
625
626		fn GetTranslation=
627		(
628			tagMatrix.translationpart
629		),
630
631		fn GetRotation=
632		(
633			tagMatrix.rotationPart
634		),
635
636		fn GetMatrix=
637		(
638			tagMatrix
639		),
640
641		fn DebugOut=
642		(
643			format "MD3_Tag: %, matrix = %\n" name tagMatrix
644		)
645	)
646
647	-- Frame:
648	struct MD3_Frame
649	(
650		bboxMin,
651		bboxMax,
652		localOrigin,
653		radius,
654		name,			-- often creator
655
656		fn LoadFromStream bstream=
657		(
658			local x = ReadFloat bstream
659			local y = ReadFloat bstream
660			local z = ReadFloat bstream
661			bboxMin = [x, y, z]
662			x = ReadFloat bstream
663			y = ReadFloat bstream
664			z = ReadFloat bstream
665			bboxMax = [x, y, z]
666			x = ReadFloat bstream
667			y = ReadFloat bstream
668			z = ReadFloat bstream
669			localOrigin = [x, y, z]
670			radius = ReadFloat bstream
671			name = ReadFixedString bstream 16
672		)
673	)
674
675	-- Header: found at start of file
676	struct MD3_Header
677	(
678		ident,			-- IDP3
679		version,		-- 15
680		name,
681		flags,
682		numFrames,
683		numTags,
684		numSurfaces,
685		numSkins,
686		offsetFrames,
687		offsetTagNames,
688		offsetTags,
689		offsetSurfaces,
690		offsetEnd,
691
692		fn LoadFromStream bstream=
693		(
694			ident			= ReadLong bstream #unsigned
695			version			= ReadLong bstream #unsigned
696			name			= ReadFixedString bstream 64
697			flags			= ReadLong bstream #unsigned
698			numFrames		= ReadLong bstream #unsigned
699			numTags			= ReadLong bstream #unsigned
700			numSurfaces		= ReadLong bstream #unsigned
701			numSkins		= ReadLong bstream #unsigned
702			offsetFrames	= ReadLong bstream #unsigned
703			offsetTags		= ReadLong bstream #unsigned
704			offsetSurfaces	= ReadLong bstream #unsigned
705			offsetEnd		= ReadLong bstream #unsigned
706		),
707
708		fn IsValid=
709		(
710			local identStr = LongToString ident
711			if (LongToString ident) != "IDP3" then return false
712			if version != 15 then
713			(
714				ShowError "Incorrect version! Found " + version + " but should be 15"
715				return false
716			)
717			if numFrames < 1 then
718			(
719				ShowError "MD3 does not have any frames!"
720				return false
721			)
722			true
723		),
724
725		fn DebugOut=
726		(
727			format "MD3_Header\n----------\n"
728			format "Ident: %, version: %, name: %\n" ident version name
729			format "Flags: %, numFrames: %, numTags: %, numSurfaces: %, numSkins: %\n" flags numFrames numTags numSurfaces numSkins
730			format "ofsFrames: %, ofsTags: %, ofsSurfs: %, ofsEnd: %\n" offsetFrames offsetTags offsetSurfaces offsetEnd
731			ok
732		)
733	)
734
735
736	-- The main MD3 loader object
737	struct MD3_Loader
738	(
739		bstream,
740
741		header,
742		surfaceList,
743		tags,
744		tagObjects,
745
746		frames,
747
748		skinFile,
749
750		fn Open filename=
751		(
752			g_modelFilename = filename
753			g_modelName = getFilenameFile filename
754			g_modelPath = getFileNamePath filename
755			bstream = fopen filename "rb"
756
757			skinFile = GetDefaultSkinFile()
758			ok
759		),
760
761		fn Close=
762		(
763			fclose bstream
764			ok
765		),
766
767		-- Create a tag object in the form of the standard Quake3 tag triangle
768		fn CreateTagObject md3Tag=
769		(
770			local verts = #([0, 0, 0], [0, -1, 0], [2, 0, 0])
771			local tri = #([1, 2, 3])
772			local tagObject = mesh name:(md3Tag.name) vertices:verts faces:tri pos:(md3Tag.GetTranslation())
773
774			in coordsys local tagObject.rotation = md3Tag.GetRotation()
775
776			tagObject
777		),
778
779		-- Load a surface and all its associated data (eg. animations, skins)
780		fn LoadSurface=
781		(
782			local surface = MD3_Surface()
783			surface.LoadFromStream bstream
784
785			--surface.DebugOut()
786
787			if not surface.IsValid() then
788			(
789				return false
790			)
791
792			-- Load triangles
793			fseek bstream (surface.offsetStart + surface.offsetTriangles) #seek_set
794
795			local modelTris = #()
796			for i = 1 to surface.numTriangles do
797			(
798				local curTri = MD3_Triangle()
799				curTri.LoadFromStream bstream
800
801				append modelTris curTri.indices
802			)
803
804			-- Load shaders
805			fseek bstream (surface.offsetStart + surface.offsetShaders) #seek_set
806			local modelShaders = #()
807			for i = 1 to surface.numShaders do
808			(
809				local curShader = MD3_Shader()
810				curShader.LoadFromStream bstream
811				append modelShaders curShader.name
812				--curShader.DebugOut()
813			)
814
815			-- Load texture co-ordinates
816			fseek bstream (surface.offsetStart + surface.offsetTexCoords) #seek_set
817			local modelTexCoords = #()
818			for i = 1 to surface.numVertices do
819			(
820				local curTC = MD3_TexCoord()
821				curTC.LoadFromStream bstream
822				append modelTexCoords curTC.stu
823			)
824
825			-- Create a material
826			local modelMaterial = standardmaterial name:(header.name + " material")
827
828			local skin = GetSkinForSurface surface.name
829
830			if skin == undefined then
831			(
832				skin = modelShaders[1]
833			)
834
835			local diffuseMap = FindTexture skin
836
837			if diffuseMap != undefined then
838			(
839				modelMaterial.maps[2] = diffuseMap
840				modelMaterial.mapEnables[2] = true
841				showTextureMap modelMaterial diffuseMap true
842			)
843
844			-- Create mesh in gmax
845			local gmaxMesh = mesh name:surface.name numVerts:surface.numVertices numFaces:surface.numTriangles \
846									position:[0,0,0] scale:[1,1,1] material:modelMaterial
847
848			-- Add faces
849			setMesh gmaxMesh faces:modelTris
850
851			-- Add per-vertex tex-coords and texture faces
852			setNumTVerts gmaxMesh surface.numVertices
853			for i = 1 to surface.numVertices do
854			(
855				setTVert gmaxMesh i modelTexCoords[i]
856			)
857
858			buildTVFaces gmaxMesh
859			for i = 1 to surface.numTriangles do
860			(
861				setTVFace gmaxMesh i modelTris[i]
862			)
863
864			-- try and help garbage collection
865			modelTexCoords = undefined
866			modelTris = undefined
867			gc()
868
869			-- Jump over to the vertex data
870			fseek bstream (surface.offsetStart + surface.offsetVertices + 0) #seek_set
871
872			local modelNormals = #()
873			for i = 1 to surface.numVertices do
874			(
875				local curVertex = MD3_Vertex()
876				curVertex.LoadFromStream bstream
877				append modelNormals (curVertex.GetNormal())
878				setVert gmaxMesh i (curVertex.position)
879			)
880
881			-- For some reason I have to set normals *after* having set all the verts
882			-- They also don't animate properly, so I've had to disable them :/
883			/*
884			for i = 1 to surface.numVertices do
885			(
886				setNormal gmaxMesh i modelNormals[i]
887			)
888			modelNormals = #()
889			*/
890
891			if loadAnimations.checked then
892			(
893
894				-- Make vertices animatable
895				animateVertex gmaxMesh #all
896
897				local animVerts = gmaxMesh[4][1]
898
899				-- Construct each subsequent animation frame
900				animate on
901				(
902					local progStr = " (" + (surfaceList.count as string) + "/" + (header.numSurfaces as string) + ")"
903					progressStart ("Loading " + surface.name + progStr + "...")
904
905					try
906					(
907						-- Seek back to start of vertices
908						fseek bstream (surface.offsetStart + surface.offsetVertices) #seek_set
909
910						local curVertex = MD3_Vertex()
911
912						for curFrame = 0 to (surface.numFrames - 1) do
913						(
914							at time curFrame
915							(
916								for i = 1 to surface.numVertices do
917								(
918									curVertex.LoadFromStream bstream
919									animVerts[i].value = curVertex.position
920								)
921							)
922
923							-- Don't update too often (pointless to update more than 100 times anyway)
924							if (mod curFrame 3) == 0 then
925							(
926								progressUpdate (100.0 * curFrame / surface.numFrames)
927							)
928
929							-- Allow the user to cancel if they get bored or panic
930							if getProgressCancel() then
931							(
932								progressEnd()
933								return undefined
934							)
935						)
936					)
937					catch
938					(
939						-- Always call this otherwise gmax kind of locks up
940						progressEnd()
941						throw
942					)
943
944					progressEnd()
945				)
946			)
947
948			update gmaxMesh
949
950			-- Move to end of surface ready for the next one
951			fseek bstream (surface.offsetStart + surface.offsetEnd) #seek_set
952
953			-- Return gmax mesh
954			gmaxMesh
955		),
956
957
958		-- Load and construct the entire MD3 model
959		fn LoadModel=
960		(
961			-- Load MD3_Header
962			fseek bstream 0 #seek_set
963
964			header = MD3_Header()
965			header.LoadFromStream bstream
966			--header.DebugOut()
967
968			if not header.IsValid() then
969			(
970				format "Invalid header, aborting.\n"
971				return false;
972			)
973
974			-- Process tags only if we're loading or attaching
975			if tagAction.state != 1 then
976			(
977				-- Load tags
978				fseek bstream header.offsetTags #seek_set
979
980				tagObjects = #()
981
982				local relativeTag
983
984				for i = 1 to header.numFrames do
985				(
986					local curTag = MD3_Tag()
987
988					for j = 1 to header.numTags do
989					(
990						curTag.LoadFromStream bstream
991
992						if i == 1 then
993						(
994							local tagObject = CreateTagObject curTag
995							append tagObjects tagObject
996
997							in coordsys world tagObject.transform = curTag.GetMatrix()
998
999							animate on
1000							(
1001								at time 0
1002								(
1003									in coordsys world tagObject.transform = curTag.GetMatrix()
1004								)
1005							)
1006						)
1007						else
1008						(
1009							animate on
1010							(
1011								at time (i-1)
1012								(
1013									in coordsys world tagObjects[j].transform = curTag.GetMatrix()
1014								)
1015							)
1016						)
1017					)
1018				)
1019			)
1020
1021			-- Load frames
1022			fseek bstream header.offsetFrames #seek_set
1023			frames = #()
1024
1025			--format "Loading % frames...\n" header.numFrames
1026			local curFrame = MD3_Frame()
1027			for i = 1 to header.numFrames do
1028			(
1029				curFrame.LoadFromStream bstream
1030				append frames (copy curFrame)
1031			)
1032
1033			-- Load skin file
1034			LoadSkinFile skinFile
1035
1036			-- Load surfaces
1037			surfaceList = #()
1038
1039			fseek bstream header.offsetSurfaces #seek_set
1040
1041			for i = 1 to header.numSurfaces do
1042			(
1043				-- Load and create the surface model
1044				local newObject = LoadSurface()
1045				if newObject == undefined then
1046				(
1047					print "MD3 Import Cancelled"
1048					ClearTextureCache()
1049					gc()
1050					return false
1051				)
1052				append surfaceList newObject
1053			)
1054
1055
1056			-- Fixed models drive a load of tags but aren't controlled by one
1057			local isFixed = false
1058			local rootTag
1059			local rootName
1060
1061			-- Make all surfaces children of the root tag for this model
1062			if tagAction.state != 1 then
1063			(
1064				/*
1065				-- Guess our root tag (depends on model, actually unnecessary)
1066				case (ToLowerCase (StripLodSuffix g_modelName)) of
1067				(
1068					"head":					(
1069						rootName = "tag_head"
1070						isFixed = false
1071					)
1072
1073					"upper":
1074					(
1075						rootName = "tag_torso"
1076						isFixed = false
1077					)
1078
1079					"lower":
1080					(
1081						rootName = undefined
1082						isFixed = true
1083					)
1084
1085
1086					default:
1087					(
1088						rootName = undefined
1089						isFixed = false
1090					)
1091				)
1092
1093
1094				format "I think the root is %\n" rootName
1095				*/
1096				rootName = undefined
1097				isFixed = false
1098
1099				if not isFixed then
1100				(
1101
1102					-- Find the root tag in our tag objects
1103					for ourTag in tagObjects do
1104					(
1105						if ourTag.name == rootName then
1106						(
1107							rootTag = ourTag
1108							exit
1109						)
1110
1111						if rootName == undefined then
1112						(
1113							-- Grab the tag at the origin - probably the root
1114							--format "Checking % pos: %\n" (ourTag.name) (ourTag.pos)
1115							if ourTag.pos == [0,0,0] then
1116							(
1117								rootTag = ourTag
1118								rootName = rootTag.name
1119								exit
1120							)
1121						)
1122					)
1123
1124					if rootTag != undefined then
1125					(
1126						format "Root tag is %\n" rootTag.name
1127
1128						-- Make surfaces children of the root tag
1129						for surf in surfaceList do
1130						(
1131							attachObjects rootTag surf move:false
1132						)
1133
1134						-- Make other tags children of the root tag
1135						for tag in tagObjects do
1136						(
1137							if tag != rootTag then
1138							(
1139								attachObjects rootTag tag move:false
1140							)
1141						)
1142					)
1143					else
1144					(
1145						format "Root tag undefined\n"
1146					)
1147
1148				)
1149			)
1150
1151
1152			-- Auto attach to a tag in the scene that matches with one in the MD3
1153			if tagAction.state == 3 then
1154			(
1155				if isFixed then
1156				(
1157					-- Attach all matching scene tags to our tags
1158					for sceneObj in objects do
1159					(
1160						for ourTag in tagObjects do
1161						(
1162							if (sceneObj != ourTag) and (ourTag.name == sceneObj.name) then
1163							(
1164								attachObjects ourTag sceneObj
1165								exit
1166							)
1167						)
1168					)
1169				)
1170				else
1171				(
1172					local hookedOnto
1173
1174					-- Make ourselves the child of the corresponding tag in the scene
1175					for obj in objects do
1176					(
1177						if (findItem tagObjects obj) == 0 then
1178						(
1179							-- Hook onto scene tag (we become its child)
1180							if (ToLowerCase obj.name) == rootName then
1181							(
1182								-- Got it. Now attach our tag with our model hierarchy to it
1183
1184								format "Found %, attaching our tag to it\n" rootName
1185								attachObjects obj rootTag
1186								hookedOnto = obj
1187								exit	-- Only 1 allowed!
1188							)
1189						)
1190					)
1191
1192					format "Hooked onto = %\n" hookedOnto
1193
1194					-- Now hook other objects onto us
1195					-- Must be careful that we're not hooked onto them
1196					-- Because of this it's only allowed if we haven't got a rootTag
1197					for obj in objects do
1198					(
1199						if (findItem tagObjects obj) == 0 then
1200						(
1201							if rootTag == undefined then
1202							(
1203								for ourTag in tagObjects do
1204								(
1205									if obj.name == ourTag.name then
1206									(
1207										format "Attaching scene tag % to our corresponding tag\n" ourTag.name
1208										attachObjects ourTag obj
1209										exit
1210									)
1211								)
1212							)
1213						)
1214					)
1215
1216				)
1217			)
1218
1219
1220			-- Set animation slider range and FPS
1221			if header.numFrames > 1 then
1222			(
1223				animationRange = interval 0 (amax animationRange.end header.numFrames-1)
1224				frameRate = 10.0 -- a guess, almost certainly wrong (TODO: read animation.cfg)
1225			)
1226
1227			-- All done, try and clean up
1228			ClearTextureCache()
1229
1230			gc()
1231			true
1232		)
1233	)
1234
1235
1236	-------------------
1237	-- Final UI Bits --
1238	-------------------
1239
1240	on importButton pressed do
1241	(
1242		local md3Filename = getOpenFileName caption:"Import Quake III Model" filename:(GetMostRecentPath()) \
1243					types:"Quake III MD3 (*.md3)|*.md3|All Files (*.*)|*.*|"
1244
1245		if md3Filename != undefined then
1246		(
1247			if DoesFileReallyExist md3Filename then
1248			(
1249				AddRecentFile md3Filename
1250
1251				--try
1252				(
1253					local loader = MD3_Loader()
1254					loader.Open md3Filename
1255					loader.LoadModel()
1256					loader.Close()
1257					format "OK\n"
1258				)
1259				--catch
1260				--(
1261				--)
1262
1263				max views redraw
1264			)
1265		)
1266	)
1267
1268	-- Select a skin file and apply it to scene meshes
1269	on applySkin pressed do
1270	(
1271		local skinFilename = getOpenFileName caption:"Select Skin to Apply" filename:(GetMostRecentPath()) \
1272					types:"Quake III Skin File (*.skin)|*.skin|All Files (*.*)|*.*|"
1273
1274		if skinFilename != undefined then
1275		(
1276			if DoesFileReallyExist skinFilename then
1277			(
1278				try
1279				(
1280					LoadSkinFile skinFilename
1281
1282					-- Apply skin to all appropriate objects
1283					for obj in objects do
1284					(
1285						local skinFile = GetSkinForSurface obj.name
1286
1287						if skinFile != undefined then
1288						(
1289							-- Set up approximate paths for object
1290							g_modelFilename = skinFilename
1291							g_modelName = getFilenameFile skinFilename
1292							g_modelPath = getFileNamePath skinFilename
1293
1294							-- Create new material
1295
1296							local newMat = standardMaterial name:(getFilenameFile skinFilename)
1297							local diffuseMap = FindTexture skinFile
1298
1299							if diffuseMap != undefined then
1300							(
1301								newMat.maps[2] = diffuseMap
1302								newMat.mapEnables[2] = true
1303								showTextureMap newMat diffuseMap true
1304							)
1305
1306							obj.material = newMat
1307
1308							update obj
1309						)
1310					)
1311
1312				)
1313				catch
1314				(
1315				)
1316
1317				max views redraw
1318			)
1319		)
1320	)
1321
1322	-- Does exactly what it says :)
1323	on createTag pressed do
1324	(
1325		local verts = #([0, 0, 0], [0, -1, 0], [2, 0, 0])
1326		local tri = #([1, 2, 3])
1327		local tagObject = mesh prefix:"tag_unnamed" vertices:verts faces:tri pos:[0,0,0]
1328		update tagObject
1329		max views redraw
1330	)
1331)
1332