Sign in to follow this  
  • entries
    45
  • comments
    44
  • views
    49596

About this blog

Info on game developement

Entries in this blog

Norman Barrows

1. you can draw a million animated plants with no culling using instancing with static instance data. dynamic data is another story ( 60 vs 23 FPS i7 6700K 4Ghz single thread, GTX1080 10-20% overclocked, vsync on at 60Hz, DX9 hardware instancing, single static vs single dynamic buffer, LOCK_WRITEONLY). Note: I have yet to test LOCK_DISCARD and round robin use of dynamic buffers.

scrndmp6.jpg

2. Good looking plants are usually high poly. No duh!

screen.png

3. Low poly plants usually don't look good. No duh!

low-poly-plant-312-3d-model-low-poly-obj-3ds-fbx-blend-dae-x3d.jpg

I'll upload a shot from Skyrim SE on Ultra when I can. believe it or not its even a bit more basic than this!

4. A good high resolution ground texture works just as well if not better than detail mapping. unless you're short on texture ram.

5. Double sampling a ground texture at two different scales and blending the results can be used to hide repeating patterns in tiled seamless ground textures. But this only works with some image textures, like small rocks and dirt, not textures with images that should be a specific size (such as leaves on the ground). It also results in two super-imposed images, like a double exposure on a photo. adjusting the sample scaling can lead to some decent (or even better) results. Double sampling does not work well with textures that contain a very high degree of detail, such as individual grains of sand or individual blades of grass.

6. sets of seamless ground textures, with textures assigned at random to ground quads can really help break up repeating tiled ground texture patterns.

< Images: single tile and tile set ground textures - dirt or scrub - 3pv - long range >

7. one problem with low poly plant meshes that use invisible quads is that often the bottom reveals that its just a clear quad with an image on it. This is especially true when the bottom of the image is wide. a trick i discovered to hide this is to lay a quad almost flat, say 20 degrees from horizontal, then place a bunch of then sticking out around the bottom of the plant. This hides the bottom edges of the quads that stand more upright.

< images: quad plant with wide image at bottom, quad based plant with low quads to hide the base >

8. another problem with quad based plant meshes is the tops can give away the fact its just a quad - especially when viewed from above. When testing meshes, i noticed some textures looked great from above, while with others you could tell it was a quad. Turns out its the amount of background you can see though the image. if the image is too solid, it gives away the quads. to test this, i loaded a texture that didn't look good into paint.net, and did a global floodfill with the background color, and then adjusted the tolerance slider, which make the image more or less "see-through". I reduced the amount of foreground by about 50%. and it worked! What this does is make the image more "see-though" - IE fewer pixels pass alpha test and get drawn. This makes the qauds less noticeable or totally unnoticeable - with no twisting, bending, muti-quad sections, etc. just a bunch of flat squares. Needless to say thinning out the image makes the plant look thinner, so you may want to add some more quads to the mesh to thicken things back up. I'm currently getting very good results with a 112 vertex mesh. But i plan to redo it using the golden ratio.

< images: quad based plant with full texture near top. quad based image with see-thorough texture. Quad based image - "Golden Ratio" >

9. not all textures look good on all meshes. Some textures just don't look good no matter what mesh you try. Just give up and go try the next texture.

< image: any plant or grass texture except 105 or 126 on mesh #4 >

10. generally speaking, textures with long thin spindly stuff works better with quads. This is because its more "see-though".

< image - any thin scrub plant - they rock! >

11. by hiding the bottom with nearly horizontal quads, and using sufficiently see-though textures, quad based low poly meshes looks to be a viable method for rendering good looking plants en mass very quickly.

scrndmp36.jpg

scrndmp29.jpg

< image: plant1x.x with just about any grass or plant texture >

12. different textures look good with different meshes. they look great on some, and just plain weird on others. You really need to try all the combos to discover the best ones.

< images: mesh 4, texture 458. mesh 567, texture 458 >

13. "massaging" textures by adjusting hue, saturation, brightness, contrast, and input and output levels can make a BIG difference. Reducing brightness and increasing contrast slightly accentuates shadows, making the texture appear more "3D", similar to bump mapping. Reducing saturation can result in more realistic texture colors. As always - it depends on the image you start with. But these types of adjustments can turn "just another" texture into a texture that really "pops".

< images: grass22x source, un-retouched. grass22x.bmp >

14: color palette

It seems that a uniform color palette is necessary for the correct overall feel of the scene. for example, right now in testing i'm drawing ~10,000 non-instanced temperate savanna grass plants around the camera, but they are just 22 kinds of plants at random, just what i came up with as meshes and textures that happened to look good together ( i excluded the day-glow green tropical textures ). there'e everything from weeds that look like dead corn to bushes made from branches of acacia trees (not bad looking actually ). redone with a more uniform look for both the ground texture and the plant textures (all long de-saturated light green wild grass) i suspect the results will be impressive. Note the use of uniform ground cover colors in this series of videos:

15: multiple mesh layers and alpha blending can be used to get a "3D" look:

http://docs.cryengine.com/display/SDKDOC2/How+to+Create+Layered+Moss

pic_21.jpg?version=1&modificationDate=1400232277000&api=v2

more screenshots to follow....

Norman Barrows

Use of the Golden Ratio when modeling plants

Found this on YouTube:

from:

https://en.wikipedia.org/wiki/Golden_ratio


Under the subtopic Nature:

"Adolf Zeising... [color=rgb(37,37,37)][font=sans-serif]

found the golden ratio expressed in the

[/font][/color]arrangement of parts[color=rgb(37,37,37)][font=sans-serif]

such as leaves and branches along the stems of plants and of

[/font][/color]veins[color=rgb(37,37,37)][font=sans-serif]

in leaves."

[/font][/color]

and under the subtopic Geometry:

[color=rgb(37,37,37)][font=sans-serif]

" at longitudes spaced by a golden section of the circle, i.e. 360?/

[/font][/color][color=rgb(37,37,37)][font='Nimbus Roman No9 L']

?

[/font][/color][color=rgb(37,37,37)][font=sans-serif]

? 222.5? "

[/font][/color]

Happy modeling! :)

Norman Barrows

thoughts on fix-your-timestep and the spiral of death:

in the fix-your timestep algo its generally assumed that on average ET will be < DT.

with no ET cap, when accumulator >= 2*DT, fix-your-timestep will update twice before rendering (IE it drops a frame).

while this is only an issue with slow software, it can be a game-breaker.

the typical approach is to cap ET to some max value. This guarantees that update will run no more than N times in a row before the next render, where N = (accumulator+ET) / DT (rounded down).

but accumulator can be any value from 0 to DT. So it would probably be more accurate to cap accumulator, not ET.

But what cap value?

It depends on how many frames you're willing to drop.

To never drop frames, the cap is DT.

this would lead to a slightly different update:

instead of:accumulator+=ETwhile (accumulator>=DT) { accumulator-=DT update_all }
you get something like:accumulator+-ETif (accumulator >=DT) { accumulator-=DT update_all if (accumulator>=DT) { // we've hit spiral of death and are updating more than once per render! } }
But now, what do you do if you hit the spiral of death?


Well, we don't want to drop a frame. From the point of view of the player, when we drop a frame, update starts running faster at the same time that render slows down. This makes it hard for the player to react in time.

So we don't want to call update again, we want to render again first. Yes, this puts render in lockstep with update temporarily - same as any ET cap.

But what do we do with accumulator? Leave it the way it is? It's already full or overflowing. and next update, we'll just add more ET to it, making it even more over-full. So that won't work. Setting it to DT (just full) will guarantee an update after the next render. But if you just set it to zero, at worst, render suddenly gets its act back together and ET drops back below DT, and you render a few frames before the next update_all. So setting it to zero seems pretty safe.

This leads to the following:accumulator+=ETif (accumulator >= DT) { accumulator-=DT update_all if (accumulator>=DT) { // accumulator>=DT*2 spiral of death / frame drop! // clear accumulator, and don't call update_all. accumulator=0 } }
This algo implements fix-your-timestep with an accumulator cap of DT, and will never drop a frame. When ET goes high, it reverts to lockstep behavior of one render, one input, one update per cycle - which degrades gracefully under heavy load - just what you need when ET is high. Note that by comparison, your typical ET cap will only revert to lockstep behavior of the form: one render, one input, N updates per cycle, where N = (accumulator+ET) / DT (rounded down). With a low enough ET cap (specifically: capping accumulator to DT, or ET to DT-accumulator... its the same thing), similar behavior can be obtained with an ET cap and the more common "while (accumulator>=DT)" form of the algo.


Happy coding! :)

Norman Barrows

Increasing ET accuracy in fix-your-timestep

The typical implementation of fix-your-timestep looks something like this:while ! quitgame { start_timer render ET = elapsed_time process_input update_all(ET) }
But to run physics at a constant speed, what you really want is the elapsed time since the beginning of the last update - not just the time spent rendering.


Which would lead to something like this:start_timerwhile ! quitgame { render process_input ET = elapsed_time start_timer update_all(ET) }
Here ET captures the total elapsed time since the last update - except for the time required to start the timer - IE saving the current value of QueryPerformanceCounter.


Happy coding! :)

Norman Barrows

The Grand List Of Console Role Playing Game Cliches

from

http://project-apollo.net/text/rpg.html



There are few people who love a good console RPG more than I. Games like Final Fantasy, Grandia, and Skies of Arcadia set a standard of majesty and wonder and immersion that American game designers are challenged to match. And yet, as I play the latest masterpiece to come out of Japan I sometimes can't help the feeling that, somehow, I've seen it all before... WARNING! There are spoilers in here for many popular CRPGs. I mean, duh.
Note that the list is closed to new entries -- this is as cliche-ridden as it's going to get. But other than that, if you have something to say about the list, [email="markbriansachs@gmail.com"]this[/email] is the address to write to.


  1. Sleepyhead Rule
    The teenaged male lead will begin the first day of the game by oversleeping, being woken up by his mother, and being reminded that he's slept in so late he missed meeting his girlfriend.
  2. "No! My beloved peasant village!"
    The hero's home town, city, slum, or planet will usually be annihilated in a spectacular fashion before the end of the game, and often before the end of the opening scene.
  3. Thinking With The Wrong Head (Hiro Rule)
    No matter what she's accused of doing or how mysterious her origins are, the hero will always be ready to fight to the death for any girl he met three seconds ago.
  4. Cubic Zirconium Corollary
    The aforementioned mysterious girl will be wearing a pendant that will ultimately prove to be the key to either saving the world or destroying it.
  5. Logan's Run Rule
    RPG characters are young. Very young. The average age seems to be 15, unless the character is a decorated and battle-hardened soldier, in which case he might even be as old as 18. Such teenagers often have skills with multiple weapons and magic, years of experience, and never ever worry about their parents telling them to come home from adventuring before bedtime. By contrast, characters more than twenty-two years old will cheerfully refer to themselves as washed-up old fogies and be eager to make room for the younger generation.
  6. Single Parent Rule
    RPG characters with two living parents are almost unheard of. As a general rule, male characters will only have a mother, and female characters will only have a father. The missing parent either vanished mysteriously and traumatically several years ago or is never referred to at all. Frequently the main character's surviving parent will also meet an awkward end just after the story begins, thus freeing him of inconvenient filial obligations.
  7. Some Call Me... Tim?
    Good guys will only have first names, and bad guys will only have last names. Any bad guy who only has a first name will become a good guy at some point in the game. Good guys' last names may be mentioned in the manual but they will never be referred to in the story.
  8. Nominal Rule
    Any character who actually has a name is important in some way and must be sought out. However, if you are referred to as a part of a posessive noun ("Crono's Mom") then you are superfluous.
  9. The Compulsories
    There's always a fire dungeon, an ice dungeon, a sewer maze, a misty forest, a derelict ghost ship, a mine, a glowing crystal maze, an ancient temple full of traps, a magic floating castle, and a technological dungeon.
  10. Luddite Rule (or, George Lucas Rule)
    Speaking of which, technology is inherently evil and is the exclusive province of the Bad Guys. They're the ones with the robots, factories, cyberpunk megalopolises and floating battle stations, while the Good Guys live in small villages in peaceful harmony with nature. (Although somehow your guns and/or heavily armed airships are exempted from this.)
  11. Let's Start From The Very Beginning (Yuna Rule)
    Whenever there is a sequel to an RPG that features the same main character as the previous game, that character will always start with beginner skills. Everything that they learned in the previous game will be gone, as will all their ultra-powerful weapons and equipment.
  12. Poor Little Rich Hero (Meis Rule)
    If the hero comes from a rich and powerful family, it will have fallen on hard times and be broke and destitute by the time the game actually starts.
  13. The Higher The Hair, The Closer To God (Cloud Rule)
    The more outrageous his hairstyle, the more important a male character is to the story.
  14. Garrett's Principle
    Let's not mince words: you're a thief. You can walk into just about anybody's house like the door wasn't even locked. You just barge right in and start looking for stuff. Anything you can find that's not nailed down is yours to keep. You will often walk into perfect strangers' houses, lift their precious artifacts, and then chat with them like you were old neighbors as you head back out with their family heirlooms under your arm. Unfortunately, this never works in stores.
  15. Hey, I Know You!
    You will accumulate at least three of these obligatory party members:

    • The spunky princess who is rebelling against her royal parent and is in love with the hero.
    • The demure, soft-spoken female mage and healing magic specialist who is not only in love with the hero, but is also the last survivor of an ancient race.
    • The tough-as-nails female warrior who is not in love with the hero (note that this is the only female character in the game who is not in love with the hero and will therefore be indicated as such by having a spectacular scar, a missing eye, cyborg limbs or some other physical deformity -- see The Good, The Bad, And The Ugly Rule.)
    • The achingly beautiful gothy swordsman who is riven by inner tragedy.
    • The big, tough, angry guy who, deep down, is a total softy.
    • The hero's best friend, who is actually much cooler than the hero.
    • The grim, selfish mercenary who over the course of the game learns what it means to really care about other people.
    • The character who is actually a spy for the bad guys but will instantly switch to your side when you find out about it.
    • The weird bonus character who requires a bizarre series of side quests to make them effective (with the ultimate result that no player ever uses this character if it can be avoided.)
    • The nauseatingly cute mascot who is useless in all battles.


  • Hey, I Know You, Too!
    You will also confront/be confronted by at least three of these obligatory antagonists:

    • The amazingly good-looking and amazingly evil long-haired prettyboy who may or may not be the ultimate villain.
    • The villain's loyal right-hand man, who comes in two versions: humorously incompetent or annoyingly persistent.
    • The villain's attractive female henchman, who is the strongest and most competent soldier in the army but always lets the party escape because she's, yes, fallen in love with the hero.
    • Your former ally who supposedly "died" and was forgotten about, until much later in the game when he/she shows up again on the villain's side and full of bitterness.
    • The irritatingly honorable foe whom you never get to kill because, upon discovering the true nature of his superiors, he either nobly sacrifices himself or joins your party.
    • The insane clown or jester who will turn out to be surprisingly difficult to subdue.
    • The mad scientist who likes creating mutated creatures and powerful weapons 'cause it's fun (and also handy if uninvited adventurers show up.)
    • The adorably cute li'l creature or six year old child who fights you and, inexplicably, kicks your butt time after time.


    • Hey, I Know You, Three!
      Furthermore, expect to encounter most of the following obligatory non-player chararcters (NPCs):

      • The townsperson or crewmember who wanders aimlessly in circles and never quite gets where he is going.
      • Hilariously incompetent or cowardly soldiers.
      • The NPC who has a crush on another NPC and can't quite work up the nerve to tell him or her, so instead tells every other person who wanders by about it at great length.
      • A group of small children playing hide-and-seek.
      • The wise and noble captain/king/high priest.
      • The wise and noble captain/king/high priest's splutteringly evil second-in-command. Nobody, including the hero, will notice the second's constant, crazed scheming until the moment when he betrays everyone to the forces of badness.
      • The NPC who is obsessed with his completely mundane job and witters on endlessly about how great it is. He's so thrilled by it that he wants to share it with everyone he sees, so given a quarter of a chance he'll make you do his job for him.
      • The (adult) NPC who has nothing better to do than play kids' games with passersby.
      • The group of young women who have formed a scarily obsessive fan club for one of your female party members.


      • Crono's Complaint
        The less the main character talks, the more words are put into his mouth, and therefore the more trouble he gets into through no fault of his own.
      • "Silly Squall, bringing a sword to a gunfight..."
        No matter what timeframe the game is set in -- past, present, or future -- the main hero and his antagonist will both use a sword for a weapon. (Therefore, you can identify your antagonist pretty easily right from the start of the game just by looking for the other guy who uses a sword.) These swords will be far more powerful than any gun and often capable of distance attacks.
      • Just Nod Your Head And Smile
        And no matter how big that big-ass sword is, you won't stand out in a crowd. Nobody ever crosses the street to avoid you or seems to be especially shocked or alarmed when a heavily armed gang bursts into their house during dinner, rummages through their posessions, and demands to know if they've seen a black-caped man. People can get used to anything, apparently.
      • Aeris's Corollary
        Just as the main male character will always use a sword or a variant of a sword, the main female character will always use a rod or a staff of some sort.
      • MacGyver Rule
        Other than for the protagonists, your choice of weapons is not limited to the prosaic guns, clubs, or swords. Given appropriate skills, you can cut a bloody swath across the continent using gloves, combs, umbrellas, megaphones, dictionaries, sketching tablets -- you name it, you can kill with it. Even better, no matter how surreal your choice of armament, every store you pass will just happen to stock an even better model of it for a very reasonable price. Who else is running around the world killing people with an umbrella?
      • O Brother, Where Art Thou? (Melfice Rule)
        If the male hero has an older sibling, the sibling will also be male and will turn out to be one of the major villains. If the hero has a younger sibling, the sibling will be female and will be kidnapped and held hostage by the villains.
      • Capitalism Is A Harsh Mistress
        Once you sell something to a shopkeeper, he instantly sells it to somebody else and you will never see the item again no matter what.
      • Dimensional Transcendence Principle
        Buildings are much, much larger on the inside than on the outside, and that doesn't even count the secret maze of tunnels behind the clock in the basement.
      • Local Control Rule
        Although the boss monster terrorizing the first city in the game is less powerful than the non-boss monsters that are only casual nuisances to cities later in the game, nobody from the first city ever thinks of hiring a few mercenaries from the later cities to kill the monster.
      • Nostradamus Rule
        All legends are 100% accurate. All rumors are entirely factual. All prophecies will come true, and not just someday but almost immediately.
      • IDKFA
        The basic ammunition for any firearms your characters have is either unlimited or very, very easy to obtain. This will apply even if firearms are extremely rare.
      • Indestructible Weapon Rule
        No matter how many times you use that sword to strike armored targets or fire that gun on full auto mode it will never break, jam or need any form of maintenance unless it is critical to the story that the weapon breaks, jams or needs maintenance.
      • Selective Paralysis
        Your characters must always keep both feet on the ground and will be unable to climb over low rock ledges, railings, chairs, cats, slightly differently-colored ground, or any other trivial objects which may happen to be in their way. Note that this condition will not prevent your characters from jumping from railroad car to railroad car later in the game.
      • Bed Bed Bed
        A good night's sleep will cure all wounds, diseases, and disabilities, up to and including death in battle.
      • You Can't Kill Me, I Quit (Seifer Rule)
        The good guys never seem to get the hang of actually arresting or killing the bad guys. Minor villains are always permitted to go free so they can rest up and menace you again later -- sometimes five minutes later. Knowing this rule, you can deduce that if you do manage to kill (or force the surrender of) a bad guy, you must be getting near the end of the game.
      • And Now You Die, Mr. Bond! (Beatrix Rule)
        Fortunately for you, the previous rule also applies in reverse. Rather than kill you when they have you at their mercy, the villains will settle for merely blasting you down to 1 hit point and leaving you in a crumpled heap while they stroll off, laughing. (This is, of course, because they're already planning ahead how they'll manipulate you into doing their bidding later in the game -- see Way To Go, Serge.)
      • Zap!
        Most villains in RPGs possess some form of teleportation. They generally use it to materialize in front of the adventurers when they reach the Obligatory Legendary Relic Room and seize the goodies just before you can. The question "if the bad guy can teleport anywhere at any time, then why doesn't (s)he just zip in, grab the artifact, and leave before the adventurers have even finished the nerve-wracking puzzle on the third floor?" is never answered.
      • Heads I Win, Tails You Lose (Grahf Rule)
        It doesn't matter that you won the fight with the boss monster; the evil task he was trying to carry out will still get accomplished somehow. Really, you might as well not have bothered.
      • Clockwork Universe Rule
        No matter how hard you try to stop it, that comet or meteor will always hit the earth.
      • Fake Ending
        There will be a sequence which pretends to be the end of the game but obviously isn't -- if for no other reason than because you're still on Disk 1 of 4.
      • You Die, And We All Move Up In Rank
        During that fake ending, the true villain of the story will kill the guy you'd thought was the villain, just to demonstrate what a badass he (the true villain) really is. You never get to kill the fake villain yourself.
      • "What are we going to do tonight, Vinsfeld?"
        The goal of every game (as revealed during the Fake Ending) is to Save the World from an evil figure who's trying to take it over or destroy it. There is no way to escape from this formidable task. No matter whether the protagonist's goal in life is to pay off a debt, to explore distant lands, or just to make time with that cute girl in the blue dress, it will be necessary for him to Save the World in order to accomplish it. Take heart, though -- once the world gets sorted out, everything else will fall into place almost immediately.
      • Zelda's Axiom
        Whenever somebody tells you about "the five ancient talismans" or "the nine legendary crystals" or whatever, you can be quite confident that Saving the World will require you to go out and find every last one of them.
      • George W. Bush Geography Simplification Initiative
        Every country in the world will have exactly one town in it, except for the country you start out in, which will have three.
      • Fodor's Guide Rule
        In the course of your adventure you will visit one desert city, one port town, one mining town, one casino city, one magic city (usually flying), one medieval castle kingdom, one clockwork city, one martial arts-based community, one thieves' slum, one lost city and one sci-fi utopia. On the way you'll also get a chance to see the cave with rocks that glow from a natural energy source, the village populated with nonhuman characters, the peaceful village where everyone knows the latest news about the hero's quest (see Guy in the Street Rule), the snow village, the magical forest/lake/mountain, the shop in the middle of nowhere, the fantastic-looking place with lots of FMVs just showing your entrance, the subtropical jungle island populated by friendly natives, the annoying cavern maze, and a place -- any place -- that was destroyed in some past disaster.
      • Midgar Principle
        The capital of the evil empire is always divided into two sections: a lower city slum filled with slaves and supporters of the rebellion, and an upper city filled with loyal fanatics and corrupt aristocrats.
      • Not Invented Here
        Trade of technology will not exist. One place in the world will have all the techno-gadgets while all the others will be harvesting dirt.
      • Law of Cartographical Elegance
        The world map always cleanly fits into a rectangular shape with no land masses that cross an edge.
      • ?Quien Es Mas Macho? (Fargo Rule)
        Every powerful character you attempt to seek aid from will first insist upon "testing your strength" in a battle to the death.
      • We Had To Destroy The Village In Order To, Well, You Know The Rest (Selene Rule)
        No matter what happens, never call on the government, the church, or any other massive controlling authority for help. They'll just send a brigade of soldiers to burn your entire village to the ground.
      • Zidane's Curse (or, Dirty Pair Rule)
        An unlucky condition in which every major city in the game will coincidentally wind up being destroyed just after the hero arrives.
      • Maginot Line Rule
        It is easy to tell which city/nation is the next conquest of the Evil Empire: its streets are filled with citizens who brag that the Empire would never dare attack them, and would be easily defeated if it tried. (This smug nationalism always fails to take into account the Empire's new superweapon.)
      • Short Attention Span Principle
        All bookshelves contain exactly one book, which only has enough text on it to fill up half a page.
      • Planet of the Apes Rule
        All cities and countries have ancestors that were wiped out by their technological advances.
      • Insomnia Rule
        A "free stay at the inn" is never really free. Expect to be woken up in the middle of the night for a mandatory plot event.
      • The Bling-Bling Thing (Lemina Rule)
        No matter how much money and treasure you acquire, the greedy member of your party will never be satisfied and won't stop griping about the sorry state of the party's finances.
      • I Don't Like Gears Or Fighting
        There are always giant robots. Always.
      • Houdini's Postulate
        Anyone, whether they are in the player's party or not, who is placed in any kind of prison, fortress, cell, or detention block will escape immediately. Party members will be freed either by a small child they just happened to befriend earlier in the day or by an unexpected disaster that overcomes the enemy base, NPCs will be freed by the released party members, and villains will break out all by themselves because they're such badasses. Once a person has escaped from jail, no attempt will be made by the police to recapture them in the future.
      • Zeigfried's Contradiction
        Just because someone is weird doesn't mean they're important.
      • Natural Monopoly Rule
        No city will have more than two shops, unless it is crucial to the story that there be a hundred vendors which you must visit in order (see You Always Travel In The Right Circles.) All of these shops will sell the same goods for the same price.
      • But They Don't Take American Express
        Every merchant in the world -- even those living in far-off villages or hidden floating cities cut off from the outside world for centuries, even those who speak different languages or are of an entirely different species -- accepts the same currency.
      • Apathy Principle
        Your group is the only bunch of people trying to save the world. All other would-be heroes will either join your party or else turn out to be cowards and/or con men.
      • The Good, The Bad, And The Ugly Rule
        a. Any male character who is ugly, malformed, or misshapen is either evil or so moral, spiritual, and/or wise that it's a wonder no one's proposed him for sainthood yet.
        b. Any male character who has a physical disfiguration that doesn't seem to impede him (i.e. a prominent scar across the face or a bad eye) is evil, unless he is the male lead, since scars are cool and no other good guy can be as cool as the hero. An exception is made for characters who are clearly ancient, and therefore automatically not as cool as the young hero.
        c. Any female character who is ugly, malformed, mishapen, or physically disfigured is evil, since all good female characters are there to be potentially seduced by the male lead -- see Know Your Audience.
      • Henchman Quota (Nana, Saki, and Mio Rule)
        One of your antagonists will have three lovably incompetent stooges whom you fight over and over again. Although they're trusted with their boss's most important plans and equipment, they will screw up repeatedly, argue incessantly among themselves, blab secret information, and generally only come out victorious when their job was to be a diversion or a delaying tactic. A high point of the game will come when the True Villain reveals himself and you're able to convince the stooges you're all on the same side. They won't help you out any more successfully than they helped the antagonist, but at least you won't have to fight them any more.
      • Thousand Year Rule
        The Ancient Evil returns to savage the land every thousand years on the dot, and the last time it showed up was just about 999.9875 years ago. Despite their best efforts, heroes of the past were never able to do more than seal the Evil away again for the future to deal with (which brings up the question of just how exactly does this "sealing away" work anyway, but never mind.) The good news is that this time, the Evil will get destroyed permanently. The bad news is that you're the one who's going to have to do it.
      • Principle of Narrative Efficiency
        If the main villain (or the enemy you've been trying to kill for most of the game before he summons the real final villain) was ever defeated in the past by another group of adventurers, one of them will secretly be in your party and one of them will be the hero's father.
      • Ayn Rand's Revenge
        Outside the major cities, there is no government whatsoever. Of course, perhaps that explains why it's so difficult and dangerous to get anywhere outside the major cities.
      • First Law of Travel
        Anything can become a vehicle -- castles, cities, military academies, you name it -- so do not be alarmed when the stones of the ancient fortress you are visiting shake underfoot and the whole thing lifts off into the sky. As a corollary, anything is capable of flight if it would be cool, aeronautics or even basic physics be damned.
      • Second Law of Travel
        There will be only one of any non-trivial type of vehicle in the entire world. Thus, only one ocean-capable steamboat, only one airship, and so forth. Massive facilities will have been constructed all over the world to service this one vehicle.
      • Third Law of Travel
        The only way to travel by land between different areas of a continent will always be through a single narrow pass in a range of otherwise impenetrable mountains. Usually a palace or monastery will have been constructed in the pass, entirely filling it, so that all intracontinental traffic is apparently required to abandon their vehicles and go on foot up stairs and through the barracks, library and throne room to get to the other side. This may explain why most people just stay home. (In some cases a cave or underground tunnel may be substituted for the palace or monastery, but it will still be just as inconvenient with the added bonuses of cave-ins and nonsensical elevator puzzles.)
      • Fourth Law of Travel
        Three out of every four vehicles you ride on will eventually sink, derail or crash in some spectacular manner.
      • Fifth Law of Travel
        All vehicles can be driven or piloted by anyone. The main character just needs to find out where the bridge or steering wheel is, as he already knows all of the controls.
      • Sixth Law of Travel
        Nobody gets to own a cooler ride than you. If you ever do see a cooler vehicle than the one you've got now, at some point before the end of the game you will either take over this vehicle, get something even bigger and better, or else see it destroyed in a glorious blaze.
      • Seventh Law of Travel
        When on a voyage to another continent, the journey will last only as long as it takes you to talk to all the other passengers and the captain.
      • Eighth Law of Travel
        There are no shortcuts, ever -- unless you are forced to take them, in which case they will be much longer and more dangerous than your original route.
      • Last Law of Travel (Big Joe Rule)
        As has been described, you must endure great trials just to get from town to town: locating different vehicles, operating ancient transport mechanisms, evading military blockades, the list goes on. But that's just you. Every other character in the game seems to have no trouble getting to any place in the world on a moment's notice.
      • If You Meet The Buddha In A Random Encounter, Kill Him!
        When you're out wandering around the world, you must kill everything you meet. People, animals, plants, insects, fire hydrants, small cottages, anything and everything is just plain out to get you. It may be because of your rampant kleptomania (see Garrett's Principle.)
      • Law of Numbers
        There will be several items or effects which depend on the numerical value of your hit points, level, etc., which makes no sense unless the characters can see all the numbers in their world and find it perfectly normal that a spell only works on a monster whose level is a multiple of 5.
      • Magical Inequality Theorem
        In the course of your travels you may find useful-sounding spells such as Petrify, Silence, and Instant Death. However, you will end up never using these spells in combat because a) all ordinary enemies can be killed with a few normal attacks, making fancy attacks unneccessary, b) all bosses and other stronger-than-average monsters are immune to those effects so there's no point in using them for long fights where they'd actually come in handy, and c) the spells usually don't work anyway.
      • Magical Inequality Corollary
        When the enemy uses Petrify, Silence, Instant Death, et cetera spells on you, they will be effective 100% of the time.
      • Pretty Line Syndrome (or, Crash Bandicoot: The RPG)
        Seen in most modern RPGs. The key to completing your quest is to walk forward in a straight line for fifty hours, stopping along the way to look at, kill, and/or have meaningful conversations with various pretty things.
      • Xenobiology Rule
        The predatory species of the world will include representatives of all of the following: giant spiders, giant scorpions, giant snakes, giant beetles, wolves, squid, fish that float in midair, gargoyles, golems, carnivorous plants, chimeras, griffons, cockatrices, hydras, minotaurs, burrowing things with big claws, things that can paralyse you, things that can put you to sleep, things that can petrify you, at least twenty different creatures with poisonous tentacles, and dragons. Always dragons.
      • Friendly Fire Principle (or, Final Fantasy Tactics Rule)
        Any attack that can target both allies and enemies will hit half of your allies and none of your enemies.
      • Dungeon Design 101
        There's always goodies hidden behind the waterfall.
      • Dungeon Design 102
        When you are confronted by two doors, the closer one will be locked and its key will be hidden behind the farther-away one.
      • Dungeon Design 103 (or, Wallpaper Warning)
        Your progress through a dungeon will be indicated by a sudden change in decor: different wall color, different torches on the wall, et cetera.
      • Dungeon Design 201 (or, The Interior Decorators Anticipated Your Out-Of-Body Experience)
        Most dungeons will include "hidden" passages which are nearly impossible to see from a bird's-eye view, yet would be blaringly obvious from the party's perspective.
      • Dungeon Design 301
        All "puzzles" in RPG dungeons can be sorted into one of the following types:

        • finding some small item and sticking it into a slot;
        • pushing blocks (rocks, statues) onto switches;
        • pulling switches or levers to open and close doors;
        • learning the correct order/position of a group of objects;
        • entering a certain combination of doors;
        • something involving a clock or elevator;
        • something that is unsolvable because a vital clue in the dialogue was mistranslated out of Japanese.


        • Wait! That Was A Load-Bearing Boss!
          Defeating a dungeon's boss creature will frequently cause the dungeon to collapse, which is nonsensical but does make for thrilling escape scenes.
        • Supply and Demand Axiom
          Killing a powerful enemy will usually yield an item or weapon that would've been extremely useful if you had gotten it before killing that enemy.
        • Edison's Lament
          No switch is ever in the right position.
        • Well, That About Wraps It Up For God
          All major deities, assuming they actually exist and weren't just made up by the Church to delude its followers, are in reality malevolent and will have to be destroyed. The only exception to this rule is the four nature spirits who have preserved the land since time immemorial, but now due to the folly of mankind have lost virtually all of their power and need you to accomplish some ludicrous task to save them.
        • Guy in the Street Rule
          No matter how fast you travel, rumors of world events always travel faster. When you get to anywhere, the people on the street are already talking about where you've been. The stories of your past experiences will spread even if no witnesses were around to see them.
        • Wherever You Go, There They Are
          Wherever the characters go, the villains can always find them. Chances are they're asking the guy in the street (see above). But don't worry -- despite being able to find the characters with ease anytime they want to, the bad guys never get rid of them by simply blowing up the tent or hotel they're spending the night in. (Just think of it: the screen dims, the peaceful going-to-sleep-now music plays, then BOOM! Game Over!)
        • Figurehead Rule
          Whenever someone asks you a question to decide what to do, it's just to be polite. He or she will ask the question again and again until you answer "correctly."
        • Puddin' Tame Rule
          The average passer-by will always say the same thing no matter how many times you talk to them, and they certainly won't clarify any of the vaguely worded warnings or cryptic half-sentences they threw at you the previous time.
        • Franklin Covey Was Wrong, Wrong, Wrong
          Sticking to the task at hand and going directly from place to place and goal to goal is always a bad idea, and may even prevent you from being able to finish the game. It's by dawdling around, completing side quests and giving money to derelicts that you come into your real power.
        • Selective Invulnerability Principle
          RPG characters are immune from such mundane hazards as intense heat, freezing cold, or poison gas... except when they're suddenly not. Surprise!
        • I'm the NRA (Billy Lee Black Rule)
          Opposition to gun control is probably the only thing you could get all RPG characters to agree upon. Even deep religious faith and heartfelt pacifism can't compete with the allure of guns.
        • Three Females Rule
          There will always be either one or three female characters in the hero's party, no matter how many male characters there are.
        • Experience Not Required
          When the main character is forced to do some complex or dangerous task for the first time, even though he has never done it before he will still always be better than the oldest veteran.
        • Law of Reverse Evolution (Zeboim Principle)
          Any ancient civilizations are inexplicably much more advanced than the current one.
        • Science-Magic Equivalence (Citan Rule)
          Although mages' specialty is magic and scientists' specialty is technology, these skills are completely interchangeable.
        • Law of Productive Gullibility (Ruby Rule)
          Whenever anybody comes up to you with a patently ludicrous claim (such as, "I'm not a cat, I'm really an ancient Red Dragon") there's an at least two-thirds chance they're telling the truth. Therefore, it pays to humor everyone you meet; odds are you'll be glad you did later on.
        • Perversity Principle
          If you're unsure about what to do next, ask all the townspeople nearby. They will either all strongly urge you to do something, in which case you must immediately go out and do that thing, or else they will all strongly warn you against doing something, in which case you must immediately go out and do that thing.
        • Near-Death Epiphany (Fei Rule)
          If the party is not dealing damage to a boss character, then there's a better-than-even chance that someone in the party will suddenly become enlightened and instantly acquire the offensive skill that can blow the creature away in a matter of seconds.
        • Wutai Rule
          Most RPGs, no matter what their mythology, include a land based on ancient Japan. Full of pagodas, shrines, shoguns, kitsune, and sushi, this completely anachronistic place is the source of the entire world's supply of ninja and samurai characters.
        • Law of Mooks
          Soldiers and guards working for the Evil Empire are, as a rule, sloppy, cowardly and incompetent. Members of the heroic Resistance Faction are, as a rule, dreadfully weak and undertrained and will be wiped out to the last man the moment they come in contact with the enemy.
        • Law of Traps
          No matter how obvious the trap, you can't complete the game unless you fall into it.
        • Arbor Day Rule
          At some point, you're going to have to talk to a tree and do what it says.
        • You Do Not Talk About Fight Club
          Any fighting tournament or contest of skill you hear about, you will eventually be forced to enter and win.
        • Invisible Bureaucracy Rule
          Other than the royal family, its shifty advisor, and the odd mad scientist, the only government employees you will ever encounter in the course of your adventure are either guards or kitchen staff.
        • The Miracle of Automation
          Similarily, any factory, power plant, or other facility that you visit during the course of the game will be devoid of any human life except for the occasional guards. There will not be a single line worker or maintenance person in sight.
        • Principle of Archaeological Convenience
          Every ancient machine you find will work perfectly the first time you try to use it and every time thereafter. Even if its city got blasted into ruins and the machine was then sunk to the bottom of the sea and buried in mud for ten thousand years, it'll still work fine. The unfortunate corollary to this rule is that ancient guardian creatures will also turn out to be working perfectly when you try to filch their stuff.
        • They Don't Make 'Em Like They Used To (Cid Rule)
          Modern-day machinery, by contrast, will always break down at the worst possible moment (for example, when you only need one more shot from the giant cannon to defeat the final boss.)
        • Place Transvestite Joke Here (Miss Cloud Rule)
          If the male lead is required to dress up like a girl for any reason, he will be regarded by everyone as much more attractive than any "real" girl. If the female lead cross-dresses as a man, she will be immediately recognized as who she is by everyone except the male lead and the main villain.
        • Make Room! Make Room!
          There are always more people in a town or village than there are houses for them to live in. Most of the village is made up of shops, temples, bars, secret passages, inns, and the mansion that belongs to the richest man in town.
        • Law of Scientific Gratification
          If the hero needs a new invention to progress, he will find out that somewhere in the world someone has spent his or her entire life perfecting this invention, and usually just needs one more key item located in a monster-infested dungeon before it is completed.
        • You Always Travel In The Right Circles
          Whenever you meet a villager or other such incidental character who promises to give you some great piece of needed knowledge or a required object in exchange for a seemingly simple item, such as a bar of soap or a nice straw mat, be prepared to spend at least an hour chasing around the world exchanging useless innocuous item after item with bizarre strangers until you can get that elusive first item you were asked for.
        • Talk Is Cheap Rule
          Nothing is ever solved by diplomacy or politics in the world of RPGs. Any declarations of peace, summits and treaty negotiations are traps to fool the ever so gullible Good Guys into thinking the war is over, or to brainwash the remaining leaders of the world.
        • Stop Your Life (Setzer Rule)
          No matter what kind of exciting, dynamic life a character was leading before joining your party, once there they will be perfectly content to sit and wait on the airship until you choose to use them.
        • Don't Stand Out
          Any townsperson who is dressed oddly or otherwise doesn't fit in with the rest of the townsfolk will either:

          1. Join your party after you complete some task,
          2. Be in the employ of your enemy, or
          3. Befriend any female member of the party, and then be immediately captured and held hostage by the villains.


        • Little Nemo Law
          If any sleeping character has a dream, that dream will be either a 100% accurate memory of the past, a 100% accurate psychic sending from the present, a 100% accurate prophetic vision of the future, or a combination of two or all three of these.
        • Child Protection Act (Rydia Rule)
          Children 12 and under are exempt from death. They will emerge alive from cataclysms that slaughter hundreds of sturdily-built adults, often with barely a scratch. Further protection is afforded if the catastrophe will orphan the child.
        • Missing Master Hypothesis
          Almost every strong physical fighter learned everything he/she knows from some old master or friend. Invariably, the master or friend has since turned evil, been killed, or disappeared without a trace.
        • Missing Master Corollary (Sabin Rule)
          If a fighter's master merely disappeared, you will undoubtedly find him/her at some point in your travels. The master will challenge the student to a duel, after which the student will be taught one final skill that the master had been holding back for years.
        • Gojira Axiom
          Giant monsters capable of leveling cities all have the following traits:

          • Low intelligence
          • Enormous strength
          • Projectile attacks
          • Gigantic teeth and claws, designed, presumably, to eat other giant monsters
          • Vulnerable to weapons 1/10,000th its size
          • Ecologically sensitive


          • "You Couldn't Get To Sleep Either, Huh?"
            If any character in the game ever meets any other character standing alone at night looking at the moon, those two will eventually fall in love.
          • Absolute Power Corrupts Absolutely (Althena Rule)
            If a good guy is manipulated to the side of evil, they will suddenly find a new inner strength that will enable them to wipe out your whole party with a wave of their hand.
          • All Is Forgiven (Nash Rule)
            However, when the trusted member of your party turns against you, do not give it a second thought. They will return to your side after they're done with their amnesia/mind control/hidden noble goal that caused them to give away all your omnipotent mystical artifacts.
          • First Law of Fashion
            All characters wear a single costume which does not change over the course of the game. The only exception is when characters dress up in enemy uniforms to infiltrate their base.
          • Second Law of Fashion
            Any character's costume, no matter how skimpy, complicated, or simply outlandish, is always completely suitable to wear when climbing around in caves, hiking across the desert, and slogging through the sewers. It will continue to be completely suitable right afterwards when said character goes to meet the King.
          • Third Law of Fashion
            In any futuristic setting, the standard uniform for female soldiers and special agents will include a miniskirt and thigh-high stockings. The standard uniform for all male characters, military or not, will include an extraordinarily silly and enormous hat.
          • First Rule of Politics (Chancellor's Axiom)
            Any advisor of a major ruler has been scheming after his throne for quite a while. Thanks to the miracle of timing, you will arrive at the king's inner sanctum just in time for the coup.
          • Second Rule of Politics (Scapegoat's Axiom)
            If the advisor works for an evil ruler, the advisor is as bad or even worse, and there's a good chance he's the final villain. (See Fake Ending Rule.) If the advisor works for a good ruler, he usually has the good of the kingdom at heart; not that that helps, because your party will invariably be made the scapegoat for all that's wrong with the nation and immediately thrown in the dungeon.
          • Last Rule of Politics
            Kingdoms are good. Empires are evil.
          • Inheritance of Acquired Characteristics (Ramus Rule)
            Twenty-three generations may pass, but any person's direct descendant will still look and act just like him.
          • Pinch Hitter Rule
            Whenever a member of the hero's team is killed or retires, no matter how unique or special he or she was there is a good chance someone will show up to replace them that has exactly the same abilities and can use the same weapons with the same proficiency.
          • Dealing With Beautiful Women, Part 1 (Yuffie Rule)
            All good-looking young females are there to help you. This rule holds even when the girl in question is annoying, useless, or clearly evil.
          • Dealing With Beautiful Women, Part 2 (Rouge Rule)
            All good-looking middle-aged females are out to kill you. This rule holds even when the woman in question has attained your unwavering trust and respect.
          • Well, So Much For That
            After you have completed your mighty quest to find the object that will save the known universe, it will either a) get lost, b) get stolen, or c) not work.
          • The Ominous Ring of Land
            The classic Ominous Ring of Land is a popular terrain feature that frequently doesn't show up on your world map. Just when you think things are going really well and you've got the Forces of Evil on the run, monsters, demons and mad gods will pour out of the center of the ring and the situation will get ten times worse. The main villain also usually hangs out in one of these after attaining godhood. If there are several Ominous Rings of Land or the entire world map is one big ring, you are just screwed.
          • Law of NPC Relativity (Magus Rule)
            Characters can accomplish superhuman physical feats, defeat enemies with one hand tied behind their back and use incredible abilities -- until they join your party and you can control them. Then these wonderful powers all vanish, along with most of their hit points.
          • Guards! Guards! (or, Lindblum Full Employment Act)
            Everything will be guarded and gated (elevators, docks, old rickety bridges, random stretches of roadway deep in the forest) except for the stuff that actually needs to be.
          • Thank You For Pressing The Self-Destruct Button
            All enemy installations and city-sized military vehicles will be equipped with a conveniently located, easy-to-operate self-destruct mechanism.
          • Falling Rule
            An RPG character can fall any distance onto anything without suffering anything worse than brief unconsciousness. In fact, falling a huge distance is an excellent cure for otherwise fatal wounds -- anyone who you see shot, stabbed, or mangled and then tossed off a cliff is guaranteed to return later in the game with barely a scratch.
          • Materials Science 101
            Gold, silver, and other precious metals make excellent weapons and armor even though in the real world they are too soft and heavy to use for that purpose. In fact, they work so well that nobody ever melts their solid gold suit of armor down into bullion, sells it, and retires to a tropical isle on the proceeds.
          • Materials Science 201
            Everyone you meet will talk enthusiastically about how some fantastically rare metal (iron, say) would make the best possible armor and weapons. Oh, if only you could get your hands on some! However, once you actually obtain iron -- at great personal risk, of course -- everyone will dismiss it as yesterday's news and instead start talking about some even more fantastically rare metal, such as gold. Repeat until you get to the metal after "mythril" (see The Ultimate Rule.)
          • Seventh Inning Stretch (Elc Rule)
            At some point in the game the main hero will receive a deadly story-driven injury and will be put in a hospital instead of having a mage heal him. This will leave him out of commission for at least the length of two sidequests; the female lead will also be temporarily out of commission as she steadfastly refuses to leave the hero's side. Ultimately a simple vision quest is all that will be required to bring the hero back to normal.
          • Vivi's Spellbook Principle
            Over the course of the game, you will spend countless hours learning between twenty and one hundred skills and/or spells, approximately three of which will still be useful by the end of the game.
          • Gender Equality, Part 1 (Feena Rule)
            Your average female RPG character carries a variety of deadly weapons and can effortlessly hack or magic her way through armies of monsters, killer cyborgs, and mutated boss creatures without breaking a sweat. She may be an accomplished ninja, a superpowered secret agent, or the world's greatest adventurer. However, if one of the game's villains manages to sneak up and grab her by the Standard Female Character Grab Area (her upper arm) she will be rendered utterly helpless until rescued by the hero.
          • Gender Equality, Part 2 (Tifa Rule)
            If any female character, in a burst of anger or enthusiasm, decides to go off and accomplish something on her own without the hero, she will fail miserably and again have to be rescued.
          • Gender Equality, Part 3 (Luna Rule)
            All of the effort you put into maxing out the female lead's statistics and special abilities will turn out to be for naught when she spends the final confrontation with the villain dead, ensorcelled, or held hostage.
          • Gender Equality Addendum (Rynn Rule)
            In the unlikely event that the main character of the game is female, she will not be involved in any romantic subplot whatsoever beyond getting hit on by shopkeepers.
          • Stealing The Spotlight (Edea Rule)
            The characters who join your party only briefly tend to be much cooler than your regular party members.
          • "Mommy, why didn't they just use a Phoenix Down on Aeris?"
            Don't expect battle mechanics to carry over into the "real world."
          • Gold Saucer Rule
            The strongest weapons/items/spells in the entire game can only be found by doing things like racing birds.
          • Evil May Live Forever, But It Doesn't Age Well
            Even though it took the greatest armies in the world and all of the world's greatest magicians to seal away an ancient evil in an apocalyptic war, once said ancient evil breaks free three fairly inexperienced warriors can destroy it.
          • Sephiroth Memorial Escape Clause
            Any misdeed up to and including multiple genocide is forgiveable if you're cool enough.
          • Doomed Utopia Theorem (Law of Zeal)
            All seemingly ideal, utopian societies are powered by some dark force and are therefore doomed to swift, flashy destruction.
          • Party Guidance Rule
            Somewhere in the last third of the story, the hero will make a stupid decision and the rest of the party must remind him of all that they have learned from being with him in order to return the hero to normal.
          • Bad Is Good, Baby!
            The heroes can always count on the support of good-hearted vampires, dragons, thieves, demons, and chainsaw murderers in their quest to save the world from evil. And on the other hand...
          • Good Is Bad, Baby!
            Watch out for generous priests, loyal military officers, and basically anyone in a position of authority who agrees to help you out, especially if they save your life and prove their sincerity innumerable times -- they're usually plotting your demise in secret (at least when they can fit it into their busy schedule of betraying their country, sponsoring international terrorism, and stealing candy from small children) and will stab you in the back at the most inconvenient moment, unless they fall under...
          • General Leo's Exception
            Honorable and sympathetic people who work for the Other Side are always the genuine article. Of course they'll be busily stabbing you in the front, so either way you lose. Eventually though, they'll fall prey to...
          • The Ineffectual Ex-Villain Theorem (Col. Mullen Rule)
            No matter how tough and bad-ass one of the Other Side's henchmen is, if he bails to the side of Good he'll turn out to be not quite tough and bad-ass enough. The main villain will defeat him easily. But don't weep -- usually he'll manage to escape just in time, leaving you to deal with the fate that was meant for him.
          • All The Time In The World (Rinoa Rule)
            Unless there's a running countdown clock right there on the screen, you have as long as you want to complete any task -- such as, say, rescuing a friend who's hanging by one hand from a slippery cliff edge thousands of feet in the air -- no matter how incredibly urgent it is. Dawdle or hurry as you will, you'll always make it just in the nick of time.
          • Ladies First (Belleza Rule)
            When things really start falling apart, the villain's attractive female henchman will be the first to jump ship and switch to the side of Good. Sadly, she still won't survive until the end credits, because later she will sacrifice her life out of unrequited love for the villain.
          • Trial By Fire (Cecil Rule)
            Any dark and brooding main characters will ultimately be redeemed by a long, ardous, quasi-spiritual quest that seems difficult at the time, but in the great scheme of things just wasn't that big of a deal after all.
          • Key Item Rule
            Never discard, sell, or otherwise remove permamently from your possession any items you begin the game with or acquire within the first town. This is especially true for items that seem to have no practical use, because of...
          • The Law of Inverse Practicality (Key Item Corollary)
            Any item that you can acquire will have some sort of purpose. Those that seem to be useless and have no practical value at all, always tend to have great power later on. The earlier you get the item, the later in the game it will be used. The longer the span of time between acquisition and use, the more powerful the item is.
          • Way To Go, Serge
            It will eventually turn out that, for a minimum of the first sixty percent of the game, you were actually being manipulated by the forces of evil into doing their sinister bidding for them. In extreme cases this may go as high as 90%. The clear implication is that it would have been better to not get involved in the first place.
          • Gilligan's Prescription
            Any character who has amnesia will be cured before the end of the game. They usually won't like what they find out about themselves, though.
          • Luke, I Am Your Tedious, Overused Plot Device (Lynx Rule)
            If there is any chance whatsoever that major villain X could be the male lead's father, then it will turn out that major villain X is the male lead's father.
          • World of Mild Inconvenience
            The devastating plague, noxious gas, planet-obliterating meteor or other large-scale disaster that led to the death of millions will affect your party (and your party's friends and family members) in no way whatsoever, save that a few party members may become lost and you can find them later.
          • Golden Chocobo Principle
            There will be at least one supremely ultimate improvement for your weapon or some way to make your trusted steed capable of going anywhere and doing anything, requiring hours and hours of hard work to acquire. Once you do achieve this, you will use it once, and it will be completely useless for the rest of the game.
          • Golden Chocobo Corollary
            The magic formula for acquiring this supreme upgrade will be only vaguely alluded to in the game itself. Ideally, you're supposed to shell out $19.95 for the strategy guide instead.
          • Flow of Goods Rule
            The quality of goods in the world is dependent upon the shop's distance from the final dungeon. It doesn't matter if the town you start in has a huge thriving economy and is the center of world trade, it will always have the game's worst equipment; and even if that village near the end is isolated and has only three people in it, it will have the game's best equipment.
          • Master Key Rule
            Any and all locked doors that the characters encounter will be unlocked by the end of the game.
          • "Evil will always triumph, because Good is dumb!"
            If the villain needs all ten legendary medallions to attain world domination and you have nine of them, everybody in your party still thinks it is neccessary to bring the nine to the villain's castle and get the final one, instead of hiding the ones they've already got and spoiling his plans that way. After you foolishly bring the legendary medallions to the villain's hideout, he will kidnap one of your companions (usually the main love interest) and you will trade the world away to rescue your friend.
          • Dark Helmet's Corollary
            After you give up the medallions to save your friend/parent/lover/other miscellaneous party member, don't expect to actually get that person back. Sucker!
          • It's Not My Department, Says Wernher Von Braun
            All space stations, flying cities, floating continents and so forth will without exception either be blown up or crash violently to earth before the end of the game.
          • The Best-Laid Schemes
            The final villain's grand scheme will have involved the deaths of thousands or even millions of innocent people, the clever manipulation of governments, armies, and entire populations, and will have taken anywhere from five to five thousand years to come to fruition. The hero will come up with a method of undoing this plan forever in less than five minutes.
          • Pyrrhic Victory
            By the time you've gotten it in gear, dealt with your miscellaneous personal crises and are finally ready to go Save the World once and for all, nine-tenths of it will already have been destroyed. Still, you've got to give your all to save the remaining one-tenth.
          • Poetic Villain Principle (Kefka Rule)
            All villains will suddenly become poets, philisophers, and/or dramatic actors when a) they first meet the hero, b) they are about to win or their evil plan is finally ready, c) some major event in the game is about to begin, d) right before the final battle, and e) right before they die, when they will frequently be feeling generous enough to reward you with some homespun wisdom about making the most of life while you have it.
          • Compression of Time
            As you approach the final confrontation with the villain, events will become increasingly awkward, contrived and disconnected from one another -- almost as if some cosmic Author was running up against a deadline and had to slap together the ending at the last minute.
          • Adam Smith's Revenge
            By the end of the game you are renowned everywhere as the Legendary Heroes, every surviving government and authority figure has rallied behind you, the fate of the world is obviously hanging in the balance, and out of nowhere random passers-by give you a pat on the back and heartfelt good luck wishes. However, shopkeepers won't even give you a discount, much less free supplies for the final battle with evil.
          • Adam Smith's Corollary
            No matter how thoroughly devastated the continent/planet/universe is, there's always some shopkeeper who survived the end of the world and sits outside the gates of the villain's castle, selling the most powerful equipment in the game, like nothing ever happened.
          • The Long Arm of the Plot
            Any bad guys, no matter how far they run, will always end up in one of two ways by the end of the game: obviously dead, or on your side. There is no in-between.
          • Apocalypse Any Time Now
            The best time to do side quests is while the huge meteor hovers in the sky above the planet, waiting to fall and destroy the world.
          • "So, Andross, you reveal your true form!"
            You will have to kill the evil villain at least twice at the end of the game. First the villain will look like a person or some creature and be rather easy to kill. Then he will grow to about 50 times the hero's size and be much harder to kill.
          • In Your Face, Jesus!
            Even if you manage to deal with him that time, you're not done -- the villain will then transform into his final form, which is always an angelic winged figure with background music remixed for ecstatic chorus and pipe organ.
          • The Moral Of The Story (Ghaleon Rule)
            Every problem in the universe can be solved by finding the right long-haired prettyboy and beating the crap out of him.
          • Weapon Rule
            There's always a hidden creature who is much harder to defeat than even the ultimate bad guy's final, world-annihilating form. It's lucky for all concerned that this hidden creature prefers to stay hidden rather than trying to take over the world himself, because he'd probably win. As a corollary, whatever reward you get for killing the hidden creature is basically worthless because by the time you're powerful enough to defeat him, you don't need it any more.
          • The Ultimate Rule
            Anything called "Ultima (whatever)" or "Ultimate (whatever)" isn't. There's always at least one thing somewhere in the world which is even more.
          • Know Your Audience (Vyse Rule)
            Every woman in the game will find the male lead incredibly attractive.

  • Norman Barrows
    Beautiful code part 3 - bottom up design

    an example of beautiful code using bottom up design.

    with this method, you start with the lowest level stuff, then add higher level layers until you have a sufficiently high level API.

    so you go from ugly low level stuff to a beautiful high level API.

    This example uses the directx 9 skinned mesh code from tiny.cpp and DXUT.h macros at the low level:

    https://www.gamedev.net/blog/1729/entry-2261223-skinned-mesh-code-part-1-low-level-microsoft-code/

    probably not what most folks would call beautiful code. but that's the low level API i had to start with.

    first step was a wrapper layer API to "beautify" things a bit:

    https://www.gamedev.net/blog/1729/entry-2261224-skinned-mesh-code-part-2-rockland-skinned-mesh-api/

    a little better, but still kind of ugly, given all the various routines for the different skinning methods. it should really be split up into separate API's, one for each skinning method.

    mesh and controller pools were required. being new APIs, they turned out to be relatively simple and beautiful:

    https://www.gamedev.net/blog/1729/entry-2261225-skinned-mesh-code-part-3-mesh-and-controller-memory-pools/

    and finally, one routine to rule them all: draw_skinmesh_caveman

    https://www.gamedev.net/blog/1729/entry-2261227-skinned-mesh-code-part-4-implementation-example/

    https://www.gamedev.net/blog/1729/entry-2261228-skinned-mesh-code-part-5-implementation-example-2/

    so all that ugly code has been beautified into the single API call draw_skinmesh_caveman
    Norman Barrows
    Beautiful code - part 2 - top down design

    an example of beautiful code.

    this was a result of top down design. IE high level routines fist, lowest level routines last.

    first, the algo was written in comments in english.

    then the highest level code was written, defining the lower level routines and their APIs as it was written, before ever actually defining the lower level code. so the high level code determined that there would be a camera_can_see_target function, and that it would take the index of a target as a parameter. the actual camera_can_see_target function was written later. the high level code defined what the lower level APIs would be. the highest level code was written to be as english-like as possible.

    this is the high level code that started this process (from SIMSpace 8.0):void draw_targets(){// for each target, if in vis rng and pass frustum cull, add to render queue// vis rng = 100x tgt size = 200x tgt.radint a;for (a=0; a
    clarity, simplicity, and read-ability are hallmarks of beautiful code.
    Norman Barrows
    Skinned mesh code part 5 - implementation example 2

    another implementation example from caveman 3.0:

    this is the actual code that draws band members and NPCs in the game now.

    void draw_caveman2(int a){// a is animal #int n;// n is npc #location f;// npc location for frustum cullint rad;// rad is NPC cliprad for frustum cullint ani;// ani is ani # to playint clothtex;// clothtex is the texture for loincloth and brafloat x,y,z;// x y z is the camera relative coordinates of the npcfloat yr;// yr is the y rotation to use for drawing the caveperson// === do frustum cull ===rad=6;f.mx=animal[a].mx;;f.mz=animal[a].mz;;f.x=animal[a].x;;f.z=animal[a].z;;f.y=heightmap(f.mx,f.mz,f.x,f.z);if (superclip4(&f,rad,animal_cliprng)) { return; }// === set npc # ====n=animal[a].butchered;// === set ani # ===ani=controller_pool[npc[n].controllerID].curani;// === set cloth texture ===// 159 = light hideclothtex=159;// === set world mat ====Mstart();camera_relative_coords(animal[a].mx,animal[a].mz,animal[a].x,animal[a].z,&x,&z);y=heightmap(animal[a].mx,animal[a].mz,animal[a].x,animal[a].z);if ((!animal[a].alive)&&(!animal[a].falling)) {// c deadani a MrotateRADS(1,pi); MrotateRADS(2,1.5f); y+=0.5f; }else { MrotateRADS(0,animal[a].xr); yr=animal[a].yr+pi; if (yr > pi*2.0f) { yr-=pi*2.0f; } MrotateRADS(1,yr); MrotateRADS(2,animal[a].zr); }Mmove(x,y,z);// === draw it ! ====draw_skinmesh_caveman(npc[n].sex,npc[n].hairmeshID,npc[n].skintexID,npc[n].hairtexID,npc[n].eyetexID,clothtex,ani,npc[n].controllerID,0.066,&Mmat);} void draw_bandmember2(int a){// a is bandmember #location f;// bandmember location for frustum cullint rad;// rad is bandmember cliprad for frustum cullint ani;// ani is ani # to playint clothtex;// clothtex is the texture for loincloth and brafloat x,y,z;// x y z is the camera relative coordinates of the bandmemberfloat yr;// yr is the y rotation to use for drawing the bandmember// === do frustum cull ===rad=6;f.mx=cm[a].mx;;f.mz=cm[a].mz;;f.x=cm[a].x;;f.z=cm[a].z;;f.y=heightmap(f.mx,f.mz,f.x,f.z);if ((a != cm0) || (cm[a].location != UPATREE)) // dont clip current bandmember when upatree {if (superclip4(&f,rad,animal_cliprng)) { return; } }// === set ani # ===ani=controller_pool[cm[a].controllerID].curani;// === set cloth texture ===// 159 = light hideclothtex=159;// === set world mat ====Mstart();camera_relative_coords(cm[a].mx,cm[a].mz,cm[a].x,cm[a].z,&x,&z);y=heightmap(cm[a].mx,cm[a].mz,cm[a].x,cm[a].z);if ((!cm[a].alive)&&(!cm[a].falling)) {// c deadani a MrotateRADS(1,pi); MrotateRADS(2,1.5f); y+=0.5f; }else { MrotateRADS(0,cm[a].xr); yr=cm[a].yr+pi; if (yr > pi*2.0f) { yr-=pi*2.0f; } MrotateRADS(1,yr); MrotateRADS(2,cm[a].zr); }Mmove(x,y,z);// === draw it ! ====draw_skinmesh_caveman(cm[a].sex,cm[a].hairmeshID,cm[a].skintexID,cm[a].hairtexID,cm[a].eyetexID,clothtex,ani,cm[a].controllerID,0.066,&Mmat);} // called from initprog:init_controller_pool();load_all_skinned_meshes(); // called at program shutdown:deactivate_all_controllers();unload_all_skinned_meshes(); // called when an NPC is added to the simulation:npc[npcID].controllerID=activate_controller(npc[npcID].skinmeshID);set_sm_npc_ani(a,ANI_STAND); // called when an NPC is removed from the simulation:c=npc.controllerID;deactivate_controller(c);
    Norman Barrows
    Skinned mesh code part 4 - implementation example

    An implementation example from Caveman 3.0:


    // called by draw_skinmesh_caveman // draws eyeballs attached to the headbone with an offset.// to draw a static mesh relative to the root of a bone, // start with the transform matrix for the object relative to the root of the bone, // then concatenate on the bone`s CombinedTransformationMatrix// don`t forget to call update_ani and add_world_transform_to_skeleton first!// use get_bone to get a D3DXFRAME pointer to the bone by name, and save it for // later use when drawing meshes attached to that bone.void draw_eyeballs2(int skmeshID,int texID){Zdrawinfo a;Zcdi(&a);a.meshID=146;a.texID=texID;a.cull=1;D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED *)skinned_mesh_pool[skmeshID].headbone;// use matrix manipulator to create bone to right eyeball transform matrix...Mstart();switch (skmeshID) { case 5: case 6: case 9: Mscale(0.013f,0.013f,-0.013f); Mrotate(0,-17); Mmove(0.0318f,0.1831f,0.082f); break; case 7: Mscale(0.013f,0.013f,-0.013f); Mrotate(0,-17); Mmove(0.0318f,0.1833f,0.0835f); break; case 8: Mscale(0.013f,0.013f,-0.013f); Mrotate(0,-17); Mmove(0.0318f,0.1833f,0.082f); break; default:// skmeshID 0,1,2,3,4 Mscale(0.015f,0.015f,-0.015f); Mrotate(0,-20); Mmove(0.036f,0.099f,0.076f); break; }// cat combined matrix onto matrix manipulator mat to get world mat... D3DXMatrixMultiply(&a.mWorld,&Mmat,&pFrame->CombinedTransformationMatrix);// add to render queue... Zdraw2(&a);// use matrix manipulator to create bone to left eyeball transform matrix... Mstart(); switch (skmeshID) { case 5: case 6: case 9: Mscale(0.013f,0.013f,-0.013f); Mrotate(0,-17); Mmove(-0.0318f,0.1831f,0.082f); break; case 7: Mscale(0.013f,0.013f,-0.013f); Mrotate(0,-17); Mmove(-0.0337f,0.1825f,0.0828f); break; case 8: Mscale(0.013f,0.013f,-0.013f); Mrotate(0,-17); Mmove(-0.0337f,0.1825f,0.082f); break; default:// skmeshID 0,1,2,3,4 Mscale(0.015f,0.015f,-0.015f); Mrotate(0,-20); Mmove(-0.035f,0.098f,0.076f); break; }// cat combined matrix onto matrix manipulator mat to get world mat... D3DXMatrixMultiply(&a.mWorld,&Mmat,&pFrame->CombinedTransformationMatrix);// add to render queue... Zdraw2(&a); } // draws hair mesh attached to the headbone with an offset.// to draw a static mesh relative to the root of a bone, // start with the transform matrix for the object relative to the root of the bone, // then concatenate on the bone`s CombinedTransformationMatrix// don`t forget to call update_ani and add_world_transform_to_skeleton first!// use get_bone to get a D3DXFRAME pointer to the bone by name, and save it for // later use when drawing meshes attached to that bone.void draw_girl_hair(int skmeshID,int hmeshID,int texID){Zdrawinfo a; Zcdi(&a); a.meshID=hmeshID; a.texID=texID;D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED *)skinned_mesh_pool[skmeshID].headbone;// use matrix manipulator to create bone to hair mesh transform matrix...// start w/ identity matrix... Mstart(); switch (hmeshID) { case 154:// long messy Mscale(0.925f,1.317f,1.2203f); Mrotate(0,-5); Mmove(0.002f,-0.032f,-0.07f); break; case 254:// short ponytail Mscale(1.2f,1.5f,1.15f); Mmove(-0.002f,0.103f,-0.05f); break; case 255:// short Mscale(1.09f,1.41f,1.1f); Mmove(-0.004f,0.051f,-0.028f); break; case 258:// short afro Mscale(1.224f,1.28f,1.15f); Mmove(-0.002f,0.06f,-0.032f); break; case 259:// long afro / dreads Mscale(1.23f,1.66f,1.27f); Mmove(0.004f,0.059f,-0.02f); break; default:// tex 260 - long straight Mscale(0.996f,1.381f,1.403f); Mrotate(0,-10); Mmove(0.002f,0.059f,-0.039f); break; }// cat combined matrix onto matrix manipulator mat to get world mat...D3DXMatrixMultiply(&a.mWorld,&Mmat,&pFrame->CombinedTransformationMatrix);// add to render queue...Zdraw2(&a);} // draws hair mesh attached to the head bone with an offset.// to draw a static mesh relative to the root of a bone, // start with the transform matrix for the object relative to the root of the bone, // then concatenate on the bone`s CombinedTransformationMatrix// don`t forget to call update_ani and add_world_transform_to_skeleton first!// use get_bone to get a D3DXFRAME pointer to the bone by name, and save it for // later use when drawing meshes attached to that bone.void draw_guy_hair(int skmeshID,int hmeshID,int texID){Zdrawinfo a;Zcdi(&a);a.meshID=hmeshID;a.texID=texID;D3DXFRAME_DERIVED *pFrame = (D3DXFRAME_DERIVED *)skinned_mesh_pool[skmeshID].headbone;// use matrix manipulator to create bone to hair mesh transform matrix...// start w/ identity matrix...Mstart();switch (hmeshID) { case 258: case 259: Mscale(1.11f,1.2f,1.2f); Mmove(0.0f,0.152f,-0.005f); break; case 250: Mscale(1.08f,1.073f,1.078f); Mmove(-0.0047f,0.177f,0.016f); break; case 265: Mscale(0.818f,0.815f,1.028f); Mmove(-0.0023f,0.143f,-0.039f); break; case 266: Mscale(1.07f,1.18f,0.8876f); Mrotate(0,-13); Mmove(-0.0023f,0.215f,-0.005f); break; default:// -1 = bald return; break; }// cat combined matrix onto matrix manipulator mat to get world mat...D3DXMatrixMultiply(&a.mWorld,&Mmat,&pFrame->CombinedTransformationMatrix);// add to render queue...Zdraw2(&a);} void draw_hair(int sex,int skmeshID,int hmeshID,int texID){if (sex == 0) { draw_guy_hair(skmeshID,hmeshID,texID); }else { draw_girl_hair(skmeshID,hmeshID,texID); }} // draws a caveperson using the specified hair mesh, textures, and animationvoid draw_skinmesh_caveman(int sex,int hair,int bodT,int hairT,int eyeT,int clothT,int ani,int ctrl,double dt,D3DXMATRIX *mWorld){// sex: 0=male 1=female// hair is hair mesh ID. -1 = bald.// bodT is body mesh texture ID// hairT is hair texture ID// eyeT is eye texture ID// clothT is clothing texture ID// ani is which ani should be playing// ctrl is the index of the character`s controllerint a;// a is the skinned_mesh_pool index of the body mesha=controller_pool[ctrl].mesh_index;// 1. set textures in the skinned mesh poolskinned_mesh_pool[a].body->texID=bodT;skinned_mesh_pool[a].loincloth->texID=clothT;if (sex == 1) { skinned_mesh_pool[a].bra->texID=clothT; }// 2. set animationcontroller_pool_setani(ctrl,ani);// 3. draw instance ( controllerID, dt, mWorld )draw_skinned_mesh_instance(ctrl,dt,mWorld);// set z3d state manager current mesh to none...Zcurrentmesh=-1;// reset FVF back to z3d FVF...Zd3d_device_ptr->SetFVF(ZFVF);// 4. draw eyesdraw_eyeballs2(a,eyeT);// 5. draw hairdraw_hair(sex,a,hair,hairT);} // tests drawing 100 instances of a skinned mesh, each with its own controller// uses update_and_draw_skinned_mesh w/ drawmeshcontainer5void skinned_mesh_test3(){// hair is which hair mesh to use. 0 through num hair meshes - 1.int i,quit,et,fps,num_anisets,hair,hairmeshID,cx,cz,xr,sex,bodT,eyeT,hairT,clothT;float x,z;char s[100],s2[100];bodT=0;eyeT=0;hairT=0;clothT=0;sex=0;xr=0;hair=0;hairmeshID=154;cx=0;cz=-100;// no need to init stuff - its done in initprog now.// c init_controller_pool// c load_all_skinned_meshesnum_anisets=(int)skinned_mesh_pool[0].controller->GetNumAnimationSets();// ss s "num anisets: "// c i2s num_anisets s2// s+ s s2// c msg3 sfor (i=0; i<100; i++) { activate_controller(i%10);// set it to play a random ani... controller_pool_setani(i,i%num_anisets); }// set a directional light...Zsetlite(0,0.0f,-1.0f,1.0f,1.0f);// turn it on...Zlite(0,1);quit=0;// set material to z3d default material....Zsetmaterial(0);et=0;fps=0;// init matrix editorAAinit();// turn on matrix editorAAturnon();while (!quit) {// timer 8 is used for framerate limiter.// like Caveman 3.0, the test runs at 15fps. i`ve also run it at 60 fps no problem. Zstarttimer(8);// set view matrix.... Zsetcam((float)cx/10.0f,5.0f,(float)cz/10.0f,0.0f,0.0f,0.0f); Zclearscreen(32,0,32); Zclearzbuf(); Zbeginscene(); Zcleardrawlist(); x=-10.0f; z=0.0f; for (i=0; i<100; i++) { sex=i%10; if (sex < 5) { sex=1; } else { sex=0; } if (sex == 0) {// male skin textures: 299 = mideastern 389 = sm_guy1.bmp bodT=389; eyeT=300; hairT=267; clothT=159; switch (hair) { case 0: hairmeshID=258; break; case 1: hairmeshID=259; break; case 2: hairmeshID=250; break; case 3: hairmeshID=265; break; case 4: hairmeshID=266; break; case 5: hairmeshID=-1; break; } } else {// girl skin textures: 387, 388 bodT=387;// eye textures: 292=blue 300=brown eyeT=292;// hair textures: 291=blonde 267=black hairT=291;// cloth textures: 159=light hide 192=dark hide clothT=159; switch (hair) { case 0: hairmeshID=154; break; case 1: hairmeshID=254; break; case 2: hairmeshID=255; break; case 3: hairmeshID=258; break; case 4: hairmeshID=259; break; case 5: hairmeshID=260; break; } } Mstart(); Mrotate(1,xr); Mmove(x,0.0f,z); draw_skinmesh_caveman(sex,hairmeshID,bodT,hairT,eyeT,clothT,i%num_anisets,i,0.066,&Mmat); x+=2.0f; if (x >= 10.0f) { x=-10.0f; z+=2.0f; } } Zdrawlist(); Zbeginsprite(); tx2(100,0,"press ESC to quit press SPACE to change hair mesh"); tx2(100,50,"use wasd to move camera L/R arrows: rotate models"); strcpy_s(s,100,"frame time: "); i2s(et,s2); strcat_s(s,100,s2); strcat_s(s,100," ms"); tx2(100,700,s); if (et == 0) { fps=0; } else { fps=1000/et; } strcpy_s(s,100,"fps: "); i2s(fps,s2); strcat_s(s,100,s2); tx2(100,750,s); Zendsprite();// draw matrix editor AAdraw();// end scene and present... Zshowscene();// process matrix editor input AAprocess_input(); if (Zkeypressed(VK_SPACE)) {// c change_ani ani_controller_ptr2 num_anis hair++; if (hair > 5) { hair=0; } while (Zkeypressed(VK_SPACE)) { } }// if they hit escape, quit the test... if (Zkeypressed(VK_ESCAPE)) { quit=1; } if (Zkeypressed(0x57)) {// w key cz++;// wh Zkeypressed(0x57)// . } if (Zkeypressed(0x41)) {// a key cx--;// wh Zkeypressed(0x41)// . } if (Zkeypressed(0x53)) {// s key cz--;// wh Zkeypressed(0x53)// . } if (Zkeypressed(0x44)) {// d key cx++;// wh Zkeypressed(0x44)// . } if (Zkeypressed(VK_LEFT)) { xr-=2; if (xr < 0) { xr=359; } } if (Zkeypressed(VK_RIGHT)) { xr+=2; if (xr > 359) { xr=0; } }// framerate limiter: wait until 66 millisconds has passed before beginning next frame...// wh Zelapsedtime(8)<66// . et=Zelapsedtime(8); }// c msg3 "unloading stuff..."// turn off matrix editorAAturnoff();// no need to uninit stuff now - its done by endprog.// c deactivate_all_controllers// c unload_all_skinned_meshes// c msg3 "all done!"} // enum ani_type: stand, walk, run, backstep; #define ANI_STAND 0#define ANI_WALKBACK 1#define ANI_WALK 2#define ANI_RUN 3 // set skinned mesh ani for a npcvoid set_sm_npc_ani(int a,int ani){// a is animal #// ani is the animation # to playint n;// n is npc #int c;// c is controller #n=animal[a].butchered;c=npc[n].controllerID;controller_pool_setani(c,ani);} // set skinned mesh ani for a player character (a band member)void set_sm_pc_ani(int a,int ani){// a is band member #// ani is the animation # to playcontroller_pool_setani(cm[a].controllerID,ani);} // ------------------------ end skinned mesh --------------------------
    Norman Barrows
    Skinned mesh code part 3 - mesh and controller memory pools
    // ----------------------------------- skinned mesh pool API ------------------------------------------- struct skinned_mesh_master{LPD3DXFRAME root;ID3DXAnimationController *controller;LPD3DXFRAME headbone;D3DXMESHCONTAINER_DERIVED *body, *bra, *loincloth;}; #define MAX_SKINNED_MESHES 100 skinned_mesh_master skinned_mesh_pool[MAX_SKINNED_MESHES]; int num_skinned_meshes=0; void load_skinned_mesh_master(char *fname){int result;LPD3DXMESHCONTAINER p;result=load_skinned_mesh(fname,&skinned_mesh_pool[num_skinned_meshes].controller,&skinned_mesh_pool[num_skinned_meshes].root);switch (result) { case 1: msg3("load skinned mesh master: invalid call!"); break; case 2: msg3("load skinned mesh master: out of memory!"); break; case 3: msg3("load skinned mesh master: setup bone matrix pointers error"); break; }skinned_mesh_pool[num_skinned_meshes].headbone=get_bone(skinned_mesh_pool[num_skinned_meshes].root,"metarig_head");if (skinned_mesh_pool[num_skinned_meshes].headbone == NULL) { msg3("load skinned mesh master: metarig_head not found in root!"); }// get pointer to body mesh to demonstrate interchangeable pooled textures for each mesh of a skinned meshp=get_mesh_container2(skinned_mesh_pool[num_skinned_meshes].root,"body");if (p == NULL) { msg3("load skinned mesh master: body frame meshcontainer is null!"); }skinned_mesh_pool[num_skinned_meshes].body=(D3DXMESHCONTAINER_DERIVED*)p;p=get_mesh_container2(skinned_mesh_pool[num_skinned_meshes].root,"bra");if (p == NULL) {// its a male skinned mesh, no bra mesh. skinned_mesh_pool[num_skinned_meshes].bra=NULL; }else { skinned_mesh_pool[num_skinned_meshes].bra=(D3DXMESHCONTAINER_DERIVED*)p; }p=get_mesh_container2(skinned_mesh_pool[num_skinned_meshes].root,"loincloth");if (p == NULL) { msg3("load skinned mesh master: loincloth frame meshcontainer is null!"); }skinned_mesh_pool[num_skinned_meshes].loincloth=(D3DXMESHCONTAINER_DERIVED*)p;num_skinned_meshes++;} void load_all_skinned_meshes(){FILE *f;int quit;char s[100];if (filefound("skinned_meshes.dat") == 0) { msg3("error: meshes.dat not found!"); return; }num_skinned_meshes=0;f=infile("skinned_meshes.dat");quit=0;while (!quit) { readfile(f,s); if (strcmp(s,"endfile") == 0) { quit=1; } else if (s[0] == '/') {// do nothing, its a comment } else {// c msg2 s load_skinned_mesh_master(s); } }fclose(f);} void unload_all_skinned_meshes(){int i;for (i=0; i.controller,skinned_mesh_pool.root); }} // -------------------------------------- controller pool API ------------------------------------------ struct skinned_mesh_instance{// curani: -1 = noneint active,mesh_index,curani;ID3DXAnimationController *controller;}; #define MAX_SKINNED_MESH_INSTANCES 200 skinned_mesh_instance controller_pool[MAX_SKINNED_MESH_INSTANCES]; void init_controller_pool(){int i;for (i=0; i.active=0; }} int get_free_controller(){int i;for (i=0; i.active == 0) { return(i); } }return(-1);} // activates a controller. // mesh_index is the index of the skinned mesh the controller should use.// returns the index of the controller. int activate_controller(int mesh_index){int j;j=get_free_controller();if (j == -1) { msg3("error: all animation controllers are in use!"); return(-1); }controller_pool[j].active=1;clone_controller(skinned_mesh_pool[mesh_index].controller,&controller_pool[j].controller);controller_pool[j].mesh_index=mesh_index;controller_pool[j].curani=-1;return(j);} void draw_skinned_mesh_instance(int i,double seconds,LPD3DXMATRIX mWorld){// i is the index of the controller in the controller pool.int j;// j is the index of the skinned mesh in the skinned mesh poolj=controller_pool.mesh_index;update_and_draw_skinned_mesh(controller_pool.controller,seconds,skinned_mesh_pool[j].root,mWorld);} void deactivate_controller(int c){// c is controller #if (controller_pool[c].active == 0) { return; }if (controller_pool[c].controller == NULL) { return; }controller_pool[c].controller->Release();controller_pool[c].controller=NULL;controller_pool[c].active=0;} void deactivate_all_controllers(){int i;for (i=0; i.active == 1) { deactivate_controller(i); } }} void controller_pool_setani(int cindex,int aniID){// sets the animation of a controller in the controller pool.// does not set if its already the current ani.// cIndex is the controller index.// aniID is the aniID.if (controller_pool[cindex].curani == aniID) { return; }set_ani(controller_pool[cindex].controller,aniID);controller_pool[cindex].curani=aniID;} // the controller should track which ani its playing// it does, via controller_pool.curani /* usage: // initinit_controller_poolload_all_skinned_meshesactivate_controller// drawdraw_skinned_mesh_instance// shutdowndeactivate_controller -or- deactivate_all_controllersunload_all_skinned_meshes for drawing in caveman:a cm or animal rec will hold the controller index, similar to an aniplayerID.the mWorld for the cm or animal will be used.seconds will usually be 0.066.there will be a draw skinned mesh model routine that will set textures, then call draw_skinned_mesh_instance, thendraw attached stuff (eyes, hair, gear, weapons etc). texture IDs will be stored in the cm rec or the npc rec. */ // -------------------------- end mesh pool and controller pool APIs --------------------------
    Norman Barrows
    Skinned mesh code part 2

    Rockland skinned mesh API
    // ------------------------ Rockland skinned mesh API -------------------------- // -------------------------------- load skinned mesh -----------------------------------// loads a skinned mesh from a .x file, and creates an animation controller for it.// NOTE: load mesh hierarchy from X automatically makes the created controller use the loaded mesh.// returns: // 0 success// 1 invalid call// 2 out of memory// 3 setup bone matrix pointers errorint load_skinned_mesh(char *filename,ID3DXAnimationController** ani_controller_ptr_addr,LPD3DXFRAME* g_pFrameRoot_addr){HRESULT result; // resultCAllocateHierarchy Alloc; // allocator routineresult=D3DXLoadMeshHierarchyFromX(filename,D3DXMESH_MANAGED,Zd3d_device_ptr,&Alloc,NULL,g_pFrameRoot_addr,ani_controller_ptr_addr);switch (result) { case D3DERR_INVALIDCALL: return(1); break; case E_OUTOFMEMORY: return(2); break; }// result = D3D_OKresult=SetupBoneMatrixPointers(*g_pFrameRoot_addr,*g_pFrameRoot_addr);if (result != S_OK) { return(3); }return(0);} // ------------------- unload skinned mesh ---------------------------------void unload_skinned_mesh(ID3DXAnimationController* ani_controller_ptr,LPD3DXFRAME g_pFrameRoot){CAllocateHierarchy Alloc;D3DXFrameDestroy(g_pFrameRoot,&Alloc);SAFE_RELEASE(ani_controller_ptr);} // ------------------- set D3DINDEXEDVS projection matrix transpose ----------// only required by D3DINDEXEDVS skinning method// must be called before calling set_indexed_shader_proj_mat!void set_D3DINDEXEDVS_proj_mat_transpose(){D3DXMatrixTranspose(&g_matProjT,&Zprojection_matrix);} // --------------------- load effect file ------------------------------------// only required for shader based skinning methods// returns S_OK on successHRESULT load_effect_file(){HRESULT hr;DWORD dwShaderFlags = D3DXFX_NOT_CLONEABLE; // Read the D3DX effect file// WCHAR str[MAX_PATH];// V_RETURN( DXUTFindDXSDKMediaFileCch( str, MAX_PATH, L"SkinnedMesh.fx" ) ); // If this fails, there should be debug output as to why the .fx file failed to compile V_RETURN( D3DXCreateEffectFromFile( Zd3d_device_ptr, "SkinnedMesh.fx", NULL, NULL, dwShaderFlags, NULL, &g_pEffect, NULL ) );return(S_OK);} // ----------------------- release effect file -------------------------------------// only required for shader based skinning methodsvoid unload_effect_file(){SAFE_RELEASE( g_pEffect );} // ------------------ set indexed shader proj mat -------------------------------------// Set the projection matrix for indexed skinning vertex shaders// only required for D3DINDEXEDVS and D3DINDEXEDHLSLVS skinning methods// must call set_D3DINDEXEDVS_proj_mat_transpose first!void set_indexed_shader_proj_mat(){HRESULT hr;if( g_SkinningMethod == D3DINDEXEDVS ) { V( Zd3d_device_ptr->SetVertexShaderConstantF( 2, ( float* )&g_matProjT, 4 ) ); }else if( g_SkinningMethod == D3DINDEXEDHLSLVS ) { V( g_pEffect->SetMatrix( "mViewProj", &Zprojection_matrix ) ); }} // --------------------------- set shader light -------------------------------// // Set Light for vertex shader// only required for shader based skinning methods// note you`ll want to modify this light to match your // fixed function one or your lighting won`t look right!void set_shader_light(){HRESULT hr; D3DXVECTOR4 vLightDir( 0.0f, 1.0f, -1.0f, 0.0f );D3DXVec4Normalize( &vLightDir, &vLightDir );V( Zd3d_device_ptr->SetVertexShaderConstantF( 1, ( float* )&vLightDir, 1 ) );V( g_pEffect->SetVector( "lhtDir", &vLightDir ) );} // ------------- update animation -----------------------------// advances the controller`s timer by "seconds", // tweens between the two closest keyframes, then sets the bone matrices// of the skeleton.void update_ani(ID3DXAnimationController* ani_controller_ptr,double seconds){ani_controller_ptr->AdvanceTime( seconds, NULL );} // --------------------- add world transform to skeleton ---------------------// call update_ani first!void add_world_transform_to_skeleton(LPD3DXFRAME g_pFrameRoot,LPD3DXMATRIX mWorld_ptr){UpdateFrameMatrices( g_pFrameRoot, mWorld_ptr );} // ------------------------ draw skinned mesh ----------------------// draws a skinned mesh// ZmView is the view matrix// to port to code not based on the z3d game library, // replace Zd3d_device ptr and ZmView with your own d3d device pointer and view matrix.void draw_skinned_mesh(LPD3DXFRAME g_pFrameRoot){DrawFrame( Zd3d_device_ptr, g_pFrameRoot, &ZmView );} // ------------------------------ get behavior flags ----------------------------// gets behavior flags (HW caps -> creation params -> behavoir flags) for indexed shading methods// only required for D3DINDEXED, D3DINDEXEDVS, and D3DINDEXEDHLSLVS skinning methodsvoid get_indexed_behavior_flags(){D3DDEVICE_CREATION_PARAMETERS cp;Zd3d_device_ptr->GetCreationParameters( &cp );g_dwBehaviorFlags = cp.BehaviorFlags;} // ---------------------------- load shaders -----------------------------// returns S_OK on success// loads the indexed vertex shaders// only required for shader based skinning methodsHRESULT load_shaders(){HRESULT hr;DWORD dwShaderFlags = 0;/*#if defined( DEBUG ) || defined( _DEBUG ) // Set the D3DXSHADER_DEBUG flag to embed debug information in the shaders. // Setting this flag improves the shader debugging experience, but still allows // the shaders to be optimized and to run exactly the way they will run in // the release configuration of this program. dwShaderFlags |= D3DXSHADER_DEBUG; #endif#if defined(DEBUG_VS) || defined(DEBUG_PS) dwShaderFlags |= D3DXSHADER_DEBUG|D3DXSHADER_SKIPVALIDATION; #endif*/ for( DWORD iInfl = 0; iInfl < 4; ++iInfl ) { LPD3DXBUFFER pCode; // Assemble the vertex shader file// WCHAR str[MAX_PATH];// DXUTFindDXSDKMediaFileCch( str, MAX_PATH, g_wszShaderSource[iInfl] ); if( FAILED( hr = D3DXAssembleShaderFromFile( g_wszShaderSource[iInfl], NULL, NULL, dwShaderFlags, &pCode, NULL ) ) ) return hr; // Create the vertex shader if( FAILED( hr = Zd3d_device_ptr->CreateVertexShader( ( DWORD* )pCode->GetBufferPointer(), &g_pIndexedVertexShader[iInfl] ) ) ) { return hr; } pCode->Release(); }return(S_OK);} // ------------------------------------ unload shaders -------------------------// only required for shader based skinning methodsvoid unload_shaders(){for( DWORD iInfl = 0; iInfl < 4; ++iInfl ) SAFE_RELEASE( g_pIndexedVertexShader[iInfl] );} // ----------------------------------- get bone (by name) -----------------------------------// returns a pointer to a bone (D3DXFRAME), given its name (as it appears in the .x file, not blender, etc!)// use this to get a pointer to a bone you want to attach a mesh to, // such as a hand bone you want to attach a weapon to.// example:// LPD3DXFRAME my_bone_pointer=get_bone(root_bone_pointer,"some_bone_name");// the bone`s world transform is then (D3DXFRAME_DERIVED*)my_bone_pointer->CombinedTransformMatrix// getting bone by name requires chasing pointers to travere the frame hierarchy tree, // and string comparisons at each node. so you want to just do it once for bones you`re // interested in when you load a skined mesh, and save the bone pointers for later use when// drawing meshes attached to those bones.LPD3DXFRAME get_bone(LPD3DXFRAME rootframe,char *s){return(D3DXFrameFind(rootframe,s));} // -------------------- get mesh container, given the mesh container name ---------------------// NOTE: blender does not export mesh container names! // it only exports them as comments in the .x file.// use get_mesh_container2 to get the first mesh container of a frame,// given the frame name, which blender does export. // recursively searches a linked list of meshcontainers and returns a pointer to a mesh container, given its name.// returns NULL if not found// NOTE: unfortunately, blender doesn`t seem to export mesh names except as comments. so mesh names are always "".// so this wont really work with files from blender.LPD3DXMESHCONTAINER get_mesh_container_part2(LPD3DXMESHCONTAINER p,char *s){msg3(p->Name);if (strcmp(p->Name,s) == 0) { return(p); }if (p->pNextMeshContainer == NULL) { return(NULL); }return(get_mesh_container_part2(p->pNextMeshContainer,s));} // returns a pointer to a meshcontainer in a skinned mesh, given its name.// returns NULL if not found// rootframe is the root frame of the skinned mesh// does a recursive traversal of the frame hierarchy. for each frame with a meshcontainer, // it does a recursive search of the linked list of meshcontainers, // looking for a meshcontainer with a matching name.// USAGE:// 1. add a texID variable to D3DXMESHCONTAINER_DERIVED// 2. after loading the skinned mesh, use get_mesh_container to get a pointer to a mesh container.// 3. use the pointer to the mesh container to set the texID// 4. use drawmeshcontainer5 to draw ( it uses the texID)// this gives you interchangeable poolable textures for each mesh of the skinned mesh.// NOTE: unfortunately, blender doesn`t seem to export mesh names except as comments. so mesh names are always "".// so this wont really work with files from blender.LPD3DXMESHCONTAINER get_mesh_container(LPD3DXFRAME rootframe,char *s){LPD3DXMESHCONTAINER p;p=NULL;if (rootframe->pMeshContainer != NULL) { p=get_mesh_container_part2(rootframe->pMeshContainer,s); if (p != NULL) { return(p); } }if (rootframe->pFrameSibling != NULL) { p=get_mesh_container(rootframe->pFrameSibling,s); if (p != NULL) { return(p); } }if (rootframe->pFrameFirstChild != NULL) { p=get_mesh_container(rootframe->pFrameFirstChild,s); if (p != NULL) { return(p); } }return(NULL);} // --------------------------- get first mesh container of a frame, given the frame name -------------------------- // returns a pointer to the first meshcontainer of a frame, given the name of the frame.// returns NULL if frame not found, or frame meshcontainer is null.// rootframe is the root frame of the skinned mesh.// uses get_bone to get the frame, then returns the frame`s mesh container pointer.// USAGE:// 1. add a texID variable to D3DXMESHCONTAINER_DERIVED// 2. after loading the skinned mesh, use get_mesh_container2 to get a pointer to a mesh container.// 3. if necessary, use meshcontainer->pNextContainer to traverse down the linked list of meshcontainers, // until you get to the one you want.// 4. just before drawing, use the pointer to the mesh container to set the texID// 5. use drawmeshcontainer5 to draw (it uses the texID)// this gives you interchangeable poolable textures for each mesh of the skinned mesh.// AND it works with .x files with no mesh container names, such as those exported from blender. LPD3DXMESHCONTAINER get_mesh_container2(LPD3DXFRAME rootframe,char *s){LPD3DXFRAME p;p=get_bone(rootframe,s);if (p == NULL) { return(NULL); }return(p->pMeshContainer);} // ---------------------------------- clone controller ---------------------------// clones an animation controller.// used when drawing multiple instances of the same skinned mesh, each with its own animation.// use this to clone a copy of the "master" controller you used to load the skinned mesh.// create a clone for each instance you want to draw. only use the master for cloning, not for animation.// when drawing just one instance, there is no need to clone, and its ok to use the master for animation.void clone_controller(ID3DXAnimationController* source,ID3DXAnimationController** target){source->CloneAnimationController( source->GetMaxNumAnimationOutputs(), source->GetMaxNumAnimationSets(), source->GetMaxNumTracks(), source->GetMaxNumEvents(), target);} // ------------------------------------ set animation --------------------------------------// sets a controller to the animation given by "index", and starts the animation from the beginning. // sorry - no animation blending - yet! .// use this to switch between multiple animationsvoid set_ani(ID3DXAnimationController* p,int index){LPD3DXANIMATIONSET a;p->GetAnimationSet((UINT)index,&a);p->SetTrackAnimationSet(0,a);p->SetTrackPosition(0,0);p->ResetTime();p->SetTrackSpeed(0,1.0f);} // -------------------------- update and draw skinned emsh -------------------------------------- // combines update_ani, add_world_transform_to_skeleton, and draw_skinned_meshvoid update_and_draw_skinned_mesh(ID3DXAnimationController* ani_controller_ptr,double seconds,LPD3DXFRAME g_pFrameRoot,LPD3DXMATRIX mWorld_ptr){update_ani(ani_controller_ptr,seconds);add_world_transform_to_skeleton(g_pFrameRoot,mWorld_ptr);draw_skinned_mesh(g_pFrameRoot);} // ========================== skinned mesh test routines 1 and 2 =============================== // toggles a controller to the next animation// only used with the 1st skinned mesh`s controller for the test routine// num_anis is the number of animation sets the controller hasvoid change_ani(ID3DXAnimationController* p,int num_anis){static int current_ani=0;current_ani++;if (current_ani >= num_anis) { current_ani=0; }set_ani(p,current_ani);} // draws a quad at the root of each frame in the hierarchy of the test .x file used.// this tested the basic ability to use combinedtransformmatrix for attaching meshes to bones.void draw_hat(LPD3DXFRAME g_pFrameRoot){Zdrawinfo a;Zcdi(&a); D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )g_pFrameRoot;a.mWorld=pFrame->CombinedTransformationMatrix;Zdraw2(&a); pFrame=(D3DXFRAME_DERIVED*)(pFrame->pFrameFirstChild);a.mWorld=pFrame->CombinedTransformationMatrix;Zdraw2(&a); pFrame=(D3DXFRAME_DERIVED*)(pFrame->pFrameFirstChild);a.mWorld=pFrame->CombinedTransformationMatrix;Zdraw2(&a); pFrame=(D3DXFRAME_DERIVED*)(pFrame->pFrameFirstChild);a.mWorld=pFrame->CombinedTransformationMatrix;Zdraw2(&a); /*pFrame=(D3DXFRAME_DERIVED*)(pFrame->pFrameSibling);a.mWorld=pFrame->CombinedTransformationMatrix;Zdraw2(&a);*/} // draws a spinning textured quad w/ alphatest, no cull, no lighting.// rotation is based on ET of timer #7// used to test drawing skinned meshes with regular meshes in the same // beginscene - end scene block. this helped to determine what needs // to be reset after drawing a skinned mesh.void draw_hat2(){Zdrawinfo a;Zcdi(&a);a.texID=250;a.sx=10.0f;a.sy=10.0f;a.sz=10.0f;a.rx=pi/2.0f;a.ry=(float)Zelapsedtime(7)/1000.0f;a.y=5.0f;a.alphatest=1;Zdraw(&a);} // draws a quad at the root of a bone (frame).// to draw a static mesh relative to the root of a bone, // start with the transform matrix for the object relative to the root of the bone, // then concatenate on the bone`s CombinedTransformationMatrix// don`t forget to call update_ani and add_world_transform_to_skeleton first!// use get_bone to get a D3DXFRAME pointer to the bone by name, and save it for // later use when drawing meshes attached to that bone.void draw_hat3(LPD3DXFRAME bone){Zdrawinfo a;// clear the zdrawinfo struct. // this sets it to drawtype:staticmesh, scale 1, meshID 0 (unit quad), texID 0 (grass tile 1), materialID 0 (Z3D default material), // no cull, no clamp, no alphatest, and no translation or rotation...Zcdi(&a);D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )bone;// set the drawinfo`s world matrix to the combined transform matrix...a.mWorld=pFrame->CombinedTransformationMatrix;// add the drawinfo to the render queue...Zdraw2(&a);} // draws eyeballs attached to bone with offset.// to draw a static mesh relative to the root of a bone, // start with the transform matrix for the object relative to the root of the bone, // then concatenate on the bone`s CombinedTransformationMatrix// don`t forget to call update_ani and add_world_transform_to_skeleton first!// use get_bone to get a D3DXFRAME pointer to the bone by name, and save it for // later use when drawing meshes attached to that bone.void draw_eyeballs(LPD3DXFRAME bone,int texID){Zdrawinfo a;Zcdi(&a);a.meshID=8;a.texID=texID;a.cull=1;D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )bone;// use matrix manipulator to create bone to right eyeball transform matrix...Mstart();Mscale(0.1f,0.1f,0.1f);Mrotate(0,90);Mrotate(1,320);Mmove(-0.096f,1.55f,0.244f);// cat combined matrix onto matrix manipulator mat to get world mat...D3DXMatrixMultiply(&a.mWorld,&Mmat,&pFrame->CombinedTransformationMatrix);// add to render queue...Zdraw2(&a);// use matrix manipulator to create bone to left eyeball transform matrix...Mstart();Mscale(0.1f,0.1f,0.1f);Mrotate(0,90);Mrotate(1,320);Mmove(0.102f,1.555f,0.206f);// cat combined matrix onto matrix manipulator mat to get world mat...D3DXMatrixMultiply(&a.mWorld,&Mmat,&pFrame->CombinedTransformationMatrix);// add to render queue...Zdraw2(&a);} void skinned_mesh_test(){LPD3DXFRAME g_pFrameRoot = NULL; // ptr to root frame of the skeletonLPD3DXFRAME bone1 = NULL; // ptr to bone.001 of the skeletonID3DXAnimationController* ani_controller_ptr = NULL; // pointer to the animation cotrollerID3DXAnimationController* ani_controller_ptr2 = NULL; // pointer to the animation cotrollerID3DXAnimationController* ani_controller_ptr3 = NULL; // pointer to the animation cotroller// number of animations the mesh hasint num_anis;int result,quit;LPD3DXMESHCONTAINER p;// NOTE: have to put the * with the varname, NOT the type!// using: int* a,b,c; a is an int pointer, but b and c are just ints!// using: int *a,*b,*c; you get a,b,and c as int pointers, the desired result.D3DXMESHCONTAINER_DERIVED *pBody, *pBra, *pLoincloth;result=load_skinned_mesh("tiny.x",&ani_controller_ptr,&g_pFrameRoot);switch (result) { case 1: msg3("error: invalid call!"); break; case 2: msg3("error: out of memory!"); break; case 3: msg3("error: setup bone matrix pointers"); break; }// get number of anis for toggle ani...num_anis=(int)ani_controller_ptr->GetNumAnimationSets(); // get pointer to bone1 for demonstrating drawing a mesh attached to a bone...// note that blender calls it "bone.001" but exports it as "Armature_Bone_001".bone1=get_bone(g_pFrameRoot,"Armature_Bone_001");if (bone1 == NULL) { msg3("error: Armature_Bone_001 not found in g_pFrameRoot!"); } // get pointer to body mesh to demonstrate interchangeable pooled textures for each mesh of a skinned meshp=get_mesh_container2(g_pFrameRoot,"body");if (p == NULL) { msg3("error: body frame meshcontainer is null!"); }pBody=(D3DXMESHCONTAINER_DERIVED*)p;p=get_mesh_container2(g_pFrameRoot,"bra");if (p == NULL) { msg3("error: bra frame meshcontainer is null!"); }pBra=(D3DXMESHCONTAINER_DERIVED*)p;p=get_mesh_container2(g_pFrameRoot,"loincloth");if (p == NULL) { msg3("error: loincloth frame meshcontainer is null!"); }pLoincloth=(D3DXMESHCONTAINER_DERIVED*)p; // display animation set names, and number of animations in each set.// looks like an animation is the keyframes for one bone (IE: D3DXFRAME), // and an animationset is all the keyframes for all the bones in a skeleton (IE: a D3DXFRAME hierarchy).// the number of animations in an animationset should match the // number of animated bones (D3DXFRAMES) in the frame hierachy.// NOTE: looks like animation sets appear in the controller in reverse order of their order in the .x file!// apparently it does an "add at front" when it adds an animation set to the list of animation sets in the controller. /* int i;// i jLPD3DXANIMATIONSET a;const char *s=NULL;char s2[100]; for (i=0; iGetAnimationSet((UINT)i,&a);// NOTE: looks like getname returns a pointer to an exisiting string, use it in a readonly manner, and do NOT free it! s=a->GetName(); strcpy_s(s2,100,s); msg3(s2);// cr j (int)a->GetNumAnimations// c i2s j s2// c msg3 s2// NOTE: GetAnimationNameByIndex also appears to return a pointer to an exisiting string, // the docs are somewhat unclear.// use it in a readonly manner, and do NOT free it! } */ // clone ani controllers:// apparently, you want to load one skinned mesh with a "master" controller, // then clone the master controller for each instance you want to draw. // so each skinned mesh instance will have a controller, plus you`ll have the master,// which is just used for cloning, not animating.// never really found out why everyone does it this way, // maybe that way you don`t have to reset everything in the clone, such as track position.// never tested using the master for both animating and cloning.// so ani_controller_ptr is the master, and ani_controller_ptr2 and 3 are for the two instances the test draws.clone_controller(ani_controller_ptr,&ani_controller_ptr2);// init controller to play the 1st animationset_ani(ani_controller_ptr2,0);clone_controller(ani_controller_ptr,&ani_controller_ptr3);// init controller to play the 2nd animationset_ani(ani_controller_ptr3,1);// set a directional light...Zsetlite(0,0.0f,-1.0f,1.0f,1.0f);// turn it on...Zlite(0,1);// set view matrix....Zsetcam(0.0f,5.0f,-10.0f,0.0f,0.0f,0.0f);quit=0;// timer 7 ET is used for the rotation of the quad in drawhat2Zstarttimer(7);// set material to z3d default material....Zsetmaterial(0);AAturnon();while (!quit) {// timer 8 is used for framerate limiter.// like Caveman 3.0, the test runs at 15fps. i`ve also run it at 60 fps no problem. Zstarttimer(8); Zclearscreen(32,0,32); Zclearzbuf(); Zbeginscene();// clear render queue... Zcleardrawlist();// draw quad relative to bone1. this call adds the quad to the render queue... draw_hat3(bone1);// turn off ligting (makes the quad easier to see)... Zlighting(0);// render the render queue... Zdrawlist();// turn lighting back on Zlighting(1); /*// update ani controller for 1st skinned mesh... update_ani(ani_controller_ptr2,0.066);// init z3d matrix manipulator to identity matrix... Mstart();// concatenate on a traslation matrix... Mmove(-1.0f,0.0f,0.0f);// add 1st skinned mesh world transform to skeleton...// Mmat is the z3d matrix manipulator`s matrix add_world_transform_to_skeleton(g_pFrameRoot,&Mmat); */ // set textures for 1st skinned mesh...// c Zsettex 387 pBody->texID=387; pBra->texID=159; pLoincloth->texID=159; /*// draw 1st skinned mesh... draw_skinned_mesh(g_pFrameRoot); */ // init z3d matrix manipulator to identity matrix... Mstart();// concatenate on a traslation matrix... Mmove(-1.0f,0.0f,0.0f);// update and draw skinned mesh with one call... update_and_draw_skinned_mesh(ani_controller_ptr2,0.066,g_pFrameRoot,&Mmat); // drawmeshcontainer (all versions) changes the current mesh and fvf, must reset...// set z3d state manager current mesh to none... Zcurrentmesh=-1;// reset FVF back to z3d FVF... Zd3d_device_ptr->SetFVF(ZFVF); // ok, draw some eyeballs attached to the head bone (now that its updated)... draw_eyeballs(bone1,266); /*// update ani controller for 2nd skinned mesh... update_ani(ani_controller_ptr3,0.066);// init z3d matrix manipulator to identity matrix... Mstart();// concatenate on a traslation matrix... Mmove(1.0f,0.0f,0.0f);// add 2nd skinned mesh world transform to skeleton...// Mmat is the z3d matrix manipulator`s matrix add_world_transform_to_skeleton(g_pFrameRoot,&Mmat); */ // set textures for 2nd skinned mesh...// c Zsettex 388 pBody->texID=388; pBra->texID=192; pLoincloth->texID=192; /*// draw 2nd skinned mesh... draw_skinned_mesh(g_pFrameRoot); */ // init z3d matrix manipulator to identity matrix... Mstart();// concatenate on a traslation matrix... Mmove(1.0f,0.0f,0.0f);// update and draw skinned mesh with one call... update_and_draw_skinned_mesh(ani_controller_ptr3,0.066,g_pFrameRoot,&Mmat); // drawmeshcontainer (all versions) changes the current mesh and fvf, must reset...// set z3d state manager current mesh to none... Zcurrentmesh=-1;// reset FVF back to z3d FVF... Zd3d_device_ptr->SetFVF(ZFVF); tx(100,800,"press ESC to quit"); tx(100,850,"press SPACE to change animation"); AAdraw();// end scene and present... Zshowscene();// if they hit spacebar, toggle the 1st skinned mesh to the next animation... if (Zkeypressed(VK_SPACE)) { change_ani(ani_controller_ptr2,num_anis); while (Zkeypressed(VK_SPACE)) { } }// if they hit escape, quit the test... if (Zkeypressed(VK_ESCAPE)) { quit=1; } AAprocess_input();// framerate limiter: wait until 66 millisconds has passed before beginning next frame... while (Zelapsedtime(8)<66) { } }msg3("unloading stuff...");AAturnoff();// unload the mesh and master controller...unload_skinned_mesh(ani_controller_ptr,g_pFrameRoot);// release the clones...ani_controller_ptr2->Release();ani_controller_ptr3->Release();msg3("all done!");} // uses update_and_draw_skinned_mesh w/ drawmeshcontainer5// code to display animation set names has been removedvoid skinned_mesh_test2(){LPD3DXFRAME g_pFrameRoot = NULL; // ptr to root frame of the skeletonLPD3DXFRAME bone1 = NULL; // ptr to bone.001 of the skeletonID3DXAnimationController* ani_controller_ptr = NULL; // pointer to the animation cotrollerID3DXAnimationController* ani_controller_ptr2 = NULL; // pointer to the animation cotrollerID3DXAnimationController* ani_controller_ptr3 = NULL; // pointer to the animation cotroller// number of animations (animation sets) the mesh hasint num_anis;int result,quit;LPD3DXMESHCONTAINER p;// NOTE: have to put the * with the varname, NOT the type!// using: int* a,b,c; a is an int pointer, but b and c are just ints!// using: int *a,*b,*c; you get a,b,and c as int pointers, the desired result.D3DXMESHCONTAINER_DERIVED *pBody, *pBra, *pLoincloth;result=load_skinned_mesh("tiny.x",&ani_controller_ptr,&g_pFrameRoot);switch (result) { case 1: msg3("error: invalid call!"); break; case 2: msg3("error: out of memory!"); break; case 3: msg3("error: setup bone matrix pointers"); break; }// get number of anis for toggle ani...num_anis=(int)ani_controller_ptr->GetNumAnimationSets(); // get pointer to bone1 for demonstrating drawing a mesh attached to a bone...// note that blender calls it "bone.001" but exports it as "Armature_Bone_001".bone1=get_bone(g_pFrameRoot,"Armature_Bone_001");if (bone1 == NULL) { msg3("error: Armature_Bone_001 not found in g_pFrameRoot!"); } // get pointer to body mesh to demonstrate interchangeable pooled textures for each mesh of a skinned meshp=get_mesh_container2(g_pFrameRoot,"body");if (p == NULL) { msg3("error: body frame meshcontainer is null!"); }pBody=(D3DXMESHCONTAINER_DERIVED*)p;p=get_mesh_container2(g_pFrameRoot,"bra");if (p == NULL) { msg3("error: bra frame meshcontainer is null!"); }pBra=(D3DXMESHCONTAINER_DERIVED*)p;p=get_mesh_container2(g_pFrameRoot,"loincloth");if (p == NULL) { msg3("error: loincloth frame meshcontainer is null!"); }pLoincloth=(D3DXMESHCONTAINER_DERIVED*)p; // clone ani controllers:// apparently, you want to load one skinned mesh with a "master" controller, // then clone the master controller for each instance you want to draw. // so each skinned mesh instance will have a controller, plus you`ll have the master,// which is just used for cloning, not animating.// never really found out why everyone does it this way, // maybe that way you don`t have to reset everything in the clone, such as track position.// never tested using the master for both animating and cloning.// so ani_controller_ptr is the master, and ani_controller_ptr2 and 3 are for the two instances the test draws.clone_controller(ani_controller_ptr,&ani_controller_ptr2);// init controller to play the 1st animationset_ani(ani_controller_ptr2,0);clone_controller(ani_controller_ptr,&ani_controller_ptr3);// init controller to play the 2nd animationset_ani(ani_controller_ptr3,1);// set a directional light...Zsetlite(0,0.0f,-1.0f,1.0f,1.0f);// turn it on...Zlite(0,1);// set view matrix....Zsetcam(0.0f,5.0f,-10.0f,0.0f,0.0f,0.0f);quit=0;// timer 7 ET is used for the rotation of the quad in drawhat2Zstarttimer(7);// set material to z3d default material....Zsetmaterial(0);AAturnon();while (!quit) {// timer 8 is used for framerate limiter.// like Caveman 3.0, the test runs at 15fps. i`ve also run it at 60 fps no problem. Zstarttimer(8); Zclearscreen(32,0,32); Zclearzbuf(); Zbeginscene(); // set textures for 1st skinned mesh... pBody->texID=387; pBra->texID=159; pLoincloth->texID=159; // init z3d matrix manipulator to identity matrix... Mstart();// concatenate on a traslation matrix... Mmove(-1.0f,0.0f,0.0f);// update and draw skinned mesh with one call... update_and_draw_skinned_mesh(ani_controller_ptr2,0.066,g_pFrameRoot,&Mmat); // drawmeshcontainer (all versions) changes the current mesh and fvf, must reset...// set z3d state manager current mesh to none... Zcurrentmesh=-1;// reset FVF back to z3d FVF... Zd3d_device_ptr->SetFVF(ZFVF); // clear render queue... Zcleardrawlist();// ok, draw some eyeballs attached to the head bone (now that its updated)...// this call adds them to the render queue. draw_eyeballs(bone1,266);// turn off ligting (makes the quad easier to see)... Zlighting(0);// render the render queue... Zdrawlist();// turn lighting back on Zlighting(1); // set textures for 2nd skinned mesh... pBody->texID=388; pBra->texID=192; pLoincloth->texID=192; // init z3d matrix manipulator to identity matrix... Mstart();// concatenate on a traslation matrix... Mmove(1.0f,0.0f,0.0f);// update and draw skinned mesh with one call... update_and_draw_skinned_mesh(ani_controller_ptr3,0.066,g_pFrameRoot,&Mmat); // drawmeshcontainer (all versions) changes the current mesh and fvf, must reset...// set z3d state manager current mesh to none... Zcurrentmesh=-1;// reset FVF back to z3d FVF... Zd3d_device_ptr->SetFVF(ZFVF); // clear render queue... Zcleardrawlist();// draw quad relative to bone1. this call adds the quad to the render queue... draw_hat3(bone1);// turn off ligting (makes the quad easier to see)... Zlighting(0);// render the render queue... Zdrawlist();// turn lighting back on Zlighting(1); tx(100,800,"press ESC to quit"); tx(100,850,"press SPACE to change animation"); AAdraw();// end scene and present... Zshowscene();// if they hit spacebar, toggle the 1st skinned mesh to the next animation... if (Zkeypressed(VK_SPACE)) { change_ani(ani_controller_ptr2,num_anis); while (Zkeypressed(VK_SPACE)) { } }// if they hit escape, quit the test... if (Zkeypressed(VK_ESCAPE)) { quit=1; } AAprocess_input();// framerate limiter: wait until 66 millisconds has passed before beginning next frame... while (Zelapsedtime(8)<66) { } }msg3("unloading stuff...");AAturnoff();// unload the mesh and master controller...unload_skinned_mesh(ani_controller_ptr,g_pFrameRoot);// release the clones...ani_controller_ptr2->Release();ani_controller_ptr3->Release();msg3("all done!");} /*skinned mesh info: exporting from blender: 1. link the 1st ani to the armature object. leave the rest unlinked with a fake user. or leave all unlinked with fake users. do not link animations to meshes - only link them to the skeleton! 2. select the armature and mesh objects, then export selected objects.3. delete the default_animation from the exported .x file with notepad. not doing so seems to interfere with changing animations. not true! xyz eulers arent exported!4. export fails if more than 1 armature in the scene.5. ALL animation sets are exported from startframe through endframe. for animationsets with different lengths, they must be exported to different files, then paseted together with notepad.6. only quaternion rotations are exported? xyz eulers dont get exported? apparently so!7. apply modifiers (armature modifier) is not required.8. export fps seems to be required. unable to determine default value of anim ticks per sec.9. export default pose seems to not matter - not required. possible optimizations:1. different skinning methods. test each. only nonindexed fixed function pure device hw vertex blending has been tested so far. but theres only about 6 fps difference on my pc with tiny sample between non-indexed fixed function hw vertex blending and indexed shader methods.2. AnimationSet resource pooling.3. body mesh resource pooling w/ interchangeable heads.4. convert the static meshmaster and controller pools to dynamic memory (vectors, etc)5. reorganize from PODs and procedural code into into oo classes. the meshmaster and controller pools would be classes. pretty much everything else would be methods in those classes or part of low level classes used by the meshmaster and controller pool classes.6. add ability to draw a mesh from a skinned mesh using the render queue separate static mesh attached to a bone:hair clothes - might need to be rigged to the skeletonwpns & equipment - some might need to be rigged to the skeleton different textures:skintoneseye colors different meshes:single mesh for head and body. separate mesh for eyes. faces (wide / narrow nose, eye shape, etc): separate heads can leave a seam. editing complete meshes is slightly less work (dont have to detach a head). therefore, caveman will use a single head and body mesh. this means: create a complete head and body mesh for each face. setting texture on a per mesh basis vs not at all makes no difference in fps when drawing 100 meshes. the bottleneck is the number of tris, not setting textures. sharing the same animation between two different skinned meshes:1. create, rig, and animate the first skinned mesh. save and export.2. delete the mesh, leaving the animated rig.3. append the second mesh (load it into the existing scene)4. rig the mesh. save and export. all done! skinned mesh LOD:1. create and export mesh as usual2. decimate mesh, then export again.3. load both meshes4. cm or npc now has two meshIDs, not one. one high LOD, one low LOD.5. when its time to draw, decide hi or low LOD based on distance. 6. if the controller`s mesh is not the LOD you want, get the current animation time from the controller, destroy the mesh instance, create a mesh instance with the correct LOD mesh, then set the new controllers time to the animation time you got from the old controller. this way the animation continues from where it was.7. update and draw as usual. adapting all this for use in caveman:every skinned mesh loaded will have a "master" animation controller to go with it.so you'll have a struct that holds a skinned mesh, and its master animation controller.and you'll have a second type of struct for an instance of a skinned mesh, with a cloned animation controller, and a pointer to the skinned mesh to use.then you'll have an array of each of these structs.and a load_mesh routine that loads a mesh into the array of skinned meshes & master controllers.and a activate_controller routine that activates a struct in the array of cloned controllers, clones the controller, and sets the skinned mesh pointer (g_pFrameRoot).then you'll have a draw_instance routine that draws the skinned mesh using a cloned controller and its skinned mesh pointer.and you'll need a deactivate_controller routineand an unload_mesh routinea skinned mesh struct should also hold a pointer to the head bone for drawing hair and eyesattached to the head bone, and pointers to the various meshcontainers (body, bra, loincloth, etc)for setting textures on the fly. */
    Norman Barrows
    skinned mesh code part 1

    low level Microsoft code from DXUT.h and tiny.cpp. June 2010 DX9.0c SDK.
    // -------------------------------------------------------------------------------// ----------------------------- directx skinned mesh code -----------------------// ------------------------------------------------------------------------------- // CONTENTS:// section 1: description of the code// section 2: a brief outline of how to use the code// section 3: low level MS code// section 4: high level ROCKAND skinned mesh API// section 5: skinned mesh test routine sample code// section 6: notes on working with skinned meshes // this code is based on the tiny.cpp sample from june 2010 dx sdk. // it allows easy integration of skinned meshes into existing dx9 apps. // it consists of two parts: // 1. low level code copied directly from tiny.cpp that loads and unloads a mesh, // and updates the skeleton with the meshe`s world matrix. this code has been // modified to use ansi strings, dx9 matrices, and to supress compiler warnings// (one unused parameter, and one non-explicit cast).// its also been modified to only search the current directory for files.// for dx10, unicode, and DXUT path searching, just use the corresponding original // tiny.cpp code instead.// the unicode and DXUT path searching has only been commented out, // and can easily be turned back on if desired.// replacing D3DXMATRIX with D3DXMATRIX16A will convert it back to dx10 matrices. // 2. a high level API written by ROCKLAND, based on the tiny.cpp and DXUT code.// this API handles loading and unloading effects and shaders, getting device caps,// cloning and updating controllers, setting up shader parameters for drawing a // skinned mesh, adding the skinned mesh`s world matrix to the skeleton, // drawing the mesh, and changing animations. it also provides a routine to get // a pointer to a bone by name for later drawing of meshes attached to that bone. // this code includes all sample skinned mesh and DXUT code required - macros, struct // definitions, etc. no DXUT required! #include should be all thats required. // note that its not based on the multi-animation sample, so it does not have a// class definition for animated skinned mesh instances. an animated skinned mesh // instance basically consists of the instance`s cloned controller, the instance`s // world matrix, and a pointer to the root frame of the skeleton used by the instance.// you might want to add the instance`s texture and material if you use the// drawmeshcontainer4 routine, which allows materials and textures on a per// instance basis. // in some areas, debug version only code might be commented out. // uncomment it if you`re compiling in debug mode. // note that this code can easily be turned into a stand alone module with its own // header file. i simply haven`t gotten around to it yet.// just copy the declarations for the ROCKLAND skinned mesh API into a header file,// turn off the test routine, and replace any Z3d specifc variable names (IE the device // pointer, the view matrix, and the projection matrix).// so while its not a turnkey library yet, it has everything you need to make one easily. /* a basic outline of how to use the high level ROCKLAND skinned mesh API: // init stuff...get_indexed_behavior_flags // only required for indexed skinning methodsload_skinned_meshload_effect_file // only required for shader based skinning methodsload_shaders // only required for shader based skinning methodsclone_cntroller // optional - for multiple instances of a skinned mesh, each with it own animation.get_bone // optional - get pointer to a bone by name, used to draw meshes attached to a bone.// render loop...while !quit// set the light for the fixed function pipeline. IE: Zsetlite and Zlite in z3d// set the view matrix. IE: Zsetcam in z3d// setup for drawing skinned mesh... set_D3DINDEXEDVS_proj_mat_transpose // only required for D3DINDEXEDVS skinning method. // must be called before set_indexed_shader_proj_mat. set_indexed_shader_proj_mat // only required for indexed shaders. not required for D3DINDEXED skinning method. set_shader_light // only required for shader based skinning methods set_ani // optional: change to a specific animation first// update the animation controller... update_ani // add the world transform for the skinned mesh to the skeleton... add_world_transform_to_skeleton// draw it! draw_skinned_mesh// drawsubset changes the current mesh and fvf! dont forget to reset them before regular drawing!// reset fvf and your state manager`s current mesh here, then draw as usual.// draw some non-skinned meshes here...}// shutdown...unload_shaders // only required for shader based skinning methodsunload_effect_file // only required for shader based skinning methodsunload_skinned_mesh// release any cloned controllers! */ // ----------------------------- LOW LEVEL MS CODE ------------------------------ // ---------------------- DXUT.h macros ----------------------------------------- #if defined(DEBUG) || defined(_DEBUG)#ifndef V#define V(x) { hr = (x); if( FAILED(hr) ) { DXUTTrace( __FILE__, (DWORD)__LINE__, hr, L#x, true ); } }#endif#ifndef V_RETURN#define V_RETURN(x) { hr = (x); if( FAILED(hr) ) { return DXUTTrace( __FILE__, (DWORD)__LINE__, hr, L#x, true ); } }#endif#else#ifndef V#define V(x) { hr = (x); }#endif#ifndef V_RETURN#define V_RETURN(x) { hr = (x); if( FAILED(hr) ) { return hr; } }#endif#endif #ifndef SAFE_DELETE#define SAFE_DELETE(p) { if (p) { delete (p); (p)=NULL; } }#endif #ifndef SAFE_DELETE_ARRAY#define SAFE_DELETE_ARRAY(p) { if (p) { delete[] (p); (p)=NULL; } }#endif #ifndef SAFE_RELEASE#define SAFE_RELEASE(p) { if (p) { (p)->Release(); (p)=NULL; } }#endif // ------------------------------- tiny.cpp code ------------------------------------ //--------------------------------------------------------------------------------------// Name: struct D3DXFRAME_DERIVED// Desc: Structure derived from D3DXFRAME so we can add some app-specific// info that will be stored with each frame//--------------------------------------------------------------------------------------struct D3DXFRAME_DERIVED : public D3DXFRAME{ D3DXMATRIX CombinedTransformationMatrix;}; //--------------------------------------------------------------------------------------// Name: struct D3DXMESHCONTAINER_DERIVED// Desc: Structure derived from D3DXMESHCONTAINER so we can add some app-specific// info that will be stored with each mesh//--------------------------------------------------------------------------------------struct D3DXMESHCONTAINER_DERIVED : public D3DXMESHCONTAINER{ LPDIRECT3DTEXTURE9* ppTextures; // array of textures, entries are NULL if no texture specified // SkinMesh info LPD3DXMESH pOrigMesh; LPD3DXATTRIBUTERANGE pAttributeTable; DWORD NumAttributeGroups; DWORD NumInfl; LPD3DXBUFFER pBoneCombinationBuf; D3DXMATRIX** ppBoneMatrixPtrs; D3DXMATRIX* pBoneOffsetMatrices; DWORD NumPaletteEntries; bool UseSoftwareVP; DWORD iAttributeSW; // used to denote the split between SW and HW if necessary for non-indexed skinning int texID;}; // enum for various skinning modes possibleenum METHOD{ D3DNONINDEXED, D3DINDEXED, SOFTWARE, D3DINDEXEDVS, D3DINDEXEDHLSLVS, NONE}; //--------------------------------------------------------------------------------------// Global variables//-------------------------------------------------------------------------------------- METHOD g_SkinningMethod = D3DNONINDEXED; // Current skinning method bool g_bUseSoftwareVP; // Flag to indicate whether software vp is // required due to lack of hardware UINT g_NumBoneMatricesMax = 0; D3DXMATRIX *g_pBoneMatrices = NULL; D3DXMATRIX g_matProjT; // Transpose of projection matrix (for asm shader) ID3DXEffect* g_pEffect = NULL; // D3DX effect interface DWORD g_dwBehaviorFlags; // Behavior flags of the 3D device IDirect3DVertexShader9* g_pIndexedVertexShader[4]; // array of four vertex shaders char g_wszShaderSource[4][30] ={ "skinmesh1.vsh", "skinmesh2.vsh", "skinmesh3.vsh", "skinmesh4.vsh"}; /*WCHAR g_wszShaderSource[4][30] ={ L"skinmesh1.vsh", L"skinmesh2.vsh", L"skinmesh3.vsh", L"skinmesh4.vsh"};*/ //--------------------------------------------------------------------------------------// Called either by CreateMeshContainer when loading a skin mesh, or when // changing methods. This function uses the pSkinInfo of the mesh // container to generate the desired drawable mesh and bone combination // table.//--------------------------------------------------------------------------------------HRESULT GenerateSkinnedMesh( IDirect3DDevice9* pd3dDevice, D3DXMESHCONTAINER_DERIVED* pMeshContainer ){ HRESULT hr = S_OK; D3DCAPS9 d3dCaps; pd3dDevice->GetDeviceCaps( &d3dCaps ); if( pMeshContainer->pSkinInfo == NULL ) return hr; g_bUseSoftwareVP = false; SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf ); // if non-indexed skinning mode selected, use ConvertToBlendedMesh to generate drawable mesh if( g_SkinningMethod == D3DNONINDEXED ) { hr = pMeshContainer->pSkinInfo->ConvertToBlendedMesh ( pMeshContainer->pOrigMesh, D3DXMESH_MANAGED | D3DXMESHOPT_VERTEXCACHE, pMeshContainer->pAdjacency, NULL, NULL, NULL, &pMeshContainer->NumInfl, &pMeshContainer->NumAttributeGroups, &pMeshContainer->pBoneCombinationBuf, &pMeshContainer->MeshData.pMesh ); if( FAILED( hr ) ) goto e_Exit; // If the device can only do 2 matrix blends, ConvertToBlendedMesh cannot approximate all meshes to it // Thus we split the mesh in two parts: The part that uses at most 2 matrices and the rest. The first is // drawn using the device's HW vertex processing and the rest is drawn using SW vertex processing. LPD3DXBONECOMBINATION rgBoneCombinations = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() ); // look for any set of bone combinations that do not fit the caps for( pMeshContainer->iAttributeSW = 0; pMeshContainer->iAttributeSW < pMeshContainer->NumAttributeGroups; pMeshContainer->iAttributeSW++ ) { DWORD cInfl = 0; for( DWORD iInfl = 0; iInfl < pMeshContainer->NumInfl; iInfl++ ) { if( rgBoneCombinations[pMeshContainer->iAttributeSW].BoneId[iInfl] != UINT_MAX ) { ++cInfl; } } if( cInfl > d3dCaps.MaxVertexBlendMatrices ) { break; } } // if there is both HW and SW, add the Software Processing flag if( pMeshContainer->iAttributeSW < pMeshContainer->NumAttributeGroups ) { LPD3DXMESH pMeshTmp; hr = pMeshContainer->MeshData.pMesh->CloneMeshFVF( D3DXMESH_SOFTWAREPROCESSING | pMeshContainer->MeshData.pMesh->GetOptions(), pMeshContainer->MeshData.pMesh->GetFVF(), pd3dDevice, &pMeshTmp ); if( FAILED( hr ) ) { goto e_Exit; } pMeshContainer->MeshData.pMesh->Release(); pMeshContainer->MeshData.pMesh = pMeshTmp; pMeshTmp = NULL; } } // if indexed skinning mode selected, use ConvertToIndexedsBlendedMesh to generate drawable mesh else if( g_SkinningMethod == D3DINDEXED ) { DWORD NumMaxFaceInfl; DWORD Flags = D3DXMESHOPT_VERTEXCACHE; LPDIRECT3DINDEXBUFFER9 pIB; hr = pMeshContainer->pOrigMesh->GetIndexBuffer( &pIB ); if( FAILED( hr ) ) goto e_Exit; hr = pMeshContainer->pSkinInfo->GetMaxFaceInfluences( pIB, pMeshContainer->pOrigMesh->GetNumFaces(), &NumMaxFaceInfl ); pIB->Release(); if( FAILED( hr ) ) goto e_Exit; // 12 entry palette guarantees that any triangle (4 independent influences per vertex of a tri) // can be handled NumMaxFaceInfl = min( NumMaxFaceInfl, 12 ); if( d3dCaps.MaxVertexBlendMatrixIndex + 1 < NumMaxFaceInfl ) { // HW does not support indexed vertex blending. Use SW instead pMeshContainer->NumPaletteEntries = min( 256, pMeshContainer->pSkinInfo->GetNumBones() ); pMeshContainer->UseSoftwareVP = true; g_bUseSoftwareVP = true; Flags |= D3DXMESH_SYSTEMMEM; } else { // using hardware - determine palette size from caps and number of bones // If normals are present in the vertex data that needs to be blended for lighting, then // the number of matrices is half the number specified by MaxVertexBlendMatrixIndex. pMeshContainer->NumPaletteEntries = min( ( d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2, pMeshContainer->pSkinInfo->GetNumBones() ); pMeshContainer->UseSoftwareVP = false; Flags |= D3DXMESH_MANAGED; } hr = pMeshContainer->pSkinInfo->ConvertToIndexedBlendedMesh ( pMeshContainer->pOrigMesh, Flags, pMeshContainer->NumPaletteEntries, pMeshContainer->pAdjacency, NULL, NULL, NULL, &pMeshContainer->NumInfl, &pMeshContainer->NumAttributeGroups, &pMeshContainer->pBoneCombinationBuf, &pMeshContainer->MeshData.pMesh ); if( FAILED( hr ) ) goto e_Exit; } // if vertex shader indexed skinning mode selected, use ConvertToIndexedsBlendedMesh to generate drawable mesh else if( ( g_SkinningMethod == D3DINDEXEDVS ) || ( g_SkinningMethod == D3DINDEXEDHLSLVS ) ) { // Get palette size // First 9 constants are used for other data. Each 4x3 matrix takes up 3 constants. // (96 - 9) /3 i.e. Maximum constant count - used constants UINT MaxMatrices = 26; pMeshContainer->NumPaletteEntries = min( MaxMatrices, pMeshContainer->pSkinInfo->GetNumBones() ); DWORD Flags = D3DXMESHOPT_VERTEXCACHE; if( d3dCaps.VertexShaderVersion >= D3DVS_VERSION( 1, 1 ) ) { pMeshContainer->UseSoftwareVP = false; Flags |= D3DXMESH_MANAGED; } else { pMeshContainer->UseSoftwareVP = true; g_bUseSoftwareVP = true; Flags |= D3DXMESH_SYSTEMMEM; } SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); hr = pMeshContainer->pSkinInfo->ConvertToIndexedBlendedMesh ( pMeshContainer->pOrigMesh, Flags, pMeshContainer->NumPaletteEntries, pMeshContainer->pAdjacency, NULL, NULL, NULL, &pMeshContainer->NumInfl, &pMeshContainer->NumAttributeGroups, &pMeshContainer->pBoneCombinationBuf, &pMeshContainer->MeshData.pMesh ); if( FAILED( hr ) ) goto e_Exit; // FVF has to match our declarator. Vertex shaders are not as forgiving as FF pipeline DWORD NewFVF = ( pMeshContainer->MeshData.pMesh->GetFVF() & D3DFVF_POSITION_MASK ) | D3DFVF_NORMAL | D3DFVF_TEX1 | D3DFVF_LASTBETA_UBYTE4; if( NewFVF != pMeshContainer->MeshData.pMesh->GetFVF() ) { LPD3DXMESH pMesh; hr = pMeshContainer->MeshData.pMesh->CloneMeshFVF( pMeshContainer->MeshData.pMesh->GetOptions(), NewFVF, pd3dDevice, &pMesh ); if( !FAILED( hr ) ) { pMeshContainer->MeshData.pMesh->Release(); pMeshContainer->MeshData.pMesh = pMesh; pMesh = NULL; } } D3DVERTEXELEMENT9 pDecl[MAX_FVF_DECL_SIZE]; LPD3DVERTEXELEMENT9 pDeclCur; hr = pMeshContainer->MeshData.pMesh->GetDeclaration( pDecl ); if( FAILED( hr ) ) goto e_Exit; // the vertex shader is expecting to interpret the UBYTE4 as a D3DCOLOR, so update the type // NOTE: this cannot be done with CloneMesh, that would convert the UBYTE4 data to float and then to D3DCOLOR // this is more of a "cast" operation pDeclCur = pDecl; while( pDeclCur->Stream != 0xff ) { if( ( pDeclCur->Usage == D3DDECLUSAGE_BLENDINDICES ) && ( pDeclCur->UsageIndex == 0 ) ) pDeclCur->Type = D3DDECLTYPE_D3DCOLOR; pDeclCur++; } hr = pMeshContainer->MeshData.pMesh->UpdateSemantics( pDecl ); if( FAILED( hr ) ) goto e_Exit; // allocate a buffer for bone matrices, but only if another mesh has not allocated one of the same size or larger if( g_NumBoneMatricesMax < pMeshContainer->pSkinInfo->GetNumBones() ) { g_NumBoneMatricesMax = pMeshContainer->pSkinInfo->GetNumBones(); // Allocate space for blend matrices delete[] g_pBoneMatrices; g_pBoneMatrices = new D3DXMATRIX[g_NumBoneMatricesMax]; if( g_pBoneMatrices == NULL ) { hr = E_OUTOFMEMORY; goto e_Exit; } } } // if software skinning selected, use GenerateSkinnedMesh to create a mesh that can be used with UpdateSkinnedMesh else if( g_SkinningMethod == SOFTWARE ) { hr = pMeshContainer->pOrigMesh->CloneMeshFVF( D3DXMESH_MANAGED, pMeshContainer->pOrigMesh->GetFVF(), pd3dDevice, &pMeshContainer->MeshData.pMesh ); if( FAILED( hr ) ) goto e_Exit; hr = pMeshContainer->MeshData.pMesh->GetAttributeTable( NULL, &pMeshContainer->NumAttributeGroups ); if( FAILED( hr ) ) goto e_Exit; delete[] pMeshContainer->pAttributeTable; pMeshContainer->pAttributeTable = new D3DXATTRIBUTERANGE[pMeshContainer->NumAttributeGroups]; if( pMeshContainer->pAttributeTable == NULL ) { hr = E_OUTOFMEMORY; goto e_Exit; } hr = pMeshContainer->MeshData.pMesh->GetAttributeTable( pMeshContainer->pAttributeTable, NULL ); if( FAILED( hr ) ) goto e_Exit; // allocate a buffer for bone matrices, but only if another mesh has not allocated one of the same size or larger if( g_NumBoneMatricesMax < pMeshContainer->pSkinInfo->GetNumBones() ) { g_NumBoneMatricesMax = pMeshContainer->pSkinInfo->GetNumBones(); // Allocate space for blend matrices delete[] g_pBoneMatrices; g_pBoneMatrices = new D3DXMATRIX[g_NumBoneMatricesMax]; if( g_pBoneMatrices == NULL ) { hr = E_OUTOFMEMORY; goto e_Exit; } } } else // invalid g_SkinningMethod value { // return failure due to invalid skinning method value hr = E_INVALIDARG; goto e_Exit; } e_Exit: return hr;} //--------------------------------------------------------------------------------------// Name: class CAllocateHierarchy// Desc: Custom version of ID3DXAllocateHierarchy with custom methods to create// frames and meshcontainers.//--------------------------------------------------------------------------------------class CAllocateHierarchy : public ID3DXAllocateHierarchy{public: STDMETHOD( CreateFrame )( THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame ); STDMETHOD( CreateMeshContainer )( THIS_ LPCSTR Name, CONST D3DXMESHDATA *pMeshData, CONST D3DXMATERIAL *pMaterials, CONST D3DXEFFECTINSTANCE *pEffectInstances, DWORD NumMaterials, CONST DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer ); STDMETHOD( DestroyFrame )( THIS_ LPD3DXFRAME pFrameToFree ); STDMETHOD( DestroyMeshContainer )( THIS_ LPD3DXMESHCONTAINER pMeshContainerBase ); CAllocateHierarchy() { }}; //--------------------------------------------------------------------------------------// Name: AllocateName()// Desc: Allocates memory for a string to hold the name of a frame or mesh//--------------------------------------------------------------------------------------HRESULT AllocateName( LPCSTR Name, LPSTR* pNewName ){ UINT cbLength; if( Name != NULL ) { cbLength = ( UINT )strlen( Name ) + 1; *pNewName = new CHAR[cbLength]; if( *pNewName == NULL ) return E_OUTOFMEMORY; memcpy( *pNewName, Name, cbLength * sizeof( CHAR ) ); } else { *pNewName = NULL; } return S_OK;} //--------------------------------------------------------------------------------------// Name: CAllocateHierarchy::CreateFrame()// Desc: //--------------------------------------------------------------------------------------HRESULT CAllocateHierarchy::CreateFrame( LPCSTR Name, LPD3DXFRAME* ppNewFrame ){ HRESULT hr = S_OK; D3DXFRAME_DERIVED* pFrame; *ppNewFrame = NULL; pFrame = new D3DXFRAME_DERIVED; if( pFrame == NULL ) { hr = E_OUTOFMEMORY; goto e_Exit; } hr = AllocateName( Name, &pFrame->Name ); if( FAILED( hr ) ) goto e_Exit; // initialize other data members of the frame D3DXMatrixIdentity( &pFrame->TransformationMatrix ); D3DXMatrixIdentity( &pFrame->CombinedTransformationMatrix ); pFrame->pMeshContainer = NULL; pFrame->pFrameSibling = NULL; pFrame->pFrameFirstChild = NULL; *ppNewFrame = pFrame; pFrame = NULL; e_Exit: delete pFrame; return hr;} //--------------------------------------------------------------------------------------// Name: CAllocateHierarchy::CreateMeshContainer()// Desc: //--------------------------------------------------------------------------------------HRESULT CAllocateHierarchy::CreateMeshContainer( LPCSTR Name, CONST D3DXMESHDATA *pMeshData, CONST D3DXMATERIAL *pMaterials, CONST D3DXEFFECTINSTANCE *pEffectInstances, DWORD NumMaterials, CONST DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer ) { HRESULT hr; D3DXMESHCONTAINER_DERIVED *pMeshContainer = NULL; UINT NumFaces; UINT iMaterial; UINT iBone, cBones; LPDIRECT3DDEVICE9 pd3dDevice = NULL; LPD3DXMESH pMesh = NULL; *ppNewMeshContainer = NULL; // ROCKLAND NOTE:// the pEffectInstances parameter isnt used in the code, causing a warning!// so we need to use it to supress the warning.// pEffectInstances is CONST, so we cant just set it to NULL.// we have to assign its value to another variable.// so we define another D3DXEFFECTINSTANCE pointer, and set it equal to pEffectInstances.CONST D3DXEFFECTINSTANCE *tmp;tmp=pEffectInstances; // this sample does not handle patch meshes, so fail when one is found if( pMeshData->Type != D3DXMESHTYPE_MESH ) { hr = E_FAIL; goto e_Exit; } // get the pMesh interface pointer out of the mesh data structure pMesh = pMeshData->pMesh; // this sample does not FVF compatible meshes, so fail when one is found if( pMesh->GetFVF() == 0 ) { hr = E_FAIL; goto e_Exit; } // allocate the overloaded structure to return as a D3DXMESHCONTAINER pMeshContainer = new D3DXMESHCONTAINER_DERIVED; if( pMeshContainer == NULL ) { hr = E_OUTOFMEMORY; goto e_Exit; } memset( pMeshContainer, 0, sizeof( D3DXMESHCONTAINER_DERIVED ) ); // make sure and copy the name. All memory as input belongs to caller, interfaces can be addref'd though hr = AllocateName( Name, &pMeshContainer->Name ); if( FAILED( hr ) ) goto e_Exit; pMesh->GetDevice( &pd3dDevice ); NumFaces = pMesh->GetNumFaces(); // if no normals are in the mesh, add them if( !( pMesh->GetFVF() & D3DFVF_NORMAL ) ) { pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH; // clone the mesh to make room for the normals hr = pMesh->CloneMeshFVF( pMesh->GetOptions(), pMesh->GetFVF() | D3DFVF_NORMAL, pd3dDevice, &pMeshContainer->MeshData.pMesh ); if( FAILED( hr ) ) goto e_Exit; // get the new pMesh pointer back out of the mesh container to use // NOTE: we do not release pMesh because we do not have a reference to it yet pMesh = pMeshContainer->MeshData.pMesh; // now generate the normals for the pmesh D3DXComputeNormals( pMesh, NULL ); } else // if no normals, just add a reference to the mesh for the mesh container { pMeshContainer->MeshData.pMesh = pMesh; pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH; pMesh->AddRef(); } // allocate memory to contain the material information. This sample uses // the D3D9 materials and texture names instead of the EffectInstance style materials pMeshContainer->NumMaterials = max( 1, NumMaterials ); pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials]; pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials]; pMeshContainer->pAdjacency = new DWORD[NumFaces*3]; if( ( pMeshContainer->pAdjacency == NULL ) || ( pMeshContainer->pMaterials == NULL ) ) { hr = E_OUTOFMEMORY; goto e_Exit; } memcpy( pMeshContainer->pAdjacency, pAdjacency, sizeof( DWORD ) * NumFaces*3 ); memset( pMeshContainer->ppTextures, 0, sizeof( LPDIRECT3DTEXTURE9 ) * pMeshContainer->NumMaterials ); // if materials provided, copy them if( NumMaterials > 0 ) { memcpy( pMeshContainer->pMaterials, pMaterials, sizeof( D3DXMATERIAL ) * NumMaterials ); for( iMaterial = 0; iMaterial < NumMaterials; iMaterial++ ) { if( pMeshContainer->pMaterials[iMaterial].pTextureFilename != NULL ) {// ROCKLAND NOTE: caveman uses ansi strings, not unicode.// so this code must be changed from unicode to ansi. // WCHAR strTexturePath[MAX_PATH];// WCHAR wszBuf[MAX_PATH];// MultiByteToWideChar( CP_ACP, 0, pMeshContainer->pMaterials[iMaterial].pTextureFilename, -1, wszBuf, MAX_PATH );// wszBuf[MAX_PATH - 1] = L'\0';// DXUTFindDXSDKMediaFileCch( strTexturePath, MAX_PATH, wszBuf );// if( FAILED( D3DXCreateTextureFromFile( pd3dDevice, strTexturePath,// &pMeshContainer->ppTextures[iMaterial] ) ) ) if( FAILED( D3DXCreateTextureFromFile( pd3dDevice, pMeshContainer->pMaterials[iMaterial].pTextureFilename, &pMeshContainer->ppTextures[iMaterial] ) ) ) pMeshContainer->ppTextures[iMaterial] = NULL; // don't remember a pointer into the dynamic memory, just forget the name after loading pMeshContainer->pMaterials[iMaterial].pTextureFilename = NULL; } } } else // if no materials provided, use a default one { pMeshContainer->pMaterials[0].pTextureFilename = NULL; memset( &pMeshContainer->pMaterials[0].MatD3D, 0, sizeof( D3DMATERIAL9 ) ); pMeshContainer->pMaterials[0].MatD3D.Diffuse.r = 0.5f; pMeshContainer->pMaterials[0].MatD3D.Diffuse.g = 0.5f; pMeshContainer->pMaterials[0].MatD3D.Diffuse.b = 0.5f; pMeshContainer->pMaterials[0].MatD3D.Specular = pMeshContainer->pMaterials[0].MatD3D.Diffuse; } // if there is skinning information, save off the required data and then setup for HW skinning if( pSkinInfo != NULL ) { // first save off the SkinInfo and original mesh data pMeshContainer->pSkinInfo = pSkinInfo; pSkinInfo->AddRef(); pMeshContainer->pOrigMesh = pMesh; pMesh->AddRef(); // Will need an array of offset matrices to move the vertices from the figure space to the bone's space cBones = pSkinInfo->GetNumBones(); pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones]; if( pMeshContainer->pBoneOffsetMatrices == NULL ) { hr = E_OUTOFMEMORY; goto e_Exit; } // get each of the bone offset matrices so that we don't need to get them later for( iBone = 0; iBone < cBones; iBone++ ) { pMeshContainer->pBoneOffsetMatrices[iBone] = *( pMeshContainer->pSkinInfo->GetBoneOffsetMatrix( iBone ) ); } // GenerateSkinnedMesh will take the general skinning information and transform it to a HW friendly version hr = GenerateSkinnedMesh( pd3dDevice, pMeshContainer ); if( FAILED( hr ) ) goto e_Exit; } *ppNewMeshContainer = pMeshContainer; pMeshContainer = NULL; e_Exit: SAFE_RELEASE( pd3dDevice ); // call Destroy function to properly clean up the memory allocated if( pMeshContainer != NULL ) { DestroyMeshContainer( pMeshContainer ); } return hr;} //--------------------------------------------------------------------------------------// Name: CAllocateHierarchy::DestroyFrame()// Desc: //--------------------------------------------------------------------------------------HRESULT CAllocateHierarchy::DestroyFrame( LPD3DXFRAME pFrameToFree ){ SAFE_DELETE_ARRAY( pFrameToFree->Name ); SAFE_DELETE( pFrameToFree ); return S_OK;} //--------------------------------------------------------------------------------------// Name: CAllocateHierarchy::DestroyMeshContainer()// Desc: //--------------------------------------------------------------------------------------HRESULT CAllocateHierarchy::DestroyMeshContainer( LPD3DXMESHCONTAINER pMeshContainerBase ){ UINT iMaterial; D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase; SAFE_DELETE_ARRAY( pMeshContainer->Name ); SAFE_DELETE_ARRAY( pMeshContainer->pAdjacency ); SAFE_DELETE_ARRAY( pMeshContainer->pMaterials ); SAFE_DELETE_ARRAY( pMeshContainer->pBoneOffsetMatrices ); // release all the allocated textures if( pMeshContainer->ppTextures != NULL ) { for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ ) { SAFE_RELEASE( pMeshContainer->ppTextures[iMaterial] ); } } SAFE_DELETE_ARRAY( pMeshContainer->ppTextures ); SAFE_DELETE_ARRAY( pMeshContainer->ppBoneMatrixPtrs ); SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf ); SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); SAFE_RELEASE( pMeshContainer->pSkinInfo ); SAFE_RELEASE( pMeshContainer->pOrigMesh ); SAFE_DELETE( pMeshContainer ); return S_OK;} //--------------------------------------------------------------------------------------// Called to setup the pointers for a given bone to its transformation matrix//--------------------------------------------------------------------------------------HRESULT SetupBoneMatrixPointersOnMesh( LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME g_pFrameRoot){UINT iBone, cBones;D3DXFRAME_DERIVED* pFrame;D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;if( pMeshContainer->pSkinInfo != NULL ) // if there is a skinmesh, then setup the bone matrices { cBones = pMeshContainer->pSkinInfo->GetNumBones(); pMeshContainer->ppBoneMatrixPtrs = new D3DXMATRIX*[cBones]; if( pMeshContainer->ppBoneMatrixPtrs == NULL ) return E_OUTOFMEMORY; for( iBone = 0; iBone < cBones; iBone++ ) { pFrame = ( D3DXFRAME_DERIVED* )D3DXFrameFind( g_pFrameRoot,pMeshContainer->pSkinInfo->GetBoneName( iBone ) ); if( pFrame == NULL ) return E_FAIL; pMeshContainer->ppBoneMatrixPtrs[iBone] = &pFrame->CombinedTransformationMatrix; } }return S_OK;} //--------------------------------------------------------------------------------------// Called to setup the pointers for a given bone to its transformation matrix//--------------------------------------------------------------------------------------HRESULT SetupBoneMatrixPointers( LPD3DXFRAME pFrame, LPD3DXFRAME g_pFrameRoot ){HRESULT hr;if( pFrame->pMeshContainer != NULL ) { hr = SetupBoneMatrixPointersOnMesh( pFrame->pMeshContainer , g_pFrameRoot ); if ( FAILED( hr ) ) return hr; }if( pFrame->pFrameSibling != NULL ) { hr = SetupBoneMatrixPointers( pFrame->pFrameSibling , g_pFrameRoot ); if( FAILED( hr ) ) return hr; }if( pFrame->pFrameFirstChild != NULL ) { hr = SetupBoneMatrixPointers( pFrame->pFrameFirstChild , g_pFrameRoot ); if( FAILED( hr ) ) return hr; }return S_OK;} //--------------------------------------------------------------------------------------// update the frame matrices//--------------------------------------------------------------------------------------void UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix ){ D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase; if( pParentMatrix != NULL ) D3DXMatrixMultiply( &pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix ); else pFrame->CombinedTransformationMatrix = pFrame->TransformationMatrix; if( pFrame->pFrameSibling != NULL ) { UpdateFrameMatrices( pFrame->pFrameSibling, pParentMatrix ); } if( pFrame->pFrameFirstChild != NULL ) { UpdateFrameMatrices( pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix ); }} /* //--------------------------------------------------------------------------------------// Called to render a mesh in the hierarchy//--------------------------------------------------------------------------------------void DrawMeshContainer( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase, D3DXMATRIX *g_matView){HRESULT hr;D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase;UINT iMaterial;UINT NumBlend;UINT iAttrib;DWORD AttribIdPrev;LPD3DXBONECOMBINATION pBoneComb; UINT iMatrixIndex;UINT iPaletteEntry;D3DXMATRIX matTemp;D3DCAPS9 d3dCaps;pd3dDevice->GetDeviceCaps( &d3dCaps ); // first check for skinningif( pMeshContainer->pSkinInfo != NULL ) { if( g_SkinningMethod == D3DNONINDEXED ) { AttribIdPrev = UNUSED32; pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() ); // Draw using default vtx processing of the device (typically HW) for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 ) { // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); // lookup the material used for this subset of faces if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) { V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } // draw the subset now that the correct material and matrices are loaded V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); } } // If necessary, draw parts that HW could not handle using SW if( pMeshContainer->iAttributeSW < pMeshContainer->NumAttributeGroups ) { AttribIdPrev = UNUSED32; V( pd3dDevice->SetSoftwareVertexProcessing( TRUE ) ); for( iAttrib = pMeshContainer->iAttributeSW; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices < NumBlend + 1 ) { // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); // lookup the material used for this subset of faces if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) { V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } // draw the subset now that the correct material and matrices are loaded V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); } } V( pd3dDevice->SetSoftwareVertexProcessing( FALSE ) ); } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, 0 ) ); } else if( g_SkinningMethod == D3DINDEXED ) { // if hw doesn't support indexed vertex processing, switch to software vertex processing if( pMeshContainer->UseSoftwareVP ) { // If hw or pure hw vertex processing is forced, we can't render the // mesh, so just exit out. Typical applications should create // a device with appropriate vertex processing capability for this // skinning method. if( g_dwBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING ) return; V( pd3dDevice->SetSoftwareVertexProcessing( TRUE ) ); } // set the number of vertex blend indices to be blended if( pMeshContainer->NumInfl == 1 ) { V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_0WEIGHTS ) ); } else { V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, pMeshContainer->NumInfl - 1 ) ); } if( pMeshContainer->NumInfl ) V( pd3dDevice->SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE ) ); // for each attribute group in the mesh, calculate the set of matrices in the palette and then draw the mesh subset pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() ); for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { // first calculate all the world matrices for( iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry ) { iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry]; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( iPaletteEntry ), &matTemp ) ); } } // setup the material of the mesh subset - REMEMBER to use the original pre-skinning attribute id to get the correct material id V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); // finally draw the subset with the current world matrix palette and material state V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); } // reset blending state V( pd3dDevice->SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE ) ); V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, 0 ) ); // remember to reset back to hw vertex processing if software was required if( pMeshContainer->UseSoftwareVP ) { V( pd3dDevice->SetSoftwareVertexProcessing( FALSE ) ); } } else if( g_SkinningMethod == D3DINDEXEDVS ) { // Use COLOR instead of UBYTE4 since Geforce3 does not support it // vConst.w should be 3, but due to COLOR/UBYTE4 issue, mul by 255 and add epsilon D3DXVECTOR4 vConst( 1.0f, 0.0f, 0.0f, 765.01f ); if( pMeshContainer->UseSoftwareVP ) { // If hw or pure hw vertex processing is forced, we can't render the // mesh, so just exit out. Typical applications should create // a device with appropriate vertex processing capability for this // skinning method. if( g_dwBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING ) return; V( pd3dDevice->SetSoftwareVertexProcessing( TRUE ) ); } V( pd3dDevice->SetVertexShader( g_pIndexedVertexShader[pMeshContainer->NumInfl - 1] ) ); pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer () ); for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { // first calculate all the world matrices for( iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry ) { iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry]; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); D3DXMatrixMultiplyTranspose( &matTemp, &matTemp, g_matView ); V( pd3dDevice->SetVertexShaderConstantF( iPaletteEntry * 3 + 9, ( float* )&matTemp, 3 ) ); } } // Sum of all ambient and emissive contribution D3DXCOLOR color1( pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Ambient ); D3DXCOLOR color2( .25, .25, .25, 1.0 ); D3DXCOLOR ambEmm; D3DXColorModulate( &ambEmm, &color1, &color2 ); ambEmm += D3DXCOLOR( pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Emissive ); // set material color properties V( pd3dDevice->SetVertexShaderConstantF( 8, ( float* )&( pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Diffuse ), 1 ) ); V( pd3dDevice->SetVertexShaderConstantF( 7, ( float* )&ambEmm, 1 ) ); vConst.y = pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Power; V( pd3dDevice->SetVertexShaderConstantF( 0, ( float* )&vConst, 1 ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); // finally draw the subset with the current world matrix palette and material state V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); } // remember to reset back to hw vertex processing if software was required if( pMeshContainer->UseSoftwareVP ) { V( pd3dDevice->SetSoftwareVertexProcessing( FALSE ) ); } V( pd3dDevice->SetVertexShader( NULL ) ); } else if( g_SkinningMethod == D3DINDEXEDHLSLVS ) { if( pMeshContainer->UseSoftwareVP ) { // If hw or pure hw vertex processing is forced, we can't render the // mesh, so just exit out. Typical applications should create // a device with appropriate vertex processing capability for this // skinning method. if( g_dwBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING ) return; V( pd3dDevice->SetSoftwareVertexProcessing( TRUE ) ); } pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer () ); for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { // first calculate all the world matrices for( iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry ) { iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry]; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); D3DXMatrixMultiply( &g_pBoneMatrices[iPaletteEntry], &matTemp, g_matView ); } } V( g_pEffect->SetMatrixArray( "mWorldMatrixArray", g_pBoneMatrices, pMeshContainer->NumPaletteEntries ) ); // Sum of all ambient and emissive contribution D3DXCOLOR color1( pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Ambient ); D3DXCOLOR color2( .25, .25, .25, 1.0 ); D3DXCOLOR ambEmm; D3DXColorModulate( &ambEmm, &color1, &color2 ); ambEmm += D3DXCOLOR( pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Emissive ); // set material color properties V( g_pEffect->SetVector( "MaterialDiffuse", ( D3DXVECTOR4* )&( pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D.Diffuse ) ) ); V( g_pEffect->SetVector( "MaterialAmbient", ( D3DXVECTOR4* )&ambEmm ) ); // setup the material of the mesh subset - REMEMBER to use the original pre-skinning attribute id to get the correct material id V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); // Set CurNumBones to select the correct vertex shader for the number of bones// ROCKLAND NOTE: had to add explict cast from DWORD to int to suppress warning. V( g_pEffect->SetInt( "CurNumBones", (int)(pMeshContainer->NumInfl - 1) ) ); // Start the effect now all parameters have been updated UINT numPasses; V( g_pEffect->Begin( &numPasses, D3DXFX_DONOTSAVESTATE ) ); for( UINT iPass = 0; iPass < numPasses; iPass++ ) { V( g_pEffect->BeginPass( iPass ) ); // draw the subset with the current world matrix palette and material state V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); V( g_pEffect->EndPass() ); } V( g_pEffect->End() ); V( pd3dDevice->SetVertexShader( NULL ) ); } // remember to reset back to hw vertex processing if software was required if( pMeshContainer->UseSoftwareVP ) { V( pd3dDevice->SetSoftwareVertexProcessing( FALSE ) ); } } else if( g_SkinningMethod == SOFTWARE ) { D3DXMATRIX Identity; DWORD cBones = pMeshContainer->pSkinInfo->GetNumBones(); DWORD iBone; PBYTE pbVerticesSrc; PBYTE pbVerticesDest; // set up bone transforms for( iBone = 0; iBone < cBones; ++iBone ) { D3DXMatrixMultiply ( &g_pBoneMatrices[iBone], // output &pMeshContainer->pBoneOffsetMatrices[iBone], pMeshContainer->ppBoneMatrixPtrs[iBone] ); } // set world transform D3DXMatrixIdentity( &Identity ); V( pd3dDevice->SetTransform( D3DTS_WORLD, &Identity ) ); V( pMeshContainer->pOrigMesh->LockVertexBuffer( D3DLOCK_READONLY, ( LPVOID* )&pbVerticesSrc ) ); V( pMeshContainer->MeshData.pMesh->LockVertexBuffer( 0, ( LPVOID* )&pbVerticesDest ) ); // generate skinned mesh pMeshContainer->pSkinInfo->UpdateSkinnedMesh( g_pBoneMatrices, NULL, pbVerticesSrc, pbVerticesDest ); V( pMeshContainer->pOrigMesh->UnlockVertexBuffer() ); V( pMeshContainer->MeshData.pMesh->UnlockVertexBuffer() ); for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { V( pd3dDevice->SetMaterial( &( pMeshContainer->pMaterials[pMeshContainer->pAttributeTable[iAttrib].AttribId].MatD3D ) ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pMeshContainer->pAttributeTable[iAttrib].AttribId] ) ); V( pMeshContainer->MeshData.pMesh->DrawSubset( pMeshContainer->pAttributeTable[iAttrib].AttribId ) ); } } else // bug out as unsupported mode { return; } } // end if skin info != NULLelse // standard mesh, just draw it after setting material properties { V( pd3dDevice->SetTransform( D3DTS_WORLD, &pFrame->CombinedTransformationMatrix ) ); for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ ) { V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[iMaterial].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[iMaterial] ) ); V( pMeshContainer->MeshData.pMesh->DrawSubset( iMaterial ) ); } } } */ // ROCKLAND NOTE: the Z3d game library uses hardware vertex processing only, so the // software vertex processing code in the following routine has been turned off.// uncomment it if you want to use software vertex preocssing as a fallback method.//--------------------------------------------------------------------------------------// Called to render a mesh in the hierarchy. skinned only, non-indexed.//--------------------------------------------------------------------------------------void DrawMeshContainer2( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase){HRESULT hr;D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;// D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase;// UINT iMaterial;UINT NumBlend;UINT iAttrib;DWORD AttribIdPrev;LPD3DXBONECOMBINATION pBoneComb; UINT iMatrixIndex;// UINT iPaletteEntry;D3DXMATRIX matTemp;D3DCAPS9 d3dCaps;pd3dDevice->GetDeviceCaps( &d3dCaps ); AttribIdPrev = UNUSED32;pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() );// Draw using default vtx processing of the device (typically HW)for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 ) { // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); // lookup the material used for this subset of faces if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) { V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } // draw the subset now that the correct material and matrices are loaded V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); } } /* // If necessary, draw parts that HW could not handle using SWif( pMeshContainer->iAttributeSW < pMeshContainer->NumAttributeGroups ) { AttribIdPrev = UNUSED32; V( pd3dDevice->SetSoftwareVertexProcessing( TRUE ) ); for( iAttrib = pMeshContainer->iAttributeSW; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices < NumBlend + 1 ) { // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); // lookup the material used for this subset of faces if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) { V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } // draw the subset now that the correct material and matrices are loaded V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); } } V( pd3dDevice->SetSoftwareVertexProcessing( FALSE ) ); } */V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_DISABLE ) );} // ROCKLAND NOTE:// this routine was written by rockland, not MS.// bascially its drawmeshcontainer2 with the commented out software vertex procesing code removed.//------------------------------------------------------------------------------------------------// Called to render a mesh in the hierarchy. skinned only, non-indexed. HW vertex processing only.//------------------------------------------------------------------------------------------------void DrawMeshContainer3( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase){HRESULT hr;D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;UINT NumBlend;UINT iAttrib;DWORD AttribIdPrev;LPD3DXBONECOMBINATION pBoneComb;UINT iMatrixIndex;D3DXMATRIX matTemp;D3DCAPS9 d3dCaps;pd3dDevice->GetDeviceCaps( &d3dCaps );AttribIdPrev = UNUSED32;pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() );for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) // Draw using default vtx processing of the device (typically HW) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 ) { for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex],pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) // lookup the material used for this subset of faces { V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) ); V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); // draw the subset now that the correct material and matrices are loaded } }V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_DISABLE ) );} // ROCKLAND NOTE:// this routine was written by rockland, not MS.// its the same as drawmeshcontainer3, but uses the current texture and material// bound to the fixed function pipeline instead of those specified by the mesh (if any).// this lets you change the textures and materials used to draw a skinned mesh - and // those textures and materials can be pooled!// note that this is only designd to work with skinned meshes that use the same texture for all subsets.//---------------------------------------------------------------------------------------------------------------------// Called to render a mesh in the hierarchy. skinned only, non-indexed. HW vertex processing only. // uses current material and texture//---------------------------------------------------------------------------------------------------------------------void DrawMeshContainer4( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase){HRESULT hr;D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;UINT NumBlend;UINT iAttrib;DWORD AttribIdPrev;LPD3DXBONECOMBINATION pBoneComb;UINT iMatrixIndex;D3DXMATRIX matTemp;D3DCAPS9 d3dCaps;pd3dDevice->GetDeviceCaps( &d3dCaps );AttribIdPrev = UNUSED32;pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() );for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) // Draw using default vtx processing of the device (typically HW) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 ) { for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex],pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) // lookup the material used for this subset of faces {// V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) );// V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); // draw the subset now that the correct material and matrices are loaded } }V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_DISABLE ) );} // ROCKLAND NOTE:// this routine was written by rockland, not MS.// its the same as drawmeshcontainer4, but uses the meshcontainer`s texID, along with the current material// bound to the fixed function pipeline.// this lets you change the textures used to draw each mesh of a skinned mesh - and // those textures can be pooled!// note that this is only designd to work with skinned meshes that use the same texture for all subsets.//---------------------------------------------------------------------------------------------------------------------// Called to render a mesh in the hierarchy. skinned only, non-indexed. HW vertex processing only. // uses current material and texture//---------------------------------------------------------------------------------------------------------------------void DrawMeshContainer5( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase){HRESULT hr;D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase;UINT NumBlend;UINT iAttrib;DWORD AttribIdPrev;LPD3DXBONECOMBINATION pBoneComb;UINT iMatrixIndex;D3DXMATRIX matTemp;D3DCAPS9 d3dCaps;pd3dDevice->GetDeviceCaps( &d3dCaps );AttribIdPrev = UNUSED32;pBoneComb = reinterpret_cast( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() );for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) // Draw using default vtx processing of the device (typically HW) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 ) { for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends { iMatrixIndex = pBoneComb[iAttrib].BoneId; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex],pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); V( pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ) ); } } V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) ); if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) // lookup the material used for this subset of faces {// V( pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ) );// V( pd3dDevice->SetTexture( 0, pMeshContainer->ppTextures[pBoneComb[iAttrib].AttribId] ) );Zsettex(pMeshContainer->texID); AttribIdPrev = pBoneComb[iAttrib].AttribId; } V( pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ) ); // draw the subset now that the correct material and matrices are loaded } }V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_DISABLE ) );} // ROCKLAND NOTE: use your choice of drawmeshcontainer, drawmeshcontainer2, // drawmeshcontainer3 , drawmeshcontainer4, or drawmeshcontainer5 in this routine.//--------------------------------------------------------------------------------------// Called to render a frame in the hierarchy//--------------------------------------------------------------------------------------void DrawFrame( IDirect3DDevice9* pd3dDevice, LPD3DXFRAME pFrame, D3DXMATRIX *g_matView ){LPD3DXMESHCONTAINER pMeshContainer;pMeshContainer = pFrame->pMeshContainer;while( pMeshContainer != NULL ) {// DrawMeshContainer( pd3dDevice, pMeshContainer, pFrame, g_matView );// DrawMeshContainer2( pd3dDevice, pMeshContainer);// DrawMeshContainer3( pd3dDevice, pMeshContainer);// drawmeshcontainer4 uses the current material and texture. set material and texture, then call draw_skinned_mesh.// DrawMeshContainer4( pd3dDevice, pMeshContainer);// drawmeshcontainer5 uses the current material and the meshcontainer`s texID. set material and texID`s, then call draw_skinned_mesh. DrawMeshContainer5( pd3dDevice, pMeshContainer); pMeshContainer = pMeshContainer->pNextMeshContainer; }if( pFrame->pFrameSibling != NULL ) { DrawFrame( pd3dDevice, pFrame->pFrameSibling, g_matView ); }if( pFrame->pFrameFirstChild != NULL ) { DrawFrame( pd3dDevice, pFrame->pFrameFirstChild, g_matView ); }} // ----------------------------- end LOW LEVEL MS CODE -----------------------
    Norman Barrows
    L Spiro on scene managers vs state managers

    an excellent description of scene and state managers by L Spiro from a recent thread:




    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]A scene manager manages a scene in your game.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]A scene is any spacial 2D or 3D area in your game (a scene manager must be dedicated to either 2D scenes or 3D scenes, not both).[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]It inherits from nothing and contains and manages all objects in the scene.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]This means:[/background]

    [/font][/color]

    • As a container:

      1. It contains a master list of all objects in the scene. All cameras, terrain instances, foliage data, sky, characters, models, static geometry, etc. The objects can be traversed linearly this way. It is not necessary to store them all in a single bucket--it is perfectly fine to store an array of terrains, an array of non-static geometry, an array of animated geometry, etc.
      2. It contains a spatial partition for traversing spacially more easily. For example when gathering objects for a render and performing frustum culling, or broadphase culling for physics.


      • As a manager:

        1. It manages the interactions between objects. This means kicking off the physics engine to perform physics etc.
        2. It is able to abstractly gather objects together for various other sub-systems such as frustum-culling for graphics.
        3. It alerts objects of certain things around them. An object doesn't know what the player's camera is by itself--there are many cameras in a scene, including those for rendering shadows. The scene manager uses an abstract interface to tell objects to update themselves according to their orientation with the main camera. This could be used to select LOD levels or create imposters.

        [color=rgb(40,40,40)][font=helvetica]


        desdemian, on 29 Jun 2015 - 10:04 PM, said:snapback.png

        [/font][/color][quote name="desdemian" post="5237605" timestamp="1435629880"]
        For example the main menu is a scene.
        The screen that shows the logo at the beginning of my game is a scene.
        The screen with highscores, and the settings menu are also scenes.
        Each level of the game is a scene.
        Even a pop-up dialog could be a scene.[/quote]
        [color=rgb(40,40,40)][font=helvetica]

        [background=rgb(250,251,252)]These are better described as states.[/background]

        [/font][/color]

        [color=rgb(40,40,40)][font=helvetica]

        [background=rgb(250,251,252)]A game state manages a state of the game. The main menu, credits, gameplay area, bonus-round area, stage-select, etc.[/background]

        [/font][/color]
        [color=rgb(40,40,40)][font=helvetica]

        [background=rgb(250,251,252)]All of these states have wildly different logic and requirements. A typical scene manager is unsuitable for this task.[/background]

        [/font][/color]


        [color=rgb(40,40,40)][font=helvetica]

        [background=rgb(250,251,252)]The relationship is instead:[/background]

        [/font][/color]

        • CMyGame inherits from CGame. CMyGame has data persistent throughout the game, such as your player stats.

          • CGame has a CStateManager object for managing game states.

          • A CStateManager object has a current state (CState). It could optionally have a stack of states depending on how you want to handle system messages such as a low battery on Nintendo Wii U.
          • CMainMenuState inherits from CState. All logic for the main menu is implemented here. There are no scene managers.
          • CGamePlayState inherits from CState. All logic for the gameplay state is implemented here. There is a single scene manager for managing the 3D world of your game. Thus CGamePlayState has an instance of CSceneManager. Upon loading, it fills the scene manager with objects based on which level is being loaded etc. The rest is largely up to the scene manager.

            [color=rgb(40,40,40)][font=helvetica]

            [background=rgb(250,251,252)]L. Spiro [/background]

            [/font][/color]





            [color=rgb(40,40,40)][font=helvetica]

            [background=rgb(250,251,252)]original thread:[/background]

            [/font][/color]
            https://www.gamedev.net/topic/669599-what-are-scene-managers-and-how-do-they-work/
    Norman Barrows
    Servant of The Lord on inheritance vs composition

    a very lucid explanation of inheritance vs composition by Servant of the Lord, from a recent thread:






    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]Classes should favor [/background]

    [/font][/color]composition[color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)] over inheritance.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    This is composition:[/background]

    [/font][/color]

    [color=rgb(0,0,136)]class[/color][color=rgb(0,0,0)] [/color][color=rgb(102,0,102)]ClassA[/color]
    [color=rgb(102,102,0)]{[/color]
    [color=rgb(0,0,0)] [/color][color=rgb(102,0,102)]ClassB[/color][color=rgb(0,0,0)] memberVariable[/color][color=rgb(102,102,0)];[/color][color=rgb(0,0,0)] [/color]
    [color=rgb(102,102,0)]};[/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    Composition is when a class has other classes as member-variables.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    Classes should be composed of multiple classes that they own as member variables.[/background]

    [/font][/color][color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    A car should never inherit wheels. A car should own wheels.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    Most classes should use composition and avoid inheritance, except where inheritance is necessary or desirable for a specific purpose.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    One common purpose for inheritance is when you need to treat two or more different classes - with their own different logic - as if they behaved the same on the surface. Basically, each having different logic, but sharing the same interface.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    A typical beginner and intermediary-level rule of thumb is to say "Has-A" (composition) and "Is-A" (inheritance).[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    A tiger has a tail (composition). A tiger is a animal (inheritance).[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    A car has a wheel. Therefore the four wheels should be owned by the car via composition.[/background]

    [/font][/color][color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    A car is a vehicle. Therefore the car should inherit the vehicle's interface.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    If you want to park a Car and a Truck into a Garage, then you make the Garage own (as member variables - composition) space for two or more MotorVehicles which provide the interface. And you make Car and Truck both be (inherit) MotorVehicle so they can both provide their own data and logic.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    But often times, your code doesn't even need to care about different logic between the Car and Truck - in that case, just initialize a single class type with different values.[/background]

    [/font][/color]

    [color=rgb(102,0,102)]Vehicle[/color][color=rgb(0,0,0)] myTruck[/color][color=rgb(102,102,0)]([/color][color=rgb(0,136,0)]"TruckImage.jpg"[/color][color=rgb(102,102,0)],[/color][color=rgb(0,0,0)] [/color][color=rgb(102,0,102)]TruckData[/color][color=rgb(102,102,0)]);[/color][color=rgb(0,0,0)] [/color][color=rgb(136,0,0)]//If they only differ by their *data*, just pass them different data.[/color]
    [color=rgb(102,0,102)]Vehicle[/color][color=rgb(0,0,0)] myCar[/color][color=rgb(102,102,0)]([/color][color=rgb(0,136,0)]"CarImage.png"[/color][color=rgb(102,102,0)],[/color][color=rgb(0,0,0)] [/color][color=rgb(102,0,102)]CarData[/color][color=rgb(102,102,0)]);[/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    Has-A and Is-A is not the 100% best way to think of architecture, but it is the best way I know of to begin learning it. As you program, you'll expand your understanding of architecture and begin to see that this rule-of-thumb doesn't always hold true. But when starting out, it's good to force yourself to follow it whenever tempted to use inheritance unnecessarily.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    Avoid inheritance entirely at first - any program you can write with inheritance, you can also write without it. It's a tool to make things easier, but it can also make things alot more complicated if you use it the wrong way. When you feel the need to use inheritance, challenge yourself to first come up with a way of not using inheritance. And then only if the inheritance way is cleaner, use it. That's my recommendation. smile.png[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    [Edit:] #include doesn't mean anything in code logic. It just copy-pastes two files together. Files don't mean anything - only the code they contain matter. A file is not a class. The class is the class. A file might maybe happen to contain a class. Or it could contain something entirely unrelated. Filenames and #includes don't affect the logic of your code - they only tell the compiler where to find the code.[/background]

    [/font][/color]
    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    You can fit an entire program like Halo or Call of Duty into a single .cpp file. It'd be silly to do, but it is possible. The file doesn't affect the program, only the code affects the program. The files and #includes affect only the compiling of the program, not the program's behavior.[/background]

    [/font][/color]


    [color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    original thread:[/background]

    [/font][/color][color=rgb(40,40,40)][font=helvetica]

    [background=rgb(250,251,252)]
    https://www.gamedev.net/topic/669599-what-are-scene-managers-and-how-do-they-work/[/background]

    [/font][/color]
    Norman Barrows
    Niklas Frykholm's articles on Managing Decoupling

    I just stumbled across this excellent series of articles right here on gamedev...

    https://www.gamedev.net/page/resources/_/technical/game-programming/managing-decoupling-r3034

    https://www.gamedev.net/page/resources/_/technical/game-programming/managing-decoupling-part-2-polling-callbacks-and-events-r3044

    https://www.gamedev.net/page/resources/_/technical/game-programming/managing-decoupling-part-3-c-duck-typing-r3060

    https://www.gamedev.net/page/resources/_/technical/game-programming/managing-decoupling-part-4-the-id-lookup-table-r3062

    I've only read the first article so far. good stuff!
    Norman Barrows

    Beautiful code

    Beautiful code

    [ i'm still working on this concept, so this is a very rough draft... ]


    writing beautiful code.

    sometimes you code, and it seems as straightforward as:
    "all nearby units - protect/serve - wilshire and santa monica". (high fives all around - but no touching! ).

    and sometimes you code, and its like "well, i know i'll need this...
    and i might need that... but then i'm also going need some other
    kind of thing to take care of this other part..." and so you code
    some, and think some, and code some more, and think some more, and
    eventually something comes together (lets hope! ). and then you
    might do some refactoring and cleanup.

    and sometimes you code and you get big complex lines of code like
    this one from Caveman 3.0 animal_in_strikezone(int cm1):

    rh=RH(cm[cm1].mx,cm[cm1].mz,(int)cm[cm1].x,(int)cm[cm1].z,(int)rad2deg(cm[cm1].yr),animal.mx,animal.mz,(int)animal.x,(int)animal.z);

    whew! just reading it hurts! and i had to write it! .

    and then sometimes you code and you get what i can only describe
    as beautiful code - things like:

    if (!camera.cansee(location)) return;

    location includes a bbox/bsphere rad or Brect dimensions.
    this one line performs both farclip and frustum cull.

    and it reads like english.

    i think thats the key to beautiful code.

    it tends to read the way you'd say in engish how to do something.

    so i suspect that top down pseudo coding in english will tend
    to create more beautiful code.

    when i've done this in the past i've noticed this.

    i also had one group project experience with this as well.
    as part of systems programming class at OSU, teams of four
    students each implemented a compiler, linker, loader, and
    emulator as a group project. we had a 2 page pseudocode algo we
    were given for the compiler. we were to use C, and were expected
    to learn it on our own as we went along. back then, OSU taught
    you pascal, then let you figure out C for yourself. at our first
    team meeting, i applied top down pseudo coding to each part of
    the compiler algo. within an hour or two, we had everything
    figured out, it was all simple, neat, and clean. coding tasks
    were assigned, all team members were comfortable with their
    assignments and confidence levels were high. at the end of
    the session one of the team members commented as to how the
    top down pseudo code approach made everything amazingly simple.
    needless to say that was about the last thing that went right
    in the project. we only had 3 team members to start with,
    and one of them dropped the class, leaving just me and one
    other coder. he did what he was originally assigned to do.
    i pulled two all nighters in a row doing the rest while
    visiting my finacee at VA Tech over the weekend. on monday
    morning, having not slept since friday night, i uploaded the
    code to my partner at OSU, who turned it in. there was only
    one minor bug in the compiler. we got an A! :)

    i think i should get into the habit of trying to code this
    way more often. it could lead to good things.

    after all, we're giving instructions to something (a computer).
    and we speak english or some other human tongue - although we
    probably tend to think in code - being programmers. perhaps its
    time to think in english (or whatever) rather than code.
    write down an instruction in english. change the nouns to
    variables, and the verbs to method calls, that kind of idea.
    following that analogy, direct objects would probably be
    parameters. an example:

    step 1 in english:
    make Jack throw the ball.

    step 1 in code:
    jack.throw(ball)

    i know, pitifully obvious and contrived! but i think it gets
    the point across.

    here's a more real-world example:
    if the camera can't see the object's location, don't render it.
    if (! camera.cansee(object.location)) return.

    so perhaps we should not only strive for good code, perhaps we
    should also try to make it beautiful - IE god-like in its clarity.
    Norman Barrows
    generic pattern maps

    often times you need a pattern for something in a game.
    for example: what texture tile to use with a given ground quad.

    i used to create randomly generated pattern maps for different things:
    plants, rocks, bushes, trees, ground quad textures, etc.

    then it occurred to me that you could use a generic random pattern map,
    and write functions to return whatever you needed.

    a generic pattern map is an array of ints with random values.

    some examples (from Caveman 3.0)...

    Caveman uses a 100x100 generic random pattern map with values from 1
    through 100.

    // ####### GENERIC RANDOM PATTERN MAP ############ int randmap[100][100]; void init_randmap(){int a,b;for (a=0; a<100; a++) { for (b=0; b<100; b++) { randmap[a]=dice(100); } } } // ####### END GENERIC RANDOM PATTERN MAP ######## // used by hm_canyon2// x and z get divded by 20 to scale them down to the pattern map.// then they get modded by 100 to map them into the pattern map // range of 0..99 by 0..99// the value at that location in the pattern map is then returned. int get_randmap_value(float x,float z){int x2,z2;x2=(int)x;x2/=20;x2=x2%100;z2=(int)z;z2/=20;z2=z2%100;return(randmap[x2][z2]);} // heightmap formula for canyon floors// the heightmap repeats every 2000 units.// 20 units in the heightmap = one unit in the generic // pattern map.// the formula does a double interpolation.// first it determines the height of the four closest // pattern map cells.// then it does a lerp from cell x,z to x+1,z // and x,z+1, to x+1,z+1.// it then lerps between these two results to get the // final interpolated height at the desired location.// 100 is then subtracted from this value, so the results// range from -100 (canyon floor) to 0 (even with canyon rim). float hm_canyon2(float x,float z){int y0,y1,y2,y3;// rem is remainderfloat rem,y4,y5,y6;rem=x;while (rem>=2000.0f) { rem-=2000.0f; }while (rem>=20.0f) { rem-=20.0f; }y0=get_randmap_value(x,z);y1=get_randmap_value(x+20.0f,z);y2=get_randmap_value(x,z+20.0f);y3=get_randmap_value(x+20.0f,z+20.0f);y4=f_lerp(rem,0.0f,20.0f,(float)y0,(float)y1);y5=f_lerp(rem,0.0f,20.0f,(float)y2,(float)y3);rem=z;while (rem>=2000.0f) { rem-=2000.0f; }while (rem>=20.0f) { rem-=20.0f; }y6=f_lerp(rem,0.0f,20.0f,y4,y5);// / y6 2.0fy6-=100.0f;return(y6);} // returns tall grass plant texture ID number to use int grasstex(int x,int z){x=x%100;z=z%100;if (randmap[x][z] < 51) { return(21); }return(121);} // returns prairie grass plant texture ID number to use int prairie_grasstex(int x,int z){x=x%100;z=z%100;if (randmap[x][z] < 31) { return(348); }if (randmap[x][z] < 61) { return(349); }if (randmap[x][z] < 81) { return(346); } return(347);} // returns savanna grass plant texture ID number to use int savanna_grasstex(int x,int z){x=x%100;z=z%100;if (randmap[x][z] < 31) { return(359); }if (randmap[x][z] < 61) { return(360); }if (randmap[x][z] < 81) { return(361); } return(362);} // returns grass plant mesh scale to use int grass_scale(int x,int z){int scale;x=x%100;z=z%100;scale=250+randmap[x][z];scale/=10;return(scale);} // returns grass plant mesh y rotation to use float grass_rotation(int x,int z){float rot;x=x%100;z=z%100;// rot = 0 thru 99% * 2pirot=(float)(randmap[x][z]-1);rot*=pi*2.0f;rot/=100.0f;return(rot);} // returns grass plant mesh offset (jitter) to use float grass_offset(int x,int z){float ofs;x=x%100;z=z%100;// grass step size is 5. offset is -2 thru 2.// offset = -2 + 4 * rand / 100ofs=(float)randmap[x][z];ofs*=4.0f;ofs/=100.0f;ofs-=2.0f;return(ofs);} // adds grass plant meshes to a terrain chunk// grass_stepsize is #defined as 5 void generate_terrain_grass(int c) // c is chunk #{int x1,z1, // SW corner of area to draw x2,z2, // NE corner of area to draw x,z, // un-normalized grass coords scale;Zdrawinfo a;get_chunk_area(c,&x1,&z1,&x2,&z2);ZeroMemory(&a,sizeof(Zdrawinfo));a.materialID=2;a.meshID=27; // 27 = plant2.x a.cull=0;a.alphatest=1;a.clamp=1;a.rad=6;a.cliprng=grass_cliprng;for (z=z1; z<=z2; z+=grass_stepsize) { for (x=x1; x<=x2; x+=grass_stepsize) {if (waterinway(CL[c].mx,CL[c].mz,x,z)) continue;if (storepit_in_way(CL[c].mx,CL[c].mz,x,z)) continue; scale=grass_scale(x,z); a.sx=(float)scale*0.1f; a.sy=(float)scale*0.09f; a.sz=(float)scale*0.1f; a.x=(float)x+grass_offset(x,z); a.z=(float)z+grass_offset(x,z); a.y=heightmap(CL[c].mx,CL[c].mz,(float)x,(float)z); a.ry=grass_rotation(x,z); a.texID=grasstex(x,z); add_zdrawinfo(c,&a); } }}
    Norman Barrows
    A design for an indexed render queue


    drawing information is stored in a drawinfo struct. it could also be hashed into an int, etc.


    the queue consists of an un-ordered list of drawinfo structs.

    the queue is supplemented by a multi-dimensional indexing system that
    allows processing of the queue in optimal draw order.

    the index system consists of a list of textures.

    for each texture there is a list of meshes.

    for each mesh there is a list of instances, which are the indexes of the
    drawinfo structs in the render queue that use that mesh and texture.

    if desired, the list of instances can be sorted from near to far. but the
    speeds i've gotten from optimal draw order alone has been fast enough i don't bother sorting instances (yet).

    another layer can be added to the index as well for materials. in that case,
    each mesh would have a list of materials, and each material would have a
    list of instances (drawinfo indexes).

    to render the queue, you go though the list of textures. you set the first texture, then go though its list of meshes used. you set the mesh, then go though its list of instances. for each instance, you set the world transform, then draw. then you continue with the next mesh until all meshes for that texture are drawn. then you continue with the next texture, until everything is drawn.
    Norman Barrows
    An oo-ish version of my typical game architecture


    all one code block, i was too lazy to split it up for posting ! .

    non-oo: void main{init_prog run_proguninit_prog}globals: boolean: quit_progetc. oo: class program{globals such as quitproginit becodes constrctoruninit becomes destructorvoid run_prog method}; non-oo: runprog{while ! quitprog { switch main_menu() { case CONTINUE: load last saved game rungame break; case TUTOR: init tutorial game rungame break; case NEW: init new game rungame break; case LOAD: load saved game rungame break; case TOOLS: run tools menu break; case QUIT: quitprog=1; break; }} oo-ish: program.run{boolean quitprogwhile ! quitprog { switch main_menu() { case CONTINUE: load last saved game game.run break; case TUTOR: init tutorial game game.run break; case NEW: init new game game.run break; case LOAD: load saved game game.run break; case TOOLS: run tools menu break; case QUIT: quitprog=TRUE; break; }} non oo: init_blank_gameload_last | init tutor | init new | load savedrungameuninit_game oo-ish: class game{boolean quitgamemethods:init_blank // private - possibly can go in constructorload_lastinit_tutorinit_newload_savedrun // IE rungameuninit - desctructor} non-oo: void rungame{ while ! quitgame { render_all process_input update_all limit_framerate // i prefer the simplicty of framerate // limiters. f-y-t is another viable option. }} oo-ish: game.run{boolean quitgamequitgame = FALSEwhile ! quitgame { game_specific_renderer.render_all input.process entity_list.update_all world.update_all }} non-oo: render_all (typical){clearscreenbegin_scenedraw_skybox clear_render_queuedraw_all_entitiesdraw_terraindraw_render_queueend_scenepresent} oo: game_specific_renderer.render_all{generic_graphics_engine.clearscreengeneric_graphics_engine.begin_scenegeneric_graphics_engine.clear_render_queueworld.render_all // includes skyboxenitity_list.render_allgeneric_graphics_engine.draw_render_queuegeneric_graphics_engine.end_scenegeneric_graphics_engine.present} non-oo: void process_input{// run windows message pump. captures mouse input.// i use get async keystate for keyboard, but windows// messages can also be used for capturing keyboard input.process_windows_messages();getmouse(&x,&y,&b);mouse_aim_camera(x,y);if (b==1) { // left mouse button pressed, do something. }else if (b==2) { // right mouse button pressed, do something. }if keypressed(VK_somecode) { // some key is pressed, do something. }if keypressed(VK_a_different_code) { // some key toher is pressed, do something else. }if keypressed(VK_and_so_on) { // i think you get the idea! . }} oo-ish: input.process{int x,y,b;generic_game_library.process_windows_messages()generic_game_library.getmouse(&x,&y,&b)camera.mouse_aim(x,y)if (b==1) { // left mouse button pressed, do something. }else if (b==2) { // right mouse button pressed, do something. }if generic_game_library.keypressed(VK_somecode) { // some key is pressed, do something. } etc. } once might also have a mouse object: mouse.get(&x,&y,&b)mouse.set(x,y)mouse.reset()etc. i'm discovering in this "port to oo" that generally, stand alone fuctions ought to go in some sort of object.i've been using the same basic architechture for years, so the basic datastructures and functions are already pretty well known at least in generalterms. so its mostly just a matter of grouping related data structures and fuctions into appropriate classes. but things that pretty much every game needs probably ought to be in a single module, as opposed to a bunch of little modules for each thing like mouse, camera, timers, etc. thats why i use the generic_game_library.getmouse syntax in the example above. you might think of generic_game_library as the top level API for all the generic game modules used such as the genric graphics engine, asset pools, generic audio system,timers, etc. really the way to do it in layers:1. have a module for each thing like timers, camera, etc.2. have higher level modules for similar things like a graphics module and an audio module.3. have a top level API (gamelib API) that uses the lower level modules. that way you can include just what you want, or the whole shebang. non-oo: void update_all{for each entity { update_entity(i) }update_world_stuff()} oo-ish:entity_list.update_all{for each entity { call entity_type[entity.type].update_function }} i use a "relational database" pattern for storing entity info and entity type info. entity variables such as location and damage are stored in entity structs or objects. these are entries in the entities_list. data which is the same for all entities of a given type is stored in entity_type structs or objects. these are entries in the entity_types list. each entity has a type - the index of its entry in the entity_types list. examples of this type of data would includedrawing info, max speeds, max turn rates, base hit points, base armor class, base chance to hit, etc. IE stuff that doesn't change from one Orc to the next for example. world.update_all{// update all world objects as needed.// things like remove dead bodies, model weather, // periodically reset spawn points, etc.} ----------------------------------------------------- so this gives us the following game specific classes:note that where i specify just one instance required, i mean for typical applications. some games may want more than one instance of some things. i personallytend to use two entity lists, one for targets, and one for missiles. entity (multiple instances) entity_type (multiple instances) entity_list (one instance)entity_types_list (one instance)program_object (one instance)game_object (one instance)game_specific_renderer (one instance)game_specific_input_handler (one instance)world_object (one instance)mission_object or level_object - if mission / level based game (one instance) and then we have the generic_game_library class (one instance).it includes:generic_graphics_engine (one instance)generic_audio_system (one instance)timers, random number gens, etc. the graphics engine includes: mesh, texture, model, and animation asset pool objects. the audio system include a wav asset pool object. program.run runs the main menu (new game, load game, etc) game.run is the main loop. in a mission/level based game, game.run is the "between missions" menu, with one or more menu options that call mission.run, which is the main game loop. some exmple class methods required:--------------------------------------------------------- world object:init renderupdate audio system:inituninitplaywavloopwavstopwavetc entity_list:render_allupdate_all entity_types_list:init asset object:loadunloadset texture etc. asset pool:initload_allunload_alluninitetc note: init and uninit methods might be placed in constuctors and destructors,or they might be explicit methods to allow for object reuse without having todestroy and recreate the object. ownership and scope issues-------------------------------------------------------------- i typically try to load all assets and allocate and initialize all resources one time at program start in init_prog, and shut everything down in reverse order in uninit_prog. but this means the program object will own the game_library,entity_list, and world object (if the world is the same every game), and will have to pass them to game.run() to avoid globals. i could simply new and dispose them each time i start a game, and let the game object own them, but that's not efficient due to unncessary memory allocations and deallocations. so in general to avoid globals while going oo, i'll need to passwhat used to be globals around as parameters. this could lead to long praramter lists. the plan is to borrow apage from directx, and pass long parameter lists in structs passedby reference. this way i can have object ownership at a higher level where reuse is possible, and not have to deal with an ugly API when calling lower level methods that i must pass high level objects (IE global data structures and api's) to. example: struct game_params{generic_game_library *gamelib;game_specific_renderer *renderer;input_handler *input;entity_list *elist;world_object *world; // if world is the same every game}; program.run{/*new() all program level objects:generic_game_librarygame_specific_rendererinput_handlerentity_listworld_object // if world is the same every game*/// set params for call to game.run....game_params gp;gp.gamelib=program.generic_game_librarygp.renderer=program.game_specific_renderergp.input=program.input_handlergp.elist=program.entity_listgp.world=program.world // if world is the same every game// call game.rungame.run(&gp)} delving deeper:---------------------------------------------- entity.render{if !camera.cansee(entity.location) return;add_to_render_queue(entity_type[entity.type].drawinfo, entity.location);} entity_type.update:{if the entity type is not player, do AI.// player input takes care of AI for their entityapply update method.} data structures:-----------------------------------------------------the entities_list, entity_types_list, and asset pools would most likelyuse std:vectors. member variables of entity and entity type objects would vary depending on the game in question, as would entity_type update methods. entity type info----------------------------------------------------------- traditionally i've used a relational database type design,with an entities list, and an entity_types list. it seems that this is no longer necessary. back in the day it was unthinkable to replicate entity type data in each instance of an entity.now there RAM to spare, and cache misses from having type info elsewhere is the big issue these days. this calls for an extremly minor change to the standard architechture. the enitity_typesdatabase stays, but now each enity object also contains all the member variables in an entity_type. when an entity is created, instead of setting its type, you copy its type info into it.when assessing type info, you use tgt.whatever, not tgttype[tgt.type].whatever. summary:--------------------------------------------------------use of a generic game library reduces the coding requiredto the following: 1. program object - init game lib etc, run main menu, shutdown.2. game object - init a game, run a game (main game loop)3. game_specific_renderer - draw a scene4. input - process mouse and keys5. entity_list - update code (including AI) and drawing info. entity type data should be data driven for non-trivial projects. a generic game library can also help with these tasks: 1. it includes a generic menu routine that takes background and mouse pointer textures as paramaters. this can be used for the main menu and any in game menus.2. it includes file i/o methods for loading and saving games. it includes timers for use in framerate limiting the main loop (if desired), as well as for other uses in the game.3. it includes a generic skybox drawing routine.4. a generic keyboard mapper could be added to the game library for help with input.5. the library includes AI routines for a built-in entities_list which could be made generic for use with any entity. due to the game specific nature of entity lists, i suspect generic entity lists are not the best way to go. case in point: after finishing my generic entity list system, complete with AI, i discovered that my next game would require an entirely different update method, due to storing world coordinates differently due to the large game world size. Since then, i've started yet another game with an even bigger game world with yet another method of storing world cooordinates.
    Norman Barrows
    background terrain chunk generation

    the foreground task (the game) generates terrain chunks on the fly as
    needed from underlying world map data, and stores them in a cache for
    use by the renderer. chunks are stored first in inactive cache slots,
    and then on a LRU basis once the cache is full.

    the background chunk generator looks ahead around the player for
    chunks that ought to be generated, and generates them in the background.



    background_gen_chunk:{if generating_chunk { if chunk_to_gen is active in the cache, start_generating = true. // the foreground task already generated the chunk on demand, we weren't fast enough! have to start over! if our_cache_slot is active, start_generating = true. // the foreground task used the cache slot we were using to generate a chunk in! // note that some sort of reserved flag in the cache should be able to preclude this issue. but with a LRU // cache, if the foreground grabs our slot, which was the least recently used slot as of pass_number of // frames ago, it must really need it bad, so reserving it could likely result in it being generated in // the foreground before we finish anyway. otoh, we set our slot to inactive, so the renderer doesnt try // to use it while we're still generating it, so its the first slot grabbed by the foreground if the // foreground needs to generate a chunk on the fly right now for the renederer. so maybe a reserved flag // could be of use. it would force the foreground task to use the LRU slot, rather than our inactive one. // but we might then finish genrating only to find its not relly needed anymore. something to think about. }else { // not generating chunk start_generating = true }if start_generating { chunk_to_gen = determine chunk to generate if chunk_to_gen = none, return // all desired chunks already in cache! we're done! getabeer! . our_cache_slot = get free cache slot pass_number = 0 generating_chunk = true }do_a_pass} do_a_pass:{switch pass_number { case 0: // add some stuff to the terrain chunk pass_number++ break case 1: // add some more stuff to the terrain chunk pass_number++ break ... case : // add final stuff to the terrain chunk // all stuff added. set cache slot to active for use by renderer cache[our_cache_entry].active = true generating_chunk = false break } }



    deterimine chunk to gen:
    this does an outward sweeping search around the player to some pre-defined distance.
    chunks are checked to see if they are in the cache or not.
    the first one not in the cache is returned as the next chunk_to_gen.
    if all chunks out to the prescribed distance are in the cache, it returns
    NONE as the next chunk_to_gen.




    fine tuning:
    increase the number of passes and reduce the items added
    to the chunk per pass until speeds are accepatble.




    a possible mod:
    if generating_chunk && !start_generating // IE on second and subsequent passes... { temp = determine chunk to generate if (temp != chunk_to_gen) { // some other chunk is more important, stop this one and start that one! clear(our_cache_entry) chunk_to_gen = temp pass_number = 0 } }

    reserved cache slot mod:

    if our_cache_slot is active, start_generating = true.
    // the foreground task used the cache slot we were using to generate a chunk in!
    // note that some sort of reserved flag in the cache should be able to preclude this issue. but with a LRU
    // cache, if the foreground grabs our slot, which was the least recently used slot as of pass_number of
    // frames ago, it must really need it bad, so reserving it could likely result in it being generated in
    // the foreground before we finish anyway. otoh, we set our slot to inactive, so the renderer doesn't try
    // to use it while we're still generating it, so its the first slot grabbed by the foreground if the
    // foreground needs to generate a chunk on the fly right now for the renderer. so maybe a reserved flag
    // could be of use. it would force the foreground task to use the LRU slot, rather than our inactive one.
    // but we might then finish generating only to find its not really needed anymore. something to think about.

    not sure if its an improvement or not.
    Sign in to follow this