I'm using raycasting to handle the collisions between mouse position and grid cells. However, it's a little bit slow and inaccurate at times, so I would like to know:
- Can I somehow offload the raycasting / collision handling to a webworker?
- Sometimes the raycaster doesn't return anything even though the mouse pointer is right over a cell. Is my mouse position caculated correctly? (see below example)
- Am I handling the collisions correctly in my example below? Is there any room to improve the speed?
This is the main function that handles the grid cell collisions:
( It only checks cells that are within the AOS (area of sight) circle )
function checkCollisions(){
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( n_grid.cells_in_AOS );
if ( intersects.length > 0 ) {
if ( intersected != intersects[ 0 ].object ) {
if ( intersected ) {
if( intersects[ 0 ].object.attr.id != prev_cell_id ){ //// make sure it's not the same cell ///
intersected.mouseOut();
prev_cell_id = intersects[ 0 ].object.attr.id;
}
}
intersected = intersects[ 0 ].object;
intersects[ 0 ].object.mouseOver();;
}
}
}
var renderer, camera, scene, controls, n_grid;
var intersects = null;
var intersected = null;
var raycaster = new THREE.Raycaster();
var prev_cell_id = -1;
var mouse = new THREE.Vector2();
///////// [ Square ] ////////////
var Square = function(options){
if(typeof options != "undefined"){
this.attr = {
coords: options.coords || [],
size : options.size,
margin: options.margin || 0,
pos: options.b_pos,
opacity: options.opacity || 1,
id: typeof(options.coords) == "undefined" ? -1 : options.coords[0]+""+options.coords[1],
};
var cell_size = (this.attr.size + ( this.attr.margin * 2 ));
this.colors = {
selected : 0x40ff00,
hover: 0x00ff00,
path: 0x3399ff,
ship: {
selected: 0xff8000,
hover: 0xff5c33
},
base : parseInt(options.color)
};
this.states = {
selected : false,
hovered: false,
isPath: false,
isPlayer: false
};
var rectGeom = this.generate_lines();
var material = new THREE.LineBasicMaterial({ side: THREE.DoubleSide, color: options.color , transparent: true, opacity: this.attr.opacity }); ///// color not being changed!!!???/ ///
THREE.Line.call( this, rectGeom, material );
this.scale.set( 1, 1, 1 );
if( this.attr.coords.length == 0 ){
this.position.set( this.attr.pos.x , this.attr.pos.y, this.attr.pos.z );
}else{
this.position.set( this.attr.pos.x + (this.attr.coords[0] * cell_size) + this.attr.margin + (cell_size/2), this.attr.pos.y, this.attr.pos.z + ( this.attr.coords[1] * cell_size) + this.attr.margin + (cell_size/2) );
}
this.rotation.set( ( Math.PI / 2 ) , 0 , 0 );
}else{
console.error("[Square] no parameters defined. ]");
}
}
Square.prototype = Object.create( THREE.Line.prototype );
Square.prototype.constructor = Square;
///// State handlers ////
Square.prototype.toggleState = function(state){
this.states[ state ] = !this.states[ state ];
};
Square.prototype.checkState = function( state ){
return this.states[ state ];
};
/////// Mouse events ////
Square.prototype.mouseOver = function(){
if( ! this.checkState( "hovered" ) ){
if( ! this.checkState( "selected" ) ){
this.toggleState( "hovered" );
this.change_color( this.colors.hover, 0.8 );
}
}
};
Square.prototype.mouseOut = function(){
if( this.checkState( "hovered" ) ){
if( ! this.checkState( "selected" ) ){
this.change_color( this.colors.base );
this.toggleState( "hovered" );
}
}
};
Square.prototype.select = function(){
if( ! this.checkState( "selected" ) ){
this.toggleState( "selected" );
this.change_color( this.colors.selected , 1 );
}
};
Square.prototype.unselect = function(){
if( this.checkState( "selected" ) ){
this.toggleState( "selected" );
this.change_color( this.colors.base );
}
};
/////// Mouse events ////
Square.prototype.change_color = function(color, opacity){
this.material.color.setHex( color );
this.material.opacity = opacity || this.attr.opacity;
};
Square.prototype.generate_lines = function(){
var rectShape = new THREE.Shape( );
var half_size = (this.attr.size/2);
rectShape.autoClose = true;
rectShape.moveTo( -half_size,-half_size );
rectShape.lineTo( half_size,-half_size );
rectShape.lineTo( half_size, half_size );
rectShape.lineTo( -half_size, half_size );
return rectShape.createPointsGeometry();
};
///////// [ Square ] ////////////
/////// [ Circle ] ////////////////
var Circle = function(options){
if(typeof options != "undefined"){
//console.log("[Circle] Initializing");
this.attr = {
segments: options.segments,
radius : options.radius,
color: options.color,
pos: options.pos
}
var segments = this.generate_segments();
var material = new THREE.LineBasicMaterial({ color: options.color , transparent: true, opacity: options.opacity});
THREE.Line.call( this , segments , material);
this.scale.set( 1, 1, 1 );
this.position.set( options.pos.x, options.pos.y, options.pos.z ); //// x,y,z ////
this.rotation.set( ( Math.PI / 2 ) , 0 , 0 );
}else{
console.error("[Circle] no parameters defined. ]");
}
};
Circle.prototype = Object.create( THREE.Line.prototype );
Circle.prototype.constructor = Circle;
Circle.prototype.generate_segments = function(){
var geometry = new THREE.Geometry();
for(var s= 0; s <= this.attr.segments; s++) {
var theta = (s / this.attr.segments) * Math.PI * 2;
geometry.vertices.push( new THREE.Vector3( Math.cos( theta ) * this.attr.radius, Math.sin( theta ) * this.attr.radius, 0 ) );
}
return geometry;
};
/////// [ Circle ] ////////////////
///////////// [ Grid ] ////////////////
var Grid = function( options ){
if(typeof options !== "undefined"){
//console.log("[Grid] Initializing");
this.cell_settings = options.cell;
this.grid_size = options.size;
THREE.Object3D.call( this );
this.pos = {
x: options.pos.x - this.cell_settings.margin,
y: options.pos.y,
z: options.pos.z - this.cell_settings.margin
};
var sq_margin = (this.cell_settings.margin * 2);
this.sq_size = this.cell_settings.size + sq_margin;
this.size = {
w: this.grid_size[0] * this.sq_size,
h: this.grid_size[1] * this.sq_size
};
//console.log( this.grid_size )
this.cells = new THREE.Object3D();
this.cells_in_AOS = []; //// cells in Area of Sight ////
this.galaxyCircle = {
pos: {
x: this.pos.x + (this.size.w / 2),
y: this.pos.y,
z: this.pos.z + (this.size.w / 2),
},
r: (this.size.w / 2),
};
this.AOS_circle = { //// Area of Sight ////
pos: {
x: this.galaxyCircle.pos.x,
y: this.galaxyCircle.pos.y,
z: this.galaxyCircle.pos.z
},
r: options.AOS.r, //// radius ////
color: 0x33cccc
};
this.generate();
}else{
console.error("[Grid] no parameters defined. ]");
}
};
Grid.prototype = Object.create( THREE.Object3D.prototype );
Grid.prototype.constructor = Grid;
Grid.prototype.generate = function(){
//////////////// Galaxy center point //////
this.add_point( this.AOS_circle.pos, 0x033d6ff );
////// AOS circle border ////
this.add_circle_border( this.AOS_circle );
////// fill the grid with square cells ////////
for ( var row = 0; row < this.grid_size[0]; row++){
for ( var col = 0; col < this.grid_size[1]; col++){
this.add_cell2grid( row, col );
}
}
this.position.set( this.pos.x , this.pos.y, this.pos.z );
///// center the grid /////
this.cells.position.set( this.pos.x - ((this.grid_size[0] * this.sq_size) / 2), this.pos.y, this.pos.z - ((this.grid_size[1]* this.sq_size) / 2) );
this.add( this.cells );
//// map borders /////
this.add_circle_border({
r: (this.size.w / 2),
color: 0x00ff00
});
this.add_square_border( this.size.w , 0xffff00);
this.add_point();
//// map borders /////
};
Grid.prototype.add_point = function( pos, color ){
var geometry =new THREE.BoxGeometry( 0.5, 5.5, 0.5 )
if( typeof pos === "object" ){
geometry = new THREE.BoxGeometry( 0.5, 10.5, 0.5 );
}
var material = new THREE.MeshBasicMaterial( {color: color || 0xcc33ff} );
var cube = new THREE.Mesh( geometry, material );
if( typeof pos !== "object" ){
cube.position.set( 0,0,0 );
}else{
cube.position.set( pos.x, pos.y, pos.z );
}
this.add(cube)
};
Grid.prototype.add_cell2grid = function(row, col){
var cell = new Square({
coords: [row, col],
size: this.cell_settings.size,
margin: this.cell_settings.margin,
opacity: this.cell_settings.opacity,
color: 0xFFFFFF,
b_pos: {
x: this.pos.x ,
y: this.pos.y,
z: this.pos.z
} //// base position ///
});
if( this.isColidingWith( this.galaxyCircle, cell ) ){
if( ! this.is_in_AOS( cell ) ){
cell.visible = false;
}else{
this.cells_in_AOS.push(cell );
}
if( this instanceof THREE.Object3D ){
this.cells.add(cell);
}else{
console.error("[Grid] is not THREE.Object3D type. ");
}
}
};
Grid.prototype.is_in_AOS = function( cell ){ //// Circle colision ////
return this.isColidingWith( this.AOS_circle, cell )
};
Grid.prototype.isColidingWith = function( cA, cell ){ //// Circle colision ////
var d_x = cA.pos.x - cell.position.x;
var d_y = cA.pos.z - cell.position.z;
var dist = Math.sqrt( d_x * d_x + d_y * d_y );
if( dist < ( cA.r - (( cell.attr.size / 2) + (cell.attr.margin * 2)) ) ){
return true;
}
return false;
};
Grid.prototype.getCellAt = function( p ){
this.wColiisions.postMessage( [ p.x , p.y ] );
};
Grid.prototype.add_circle_border = function( options ){
var circle = new Circle({
segments: 50,
radius: options.r,
color: options.color,
opacity: 0.5,
pos: {
x: this.pos.x,
y: 0.5,
z: this.pos.z
}
});
this.add( circle );
};
Grid.prototype.add_square_border = function( square_size, color ){
var square = new Square({
size: square_size,
color: color,
b_pos: { //// base position ///
x: this.pos.x,
y: this.pos.y,
z: this.pos.z
}
});
this.add(square);
};
///////////// [ Grid ] ////////////////
////// Initializers ////////////////////////
function initEvents(){
//console.log("- Events");
//renderer.domElement.addEventListener( 'mousedown', onMouseDown, false );
renderer.domElement.addEventListener( 'mousemove', onMouseMove, false );
}
function initRenderer(){
//console.log("- Renderer");
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize( window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.setClearColor(0x264d73, 1);
}
function initScene(){
//console.log("- Scene")
scene = new THREE.Scene();
}
function initCamera(){
//console.log("- Camera");
camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 1, 10000);
camera.position.set(0, 40, 40);
camera.lookAt(scene.position);
scene.add(camera);
//controls = new THREE.OrbitControls( camera , renderer.domElement );
}
function initLights(){
//console.log("- Lights");
var aLight = new THREE.AmbientLight(0xD0D0D0, 0.5);
scene.add(aLight);
}
function initGrid(){
n_grid = new Grid({
size: [20, 20],
pos: { x: 0, y: 0, z: 0 },
cell: {
size: 7,
margin: 0.1,
opacity: 0.4
},
AOS: {
r: 30
}
});
scene.add( n_grid );
}
////// Initializers ////////////////////////
///// Mouse events ////////
function onMouseMove(e){
e.preventDefault();
mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
mouse.y = -( e.clientY / window.innerHeight ) * 2 + 1;
}
function checkCollisions(){
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( n_grid.cells_in_AOS );
if ( intersects.length > 0 ) {
if ( intersected != intersects[ 0 ].object ) {
if ( intersected ) {
if( intersects[ 0 ].object.attr.id != prev_cell_id ){ //// make sure it's not the same cell ///
intersected.mouseOut();
prev_cell_id = intersects[ 0 ].object.attr.id;
}
}
intersected = intersects[ 0 ].object;
intersects[ 0 ].object.mouseOver();;
}
}
}
function debounce( func, wait, immediate ) {
var timeout;
return function(){
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
}
///// Mouse events ////////
///// Main /////////
function main(){
//console.log(" Initializing: ");
initRenderer(window.innerWidth, window.innerHeight );
initScene();
initCamera(window.innerWidth, window.innerHeight );
initLights();
initGrid();
initEvents();
animate();
}
function animate(){
window.requestAnimationFrame( animate );
checkCollisions();
render_all();
}
function render_all(){
//controls.update();
renderer.render(scene, camera);
}
main();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r77/three.min.js"></script>
<header>
<style>
body canvas{
width: 100%,
height: 100%;
margin:0;
padding:0;
}
</style>
</header>
<body>
</body>
Aucun commentaire:
Enregistrer un commentaire