Draw text on a plane


#1

I Have plane instane and would like to write some random text on it. This seems to be posible i both DX and openGL, but can it be done in max? Also it should worck with posibly all versions of max, so please no feature avaible only isnce max 2016 or 2017.


#2

not quite sure what you want but some of this stuff I did (prototype) might shine a light on what you want to do.

first of all down load bmfont from AngelCode . Use it to generate png font bitmap (and xml description file) which you can use as an alpha for a material which you can the apply to the following scripted object. Note the code was only prototyping for a sdk version so it’s not shall we say a release version

plugin simpleObject sbmtext
	name:"sbmtext"
	classID:#(0x621a9bb3, 0x463d5a7)
	category:"Scripted"
(
	local size = 0, width = 0, height = 0, base = 0, uvshift = [0.0,0.0], lineHeight = 0;
	local textures = #(), lookup = #(), glyph_metrics = #(), kerning = #();
	local loaded_fntfile = false;
	local loadfnt_images  = #("SetProjectFolder_16i.bmp", "SetProjectFolder_16a.bmp", 1, 1, 1, 1, 1, true);
	local reloadfnt_images  = #("MergeAnim_16i.bmp", "MergeAnim_16a.bmp", 1, 1, 1, 1, 1, true);
	
	
--******************************************************************************************************	
	
	parameters main rollout:params
	(
		textstr	 type:#string default:"Hello World"  ui:ui_textstr;
		fntfile  type:#filename default:((getdir #font) + "/Tahoma42.fnt");
		usekerning type:#boolean animatable:false default:false ui:ui_usekerning; 
		alignment type:#integer default:1 animatable:false ui:ui_alignemt;
		xadj type:#float default:0.0 ui:ui_xadj;
		xscale type:#float default:1.0 ui:ui_xscale;
		lhadj type:#float default:0.0 ui:ui_lhadj;h
	)	
	
--******************************************************************************************************	
	
	fn getNode = (refs.dependentnodes this)[1];
	
--******************************************************************************************************	
	
	fn haship a b = (a + b * 256;)	
	
--******************************************************************************************************	
	
	fn cmpfn a b = 
	(
		res = 0;
		if a[1] > b[1] then 
			res = 1 ;
		else if a[1] < b[1] then
			res = -1;
		res;
	)	
	
--******************************************************************************************************
	
	fn readfntfile =
	(
		loaded_fntfile = false;
		textures = #();
		lookup = #();
		glyph_metrics = #();
		kerning = #();
		
		xmlDoc = dotNetObject "system.xml.xmlDocument";
		try
			xmlDoc.load fntfile;
		catch
			return false;			

		local font = xmlDoc.DocumentElement;
		if font.name == "font" then -- main font node		
		(	
			local info = font.item["info"];
			size = info.getAttribute "size" as integer * -1; 
			uvshift = 0.5 * execute ("[" + (info.getAttribute "spacing") + "]")

			local common = font.item["common"];
			lineHeight = common.getAttribute "lineHeight" as integer;
			base = common.getAttribute "base" as integer; 
			width = common.getAttribute "scaleW" as integer; 
			height = common.getAttribute "scaleH" as integer; 
			local num_pages = common.getAttribute "pages" as integer; 
			
			local pages = font.item["pages"];
			textures = for i = 1 to num_pages collect ;	
			(
				local n = pages.ChildNodes.item (i-1);
				#((n.getAttribute "id" as integer)+1,n.getAttribute "file");
			)				
				
			lookup.count = 256;
			for i = 1 to lookup.count do lookup[i] = 1;
			local chars = font.item["chars"];
			local num_chars = chars.getAttribute "count" as integer; 
			glyph_metrics = for i = 1 to num_chars collect 
			(
				local n = chars.ChildNodes.item (i-1);
				local c = n.getAttribute "id" as integer; 
				lookup[c] = i; 
				local x = n.getAttribute "x" as float;
				local y = n.getAttribute "y" as float;
				local w = n.getAttribute "width" as float; 
				local h = n.getAttribute "height" as float; 
				local xoff = n.getAttribute "xoffset" as float;
				local yoff = n.getAttribute "yoffset" as float;
				local xadd = n.getAttribute "xadvance" as float;
				local ndx = n.getAttribute "page" as integer;
				#(c,x,y,w,h,xoff,yoff,xadd,(ndx+1));
			)	
			
			local kernings = font.item["kernings"];
			if usekerning and kernings != undefined then
			(	
				num_kernings = kernings.getAttribute "count" as integer;
				kerning = for i = 1 to num_kernings collect 
				(	
					local n = kernings.ChildNodes.item (i-1);
					[haship (n.getAttribute "first" as integer) (n.getAttribute "second" as integer), n.getAttribute "amount" as integer];
				)
				qsort kerning cmpfn;		
			)		
			loaded_fntfile = true;
		)
	)	
	
--******************************************************************************************************	
	
	fn getlinelengths str = 
	(	
		local strlen = str.count;
		local lengths = #();
		local len = 0.0;
		for i = 1 to strlen do
		(	
			local c = bit.charAsInt str[i];
			if usekerning and kerning.count != 0 and i > 1 then
			(
				local res = bsearch [haship (bit.charAsInt str[i-1]) c, 0] kerning cmpfn;
				if res != undefined then len += res[2];
			)	
			
			local cmet = glyph_metrics[lookup[c]];
			len += cmet[8];
			len += xadj;
			if c == 10 then
			(
				append lengths (len - (cmet[8] + xadj));
				len = 0.0;
			)	
		)	
		append lengths len;
		lengths;
	)	

--******************************************************************************************************
	
	fn doBuildMesh = 
	(
		if not loaded_fntfile then
			readfntfile();
		
		if loaded_fntfile and usekerning and kerning.count == 0 then
			readfntfile();
		
		local strlen = textstr.count;
		
		if loaded_fntfile and (lookup.count != 0 or glyph_metrics.count != 0) and strlen != 0 then
		(	
			local linelenghts = getlinelengths textstr;
			
			local nverts = strlen * 4;
			local nfaces = strlen * 2;

			setNumVerts mesh nverts;
			setNumFaces mesh nfaces;
			
			meshop.setMapSupport mesh 1 true;
			meshop.setNumMapVerts mesh 1 nverts;
			meshop.setNumMapFaces mesh 1 nfaces; 
			
			local xoffset = 0.0;
			local yoffset = 0.0;
			local vi = 1;
			local fi = 1;
			local curline = 1;
			for i = 1 to strlen do
			(
				local c = bit.charAsInt textstr[i];
				if usekerning and kerning.count != 0 and i > 1 then
				(
					res = bsearch [haship (bit.charAsInt textstr[i-1]) c, 0] kerning cmpfn;
					if res != undefined then xoffset += res[2];
				)	
				
				local cmet = glyph_metrics[lookup[c]];
				if c == 10 then -- \n character
				(
					xoffset = -(cmet[8] + xadj);
					yoffset += lineHeight;
					yoffset += lhadj;
					curline += 1;
				)			
				
				local x = xoffset + cmet[6] , u = cmet[2], v = cmet[3],w = cmet[4];
				local h = cmet[5], yoff = cmet[7], mid = cmet[9];
				local d = 0.5 * (xscale * w - w);
				
				if alignment == 2 then x -= linelenghts[curline] * 0.5;
				if alignment == 3 then x -= linelenghts[curline];	
					
				setvert mesh vi [x - d,base - yoff - h - yoffset, 0];  
				v1 = vi; 
				meshop.setmapvert mesh 1 vi [(u - uvshift.x)/width,1.0 - ((v + h) + uvshift.y)/height,0];
				vi += 1;
				
				setvert mesh vi [x - d,base - yoff - yoffset, 0]; 
				v2 = vi; 
				meshop.setmapvert mesh 1 vi [(u - uvshift.x)/width,1.0 - (v - uvshift.y)/height,0];
				vi += 1;
				
				setvert mesh vi [x + w + d,base - yoff - h - yoffset, 0]; 
				v3 = vi; 
				meshop.setmapvert mesh 1 vi [((u + w) + uvshift.x)/width,1.0 - ((v + h) + uvshift.y)/height,0];
				vi += 1;
				
				setvert mesh vi [x + w + d,base - yoff - yoffset, 0]; 
				v4 = vi; 
				meshop.setmapvert mesh 1 vi [((u + w) + uvshift.x)/width,1.0 - (v - uvshift.y)/height,0];
				vi += 1;

				setface mesh fi [v1,v3,v2];
				meshop.setMapFace mesh 1 fi [v1,v3,v2];
				setFaceMatID mesh fi mid;
				setEdgeVis mesh fi 1 true;			
				setEdgeVis mesh fi 3 true;
				fi += 1;

				setface mesh fi [v4,v2,v3]; 
				meshop.setMapFace mesh 1 fi [v4,v2,v3];
				setFaceMatID mesh fi mid;
				setEdgeVis mesh fi 1 true;
				setEdgeVis mesh fi 3 true;
				fi += 1;
				
				xoffset += cmet[8];
				xoffset += xadj;
			)
		)
		else
			delete mesh
		update mesh;
	)		
	
--******************************************************************************************************	
	
	rollout params "Params"
	(
		edittext  ui_textstr "Text:" labelOnTop:true fieldwidth:150 fieldwidth:150 height:150 offset:[-8,0];
		edittext  ui_fntfile "fnt file:" labelOnTop:true fieldwidth:100 offset:[-8,0] readOnly:true across:3;
		button load_fntfile_btn images:loadfnt_images offset:[40,15];
		button reload_fntfile_btn images:reloadfnt_images offset:[20,15];
		checkbox ui_usekerning "Use Kerning" align:#center across:2 offset:[0,2];
		dropdownlist ui_alignemt  items:#("left","center","right") width:56 offset:[20,0];
		spinner ui_xadj "Glyph Spacing:" range:[-999999999.0,999999999.0,0.0] type:#float scale:1.0 align:#right;
		spinner ui_xscale "XScale:" range:[0.001,999999999.0,1.0] type:#float scale:0.01 align:#right;
		spinner ui_lhadj "Line Spacing:" range:[-999999999.0,999999999.0,0.0] type:#float scale:1.0 align:#right;
		
		on load_fntfile_btn pressed do
		(	
			file = getOpenFileName caption:"Select Font File" types:"Font(*.fnt)|*.fnt|All|*.*|" filename:fntfile;
			if file != undefined  then
			(	
				if fntfile != file then
				(	
					ui_fntfile.text = filenameFromPath file;
					fntfile = file;
					readfntfile();
					doBuildMesh();
				)	
			)	
		)
		
		on reload_fntfile_btn pressed do 
		(	
			readfntfile();
			doBuildMesh();
		)	
		
		on params open do
		(	
			ui_fntfile.text = filenameFromPath fntfile;
		)	
	)	

--******************************************************************************************************
	
	tool create
	(
 		on mousePoint click do case click of
 		(
 			1: nodeTM.translation = gridPoint;
 			2: #stop;
 		)
	)
	
--******************************************************************************************************	
	
	on buildmesh do doBuildMesh();

--******************************************************************************************************	

	on update do doBuildMesh();
)

#3

First thanks for the answer, my max knowledge is really low, so I could be complete wrong, but at the moment don´t se how that script can help me. I think, could need something like that TextMap avaible since max 2017, however taht TextMap is complete useles for me, since I need it to worck with older max versions.
I am not really shure about that, but think writing text to a bitmap and then apliing it to a plane as UV map could solve my problem.


#4

Well if you don’t want examples of how to code text and just want to slap a texture on a plane don’t ask questions in the coding forum. You can output text to a bitmap from max using dot Net btw but I’ll not post it as I’m sure you’ll find issue with it.

have a look at pastebitmap and look at the code above work out how to get the source rectangle and destination position for each letter etc


#5

Sory for the misunderstanding, like I said the script should worck with posibly all max versions including really old ones, so dot Net is not a god idea and I also stronglly dislike dot Net.
To make one thing clear I never said something like your code is bad, if you understood it so I apologize, just not understanding it.


#6

I doubt anyone is still using max older than max 9 which is when .NET was introduced. That said, Klunk’s solution above is a great approach, and if you for one reason or another want to use it in say max 4, pretty much all you have to change is to parse the xml file manually.


#7

I am using max 9 and it has no .net suport, if I got it right some kind of service pac is necesary. OK max 4 is really old and not the version I am really care about, but let say max 6 is the start version my scripts should suport.
Like already mentioned in my previous post, I never said that Klunk’s solution is bad. With my very limited max knowledge I am not really understanding what that code does and defently unable to modify it the way, it does what I need.
Also I am using no fonts for the text.
Hope was able to explain myself, as my english is awfull.