var Doors = function(game, maze, options) { var self=this; this.door_separators = {}; this.outside_separators = []; var depth; var depth2; var available_nodes = []; // Cells, // by array this.cells = []; // Cells, by line/row this.generated_doors = {}; this.closed_doors = {}; this.data_doors = {}; this.doors_keys = {}; this.doors_paths = {}; this.doors_paths_refs = {}; this.build = function() { var self=this; depth = (Math.sqrt(3)/2) * game.opt.door_size*1.0; depth2 = (Math.sqrt(3)/2) * game.opt.door_size * Math.sqrt(3)/2 *1.35; this.container = new THREE.Object3D(); this.floor_geom_refs = {}; this.floor_geom = new THREE.Geometry(); this.walls_geom = new THREE.Geometry(); this.num_items_line = Math.floor(Math.sqrt(options.maze_num)); var total=0; self.max_row = 0; self.max_line = 0; // Loop to create maze // Loop lines for(var row=0; (row1) { floor_material = new THREE.MeshPhongMaterial(); } var floor = new THREE.Mesh( this.floor_geom, floor_material); this.container.add(floor); this.walls = new THREE.Mesh( this.walls_geom, new THREE.MultiMaterial(game.assets.wall_mat)); this.walls.name='walls'; this.container.add(this.walls); // Correct position of the maze depending on the start position and direction var move_x = this.start_x*depth2*2; var move_z = this.start_z*depth2*2; this.container.position.x = options.x - move_x; this.container.position.y = 0; this.container.position.z = options.z - move_z; game.scene.add( this.container ); }; function assignUVs(geometry) { geometry.faceUvs = [[]]; geometry.faceVertexUvs = [[]]; for (var f=0;f0) { var doors_to_open = Math.ceil(Math.random()*3); for(var i=0; i1) { var ref = this.cells[initial_x*this.num_items_line + initial_z]; var dest = this.cells[dest_x*this.num_items_line + dest_z]; draw_line({ opacity:1, color: Math.random()*0xff0000, force_y: 1, origin: ref.position, destination: dest.position, container: this.container }); } } } // Do a maze with the not connected dots too else { for(var x=0; x<=this.max_row; x++) { for(var z=0; z<=this.max_line; z++) { // Need to link and unused cell with a used cell if(this.generated_doors[x][z] && !this.generated_doors[x][z].used) { next_doors_full = this.near_doors(x, z); next_doors_used=[]; next_doors_full.forEach(function(door) { if(self.generated_doors[door[0]][door[1]].used) { next_doors_used.push(door); } }); if(next_doors_used.length>0) { var connected_door = next_doors_used[Math.floor(Math.random()* next_doors_used.length)]; var cellid = connected_door[0] * this.num_items_line + connected_door[1]; parent_path = this.doors_paths_refs[initial_cellid]; cellid = x*this.num_items_line + z; parent_path[cellid] = {}; this.doors_paths_refs[cellid] = parent_path[cellid]; this.generated_doors[connected_door[0]][connected_door[1]].used=1; this.maze_doors_queue.push(connected_door); } } } } } this.maze_doors_next(); }; // Close some doors, add ennemys, etc this.game_doors = function(currentid, subnodes) { var doors = Object.keys(subnodes); if(!doors || !doors.length) { return; } if(currentid) { available_nodes.push(currentid); } var has_closed_door =Math.random()*4>3; if(available_nodes.length>1 && has_closed_door) { var close_door = doors[Math.floor(Math.random()*doors.length)]; var cell_door = this.cells[close_door]; var close_i = cell_door.opened_doors[Math.floor(Math.random()*cell_door.opened_doors.length)]; if(cell_door.opened_doors.length>0 && cell_door.closed_doors.indexOf(close_i)===-1) { var interruptor_idx = Math.floor(Math.random()*available_nodes.length); var cell_interruptor = this.cells[available_nodes[interruptor_idx]]; cell_door.closed_doors.push(close_i); console.log('closing door ', close_door, close_i, ' interruptor is ', available_nodes[interruptor_idx]); // Needs to add corresponding closed door from the other side var opposite_pos = this.get_coord_next_door(cell_door.params.x, cell_door.params.z, close_i); var cellid = this.getCellId(opposite_pos[0], opposite_pos[1]); var opposite_cell = this.cells[cellid]; // If it is the end cell, there is no opposite cell if(opposite_cell) { opposite_cell.closed_doors.push(opposite_pos[2]); } var pos_interruptor = this.get_cell_pos(available_nodes[interruptor_idx]); var key = game.add_key({ mazeid: maze.id, cellid: this.getCellId(cell_interruptor.params.x, cell_interruptor.params.z), type: 'key', callback: this.openDoor.bind(this, cell_door, close_i), x: pos_interruptor.x, y: 0, z: pos_interruptor.z, }); cell_interruptor.keys.push(key.container_mesh); available_nodes = available_nodes.splice(interruptor_idx-1, 1); } } else if(currentid && doors.length>1) { var has_ennemy = true; if(has_ennemy) { var end_door = doors[Math.floor(Math.random()*doors.length)]; var end_cell = this.cells[end_door]; var near_doors = this.near_doors(end_cell.params.x, end_cell.params.z,true); if(near_doors.length>0) { var near_door= near_doors[Math.floor(Math.random()*near_doors.length)]; var near_door_id = this.getCellId(near_door[0], near_door[1]); var start = this.get_cell_pos(end_door); var end = this.get_cell_pos(near_door_id); var patrol_positions = [start, end]; maze.add_ennemy( { x: start.x, z: start.z, patrol_positions: patrol_positions }); } else { console.log('ERROR'); } } } for(var i=0; inext_cellid) { var t = next_cellid; next_cellid=first_cellid; first_cellid=t; } return 'closed_from_'+first_cellid+'_to_'+next_cellid; }; this.get_opposide_door = function(i) { switch(i) { case 0: return 3; case 1: return 4; case 2: return 5; case 3: return 0; case 4: return 1; case 5: return 2; } }; this.get_coord_next_door = function(initial_x, initial_z, i) { var pair = !!(initial_x%2===0); switch(i) { //bottom case 0: return([ initial_x, initial_z+1, this.get_opposide_door(i)]); break; // right bottom case 1: return([ initial_x+1, pair ? initial_z : initial_z+1, this.get_opposide_door(i)]); break; // right top case 2: return([ initial_x+1, pair ? initial_z-1 : initial_z, this.get_opposide_door(i)]); break; // top case 3: return([ initial_x, initial_z-1, this.get_opposide_door(i)]); break; // left top case 4: return([ initial_x-1, pair ? initial_z-1 : initial_z, this.get_opposide_door(i) ]); break; // left bottom case 5: return([ initial_x-1, pair ? initial_z : initial_z+1, this.get_opposide_door(i)]); break; } }; this.near_doors = function(initial_x, initial_z, only_connected) { var self=this; var next_doors = []; var check = []; var door = this.generated_doors[initial_x][initial_z]; var add_check = only_connected ? [ door.opened_doors.indexOf(0)!==-1 ? 1 : 0, door.opened_doors.indexOf(1)!==-1 ? 1 : 0, door.opened_doors.indexOf(2)!==-1 ? 1 : 0, door.opened_doors.indexOf(3)!==-1 ? 1 : 0, door.opened_doors.indexOf(4)!==-1 ? 1 : 0, door.opened_doors.indexOf(5)!==-1 ? 1 : 0 ] : [ 1, 1, 1, 1, 1, 1 ]; for(var i=0; i<6;i++) { if(add_check[i]) { check.push(this.get_coord_next_door(initial_x, initial_z, i)); } } check.forEach(function(c) { var x = c[0]; var z = c[1]; var i = c[2]; if(self.generated_doors[x] && self.generated_doors[x][z]) { next_doors.push([x,z,i]); } }); return next_doors; }; /* Memorize the doors created, to avoid creating doble contiguous doors */ this.register_door= function(x, z, i, cell) { if(!this.data_doors[x+'.'+z]) { this.data_doors[x+'.'+z]={}; } this.data_doors[x+'.'+z][i] = 1; if(cell) { if(i<2) { var coords = this.get_coord_next_door(x, z, this.get_opposide_door(i)); this.register_door(coords[0], coords[1], i, false); } } }; this.get_pos = function(params) { var pair = params.x%2 ? depth2 : 0; var x =params.x * depth *2; var z =params.z * depth2 *2 + pair; return { x: x, z: z}; }; this.get_start_pos = function() { var coord = this.get_pos({ x: this.start_x , z: this.start_z }); return { x: coord.x + options.x , z: coord.z + options.z, cellid: 0 }; }; this.get_cell_pos = function(cellid) { var cell = this.cells[cellid]; var coord = this.get_pos({ x: cell.params.x - this.start_x , z: cell.params.z - this.start_z }); return { x: coord.x + options.x , z: coord.z + options.z, cellid: 0 }; }; this.get_end_pos = function() { var coord = this.get_pos({ x: this.end_x - this.start_x , z: this.end_z - this.start_z }); var angle = Math.radians(30); coord.x += Math.cos(angle) * game.opt.door_size; coord.z += Math.sin(angle) * game.opt.door_size; return { x: coord.x + options.x , z: coord.z + options.z, cellid: this.cells.length }; }; this.create_cell = function(params) { var self=this; var cellid = params.real_x=='outside' ? 'outside' : params.real_x*this.num_items_line + params.real_z; this.fulldepth = game.opt.door_size + game.opt.door_size*2; var pair = params.x%2 ? depth2 : 0; var cell = new THREE.Object3D(); cell.id=cellid; cell.name='pos '+params.x+' / '+params.z; cell.separation_lines=[]; cell.keys=[]; cell.opened_doors=[]; cell.closed_doors=[]; var pos = this.get_pos(params); cell.position.x=pos.x; cell.position.y=0; cell.position.z=pos.z; cell.incell=false; cell.params=params; this.cells.push(cell); this.container.add(cell); var key = params.x+'_'+params.z; var start_idx = this.floor_geom.vertices.length; // Create ground of that pivot // V1 = top left, V2 = left, V3 = bottom left, etc var vcenter = new THREE.Vector3(cell.position.x ,1,cell.position.z); this.floor_geom.vertices.push(vcenter); var vcenter_idx = start_idx; start_idx++; if(!this.floor_geom_refs[key] || !this.floor_geom_refs[key].v1 ) { var v1 = new THREE.Vector3(cell.position.x - game.opt.door_size/1.8 , 0 , cell.position.z-depth2*1.0 ); this.floor_geom.vertices.push(v1); v1_idx = start_idx; start_idx++; } else { v1_idx = this.floor_geom_refs[key].v1; } if(!this.floor_geom_refs[key] || !this.floor_geom_refs[key].v2) { var v2 = new THREE.Vector3(cell.position.x - game.opt.door_size*1.2 , 0,cell.position.z); this.floor_geom.vertices.push(v2); v2_idx = start_idx; start_idx++; } else { v2_idx = this.floor_geom_refs[key].v2; } if(!this.floor_geom_refs[key] || !this.floor_geom_refs[key].v3) { var v3 = new THREE.Vector3(cell.position.x - game.opt.door_size/1.8 , 0 , cell.position.z+depth2*1.0 ); this.floor_geom.vertices.push(v3); v3_idx = start_idx; start_idx++; } else { v3_idx = this.floor_geom_refs[key].v3; } if(!this.floor_geom_refs[key] || !this.floor_geom_refs[key].v4) { var v4 = new THREE.Vector3(cell.position.x + game.opt.door_size/1.8 , 0 , cell.position.z+depth2*1.0 ); this.floor_geom.vertices.push(v4); v4_idx = start_idx; start_idx++; } else { v4_idx = this.floor_geom_refs[key].v4; } if(!this.floor_geom_refs[key] || !this.floor_geom_refs[key].v5) { var v5 = new THREE.Vector3(cell.position.x + game.opt.door_size*1.2 , 0,cell.position.z); this.floor_geom.vertices.push(v5); v5_idx = start_idx; start_idx++; } else { v5_idx = this.floor_geom_refs[key].v5; } if(!this.floor_geom_refs[key] || !this.floor_geom_refs[key].v6) { var v6 = new THREE.Vector3(cell.position.x + game.opt.door_size/1.8 , 0 , cell.position.z-depth2*1.0 ); this.floor_geom.vertices.push(v6); v6_idx = start_idx; start_idx++; } else { v6_idx = this.floor_geom_refs[key].v6; } var subkey; // Top / Bottom vertices subkey = (params.x)+'_'+(params.z+1); if(!this.floor_geom_refs[subkey]) { this.floor_geom_refs[subkey] = {}; } this.floor_geom_refs[subkey].v1 = v3_idx; this.floor_geom_refs[subkey].v6 = v4_idx; // Side vertices if(params.x%2==0) { subkey = (params.x+1)+'_'+(params.z); if(!this.floor_geom_refs[subkey]) { this.floor_geom_refs[subkey] = {}; } this.floor_geom_refs[subkey].v1 = v5_idx; this.floor_geom_refs[subkey].v2 = v4_idx; subkey = (params.x+1)+'_'+(params.z-1); if(!this.floor_geom_refs[subkey]) { this.floor_geom_refs[subkey] = {}; } this.floor_geom_refs[subkey].v2 = v6_idx; this.floor_geom_refs[subkey].v3 = v5_idx; } else { subkey = (params.x+1)+'_'+(params.z); if(!this.floor_geom_refs[subkey]) { this.floor_geom_refs[subkey] = {}; } this.floor_geom_refs[subkey].v2 = v6_idx; this.floor_geom_refs[subkey].v3 = v5_idx; subkey = (params.x-1)+'_'+(params.z+1); if(!this.floor_geom_refs[subkey]) { this.floor_geom_refs[subkey] = {}; } this.floor_geom_refs[subkey].v6 = v2_idx; this.floor_geom_refs[subkey].v5 = v3_idx; } var material = new THREE.MeshBasicMaterial( { color: 0x3366bb } ); this.floor_geom.faces.push( new THREE.Face3( vcenter_idx, v1_idx, v2_idx ) ); var faceuv = [ new THREE.Vector2(1,0), new THREE.Vector2(0,1), new THREE.Vector2(1,1), ]; this.floor_geom.faceVertexUvs[0].push(faceuv); this.floor_geom.faces.push( new THREE.Face3( vcenter_idx, v2_idx, v3_idx ) ); var faceuv = [ new THREE.Vector2(1,0), new THREE.Vector2(1,1), new THREE.Vector2(0,0) ]; this.floor_geom.faceVertexUvs[0].push(faceuv); this.floor_geom.faces.push( new THREE.Face3( vcenter_idx, v3_idx, v4_idx ) ); var faceuv = [ new THREE.Vector2(1,0), new THREE.Vector2(1,1), new THREE.Vector2(0,1) ]; this.floor_geom.faceVertexUvs[0].push(faceuv); this.floor_geom.faces.push( new THREE.Face3( vcenter_idx, v4_idx, v5_idx ) ); var faceuv = [ new THREE.Vector2(0,1), new THREE.Vector2(1,0), new THREE.Vector2(1,1), ]; this.floor_geom.faceVertexUvs[0].push(faceuv); this.floor_geom.faces.push( new THREE.Face3( vcenter_idx, v5_idx, v6_idx ) ); var faceuv = [ new THREE.Vector2(0,0), new THREE.Vector2(1,0), new THREE.Vector2(0,1), ]; this.floor_geom.faceVertexUvs[0].push(faceuv); this.floor_geom.faces.push( new THREE.Face3( vcenter_idx, v6_idx, v1_idx) ); var faceuv = [ new THREE.Vector2(1,0), // 3 new THREE.Vector2(0,0), // 1 new THREE.Vector2(0,1), // 2 ]; this.floor_geom.faceVertexUvs[0].push(faceuv); return cell; }; this.get_pivot = function(cell, params, i) { var pivot = new THREE.Object3D(); pivot.name ='p '+params.x+'/'+params.z+'/'+i; pivot.rotation.y= Math.radians(i*60); cell.add(pivot); return pivot; }; this.set_mesh_orientation = function(mesh,i) { var ratio = 0.6; mesh.scale.x=ratio*game.opt.door_size; mesh.scale.y= ratio*game.opt.door_size; mesh.scale.z= ratio*game.opt.door_size; mesh.rotation.y= Math.radians(i*60); switch(i) { case 0: mesh.position.z += game.opt.door_size; break; case 1: mesh.position.z += game.opt.door_size * 0.50; mesh.position.x += depth; break; case 2: mesh.position.z -= game.opt.door_size * 0.50; mesh.position.x += depth; break; case 3: mesh.position.z -= game.opt.door_size; break; case 4: mesh.position.z -= game.opt.door_size * 0.50; mesh.position.x -= depth; break; case 5: mesh.position.z += game.opt.door_size * 0.50; mesh.position.x -= depth; break; } mesh.updateMatrix(); }; this.create_meshes = function(params) { var self=this; this.fulldepth = game.opt.door_size + game.opt.door_size*2; var cell = this.cells[params.num]; for(var i=0; i<6;i++) { if(!this.data_doors[params.x+'.'+params.z] || !this.data_doors[params.x+'.'+params.z][i]) { this.register_door(params.x, params.z, i, cell); var pivot = this.get_pivot(cell, params, i); var materials = []; var mesh=null; if(this.generated_doors[params.real_x][params.real_z].opened_doors.indexOf(i)!==-1) { mesh = new THREE.Mesh( game.assets.door_geo); var closed_door_id = this.get_closed_door_id(params,i); if(this.generated_doors[params.x][params.z].closed_doors.indexOf(i)!==-1 && !this.closed_doors[closed_door_id]) { var close_mesh = new THREE.Mesh( game.assets.wall_geo, new THREE.MultiMaterial(game.assets.wall_mat)); close_mesh.name=closed_door_id; this.closed_doors[closed_door_id] = close_mesh; console.log('adding close mesh', closed_door_id); this.container.add(close_mesh); close_mesh.position.x = cell.position.x; close_mesh.position.y = cell.position.y; close_mesh.position.z = cell.position.z; this.set_mesh_orientation(close_mesh, i); } } else { mesh = new THREE.Mesh( game.assets.wall_geo); } if(mesh) { mesh.position.x = cell.position.x; mesh.position.y = cell.position.y; mesh.position.z = cell.position.z; this.set_mesh_orientation(mesh, i); this.walls_geom.merge(mesh.geometry, mesh.matrix); } } var areas=[]; // Create collision items to detect enter in the cell if(this.generated_doors[params.real_x][params.real_z].opened_doors.indexOf(i)!==-1) { var door = this.generated_doors[params.real_x][params.real_z]; var outside_door = ( params.real_x == this.start_x && params.real_z == this.start_z && i == this.start_i) || ( params.real_x == this.end_x && params.real_z == this.end_z && i == this.end_i); this.create_separation_line(cell,params, i, false, outside_door); } } return cell; }; this.create_separation_line= function(cell,params, i, extra_door, outside_door) { var material = new THREE.MeshBasicMaterial( { color: 0x666699, transparent:true, opacity:0 } ); if(game.opt.debug_level>1) { material = new THREE.MeshBasicMaterial( { color: 0x33bb99 + 0x0000ff } ); } var extra = extra_door ? game.opt.door_size*0.4 : 0; var separator = outside_door ? 1 : 0.85; var pivot = this.get_pivot(cell, params, i); draw_line({ opacity:game.opt.debug_level>1 ? 1 : 0, container: pivot, color: Math.random()*0x777777, origin: { x: depth*( !extra_door ? .65 : .7), y: 1, z: game.opt.door_size*separator +extra }, destination: { x: -depth*(!extra_door ? .65 : .7), y: 1, z: game.opt.door_size*separator +extra } }); var cellid = params.real_x=='outside' ? 'outside' : params.real_x*this.num_items_line + params.real_z; line.callback_data = { mazeid: maze.id, cellid:cellid, id:'cell_'+params.real_x+'_'+params.real_z, type:'change_cell', x: params.real_x, z:params.real_z }; line.mazeid = maze.id; line.cellid = cellid; cell.separation_lines.push(line); if(!self.door_separators[cellid]) { self.door_separators[cellid]=[]; } self.door_separators[cellid].push(line); if(extra_door=='start'){ line.callback_data.mazeaction = 'leave_maze_from_start'; } if(extra_door=='end'){ line.callback_data.mazeaction = 'leave_maze_from_end'; } line.enter_leave_door = !!extra; line.outside_door = outside_door; if(outside_door) { self.outside_separators.push(line); } }; }