Converting simple renderer script to max sdk plugin


#1

Hello there,
i am working on a kind of long term project right now. I ve reached my goal ( with help of the user @PolyTools3D ) using a maxscript. That maxscript is based on the svg renderer tutorial and is modified so it outputs the brightness of all faces of one specific object in the scene. I also implemented very simple dropshadows using RayMeshGridIntersect to check how many of a faces vertices cross and object if you draw a line from it to a light source.
Now to my problem. Actually the performance without dropshadows is quite okay (32ms per frame) But after implementing the dropshadows it is around 370ms per frame. Thats very sad because I am trying to do some realtime experiments. Now I thought that I have to somehow do it with max sdk to make it faster. But it seems to me that it would be more efficient to p- ay someone to convert it for me . Can someone help me ? or offer me how much it would c - ost? I am a poor art student, so i cannot p - ay huge amounts… I ask also because I have no clue if it is much work for some experienced person.
Thanks in advance,
Flub
thats the code :
`
(

clearListener()
-- Generals
global myNodeName = "kopfmodell"
-- face mapping position in uv map from left top to hardware order
global faceMap = #(159, 154, 153, 60, 134, 59, 79, 19, 43, 94, 56, 7, 12, 20, 40, 27, 65, 82, 121, 176, 1, 77, 90, 30, 91, 89, 78, 81, 85, 57, 76, 31, 44, 13, 11, 10, 83, 49, 50, 64, 93, 87, 88, 5, 4, 3, 6, 92, 75, 66, 34, 22, 58, 24, 23, 33, 21, 80, 41, 28, 39, 26, 47, 61, 9, 46, 32, 45, 2, 8, 14, 16, 15, 35, 36, 51, 73, 69, 72, 67, 74, 52, 37, 62, 18, 17, 86, 84, 63, 48, 150, 101, 106, 114, 38, 68, 70, 42, 25, 71, 29, 55, 54, 53, 185, 183, 172, 175, 179, 151, 170, 125, 138, 107, 173, 113, 137, 188, 144, 158, 187, 181, 182, 99, 98, 97, 100, 186, 95, 171, 184, 124, 152, 118, 117, 127, 115, 174, 135, 122, 133, 120, 105, 104, 177, 143, 126, 139, 96, 102, 108, 110, 109, 129, 130, 145, 169, 160, 128, 116, 168, 146, 131, 156, 112, 111, 180, 178, 157, 142, 141, 155, 103, 140, 132, 162, 164, 136, 119, 165, 123, 149, 148, 147, 167, 163, 166, 161);
-- face mpping face ids to hardware order of leds
global faceMap2 = #(132,162,164,136,119,165,123,149,148,147,167,163,166,161,168,146,131,156,112,111,180,178,157,142,141,155,103,140,126,139,96,102,108,110,109,129,130,145,169,160,128,116,152,118,117,127,115,174,135,122,133,120,105,104,177,143,144,158,187,181,182,99,98,97,100,186,95,171,184,124,185,183,172,175,179,151,170,125,138,107,173,113,137,188,150,101,106,114,38,68,70,42,25,71,29,55,54,53,73,69,72,67,74,52,37,62,18,17,86,84,63,48,47,61,9,46,32,45,2,8,14,16,15,35,36,51,75,66,34,22,58,24,23,33,21,80,41,28,39,26,11,10,83,49,50,64,93,87,88,5,4,3,6,92,1,77,90,30,91,89,78,81,85,57,76,31,44,13,79,19,43,94,56,7,12,20,40,27,65,82,121,176,159,154,153,60,134,59);
global mappedOutput = #(); -- output array
mappedOutput = (for i=1 to 188 collect (if i == 1 then 255 else 0)) --debugging values

try(
	global telnetClient  = DotNetObject "System.Net.Sockets.TcpClient" "192.168.4.1" 23
	global telnetStream =  telnetClient.GetStream()
) catch (
	print "Konnte die Verbindung nicht aufbauen."
)
fn telnetClose = (
	if (telnetClient != undefined) then (
		telnetStream.Close()
		telnetClient.Close()
	)
)
fn telnetWrite msg = (
	telnetStream =  telnetClient.GetStream()
	data = (dotNetClass "System.Text.Encoding").ASCII.GetBytes (msg + "\r\n")
	for b in data do telnetStream.WriteByte b
	telnetStream.Flush()
)
fn telnetWriteBytes  byteArray = (
	telnetStream.Write byteArray 0 188
	telnetStream.Flush()
)

try destroydialog ::RO_DISPLAY_FACES_COLORS catch()

rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:250
(
	label lab1 "Live Mode - Serial Transfer"
	checkbutton btnStartStop "Start" pos:[8,100] width:154 height:28
	
	timer clock interval:2000 active:false
    global GW_DisplayFacesColors
	
	local node = getNodeByName (myNodeName as string)
    local GetfaceNormal = polyop.getfaceNormal
    local GetFaceCenter = polyop.getFaceCenter

    fn CalculateFacesColors obj =
    (
		meshIntersections  = #()
		for otherObject in Geometry where (otherObject.name != obj.name) do (
			rm = RayMeshGridIntersect () --create an instance of the Reference Target
			rm.Initialize 10 --init. the voxel grid size to 10x10x10
			rm.addNode intersectionObject 
			rm.buildGrid ()
			append meshIntersections rm
		)
        for faceIndex = 1 to obj.numfaces do (
            value = 0
			faceNormal = normalize (GetfaceNormal obj faceIndex)
			faceCenter = GetFaceCenter obj faceIndex
			
			lightIndex = 0
			for k in lights where classof k != targetobject do (
			--for k in lights where (matchPattern k.name pattern:"*.Target" != true) do (
				lightIndex += 1
				lightDir = normalize (k.pos - faceCenter)
				
				-- calculate drop shadow
				faceVerts = polyop.getFaceVerts obj faceIndex
				dropShadowStrength = 0
				for vertexIndex in faceVerts do (
					thePos = polyop.getVert obj  vertexIndex
					theVector = k.pos - thePos
					for rm in meshIntersections do (
						theHitsCount = rm.intersectRay thePos theVector false --intersect the ray with the theObj
						if theHitsCount > 0 then --if have hit anything...
						(
							dropShadowStrength += (1.0/theFaceVerts.count)
						)
					)
				)
				-- end drop shadow calculation
				
				diffuse = amax ((dot lightDir faceNormal)*k.multiplier) 0
				value += k.color.r * diffuse * (1-dropShadowStrength)
			)
			
			value = amin (int value) 255
			mappedOutput[faceMap2[faceIndex]] = value
		)
    )
    
    fn GW_DisplayFacesColors =
    (
		st = timestamp()
		CalculateFacesColors(node)
		--telnetWriteBytes(mappedOutput)
		format "% ms\n" (timestamp()-st)
    )
	
	on clock tick do
    (
        GW_DisplayFacesColors()
		--print "tick"
	)

	fn exitProgram = (
		print "Exit program"
		telnetClose()
		clock.active = false
		unregisterRedrawViewsCallback GW_DisplayFacesColors
		completeredraw()
	)
	
    on RO_DISPLAY_FACES_COLORS open do
    (
        unregisterRedrawViewsCallback GW_DisplayFacesColors
        completeredraw()
    )

    on RO_DISPLAY_FACES_COLORS close do
    (
		exitProgram()
    )
	on btnStartStop changed arg do
	(
		unregisterRedrawViewsCallback GW_DisplayFacesColors
		if arg then -- start program
        (
			--telnetWrite("q") -- serial command to enter menu 
			--telnetWrite("livemode") -- serial command to enter live mode
			--registerRedrawViewsCallback GW_DisplayFacesColors
			clock.active = true
			if lights.count == 0 then
			(
				node = undefined
				btnStartStop.checked = false
				messagebox "There must be at least one light source in the scene"
			) else (
				if classof node.mat == standardmaterial do node.mat.diffuse = cp1.color
			)		
			
		) else (
			clock.active = false
			unregisterRedrawViewsCallback GW_DisplayFacesColors
		)
		if btnStartStop.checked then (
			btnStartStop.Text = "Stop"
		) else (
			btnStartStop.Text = "Start"
		)
		completeredraw()
	)

)
createdialog RO_DISPLAY_FACES_COLORS


#2

You can ask here: https://www.sinisoftware.net/index.php?option=com_content&view=article&id=36&Itemid=526


#3

Before moving to anything more complex (e.g., a c++ implementation), you should set a goal.
You say that the script is not fast enough … well … but maybe there is a way to make it works faster.
(but… if it was originally written by @PolyTools3D, the chance is little).

anyway… what performance is good enough for you?


#4

It may take a working week. This means it can cost ~ $1000, by given, that’s a piece goods

You asked, I answered. (Honestly, it never makes sense to order a small script (tool) for someone else’s commercial development)


#5

Hello, thanks for your answers. The goal is to have it around 15 ms per frame with drop shadows . My obj to get those face values about has only 188 faces. But a maximum of 40 ms would be acaptable too . The other low poly objects for the play with shadows would be maximum two. If I can manage to get a plug-in skeleton from the sdks plugin wizard I imagine it to be quite possible to write those calculations there but I don’t know if it will be so much faster. If not I am not skilled enough to implement it better than just plain in one function which gets all lights and objects and returns those values to send them via telnet or serially

Continuing the script I would need to find way for faster Ray intersection calculations . I don’t know how but i think instead of creating a ray intersection grid obj for every other object and than looping through them for every vertex I should some how snapshot and attach all other objects to one big shadow object (but how?) and then create an array for all 188 vertices
Which stores if they are in a drop shadow or not.
Then I I wouldn’t check vertices multiple times but I would need two light loops:
Now i am at around 90ms per frame with 1 light source and one extra object with 200 faces for this function, do you see any possible performance improvements for it:
if i put in a second light source execution time doubles.
´

fn CalculateFacesColors obj =
(
	-- collect lights
	theLights = #()
	for k in lights where classof k != targetobject do (
		append theLights #(k.pos, k.multiplier, k.color.r)
	)
		
	-- calculate shadow information
	vertexShadows = #()
	for otherObject in Geometry where (otherObject.name != obj.name) do (
		rm = RayMeshGridIntersect () --create an instance of the Reference Target
		rm.Initialize 10 --init. the voxel grid size to 10x10x10
		rm.addNode otherObject 
		rm.buildGrid ()
		for i=1 to obj.numVerts do (
			v = polyop.getVert obj i
			for lightIndex = 1 to theLights.count do (
				shadows = #()
				theVector = theLights[lightIndex][1] - v
				theHitsCount = rm.intersectRay v theVector true --intersect the ray with otherObject
				if theHitsCount > 0 then shadows[i] = true else shadows[i] = false --if have hit anything...
				vertexShadows[lightIndex] = shadows	
			)
                )
        )
		
	for faceIndex = 1 to obj.numfaces do (
		value = 0
		faceNormal = normalize (GetfaceNormal obj faceIndex)
		faceCenter = GetFaceCenter obj faceIndex
		faceVerts = polyop.getFaceVerts obj faceIndex
		
		for lightIndex = 1 to theLights.count do (
			lightDir = normalize (theLights[lightIndex][1] - faceCenter)
			
			-- calculate drop shadow
			dropShadowStrength = 0
			for v in faceVerts do (
				if vertexShadows[v] == true then dropShadowStrength += (1.0/theFaceVerts.count)
			)
			-- end drop shadow calculation
				
			diffuse = amax ((dot lightDir faceNormal)*theLights[lightIndex][2]) 0
			value += theLights[lightIndex][3] * diffuse * (1-dropShadowStrength)
		)
		
		value = amin (int value) 255
		mappedOutput[faceMap2[faceIndex]] = value
	)

)

´


#6

if none of the objects are moving or animated you can cache out faceVerts, verts poisitions, faceCenters … etc
rIafZOL1C9
here’re my numbers (nothing is cached except functions, btw)

if I turn my laptop to performance mode total calc time is under 20ms including SetFaceColors


#7

wow thats cool ! can you share your code? what do you mean by caching? saving it to a variable ?
I want to move all objects and lights. What do you mean by caching functions?
i am running win 10 on lenovo w510 i7 q820 1.7ghz quadcore , gpu quadro fx 880m and 16gb ram and ssd drive and am at 90ms on “turbo mode” = /

ps . how did you make that gif? is there a tool for these kind of gifs?


#8

If you want to move everything then caching will be mostly useless
caching functions is something that you already did with GetFaceCenter

I use ShareX for gifs


#9

Hello @Flub,

Could you provide a full description of the task or a test scene?

Amount of objects, faces, lights, animated parameters of any involving node?

I’ll see if I can help you with this.


#10


example_scene01.max (732 KB)

Hello,
I created a test scene which shows the scenario.
in the scene you see a white object . it consists mainly out of planar quad faces. I have a real object with the same structure with lights installed in a way that i am able to control the brightness of every face of it. The goal is to realtime render/calculate how bright every of its faces is in different constellations of objects and light sources. For now only the angle of every face and a drop shadow are implemented in my script.
I want to be able to play around with animations and different objects in max and realtime transfer the look of the object in the max viewpor to the real object via telnet or serial port.
The red objects in the testscene are just examples to see the shadow.
The yellow squares with 6x6 faces are a future idea to be implemented. actually it is just the idea to select one or more objects which faces brightness are calculated and sent out in realtime. Than it would also be perhaps necessary to define which of them throws shadows and which are excluded from this.
Because of your help there is no problem for me to build it with the script now. But it is actually too slow. It takes 200 ms per frame for the test scene at the moment. light_transfer_script_edit09.ms (7.2 KB)

Thank you!!!
Flub


#11

OK, here is a little optimized version. I’ve also included a test scene with animated objects and lights for better comparison.

It can still be optimized.

It runs around 4-5 times faster.

(
	/* SETUP TEST SCENE ####################################################################################################### */
	
	delete objects
	
	master = converttopoly (plane length:300 width:300 pos:[0,0,0] lengthsegs:15 widthsegs:15 name:"master" wirecolor:gray)
	
	max zoomext sel
	
	b1 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[ 50,   0, 50] wirecolor:red)
	b2 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[-50,   0, 50] wirecolor:red)
	b3 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[  0,  50, 50] wirecolor:red)
	b4 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[  0, -50, 50] wirecolor:red)
	
	converttopoly (box lengthsegs:1 widthsegs:1 heightsegs:1 length:20 width:20 height:75 pos:[0, 0, 0] wirecolor:orange)

	l1 = omnilight rgb:(color 255 255 255) pos:[50,0,120] multiplier:1.0
	l2 = omnilight rgb:(color 255 255 255) pos:[ 0,0,150] multiplier:0.0
	
	d1 = dummy()
	d2 = dummy()
	
	b1.parent = b2.parent = b3.parent = b4.parent = d1
	l1.parent = d2
	
	with animate on
	(
		at time 100
		(
			rotate d1 (angleaxis  90 [0,0, 1])
			rotate d2 (angleaxis 360 [0,0,-1])
		)
	)
	
	/* END SETUP TEST SCENE ################################################################################################### */
	
	try destroydialog ::RO_DISPLAY_FACES_COLORS catch()
	
	rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:100
	(
		checkbutton bt_start "Start" pos:[8,8] width:154 height:32
		
		spinner sp_l1 "Light 1 Multiplier: " pos:[8,54] fieldwidth:48 range:[0,1,1.0] scale:0.01
		spinner sp_l2 "Light 2 Multiplier: " pos:[8,76] fieldwidth:48 range:[0,1,0.0] scale:0.01
		
		global GW_DisplayFacesColors
		
		local node = $master
		
		local GetfaceNormal = polyop.getfacenormal
		local GetFaceCenter = polyop.getfacecenter
		local GetFaceVerts  = polyop.getfaceverts
		local GetVert       = polyop.getvert
		local SetMapVert    = polyop.setmapvert
		
		local MRIntersect  = #()
		local facesVerts   = #()
		local mapFaces     = #()
		local sourceLights = #()
		local colour       = [0,0,0]
		
		fn CalculateFacesColors = with undo off
		(
			MRIntersect = for j in geometry where j != node collect
			(
				rm = RayMeshGridIntersect()
				rm.Initialize 5
				rm.addNode j 
				rm.buildGrid()
				#(rm, rm.intersectRay)
			)
			
			for f = 1 to node.numfaces do
			(
				value = 0
				
				faceNormal = GetfaceNormal node f
				faceCenter = GetFaceCenter node f
				vertsPos   = for k in facesVerts[f] collect GetVert node k
				
				strength = 1.0/vertsPos.count
				
				for k in sourceLights do
				(
					lightPos = k.center
					lightDir = normalize (lightPos - faceCenter)
					
					shadowStrength = 0
					
					for i in vertsPos do
					(
						for rm in MRIntersect where (rm[2] i (lightPos-i) false) > 0 do shadowStrength += strength
					)
					
					diffuse = amax ((dot lightDir faceNormal)*k.multiplier) 0
					value  += diffuse * (1.0-shadowStrength)
				)
				
				colour.x = colour.y = colour.z = amax 0.0 (amin value 1.0)
				
				for k in mapFaces[f] do SetMapVert node 0 k colour
			)
			
			update node
			
			for rm in MRIntersect do rm[1].free()
		)
		
		fn GW_DisplayFacesColors =
		(
			clearlistener()
			
			st = timestamp(); sh = heapfree
			
			CalculateFacesColors()
			
			format "time:% heap:%\n" (timestamp()-st) (sh-heapfree)
		)
		
		fn SetupScene =
		(
			polyop.applyuvwmap node #face channel:0
			
			sourceLights = for j in lights where classof j != targetobject collect j
			mapFaces     = for j = 1 to node.numfaces collect polyop.getmapface node 0 j
			facesVerts   = for j = 1 to node.numfaces collect GetFaceVerts node j
			
			registerredrawviewscallback GW_DisplayFacesColors
			
			node.vertexColorType  = 0
			node.showVertexColors = on
			
			completeredraw()
		)
		
		on bt_start changed arg do
		(
			unregisterredrawviewscallback GW_DisplayFacesColors
			
			if arg then
			(
				SetupScene()
				playanimation()
				bt_start.text = "Stop"
			)else(
				bt_start.text = "Start"
				stopanimation()
			)
		)
		
		on RO_DISPLAY_FACES_COLORS open do
		(
			unregisterredrawviewscallback GW_DisplayFacesColors
			gc()
		)
		
		on RO_DISPLAY_FACES_COLORS close do unregisterredrawviewscallback GW_DisplayFacesColors
		
		on sp_l1 changed arg do l1.multiplier = arg
		on sp_l2 changed arg do l2.multiplier = arg
		
	)
	
	createdialog RO_DISPLAY_FACES_COLORS

)

#12

What you described looks like build in “Pseudo color exposure control”.
And that you can view in real real time (with “normal” Max lights)
Can view un/smoothed meshes, bump/normal mapped materials also…


#13

Actually, when think bout it, that’s how Max viewport rendering “works” (with lights and shadows).
So don’t get it, you want to transfer that to some specific format, or?


#14

Wow, thats amazing ! thank you thats a big step forward. your testcene runs at 60 ms per frame (around 16 fps) on my computer. Is it much faster on yours?
Do you know if it is an improvement efficient to snapshotToMesh and append all objects instead of the master object to one temporary (data only) object so rm.intersectRay has to run once only? How much speed improvement would a compiled plugin lead to in your opinion? I also just thought about if there s a way to just send out all relevant data on every frame to an external program which is just there to calculate the brightness values out of them. then Max would stuck while running the script. But I think that it would still take some time to output all the mesh data. But i dont know if that s a realistic idea. I mean perhaps it is possible to write a standalone windows program which somehow gets data and calculates but much faster then the script evaluation.

@domos
I want to transfer the appearance of the virtual 3d model to a real one made of plastic and leds.

— another idea is to process the critical calculations via dotnet, do you think that might be a way


#15

The test scene runs at 14-15 ms (66 FPS) per iteration on my end, while the original code runs at 70-71 (14 FPS).

The most critical point I see is to reduce the amount of ray test. Currently, there are 4 test for most of the vertices, when we only need 1.

So for the plane (225 faces) we are casting 900 rays per light, but we only need to cast 256. That will give you a much better performance.

Other point was that SetFaceColor() is much slower than SetMapVert().

I don’t think there will be a huge performance gain using the SDK.

Things that could be improved:

  • Cast only 1 ray per light and vertex
  • If the lit object is static, then caching the values (normal, vertex positions, etc.)
  • If the “blockers” objects (the ones that cast the shadows) are spheres, planes or boxes, then there might be a chance to improve the ray casting by using a custom routine. This should be better to implement using the SDK.
  • If you will just send an array of integers via telnet, then there should be some speed up since you don’t need to actually lit the object.

At the moment I can’t think of any other thing that could have a big impact in the performance.

Here is a different approach, casting only one ray per vertex, but only works with 1 light. It could be modified though to work with more lights.

As you can see it performs around 2 times faster.

(
	/* SETUP TEST SCENE ####################################################################################################### */
	
	delete objects
	
	master = converttopoly (plane length:300 width:300 pos:[0,0,0] lengthsegs:15 widthsegs:15 name:"master" wirecolor:gray)
	
	max zoomext sel
	
	b1 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[ 50,   0, 50] wirecolor:red)
	b2 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[-50,   0, 50] wirecolor:red)
	b3 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[  0,  50, 50] wirecolor:red)
	b4 = converttopoly (box lengthsegs:2 widthsegs:2 heightsegs:2 length:20 width:20 height:20 pos:[  0, -50, 50] wirecolor:red)
	
	converttopoly (box lengthsegs:1 widthsegs:1 heightsegs:1 length:20 width:20 height:75 pos:[0, 0, 0] wirecolor:orange)

	l1 = omnilight rgb:(color 255 255 255) pos:[50,0,110] multiplier:1.0
	l2 = omnilight rgb:(color 255 255 255) pos:[ 0,0,150] multiplier:0.0
	
	d1 = dummy()
	d2 = dummy()
	
	b1.parent = b2.parent = b3.parent = b4.parent = d1
	l1.parent = d2
	
	with animate on
	(
		at time 100
		(
			rotate d1 (angleaxis  90 [0,0, 1])
			rotate d2 (angleaxis 360 [0,0,-1])
		)
	)
	
	/* END SETUP TEST SCENE ################################################################################################### */
	
	try destroydialog ::RO_DISPLAY_FACES_COLORS catch()
	
	rollout RO_DISPLAY_FACES_COLORS "Faces Colors" width:172 height:100
	(
		checkbutton bt_start "Start" pos:[8,8] width:154 height:32
		
		spinner sp_l1 "Light 1 Multiplier: " pos:[8,54] fieldwidth:48 range:[0,1,1.0] scale:0.01
		spinner sp_l2 "Light 2 Multiplier: " pos:[8,76] fieldwidth:48 range:[0,1,0.0] scale:0.01 enabled:false
		
		global GW_DisplayFacesColors
		
		local node = $master
		
		local GetfaceNormal = polyop.getfacenormal
		local GetFaceCenter = polyop.getfacecenter
		local GetFaceVerts  = polyop.getfaceverts
		local GetVert       = polyop.getvert
		local SetMapVert    = polyop.setmapvert
		
		local MRIntersect  = #()
		local facesVerts   = #()
		local mapFaces     = #()
		local sourceLights = #()
		local colour       = [0,0,0]
		
		fn CalculateFacesColors = with undo off
		(
			MRIntersect = for j in geometry where j != node collect
			(
				rm = RayMeshGridIntersect()
				rm.Initialize 5
				rm.addNode j
				rm.buildGrid()
				#(rm, rm.intersectRay)
			)
			
			vertsHits = #()
			for j = 1 to node.numverts do
			(
				vpos = GetVert node j
				hits = 0
				
				for k in sourceLights do
				(
					lightPos = k.center
					for rm in MRIntersect where (rm[2] vpos (lightPos-vpos) false) > 0 do hits += 1
				)
				vertsHits[j] = hits
			)
			
			for f = 1 to node.numfaces do
			(
				value = 0
				
				faceNormal = GetfaceNormal node f
				faceCenter = GetFaceCenter node f
				vertsPos   = for k in facesVerts[f] collect GetVert node k
				
				strength = 1.0/vertsPos.count
				
				for k in sourceLights do
				(
					lightPos = k.center
					lightDir = normalize (lightPos - faceCenter)
					
					shadowStrength = 0
					
					for i in facesVerts[f] do
					(
						for v = 1 to vertsHits[i] do shadowStrength += strength
					)
					
					diffuse = amax ((dot lightDir faceNormal)*k.multiplier) 0
					value  += diffuse * (1.0-shadowStrength)
				)
				
				colour.x = colour.y = colour.z = amax 0.0 (amin value 1.0)
				
				for k in mapFaces[f] do SetMapVert node 0 k colour
			)
			
			update node
			
			for rm in MRIntersect do rm[1].free()	-- Prevent memory leaking
		)
		
		fn GW_DisplayFacesColors =
		(
			clearlistener()
			
			st = timestamp(); sh = heapfree
			
			CalculateFacesColors()
			
			format "time:% heap:%\n" (timestamp()-st) (sh-heapfree)
		)
		
		fn SetupScene =
		(
			polyop.applyuvwmap node #face channel:0
			
			sourceLights = for j in lights where classof j != targetobject collect j
			mapFaces     = for j = 1 to node.numfaces collect polyop.getmapface node 0 j
			facesVerts   = for j = 1 to node.numfaces collect GetFaceVerts node j
			
			registerredrawviewscallback GW_DisplayFacesColors
			
			node.vertexColorType  = 0
			node.showVertexColors = on
			
			completeredraw()
		)
		
		on bt_start changed arg do
		(
			unregisterredrawviewscallback GW_DisplayFacesColors
			
			if arg then
			(
				SetupScene()
				playanimation()
				bt_start.text = "Stop"
			)else(
				bt_start.text = "Start"
				stopanimation()
			)
		)
		
		on RO_DISPLAY_FACES_COLORS open do
		(
			unregisterredrawviewscallback GW_DisplayFacesColors
			gc()
		)
		
		on RO_DISPLAY_FACES_COLORS close do unregisterredrawviewscallback GW_DisplayFacesColors
		
		on sp_l1 changed arg do l1.multiplier = arg
		on sp_l2 changed arg do l2.multiplier = arg
		
	)
	
	createdialog RO_DISPLAY_FACES_COLORS

)

This code with just 1 light, runs at 8 ms per iteration (125 FPS).


#16

so what about VertexPaint modifier, did you try it?
standard 4 segment Teapot runs at about 10fps with 2 lights and shadows on
(but it feels like it has some issues with self-shadowing)

(
		
	modPanelHWND = undefined
	for c in UIAccessor.GetChildWindows (windows.getMAXHWND()) while modPanelHWND == undefined where (d = windows.getHWNDData c)[4] == "ModifyTask" do modPanelHWND = d[1]

	for c in windows.getChildrenHWND modPanelHWND where c[4] == "CustButton" and c[5] == "Assign" do
	(
		hwnd = c[1]
		for i=1 to 30 do
		(
			slidertime = i		
			UIAccessor.PressButton hwnd
		)
		
		exit
	)

)

tcPHoJAT9H


#17

@PolyTools3D
Is your GUI also flickering while running the script?


@Serejah
is there something missing in your code, i have to think about it i didnt get what your script is doing yet


#18

Yes, the Set Keys button flickers.

BTW, I don’t think we are working with the right approach for this. If the task is just to get the color of the faces there must be a much better way of doing it.

You should be able to use much more dense geometry and a lot more lights and get it working at 120 FPS (at least).


#19

I think so too …


#20

Yes, I think so too but I didnt find it so far.

I started with the approach of render to texture onto a very small uv map in real time, it was not perfomative and bad for further user interactions because max cannot do it without selecting the object to be baked.

Then you pointed out the idea of capturing the Viewport from left and right. (thats possible with my model) which was quite good in performance but jitttery in drop shadow values, so the result was flickering kind of when shadows moves in + the UX suffers alot because the gui is stuttering while captures are being made + two viewports are unusable.

The third approach (based on the svg renderer script) runs best so far. But as you said it seems there must be a more efficent approach to get a better realtime experience.

I dont know if i should just use a different software for it, but i dont know which one is structured in a way you can grab internal rendering results from the viewport preview renderer.

I am thinking about if OSL Shaders might offer some possiblities concerning this. But I think actually you cannot get the face id and light positions from the scene and you cannot output things via telnet … but via viewport capturing a small rectangle filled with the resulting data one could get back to max script from the shader.

Is 3ds Max Interactive helpful ? i have no clue if it is for anything other than VR interactions.