my method is incorrect anyway as you can’t trust the max interpolater to be linear when you need it to be. Though my solution on the surface looks correct, but the central ring is shaded as a totally convex surface, but in reality it’s two flat faces with shared normals.
to show what i mean, create a new sphere tesselate once, set the center rings to smoothing group 2 and the rest to smoothing group 1, and you’ll notice 2 lighting “aberrations” on the central ring. If you use any kind of non linear interpolation for creating the new “in between” normals you will in effect be smoothing the object. If you use a linear interpolation do you end up with a weird smoothed & faceted object ?
your method is absolutely correct. you just don’t need to detach faces. there is no reason for that.
performance is all about setting normal using Edit_Normals modifier and I will show how to do it fast.
no its not, it works for where there would be no difference between smoothed and tessellated faces. But in some areas max’s uv interpolation is incorrect for the situation.
you need to modify your algorithm a little to use GetFaceDegree and GetNormalID methods.
i would just like to see the result of the lighting on the central ring of your script. that’s all.
give some time to other people to find their own solution.
You can do yourself. I give you a hint.
Why settings normals using Edit_Normals modifier is slow? Is it a big deal to put some number in some piece of memory? What is really causing slow down?
I guess you are talking about the modify panel. I tried to switch to the create panel but it causes the setnormal method to fail.
Can I have another hint please?
it’s not modifier panel. unfortunately we can’t do anything with it. Edit_Normal interface works when the modifier is current modifier panel object. It doesn’t work in any mode other than #modify.
well… another hint.
what Edit_Normals modifier actually does do? It modifies normals of mesh below. Every time when the modifier changes the mesh it calls to update the mesh. Every change! Do we need it?
I guess you are talking about disabling the modifier.
When I do that I gain a couple of seconds, but I can only disable it for the setNormal part of the code.
here is my updated version:
(
struct s_VertData
(
index,
OriginalNormals = #()
)
struct s_EdgeData
(
owner,
index,
verts,
midVert,
fn divide =
(
midVert = owner.divideEdge index 0.5
-- format "Edge % divided
" index
),
fn init =
(
verts = polyop.getEdgeVerts owner index
not polyop.isEdgeDead owner index
),
valid = init()
)
struct s_NormalData
(
owner,
faceIndex,
Vert,
index,
dir,
pos,
fn init =
(
local faceNorms = #{}
local vertNorms = #{}
owner.ConvertFaceSelection #{faceIndex} faceNorms
owner.ConvertVertexSelection #{Vert.index} vertNorms
faceNorms = faceNorms as array
vertNorms = vertNorms as array
for i in faceNorms where findItem vertNorms i > 0 do exit with index = i
if index != undefined then (
dir = owner.getNormal index
pos = owner.getVertex Vert.index
true
) else
false
),
fn reconstruct =
(
dir = [0,0,0]
for N in Vert.OriginalNormals do
dir += N.dir
dir = normalize dir
owner.setNormal index dir
),
valid = init()
)
struct s_FaceData
(
owner,
index,
center,
MidVert,
originalVerts = #(),
OriginalVertsData = #(),
originalEdges = #(),
OriginalNormals = #(),
EdgesData = #(),
faceChildren = #{},
midEdgeVerts = #(),
MidEdgeVertsData = #(),
fn getFaceNormals face:index =
(
local faceVerts = polyop.getFaceVerts owner face
local OVerts = for i = 1 to originalVerts.count where findItem faceVerts originalVerts[i] > 0 collect OriginalVertsData[i]
local EVerts = for i = 1 to midEdgeVerts.count where findItem faceVerts midEdgeVerts[i] > 0 collect MidEdgeVertsData[i]
local Verts = #()
join Verts OVerts
join Verts EVerts
if MidVert != undefined then
append Verts MidVert
local NormsData = #()
for V in Verts do (
local NewNormData = s_NormalData owner:owner.norms faceIndex:face Vert:V
if NewNormData.valid then
append NormsData NewNormData
)
NormsData
),
fn getNormalByVert v =
(
for N in OriginalNormals where N.Vert.index == v do
exit with N
),
fn init =
(
if not polyop.isFaceDead owner index then (
center = polyop.getFaceCenter owner index
originalVerts = polyop.getFaceVerts owner index
originalEdges = polyop.getFaceEdges owner index
OriginalVertsData = for v in originalVerts collect s_VertData index:v
OriginalNormals = getFaceNormals()
for V in OriginalVertsData do
V.OriginalNormals = #(getNormalByVert V.index)
faceChildren[index] = true
-- format "Face % initialized
" index
true
) else
false
),
fn setEdgesData Edges =
(
EdgesData = for i in originalEdges collect Edges[i]
),
fn divide =
(
local bit1 = bit.set 0 1 true
local numFacesBefore = polyop.getNumFaces owner
for D in EdgesData do (
local OrigNorms = for v in D.verts collect OriginalNormals[findItem originalVerts v]
append midEdgeVerts D.midVert
append MidEdgeVertsData (s_VertData index:D.midVert OriginalNormals:OrigNorms)
)
polyop.setVertSelection owner #{midEdgeVerts[1], midEdgeVerts[2]}
owner.ConnectVertices vertexFlag:bit1
local newEdge = polyop.getNumEdges owner
MidVert = s_VertData index:(owner.divideEdge newEdge 0.5) OriginalNormals:OriginalNormals
polyop.setVert owner MidVert.index center
for i = 3 to MidEdgeVerts.count do (
polyop.setVertSelection owner #{midEdgeVerts[i], MidVert.index}
owner.ConnectVertices vertexFlag:bit1
)
local numFacesAfter = polyop.getNumFaces owner
for i = numFacesBefore + 1 to numFacesAfter do
faceChildren[i] = true
-- format "Face % divided
" index
),
fn reconstructNormals =
(
for f in faceChildren do (
local NewFaceNormals = getFaceNormals face:f
for N in NewFaceNormals do
N.reconstruct()
)
-- format "Face % normals reconstructed
" index
),
valid = init()
)
fn tessellateObject obj =
(
if canConvertTo obj Editable_Poly then (
local ts = timeStamp()
max modify mode
obj = convertToPoly (copy obj)
addModifier obj (edit_normals name:"norms")
modPanel.setCurrentObject obj.norms
obj.norms.SelLevel = #Object
local EdgesData = #()
local FacesData = #()
/* Edge Data collection loop */
for i = 1 to polyop.getNumEdges obj do (
local NewEdgeData = s_EdgeData owner:obj index:i
if NewEdgeData.valid then
append EdgesData NewEdgeData
)
/* Face Data collection loop */
for i = 1 to polyop.getNumFaces obj do (
local NewFaceData = s_FaceData owner:obj index:i
if NewFaceData.valid then (
NewFaceData.setEdgesData EdgesData
append FacesData NewFaceData
)
)
/* Edges Subdivision loop */
for D in EdgesData do
D.divide()
/* Faces Subdivision loop */
for F in FacesData do
F.divide()
/* Normals loop */
obj.norms.enabled = false
for F in FacesData do
F.reconstructNormals()
obj.norms.enabled = true
obj.norms.MakeExplicit()
collapseStack obj
format "% tessellation took %
" obj.name (timeStamp() - ts)
gc()
obj
) else
undefined
)
local newObjs = #()
for o in selection do (
local newObj = tessellateObject o
if newObj != undefined then
append newObjs newObj
)
select newObjs
redrawViews()
)
it’s exactly the same place where I put two extra lines of code. But my code is different. Your guess is right, but there is another way to get the result.
since you tessellate the mesh yourself and you exactly know the normal of created vertex you can smartly move the vertex to do tessellation and mesh smoothing at same time (tessellation with tension).
I don’t have a lot of time right now, so I’ll just post my unbuild approach
I’ll use the ‘simpleFaceManager’ to add extra point3 data channels to the faces and store all the face’s vertex normals in there.
“The Per-Face data will survive topology changes and class conversions. For example, if you would convert the plane from the above example to Editable Mesh, the channels will be preserved and each of the four faces in the new mesh will inherit the data from the respective polygons in the Editable Poly. When changing topology by tessellating existing faces, the new faces will inherit the values of the original face. “
So after backing up all the data I’ll just add a tessellate modifier. All the new faces will have the data form their ‘parent’-face. Then it’s just finding a clever way of setting the new normals to the old data.
So the ‘simpleFaceManager’ is the main trick to my approach
TzMtN,
polyop.setVertSelection is the bottleneck of your Face-divide algorithm. Any updating selection of editable poly structure is slow. (ask lo. lo can double check it for you with lo’s new tool ;)) use polyop.setvertflags to connect vertices with custom flag. it will make your divide algorithm ~4 times faster.
I have modified the divide function as you suggested but gain only ~1 sec in general run time on my machine (which is now aprox 23 sec)
am I doing it as you intended?
(
struct s_VertData
(
index,
OriginalNormals = #()
)
struct s_EdgeData
(
owner,
index,
verts,
midVert,
fn divide =
(
midVert = owner.divideEdge index 0.5
-- format "Edge % divided
" index
),
fn init =
(
verts = polyop.getEdgeVerts owner index
not polyop.isEdgeDead owner index
),
valid = init()
)
struct s_NormalData
(
owner,
faceIndex,
Vert,
index,
dir,
pos,
fn init =
(
local faceNorms = #{}
local vertNorms = #{}
owner.ConvertFaceSelection #{faceIndex} faceNorms
owner.ConvertVertexSelection #{Vert.index} vertNorms
faceNorms = faceNorms as array
vertNorms = vertNorms as array
for i in faceNorms where findItem vertNorms i > 0 do exit with index = i
if index != undefined then (
dir = owner.getNormal index
-- pos = owner.getVertex Vert.index
true
) else
false
),
fn reconstruct =
(
dir = [0,0,0]
for N in Vert.OriginalNormals do
dir += N.dir
dir = normalize dir
owner.setNormal index dir
),
valid = init()
)
struct s_FaceData
(
owner,
index,
center,
MidVert,
originalVerts = #(),
OriginalVertsData = #(),
originalEdges = #(),
OriginalNormals = #(),
EdgesData = #(),
faceChildren = #{},
midEdgeVerts = #(),
MidEdgeVertsData = #(),
fn getFaceNormals face:index =
(
local faceVerts = polyop.getFaceVerts owner face
local OVerts = for i = 1 to originalVerts.count where findItem faceVerts originalVerts[i] > 0 collect OriginalVertsData[i]
local EVerts = for i = 1 to midEdgeVerts.count where findItem faceVerts midEdgeVerts[i] > 0 collect MidEdgeVertsData[i]
local Verts = #()
join Verts OVerts
join Verts EVerts
if MidVert != undefined then
append Verts MidVert
local NormsData = #()
for V in Verts do (
local NewNormData = s_NormalData owner:owner.norms faceIndex:face Vert:V
if NewNormData.valid then
append NormsData NewNormData
)
NormsData
),
fn getNormalByVert v =
(
for N in OriginalNormals where N.Vert.index == v do
exit with N
),
fn init =
(
if not polyop.isFaceDead owner index then (
center = polyop.getFaceCenter owner index
originalVerts = polyop.getFaceVerts owner index
originalEdges = polyop.getFaceEdges owner index
OriginalVertsData = for v in originalVerts collect s_VertData index:v
OriginalNormals = getFaceNormals()
faceChildren[index] = true
for V in OriginalVertsData do
V.OriginalNormals = #(getNormalByVert V.index)
-- format "Face % initialized
" index
true
) else
false
),
fn setEdgesData Edges =
(
EdgesData = for i in originalEdges collect Edges[i]
),
fn divide =
(
-- local flag = bit.set 0 1 true
local flag = bit.set 0 30 true
local numFacesBefore = polyop.getNumFaces owner
for D in EdgesData do (
local OrigNorms = for v in D.verts collect OriginalNormals[findItem originalVerts v]
append midEdgeVerts D.midVert
append MidEdgeVertsData (s_VertData index:D.midVert OriginalNormals:OrigNorms)
)
-- polyop.setVertSelection owner #{midEdgeVerts[1], midEdgeVerts[2]}
polyop.setVertFlags owner #{midEdgeVerts[1], midEdgeVerts[2]} flag mask:flag
owner.ConnectVertices vertexFlag:flag
polyop.setVertFlags owner #{midEdgeVerts[1], midEdgeVerts[2]} 0 mask:flag
local newEdge = polyop.getNumEdges owner
MidVert = s_VertData index:(owner.divideEdge newEdge 0.5) OriginalNormals:OriginalNormals
polyop.setVert owner MidVert.index center
for i = 3 to MidEdgeVerts.count do (
-- polyop.setVertSelection owner #{midEdgeVerts[i], MidVert.index}
polyop.setVertFlags owner #{midEdgeVerts[i], MidVert.index} flag mask:flag
owner.ConnectVertices vertexFlag:flag
polyop.setVertFlags owner #{midEdgeVerts[i], MidVert.index} 0 mask:flag
)
local numFacesAfter = polyop.getNumFaces owner
for i = numFacesBefore + 1 to numFacesAfter do
faceChildren[i] = true
-- format "Face % divided
" index
),
fn reconstructNormals =
(
for f in faceChildren do (
local NewFaceNormals = getFaceNormals face:f
for N in NewFaceNormals do
N.reconstruct()
)
-- format "Face % normals reconstructed
" index
),
valid = init()
)
fn tessellateObject obj =
(
if canConvertTo obj Editable_Poly then (
local ts = timeStamp()
max modify mode
obj = convertToPoly (copy obj)
addModifier obj (edit_normals name:"norms")
modPanel.setCurrentObject obj.norms
obj.norms.SelLevel = #Object
local EdgesData = #()
local FacesData = #()
/* Edge Data collection loop */
for i = 1 to polyop.getNumEdges obj do (
local NewEdgeData = s_EdgeData owner:obj index:i
if NewEdgeData.valid then
append EdgesData NewEdgeData
)
/* Face Data collection loop */
for i = 1 to polyop.getNumFaces obj do (
local NewFaceData = s_FaceData owner:obj index:i
if NewFaceData.valid then (
NewFaceData.setEdgesData EdgesData
append FacesData NewFaceData
)
)
/* Edges Subdivision loop */
for D in EdgesData do
D.divide()
/* Faces Subdivision loop */
for F in FacesData do
F.divide()
/* Normals loop */
obj.norms.enabled = false
for F in FacesData do
F.reconstructNormals()
obj.norms.enabled = true
obj.norms.MakeExplicit()
collapseStack obj
format "% tessellation took %
" obj.name (timeStamp() - ts)
gc()
obj
) else
undefined
)
local newObjs = #()
for o in selection do (
local newObj = tessellateObject o
if newObj != undefined then
append newObjs newObj
)
select newObjs
redrawViews()
)
and what about your other hint? I can’t think of anything there, it must be something I am not familiar with or am I just stupid?
so… what do you have now? we decreased tessellation time from 32 to 23 sec. which is good!
here is my benchmarking:
Edge Data collection loop: 8
Face Data collection loop: 933
Edges Subdivision loop: 1275
[color=Red]Faces Subdivision loop: 14058
Normals loop: 4538
Tessellation: 21208[/color]
there two things to need improvement. start with faces… let me check what can we do.
i changed the divide function to:
fn divide =
(
local numFacesBefore = owner.numfaces
for D in EdgesData do (
local OrigNorms = for v in D.verts collect OriginalNormals[findItem originalVerts v]
append midEdgeVerts D.midVert
append MidEdgeVertsData (s_VertData index:D.midVert OriginalNormals:OrigNorms)
)
newEdge = polyop.createEdge owner midEdgeVerts[1] midEdgeVerts[2]
MidVert = s_VertData index:(polyop.divideEdge owner newEdge 0.5) OriginalNormals:OriginalNormals
polyop.setVert owner MidVert.index center
for i = 3 to MidEdgeVerts.count do (
polyop.createEdge owner midEdgeVerts[i] MidVert.index
)
local numFacesAfter = owner.numfaces
for i = numFacesBefore + 1 to numFacesAfter do
faceChildren[i] = true
),
it saves me ~5.5 sec
Faces Subdivision loop: 8680
the real bottleneck was polyop.getNumEdges
I’m sure that storing all the polyop methods in a variable will save you a bit of time.
it’s an interesting situation… there are almost 900 views for this topic but only 4 people are really taking part in it.
is it:
1) too complicated for beginners
2) too trivial for pros
3) not having any practical application
4) just wished to be got than discovered
5) other
?
why would people like to watch but not touch?
I’m not having to much time this week, but has I’ve said earlier this is a little bit out of my league, I dont do to many mesh operation scripts