/* 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 ) )