| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- --
- -- Command & Conquer Renegade(tm)
- -- Copyright 2025 Electronic Arts Inc.
- --
- -- This program is free software: you can redistribute it and/or modify
- -- it under the terms of the GNU General Public License as published by
- -- the Free Software Foundation, either version 3 of the License, or
- -- (at your option) any later version.
- --
- -- This program is distributed in the hope that it will be useful,
- -- but WITHOUT ANY WARRANTY; without even the implied warranty of
- -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- -- GNU General Public License for more details.
- --
- -- You should have received a copy of the GNU General Public License
- -- along with this program. If not, see <http://www.gnu.org/licenses/>.
- --
- ------------------------------------------------------------------------
- --
- -- SceneSetup.ms - This script allows an artist to set up their MAX
- -- scene to contain multiple LOD and damage models. It contains various
- -- functions that the artist can invoke manually if they wish. Those
- -- functions are named in "studlyCaps". Functions intended to be used
- -- internally have lowercase names with underscores.
- --
- ------------------------------------------------------------------------
- struct SS_bounding_box ( _min = [0,0,0], _max = [0,0,0] )
- -- SS_replace_extension provides the same functionality as the "Assign
- -- Extensions" button on the W3D tools panel, except in script.
- function SS_replace_extension
- name_str
- new_extension
- = (
- -- If there's no extension, return the given name.
- if new_extension == undefined then
- return name_str
- -- Find the "." in the name. If there is none, just tack the
- -- extension on then end and return it.
- local dot_index = findString name_str "."
- if dot_index == undefined then
- (
- if new_extension.count == 1 then
- return name_str + ".0" + new_extension
- else
- return name_str + "." + new_extension
- )
- -- Replace the two characters after the dot with the new extension.
- local replace_str
- if new_extension.count == 1 then
- replace_str = "0" + new_extension
- else
- replace_str = new_extension
- return (replace name_str (dot_index+1) 2 replace_str)
- )
- -- SS_clone_tree is used to clone a whole hierarchy of objects. The cloning
- -- procedure to be used is passed in as an argument, making this a very
- -- flexible function. It operates recursively, cloning each object and
- -- maintaining each object's place in the hierarchy and its W3D AppData.
- function SS_clone_tree
- tree_root
- offset:[-100,0,0]
- parent:undefined
- clone_proc:reference
- extension:undefined
- = (
- -- Create a new object that is a clone of the given one.
- local new_object = clone_proc tree_root
-
- -- Change the extension of this node's name if we were given one.
- if extension != undefined then
- (
- local new_name = SS_replace_extension new_object.name extension
- if new_name != undefined then
- new_object.name = new_name
- )
-
- -- Copy the AppData attached to the tree_root to the new node.
- wwCopyAppData new_object tree_root
-
- -- Move the new object by the given offset.
- move new_object offset
-
- -- Attach the object to its parent in the new tree (if it's not undefined).
- if parent != undefined then
- attachObjects parent new_object move:false
-
- -- Dupe all the children of the current root.
- for child in tree_root.children do
- (
- SS_clone_tree child offset:offset parent:new_object \
- clone_proc:clone_proc extension:extension
- )
-
- -- Return the new root.
- return new_object
- )
- function SS_duplicate_skin_info
- source_root
- target_root
- new_wsm:undefined -- WWSkin WSM to attach target objects to
- tree:undefined -- root node of the target tree
- = (
- if tree == undefined then tree = target_root
- if source_root.modifiers["WWSkin Binding"] != undefined then
- (
- -- Copy the skin info for this object into the target object.
- -- If we haven't copied the WSM yet, it will be copied.
- local retval = wwCopySkinInfo source_root target_root new_wsm tree
- if retval == undefined then
- (
- print("Error copying skin info from " + source_root.name + \
- " to " + target_root.name)
- )
- else new_wsm = retval
- )
-
- -- Duplicate the skin info for all of our children
- local i
- for i = 1 to source_root.children.count do
- (
- local source_child = source_root.children[i]
- local target_child = target_root.children[i]
- new_wsm = SS_duplicate_skin_info source_child target_child \
- new_wsm:new_wsm tree:tree
- )
-
- return new_wsm
- )
- -- SS_create_lod_models creates a number of LOD models based on the given
- -- hierarchy root. Each LOD is cloned from the previous one (as opposed
- -- to them all being clones of the root).
- function SS_create_lod_models
- number
- tree_root
- offset:[-100,0,0]
- clone_proc:reference
- = (
- local lod_roots = #()
- local previous_lod = tree_root
- for i = 1 to number do
- (
- local ext = i as string
- lod_roots[i] = SS_clone_tree previous_lod offset:offset \
- parent:previous_lod.parent clone_proc:clone_proc \
- extension:ext
-
- local original_wsm = wwFindSkinNode previous_lod
- if original_wsm != undefined then
- (
- -- Find the WSM we cloned.
- local cloned_wsm = wwFindSkinNode lod_roots[i]
- if cloned_wsm == undefined then
- print "Warning: A WWSkin object was found but it wasn't linked to the base object!"
- else
- (
- -- Duplicate the WWSkin WSM (but meshes will not be bound to it).
- -- ie. The duplicated WSM will contain the correct bone names.
- local wsm = wwDuplicateSkinWSM original_wsm lod_roots[i]
- if wsm == undefined then
- messageBox("Error: Unable to duplicate the skin object for LOD " + ext)
- else
- (
- -- Copy position, name, etc.
- wsm.name = cloned_wsm.name
- wsm.transform = cloned_wsm.transform
- wsm.parent = cloned_wsm.parent
- delete cloned_wsm
- -- Create a new WWSkin WSM for this LOD with no bindings at all.
- --local wsm = WWSkinSpaceWarp()
- --wsm.name = SS_replace_extension original_wsm.name ext
- --wsm.transform = original_wsm.transform
- --move wsm offset
- --wsm.parent = lod_roots[i]
- )
- )
- )
- print ("Built model: " + lod_roots[i].name)
- previous_lod = lod_roots[i]
- )
-
- -- Return the array of LOD root nodes in the order they were created.
- return lod_roots
- )
- -- SS_create_damage_models creates a number of LOD models based on the
- -- given hierarchy root. Each damage model is cloned from the root.
- function SS_create_damage_models
- number
- tree_root
- offset:[0,-100,0]
- clone_proc:reference
- = (
- local apply_offset = offset
- local damage_roots = #()
- for i = 1 to number do
- (
- local ext = i as string
- damage_roots[i] = SS_clone_tree tree_root offset:apply_offset \
- parent:tree_root.parent clone_proc:clone_proc \
- extension:ext
-
- -- Rename the damage root from "Origin.xx" to "Damage.%02d",i
- if i < 10 then
- damage_roots[i].name = "Damage.0" + ext
- else
- damage_roots[i].name = "Damage." + ext
-
- -- If there is an object bound to a WWSkin WSM, duplicate the
- -- WSM and copy the skin information over to the new object.
- local wsm = SS_duplicate_skin_info tree_root damage_roots[i]
-
- -- Skin information has been duplicated, now replace the cloned
- -- skin WSM with the one we just created with the correct data.
- if wsm != undefined then
- (
- local cloned_wsm = wwFindSkinNode damage_roots[i]
- if cloned_wsm != undefined then
- (
- wsm.name = cloned_wsm.name
- wsm.transform = cloned_wsm.transform
- wsm.parent = cloned_wsm.parent
- delete cloned_wsm
- )
- else
- print "Warning: A WWSkin object was found but it wasn't linked to the base object!"
- )
- print ("Built model: " + damage_roots[i].name)
-
- -- Shift the next model by offset again.
- apply_offset = apply_offset + offset
- )
-
- -- Return the array of damage root nodes in the order they were created.
- return damage_roots
- )
- -- SS_query_integer prompts the user to input an integer value. This
- -- interaction takes place in the pink/white window in the bottom left
- -- of the MAX interface, making it a fallback UI.
- function SS_query_integer
- prompt:
- = (
- local number = getKBValue prompt:prompt
- if classOf number != integer then
- throw "The number must be an integer!"
- return number
- )
- -- SS_get_tree_bbox figures out the bounding box of a hierarchical model.
- -- Returns a SS_bounding_box struct containing the min and max points of the
- -- node and its children.
- function SS_get_tree_bbox
- root
- bbox:undefined
- = (
- if root == undefined then return undefined
-
- if bbox == undefined then
- (
- bbox = SS_bounding_box _min:root.min _max:root.max
- )
- else
- (
- if root.min.x < bbox._min.x then
- bbox._min.x = root.min.x
- if root.min.y < bbox._min.y then
- bbox._min.y = root.min.y
- if root.min.z < bbox._min.z then
- bbox._min.z = root.min.z
-
- if root.max.x > bbox._max.x then
- bbox._max.x = root.max.x
- if root.max.y > bbox._max.y then
- bbox._max.y = root.max.y
- if root.max.z > bbox._max.z then
- bbox._max.z = root.max.z
- )
-
- for child in root.children do
- bbox = SS_get_tree_bbox child bbox:bbox
-
- return bbox
- )
- -- SS_get_set_bbox figures out the bounding box of an ObjectSet.
- function SS_get_set_bbox
- object_set
- bbox:undefined
- = (
- if object_set == undefined then return undefined
-
- bbox = SS_bounding_box _min:object_set.min _max:object_set.max
- return bbox
- )
- -- SS_get_origin_size figures out how big to make the origin cube based
- -- on the size of the bounding box of the given object (including it's
- -- children in the bbox calculation).
- function SS_get_origin_size
- object_set
- = (
- bbox = SS_get_set_bbox object_set
- if bbox == undefined then
- (
- print "Bounding box calculation error for new origin, defaulting" \
- " to origin of size 5"
- return 5.0
- )
-
- -- Figure out the smallest bounding box dimension.
- local min_size = bbox._max.x - bbox._min.x
- if (bbox._max.y - bbox._min.y) < min_size then
- min_size = bbox._max.y - bbox._min.y
- if (bbox._max.z - bbox._min.z) < min_size then
- min_size = bbox._max.z - bbox._min.z
-
- -- Origin size will be 1/6 of the smallest bounding box dimension.
- return min_size / 6
- )
- -- SS_create_origin creates a box to serve as the origin of a model. This is used
- -- by sceneSetup if there is no Origin.00 node present in the scene.
- function SS_create_origin
- name:"Origin.00"
- = (
-
- -- Figure out how big our origin should be. Its size will be based
- -- on the bounding box of the objects in the scene.
- local origin_size = SS_get_origin_size geometry
-
- -- Create a new box, and rename it to the given name.
- -- The origin is created very small so that it doesn't interfere with
- -- the bounding box calculations later on when we figure out exactly
- -- how big the origin should be!
- local origin = Box width:origin_size height:origin_size length:origin_size
- origin.name = name
- origin.pos.z -= origin_size / 2
- origin.pivot.z = 0
- -- Set the box's AppData to the appropriate values for an origin node.
- wwSetOriginAppData origin
- return origin
- )
- -- SS_build_hierarchy attaches all top-level objects to the given root node.
- -- It then assigns ALL OBJECTS IN THE SCENE an extension of ".00"
- function SS_build_hierarchy
- root
- = (
- -- Attach all top-level objects to the given root.
- local top_level = $/*
- for obj in top_level do
- (
- if obj == root then continue
- print ("Attaching " + obj.name + " to " + root.name)
- attachObjects root obj move:false
- )
- -- Append a ".00" extension to ALL objects in the scene.
- for obj in objects do
- (
- local new_name = SS_replace_extension obj.name "00"
- if new_name != undefined then
- obj.name = new_name
- )
- )
- -- USER-CALLABLE: Set up LODs based on the Origin.00 hierarchy (by default).
- -- The user can specify the number of LODs to create, how much to offset
- -- each model by, the method of cloning (copy, instance, reference), and
- -- the root of the hierarchy the LODs should be based on.
- --
- -- Sample call:
- -- createLOD count:2 offset:[-2,0,0] clone_by:copy root:$'Origin.02'
- function createLOD
- count:-1 -- default of -1 means prompt the user
- offset:[-100,0,0] -- offset by -100 on X axis by default
- clone_by:reference -- default to cloning by reference
- root:undefined -- clone Origin.00 if not specified
- = (
- if root == undefined then
- root = $'Origin.00'
- if root == undefined then
- (
- -- Create the origin node and link up a hierarchy.
- root = SS_create_origin()
- SS_build_hierarchy root
- )
- -- Query the user for the number of LODs to create if she didn't supply one.
- local num_lods = count
- if num_lods == -1 then
- num_lods = SS_query_integer prompt:"Number of LODs to create:"
-
- -- Create LODs by cloning the hierarchy 'count' times.
- local lod_roots = SS_create_lod_models num_lods root offset:offset \
- clone_proc:clone_by
- )
- -- USER-CALLABLE: Set up Damage models based on the Origin.00 hierarchy
- -- (by default). The user can specify the number of damage models to
- -- create, how much to offset each model by, the method of cloning
- -- (copy, instance, reference), and the root of the hierarchy the models
- -- should be based on.
- --
- -- Sample call:
- -- createDamage count:3 offset:[0,-2,0] clone_by:instance root:$'Origin.00'
- function createDamage
- count:-1 -- default of -1 means prompt the user
- offset:[0,-100,0] -- offset each model by -100 on the Y axis
- clone_by:reference -- default to cloning by reference
- root:undefined -- default to Origin.00 if not supplied
- = (
- if root == undefined then
- root = $'Origin.00'
- if root == undefined then
- (
- -- Create the origin node and link up a hierarchy.
- root = SS_create_origin()
- SS_build_hierarchy root
- )
-
- -- Query the user for the number of Damage models to create if she didn't supply one.
- local num_damage = count
- if num_damage == -1 then
- num_damage = SS_query_integer prompt:"Number of Damage models:"
-
- -- Create the damage models by cloning the hierarchy 'count' times.
- local damage_roots = SS_create_damage_models num_damage root offset:offset \
- clone_proc:clone_by
- )
- -- USER-CALLABLE: Displays a friendly dialog where the user can choose a number
- -- of settings for how the scene should be set up. This function has no arguments
- -- and will be displayed as a button on the MAX UI.
- --
- -- Sample call:
- -- sceneSetup()
- function sceneSetup
- = (
- -- Figure out some reasonable values for the lod and damage offsets.
- -- These values will be plugged into the dialog that prompts the user
- -- for values.
- local bbox
- if $'Origin.00' != undefined then
- bbox = SS_get_tree_bbox $'Origin.00'
- else
- bbox = SS_get_set_bbox geometry
-
- local x_offset = (bbox._max.x - bbox._min.x) * -1.5
- local y_offset = (bbox._max.y - bbox._min.y) * -1.5
- -- Create an array of default values that will be displayed in the dialog.
- -- (lod_count, lod_offset, lod_clone_proc, damage_count, damage_offset,
- -- damage_clone_proc)
- -- for procs: 1==copy 2==instance 3==reference
- --local default_args = #(2, -2, 3, 3, -3, 3)
- local default_args = #(2, x_offset, 3, 3, y_offset, 3)
-
- -- Show the dialog to get the parameters from the user.
- -- The user's choices will override the above default values.
- local chosen_args = wwSceneSetup default_args
- if chosen_args != undefined then
- (
- -- Pick the user's choices out of the array returned from wwSceneSetup.
- local lod_count = chosen_args[1]
- local lod_offset = chosen_args[2]
- local lod_clone_proc = chosen_args[3]
- local damage_count = chosen_args[4]
- local damage_offset = chosen_args[5]
- local damage_clone_proc = chosen_args[6]
- local lod_proc
- local damage_proc
-
- -- Choose the clone procs
- case lod_clone_proc of
- (
- 1: lod_proc = copy
- 2: lod_proc = instance
- 3: lod_proc = reference
- default: throw "Invalid selection for LOD cloning procedure" lod_clone_proc
- )
- case damage_clone_proc of
- (
- 1: damage_proc = copy
- 2: damage_proc = instance
- 3: damage_proc = reference
- default: throw "Invalid selection for Damage cloning procedure" damage_clone_proc
- )
-
- -- Create the LOD models
- createLOD count:lod_count offset:[lod_offset,0,0] clone_by:lod_proc
-
- -- Create the Damage models
- createDamage count:damage_count offset:[0,damage_offset,0] clone_by:damage_proc
- )
-
- return OK
- )
- -- The macro script definition for the toolbar button.
- macroScript LOD_And_Damage_Setup
- category:"Westwood Scripts"
- buttonText:"LOD and Damage Setup"
- toolTip:"LOD and Damage Setup"
- icon:#("GameTools", 1)
- (
- if objects.count == 0 then
- (
- messageBox("There are no objects in the current scene. Load " + \
- "a scene first.")
- )
- else
- (
- sceneSetup()
- )
- )
- -- The macro script definition for the "create origin" toolbar
- -- button. This will create a new origin object centered at 0,0,0
- -- assign all objects a ".00" extension, and link all top-level
- -- objects to the new origin.
- macroScript Create_Origin
- category:"Westwood Scripts"
- buttonText:"Create Origin"
- toolTip:"Create Origin"
- icon:#("Helpers", 2)
- (
- if objects.count == 0 then
- (
- messageBox "An origin is not useful in an empty scene."
- return OK
- )
- if $'Origin.00' == undefined then
- (
- local origin = SS_create_origin()
- SS_build_hierarchy origin
- print(origin.name + " created.")
- )
- else
- (
- messageBox "Origin.00 already exists"
- )
- )
|