Jump to content

Terrain painting


Dad72
 Share

Recommended Posts

Hello,

 

Do you think that would be appropriate to paint decals roads on terrain?
I think decal because it seems the easiest to achieve, but I told myself that this is not the best ways to do this (can be).
I use the basic WorldMonger (demo available at BabylonJS) that automatically painted textures, but I would like an option to make manual adjustments, but I do not really know how.
 
Thank you in advance for your advice, support ...
Link to comment
Share on other sites

Decals is not. Using a canvas is much better!

 

By using dynamic texture!

 

If you use a chunk system such as below, (I'll explain more in a PM) then each chunk will contain this function. The _x and _y is calculated  as such:

 

var _x = pickInfo.pickedPoint.x* (this.chunk.texSize.width / this.chunk.parent.chunkSize);
var _y = pickInfo.pickedPoint.z* (this.chunk.texSize.height / this.chunk.parent.chunkSize);
 
Here's an example of chunks:
Basically many different meshes, each with seperate textures and vertices.
this.paint = function(_x,_y) {		this.chunk.parent.unsavedChanges.texture = true;		var size = this.options.brushSize;		var _width = Game.data.brushes[this.options.selectedBrush].img.width;		var _height = Game.data.brushes[this.options.selectedBrush].img.height;		this.chunk.textureContext.save();		//Mask		this.chunk.maskcontext.clearRect(0,0,this.chunk.texSize.width,this.chunk.texSize.height);		this.chunk.maskcontext.save();		var wx = _x - (this.chunk.x*this.chunk.texSize.width);		var wy = _y - (this.chunk.y*this.chunk.texSize.height);		wy = this.chunk.texSize.height - wy;		this.chunk.maskcontext.drawImage(Game.data.masks[this.options.selectedMask].img,0,0,Game.data.masks[this.options.selectedMask].img.width,Game.data.masks[this.options.selectedMask].img.height,wx-(size),wy-(size),size*2,size*2);		this.chunk.maskcontext.globalCompositeOperation = 'source-in';		//Paint Mode		this.chunk.brushcontext.clearRect(0,0,this.chunk.texSize.width,this.chunk.texSize.height);		this.chunk.brushcontext.save();		if(this.options.paintType == "texture") {			if (this.options.paintMode == "Tiled") {				var scale = 1;				var _x = (Math.ceil(_x/_width) -1)*_width				var _y = (Math.ceil(_y/_height) -1)*_height				//After...				_x = _x - (this.chunk.x*this.chunk.texSize.width);				_y = _y - (this.chunk.y*this.chunk.texSize.height);				_y = this.chunk.texSize.height - Game.data.brushes[this.options.selectedBrush].img.height - _y;				//Compute the width and height of the looper				var mx = Math.ceil(size/_width) + 1;				var my = Math.ceil(size/_height) + 1;				//Back				for(lx=0;lx<mx;lx++) {					for(ly=0;ly<my;ly++) {						this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x-(lx*_width),_y-(ly*_height),_width,_height);						this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x+(lx*_width),_y-(ly*_height),_width,_height);						this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x-(lx*_width),_y+(ly*_height),_width,_height);						this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x+(lx*_width),_y+(ly*_height),_width,_height);					}				}			}		} else if (this.options.paintType == "color") {			this.chunk.brushcontext.fillStyle= this.options.brushColor;			this.chunk.brushcontext.fillRect(0,0,this.chunk.texSize.width,this.chunk.texSize.height);		}		//Now transfer to mask the image		this.chunk.maskcontext.drawImage(this.chunk.brushcanvas,0,0);		this.chunk.maskcontext.restore();		this.chunk.textureContext.drawImage(this.chunk.maskcanvas,0,0);		this.chunk.textureContext.restore();		this.chunk.texture.update();	}

Basically each chunk has two contexts, and a dynamic texture.

One context contains the mask - a transparent image with a black shape. This shape is what shape the brush is:

http://i.imgur.com/FCZo2Gi.png

 

The other context contains the actual image you painting. So the editor gets where you clicked, realigns the area so it fits relative to the chunk, and draws squares of texture.

My version is a bit complex, in that if the texture is super small and smaller than the mask, it repeats so it fills it.

 

Then, on the mask context, it enables globalCompositeOperation = 'source-in'. This makes so whatever is painted next, will only be visible where there is black.

Then the texture context is painted on the mask context, and then the texture is saved to the dynamic texture!

 

(And if the mode is painting color, it just draws a colored box quickly instead of textures)

 

Pretty complex, sadly. :S

Link to comment
Share on other sites

I feel that having made WorldMonger use as a base for my terrain editor was a very bad idea in terms paint textures on the ground.

Or I have not understood how to paint a texture on it existing. I do not understand as drawImage and textureDynamic and masks. I am total newbie on that. any help would be appreciated. 

Link to comment
Share on other sites

EDIT: THIS TUTORIAL DOES NOT WORK DUE TO CROSS ORIGIN STUFF. IF SOMEONE COULD ADD THIS IMAGE TO THE PLAYGROUND:

http://steinhauer.x10.mx/links/mask_1.png

I COULD GET IT WORKING 

THANKS

 

Sadly I do not have time to actually make a demo... but if you want I can explain how canvas works...

 

So, first off: dynamic texture is a canvas. You can draw "stuff" onto a canvas. Here are some examples:

http://www.w3schools.com/html/html5_canvas.asp

 

Ok, so first you create a dynamic texture, like so:

var texture = new BABYLON.DynamicTexture("dynamic texture", 500, scene, true);var textureContext = texture.getContext();

Sweet! So now you can draw stuff! Here's a simple example of that:

http://www.babylonjs-playground.com/#1OQFOC

 

Ok, so now we need to detect clicks, and draw a square there.

 

After this, we need to change things up. To do this, we need to create 2 new canvases - brushCanvas and maskCanvas.

 

brushCanvas holds whatever new changes we're adding - a square of color, some texture, whatever. This needs to be done in case we have several steps, like sevreal images.

maskCanvas simply at first holds a black circle - and then we paste everything on the brushCanvas on top - whatever is black will show brushCanvas. So in other words, we'll end up with a circle

of texture/color! Then, finally, we copy and paste whatever's on the mask onto the actual dynamic texture.

 

Example of mask: (Doesn't work)

http://www.babylonjs-playground.com/#1OQFOC#4

Link to comment
Share on other sites

Josh, what kind of performance do you get with your chunk system? Any sign of upper limits on how many dynamic textures can be handled? How would you handle a wide open terrain stretching off into the horizon?

 

Dad72 - I've used worldmonger as the basis for some of my work, given that it uses 4/5 textures and applys them based on GPU to height, if you were applying josh's method in a basic way (to each of the ground textures for each chunk) you would need:

 

4 (base textures) x 2 (fill & mask for each) x Y chunks = Lots of dynamic textures

 

Although I think that is not a clever way to do it. A much better might be to keep worldmonger as it is, but adopt Josh's code to add a chunk system ontop (so you have 4 tiled textures for worldmonger, and then lots of chunk textures not tiled). It would only need a small re-write of the worldmonger ground shader in order to draw the chunk textures on top of the existing worldmonger logic where alpha is visible. It would be interesting to know how many chunks can be handled in this way

Link to comment
Share on other sites

Thank you very much for your explanations Josh I think that will help me a lot. I think I begin to understand. Thank you Temechon for the fix, it helps me to have a concrete overview.

If I understand correctly, you need a dynamic texture and a canvas / layer of texture?

 

Me, after I prefer to work on a single tile/terrain and in the future when the LOD system in the terrain would be done, I would use it to make large terrain. Meanwhile I use octree allows cutting the ground also.

 

I agreement Xeonzinc , it would be good to change this WorldMonger demo.

 

Thank you again Josh for the explanations. 

Link to comment
Share on other sites

@Xeonzinc:

 

Well, for my editor I have not tested having a million chunks. The reason why chunks are useful is because they can be hidden, (less rendering) or even deleted if their far away.

The editor uses dynamic texture, which may be somewhat worse than in-game - within the game the chunks just have the texture image itself as a diffuse.

If you added a feature where far away chunks downloaded lower resolutions, that would even be better!

 

@dad72:

1) There are three components - dynamic texture, and it's canvas. Then a textureCanvas, (Think of it as brushCanvas) and then maskCanvas.

Yes, to allow transfering. So if you wanted to do several steps, (Such as a repeating texture) it won't work to paste each step onto the mask - the mask only works with a single image being pasted.

Basically textureCanvas (Or brushCanvas) holds the current image. MaskCanvas holds the mask, and the dynamic texture holds the already painted stuff from before.

 

2) I use php: Here's how I send it, not including directory creation/cleaning and POST variables

function saveImg($img,$finalDir,$title) {		$img = str_replace('data:image/png;base64,', '', $img);		$img = str_replace(' ', '+', $img);		$data = base64_decode($img);		$file = $finalDir.$title.'.png';		$success = file_put_contents($file, $data);		echo "Success: ".$success;	}

Basically you send the image data, and it saves it to a certain directory.

 

3) Not sure what you mean... I noticed in the newest demo there is no transparency when the mask is partly transparent, which is strange. Do you mean this?

 

I'll try to add the mask transparency and make it so you can drag the mouse around in the next few days. :)

Link to comment
Share on other sites

Thank you Josh for your help.
 
for part 3. I mean, do textures blend. for example, a grass texture with an opacity above a ground texture. exemple:
 
post-5292-0-95807700-1429658886.png
 

I'll try to add the mask transparency and make it so you can drag the mouse around in the next few days.


Yes, it would help me.

 

I have 1 questions.

How I should do for the texture repeats this several times because this pixelated? (vScale and uScale not work)

 

Thank you again for help Josh

Link to comment
Share on other sites

So first you need to add scaling. Here's the function to scale without blurring:

var resizeImage = function( img, scale ) {    // Takes an image and a scaling factor and returns the scaled image    // The original image is drawn into an offscreen canvas of the same size    // and copied, pixel by pixel into another offscreen canvas with the    // new size.    var widthScaled = img.width * scale;    var heightScaled = img.height * scale;    var orig = document.createElement('canvas');    orig.width = img.width;    orig.height = img.height;    var origCtx = orig.getContext('2d');    origCtx.drawImage(img, 0, 0);    var origPixels = origCtx.getImageData(0, 0, img.width, img.height);    var scaled = document.createElement('canvas');    scaled.width = widthScaled;    scaled.height = heightScaled;    var scaledCtx = scaled.getContext('2d');    var scaledPixels = scaledCtx.getImageData( 0, 0, widthScaled, heightScaled );    for( var y = 0; y < heightScaled; y++ ) {        for( var x = 0; x < widthScaled; x++ ) {            var index = (Math.floor(y / scale) * img.width + Math.floor(x / scale)) * 4;            var indexScaled = (y * widthScaled + x) * 4;            scaledPixels.data[ indexScaled ] = origPixels.data[ index ];            scaledPixels.data[ indexScaled+1 ] = origPixels.data[ index+1 ];            scaledPixels.data[ indexScaled+2 ] = origPixels.data[ index+2 ];            scaledPixels.data[ indexScaled+3 ] = origPixels.data[ index+3 ];        }    }    scaledCtx.putImageData( scaledPixels, 0, 0 );	var exported = new Image();	exported.src = scaled.toDataURL("image/png");	return exported;} 

To repeat texture, you basically compute the scaling of brush and the size of the texture. Here's my code for my editor:

//Compute the width and height of the looper	var mx = Math.ceil(size/_width) + 1;	var my = Math.ceil(size/_height) + 1;	for(lx=0;lx<mx;lx++) {		for(ly=0;ly<my;ly++) {		this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x-(lx*_width),_y-(ly*_height),_width,_height);		this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x+(lx*_width),_y-(ly*_height),_width,_height);		this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x-(lx*_width),_y+(ly*_height),_width,_height);		this.chunk.brushcontext.drawImage(Game.data.brushes[this.options.selectedBrush].img,0,0,_width,_height,_x+(lx*_width),_y+(ly*_height),_width,_height);		}	}

(Note: The variables aren't the same, but I don't have time to implement it into the demo for now. Maybe when I find time?)

 

And yes, adding opacity to the mask would work perfectly - draw, then switch textures. This would give the effect you wish.

Link to comment
Share on other sites

I improve your function saveImag() Josh to compress the final image. an image of 1024 * 1024 is reduced to 2.0 MB => 1.60 MB.

I also add permissions with chmod. locally it is not necessary, but once online is necessary.

<?phpheader('Content-Type: text/html; charset=UTF-8');header('Content-Type: image/png');function saveImg($img, $finalDir, $nameImage) {	$img = str_replace('data:image/png;base64,', '', $img);	$img = str_replace(' ', '+', $img);	$data = base64_decode($img);	$file = $finalDir.$nameImage.'.png';	chmod($file, 0777);	$success = file_put_contents($file, $data);		$im = imagecreatefrompng($file);	imagepng($im, $file, 9);	chmod($file, 0644);	}saveImg($_POST['image'], "./path/images/", $_POST['terrainName']);?>
Link to comment
Share on other sites

Keep this post going!  This is one of the best topics I've seen in a while, and am having a blast just trying to keep up with everything that's being covered. I was sitting down today to begin working on a multi-user creative drawing application (just for fun,) and this discussion has caused me to change my approach - and will already save me loads of time.  :)

 

You guys are brilliant!

Link to comment
Share on other sites

How does compression work? Does it make it look fuzzier or make it so the resolution less? It sounds awesome!

 

All the resize image function does is make it so the image isn't fuzzy when scaled. This is useful for retro, if you don't want this feature just ignore it. Even so probably a better idea to just not add that until later. :)

 

Also, yeah when I uploaded the image I think it got bigger. And thanks for the brushes, their amazing!!!!!

 

I'll get to working on the demo for a bit :)

Link to comment
Share on other sites

Looks like the mask link is broken, and I can't seem to get it working on my dropbox. Dad72 wanna try it on yours? 

 

EDIT: Got it working!

 

Here's my newest version. All I added was so you can drag.

 

http://www.babylonjs-playground.com/#1OQFOC#13

 

I'm not sure what's up with the opacity not working! It should...

 

 

Also, we need to make it so the image doesn't overlap and stuff. If you want to see an example, hop over to this link:

 

http://steinhauer.x10.mx/projects/editor/0.55/editor/

 

Instructions: Under the "project" panel, press "load project". Then select "terrainExample", and load. Now go to the "Map" on the topbar, and click "select map".

Create a new map.

 

Now you can go to "Terrain", then "New terrain". Create terrain. Now you can sculpt and paint! If you select your mask/texture, notice how it paints in a seemless texture. we need this!

 

Note: Right click to move camera, left click to interact, middle click to select, and spacebar to move camera to selection.

 

BTW this version seems to be very very buggy, so be warned! Stuff has been breaking a lot today!

Link to comment
Share on other sites

I have reproduced the problem I have at home (pixelation/zoom/big of the image)
 
 
The problem I think is that I work directly on large ground. You, you work on several small ground, which causes this problem at my huge textures.
 
How to solve this?
 
I like the results on your demo of your editor, this is what I seek, also with the opacity, but it is not working on your editor yet.
 
Thanks

 

Link to comment
Share on other sites

Ahhh I see! What we need to implement is scaling! I haven't even added this feature to mine, LOL! Well, it's time, I guess xD For my editor I will be not working on the terrain editing, because my current project does not use terrain at all. :) 

 

First step is implementing repeating and seamless texture.

I changed the texture to something tiny, where we can see the image repeating.

 

Here's what I have so far. Now the texture "fits" correctly, but... everything is so wrong.

http://www.babylonjs-playground.com/#1OQFOC#15

 

I'm having lots of trouble getting my old code merged with this new stuff.... idk why, exactly. :/

Link to comment
Share on other sites

I only see a white image on your example. but I put the grass texture, it always zoom in on the image, it would have to face the opposite. the image must have a resolution more higher.

I have confessed really struggling with full functionality to draw on a canvas.

Link to comment
Share on other sites

I do not see black line around.

 

http://www.babylonjs-playground.com/#1OQFOC#17

 

try this. you can still see the image zooms. we close track. it should be the reverse.
Have you been able to continue this demo. for example I brushScale that you add is not used.
 
Again thank you Josh. I have over this feature with the opacity of the texture by layer to finish to complete the V2.3 of my editor and start creating a game demo to finalize the editor and the site
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...