Notifications
Clear all

[Closed] Converting simple renderer script to max sdk plugin

Okay. I am just reading through what .net is and starting to do tutorials. Do you know what kind of app has to be written and how it will be accessed from within max? I think i am gonna learn c++ .net programming as i know c stuff from µC programming a little.

Or do I have to use C# i just started this : https://www.youtube.com/watch?v=1CgsMtUmVgs please tell me if it is the totally wrong direction.
I tried out this but max 2019 does not have utitlities->maxdotnet option
https://www.youtube.com/watch?v=MqyrBop5uzc

The Problem with all these other programs is that they actually dont have the interface to play with different kind of standard objects (text, teapot, vectorgraphics), light and animation, creating loops etc like max has. I started to look into blenders sourcecode to see if i can find the place where viewport is rendered but it is too complicated to understand for me at them moment. and anyways i dont like the kind of navigation in space blender offers. (perhaps i am just used to max)

Okay, if I understand that correctly, I want to write a .net assembly. which is actually some C# code which contains a class with public methods or so. After compiling it to a dll. I copy that dll somewhere in the 3ds max 2019 directory structure. After this I can somehow access those methods from within Maxscript. In other words i can pass data to these functions and retrieve data. All in all i could profit from the better performance of compiled code.
Is this right? If so, can someone help me finding a point to start from? are there any Tutorials or examples of someone who did a similar way of extending .net functions for use in max script? I am very confused by all these different versions of visual studio, as I could only get the sdk plugin wizard running on visual studio express 2015 because in newer versions the file structure changed and i didnt knew where to add those wizard files (vsz …)
Anyways I think I ve understood that, the idea of using a .net thing (assembly?) means it is not related to max sdk anymore.

I doubt that you can benefit from it at all unless you’re willing to implement anything similar to RayMeshGridIntersect.
At least my attempts to use mesh.IntersectRay without any accelerating structure didn’t show up any significant perfomance gains compared to mxs RayMeshGridIntersect

@Flub

As we all well understand, this is a trivial task for current rendering engines and algorithms. The only problem is how and in what format you want to get the result.

And you still have not told us about this.

I don’t know how yet but I just want to have an array with 8bit brightness values (0-255) for each face of an object in real-time To send it serially or via Telnet to some electronics. So there is a real-time relation between the appearance of an object in the viewport and a real object. Just for experiments of perception.
EDIT: I just found this tutorial and I think it is quite interesting for me and other beginners who want to try around extending max script : http://www.klaasnienhuis.nl/2014/08/fly-assemblies-maxscript/

I just started to follow the tutorial to try around with on the fly assemblies.

(
clearListener()
function fn_onTheFlyAssembly_readFileOps =
(
    /*<FUNCTION>
    Description
        creates an on the fly assembly
        the method reads bytes from a binary file and returns integers. Also takes care of
        converting the data from big endian to little endian
        major parts of this code by denisT:  http://forums.cgsociety.org/showpost.php?p=7838323&postcount=6 
    Arguments
    Return
    <FUNCTION>*/
    
    --the C# code
    source  = ""
    source += "using System;\n"
	source += "using Autodesk.Max;\n"
    source += "public class ReadFileOps\n"
    source += "{\n"
	source += "    public string helloWorld()\n"
    source += "    {\n"
	source += "        string result = \"hello World\";\n"
	source += "        return result;\n"
    source += "    }\n"
    source += "}\n"

    --setting up the assembly in memory
    csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
    compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
    compilerParams.GenerateInMemory = on
    compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
    compilerResults.CompiledAssembly.CreateInstance "ReadFileOps"
)
local ReadFileOps = fn_onTheFlyAssembly_readFileOps()
helloWorld = ReadFileOps.helloWorld()

)

How could I pass Max geometry and light (or even all scene )data to the dotnet c# function or How could i access geometry from within the function.
And can someone clarify what dotnet actually is, why cant i just use c#?
Is it only used to compile the c# sharp code into memory? if so, when i create a class library dll file instead. Can I access its functions through maxscrip only via dotnet functions? And to get rid of dotnet one has to embed the code for the dll into some code structure which is fitting the plugin api of 3ds max ? But then i cannot access it through max script anymore. How does the viewport renderer functions? and do you think this might be a starting point : http://docs.autodesk.com/3DSMAX/16/ENU/3ds-Max-SDK-Programmer-Guide/index.html?url=files/GUID-40ED5D02-BBCF-4EE3-9EE7-E59425B49CBB.htm,topicNumber=d30e2631

sorry for asking all these questions… Id really love to come closer to the goal.

1 Reply
(@serejah)
Joined: 11 months ago

Posts: 0

Check out this project if you need examples.
But what is your plan? How do you suppose to make script faster using c# compiled dll?

you are talking about real-time meaning a ‘quick’ way to pass data to the external application. You try to make a ‘quick render’ in the MAX but you have a lot of other things that can slowdown all pipeline – write to file, read from file, parse the data… and these things might be much more critical than the render itself.

I only want output the array from the ram serially or via telnet to some hardware, no file interactions are involved.
I think the actual calculation takes 80 ms per frame on my computer for polyTools3d’s second script with 3ds max 2020. This is without sending the data to hardware but with making the result visible by setting the vertex color.
In some places it says that a plugin code could execute 1000 times faster than using maxscript. But somehow it seems very chaotic and unclear to me how to learn it. Perhaps I should just try to add the rest of the performance fixes to the script and see how fast it runs. But it would be better to have some code which runs like much faster than 35ms per frame so there is some headroom in terms of performance to increase the number of objects to be analysed and to have some time for sending stuff.

My plan is not a real plan = / I just thought that the compiled version of the calculateFaces function would run faster because it runs on a lower abstraction level than maxscript and uses the cpu more efficient.

Ok, here’s the modified Jorge’s version that uses c# function to speed up setting vertex colors

(
	/* SETUP TEST SCENE ####################################################################################################### */
	
	delete objects
	
	master = convertToMesh (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:50 width:50 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 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
			)

			hits = dotnet.ValueToDotNetObject vertsHits (dotNetClass "system.int32[]")
			lights_n_mults = #()
			for l in lights do
			(
				append lights_n_mults l.pos.x
				append lights_n_mults l.pos.y
				append lights_n_mults l.pos.z
				append lights_n_mults l.multiplier
			)
			lights_n_mults = dotnet.ValueToDotNetObject lights_n_mults (dotNetClass "system.single[]")
			
			SetMeshFaceLigthness node.inode.handle hits lights_n_mults
			
			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 =
		(
			meshop.setMapSupport node 0 true
			
			sourceLights = for j in lights where classof j != targetobject collect 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

)

and c# code

static public void SetMeshFaceLigthness( uint handle, int[] vert_hits, float[] light_pos_and_multipliers )
{

	IGlobal ip = GlobalInterface.Instance;
	IINode node = ip.COREInterface7.GetINodeByHandle( handle );

	if ( node == null ) return;

	//var TM_src = node.GetObjectTM( ip.COREInterface7.Time, null );
	//TM_src.Invert();

	IObject obj = node.EvalWorldState( ip.COREInterface7.Time, true ).Obj;
	node.Dispose();
	ITriObject tri = (ITriObject)obj;
	IMesh mesh = tri.Mesh;
	tri.Dispose();
	obj.Dispose();



	IList<ITVFace> map_faces = mesh.MapFaces( 0 );
	var map_verts = mesh.MapVerts( 0 );

	for ( int f = 0; f < mesh.NumFaces; f++ )
	{
		double value = 0f;


		var meshfaceverts = mesh.Faces[ f ].V;

		IPoint3 N = mesh.FaceNormal( (uint)f, true );
		IPoint3 C = mesh.FaceCenter( (uint)f );

		double strength = 1.0 / 3;


		for ( int i = 0; i < light_pos_and_multipliers.Length; i += 4 )
		{
			//IPoint3 light_pos = ip.Point3.Create( light_pos_and_multipliers[ i ], light_pos_and_multipliers[ i + 1 ], light_pos_and_multipliers[ i + 2 ] );
			//IPoint3 light_dir = light_pos.Subtract( C ).FNormalize;

			double x = light_pos_and_multipliers[ i ] - C.X;
			double y = light_pos_and_multipliers[ i + 1 ] - C.Y;
			double z = light_pos_and_multipliers[ i + 2 ] - C.Z;
			double len = Math.Sqrt( x * x + y * y + z * z );

			x = x / len;
			y = y / len;
			z = z / len;

			double shadowStrength = 0;

			for ( int v = 0; v < 3; v++ )
			{

				for ( int j = 0; j < vert_hits[ meshfaceverts[ v ] ]; j++ )
				{
					shadowStrength += strength;
				}

			}
			
			//double diffuse = Math.Max( (light_dir.X * N.X + light_dir.Y * N.Y + light_dir.Z * N.Z) * light_pos_and_multipliers[ i + 3 ], 0 );
			double diffuse = Math.Max( (x * N.X + y * N.Y + z * N.Z) * light_pos_and_multipliers[ i + 3 ], 0 );

			value += diffuse * (1.0 - shadowStrength);

		}

		float avg_value = (float)(Math.Max( Math.Min( value, 1 ), 0 ));


		map_verts[ (int)map_faces[ f ].T[ 0 ] ].Set( avg_value, avg_value, avg_value );
		map_verts[ (int)map_faces[ f ].T[ 1 ] ].Set( avg_value, avg_value, avg_value );
		map_verts[ (int)map_faces[ f ].T[ 2 ] ].Set( avg_value, avg_value, avg_value );

		N.Dispose();
		C.Dispose();
	}

	mesh.Dispose();
	

}

it can further be heavily optimized using unsafe for direct memory reads/writes
and don’t forget to dispose everything that needs to be

Page 3 / 5