Outerra forum

Please login or register.

Login with username, password and session length
Advanced search  

News:

Outerra Tech Demo download. Help with graphics driver issues

Pages: 1 2 [3] 4

Author Topic: Anteworld Game Design Think Tank  (Read 14267 times)

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #30 on: July 26, 2022, 03:56:02 am »

The first rule of multiplayer is plan for it early and in every change. Otherwise you have to rewrite all the code from scratch to make it work. So that's why I always consider it.
OK...I'll take it into account.
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #31 on: August 02, 2022, 02:58:30 pm »

"green transition" becoming reality in outerra (experimenting with procedural corn)   :D



tecnical note : corn-spawning empties (spawning each 15 corn plants randomly) are created around the camera: each time the camera passes again near the same spot the corn-spawners are created again and corn is spawned randomly again so resulting in different positions each time
...good ? bad ? doesn't matter ? idk
In case I'd need to store the positions the first time they're generated, saving them in a file and then figure out how to recover them for each of the corn spawning empties when needed

« Last Edit: August 02, 2022, 04:52:06 pm by fly77 »
Logged

wesleyibruce

  • Jr. Member
  • *
  • Posts: 21
Re: Anteworld Game Design Think Tank
« Reply #32 on: August 04, 2022, 04:13:11 am »

No you don't need to save to a file to get them in the same places. Each spawn pattern needs to seem random but without being random. That is how minecraft does huge map saves with tiny files sizes. Nothing is really random.
Take the absolute value of (latitude + longitude + altitude)/3 ignoring the degrees and using only the minutes and seconds. Put the empties on a grid so they are uniform. Because the lat, lon, and alt of the empty is known but differs you can split that number into values that are the pseudorandom location for each corn stalk.
https://en.wikipedia.org/wiki/Pseudorandomness
Thus two players see the corn in the same place and the corn does not move/change as the player moves and comes back. To add more randomness create a short list if corn clusters, other vegetation and use the same pseudorandom call to pick one from the list. This will repeat every few hundred miles, a degree at the equator is ~111 km
this is why I've been asking about random numbers from location.
This is how many big open worlds do multiplayer.
« Last Edit: August 04, 2022, 04:27:22 am by wesleyibruce »
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #33 on: August 04, 2022, 04:18:01 am »

No you don't need to save to a file to get them in the same places. Each spawn pattern needs to seem random but without being random. That is how minecraft does huge map saves with tiny files sizes. Nothing is really random.
Take the absolute value of (latitude + longitude + altitude)/3 ignoring the degrees and using only the minutes and seconds. Put the empties on a grid so they are uniform. Because the lat, lon, and alt of the empty is known but differs you can split that number into values that are the pseudorandom location for each corn stalk.
https://en.wikipedia.org/wiki/Pseudorandomness
Thus two players see the corn in the same place and the corn does not move/change as the payer moves and comes back. To add more randomness create a short list if corn clusters, other vegetation and use the same pseudorandom call to pick one from the list. This will repeat every few hundred miles, a degree at the equator is ~111 km
this is why I've been asking about random numbers from location.
This is how many big open worlds do multiplayer.

Thanks ..yes I need to use pseudorandom numbers...will do it !
Logged

wesleyibruce

  • Jr. Member
  • *
  • Posts: 21
Re: Anteworld Game Design Think Tank
« Reply #34 on: August 04, 2022, 04:29:43 am »

Notches original pseudorandom code is why he's a billionaire and I'm not. It's in a hundred games.
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #35 on: August 04, 2022, 03:30:31 pm »

yep it works !

going back and forth to the same area multiple times leaves everything the same !

Wonderful "invention" these pseudorandom numbers ! :))


Find the differences !

Logged

wesleyibruce

  • Jr. Member
  • *
  • Posts: 21
Re: Anteworld Game Design Think Tank
« Reply #36 on: August 05, 2022, 07:01:18 pm »

Its a powerful tool. I'd be dangerous if I could read and write code as fast as you. I would still love to see the code. Where can you post it?
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #37 on: August 05, 2022, 11:01:45 pm »

Its a powerful tool. I'd be dangerous if I could read and write code as fast as you. I would still love to see the code. Where can you post it?

I'd like to fix some gross error before posting it. Il'll post it here.
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #38 on: August 06, 2022, 12:33:00 am »

OK first bug fix: spawn the empties allways oriented in same way , besides with same positions

Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #39 on: August 06, 2022, 12:48:30 am »

so how does it work ?
a top level empty (empty3) spawns lower level empties (empty2) all north oriented on a 3x3 lat-lon mesh around the camera , but not on all meshpoints. On which meshpoints to spawn empty2 is controlled by pseudorandom number computed by function mulberry32. Each meshpoint is assigned a different pseudorandom number and empty2 is spawned if the number exceeds a threshold.
empty2 are only spawned within a certain range about the camera..and a list of the spawned objects is stored in an array this.spawnedObject=[]. 
An id is assigned to each empty2 object spawned computed from lat lon (100000*Math.round(100000*this.meshLat[i+3][k+3])+Math.round(100000*this.meshLon[i+3][k+3]))
To do the cleanup of empty2 which get out of camera range a scan is done on a 6x6 grid of meshpoints around the camera and any empty2 that exists in the list of created objects beyond the camera range is called - via my plugin and passing the apropriate id - that it should
do the cleanup of its stuff (plants, deers, etc.) and then delete itself. Its assumed that empty2 does what it needs to do  and so the list of spawned empty2 objects is updated.
Note the cleanup still does not work correctly ..it still leaves many empty2 behind..I don't know why.

Code: [Select]

function mulberry32(a) {  // generate pseudorandom number from  seed a
   // return function() {
      var t = a += 0x6D2B79F5;
      t = Math.imul(t ^ t >>> 15, t | 1);
      t ^= t + Math.imul(t ^ t >>> 7, t | 61);
      return ((t ^ t >>> 14) >>> 0) / 4294967296;
   // }
}

...other functions used in the code (not reported here)

function init_chassis(param)
{
 
   
  $igc = this.$query_interface("ot::js::igc.get"); 
 
  $world = this.$query_interface("ot::js::world.get");
 
  screen_size = $world.screen_size();

  $plugin = this.$query_interface('xt::js::fly77_plugin.get');

 

}



function init_vehicle()
{
 
   
  this.geomob = this.get_geomob(0);
 
  this.cnv=this.$query_interface("ot::js::canvas.create",true);
 
  $world = this.$query_interface("ot::js::world.get");

  fnt=this.cnv.load_font("ui/courier_new_18_bold.fnt");   
 
 
 
  this.ecef = this.geomob.get_pos();
 
  this.norm = gravity_normal(this.ecef);
 
  this.rot = this.geomob.get_rot();
 
  this.north = localnorth(this.ecef);
 
   this.west = localwest(this.ecef);
   
   
    // retrieve camera heading ..needed to get north oriented rotation for spawning empty2 north oriented
   
    this.camera_rot = this.rot;   
   
    var unitquat = {x:0,y:1,z:0,w:0};  // y is model forward durection 

    this.cam_direc =   Quatmult(this.camera_rot,Quatmult(unitquat,QuatConjug(this.camera_rot)));
   
   
    this.cam_heading = Math.acos( this.cam_direc.x*this.north.x +   this.cam_direc.y*this.north.y +  this.cam_direc.z*this.north.z)*180.0/PI;
 
   if ( this.cam_direc.x * this.west.x +  this.cam_direc.y * this.west.y +   this.cam_direc.z * this.west.z >  0.00001){
 
      this.cam_heading =   360.0 - this.cam_heading;
   }
   

   // get north rotation quaternion for spawning empty2 north oriented
   
   var Q = toQuatRot( this.cam_heading/180*PI, {x:0,y:0,z:1});  // rotation about model z axis   by angle   -heading   
   
         
   this.rotNorth =  Quatmult(this.rot,Q);   
 
 

 
  this.ecef_cam = $igc.pos(); 
  this.camLat = ecef2lat(this.ecef_cam);
  this.camLon = ecef2lon(this.ecef_cam); 
  this.camLat5 = Math.round(this.camLat*E5)/E5;
  this.camLon5 = Math.round(this.camLon*E5)/E5;
 
  // setup  spawnpoints mesh
  this.meshLat = [];
  this.meshLon = [];
  this.meshEcef = [];
  this.meshEcefDist = [];
    for (var i=-3; i<3; i++){
this.meshLat[i+3] = [];
this.meshLon[i+3] = [];
    this.meshEcef[i+3] = [];
this.meshEcefDist[i+3] = [];
for (var k = -3; k<3; k++){
this.meshLat[i+3][k+3] = 0;
this.meshLon[i+3][k+3] = 0;
this.meshEcef[i+3][k+3] = 0;
this.meshEcefDist[i+3][k+3] = 0;
}
  }
    for (var i=-3; i<3; i++){
  for (var k = -3; k<3; k++){
  this.meshLat[i+3][k+3]= this.camLat5 + i/E5;  
  this.meshLon[i+3][k+3]= this.camLon5 + k/E5;
          this.meshEcef[i+3][k+3]= latlon2ecef(this.meshLat[i+3][k+3],this.meshLon[i+3][k+3]);
  this.elev = $world.elevation_above_terrain_layers(this.meshEcef[i+3][k+3],1000).x;
  this.meshEcef[i+3][k+3]  = { x:  this.meshEcef[i+3][k+3].x -this.elev*this.norm.x, y:  this.meshEcef[i+3][k+3].y -this.elev*this.norm.y,  z:  this.meshEcef[i+3][k+3].z -this.elev*this.norm.z};
  this.meshEcefDist[i+3][k+3]= Math.sqrt(  (this.meshEcef[i+3][k+3].x-this.ecef_cam.x)*(this.meshEcef[i+3][k+3].x-this.ecef_cam.x) + 
   (this.meshEcef[i+3][k+3].y-this.ecef_cam.y)*(this.meshEcef[i+3][k+3].y-this.ecef_cam.y) +
   (this.meshEcef[i+3][k+3].z-this.ecef_cam.z)*(this.meshEcef[i+3][k+3].z-this.ecef_cam.z)   );

  }  
  }
 
 
  // setup secon (wider) spawnpoints mesh (for cleanup procedure)
 

  this.meshLat2 = [];
  this.meshLon2 = [];
  this.meshEcef2 = [];
  this.meshEcefDist2 = [];
    for (var i=-6; i<6; i++){
this.meshLat2[i+6] = [];
this.meshLon2[i+6] = [];
    this.meshEcef2[i+6] = [];
this.meshEcefDist2[i+6] = [];
for (var k = -6; k<6; k++){
this.meshLat2[i+6][k+6] = 0;
this.meshLon2[i+6][k+6] = 0;
this.meshEcef2[i+6][k+6] = 0;
this.meshEcefDist2[i+6][k+6] = 0;
}
  }
    for (var i=-6; i<6; i++){
  for (var k = -6; k<6; k++){
  this.meshLat2[i+6][k+6]= this.camLat5 + i/E5;  
  this.meshLon2[i+6][k+6]= this.camLon5 + k/E5;
          this.meshEcef2[i+6][k+6]= latlon2ecef(this.meshLat2[i+6][k+6],this.meshLon2[i+6][k+6]);  
  this.elev = $world.elevation_above_terrain_layers(this.meshEcef2[i+6][k+6],10000).x;
  this.meshEcef2[i+6][k+6]  = { x:  this.meshEcef2[i+6][k+6].x -this.elev*this.norm.x, y:  this.meshEcef2[i+6][k+6].y -this.elev*this.norm.y,  z:  this.meshEcef2[i+6][k+6].z -this.elev*this.norm.z};
  this.meshEcefDist2[i+6][k+6]= Math.sqrt(  (this.meshEcef2[i+6][k+6].x-this.ecef_cam.x)*(this.meshEcef2[i+6][k+6].x-this.ecef_cam.x) + 
   (this.meshEcef2[i+6][k+6].y-this.ecef_cam.y)*(this.meshEcef2[i+6][k+6].y-this.ecef_cam.y) +
   (this.meshEcef2[i+6][k+6].z-this.ecef_cam.z)*(this.meshEcef2[i+6][k+6].z-this.ecef_cam.z)   );

  }  
  }
 
 

 

// spawned object variables

  this.spawnedMax=0;
  this.spawnedObject = [];
  this.spawnedObjectId = [];
  this.spawnedObjectCreated = [];
 
 

 

 
}



function update_frame(dt)
{
 
 
 

for (var m=0; m< this.spawnedMax; m++){
scaledtext( this.cnv, this.spawnedObjectId[m],1, {x: 1900 , y:300+m*20},   {x:255, y:0, z:0, w:255});
}



       // update mesh of "fixed" spawnpoints around camera and figure out if objects need to be spawned for those which are within a range around the camera

  this.ecef_cam = $igc.pos(); 
  this.camLat = ecef2lat(this.ecef_cam);
  this.camLon = ecef2lon(this.ecef_cam); 
  this.camLat5 = Math.round(this.camLat*E5)/E5;
  this.camLon5 = Math.round(this.camLon*E5)/E5;
 



  for (var i=-3; i<3; i++){
  for (var k = -3; k<3; k++){
 
  this.meshLat[i+3][k+3]= Math.round(this.camLat5*E5 + i)/E5 ;  
  this.meshLon[i+3][k+3]= Math.round(this.camLon5*E5 + k)/E5;
  this.meshEcef[i+3][k+3]= latlon2ecef(this.meshLat[i+3][k+3],this.meshLon[i+3][k+3]);  
  this.elev = $world.elevation_above_terrain_layers(this.meshEcef[i+3][k+3],1000).x;
  this.meshEcef[i+3][k+3]  = { x:  this.meshEcef[i+3][k+3].x -this.elev*this.norm.x, y:  this.meshEcef[i+3][k+3].y -this.elev*this.norm.y,  z:  this.meshEcef[i+3][k+3].z -this.elev*this.norm.z};



  this.meshEcefDist[i+3][k+3]= Math.sqrt(  (this.meshEcef[i+3][k+3].x-this.ecef_cam.x)*(this.meshEcef[i+3][k+3].x-this.ecef_cam.x) + 
   (this.meshEcef[i+3][k+3].y-this.ecef_cam.y)*(this.meshEcef[i+3][k+3].y-this.ecef_cam.y) +
                                           (this.meshEcef[i+3][k+3].z-this.ecef_cam.z)*(this.meshEcef[i+3][k+3].z-this.ecef_cam.z)   );    


this.id =   (100000*Math.round(100000*this.meshLat[i+3][k+3])+Math.round(100000*this.meshLon[i+3][k+3])) ; // % 4294967295  ;


              // to avoid creating an object twice ..if its id has already been associated with a created object mark it as already created
  this.ObjectFoundAtMeshPoint=false;
  this.foundIndex=0;
  for (var j=0; j<this.spawnedMax;j++){
if(this.id==this.spawnedObjectId[j] ) {this.ObjectFoundAtMeshPoint = true; this.foundIndex=j; break;};
  }
 



  if (this.meshEcefDist[i+3][k+3] < factor*radius/E5 && !this.ObjectFoundAtMeshPoint && mulberry32((i+3)+10*(k+3)) <0.15 ) {   // if meshpoint is within range and  was not previously created at that meshpoint ..create it

this.spawn_pos =  { x:this.meshEcef[i+3][k+3].x + (1)*this.norm.x , y:this.meshEcef[i+3][k+3].y + (1)*this.norm.y, z:this.meshEcef[i+3][k+3].z + (0)*this.norm.z }  ;

this.spawnedObject[this.spawnedMax]= $world.create_instance(name,  this.spawn_pos  ,  this.rotNorth , false);
this.spawnedObjectId[this.spawnedMax]= (100000*Math.round(100000*this.meshLat[i+3][k+3])+Math.round(100000*this.meshLon[i+3][k+3])) ; //  % 4294967295  ;
                           
this.spawnedObject[this.spawnedMax].set_editor_id((100000*Math.round(100000*this.meshLat[i+3][k+3])+Math.round(100000*this.meshLon[i+3][k+3])) );  
 
this.spawnedMax++;



}

  }
 
  }






      // scan the meshoints around the camera and do a "remove-yourself" call on any objects  who are out of range




  for (var i=-6; i<6; i++){
  for (var k = -6; k<6; k++){

          this.meshLat2[i+6][k+6]= Math.round(this.camLat5*E5 + i)/E5 ;  
  this.meshLon2[i+6][k+6]= Math.round(this.camLon5*E5 + k)/E5;
  this.meshEcef2[i+6][k+6]= latlon2ecef(this.meshLat2[i+6][k+6],this.meshLon2[i+6][k+6]);  
  this.elev = $world.elevation_above_terrain_layers(this.meshEcef2[i+6][k+6],1000).x;
  this.meshEcef2[i+6][k+6]  = { x:  this.meshEcef2[i+6][k+6].x -this.elev*this.norm.x, y:  this.meshEcef2[i+6][k+6].y -this.elev*this.norm.y,  z:  this.meshEcef2[i+6][k+6].z -this.elev*this.norm.z};


  this.meshEcefDist2[i+6][k+6]= Math.sqrt(  (this.meshEcef2[i+6][k+6].x-this.ecef_cam.x)*(this.meshEcef2[i+6][k+6].x-this.ecef_cam.x) + 
   (this.meshEcef2[i+6][k+6].y-this.ecef_cam.y)*(this.meshEcef2[i+6][k+6].y-this.ecef_cam.y) +
                                           (this.meshEcef2[i+6][k+6].z-this.ecef_cam.z)*(this.meshEcef2[i+6][k+6].z-this.ecef_cam.z)   );

this.id =   (100000*Math.round(100000*this.meshLat2[i+6][k+6])+Math.round(100000*this.meshLon2[i+6][k+6])) ; // % 4294967295  ;


              // signal for removal only those object if its found on the list of spawned objects
  this.ObjectFoundAtMeshPoint=false;
  this.foundIndex=0;
  for (var j=0; j<this.spawnedMax;j++){
if(this.id==this.spawnedObjectId[j] ) {this.ObjectFoundAtMeshPoint = true; this.foundIndex=j; break;};
  }
     


               
if (this.meshEcefDist2[i+6][k+6] >= factor*radius/E5 && this.ObjectFoundAtMeshPoint ){

$plugin.my_set_id( 0, (100000*Math.round(100000*this.meshLat2[i+6][k+6])+Math.round(100000*this.meshLon2[i+6][k+6]))  ) ; //do a "remove yourself" call


if (this.spawnedMax>=1){
for (var q=this.foundIndex; q<this.spawnedMax-1; q++){
this.spawnedObject[q]= this.spawnedObject[q+1];
this.spawnedObjectId[q]=  this.spawnedObjectId[q+1];
}
}
this.spawnedObjectId[this.spawnedMax]=null;
this.spawnedMax--;



}

}
  }


 

}

« Last Edit: August 06, 2022, 06:00:01 am by fly77 »
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #40 on: August 06, 2022, 06:03:18 am »

I found a much better way to do the cleanup :
instead of doing the camera distance check only in empty3 I do it also in empty2 and if empty2 recognizes that its distance from the camera is greater than some range it does what it needs to do and finally deletes itself.
In this way there is no need for any calls from empty3 to empty2 (which seem do not behave well with the asyncronous creation/deletion events). So now Empty2 is fully autonomous. I don't know why I didn't think of it earlier.
Indeed it works like a charm !
Camera distance check in empty3 still serves to update the list of created objects which needs to be known by empty3 in order to not recreate objects which are still in the scene.

So now its time to introduce a lot more variety, randomness and some constraints on where to place stuff:
first one : random  plant size and object rotations



« Last Edit: August 06, 2022, 07:26:21 am by fly77 »
Logged

wesleyibruce

  • Jr. Member
  • *
  • Posts: 21
Re: Anteworld Game Design Think Tank
« Reply #41 on: August 06, 2022, 09:03:45 am »

I make one suggestion and he makes a whole new world of possibilities.  :o
Your left over empties may be a variant of the north west problem in minecraft. Because it checks in a particular order it can miss something. Running two passes works and doing another check cycle with a starting point +1x & +1y (assuming z is vertical.) May pick them up.
Height may matter too if the empty is in a dip its refence point may be out of the checked plane, volume. 

It would be really nice if we could use this to fix the tree number & type limits. So I can get my southern hemisphere trees.  =D
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #42 on: August 06, 2022, 09:20:36 am »

I make one suggestion and he makes a whole new world of possibilities.  :o
..
It would be really nice if we could use this to fix the tree number & type limits. So I can get my southern hemisphere trees.  =D

HaHa ..well its addicting !
yes, if you have some 3D tree models we can procedurally spawn them....but I can't suppress default outerra trees....also they need to have several LODs, optimized for low impact at great vewing distances ....as procedural trees will not be spawned in small numbers locally (see my corn in egypt...the corn LODs are optimized and there is a limit on how many can be in view within some hundred meters)
« Last Edit: August 06, 2022, 09:29:03 am by fly77 »
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #43 on: August 07, 2022, 12:48:30 pm »

Next step I like to explore is to make the objects spawned by empties "pickable" . So if an object has been picked I will make it invisible.. But I also will need to store the list of visible objects for each empty permanently somewhere....I suppose a file...is that what you call "Traits" ?
« Last Edit: August 07, 2022, 01:06:38 pm by fly77 »
Logged

fly77

  • Outerra Master Modder
  • Hero Member
  • *****
  • Posts: 1755
Re: Anteworld Game Design Think Tank
« Reply #44 on: August 09, 2022, 05:48:31 am »

Andfly ! Please help !  Those flowers are "too strong"  :))

« Last Edit: August 10, 2022, 09:52:23 am by fly77 »
Logged
Pages: 1 2 [3] 4