Loading...
Logo
Processing Forum
I'm trying polymorphism in Processing for the first time and have a class Sphere that extends class Shape. I have an ArrayList<Shape> called objects and when I try to access a variable that is in Shape like:
Copy code
  1. println(objects.get(0).n);

There is no problem. The variable, n, above does not appear in Sphere, only in Shape (Sphere inherits it). On the other hand accessing something that is in Sphere but not in Shape does not seem to work:
Copy code
  1. println(objects.get(0).radius);

I get an error message that tells me "radius cannot be resolved or is not a field". How can I go about accessing this? Code in case that is helpful:

Copy code
  1. ArrayList<Shape> objects;
  2. PVector light, prp, vrp;
  3. float uMin, uMax, vMin, vMax;

  4. void setup() {
  5.   size(800, 800, P2D);
  6.   loadPixels();

  7.   /* Just one light for now:
  8.    Without the code to handle otherwise, it is assumed
  9.    that the light produces pure white color */
  10.   light = new PVector(20.0, 20.0, -5.0);

  11.   // Prepare Viewing-Reference Coordinate System
  12.   prp = new PVector(0.0, 0.0, 0.0);
  13.   vrp = new PVector(0.0, -20.0, -40.0);
  14.   uMin = -40.0;
  15.   uMax = 40.0;
  16.   vMin = -40.0;
  17.   vMax = 40.0;

  18.   // Prepare objects
  19.   objects = new ArrayList<Shape>();

  20.   // Silver Sphere
  21.   PVector ssCen = new PVector(15.0, -5.0, -80.0);
  22.   Color ssKa = new Color(0.231, 0.231, 0.231);
  23.   Color ssKd = new Color(0.278, 0.278, 0.278);
  24.   Color ssKs = new Color(0.774, 0.774, 0.774);
  25.   objects.add(new Sphere(ssCen, 12.0, ssKa, ssKd, ssKs, 89.6, true));

  26.   // Test Silver Sphere duplicate 1
  27.   PVector test = new PVector(-10.0, 0.0, -80.0);
  28.   objects.add(new Sphere(test, 12.0, ssKa, ssKd, ssKs, 89.6, true));

  29.   // Test Silver Sphere duplicate 2
  30.   PVector test2 = new PVector(0.0, -25.0, -80.0);
  31.   objects.add(new Sphere(test2, 12.0, ssKa, ssKd, ssKs, 89.6, true));
  32. }

  33. void draw() {
  34.   println(objects.get(0).radius);
  35.   
  36.   // Delta i and j
  37.   float di = (uMax-uMin)/width;
  38.   float dj = (vMax-vMin)/height;

  39.   // Fire a ray that bounces recursively
  40.   float left = uMin+vrp.x;
  41.   for (int i = 0; i < width; i++) {
  42.     float top = vMax+vrp.y; // Up is up, not down!
  43.     for (int j = 0; j < height; j++) {
  44.       // Rays fire from the prp to a point on the vrp
  45.       PVector direction = new PVector(left, top, vrp.z);
  46.       Color intensity = rayTrace(prp, direction, 0);

  47.       // Map the colors and make sure none exceed 255
  48.       int r = (int)(min(intensity.r, 1.0)*255.0);
  49.       int g = (int)(min(intensity.g, 1.0)*255.0);
  50.       int b = (int)(min(intensity.b, 1.0)*255.0);
  51.       pixels[i+j*width] = (255<<24)|(r<<16)|(g<<8)|b;
  52.       top -= dj;
  53.     }
  54.     left += di;
  55.   }
  56.   updatePixels();
  57. }

  58. class Color {
  59.   float r, g, b;
  60.   Color(float inR, float inG, float inB) {
  61.     r = inR;
  62.     g = inG;
  63.     b = inB;
  64.   }
  65. }

  66. class Shape {
  67.   boolean reflects;
  68.   float n;
  69.   Color ka, kd, ks;
  70.   Shape(Color inKa, Color inKd, Color inKs, float inN, boolean inRe) {
  71.     ka = inKa;
  72.     kd = inKd;
  73.     ks = inKs;
  74.     n = inN;
  75.     reflects = inRe;
  76.   }
  77.   float intersection(PVector a, PVector b) {
  78.     return -1.0;
  79.   }
  80.   PVector surfaceNormal(PVector in) {
  81.     PVector out = new PVector(0.0, 0.0, 0.0);
  82.     return out;
  83.   }
  84. }

  85. class Sphere extends Shape {
  86.   float radius, radSq;
  87.   PVector center;
  88.   Sphere(PVector inC, float inR, Color inKa, Color inKd, Color inKs, float inN, boolean inRe) {
  89.     super(inKa, inKd, inKs, inN, inRe);
  90.     center = inC;
  91.     radius = inR;

  92.     // Math simplification
  93.     radSq = radius*radius;
  94.   }
  95.   float intersection(PVector a, PVector b) {
  96.     // Delta x, y, and z
  97.     float dx = b.x-a.x;
  98.     float dy = b.y-a.y;
  99.     float dz = b.z-a.z;

  100.     // Math simplifications
  101.     float rxa = a.x-center.x;
  102.     float rya = a.y-center.y;
  103.     float rza = a.z-center.z;

  104.     // Prepare Quadratic Formula
  105.     float qa = dx*dx+dy*dy+dz*dz;
  106.     float qb = 2*(dx*rxa+dy*rya+dz*rza);
  107.     float qc = rxa*rxa+rya*rya+rza*rza-radSq;

  108.     // Parametric t value
  109.     float t = -1;

  110.     // Calculate (up to) two intersections
  111.     float qRight = sqrt(qb*qb-4*qa*qc);
  112.     if (!Float.isNaN(qRight)) {
  113.       float twoQa = 2*qa;
  114.       t = min((-qb+qRight)/twoQa, (-qb-qRight)/twoQa);
  115.     }
  116.     return t;
  117.   }
  118.   PVector surfaceNormal(PVector in) {
  119.     PVector out = new PVector(
  120.     (in.x-center.x)/radius, 
  121.     (in.y-center.y)/radius, 
  122.     (in.z-center.z)/radius);
  123.     return out;
  124.   }
  125. }

  126. Color rayTrace(PVector a, PVector b, int depth) {
  127.   Color out = new Color(0.0, 0.0, 0.0);

  128.   // Determine which object (if any) is closest
  129.   float t = 1000000; // Arbitrarily large
  130.   int id = -1;
  131.   for (int i = 0; i < objects.size(); i++) {
  132.     float objDist = objects.get(i).intersection(a, b);
  133.     if (objDist > 0 && objDist < t) {
  134.       t = objDist;
  135.       id = i;
  136.     }
  137.   }

  138.   // If an object was intersected
  139.   if (id != -1) {
  140.     // Add ambient light
  141.     out.r += objects.get(id).ka.r;
  142.     out.g += objects.get(id).ka.g;
  143.     out.b += objects.get(id).ka.b;

  144.     // Point of intersection
  145.     PVector intVec = new PVector(
  146.     a.x+t*(b.x-a.x), 
  147.     a.y+t*(b.y-a.y), 
  148.     a.z+t*(b.z-a.z));

  149.     // Get normalized surface normal vector
  150.     PVector surNor = objects.get(id).surfaceNormal(intVec);

  151.     // Get normalized light direction vector
  152.     PVector ligVec = new PVector(light.x-intVec.x, light.y-intVec.y, light.z-intVec.z);
  153.     ligVec.normalize();

  154.     // Calculate (N*L)
  155.     float dotNL = surNor.dot(ligVec);
  156.     if (dotNL > 0) {
  157.       // Add diffuse light
  158.       out.r += objects.get(id).kd.r*dotNL;
  159.       out.g += objects.get(id).kd.g*dotNL;
  160.       out.b += objects.get(id).kd.b*dotNL;

  161.       // Get normalized reflection direction vector
  162.       PVector refVec = new PVector(2*surNor.x*dotNL-ligVec.x, 
  163.       2*surNor.y*dotNL-ligVec.y, 
  164.       2*surNor.z*dotNL-ligVec.z);
  165.       refVec.normalize();

  166.       // Get normalized viewer direction vector
  167.       PVector vieVec = new PVector(a.x-intVec.x, a.y-intVec.y, a.z-intVec.z);
  168.       vieVec.normalize();

  169.       // Calculate (R*V) to the nth power
  170.       float dotRVSq = refVec.dot(vieVec);
  171.       dotRVSq = pow(dotRVSq, objects.get(id).n);
  172.       if (dotRVSq > 0) {
  173.         // Add specular light
  174.         out.r += objects.get(id).ks.r*dotRVSq;
  175.         out.g += objects.get(id).ks.g*dotRVSq;
  176.         out.b += objects.get(id).ks.b*dotRVSq;
  177.       }

  178.       // Recursively get the bounce light
  179.       if (objects.get(id).reflects && depth < 3) {
  180.         PVector direction = new PVector(intVec.x+refVec.x, intVec.y+refVec.y, intVec.z+refVec.z);
  181.         Color reflection = rayTrace(intVec, direction, depth+1);
  182.         out.r += reflection.r*objects.get(id).ks.r;
  183.         out.g += reflection.g*objects.get(id).ks.g;
  184.         out.b += reflection.b*objects.get(id).ks.b;
  185.       }
  186.     }
  187.   }
  188.   return out;
  189. }
Edit: Realized highlighting would probably be helpful

Replies(13)

Well...
If you instantiate a class, you only have what they got!
Class Shape does not extend any class, so what you see there is what you get!

On the other hand, class Sphere extends from class Shape.
So it has what itself got plus what Shape got!

If you need to access something exclusive from Sphere (like that variable radius), your ArrayList has to be type Sphere instead of its parent Shape!
Edit: Opps, just reread that last line you wrote.

Well, eventually I want to have other kinds of shapes (Plane, Cylinder, etc). I was hoping to put them all into one ArrayList. How should I set this up?
For what I know, you need type Sphere to get Sphere's exclusive fields & methods!

So, instead of ArrayList<Shape>, you need ArrayList<Sphere>.

A parent class cannot use anything exclusive from classes which inherit from it.

Inheritance is always from top to bottom!
Sorry, I was editing my comment while you wrote that. Any idea on it?
As you point out yourself, the ArrayList holds Shapes. So only Shape variables and methods are safe. There could be 100 extended classes of Shape with 100 different methods and variables. But since the computer only knows for sure it's a Shape (because it's in a basket of Shapes) the only methods and variables that are allowed are from the Shape class. There are a few ways to workaround this. For example...

Code Example (direct)
Copy code
  1.   Sphere s = (Sphere) objects.get(0); // cast explicitly to a Sphere
  2.   println(s.radius);
Code Example (instanceof)
Copy code
  1.   Shape shape = objects.get(0);
  2.   if (shape instanceof Sphere) { // test which class the object instance comes from
  3.     Sphere sphere = (Sphere) shape; // then cast explicitly to that class (i.e. Sphere)
  4.     println(sphere.radius); // then use class-specific variables and methods
  5.   }
But I believe that both these workarounds aren't a first-resort option and it's "better" OOP to have a general method name that just does different things in different extended classes. Just my $0.02 Perhaps others can shed more light on this.

Well, eventually I want to have other kinds of shapes (Plane, Cylinder, etc). I was hoping to put them all into one ArrayList. How should I set this up?
I'm new to Java as well and never did anything like inheritance.

Probably you're gonna need to have many ArrayLists of each subclass if you need access to things exclusive to them.



Also, take a look in this thread where me and Philho had to deal w/ something very similar to yours:

http://forum.processing.org/topic/calling-separate-functions-for-subclasses

Perhaps you can use Philho's idea of abstract classes.

Make class Shape abstract, so you can have an ArrayList of Shape to store all subclasses together.

But even abstract classes cannot have abstract fields, only methods.

I guess you should provide a "get" and/or "set" methods to deal w/ fields exclusive to subclasses!
" But even abstract classes cannot have abstract fields, only methods."
No. You are confusing with interfaces. Abstract classes can have concrete methods and fields.

The ArrayList<Shape> is the right approach.
As said, either you check the sub-type and cast it to get access to the specific stuff (but it isn't very elegant), or you design your shape class to allow access to generic properties. For example, you make a getDimension() method, which returns the diameter of a circle, the side size of a square, etc. Might be getDimensionX() and getDimensionY(), for example.
The design depends on your exact needs.

" But even abstract classes cannot have abstract fields, only methods."
No. You are confusing with interfaces. Abstract classes can have concrete methods and fields.
I didn't mean abstract classes can only have abstract methods. I know pretty well abstract classes can have concrete things!  
I was trying to highlight fields vs methods, about the fact that even abstract classes can't have abstract fields.
That is, I can't have abstract int radius within class Shape.
That's why I've mentioned right after that he needs to provide getter & setter methods to provide access to var radius for example.
Nevertheless, I guess abstract concept is only applied to methods anyways! 


Ah, sorry, I misread your message, you are right, of course.
Got it, it even moves the Sphere. I was worried that it would be sending me a copy of the PVector center but it sends me the one that controls its location. Thanks guys, code if interested (new code in red, returns an ArrayList<PVector> because some shapes will have more than one control point):
Copy code
  1. ArrayList<Shape> objects;
  2. PVector light, prp, vrp;
  3. float uMin, uMax, vMin, vMax;

  4. void setup() {
  5.   size(800, 800, P2D);
  6.   loadPixels();

  7.   /* Just one light for now:
  8.    Without the code to handle otherwise, it is assumed
  9.    that the light produces pure white color */
  10.   light = new PVector(20.0, 20.0, -5.0);

  11.   // Prepare Viewing-Reference Coordinate System
  12.   prp = new PVector(0.0, 0.0, 0.0);
  13.   vrp = new PVector(0.0, -20.0, -40.0);
  14.   uMin = -40.0;
  15.   uMax = 40.0;
  16.   vMin = -40.0;
  17.   vMax = 40.0;

  18.   // Prepare objects
  19.   objects = new ArrayList<Shape>();

  20.   // Silver Sphere
  21.   PVector ssCen = new PVector(15.0, -5.0, -80.0);
  22.   Color ssKa = new Color(0.231, 0.231, 0.231);
  23.   Color ssKd = new Color(0.278, 0.278, 0.278);
  24.   Color ssKs = new Color(0.774, 0.774, 0.774);
  25.   objects.add(new Sphere(ssCen, 12.0, ssKa, ssKd, ssKs, 89.6, true));

  26.   // Test Silver Sphere duplicate 1
  27.   PVector test = new PVector(-10.0, 0.0, -80.0);
  28.   objects.add(new Sphere(test, 12.0, ssKa, ssKd, ssKs, 89.6, true));

  29.   // Test Silver Sphere duplicate 2
  30.   PVector test2 = new PVector(0.0, -25.0, -80.0);
  31.   objects.add(new Sphere(test2, 12.0, ssKa, ssKd, ssKs, 89.6, true));
  32. }

  33. void draw() {
  34.   ArrayList<PVector> getCenter = objects.get(0).getControlPoints();
  35.   getCenter.get(0).x += 1;
  36.   
  37.   // Delta i and j
  38.   float di = (uMax-uMin)/width;
  39.   float dj = (vMax-vMin)/height;

  40.   // Fire a ray that bounces recursively
  41.   float left = uMin+vrp.x;
  42.   for (int i = 0; i < width; i++) {
  43.     float top = vMax+vrp.y; // Up is up, not down!
  44.     for (int j = 0; j < height; j++) {
  45.       // Rays fire from the prp to a point on the vrp
  46.       PVector direction = new PVector(left, top, vrp.z);
  47.       Color intensity = rayTrace(prp, direction, 0);

  48.       // Map the colors and make sure none exceed 255
  49.       int r = (int)(min(intensity.r, 1.0)*255.0);
  50.       int g = (int)(min(intensity.g, 1.0)*255.0);
  51.       int b = (int)(min(intensity.b, 1.0)*255.0);
  52.       pixels[i+j*width] = (255<<24)|(r<<16)|(g<<8)|b;
  53.       top -= dj;
  54.     }
  55.     left += di;
  56.   }
  57.   updatePixels();
  58. }

  59. class Color {
  60.   float r, g, b;
  61.   Color(float inR, float inG, float inB) {
  62.     r = inR;
  63.     g = inG;
  64.     b = inB;
  65.   }
  66. }

  67. class Shape {
  68.   boolean reflects;
  69.   float n;
  70.   Color ka, kd, ks;
  71.   Shape(Color inKa, Color inKd, Color inKs, float inN, boolean inRe) {
  72.     ka = inKa;
  73.     kd = inKd;
  74.     ks = inKs;
  75.     n = inN;
  76.     reflects = inRe;
  77.   }
  78.   float intersection(PVector a, PVector b) {
  79.     return -1.0;
  80.   }
  81.   PVector surfaceNormal(PVector in) {
  82.     PVector out = new PVector(0.0, 0.0, 0.0);
  83.     return out;
  84.   }
  85.   ArrayList<PVector> getControlPoints() {
  86.     ArrayList<PVector> out = new ArrayList<PVector>();
  87.     return out;
  88.   }
  89. }

  90. class Sphere extends Shape {
  91.   float radius, radSq;
  92.   PVector center;
  93.   Sphere(PVector inC, float inR, Color inKa, Color inKd, Color inKs, float inN, boolean inRe) {
  94.     super(inKa, inKd, inKs, inN, inRe);
  95.     center = inC;
  96.     radius = inR;

  97.     // Math simplification
  98.     radSq = radius*radius;
  99.   }
  100.   float intersection(PVector a, PVector b) {
  101.     // Delta x, y, and z
  102.     float dx = b.x-a.x;
  103.     float dy = b.y-a.y;
  104.     float dz = b.z-a.z;

  105.     // Math simplifications
  106.     float rxa = a.x-center.x;
  107.     float rya = a.y-center.y;
  108.     float rza = a.z-center.z;

  109.     // Prepare Quadratic Formula
  110.     float qa = dx*dx+dy*dy+dz*dz;
  111.     float qb = 2*(dx*rxa+dy*rya+dz*rza);
  112.     float qc = rxa*rxa+rya*rya+rza*rza-radSq;

  113.     // Parametric t value
  114.     float t = -1.0;

  115.     // Calculate (up to) two intersections
  116.     float qRight = sqrt(qb*qb-4*qa*qc);
  117.     if (!Float.isNaN(qRight)) {
  118.       float twoQa = 2*qa;
  119.       t = min((-qb+qRight)/twoQa, (-qb-qRight)/twoQa);
  120.     }
  121.     return t;
  122.   }
  123.   PVector surfaceNormal(PVector in) {
  124.     PVector out = new PVector(
  125.     (in.x-center.x)/radius, 
  126.     (in.y-center.y)/radius, 
  127.     (in.z-center.z)/radius);
  128.     return out;
  129.   }
  130.   ArrayList<PVector> getControlPoints() {
  131.     ArrayList<PVector> out = new ArrayList<PVector>();
  132.     out.add(center);
  133.     return out;
  134.   }
  135. }

  136. Color rayTrace(PVector a, PVector b, int depth) {
  137.   Color out = new Color(0.0, 0.0, 0.0);

  138.   // Determine which object (if any) is closest
  139.   float t = 1000000.0; // Arbitrarily large
  140.   int id = -1;
  141.   for (int i = 0; i < objects.size(); i++) {
  142.     float objDist = objects.get(i).intersection(a, b);
  143.     if (objDist > 0 && objDist < t) {
  144.       t = objDist;
  145.       id = i;
  146.     }
  147.   }

  148.   // If an object was intersected
  149.   if (id != -1) {
  150.     // Add ambient light
  151.     out.r += objects.get(id).ka.r;
  152.     out.g += objects.get(id).ka.g;
  153.     out.b += objects.get(id).ka.b;

  154.     // Point of intersection
  155.     PVector intVec = new PVector(
  156.     a.x+t*(b.x-a.x), 
  157.     a.y+t*(b.y-a.y), 
  158.     a.z+t*(b.z-a.z));

  159.     // Get normalized surface normal vector
  160.     PVector surNor = objects.get(id).surfaceNormal(intVec);

  161.     // Get normalized light direction vector
  162.     PVector ligVec = new PVector(light.x-intVec.x, light.y-intVec.y, light.z-intVec.z);
  163.     ligVec.normalize();

  164.     // Calculate (N*L)
  165.     float dotNL = surNor.dot(ligVec);
  166.     if (dotNL > 0) {
  167.       // Add diffuse light
  168.       out.r += objects.get(id).kd.r*dotNL;
  169.       out.g += objects.get(id).kd.g*dotNL;
  170.       out.b += objects.get(id).kd.b*dotNL;

  171.       // Get normalized reflection direction vector
  172.       PVector refVec = new PVector(2*surNor.x*dotNL-ligVec.x, 
  173.       2*surNor.y*dotNL-ligVec.y, 
  174.       2*surNor.z*dotNL-ligVec.z);
  175.       refVec.normalize();

  176.       // Get normalized viewer direction vector
  177.       PVector vieVec = new PVector(a.x-intVec.x, a.y-intVec.y, a.z-intVec.z);
  178.       vieVec.normalize();

  179.       // Calculate (R*V) to the nth power
  180.       float dotRVSq = refVec.dot(vieVec);
  181.       dotRVSq = pow(dotRVSq, objects.get(id).n);
  182.       if (dotRVSq > 0) {
  183.         // Add specular light
  184.         out.r += objects.get(id).ks.r*dotRVSq;
  185.         out.g += objects.get(id).ks.g*dotRVSq;
  186.         out.b += objects.get(id).ks.b*dotRVSq;
  187.       }

  188.       // Recursively get the bounce light
  189.       if (objects.get(id).reflects && depth < 3) {
  190.         PVector direction = new PVector(intVec.x+refVec.x, intVec.y+refVec.y, intVec.z+refVec.z);
  191.         Color reflection = rayTrace(intVec, direction, depth+1);
  192.         out.r += reflection.r*objects.get(id).ks.r;
  193.         out.g += reflection.g*objects.get(id).ks.g;
  194.         out.b += reflection.b*objects.get(id).ks.b;
  195.       }
  196.     }
  197.   }
  198.   return out;
  199. }
It would make sense to return an array instead of an ArrayList, since the list of control points won't change.
Ah, true, thanks. I originally wrote this in C++ (translating to Processing) and now I have a habit of not returning arrays.