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