Spline Tool

ok so I have an issue where I have outsourced art resources that have been converted into Meshes. (I work with 3d Max)

The problem is that this data is well beyond the acceptable limits of real-time.

The main problem is that every cable will probably need to be replaced with a (much more manageable) spline, and the sheer amount of cables that must be replicated will be too much to accomplish by hand. Here’s a screen to illustrate


(there’s many more files like this)

So I think that it should be possible to create a script that will result in a spline created in the center, and in a perfect world, I’d want it to match that cable radius as well, because as you can see there are varying widths of cables strewn around everywhere.

So my initial idea was spawned from seeing this script on scriptspot


but instead of utilizing that center between two vertices as an alignment point for the pivot, using that as a generation point for spline vertices.
obvious I’d need more than two inputs, but it’s the basic idea. I thought about selecting 2 sets of edges length wise along the cable, perhaps using a select edge loop, but the mesh is triangulated and even retrieving those values would be pretty difficult without manual cleanup.

granted you’d have to specify which Geometry objects will become splines, but that’s a simpler problem.

so to recap

  1. I’m using 3dMax, so solution should probably be maxscript based
  2. I want specified triangulated meshes to result in a spline, and hopefully match the thickness of the cable
  3. final product will be suitable for real-time and simple to map and control LOD

any ideas would be helpful at this point
I did get another idea about perhaps generating the spline utilizing the normals? but I wasn’t entirely sure of the math behind it, and how I’d go about creating it.

It’s likely that those hoses were created from splines to begin with. Have you tried getting that source art?

If that’s not possibly, I think you’re looking at a big headache.

well unfortunately it was made in a different program, and most likely created in NURBs format. so I’m stuck with this essentially. I doubt Max’s importer will be able to translate that curve data into anything better than a triangulated mesh

I’m thinking out loud here, but this might work as long as each pipe of your current file is either a seperate object, or a seperate element.

You start by finding all of the ring verts on one of the end caps. Take the average center of those verts and you have your starting point for the spline. From here, you have a loop that finds the next adjacent ring of verts, then get the average center of those verts - you now have your second knot of the spline. You could also keep track of the verts you’ve already processed if finding the adjacent verts becomes troublesome. Rinse and repeat until you run out of verts to process. Probably the hardest part of this would be finding the adjacent verts, but it should be solvable.

As for the thickness of the pipe, once you find the average center of the end cap, just measure the distance between that point and one of the verts of the end cap. That should give you the radius for the renderable spline.

Hope this helps.

This should get you the centres of each edge ring, assuming your cables are meshes and you’ve selected the first ring of verts.


fn getNextRing obj currentRingVerts visitedVerts =
(
	local allVertsBitArray = #{}
	allVertsBitArray.count = obj.numVerts
	local connectedEdges = meshop.getEdgesUsingVert obj currentRingVerts
	local edgeVerts = meshop.getVertsUsingEdge obj connectedEdges
	local visitedVertsBit = (visitedVerts as bitArray) + allVertsBitArray 
	(edgeVerts * -visitedVertsBit) as array
)

fn averagePos obj vertList =
(
	local total = [0, 0, 0]
	for aVert in vertList do
	(
		total += obj.verts[aVert].pos
	)
	total / vertList.count
)

fn getSplinePoints obj =
(
	local currentRing = (getVertSelection obj) as array
	local visitedVerts = #()
	local splinePoints = #()
	while visitedVerts.count < obj.numVerts do
	(
		append splinePoints (averagePos obj currentRing)
		visitedVerts = join visitedVerts currentRing
		currentRing = getNextRing obj currentRing visitedVerts
	)
	splinePoints 
)
local splineVertPositions = getSplinePoints <yourObj>

All largely untested.

I’ll leave you to build the spline yourself.

Cheers,

Drea

wow brilliant method that.
That looks incredibly promising! I’ll work up the spline draw tonight so I can test it out.
thanks a lot Drea that should be a life saver.

also thanks guys for the constructive input. I really appreciate it

Another conceptual idea is to shrink each cable along it’s normal (reverse “push”) until it is totally collapsed to a line, which will look like a spline, then create the spline by adding a point at every vert, skipping if they are within a threshold distance. This would work better with completely arbitrary meshes. If you have nice rings structures then the other ideas will probably be faster for you.

alright an update for today is It working, I had to change Drea’s average algorithm a little to accomodate some arbitrary vertex weighting that was throwing off the center values.
and I set it so it selects the cap vertices without initial selection input. but it works, still need to adapt it to and perhaps do some optimizing of the curve since it does generate a corner point at each new ring. I’ll post up the final code here when it’s finished if you’d like.
It will probably not be error proof but it’s getting there

Glad it’s working out :D:

The joys of outsourcing. It never gets any easier.

ok so here is a (somewhat) finished code
I hardcoded some optimizing values in there, so you may want to comment that out, but otherwise I think it turned out excellent. does all that I could have hoped for, this will certainly save me countless hours of rebuilding cables.


macroScript Splinify_v 0.3
	Category: "Useful Scripts"
	
--Thanks to Andreas Tawn for helping produce some code for this

(
fn getNextRing obj currentRingVerts visitedVerts =
(
	local allVertsBitArray = #{}
	allVertsBitArray.count = obj.numVerts
	local connectedEdges = meshop.getEdgesUsingVert obj currentRingVerts
	local edgeVerts = meshop.getVertsUsingEdge obj connectedEdges
	local visitedVertsBit = (visitedVerts as bitArray) + allVertsBitArray 
	(edgeVerts * -visitedVertsBit) as array
)--retrieves vertices by storing visited vertex selections, converting vertex selections into edges, 
--then reconverting to vertex to grow selection to next loop

fn averagePos obj vertList =
(
	local total = [0, 0, 0]
	--for aVert in vertList do
	--(
	--	total += obj.verts[aVert].pos
	--)
	--total / vertList.count
	
	for i in vertList do
	(
		longest = 0
		longestcenter = [0,0,0]
		for j in vertList do
		(
			candidate = distance obj.verts[j].pos obj.verts[i].pos
			if candidate > longest then
			(
				longest = candidate	
				longestcenter = (obj.verts[j].pos + obj.verts[i].pos)/2
			)
		)
		total += longestcenter
	)
	total / vertList.count
)-- the commented portion offered problems with vertex weighting for poorly tesselated geometry
-- to fix it this algorithm takes midpoints of distance vectors of 



fn CalculateFirstVerts obj =
(
	local LastNormal 
	
	FirstVerts = #(1,2,3)
	
	for i = 3 to obj.numverts do
	(
		local v1 = (getVert obj 1) 
		local  k = i-1
		local v2 = (getVert obj k) 
		local v3 = (getVert obj i) 
		local VectorA = v2 - v1
		local VectorB = v3 - v1
		VectorA = normalize VectorA
		VectorB = normalize VectorB
		
		

		
		local CurrentNormal = (normalize(cross VectorA VectorB))  
		if i>3 do
		(
			TheAngle = acos ( dot CurrentNormal LastNormal)
						
			if abs(theAngle) < 5 or abs(abs(TheAngle) - 180) < 5 then
			(append	FirstVerts i)
			else
			(exit)
		)
		LastNormal = CurrentNormal
	)	
	
	FirstVerts
)



fn getSplinePoints obj =
(
	--local currentRing = (getVertSelection obj) as array
	local currentRing = CalculateFirstVerts obj 
	obj.selectedVerts = currentRing
	local visitedVerts = #()
	local splinePoints = #()
	while visitedVerts.count < obj.numVerts do
	(
		append splinePoints (averagePos obj currentRing)
		visitedVerts = join visitedVerts currentRing
		currentRing = getNextRing obj currentRing visitedVerts
	)
	splinePoints 
)

local splineVertPositions = getSplinePoints $
local firstRing = (getVertSelection $) as array 
local thickness = distance (meshop.GetVert $ firstRing[1]) splineVertPositions[1] --retrieves radius of cable


new_Spline = splineshape() --creates spline shape
cablespline = addNewSpline new_Spline --creates spline
new_Spline.name = (selection[1].name as string) --names spline same as source object

for i in splineVertPositions do (
	addKnot new_Spline cablespline #corner #curve i --adds knots according to the generated array
)--end loop i

updateShape new_Spline
new_Spline.baseobject.render_displayRenderMesh = true
new_Spline.render_thickness = (thickness * 2) --adds diameter to thickness value
new_Spline.render_mapcoords = true --generate nicely laid out map coords
new_Spline.realWorldMapSize = true --allows for uniform scale across all

--Optimize
new_Spline.steps = 3
Opt_Spline = normalize_spl()
Opt_Spline.length = 2350 -- arbitrary value that should probably not be hardcoded
addmodifier new_Spline Opt_Spline

addmodifier new_Spline (edit_mesh())
--you may want to comment above part out depending on your results
--especially since it's hardcoded in here, it seems to work for me, but has not been extensively tested


)--EndMacro


great result!

mind you, i like warghoul’s approach, it’s more my style that kind of thinking :smiley:

great job either way though. :slight_smile:

honestly I think your right. the pushing normals method might be in the works coming up, I’ve already discovered some hiccups with vertex count here and there on non-ideal geometry, and it results with a few crooked vertices periodically. They’re easily fixed manually, but if I have to address that, then I’ll post up more code utilizing that method.