Page 1 of 3 123 LastLast
Results 1 to 10 of 25

Thread: C++ question on "clean code design"

  1. #1
    Member Contributor
    Join Date
    Oct 2000
    Location
    Pittsburgh, PA
    Posts
    52

    C++ question on "clean code design"

    So there's no OpenGL problem here, but many of you I know are great C++ programmers, so here's my question:

    Say I have a base class "Geometry" and two derived classes "Sphere" and "Cylinder". (This is a simplified version of how I have collision detection/reaction working right now).

    Okay, so I have a vector of a bunch of these objects. So I drop them all in but have to cast them to their base class "Geometry" since it's a vector of <Geometry *>.

    Now, each game loop I'm testing items in the vector against other for collisions. But there are different tests to do depending on what kind of geometry they are. Sphere-Sphere collisions are different from Sphere-Cylinder, etc.

    So I need to know what kind of objects they are, so I'm using RTTI's typeid(). But I'm told basing code logic on RTTI or dynamic_cast is poor design, and I agree.

    Is there a cleaner way that anyone can think of to do this? If my explanation isn't clear enough I can post some code.

  2. #2
    Senior Member Regular Contributor
    Join Date
    Mar 2002
    Posts
    103

    Re: C++ question on "clean code design"

    What you want to do is known as "double dispatching". You want to call the appropriate version of some virtual function based on the types of two objects. Look at the "visitor" design pattern. I use this for my collision detection and it works nicely.

  3. #3
    Senior Member OpenGL Pro sqrt[-1]'s Avatar
    Join Date
    Jun 2002
    Location
    Australia
    Posts
    1,000

    Re: C++ question on "clean code design"

    The visitor design pattern in this case would look like this:

    class Sphere;
    class Cylinder;

    class Geometry
    {
    //General collision
    virtual bool TestCollision(Geometry *)=0;

    //Specific collisions
    virtual bool TestCollisionSphere(Sphere *)=0;
    virtual bool TestCollisionCylinder(Cylinder *)=0;
    }

    class Sphere : public Geometry
    {
    bool TestCollision(Geometry *g){
    return g->TestCollisionSphere(this);
    }

    //Specific collisions
    bool TestCollisionSphere(Sphere *){
    //Code for sphere-sphere collision
    }
    bool TestCollisionCylinder(Cylinder *){
    //Code for sphere-cylinder collision
    }
    }

    class Cylinder : public Geometry
    {
    bool TestCollision(Geometry *g){
    return g->TestCollisionCylinder(this);
    }

    //Specific collisions
    bool TestCollisionSphere(Sphere *){
    //Code for cylinder-sphere collision
    }
    bool TestCollisionCylinder(Cylinder *){
    //Code for cylinder-cylinder collision
    }
    }


    Then to simply test the collision between two Geometry objects, simply call
    geometryArray[0]->TestCollision(geometryArray[1]);

    Edit: Forgot the virtuals..

    [This message has been edited by sqrt[-1] (edited 06-20-2002).]

  4. #4
    Member Contributor
    Join Date
    Oct 2000
    Location
    Pittsburgh, PA
    Posts
    52

    Re: C++ question on "clean code design"

    Thanks gumby, thanks sqrt[-1], that's exactly the sort of solution I was looking for to make my code more "elegant"... We must have elegant code nowadays, mustn't we?

    Heh heh. It'll certainly clean things up on my end. Thanks again guys!

  5. #5
    Senior Member Regular Contributor
    Join Date
    Jan 2002
    Posts
    292

    Re: C++ question on "clean code design"

    I prepared a simple sample of base classes... Check it out.
    http://a502j271.tower.wayne.edu/BaseClass.zip

    I hope it helps.

  6. #6
    Senior Member Regular Contributor nickels's Avatar
    Join Date
    Feb 2000
    Location
    Colorado
    Posts
    284

    Re: C++ question on "clean code design"

    I would have to argue that using the dynamic cast is less bad than the answer proposed by i.

    Reason:

    You should avoid using dynamic cast when possible, but there are appropriate times. When you implement a container (which your geometry array is) you have to allow casting to recover the base type, and this is an appropriate use of dynamic cast. Don't let other people tell you never to use it; they probably don't know how, that is why they tell you that.

    The i solution has problems, because to add a new derived class you have to go edit every other derived class. This violates extensibility in a very bad way.

    Remember, don't believe everything people tell you; whoever told you dynamic cast is poor design is ignorant likely ignorant. This is the same phenomenon as when
    the baptists (or muslims) say you shouldn't dance because it will corrupt you. It is the product of weak-mindedness.

    From Bjarney Soupstrap:
    "..(use of dynamic cast) allows us to wrap a concrete type in a polymorphic type, say for transmission through an object I/O system (or container) and the unwrap the concrete type later."

    [This message has been edited by nickels (edited 06-20-2002).]

  7. #7
    Member Contributor
    Join Date
    Oct 2000
    Location
    Pittsburgh, PA
    Posts
    52

    Re: C++ question on "clean code design"

    The plot thickens...
    but I enjoy different opinions on things. I see...

  8. #8
    Senior Member OpenGL Pro sqrt[-1]'s Avatar
    Join Date
    Jun 2002
    Location
    Australia
    Posts
    1,000

    Re: C++ question on "clean code design"

    nickels, Yes I do agree that dynamic casting has a place in code. (weither or not this belongs in this case is up to debate)

    Just out of couriousity, how would you add a new collision type like Box? It seems to me you would need to add box-box collision, box-sphere collision and box-cylinder collision. The logical place to put these new methods would be in the Box, Sphere and Cylinder classes. Since you are updating the other derived methods you may as well insert a TestCollisionBox into the Geometry class.

    The other advantage of making the base class contain abstract methods for collision is that and new descendant class MUST implement all the collision types to be valid (or you will get a compile error) This prevents you accedently "forgetting" to implement a collision against two different types of geometry.

    But I'm curious nickels, how would you implement this problem?

    Edit: In the above code I origionaly forgot the virtuals, OK now

    [This message has been edited by sqrt[-1] (edited 06-20-2002).]

  9. #9
    Senior Member Regular Contributor nickels's Avatar
    Join Date
    Feb 2000
    Location
    Colorado
    Posts
    284

    Re: C++ question on "clean code design"

    i : I don't mean to put down on the usefullness of your solution, certainly. But as far as C++ coding conventions go I have the concerns above.

    Is there no way to truly abstract the TestCollision? Store the geometry in some way so that you really can test for a collision without even consulting the derived class. Can this function be implemented entirely in the base class by storing the vertices in the base class.

    Otherwise having a class heirarchy really doesn't help (at least in this respect). You are trying to milk polymorphism from things that don't really have anything in common, it seems.

    This is the direction I would head, though I would have to be more familiar with the internals.

    I admit, I am the critic without a solution....


    But, a word on why I don't like the above solution. A user should be able to implement a new derived class without knowing anything about the siblings. A derived class is simply an extension of the base interface and should not rely on siblings. Also, now the dependancies are extremelly circular among siblings, with everyone knowing about everyone else; very messy and not even possible in some languages (F90, for instance). Also, now everytime someone adds a new class the entire library has to be recompiled. If your code was a library and users were supposed to be able to extend your code by creating their own geometry types, it wouldn't work because they would have to have your source to add these collision functions to the siblings and they would have to compile your code.

    There has to be a better model for this. However, sometimes what works is the best design (especially for home projects).

    [This message has been edited by nickels (edited 06-21-2002).]

  10. #10

    Re: C++ question on "clean code design"

    Unfortunately, I don't think a fully generic solution could be implemented using just geometry, unless you wanted to force all derived geometry classes to provide a hyperquadric representation of themselves (or something similar) to the base class, so all collision tests could be handled in the same way.

    However, you could generate a completely abstracted TestCollision with ray-casting; however, you can't guarantee that collisions will always be detected.

    The Geometry class would look something like:

    Code :
    enum { NO, MAYBE, YES };
     
    class Geometry {
    public:
      static bool TestCollision(Geometry*, Geometry*);
    protected:
      virtual void GenerateRays(std::vector<Ray>&amp;) = 0;
      virtual void RayIntersection(const std::vector<Ray>&amp;, std::vector<float>&amp;) = 0;
      virtual unsigned int TestIntersection(const std::vector<Ray>&amp;, const std::vector<float>&amp;) = 0;
    };
    The pseudo-code for TestCollision would look like:

    Code :
    bool TestCollision(A, B) {
      status <- MAYBE
      while (status==MAYBE) {
        rayVector <- A.GenerateRays;
        floatVec <- B.RayIntersection(rayVector);
        status <- A.TestIntersection(rayVector, floatVec);
      }
      if (status==NO) return false;
      else return true;
    }
    GenerateRays generates a number of random rays from the piece of geometry (the number of rays generated can vary from class to class), RayIntersection computes the distance from the RayOrigin to the closest intersection point of the geometry, and TestIntersection determines if the nearest intersection point with a different geometry is also inside the current geometry.

    However, this solution will probably be noticeably slower than special-casing every type of geometry you create, since you'll want to perform a number of ray/geometry intersection tests to ensure that you aren't undersampling (and returning false negatives).

Page 1 of 3 123 LastLast

Similar Threads

  1. GLSL design & glVertexAttribPointer question.
    By Red_Riot in forum OpenGL: GLSL
    Replies: 7
    Last Post: 03-10-2011, 03:46 PM
  2. How to clean up
    By LLM in forum OpenGL: GLSL
    Replies: 3
    Last Post: 05-12-2010, 09:23 PM
  3. Design Question: How to handle rendering order?
    By Jengu in forum OpenGL: Basic Coding
    Replies: 2
    Last Post: 04-11-2006, 08:18 PM
  4. Engine design theory question
    By jefftkd in forum OpenGL: Advanced Coding
    Replies: 5
    Last Post: 03-01-2002, 03:29 PM
  5. Visual C++ Design Question
    By ogre in forum OpenGL: Basic Coding
    Replies: 2
    Last Post: 03-30-2000, 12:55 AM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Proudly hosted by Digital Ocean