598 lines
18 KiB
Plaintext
Executable File
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
|
|
)
|
|
)
|