Search this blog

16 December, 2010

Shameless plug

Can't help being proud of this game. At the end of Round 4 I felt like doing more was going to be hard, especially as we optimized the shit out of it and we were "done" in terms of the amount of computation we could push on this generation. Champion manages to do much more without computing much more, while also handling a lot more content. It was mostly an achievement of hygiene and understanding rather than optimization and complex techniques. That also left plenty of space for the future...

Round 4

Champion


http://www.gametrailers.com/video/debut-trailer-fight-night/707453

http://www.ign.com/videos/2010/12/21/fight-night-champion-champion-mode-trailer?objectid=81346&show=HD

15 December, 2010

Stupid sample generator - 3d version

//  Generates a packing of points inside a shape
//  press right/middle mouse buttons a few times until convergence...
class pnt
{
  float x; float y; float z;
  
  float len()
  {
    return sqrt(x*x + y*y + z*z);
  }
};
pnt points[] = new pnt[32];
float scalefact = 100;
float mindist = scalefact * 0.5;
void setup() {
  size(512, 512, P3D);
  fill(204);
  
    for(int i=0; i< points.length; i++)
  {
    points[i]=new pnt();
    points[i].x = random(scalefact*2)-scalefact;
    points[i].y = random(scalefact*2)-scalefact;
    points[i].z = random(scalefact*2)-scalefact;
  }
}
float getImportance(pnt a)
{
  return 1.3 - (((a.z/scalefact) + (a.z/a.len()))/2);
}
void mousePressed() 
{
  if(mouseButton == LEFT) // dump points, report
  {  
    final float UNIT_HEMISPHERE_VOL = (4.0/3.0) * 3.141592 * 0.5;
    float totalVol = 0;
    
    for(int i=0; i< points.length; i++)
    {
      float normRadius = mindist * getImportance(points[i]) / scalefact;
      float vol = (4.0/3.0) * 3.141592 * normRadius * normRadius * normRadius;
      totalVol+=vol;
    }
    println("vol ratio" + UNIT_HEMISPHERE_VOL/totalVol);
    
    for(int i=0; i< points.length; i++)
    {
      //println(points[i].x/scalefact+", "+points[i].y/scalefact+", "+points[i].z/scalefact+", "+points[i].len()/scalefact+",");
      
      float normRadius = mindist * getImportance(points[i]) / scalefact;
      float vol = (4.0/3.0) * 3.141592 * normRadius * normRadius * normRadius;
      float weight = vol / totalVol;
      
      println(points[i].x/scalefact+", "+points[i].y/scalefact+", "+points[i].z/scalefact+", "+weight+",");
    }
    println("vol ratio: " + UNIT_HEMISPHERE_VOL/totalVol);
  }
  else if(mouseButton == CENTER)
  {
    mindist -= 5; // manual control...
  }
  else // RIGHT mouse button
  {
    // A LITTLE BIT OF NOISE
    for(int i=0; i< points.length; i++)
    {
      points[i].x += random(mindist/(scalefact/10))-(mindist/(scalefact/5));
      points[i].y += random(mindist/(scalefact/10))-(mindist/(scalefact/5));
      points[i].z += random(mindist/(scalefact/10))-(mindist/(scalefact/5));
    }
  }
}
void draw() {
  lights();
  background(0);
  
  // Change height of the camera with mouseY
  camera(sin(mouseX/512.0 * 6.0)*300, cos(mouseX/512.0 * 6.0)*300, mouseY, // eyeX, eyeY, eyeZ
         0.0, 0.0, 0.0, // centerX, centerY, centerZ
         0.0, 0.0, -1.0); // upX, upY, upZ
  
  noStroke();
  
  // SHAPE CONSTRAINT
  for(int i=0; i< points.length; i++)
  {    
    float len = points[i].len();
    
    float radius = getImportance(points[i])*mindist;
    
    len+=radius;
    
    if(len > scalefact) // inside the sphere
    {
      points[i].x /= len / scalefact;
      points[i].y /= len / scalefact;
      points[i].z /= len / scalefact;
    }
    // hemisphere
    if(points[i].z-radius< 0) points[i].z=radius;
  }
  float totaldist = 0;
  // DISTANCE CONSTRAINT
  for(int i=0; i< points.length; i++)
  {       
    float radius = getImportance(points[i])*mindist;
    
    for(int k=0; k< 50; k++)
    {
      int j = (int)random(points.length);
      
      if(i!=j)
      {
        pnt tmp = new pnt();
        
        tmp.x = points[j].x - points[i].x;
        tmp.y = points[j].y - points[i].y;
        tmp.z = points[j].z - points[i].z;
        
        float len = tmp.len();
        
        if( len <  radius )
        {
          tmp.x = (tmp.x/len) * radius;
          tmp.y = (tmp.y/len) * radius;
          tmp.z = (tmp.z/len) * radius;
          
          points[i].x = points[j].x - tmp.x;
          points[i].y = points[j].y - tmp.y;
          points[i].z = points[j].z - tmp.z;
          
          totaldist += len;
        }
      }
    }
  }
  
  // display distance violation
  fill(15,15,15,255);
  box(scalefact,scalefact,1);
  
  totaldist = sqrt(totaldist);
  fill(128,0,128,128);
  box(totaldist,totaldist,5);
    
  for(int i=0; i< points.length; i++)
  {
      pushMatrix();
      translate(points[i].x,points[i].y,points[i].z);
      
      if(mindist>0)
      {
        fill(255,255,255,64);
        sphere(mindist * getImportance(points[i]) );
      }
      
      fill(255,0,0,255);
      sphere(5 * getImportance(points[i]) );
      popMatrix();
      stroke(255,0,0,255);     
      line(0,0,0,points[i].x,points[i].y,points[i].z);
      noStroke();
  }
}

Stupid sample generator

I had to write in a rush some code to generate low-discrepancy samples in a shape (i.e. hemisphere). For those kind of quick hacks I find processing to be great, mostly because of its visual nature.



The code is really stupid and it requires some manual interaction. With the left mouse button you dump the sample info, with the middle you shrink the sample minimum distance spheres, with the right you add a random shake to the whole thing (yes, it's laughable...).

Anyways. Here it is.




//  Generates a packing of points inside a 2d shape
//  press right/middle mouse buttons a few times until convergence...
class pnt
{
  float x; float y;
};


pnt points[] = new pnt[16];
float mindist = 60;


void setup() {
  size(512, 512, P3D);
  fill(204);
  
    for(int i=0; i< points.length; i++)
  {
    points[i]=new pnt();
    points[i].x = random(200)-100;
    points[i].y = random(200)-100;
  }
}


void mousePressed() 
{
  if(mouseButton == LEFT)
  {
    for(int i=0; i< points.length; i++)
    {
      float len = sqrt(points[i].x*points[i].x + points[i].y*points[i].y);
      println(points[i].x/100.0 + ", " + points[i].y/100.0 + ", " + len/100.0 + ",");
    }
  }
  else if(mouseButton == CENTER)
  {
    mindist -= 5; // manual control...
  }
  else // RIGHT mouse button
  {
    // A LITTLE BIT OF NOISE
    for(int i=0; i< points.length; i++)
    {
      points[i].x += random(mindist/10)-(mindist/20);
      points[i].y += random(mindist/10)-(mindist/20);
    }
  }
}


void draw() {
  lights();
  background(0);
  
  // Change height of the camera with mouseY
  camera(30.0, mouseY, 400.0, // eyeX, eyeY, eyeZ
         0.0, 0.0, 0.0, // centerX, centerY, centerZ
         0.0, 1.0, 0.0); // upX, upY, upZ
  
  noStroke();


  float totaldist = 0;


  // DISTANCE CONSTRAINT
  for(int i=0; i< points.length; i++)
  {       
    for(int k=0; k<50; k++)
    {
      int j = (int)random(points.length);
      if(i!=j)
      {
        pnt tmp = new pnt();
        
        tmp.x = points[j].x - points[i].x;
        tmp.y = points[j].y - points[i].y;
        
        float len = sqrt(tmp.x*tmp.x + tmp.y*tmp.y);
        
        if(len< mindist)
        {
          tmp.x = (tmp.x/len)*mindist;
          tmp.y = (tmp.y/len)*mindist;


          points[i].x = points[j].x - tmp.x;
          points[i].y = points[j].y - tmp.y;


          
          totaldist += len;
        }
      }
    }
  }
  
  // DISTANCE FROM ZERO CONSTRAINT
  for(int i=0; i< points.length; i++)
  {       
      float len = sqrt(points[i].x*points[i].x + points[i].y*points[i].y);
      
      if(len< mindist)
      {        
        points[i].x /= len/(mindist);
        points[i].y /= len/(mindist);
      }
  }
  
  // SHAPE CONSTRAINT
  for(int i=0; i< points.length; i++)
  {    
    float len = sqrt(points[i].x*points[i].x + points[i].y*points[i].y);
    
    if(len>100)
    {
      points[i].x/=len/100.0;
      points[i].y/=len/100.0;
    }
  }
  
  // display distance violation
  totaldist = sqrt(totaldist);
  fill(15,15,15,255);
  box(totaldist);
    
  for(int i=0; i< points.length; i++)
  {
      pushMatrix();
      translate(points[i].x,points[i].y,0);
      if(mindist>0)
      {
        fill(255,255,255,64);
        sphere(mindist);
      }
      fill(255,0,0,255);
      sphere(5);           
      popMatrix();
  }
}




05 December, 2010

Leaving EA

I've always avoided mentioning the company I've been working with while writing this blog. Not that it was a secret, actually many people in my company knew the blog, it's just that I don't love the idea of linking the blog to a company as I don't want to be bothered by people asking questions on the blog about my work. 

Recently though I've decided to resign and accept a new offer, so now I'm free to write about my last three years of work. 

I'll publish also some photos, I have to admit that for the first few days when I started working there I could not avoid smiling while walking around in the amazing Burnaby's campus.

My cube, while working for Fight Night Champion
I've been very lucky there. When I left Milan for EA Canada, most of my friends in the industry were telling me that in such a big company I was going to end up taking care of a bunch of small tasks, not being able to influence much such huge and bureaucratic teams.
Two of the three building of EA Canada's studio
Bullshit! It turns out that to make great games you need great teams, able to move quickly, experiment new ideas, be agile and open to all the relevant opinions. I actually was able to do more research in my job as a on-team rendering engineer than my previous two years, in a smaller company, where I was a R&D engineer working on their next-gen graphics engine!
Secondary entrance, at night
It is true that not all the games at EA are the same, and that you can be moved around regardless of your preferences. But if you're smart, if you fight for it, and if you're not too unlucky, you'll end up being very happy about your job.
One of the cafeterias
It's not easy, and there is a lot to learn about how such a huge company works, but it can be an unique experience. Surely there are some opportunities that you won't find anywhere else...
Study rooms area
One of the best points for me was the ability of learning, talking, and sharing knowledge with such a huge worldwide audience.
Technology sharing at EA is somewhat a recent process, and it has its problems (you have a lot of nicely packaged, great tech, but there is not much overall design as you put together a game made of lots of different bits that came from all over the world) but it doesn't matter.
One of the many patios
The best asset is not the technology, is the knowledge! Even if EA has some stupid legal restrictions and there is still a lot of work to be done to let every game influence each other better and in a more effective way (I tried to do my part, for what I could, to address that), you won't easily find another company where you can access so much data and papers about virtually anything. Or where you can so easily talk with the best individuals across the world in any game-related field, work with universities, or directly with the hardware manufacturers.
Corridor of the second building

Why I've left? Because even if you can love your job, and love your team, hardly you can have such an empathy for the whole company. EA is a big corporation, it has its own objectives, mostly driven by the market. That is fine, and I don't mind the fact that it can move people around or fire them faster than lightspeed, but it means that the relationship with it has to be strictly a business one. It's not a family, it won't care much about you, so in my opinion it's hard to develop any emotional attachment to it. For a couple of years it failed to match what I believed to be my market value, so I ended up signing a more lucrative contract, it's as simple as that.

Fight Night Champion, WIP screenshot

Fight Night Champion has been my last game at EAC. It's a game that I feel particularly "mine", and I hope it will perform great! It surely has all the potential to be 2/3 metacritic points above Round 4 (that was 87/88) but who knows, reviews are influenced by many factors other than quality (i.e. hype...). We'll see, I surely think the team did a great job.