Stalker

Draw rectangle and mesh selection

Recommended Posts

Hi!

 

I'm trying to create unit (mesh) selection as it is in various strategy games(AoE, CnC (attached image) ...).

After some exploring, I've came to the following, but there are a couple of steps I don't know how to do:

 

1. Hook to events (OK - trivial)

2. Draw lines using BABYLON.Mesh.CreateLines - at what position? Set LineMesh parent to camera? How to translate mouse position (scene.pointerX and Y) to world coordinates? Just draw rectangle and and set transformation matrix to identity (4x4)? Canvas 2d and 3d context can't be used together.

3. Once that is known, visible meshes are selected with octree (frustum plane is created and used for selection via octree.select method). But frustum plane can be very small and very close, or very big and far away (but this it's probably solved at step 2)

 

I haven't really got to point 3. (got stuck at 2) so it's only my speculation.

I'm open to suggestions if there are any better options :).

 

Explanation for attached image for non strategy game players:

Green rectangle was drawn simply clicking in one corner and dragging. It's always orthogonal to camera view and at the same position (also based on camera) even if camera is moved or rotated. Its rendered above everything and every unit inside is selected.

 

Thanks!

post-17157-0-57686900-1447352414.jpg

Share this post


Link to post
Share on other sites

Hi Stalker!

 

Step 1 : Good!

 

Step 2 :

Maybe you can "cheat', and draw a simple html div with borders, following the mouse coordinates ?

 

Step 3 : 

(from http://answers.unity3d.com/questions/33901/view.html)

Instead of trying to use a world-space selection box like taht, see if the objects' coordinates when converted to screen space are within the screen-space rectangle instead.

 

Let us know if it's ok! And don't hesisate to share that with the community :D

Share this post


Link to post
Share on other sites

Hi Jaskar!

 

2.  I came to a similar idea and after some exploring found an example from user gwenael. He has two canvases one over another, bottom one for 3D graphics and top one for 2D, where he draws rectangles and lines. It's a cool idea, just haven't found any information yet about performance penalty, if it's negligible or not.

 

3. Also came across my mind, but goal is to have somewhere around 1k - 5k objects and would like to have quick access. Number isn't that big at all, but JavaScript isn't exactly known for speed. A hybrid way of using Octree would probably be using getVisibleObjects (or something similar I found somewhere in the docs) and filtering afterwards.

 

I'll proceed in this direction and let you know with what I came up in the next few days (and I'm also open for countless of other suggestions, bad and good ones).

 

Thank you very much for your help :)!

Share this post


Link to post
Share on other sites

At the end I did it as I wrote in my last post.

  • 2D Canvas overlaying 3D canvas where rectangle is drawn. The performance (frame time, render time, and potential FPS weren't affected), but the frame rate was slightly decreased (55-60fps) when rectangle was visible.
  • All corners are then translated to 3D world coordinates (scene.pick method) (done when mouse button is released)
  • All visible meshes (scene.getActiveMeshes) are stored into an array and filtered by:
    • Name (to avoid skybox and ground)
    • Checked if mesh is inside an rectangle using this.

 

I'm sure the entire process can be speed up (a lot), but it will do for now.

Share this post


Link to post
Share on other sites

At the end I did it as I wrote in my last post.

  • 2D Canvas overlaying 3D canvas where rectangle is drawn. The performance (frame time, render time, and potential FPS weren't affected), but the frame rate was slightly decreased (55-60fps) when rectangle was visible.
  • All corners are then translated to 3D world coordinates (scene.pick method) (done when mouse button is released)
  • All visible meshes (scene.getActiveMeshes) are stored into an array and filtered by:
    • Name (to avoid skybox and ground)
    • Checked if mesh is inside an rectangle using this.

 

I'm sure the entire process can be speed up (a lot), but it will do for now.

Thanks for sharing this, I will need some solution for my RTS game, although I'm not to keen on the complexity of needing another canvas to accomplish this.

 

Are there any other options?

Share this post


Link to post
Share on other sites

Hi!

There always are other options :), just that I haven't found any nice. One is definitely ordinary div element with absolute position and set width, height, left and right css properties (which is pretty straight forward).

 

I was also thinking of doing everything inside BJS, but by my rough assessment it was way to complicated. Drawing a plane, parenting it to camera (depends on what kind of selection do you need), creating a render group so it's render above everything, and (probably) translating mouse coordinates to world and doing some sort of transformations so it's orthogonal to camera, keeping word coordinates after transformations... There might be a better way to do this, but didn't find it at the time. (All this is for a specific selection described at my first post).

 

I went with another canvas because I will use it for additional 2D drawing, but it is possible that I will rewrite that part using an ordinary HTML elements.

 

P.S.: I like what you've done so far, can't wait to see more :)

Share this post


Link to post
Share on other sites

Hi!

There always are other options :), just that I haven't found any nice. One is definitely ordinary div element with absolute position and set width, height, left and right css properties (which is pretty straight forward).

 

I was also thinking of doing everything inside BJS, but by my rough assessment it was way to complicated. Drawing a plane, parenting it to camera (depends on what kind of selection do you need), creating a render group so it's render above everything, and (probably) translating mouse coordinates to world and doing some sort of transformations so it's orthogonal to camera, keeping word coordinates after transformations... There might be a better way to do this, but didn't find it at the time. (All this is for a specific selection described at my first post).

 

I went with another canvas because I will use it for additional 2D drawing, but it is possible that I will rewrite that part using an ordinary HTML elements.

 

P.S.: I like what you've done so far, can't wait to see more :)

 

Thanks for the Kudos, its bringing fun to the community that is one of the motivations for making such a game)

 

I like the idea of using a div to draw the selector, I will investigate it. 

Share this post


Link to post
Share on other sites

At the end I did it as I wrote in my last post.

  • 2D Canvas overlaying 3D canvas where rectangle is drawn. The performance (frame time, render time, and potential FPS weren't affected), but the frame rate was slightly decreased (55-60fps) when rectangle was visible.
  • All corners are then translated to 3D world coordinates (scene.pick method) (done when mouse button is released)
  • All visible meshes (scene.getActiveMeshes) are stored into an array and filtered by:
    • Name (to avoid skybox and ground)
    • Checked if mesh is inside an rectangle using this.

 

I'm sure the entire process can be speed up (a lot), but it will do for now.

I decided to go for this method as I will also use canvas for drawing stuff like hit points from units taking damage and perhaps health indicators.

Share this post


Link to post
Share on other sites

Hi all,

for my strategy game I create a selection square that work very well, here the algorithm :

1) on mouse down

      a ) place a DIV on the mouse start (SourisPoint1)
      b ) save babylon PICK point under the mouse (CanvasPoint1)
2) on mouse move

      a ) draw selection DIV with current mouse position
3) on mouse up
      a ) save babylon PICK point where is mouse (CanvasPoint2)
      b ) save mouse position (SourisPoint2)
      c) proceed selection

Here algorithme for selection :
1) create 2 more PICK point to make a 3D square on BABYLON Canvas for that :

      a ) calculate the distance between SourisPoint1.x and SourisPoint2.x
      b ) calculate the distance between SourisPoint1.y and SourisPoint2.y
      c ) calculate the x and y for SourisPoint3 and SourisPoint4 (ex. SourisPoint1.x + distanceX, SourisPoint1.y + distanceY) WARNING is depending of direction of selection square was made
      d ) with SourisPoint3 and SourisPoint4, create 2 PICK point in babylon canvas
2) calculate all lenghts of the square in the canvas (pythagore)

3) calculate the surface of the 2 triangles making that square (Surface1 and Surface2)

4) calculate the total surface of the 3D square (addition of the 2 triangles surfaces) (SurfaceSquare)

5) for each unit

      a ) calculate the surface of each triangle between position of unit and each points of the 3D square (Surface1, Surface2, Surface3, Surface4)

      b ) calculate total surface of all that triangles (SurfaceUnit)
6) If SurfaceUnit == SurfaceSquare, so unit is in the selection square.

In attachment a picture representing all etapes

Hope it will help.


    
     
 

post-18113-0-25905500-1451573398_thumb.j

Share this post


Link to post
Share on other sites

Yes I could paste here the code, but please be carefull this will be not workable because I use MY class names, and MY naming rules

In your HTML

<style>    div.rectangleSelection    {        position            :absolute;        display                :none;                width                :0px;        height                :0px;        top                    :0px;        left                :0px;                background-color    :rgba(0,50,70,0.3);        border                :1px solid white;        box-shadow            :0 0 5px #FFF;                z-index                :3;    }</style><div id="rectangleSelection" class="rectangleSelection"></div><canvas id="nkoCanvas"></canvas>

In your event listener WARNING I USE MY "controleurInput" class here !!!

// On mouse move// Saving mouse coordinate// If an action was registred in "controleurInput" do itwindow.addEventListener("mousemove", function(e){	controleurInput.sourisX 		= e.pageX;	controleurInput.sourisY 		= e.pageY;		if (controleurInput.action)		controleurInput.doAction();});// On mouse down, if it's left button, put action "SelectionStart" in "ControleurInput"// and Do itwindow.addEventListener("mousedown", function(e){		switch (e.button)	{		case 0:			controleurInput.action = "selectionStart";			controleurInput.doAction();	}});// On mouse up, if "controleurInput" is doing action of "selectionEnCours"// It mean we must stop selection, so we put action of "SelectionFin" in "controleurInput"window.addEventListener("mouseup", function(e){	if (controleurInput.action == "selectionEnCours")	{		controleurInput.action = "selectionFin";		controleurInput.doAction();	}});

In your "controleurInput" class, we update position and we do action

controleurInput.prototype.doAction = function(){	switch (this.action)	{		case 'selectionStart':			this.actualisePosition();			controleurObjet.doAction('selectionStart');			this.action = "selectionEnCours";			break;		case 'selectionEnCours':			controleurObjet.doAction('selectionEnCours');			break;		case 'selectionFin':			this.actualisePosition();			controleurObjet.doAction('selectionFin');			this.action = "";			break;	}}// We save the current canvas positioncontroleurInput.prototype.actualisePosition = function(){	var pickResult = scene.pick(scene.pointerX, scene.pointerY,function(mesh)	{		return mesh.name == "floor";	});	if (pickResult.hit)	{		// Nouvelles positions		this.posX 				= pickResult.pickedPoint.x;		this.posZ 				= pickResult.pickedPoint.z;		}}

Here in the "controleurObjet" class that control all object in the game.

controleurObjet.prototype.doAction = function(action){	switch (action)	{		case 'selectionStart':			this.rectangleSelection['x1'] 	= controleurInput.posX;			this.rectangleSelection['z1'] 	= controleurInput.posZ;						this.rectangleSelection['sx1']	= controleurInput.sourisX;			this.rectangleSelection['sy1']	= controleurInput.sourisY;						(function($)			{				$('#rectangleSelection').css({					'top'		:controleurInput.sourisY-5,					'left'		:controleurInput.sourisX-5				});															$('#rectangleSelection').fadeIn(200);			})(jQuery);			break;		case 'selectionEnCours':			this.selectionEnCours();			break;		case 'selectionFin':			this.rectangleSelection['x2']	= controleurInput.posX;			this.rectangleSelection['z2'] 	= controleurInput.posZ;						this.rectangleSelection['sx2']	= controleurInput.sourisX;			this.rectangleSelection['sy2']	= controleurInput.sourisY;			(function($)			{				$('#rectangleSelection').fadeOut(200);				$('#rectangleSelection').css({					'top'		:0,					'left'		:0,					'width'		:0,					'height'	:0				});			})(jQuery);			this.selection();			break;	}}// This function only place selection square and resize it when mouse movingcontroleurObjet.prototype.selectionEnCours = function(){			var widthRectangle	= Math.abs(this.rectangleSelection['sx1'] - controleurInput.sourisX); 	var heightRectangle  	= Math.abs(this.rectangleSelection['sy1'] - controleurInput.sourisY);	(function($)	{			$('#rectangleSelection').css({				'width'		: widthRectangle-5,				'height'	: heightRectangle-5		});	})(jQuery);		// On recale la div suivant si on séléctionne depuis la gauche vers la droite, ou de bas en haut	// Depending that if you select from left, right, up or down	if ((controleurInput.sourisX <= this.rectangleSelection['sx1']) && (controleurInput.sourisY >= this.rectangleSelection['sy1']))	{		(function($)		{				$('#rectangleSelection').css({				'width'		: widthRectangle-5,				'height'	: heightRectangle-5,				'left'		: controleurInput.sourisX+5			});		})(jQuery);	} 	else if ((controleurInput.sourisY <= this.rectangleSelection['sy1']) && (controleurInput.sourisX >= this.rectangleSelection['sx1']))	{		(function($)		{			$('#rectangleSelection').css({				'width'		: widthRectangle-5,				'height'	: heightRectangle-5,				'top'		: controleurInput.sourisY+5			});		})(jQuery);	} 	else if ((controleurInput.sourisY < this.rectangleSelection['sy1']) && (controleurInput.sourisX < this.rectangleSelection['sx1']))	{		(function($)		{			$('#rectangleSelection').css({				'width'		: widthRectangle-5,				'height'	: heightRectangle-5,				'left'		: controleurInput.sourisX+5,				'top'		: controleurInput.sourisY+5			});		})(jQuery);	}}// Here the real algorithme of selectioncontroleurObjet.prototype.selection = function(){	// ************************************************** Rectangle vue page	var sx1 		= this.rectangleSelection['sx1'];	var sx2 		= this.rectangleSelection['sx2'];	var sy1 		= this.rectangleSelection['sy1'];	var sy2 		= this.rectangleSelection['sy2'];	var dx 			= sx2-sx1;	var dy			= sy2-sy1;	var __SELECTION_ADOUCIE__ 	= 3;			// Distance entre X1souris et X2Souris, hypothénus	// Distance between X1Souris and X2Souris	var d 			= Math.sqrt( (dy*dy)+(dx*dx) );	// Les deux autres cotés du rectangle	// The 2 overs points of the square inside the canvas	var pickResult3 = scene.pick(sx1+dx, sy1);	var pickResult4 = scene.pick(sx2-dx, sy2);		// ************************************************** Rectangle vue scene 	// Coordonnées point	// All point coordinate (inside the canvas)	var x1 = Math.round(this.rectangleSelection['x1']*1000)/1000;	var z1 = Math.round(this.rectangleSelection['z1']*1000)/1000;		var x2 = Math.round(this.rectangleSelection['x2']*1000)/1000;	var z2 = Math.round(this.rectangleSelection['z2']*1000)/1000;		var x3 = Math.round(pickResult3.pickedPoint.x*1000)/1000;	var z3 = Math.round(pickResult3.pickedPoint.z*1000)/1000;		var x4 = Math.round(pickResult4.pickedPoint.x*1000)/1000;	var z4 = Math.round(pickResult4.pickedPoint.z*1000)/1000;		// Calcul des longueurs des 4 cotés du rectangle du canvas	// Square lenght inside canvas	var a = Math.sqrt( ((x3 - x2)*(x3 - x2))+((z3 - z2)*(z3 - z2)) );	var b = Math.sqrt( ((x3 - x1)*(x3 - x1))+((z3 - z1)*(z3 - z1)) );	var i = Math.sqrt( ((x2 - x4)*(x2 - x4))+((z2 - z4)*(z2 - z4)) );	var j = Math.sqrt( ((x4 - x1)*(x4 - x1))+((z4 - z1)*(z4 - z1)) );	// Diagonale	var h = Math.sqrt( ((x2 - x1)*(x2 - x1))+((z2 - z1)*(z2 - z1)) );		// Calcul des perimetres	// perimeter of the 2 triangles	var per1 = (a + b + h)/2;	var per2 = (i + j + h)/2;	// Surface	var Ss = Math.round((Math.sqrt(per1*(per1-a)*(per1-*(per1-h))+Math.sqrt(per2*(per2-i)*(per2-j)*(per2-h)))*100)/100;	var thisObjet = this;	(function($)	{		// Détermine si l'unité est dans le rectangle		// If unit is inside the square WARNING I use a SELECTION_MAX to avoid too much selection		$.each(thisObjet.tabUnitesEquipe,function(id,selectedUnit)		{			if ((thisObjet.tabSelection.length < thisObjet.__SELECTION_MAX__)&&(selectedUnit.mesh.isVisible))			{					// Coordonnée unité											var xu = selectedUnit.mesh.position.x;				var zu = selectedUnit.mesh.position.z;				// Calcul longueur				// Lenght between unit and all square's points				var m = Math.sqrt( ((xu - x1)*(xu - x1))+((zu - z1)*(zu - z1)) );				var n = Math.sqrt( ((xu - x3)*(xu - x3))+((zu - z3)*(zu - z3)) );				var o = Math.sqrt( ((xu - x2)*(xu - x2))+((zu - z2)*(zu - z2)) );				var p = Math.sqrt( ((xu - x4)*(xu - x4))+((zu - z4)*(zu - z4)) );				// Calcul des périmètres				// Perimeter				var per3 = (m + p + j)/2;				var per4 = (m + n + b)/2;				var per5 = (n + o + a)/2;				var per6 = (o + p + i)/2;				// Calcul des surfaces				var St1 = Math.sqrt(per3*(per3-m)*(per3-p)*(per3-j));				var St2 = Math.sqrt(per4*(per4-m)*(per4-n)*(per4-);				var St3 = Math.sqrt(per5*(per5-n)*(per5-o)*(per5-a));				var St4 = Math.sqrt(per6*(per6-o)*(per6-p)*(per6-i));								var St = Math.round((St1 + St2 + St3 + St4)*100)/100;								if (St == Ss)				{					thisObjet.tabSelection.push(selectedUnit.selection());				}			}		});	})(jQuery);}

Hope it could help :)

Share this post


Link to post
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...

  • Recently Browsing   0 members

    No registered users viewing this page.