|  | ||
| 
 | 
Alright, here's a usage example. A guy with a spear versus a dragon,and a simulation built up around that.Untested and full of nonexistent functions:+++++++++++++++++++++++++++++++++++++++++++++++++-- Object-object hit test, two parameters. By default, objects don't collide.local Hit = class.New("Multimethod", 2)Hit:Define(function()return falseend, nil, nil) -- nil: accept anything-- Post-hit response, ditto. No-op by default.local Response = class.New("Multimethod", 2)Response:Define(function() end) -- omitting the nils-- Hit test for a thrown spear and any kind of sphere (I dunno, floating rocks or something). If there's-- an intersection, it also returns an information packet.Hit:Define(function(sphere, spear)-- Sphere-ray intersection at time t: (c - (p + t * v)) . (c - (p + t * v)) = r^2local diff = sphere.c - spear.plocal t = SolveQuadratic(spear.v * spear.v, -2 * diff * spear.v, diff * diff - sphere.r * sphere.r)if t thenlocal point = spear.p + t * spear.vreturn true, { p = point, n = Normalize(point - sphere.c), t = t }endreturn falseend, "Sphere", "Spear") -- if not nil, name or unique id used to instantiate class, returned by Type (see below)-- Response to the above. Note that since the multimethod only knows about two parameters, "how",-- the collision info from the hit test, will be ignored when deciding the method dispatch.Response:Define(function(sphere, spear, how)spear:BounceOff(how.p, how.n, sphere:SurfaceProperties())end, "Sphere", "Spear")-- Bubbles? In this case, "SquishySphere" is derived from "Sphere", so this gets called instead when-- our spear meets the squishier sort of sphere.Response:Define(function(Sphere, Spear, how)PlaySound("squish")Spear:GetStuck(how.p, how.n)end, "SquishySphere", "Spear")-- If anything else ran into a sphere (normal, squishy, other), blow it up!Response:Define(function(Sphere, OtherThing)Sphere:Explode()end, "Sphere", nil)-- Hit test against a composite thing, in this case a dragon. If it hits a part, include-- that part along with the usual collision info.Hit:Define(function(dragon, spear)for part in dragon:GetParts() dolocal bHit, arg = Hit(part, spear)if bHit thenreturn true, { part = part, info = arg }endendend, "Dragon", "Spear")-- Another composite case, in this case with one of the built-in types as a parameter.Hit:Define(function(terrain_things, spear)for _, thing in ipairs(terrain_things) dolocal bHit, arg = Hit(thing, spear)if bHit thenreturn true, argendendreturn falseend, "table", "Spear")-- The spear struck a dragon.Response:Define(function(dragon, spear, how)local name = how.part:GetName()-- Spear to the head: dragon flinches and takes damage.if name == "head" thenhow.part:KnockBack(spear.v)dragon:Hurt(spear:GetPower())-- Cripple the wing if it's not already in tatters.elseif name == "wing" thenif how.part:IsIntact() thenhow.part:Maim()end-- Thick scales everywhere else.elsespear:BounceOff(how.info.p, how.info.n, how.part:SurfaceProperties())endend, "Dragon", "Spear")-- If the spear hits something else, it just drops dead.Response:Define(function(object, spear)PlaySound("clank")spear.v = Vec3(0, 0, 0)end, nil, "Spear")---- etc. etc. etc. All kinds of other interactions---- Updates objects, and updates any that run into each other. Owing to laziness, objects are assumed-- to be ordered so that the arguments are always passed in the order listed in the above signatures.function RunDynamics (objects, dt)UpdateObjects(objects, dt)for i = 1, #objects dofor j = i + 1, #objects dolocal bHit, how = Hit(objects[i], objects[j])if bHit thenResponse(objects[i], objects[j], how)endendendend+++++++++++++++++++++++++++++++++++++++++++++++++Comments on the implementation itself:callops.IsCallable is just this:int IsCallable (lua_State * L){lua_pushboolean(L, lua_isfunction(L, 1) || luaL_getmetafield(L, index, "__call") != 0);return 1;}Right now, this only supports single inheritance, so class.Supers is actually only returning one value here... I'll probably generalize thateventually with an iterator up the hierarchy linearization. The three class functions just have to behave like these:function IsType (item, what)assert(type ~= nil)local itype = item ~= nil and WeakTableOfClassInstances[item] or type(item)repeatif itype == what thenreturn trueenditype = Superclasses[itype]until itype == nilreturn falseendfunction Supers (type)assert(type ~= nil)return Superclasses[type]endfunction Type (item)if item ~= nil thenlocal itype = WeakTableOfClassInstances[item]-- Is an instance.if itype ~= nil thenreturn itype, trueendend-- Not an instance.return type(item), falseend