RSM Viewer

by Vincent Thibault, posted May 27, 2013 in Blog (No Comments)

RSM (Resource Model) is a Ragnarok Online file to store 3D objects. It’s really difficult to render because of the lack of source and informations about its structure.

I build some months ago a new RSM viewer using roBrowser’s library, this version display all files stored in my remote client.
Because of some optimization in roBrowser, the animation are not working (faster to render static mesh than dinamic ones), you can see the result behind.

Demo

(take some times to load)

loading…



Source code is part of roBrowser.



Javascript TGA Loader

by Vincent Thibault, posted May 27, 2013 in Blog (6 Comments)

Truevision TGA file is an image file often used in video games.

For roBrowser’s project, I had to work with this kind of files. I firstly wrote a PHP converter (tga to png) to do this process on the fly from my web host, but since my project designs changed a little and need to work with local files too, I had to implement the converter on the client-side.

Lot of videos games are using the TGA file to store textures, since browsers try to bring games to the web I think it’s a good idea to share this code.

Demo


If you don’t see the image, you should try updating your browser (it required at least HTML5Canvas and Uint8Array support).

How to use ?

It’s really easy to use:

var tga = new TGA();
tga.open( "test.tga", function(data){
   document.body.appendChild(
      tga.getCanvas()
   );
});

It’s possible to parse content without having to download it too:

// Uint8Array tga_data; // content of tga file
var tga = new TGA();
tga.load(tga_data);
//tga.getCanvas();
//tga.getDataURL('image/png');
//tga.getImageData();

It should work with all versions (8, 16, 24, 32 bits), grey or rgba, RLE or not, palette or true color.

Source Code

Github – TGA Loader

Have fun~



JSuper Mario 1K – My JS1K Spring Entry

by Vincent Thibault, posted Apr 10, 2013 in Blog (No Comments)

jsuper-mario

Two weeks ago ended the latest JS1K contest (submit an awesome demos in just 1K of javascript), I created for the occasion a Super Mario running in an infinite world (If you want to take a look at the Source code).

I had the idea of doing a Mario by visiting @jseidelin’s site, you can actually see in the background the sprites I used for my entry (thanks to him to allow me to use them).

Storing sprite

Before starting I knew that just storing the sprite in the script will exceed the 1K limit. I decided to store the pixel color using 0 and 1 and let JSCrush do its job, since there will have a lot of repeated sequence, the compression will be far better.

I came finaly with this:

for(k=a.createImageData(91,16);5824>i;i+=4)
    k.data[i+3]=1<<"0008888880000000088888800000088888800000008888880000000888888000008000000008800888888888880008888888888000088888888880088888888880008888888888000888888888000088888888808888888888888800888008000000008880080000008880080000000888008000000088800800888008888888880888888888888880808000800000008080008000008080008000000808000800000080800080088800888888888088888000008888080880008000000808800080000808800080000080880008000008088000800080088888888808888008880088808800008888800088000088888088000088888008800008888800880000888888008888888888888800888008880000000000000000000000000000000000000000000000000000000000000088000888888888008880088800888008888888000000088888880000088888880000888888888000088888888888000088888888808888888800088808888888888800008888880000088888888800088888888880008888888888808008888888880888888800888888888888888888000888888000008888808800000088808888800888888888880800888888888088888880088888008808808800008888888888000888888888800008888888808808888808808880888888888088888888888888800088888800000888888888800088800088880008888888888888888888888888000888888808888888800888880088888888000888888888800000880088880008888888888888888888888888800800888808888888880088888008888088880088000888800000008888888000888880088888888888888800000088800080888888888888888808888000888800000088888000000888888880088800000000000888880000000008888888088888888888888888888800088888000000000000000088888000008888000000000000000000000008888888808880000000000000"[i>>2]; // can be replace by i/4
(m=c.cloneNode()).getContext("2d").putImageData(k,0,0);

Which generate this:
mario-sprite

Map rendering

The map rendering wasn’t complexe at all, so I will not explain it here, just check the source and you will understand.
The tricky part was to change the sprite base on the Y position : if ( y < 4 ) rendering ground else rendering [?] cube.

Optimizing Space

Seems I know I will use JSCrush because just the sprite exceed the 1K, I did a lot of optimizations to gain bytes.
The idea was simple, try to repeat the same code multiple times to help the packer to pack bytes.

  • 150-16*j-0“, the -0 is not needed but added it to the code allow me to gain one byte (reason : there were multiple occurrences of “150-16*j-” in the code) (same as 150-16*j-16, it was at start 150-17*j).
  • (s=13,160)” before it was 150. I modify this part because there were a lot of occurences of 13,16 in my script.
  • j&&(i-5+x/13)% Another de-optimized part, the j was inversed in the check ecause used each time, it was better to not cache this operations.
  • Also, I don’t know if I’m the only one to think about it but JSCrush use variables, why not use the same variables to avoid recreating some others and gaining bytes ? So I used Y as a tick variable and $ as a look at variable. Which saved me 4 bytes.

Result ?

jscrush


Best compression Ever ?

Source code



JS1K first attempt !

by Vincent Thibault, posted Feb 20, 2012 in Blog (No Comments)

javascript contest love 2012

Yesterday I learned the start of the 4th edition of js1k with the love theme.
The goal is simple: build an amazing demo with only 1k of javascript (1024 bytes).

For this king of contest you have to compress/obfuscate your code a maximum to not exceed the size limit. So you can divide your work in two parts:

  • Build your awesome demo.
  • Compress it with some weird stuffs.

I submit a little demo, a beating heart with animating sex particles, you can see it here.

 

 

First step : build an amazing demo !

Building a demo it’s not really difficult, you just have to make it awesome.
Don’t forget that you are in a 1K contest so you must know that you are limited in space.
Here my source script:

(function JS1K_love(ctx,canvas,body){
 
 
	var // Global variables and settings
		width  = canvas.width  = 600,
		height = canvas.height = 450,
		list   = ["♂","♀","#6371b7","#db7189"],
		particles = [],
		love_str,
		love_tick=-1,
		tick   = (new Date).getTime(),
		random = Math.random,
		floor  = Math.floor,
		round  = Math.round
	;
 
 
	// Canvas in the center, define the background color.
	body.style.background = "#724567";
	body.style.textAlign  = "center";
 
 
	// Function to draw a heart shape.
	function draw_heart() {
		ctx.lineWidth = 30;
		ctx.beginPath();
		ctx.moveTo( width/2, height/3);
		ctx.bezierCurveTo( width/10,       0,         0, height*0.6, width/2, height*0.9);
		ctx.bezierCurveTo( width, height*0.6, width*0.9,          0, width/2, height/3 );
		ctx.closePath();
		ctx.stroke();
	}
 
 
	// Return particles from canvas pixels.
	function get_particles_from_heart_pixels() {
		var pixels = ctx.getImageData( 0, 0, width, height).data;
		var p = [];
		// Put a particle with a chance of 1.5% where there is the heart shape.
		for ( var i=0; i<pixels.length; i+=4 )
			if ( pixels[i+3] && random()<.015 )
				p.push({ sex:round(random()), x:i/4%width, y:i/4/width }); // randomly select the particle sex.
		return p;
	}
 
 
	// Generate the animation
	function process() {
 
		// Generate settings
		var
			time     =  (new Date).getTime() - tick,
			radius   =  Math.cos(time*3E-4),
			gradient =  ctx.createRadialGradient( width/2, height/2, 0, width/2, height/2, width/2 ),
			i,
			particle,
			angle,
			_x,
			_y
		;
 
		// Generate the dradiant effect (move with time)
		gradient.addColorStop( 0, "#c68fb8" );
		gradient.addColorStop( 0.6-Math.sin(time*3E-4)/10, "#724567" );
 
		// Blur effect, just erase with 0.3 opacity
		ctx.globalAlpha = 0.3;
		ctx.fillStyle   = gradient;
		ctx.fillRect( 0, 0, width, height );
 
		// Draw particles
		for ( i in particles ) {
 
			particle = particles[i];
			angle     = Math.PI * 5 * Math.sin(time*3E-4) + i * Math.PI/30;
			_x        = Math.sin(angle);
			_y        = Math.cos(angle);
 
			// Change position
			particle.x += radius * _x;
			particle.y += radius * _y;
 
			// Change size and opacity and generate particle
			ctx.font  = floor( _x*5 + 17) + "px Arial";
			ctx.globalAlpha = 1.5 - ( _x + 1.5 ) / 2;
			ctx.strokeStyle = list[particle.sex + 2];
			ctx.strokeText( list[particle.sex], particle.x, particle.y );
		}
 
		// Restore opacity for the LOVE, and find size.
		ctx.globalAlpha = 1;
		ctx.font = "bold " + floor( _x * 2 + 55 ) + "px Arial";
		ctx.fillStyle   = "rgba(255,255,255,0.1)";
		ctx.strokeStyle = "rgba(255,255,255,0.2)";
		ctx.textAlign   = "center";
 
		// Change sex in LOVE, every 4secs.
		if ( floor(time/4000) !== love_tick ) {
			love_tick = floor(time/4000);
			love_str  = list[ round(random()) ] + " LOVE " + list[ round(random()) ];
		}
 
		// Draw Love text.
		ctx.fillText( love_str, width/2, height/2 + 30 );
		ctx.strokeText( love_str, width/2, height/2 + 30);
	}
 
 
	// start the contest
	(function init() {
		draw_heart(); // Draw the heart
		particles = get_particles_from_heart_pixels(); // Generate particles from heart pixels
		ctx.clearRect( 0, 0, width, height ); // clear the canvas
		ctx.lineWidth = 2; // restore the line width
		setInterval( process, 30 ); // animation.
	})()
 
})(a,c,b);

It’s a clean script of 3323 bytes. A little too much for the contest…
I don’t really need to explain the script, all things are already explains in comments and it’s not difficult to understand how it works.
So now we can start to reduce the size…

 

 

Second step : Optimize the size

This part is a pain to ass; you have to drastically reduce your script length by using some sort of weird things :

  • Remove function if you can
  • Remove “var”, use only global variable
  • Change variables name, use for example “w”, instead of “width”
  • Optimize numbers : use “.9″ instead of “0.9″n, use 5E3 instead of 5000
  • Use with() to access object members
  • Rewrite your code differently…

Doing this by hand is better than using a javascript packer in a lot of case.

So here my script after this changes:

w=c.width=600,
h=c.height=450,
k=["♂","♀","#6371b7","#db7189"],
p=[],
t=0,
l=m=1,
n=(new Date).getTime(),
r=Math.random,
f=Math.floor,
o=Math.round;
 
with(a)
	lineWidth=30,
	beginPath(),
	moveTo(w/2,h/3),
	bezierCurveTo(w/10,0,0,h*.6,w/2,h*.9),
	bezierCurveTo(w,h*.6,w*.9,0,w/2,h/3),
	closePath(),
	stroke(),
	d=getImageData(0,0,w,h).data,
	clearRect(0,0,w,h),
	lineWidth=2;
 
for(i=0;i<w*h*4;i+=4)
	if(d[i+3]&&r()<.015)
		p.push({t:o(r()),x:i/4%w,y:i/4/w});
 
setInterval(
	'with(a){\
		t+=Math.max((new Date).getTime()-n,30);\
		n=(new Date).getTime();\
		z=Math.cos(t*3E-4),\
		globalAlpha=.3;\
		g=createRadialGradient(w/2,h/2,0,w/2,h/2,w/2);\
		g.addColorStop(0,"#c68fb8");\
		g.addColorStop(.6-Math.sin(t*3E-4)/10,b.style.background="#724567");\
		fillStyle=g;fillRect(0,0,w,h);\
		\
		for(i in p)\
			q=p[i],\
			g=Math.PI*5*Math.sin(t*3E-4)+i*Math.PI/30,\
			u=Math.sin(g),\
			v=Math.cos(g),\
			q.x+=z*u,\
			q.y+=z*v,\
			font=f(u*5+17)+"px Arial",\
			globalAlpha=1.5-(u+1.5)/2,\
			strokeStyle=k[q.t+2],\
			strokeText(k[q.t],q.x,q.y);\
		\
		font="bold "+f(u*2+55)+"px Arial",\
		globalAlpha=1,\
		fillStyle="rgba(255,255,255,.1)";\
		strokeStyle="rgba(255,255,255,.2)",\
		b.style.textAlign=textAlign="center";\
		if((s=f(t/5E3))!==l)\
			l=s,m=k[o(r())]+" LOVE "+k[o(r())];\
		fillText(m,w/2,h/2+30);\
		strokeText(m,w/2,h/2+30)\
	}',1)

1354 bytes, a lot better ! But need to be 1024 max !
Now we can remove spaces and indent to gain some bytes.

w=c.width=600,h=c.height=450,k=["♂","♀","#6371b7","#db7189"],p=[],t=0,l=m=1,n=(new Date).getTime(),r=Math.random,f=Math.floor,o=Math.round;with(a)lineWidth=30,beginPath(),moveTo(w/2,h/3),bezierCurveTo(w/10,0,0,h*.6,w/2,h*.9),bezierCurveTo(w,h*.6,w*.9,0,w/2,h/3),closePath(),stroke(),d=getImageData(0,0,w,h).data,clearRect(0,0,w,h),lineWidth=2;for(i=0;i<w*h*4;i+=4)if(d[i+3]&&r()<.015)p.push({t:o(r()),x:i/4%w,y:i/4/w});setInterval('with(a){t+=Math.max((new Date).getTime()-n,30);n=(new Date).getTime();z=Math.cos(t*3E-4),globalAlpha=.3;g=createRadialGradient(w/2,h/2,0,w/2,h/2,w/2);g.addColorStop(0,"#c68fb8");g.addColorStop(.6-Math.sin(t*3E-4)/10,b.style.background="#724567");fillStyle=g;fillRect(0,0,w,h);for(i in p)q=p[i],g=Math.PI*5*Math.sin(t*3E-4)+i*Math.PI/30,u=Math.sin(g),v=Math.cos(g),q.x+=z*u,q.y+=z*v,font=f(u*5+17)+"px Arial",globalAlpha=1.5-(u+1.5)/2,strokeStyle=k[q.t+2],strokeText(k[q.t],q.x,q.y),font="bold "+f(u*2+55)+"px Arial",globalAlpha=1,fillStyle="rgba(255,255,255,.1)";strokeStyle="rgba(255,255,255,.2)",b.style.textAlign=textAlign="center";if((s=f(t/5E3))!==l)l=s,m=k[o(r())]+" LOVE "+k[o(r())];fillText(m,w/2,h/2+30);strokeText(m,w/2,h/2+30)}',1)

Result: 1174 bytes. It’s not enough !
We are not forced to think about a packing method, a compressor.

 

 

Step 3 : Pack it

You can see in this code that a lot of same words are repeating: globalAlpha, stroke, fill, text, Math… We can think about keep one reference for each words and use a specify byte to replace them.

So I wrote a little compression tool that search for this words and replace them with one byte. Here the result:

for(l in $='w=c.wੂ60ྺh=c.height=45ྺk=["♂ಚ♀ಚ#6371b7ಚ#db7189"ෆp=[ෆt=ྺx=m=1,n=â,r=દrandom,f=દfloor,y=દ৞;׶Ξ3ྺbegส֒moॺࡎ3Ȏ/1ྺྺྺܢశh*.9Ȏ,ܢ*.9,ྺࡎ3བclose֒ߪ(བd=getImageD໲aЂ.d໲a,clearɲ,Ξ2ھ=0;i<w*h*4;i+=4)iൢd[i+3]&&r()<.015)p.push({t:ކ,x:i/4%w,y:i/4/w});setInterval(\'׶{t+=દmax(â-n,30);n=â;z=દcosԮ˖.3;g=cre໲eRadialGradient(ࡎ2,ྺࡎ2,w/2ƪྺ"#c68fb8"ƪ.6-દsสԮ/1ྺѦbackg৞="#724567");୮ٚg;୮ɲھ ส p)q=p[iෆg=દPI*5*દsสԮ+i*દPI/3ྺu=દsส(gབ_=દcos(gབຎx+=z*u,ຎy+=z*_,खൢu*5+17.5-(u+1.5)శߪٚk[ຎt+2ෆߪࢲk[ຎtෆຎx,ຎyབख"bold "+ൢu*2+55,୮~1)";ߪ~2)",Ѧ̺̺"center";iൢ(s೾t/5E3))!==x)x=s,m=k[ކ]+" LOVE "+k[ކ];୮ņ;ߪņ}\',1)',_=')+"px Arial"˖1|ٚ"rgba(ӊଊ.|(new D໲e).getTime()|ࢲm,ࡎ2+30)|);g.addColorStop(|བbezierCurॺw|RectЂ|,globalAlpha=|textAlign=|lสeWੂ|(ྺྺw,h)|b.s௒.|ଊଊ|(t*3E-4)|P໲h(བ|with(a)|S௒=|;for(i|h*.6,w|y(r())|stroke|wశh/|Text(|font=|veTo(|round|idth=|M໲h.|255,|fill|tyle|/2,|","|=ൢ|f(|],|in|q.|at|),|0,'.split("|"))$=$.split(String.fromCharCode(l+26)).join(_[l]);eval($)

925 bytes ! Here we go !

So now… Maybe we can add a little header, just for fun; we have 99 free bytes.

(function JS1K(l,o,v,e){ <code> })(2,0,1,2)

And finally, the result:

(function JS1K(l,o,v,e){for(l in $='w=c.wੂ60ྺh=c.height=45ྺk=["♂ಚ♀ಚ#6371b7ಚ#db7189"ෆp=[ෆt=ྺx=m=1,n=â,r=દrandom,f=દfloor,y=દ৞;׶Ξ3ྺbegส֒moॺࡎ3Ȏ/1ྺྺྺܢశh*.9Ȏ,ܢ*.9,ྺࡎ3བclose֒ߪ(བd=getImageD໲aЂ.d໲a,clearɲ,Ξ2ھ=0;i<w*h*4;i+=4)iൢd[i+3]&&r()<.015)p.push({t:ކ,x:i/4%w,y:i/4/w});setInterval(\'׶{t+=દmax(â-n,30);n=â;z=દcosԮ˖.3;g=cre໲eRadialGradient(ࡎ2,ྺࡎ2,w/2ƪྺ"#c68fb8"ƪ.6-દsสԮ/1ྺѦbackg৞="#724567");୮ٚg;୮ɲھ ส p)q=p[iෆg=દPI*5*દsสԮ+i*દPI/3ྺu=દsส(gབ_=દcos(gབຎx+=z*u,ຎy+=z*_,खൢu*5+17.5-(u+1.5)శߪٚk[ຎt+2ෆߪࢲk[ຎtෆຎx,ຎyབख"bold "+ൢu*2+55,୮~1)";ߪ~2)",Ѧ̺̺"center";iൢ(s೾t/5E3))!==x)x=s,m=k[ކ]+" LOVE "+k[ކ];୮ņ;ߪņ}\',1)',_=')+"px Arial"˖1|ٚ"rgba(ӊଊ.|(new D໲e).getTime()|ࢲm,ࡎ2+30)|);g.addColorStop(|བbezierCurॺw|RectЂ|,globalAlpha=|textAlign=|lสeWੂ|(ྺྺw,h)|b.s௒.|ଊଊ|(t*3E-4)|P໲h(བ|with(a)|S௒=|;for(i|h*.6,w|y(r())|stroke|wశh/|Text(|font=|veTo(|round|idth=|M໲h.|255,|fill|tyle|/2,|","|=ൢ|f(|],|in|q.|at|),|0,'.split("|"))$=$.split(String.fromCharCode(l+26)).join(_[l]);eval($)})(2,0,1,2)

960 bytes.
 

 

Step 4 : Submit !

Now submit your script at js1k.com and share your knowledge !