Files
2025-09-29 00:52:08 +02:00

598 lines
18 KiB
Plaintext
Executable File

/*
User selects the cable mesh, then sets the variables required.
After button press, the util does the following...
Generates the splines/helpers
Converts the splines to cable meshes
Attaches the spline meshes just generated together
Centres pivot
Condenses multisub (if required)
Copies draw distance and txd attributes from original object to new object
Adds everything back into the same container as the source object.
Sets original source object to don't export.
*/
struct LegacyCableUpgrade
(
obj = undefined,
elemVerts = #{},
startPos = undefined,
endPos = undefined,
curveDeviance = 0.0,
cableDeviance = 0.0,
tolerance = 0.2,
maxIter = 70,
validCurveCounter = 0,
fn getSubElements obj =
(
-- Array to store elements
local Elements = #()
-- Array containing all face indices
local Faces = #{1..obj.numFaces} as array
-- Loop until nothing left in 'Faces' array
while Faces.count > 0 do
(
-- Get the element using the first face in the 'Faces' array
local FaceElem
if classOf obj == Editable_Poly then FaceElem = polyOp.getElementsUsingFace obj Faces[1]
else FaceElem = meshOp.getElementsUsingFace obj Faces[1]
-- Subtract the element from the 'Faces' array
Faces = ((Faces as bitArray) - FaceElem) as array
-- Append the elements
append Elements FaceElem
)
-- 'Elements' now contains an array of bitarrays
Elements
),
/*
fn evalCableDeviance =
(
--get the cable most recently generated from the cableMaker struct and get the positions from its knots
knotCount = numKnots cableMaker.cable
knotPositions = for k = 1 to knotCount collect (in coordsys world getKnotPoint cableMaker.cable 1 k)
knotPositionsOffsets = for k in knotPositions collect cableMaker.pointLineDist2 startPos endPos k
cableDeviance = 0.0
for k in knotPositionsOffsets do cableDeviance += k
cableDeviance /= knotCount
format "cableDeviance: %\n" cableDeviance
cableDeviance
),
*/
--////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- Find out how far the current curve deviates from the staright line connecting start and end
--////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fn evalCableDeviance =
(
cableDeviance = 0.0
catPointOffsets = for p in catenaryCurve.points collect cableMaker.pointLineDist2 startPos endPos p
for v in catPointOffsets do cableDeviance += v
cableDeviance /= 10.0
cableDeviance
),
--////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- Find out the closest slack value for the new cable based on the legacy cable
--////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fn estimateSlack =
(
--format "startPos: % endPos: % \n" startPos endPos
--first lets get all the verts for the element, get their positions and do some calculation to get an average offset from the straightline from start to end
--using distance to line
elemVertPositions = for v in elemVerts collect polyop.getVert obj v
elemVertPositionOffsets = for v in elemVertPositions collect cableMaker.pointLineDist2 startPos endPos v
--print elemVertPositions
--So now for an overall value of deviancy from the straight line, sum the elemVertPositionOffsets to a single value to test against
local curveDeviance = 0.0
for v in elemVertPositionOffsets do curveDeviance += v
curveDeviance /= elemVertPositionOffsets.count
--should be max deviance
--curveDeviance = amax elemVertPositionOffsets
--
--Now lets get the new cable deviancy to test against
--
local currentSlack = 0.5
catenaryCurve.slack = currentSlack
catenaryCurve.simulateHangingWire startPos endPos ::CableGenerationUI.spnNumSegs.value
--format "inital slack: % \n" catenaryCurve.slack
local cableDeviance = evalCableDeviance()
--format "initial cableDeviance: %\n" cableDeviance
--
--Now we have two metrics to work with start adjusting the cable slack and recalculalting the cableDeviance to converge on the curveDeviance value
--
local iter = 1
local currentSlack = 0.5
local lowSlack = 0.001
local highSlack = 1.0
local errorValMin = 0.01
local errorVal = 1.0
--adjust the slack and recalculate the gap between the old curve and the new cable
while errorVal > errorValMin and iter < maxIter do --we'll tapout after 200 hits if we havent got within tolerance
(
if highSlack > 1.0 then highSlack = 1.0
if currentSlack < lowSlack then currentSlack = lowSlack + errorValMin
--modify slack by recalculating a new cat curve and evaluating those point
if cableDeviance > curveDeviance then --in top band
(
highSlack = highSlack - ((highSlack - lowSlack) / 2.0)
)
else --in low band
(
lowSlack = lowSlack + ((highSlack - lowSlack) / 2.0)
)
currentSlack = lowSlack + ((highSlack - lowSlack) / 2.0)
catenaryCurve.slack = currentSlack
catenaryCurve.simulateHangingWire startPos endPos ::CableGenerationUI.spnNumSegs.value
--recalculate cableDeviance
cableDeviance = evalCableDeviance()
--go round again
--inc
iter += 1
if iter > maxIter then exit
errorVal = abs(cableDeviance - curveDeviance)
gap = cableDeviance - curveDeviance
--break()
)
--Now we have an acceptable gap apply the discovered slack value to the actual cable
cableMaker.startNode.slack = currentSlack
),
--////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-- Create the new cable
--////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
fn doUpgrade =
(
obj = selection[1]
if obj == undefined then
(
messagebox "Select some old cable" title:"ERROR"
return false
)
--check its in a container
theContainer = obj.parent
if theContainer == undefined then
(
messageBox "Not in a container" title:"ERROR"
return false
)
--get the data about the mesh for setting later
theName = obj.name
LODDistance = getAttr obj (GetAttrIndex "Gta Object" "LOD distance")
TXDName = getAttr obj (GetAttrIndex "Gta Object" "TXD")
--convert to ePoly
convertToPoly obj
objElements = getSubElements obj
--SetSelectionLevel obj #Face
--setup progressbar
progressStart "Cable Upgrade:"
cableMeshes = #()
proxyCables = #()
undo "cable upgrade" on
(
--loop through elements
progCounter = 1
for elem in objElements do
(
--obj.setselection #Face elem
--
--get start and end positions
--
set coordsys #world
--Lets try to find end faces using number of edges from a vert. If its on the end it should have no more than 3 edges linked to it.
elemVerts = (polyop.getVertsUsingFace obj elem) as array
endVerts = for v in elemVerts where ((polyop.getEdgesUsingVert obj v) as array).count == 3 collect v
if endverts.count == 0 then
(
progressEnd()
messageBox "Can't convert this cable.\nCheck that it is a quad mesh" title:"ERROR"
return false
)
--format "endVerts: %\n" endVerts
--Now lets go through and find all the verts in the endVerts that a re close by tolerance(wire thickness) to the first
--That will be our first group, the leftovers will be the second group
startCluster = #()
endCluster = #()
initialPos = polyop.getvert obj endVerts[1]
append startCluster initialPos
endVerts[1] = false
for v = 2 to endVerts.count do
(
vPos = polyop.getvert obj endVerts[v]
if distance initialPos vPos < 0.1 then
(
--remove this vertID from the list
endVerts[v] = false
--append the loc to the cluster
append startCluster vPos
)
)
--format "startcluster: %\n" startcluster
--Now get the positions of the remaining verts for the endCluster
endCluster = for v in endVerts where v != false collect polyop.getvert obj v
--format "endCluster: %\n" endcluster
--startCluster = for v = 1 to 3 collect polyop.getvert obj elemVerts[v]
--endCluster = for v = (elemVerts.count - 2) to elemVerts.count collect polyop.getvert obj elemVerts[v]
--format "startCluster: % endCluster: %\n" startCluster endCluster
--Now average up the positions in each cluster to get our start and end cable points
startClusterCentre = [0, 0, 0]
for i = 1 to startCluster.count do
(
startClusterCentre += startCluster[i]
)
startPos = startClusterCentre / startCluster.count
endClusterCentre = [0, 0, 0]
for i = 1 to endCluster.count do
(
endClusterCentre += endCluster[i]
)
endPos = endClusterCentre / endCluster.count
--create cables
valid = cableMaker.validateCurve startPos endPos 10
--print valid
if valid then
(
validCurveCounter += 1
--format "good curve % \n" validCurveCounter
local startNode = Point pos:startPos constantscreensize:on
local endNode = Point pos:endPos constantscreensize:on
cableProxy = cableMaker.createCableProxy startPos endPos 10
--set the spline to the preset name for future use
setAppData cableProxy 1 ::CableGenerationUI.ddlPreset.selected
append proxyCables cableProxy
--
--estimate slack
--
estimateSlack()
--get old cable segments for the new mesh
--local meshSegs = (((elemVerts as Array).count / 4) - 1) as Integer
local meshSegs = ::CableGenerationUI.spnMeshSegs.value
--format "meshSegs: % \n" meshSegs
--Generate mesh
--Use the current settings values for mesh creation
cablePresets.getPreset ::CableGenerationUI.ddlPreset.selected
cableMaker.radius = cablePresets.presetValues[3] as float
cableMaker.abrv = cablePresets.presetValues[1] as string
--the cableMakerColour
theColour = filterString cablePresets.presetValues[2] " "
cableMaker.cableColour = (color (theColour[1] as float) (theColour[2] as float) (theColour[3] as float))
--set iteration count higher for better curve accuracy
catenaryCurve.maxIter = 200
cableMesh = cableMaker.createCableMesh cableProxy startPos endPos meshSegs
append cableMeshes cableMesh
catenaryCurve.maxIter = 75
)
else
(
print "bad curve"
)
--update progress
progCounter += 1
progressUpdate (100.0 * progCounter / objElements.count)
windows.processPostedMessages()
)
--check for now cable meshes and bail if there are none
if cableMeshes.count == 0 then
(
progressEnd()
messageBox "No meshes created \nCheck your legacy cables" title:"Error"
return false
)
--------------------------------------------------------------
--COMBINE THE GENERATED MESHES
-------------------------------------------------------------
if cableMeshes.count > 1 then
(
for c = 2 to cableMeshes.count do
(
attach cableMeshes[1] cableMeshes[c]
)
combiCable = cableMeshes[1]
--Fix up GTA attrs
--export degenerate Polys
attrIdx = GetAttrIndex "Gta Object" "Remove Degenerate Polys"
setAttr combiCable attrIdx false
--weld mesh
attrIdx = GetAttrIndex "Gta Object" "Weld Mesh"
setAttr combiCable attrIdx true
--set txd name
attrIdx = GetAttrIndex "Gta Object" "TXD"
setAttr combiCable attrIdx TXDName
--set LODDist
attrIdx = GetAttrIndex "Gta Object" "LOD distance"
setAttr combiCable attrIdx LODDistance
--set appdata on mesh to mark it out as a cable
setAppData combiCable 1 "cableMesh"
)
--centre pivot
CenterPivot cableMeshes[1]
--optimize material assignment
if classOf cableMeshes[1].material == Multimaterial then
(
cableMeshes[1].material = cableMeshes[1].material[1]
)
--fix normals
--cableMaker.fixMergedCableNormals theMesh:cableMeshes[1]
--attach to container
if theContainer != undefined then
(
theContainer.AddNodeToContent cableMeshes[1]
)
cableMeshes[1].name = theName
--fix normals
select cableMeshes[1]
--cableMaker.fixMergedCableNormals()
--Deal with legacy mesh
obj.name = obj.name + "_LEGACY"
--set old cables to non exportable
attrIdx = GetAttrIndex "Gta Object" "Dont Export"
setAttr obj attrIdx true
--add proxy cables to the container
for pc in proxyCables do
(
pc.parent = theContainer
)
)
progressEnd()
messageBox ("Created "+(validCurveCounter as String)+" out of "+(objElements.count as String)+" cables.")
validCurveCounter = 0
return true
),
/***
Upgrade cable proxy from previous complicated rig to the new CableProxy helper
***/
fn upgradeSceneCableProxies =
(
local startNodes = for o in objects where (classOf o == Point) and (isProperty o "slack") collect o
print startNodes
if startNodes.count == 0 then
(
messagebox "no cables to upgrade" title:"Done"
return false
)
local deletedItems = #()
local containerNode = undefined
progressStart "Upgrading..."
progressUpdate 1
local counter = 1
for item in startNodes while (progressUpdate (60.0 * (counter as float / startNodes.count))) do
(
--find it parent container, so we can add the new proxy to it
--local containerNode = undefined
local containerNotFound = true
local theNode = item
while (theNode != undefined) and (theNode != rootNode) and containerNotFound do
(
if (classof theNode.parent == Container) then
(
containerNotFound = false
containerNode = theNode.parent
)
else
(
theNode = theNode.parent
)
)
--startNodePos
local legacyStartNodePos = item.pos
local legacySlack = item.slack
local legacyEndNodePos = undefined
local legacyNameTag = ""
--find the parent
local legacyParent = item.parent
if (legacyParent != undefined) then
(
if (classOf legacyParent == container) then
(
print "Orphaned Start Node"
append deletedItems (DataPair name:item.name class:(classOf legacyParent))
delete item
continue
)
if (classof legacyParent == text) then
(
legacyNameTag = legacyParent.text
)
local legacyGrandParent = legacyParent.parent
if (classof legacyGrandParent == container) or (classOf legacyGrandParent != SplineShape) then
(
legacyGrandParent = undefined
)
--break()
--get all the children
--local legacyCompleteRig =
for child in legacyParent.children do
(
--store the postion for the end node
if(child != item) then
(
legacyEndNodePos = child.pos
)
)
if (legacyStartNodePos != undefined) and (legacyEndNodePos != undefined) then
(
--create a new cableProxy
local newStartNode = Point pos:legacyStartNodePos wirecolor:green constantscreensize:true name:"CableStart"
local newEndNode = Point pos:legacyEndNodePos wirecolor:red constantscreensize:true name:"CableEnd"
local newCableProxy = CableProxy startNode:newStartNode endNode:newEndNode slack:legacySlack
--add them to the container if its found
if (containerNode != undefined) then
(
containerNode.AddNodeToContent newStartNode
containerNode.AddNodeToContent newEndNode
containerNode.AddNodeToContent newCableProxy
)
setTransformLockFlags newCableProxy #all
newCableProxy.points = genCablePoints newStartNode newEndNode 10
newCableProxy.uid = genGuid()
newCableProxy.editing = true
newCableProxy.nameTag = legacyNameTag
)
--delete the old cableProxy
for child in legacyParent.children where (classOf child == Point) do
(
append deletedItems (DataPair name:child.name class:(classOf child))
delete child
)
--now delete the legacyParent iteself
append deletedItems (DataPair name:legacyParent.name class:(classOf legacyParent))
delete legacyParent
if legacyGrandParent != undefined then
(
for child in legacyGrandParent.children where (classOf child == Point) or (classOf child == Text) do
(
print child
append deletedItems (DataPair name:child.name class:(classOf child))
delete child
)
if (classOf legacyGrandParent == SplineShape) do
(
append deletedItems (DataPair name:legacyGrandParent.name class:(classOf legacyGrandParent))
delete legacyGrandParent
)
)
)
counter += 1
)
--now maybe we can find any orphaned partial cabel structures to clean up
local partialCableRigs = for o in objects where (classOf o == SplineShape) and (matchPattern o.name pattern:"cable*") collect o
for item in partialCableRigs do
(
--delete the children
if (item.children.count != 0) do
(
for child in item.children where (classOf child == Point) and (MatchPattern child.name pattern:"cableControl_*") do
(
append deletedItems (DataPair name:child.name class:(classOf child))
delete child
)
)
append deletedItems (DataPair name:item.name class:(classOf item))
delete item
)
progressUpdate 80
--finally look for an orphaned cabelContorls under the container
if (containerNode != undefined) then
(
for item in containerNode.children where (classOf item == Point) and (MatchPattern item.name pattern:"cableControl_*") do
(
append deletedItems (DataPair name:item.name class:(classOf item))
delete item
)
)
progressUpdate 100
print deletedItems
progressEnd()
return true
)
)