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