Files
gtav-src/tools_ng/dcc/current/max2012/scripts/pipeline/ui/RsClothLiveLink.ms
T
2025-09-29 00:52:08 +02:00

589 lines
18 KiB
Plaintext
Executable File

--
-- File:: RsClothLiveLink.ms
-- Description:: RAGE Cloth Editor Live-Link Rollout
--
-- Author:: Michael Taschler <michael.taschler@rockstarnorth.com>
-- Date:: 02 February 2012
--
-----------------------------------------------------------------------------
-- Rollout
-----------------------------------------------------------------------------
rollout RageClothLiveLink "Cloth Live Link" width:280 height:100
(
-------------------------------------------------------------------------
-- Locals
-------------------------------------------------------------------------
local connectionTimer = undefined -- Timer used to periodically verify that a connection is up.
local connectionTimerInterval = 5000 -- Tick every 5 seconds
local restTimer = undefined -- Timer used to periodically verify that a connection is up.
local restTimerInterval = 1000 -- Tick every second
local liveClothEditingService
local curObjSel = undefined
-------------------------------------------------------------------------
-- UI Widgets
-------------------------------------------------------------------------
checkButton btnConnectToGame "Connect to Game" tooltip:"Toggle this to establish a connection with the game." width:250
edittext etLinkStatus "Link Status:" text:"Not connected to the game" enabled:false
button btnSpawnCharacter "Spawn Character in Game" width:250 enabled:false
checkButton btnLiveUpdate "Enable Live Updates" tooltip:"Toggle this for changes you make in max to be automatically sent to the game." width:250 enabled:false
edittext etRestStatus "REST Status:" text:"Not registered" enabled:false caption:"test" tooltip:"blah"
label liveLinkInfo "While Live-Editing is enabled only the pin radii can be modified" width:260 height:28 style_sunkenedge:true offset:[15,0] visible:false
------------------------------------------------------------------------------------
-- RAG Widgets:
------------------------------------------------------------------------------------
local CreatePedsWidgetName = "Peds/Create Peds widgets"
local RentaCrowdPed1WidgetName = "Peds/RentaCrowd(tm)/Crowd Ped 1"
local RentaCrowdPed2WidgetName = "Peds/RentaCrowd(tm)/Crowd Ped 2"
local RentaCrowdPed3WidgetName = "Peds/RentaCrowd(tm)/Crowd Ped 3"
local RentaCrowdSizeWidgetName = "Peds/RentaCrowd(tm)/Crowd size"
local RentaCrowdSpawnWidgetName = "Peds/RentaCrowd(tm)/Spawn crowd"
local CreateClothWidgetName = "Cloth Management/Create Widgets"
local ClothManagementBankName = "Cloth Management"
local ClothRestInterfaceWidgetName = "Rest Interface"
------------------------------------------------------------------------------------
-------------------------------------------------------------------------
-- Methods
-------------------------------------------------------------------------
-- Called whenever the selection changes
fn UpdateInterface sel =
(
curObjSel = sel
if(curObjSel != undefined) then
(
if (btnConnectToGame.state == on) then
(
btnLiveUpdate.enabled = true
)
)
else
(
if (btnLiveUpdate.state == on) then
(
btnLiveUpdate.state = off
RageClothLiveLink.OnLiveUpdateStateChanged false
btnLiveUpdate.enabled = false
)
)
)
fn GetSelectedVertCount =
(
count = 0
curObjSel = ::GetValidObjectSelection()
if (curObjSel != undefined) then
(
count = polyop.getNumVerts curObjSel
)
return count
)
fn GetRestInterfaceWidgetName =
(
local tokens = filterString maxFileName "."
local vertCount = GetSelectedVertCount()
local restInterfaceWidget = stringStream ""
format "%/% % vertices/%" ClothManagementBankName tokens[1] vertCount ClothRestInterfaceWidgetName to:restInterfaceWidget
--print (restInterfaceWidget as string)
return (restInterfaceWidget as string)
)
-- Create the cloth management and ped widgets
fn InitialiseRAGWidgets =
(
RemoteConnection.SendCommand("widget \"" + CreateClothWidgetName + "\"")
RemoteConnection.SendCommand("widget \"" + CreatePedsWidgetName + "\"");
RemoteConnection.SendSyncCommand()
)
fn GetMaxValuesFromChannel obj dataChannel =
(
local values = #()
if (GetChannelSupport obj dataChannel) then
(
for i in 1 to obj.numverts do
(
append values (GetChannelValue obj dataChannel i)
)
)
return values
)
fn SetMaxValuesForChannel obj dataChannel values =
(
if (GetChannelSupport obj dataChannel) then
(
for i in 1 to obj.numverts do
(
SetChannelValue obj dataChannel i values[i]
)
)
)
-- Verifies that max and the game have the same values set, prompting the user if this isn't the case
-- Returns whether the values are now in sync
fn CheckInGameValuesMatchMax =
(
try
(
--liveClothEditingService.UpdateVertexMapping()
-- Check that the number of verts in max match those in game
local inGamePinRadii = liveClothEditingService.GetAllPinRadii(0)
local maxPinRadii = (GetMaxValuesFromChannel curObjSel RAGEClothChannelTable[2].vDataChn)
local pinRadiiAreSame = true
/*
print ("InGame Radii: " + (inGamePinRadii.count as string))
for i in 1 to inGamePinRadii.count do
(
format "%, " inGamePinRadii[i]
)
format "\n"
print ("Max Radii: " + (maxPinRadii.count as string))
for i in 1 to maxPinRadii.count do
(
format "%, " maxPinRadii[i]
)
format "\n"
*/
for i in 1 to inGamePinRadii.count do
(
if (abs(inGamePinRadii[i] - maxPinRadii[i]) > 0.0001) then
(
pinRadiiAreSame = false
break
)
)
if (pinRadiiAreSame == false) then
(
local msg = "The pin radii values set for this cloth are different in max to those in game.\n" +
"Do you wish to copy the max values to game or do you wish to copy the game values to max?\n\n" +
"Select:\n" +
"\t'Yes' to send the max values to the game\n" +
"\t'No' to copy the game values into max\n" +
"\t'Cancel' to disable live editing."
local result = yesNoCancelBox msg title:"Pin Radii Mismatch"
if (result == #yes) then
(
print "Updating game pin radii"
--print maxPinRadii
liveClothEditingService.UpdateAllPinRadii 0 maxPinRadii
)
else if (result == #no) then
(
print "Updating max pin radii"
--print inGamePinRadii
SetMaxValuesForChannel curObjSel RAGEClothChannelTable[2].vDataChn inGamePinRadii
)
else
(
return false
)
)
return true
)
catch
(
messagebox liveClothEditingService.LastExceptionMessage title:liveClothEditingService.LastExceptionType
return false
)
)
fn OnConnectionStateChanged connected =
(
if( connected ) then
(
if ( not RemoteConnection.IsConnected() ) then
(
RemoteConnection.Connect()
)
ipAddress = RemoteConnection.GetGameIP()
--print ("IP: " + ipAddress)
if ( RemoteConnection.IsConnected() and (ipAddress != undefined) ) then
(
etLinkStatus.text = ("Connected to game at " + ipAddress)
liveClothEditingService = dotnetobject "RSG.RESTServices.Game.Cloth.LiveClothEditingService" ipAddress
connectionTimer.Start()
if (curObjSel != undefined) then
(
print "btnLiveUpdate.enabled = true"
btnLiveUpdate.enabled = true
)
btnSpawnCharacter.enabled = true
InitialiseRAGWidgets()
)
else
(
messagebox "No connection to the game could be established"
btnSpawnCharacter.enabled = false
btnConnectToGame.state = off
etLinkStatus.text = "Not connected to the game"
btnLiveUpdate.state = off
btnLiveUpdate.enabled = false
liveClothEditingService = undefined
connectionTimer.Stop()
)
)
else
(
etLinkStatus.text = "Not connected to the game"
btnSpawnCharacter.enabled = false
btnLiveUpdate.state = off
btnLiveUpdate.enabled = false
liveClothEditingService = undefined
connectionTimer.Stop()
)
)
-- Retrieves distance down the tree this particular object is
fn GetNumLevels obj =
(
levels = 0
if (obj != undefined) then
(
while (obj.parent != undefined) do
(
levels = levels + 1
obj = obj.parent
)
)
return levels
)
-- Retrieves the root bone from a skin modifier
fn GetRootBone curObjSel theSkin =
(
-- Keep track of what was selected in the modifiers panel before changing it
local previousSelectedObject = ModPanel.GetCurrentObject()
SetCommandPanelTaskMode #modify
ModPanel.SetCurrentObject curObjSel.modifiers[#skin]
--print "Skin:"
--print theSkin
if (theSkin == undefined) then
(
return undefined
)
boneCount = skinops.getnumberbones theSkin
if (boneCount == 0) then
(
return undefined
)
--print boneCount
local boneObjName = skinops.GetBoneName theSkin 1 0
local rootBoneNode = getNodeByName boneObjName
for boneIdx = 2 to boneCount do
(
boneObjName = skinops.GetBoneName theSkin boneIdx 0
boneNode = getNodeByName boneObjName
if (boneNode != undefined) then
(
boneNodeLevels = GetNumLevels boneNode
rootBoneNodeLevels = GetNumLevels rootBoneNode
if (boneNodeLevels < rootBoneNodeLevels) then
(
rootBoneNode = boneNode
)
)
)
--print "Root Bone:"
--print rootBoneNode
--ModPanel.SetCurrentObject previousSelectedObject
return rootBoneNode
)
-- Does the same thing that the exported does so that we can attempt to match up in-game verts
-- exposed via REST to those in max.
fn GetExportedVertsForSelection =
(
local exportedVertices = #()
curObjSel = ::GetValidObjectSelection()
if (curObjSel != undefined) then
(
-- Get the root bone node for calculating the vert transform
rootBoneNode = GetRootBone curObjSel curObjSel.modifiers[#skin]
if (rootBoneNode != undefined) then
(
parentNode = rootBoneNode.parent
--print "Parent:"
--print parentNode
parentTransform = parentNode.transform
invParentTransform = inverse parentTransform
--print invParentTransform
objTransform = curObjSel.transform
orthogonalize objTransform
objTranslation = objTransform.translation
modifiedObjTransform = transMatrix objTranslation
SetCommandPanelTaskMode #modify
ModPanel.SetCurrentObject curObjSel
for i=1 to (polyop.getNumVerts curObjSel) do
(
currentVert = polyop.getVert curObjSel i node:$.parent
transformedVert = currentVert * modifiedObjTransform * invParentTransform
-- Convert the exported vert to a RSG.Base.Math.Vector3f's
rsgBaseVert = dotNetObject "RSG.Base.Math.Vector3f" transformedVert.x transformedVert.y transformedVert.z
append exportedVertices rsgBaseVert
)
)
)
return exportedVertices
)
fn OnLiveUpdateStateChanged enabled =
(
if (enabled) then
(
local restInterfaceWidgetName = GetRestInterfaceWidgetName()
local keepGoing = false
if (RemoteConnection.WidgetExists(restInterfaceWidgetName) == true) then
(
RemoteConnection.WriteBoolWidget restInterfaceWidgetName true
keepGoing = true
)
else
(
keepGoing = queryBox "Unable to determine if the character is present in game!\n\nPlease ensure that the character is spawned and that the REST interface is enabled for the character you are editing:\nCloth Management -> <character_name> <vertex_count> <address> -> Rest Interface\n\nClick 'yes' once this has been set up, or 'no' to cancel." title:"Warning: RAG Connection Issue"
)
if (keepGoing) then
(
-- Get the vertices as they would appear in the independent export data
exportedVertices = GetExportedVertsForSelection()
--print "Exported Verts:"
--print exportedVertices
local pinRadii = undefined
try
(
liveClothEditingService.UpdateVertexMapping(exportedVertices)
-- Check that the number of verts in max match those in game
pinRadii = liveClothEditingService.GetAllPinRadii(0)
)
catch
(
messagebox liveClothEditingService.LastExceptionMessage title:liveClothEditingService.LastExceptionType
)
--print ("Pin Radii Count: " + (pinRadii.count as string))
--print ("Selection Count: " + ($.numverts as string))
-- Check that we managed to match the pin radii
if (pinRadii == undefined) then
(
etRestStatus.text = "Not registered. Max->Game mapping issue."
btnLiveUpdate.state = off
liveClothEditingService.ClearVertexMapping()
)
-- Ensure that the game version of the piece of cloth has the same number of verts as the max version
else if (pinRadii.count != $.numverts) then
(
local msg = "Unable to live edit the cloth due to a mismatch in the number of vertices.\n" +
"The max version of the cloth has " + ($.numverts as string) +
" verts whereas the game version has " + (pinRadii.count as string) + " verts."
messagebox msg title:"Vertex Count Mismatch"
etRestStatus.text = "Not registered. Vertex count mismatch."
btnLiveUpdate.state = off
liveClothEditingService.ClearVertexMapping()
)
else
(
-- Last thing to check is that the ingame values match the max values
if (CheckInGameValuesMatchMax() == false) then
(
etRestStatus.text = "Not registered. Aborted due to data mismatch."
btnLiveUpdate.state = off
liveClothEditingService.ClearVertexMapping()
)
else
(
local tokens = filterString maxFileName "."
etRestStatus.text = ("Registered to " + tokens[1])
restTimer.Start()
)
)
)
else
(
etRestStatus.text = "Not registered. Aborted by user."
btnLiveUpdate.state = off
liveClothEditingService.ClearVertexMapping()
restTimer.Stop()
)
)
else
(
etRestStatus.text = "Not registered"
liveClothEditingService.ClearVertexMapping()
restTimer.Stop()
)
liveLinkInfo.visible = (btnLiveUpdate.state == on)
::RageClothChannelTuning.UpdateInterface()
)
-- Monitors the state of the game connection
fn OnConnectionTick s e =
(
if (btnConnectToGame.state == on and RemoteConnection.IsConnected() == false) then
(
--If connection has faltered, then notify the UI.
print "Lost connection to the game!"
btnConnectToGame.state = off
OnConnectionStateChanged false
)
)
-- Monitors the state of the rest connection
fn OnRestConnectionTick s e =
(
if (btnLiveUpdate.state == on) then
(
local restInterfaceWidgetName = GetRestInterfaceWidgetName()
-- Check that the cloth is still on screen and that the REST connection is still
-- registered to it.
if (RemoteConnection.WidgetExists(restInterfaceWidgetName) == false or
RemoteConnection.ReadBoolWidget(restInterfaceWidgetName) == false) then
(
btnLiveUpdate.state = off
OnLiveUpdateStateChanged false
etRestStatus.text = "Lost connection"
)
)
)
fn OnOpen =
(
--Create a periodic timer to ping the connection status in the assembly.
connectionTimer = dotNetObject "System.Windows.Forms.Timer"
dotnet.addEventHandler connectionTimer "tick" OnConnectionTick
connectionTimer.Interval = connectionTimerInterval
restTimer = dotNetObject "System.Windows.Forms.Timer"
dotnet.addEventHandler restTimer "tick" OnRestConnectionTick
restTimer.Interval = restTimerInterval
-- Update the locally stored selection
curObjSel = ::GetValidObjectSelection()
)
fn OnClose =
(
-- Switch off the live connection
btnConnectToGame.state = off
dotnet.removeEventHandler connectionTimer "tick" OnConnectionTick
connectionTimer = undefined
dotnet.removeEventHandler restTimer "tick" OnRestConnectionTick
restTimer = undefined
)
fn OnSpawnCharacter =
(
local tokens = filterString maxFileName "."
RemoteConnection.WriteStringWidget RentaCrowdPed1WidgetName tokens[1]
RemoteConnection.WriteIntWidget RentaCrowdPed2WidgetName 0
RemoteConnection.WriteIntWidget RentaCrowdPed3WidgetName 0
RemoteConnection.WriteIntWidget RentaCrowdSizeWidgetName 1
RemoteConnection.SendCommand("widget \"" + RentaCrowdSpawnWidgetName + "\"");
RemoteConnection.SendSyncCommand()
)
fn UpdatePinRadiiForSelection selectedVerts newValue =
(
if (btnLiveUpdate.state == on and btnLiveUpdate.state == on) then
(
try
(
-- For now transfer the values one by one
nSelVerts = selectedVerts.count
for i in 1 to nSelVerts do
(
liveClothEditingService.UpdatePinRadius (selectedVerts[i].index - 1) 0 newValue
)
)
catch
(
messagebox liveClothEditingService.LastExceptionMessage title:liveClothEditingService.LastExceptionType
)
)
)
-------------------------------------------------------------------------
-- Events
-------------------------------------------------------------------------
-- rollout is opening
on RageClothLiveLink open do
(
OnOpen()
)
-- rollout is closing
on RageClothLiveLink close do
(
OnClose()
)
-- connect to game button changes state
on btnConnectToGame changed state do
(
OnConnectionStateChanged (state == on)
)
-- user requests that the character is spawned
on btnSpawnCharacter pressed do
(
OnSpawnCharacter()
)
-- enable live updates button changes state
on btnLiveUpdate changed state do
(
OnLiveUpdateStateChanged (state == on)
)
)