[Haskell-cafe] Glome.hs-0.3 (bounding objects, heterogeneous
jsnow at cs.pdx.edu
Mon Apr 28 18:49:59 EDT 2008
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...
>> 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
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
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.
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 <jsnow at cs.pdx.edu> 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.
More information about the Haskell-Cafe