Andrew Coppin wrote:
Well, for example, Haskell doesn't have hetrogenous lists - which are trivial in any OOP language. That's quite awkward to get around. Also, both spheres and cylinders have a "radius" property, but then you end up with name clashes. Again, a non-issue in OOP languages. [I gather there's some new GHC language extension to partially solve this one - but only when types are statically known. In the general case, you'd have to write a "radius class" and define instances... yuck!]

It's funny you mention that, those are actually problems I ran into, but (having learned my lesson the hard way in Ocaml), I decided not to try and force the language to do things my way, but instead try to do things in a way that Haskell makes easy. For instance, I started out by making each primitive a separate type (Sphere, Triangle, etc...), and then made a type class that defines a ray intersection method (and a few other odds and ends), but then I realized that I can't do something as simple as store a list of primitives (or if there is in fact a way, I'm not aware of it).
Instead, I made a single large sum type with all my primitives:
so that now I can easily make a list of primitives. (In fact, that's what a Group is.) I also gave up on using named fields, since coming up with a separate name for each one gets ugly: instead of radius, you have sphere_radius, cylinder_radius, cone_radius disc_radius, etc...

All of which works, but now it's a pain to add new primitives. And *all* supported primitives must be defined in a single monolithic module. And you can't reuse spheres as bounding volumes without either reimplementing them or loosing type safety.
The part about spheres and bounding spheres is actually not so much of a problem: I implemented a general "Bound" primitive that can bound any primitive with any other. In general, you would use a simple object like a sphere or box as the left argument and a complex object as the right argument.

data Solid =  ...
           | Bound Solid Solid
             ...

and then to intersect the "Bound" object, you first do a shadow test on the left object before testing the right object:

rayint :: Solid -> Ray -> Flt -> Texture -> Rayint
rayint (Bound sa sb) r d t =
let (Ray orig _) = r
in if inside sa orig || shadow sa r d
   then rayint sb r d t
   else RayMiss

Some kinds of primitives aren't likely to work well for bounding since they don't have a well-defined inside and outside (like triangles and discs), but I'd rather provide maximum flexibility and assume that users know how to use it sensibly.

http://www.haskell.org/haskellwiki/Glome_tutorial#Bounding_Objects

As for the maintenance issues, that is still a problem. It would be nice to split all the individual primitives into their own modules.

Sebastian Sylvan wrote:

On 4/27/08, Jim Snow <[EMAIL PROTECTED]> wrote:
> For instance, I started out by making each primitive a separate type
> (Sphere, Triangle, etc...), and then made a type class that defines a
> ray intersection method (and a few other odds and ends), but then I
> realized that I can't do something as simple as store a list of
> primitives (or if there is in fact a way, I'm not aware of it).

You can, by using a "wrapper" type which wraps up any instance of the
Intersect class:

data Intersectable = forall a. Intersect a => MkIntersectable a

For convenience you probably want to instantiate this wrapper in the
class itself:

instance Intersect Intersectable where
  rayIntersection (MkIntersectable x) ray = rayIntersection x ray
  boundingVolume (MkIntersectable x) = boundingVolume x
  -- etc...

Now you can stick Intersectables in lists etc.

I think that sounds like what I ought to be doing.

-jim


_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to