Jump to content
  • Advertisement
Sign in to follow this  
  • entries
    12
  • comments
    81
  • views
    11581

About this blog

The tramp of a hobby engine.

My completed games using Unity, Monogame (c#) and an older ogl2/c++ framework. 

asteroidsClone_sm.jpg Asteroids Clone [ Download ]

joustClone_sm.jpg Joust Clone [ Download ]

lightningCommand_sm.jpg Lightning Command [ Download ]

octaball_sm.jpg Octaball [ Download ]

explosive_balls_thumbWide.jpg.9217ea5e9738371bf03c8aa9f4eee2e7.jpg Explosive Balls [ Internal Project Page ]

Entries in this blog

Doom Game Challenge : þoom

I see the playing field populating and players taking their turns...   Special thanks for this particular challenge opportunity. Full disclosure, I've actually never played Doom. Which made this first week a research exercise. Sorry JohnC and crew, but wow...I'm put back in anticipation of what may be to come from the quantity of declared participants. So tally-ho, everyone in on the ride.  I'm going the raycaster route to stay more in key, with an old wolfenstein clone framework from an early gameinstitute.com, Perlin Noise and AI Seminar offerings by Author : JohnDeGoes(12-15 years ago). One of the graphics items Doom brought to the table were textures on the ceiling and floor. A concept my chosen framework is unaware. This go, I'd like to do a proper AI agent type or two. Also on the todo list is a simple sprite animation atlas tool that is feed from targeted screen grab so I can render the art instead of doing pixel art or photo persuasion, but mostly hand packing image sequences is such a chore. I'd like to open this project up to others. Right now I'm thinking heavy guitar loops for game play. It would be nice to have a sound guy of the programmer persuasion and an ai guy. Lets talk.    ...but after kicking the can for more than a while, the flame piddled out. I've had this project collecting dust for a decade and thought it would be a good idea to start from wolfenstein to transition to doom. Then I was umm...whatever, I've done enough 2D...moving onward.  Here I'm showing the abandoning and rebirth starting with a nice modular wall asset gem from the unity store and a first stab at some licks. Makes me happy.   

MarkK.

MarkK.

GC : Explosive Balls (game play)

Continued adventures with the oneLoneCoder pixelGameEngine (youtube).  With the current game challenge, time has started to arc over and on the surface, I'm not looking all that great. Under the hood though, things are playing well with each other. I have most all of my render system in place, most update behaviors ready and still have some performance headroom, although I've dipped under standard monitor speed (60hz) sometime last week in test runs. So, two weeks. That's what I get to finish game play, leaving something for cleanup and GC blog/project setup. The original plan is still good and taken from concept to reality. We're here. Got balls, they go boom, they dig a hole and do a pretty splash on their way out the door. Perfect.    This last week a terrain smoothing feature was added. After a carve operation and the damage the particle system does as well, leaves pointy/jagged peaks. The smoothing provides landslide behavior and drops stray leftover pixels into the pile. As soon as I realized that, I saw a water implementation peeking it's head out of the sand. So again, here we are. The new yellow object is the puncture pin. Balls and pin against character and his pipes. If balls get the pin down to the pipe, we start flooding for effect game over. That's where my idea went. Did I mention, "not ...all that great"   Working on that mechanic for the next day or two. (I'll be back) (back) What I appreciate in this video is what you get just from std::rand and mod for polarity from the screen vertical position, plus a higher resolution chaos threshold to act or not. But watching std::rand() cycle through is not a bad thing at all in my opinion. Especially for fire and forget.  The initial stab at it was nice. Tomorrow, another particle system type as the spawner.  As of now, I set an int in my world array to the water ID and update the array like so. Pretty easy and nice addition...now we're making progress. if (terrain.isStable == true) // not used? always fires at 100 pixel row chunks { static int tty = terrain.nMapHeight - 1; // search row cursor location (y axis marker) for (int n = 0; n < 200; n++) // update count rows of pixels { tty--; // advance on start for the iteration of the row for (int x = 0; x < terrain.nMapWidth; x++) { // scan the single row this frame int index = (tty * terrain.nMapWidth) + x; // this location data index int indexDown = index + terrain.nMapWidth; // the pixel below if (terrain.map[index] != 0) { // we're a pixel. is someone not below me? if (terrain.map[indexDown] == 0) { // fall - swap down one pixel terrain.map[indexDown] = terrain.map[index]; terrain.map[index] = 0; } // no, is someone down and left? if (terrain.map[index] == 1) { // dirt slide float rnd = (float)std::rand() / (float)RAND_MAX; bool bChaos = rnd > 0.95 ? true : false; // determine chaos for horizon movement if (terrain.map[indexDown - 1] != 1 && bChaos == true) { // settle left - swap (down/left) one pixel terrain.map[indexDown - 1] = 1; terrain.map[index] = 0; } // no, is someone down and right? else if (terrain.map[indexDown + 1] != 1 && bChaos == true) { // settle right - swap (down/right) one pixel terrain.map[indexDown + 1] = 1; terrain.map[index] = 0; } } if (terrain.map[index] == 2) // { // water slide // just right here (behavior) int sign; // tty % 2 == 0 ? sign = -1 : sign = 1; if (terrain.map[index+sign] == 0) { // flow left - swap (left) one pixel terrain.map[index+sign] = 2; terrain.map[index] = 0; } } } } if (tty == 0) // top row tested. reset to bottom to restart scan. tty = terrain.nMapHeight - 1; } } Reaching another week, with one remaining and some fluff. How we doing? pffttt....oh boy. yes and no. In a way, like that. I'm missing on a new and isolated collision check but the plan is improving.  I have a loose event and restart working fine. Making progress on the the win aspect being one trigger away. The idea is to get at least one red ball over to the other side of the scrolling play area where a collector of sort awaits.. My level advance logic already works. The power up is in, I just havn't had the want to add in the cheezy AABB test or worse just length proximity trigger. Or imagined what I'm going to be powering up yet.   This weeks visual and the few issues I'm going after right now.    Picking up speed..is there enough for a safe landing?  we'll see.  Second video added showing improved game play and the win case. An ace fell out of my sleeve when testing goal placement for the level progression. An interesting twist, to have to un-bury your target first...from here on out I'm telling the story different.  But the block to steer and not take damage is a bonus. pffttt...everything from a step back has been bonus with the bogus plan I had       And lastly now, character involvement. In order to satisfy the scrolling aspect, the camera follows. In order to pretend like he has purpose, a shield has been granted to sink those explosive balls deep. This will close this entry. I now have power ups and an ester egg to go. I plan on tool tips (game play tips) in some small capacity and on the menu screen some clues on how this thing is played. Thanks for hanging out.   

MarkK.

MarkK.

GC : Explosive Balls (game state)

In order to stay out of trouble, I'm going back to work.   Previous Blog Entry for this game challenge round. Today's topic is my drop in game state manager system for moving between game screens such as an introduction sequence, main menu hierarchy, game play and first say on app termination. I don't remember where I initially found this gem because it has been quite a number of years that I've been reusing this. The concept is simple though. Manage screens as a stack. Push a screen pointer to the back to become active and pop to activate the previous. So how do we get a system like this up and running? The following are sloppy source grab copies. Two objects start us out, First a manager and an abstract base class. From the abstract, we derive the game screen objects. Each screen object handles their own behavior not knowing anything about the others. // file : GameState.cpp #include "olcPixelGameEngine.h" // <-- yours renderer may be different #include "GameState.h" #include "States/intro.h" #include "States/game.h" #include "States/menu.h" using namespace Core; std::shared_ptr<States::Intro> introState; std::shared_ptr<States::Menu> menuState; std::shared_ptr<States::Game> gameState; void StateManager::initialize(olc::PixelGameEngine* engine, vec2 clientSize) { this->engine = engine; introState = std::make_shared<States::Intro>(this, clientSize); menuState = std::make_shared<States::Menu>(this, clientSize); gameState = std::make_shared<States::Game>(this, clientSize); states.push_back(menuState); #ifdef _RELEASE states.push_back(introState); #endif } void StateManager::shutdown() { introState->shutdown(); menuState->shutdown(); gameState->shutdown(); } void StateManager::changeState(States::Base_state* _state) { } void StateManager::pushState(States::Base_state* _state) { } void StateManager::popState() { states.pop_back(); } void StateManager::draw() { states.back()->draw(); } void StateManager::update(float deltaTime) { states.back()->update(deltaTime); } void StateManager::handleEvents(eStateAction _action) { switch (_action) { case eStateAction::pause: if (states.back()->name == "game_state") states.pop_back(); break; case eStateAction::play: if (states.back()->name == "menu_state") { int w = engine->GetDrawTargetWidth(); int h = engine->GetDrawTargetHeight(); RECT rect = { 0, 0, w, h }; // <-- I know --^ states.push_back(gameState); } break; case eStateAction::win: if (states.back()->name == "game_state") { MessageBox(0, "you win", "", 0); //winState->winnerName = gameState->getWinnerName(); //winState->winningTime = gameState->getTimeString(); //states.push_back(winState); } break; case eStateAction::loose: if (states.back()->name == "game_state") { MessageBox(0, "you loose", "", 0); //states.push_back(scoreState); } break; case eStateAction::quit: if (states.back()->name == "menu_state") PostQuitMessage(0); if (states.back()->name == "game_state") { ::ShowCursor(true); // umm, you're counting me aren't you ::ClipCursor(nullptr); states.pop_back(); } if (states.back()->name == "score_state") states.pop_back(); if (states.back()->name == "win_state") { // todo : record score gameState->reset(); states.pop_back(); } break; } // end switch(action) }                   With the manager out of the way, the base class for the stack-able objects. // file : base_state.h #ifndef _engine_core_states_base_state_h_ #define _engine_core_states_base_state_h_ #include <olcpixelgameengine.h> #include <string> namespace Core { namespace States { // abstract class Base_state { public: Base_state(void* _manager, std::string _name, vec2 _clientSize) { pManager = _manager; name = _name; clientSize = _clientSize; } virtual void initialize() = 0; virtual void shutdown() = 0; virtual void pause() = 0; virtual void resume() = 0; virtual void handleEvents() = 0; virtual void update(float _deltaTime) = 0; virtual void draw() = 0;s void* pManager; // que sara sara std::string name; // todo : refactor string to numeric id? :) vec2 clientSize; protected: Base_state() { /* empty constructor */ } }; } } #endif Correct me if I'm wrong (please) but of all the reasons to use polymorphism, this case is one of the better (subtyping). To be able to keep like items that are different in a common container.  The derived class header would be as expected. Base class overloads in place plus specific functions and variables that would be required to make the new object behave as it will. Nothing new. I'll admit, I'm trapped in the common cycle that most online tutorials mold where the loop is divided into update / draw / check state / repeat. Is it correct? <shrug> So all this may be familiar and you hate it, or it's new and reader be cautious. The basic take away here is the stack concept. On the main loop side, we declare a Core::StateManager instance and initialize. During the loop, update and draw calls to the manager fire. I feel I don't need to list any additional modules but will list an example derived menu class. #include "menu.h" #include "../GameState.h" using namespace Core::States; void Menu::initialize() { rectPlay = { 360, 140, 440, 160 }; // 80 x 20 rectQuit = { 360, 165, 440, 185 }; // need more data or buttons are a known size } void Menu::shutdown() { } void Menu::pause() { } void Menu::resume() { } void Menu::handleEvents() { } void Menu::update(float _deltaTime) { } void Menu::draw() { olc::Pixel colorDefault = olc::Pixel(255, 255, 255); olc::Pixel colorHover = olc::Pixel(255, 255, 0); // _ // .--- todo : don't like handling user input inside a draw routine...fix me ----. \_(O,O)_ // V V ~ \ // ------------------- Game Menu ------------------------- Core::StateManager* manager = (StateManager*)pManager; olc::PixelGameEngine* e = manager->engine; POINT cursorPos; cursorPos.x = manager->engine->GetMouseX(); cursorPos.y = manager->engine->GetMouseY(); e->DrawRect(rectPlay.left, rectPlay.top, rectPlay.right - rectPlay.left, rectPlay.bottom - rectPlay.top); e->DrawRect(rectQuit.left, rectQuit.top, rectQuit.right - rectQuit.left, rectQuit.bottom - rectQuit.top); std::string cursorInfo = "CursorPos "; char buf[16] = ""; _itoa_s(int(cursorPos.x), buf, 10); e->DrawString(20, 20, std::string(buf)); _itoa_s(int(cursorPos.y), buf, 10); e->DrawString(70, 20, std::string(buf)); vec2 textOffset = { 25.f, 6.f }; int x = int(rectPlay.left + textOffset.x); int y = int(rectPlay.top + textOffset.y); // Play ------------------------------------------------- olc::Pixel textColor = colorDefault; if (::PtInRect(&rectPlay, cursorPos)) { textColor = colorHover; if(e->GetMouse(0).bPressed) manager->handleEvents(Core::eStateAction::play); } e->DrawString(x, y, "Play", textColor); // Quit -------------------------------------------------- x = int(rectQuit.left + textOffset.x); y = int(rectQuit.top + textOffset.y); textColor = colorDefault; if (::PtInRect(&rectQuit, cursorPos)) { textColor = colorHover; if(manager->engine->GetMouse(0).bPressed) { if (MessageBox(nullptr, "Are you sure you want to quit?", "Serious?", MB_YESNO) == IDYES) exit(0); } } manager->engine->DrawString(x, y, "Quit", textColor); } The game screen state would be similar but behave as what the main game play would be. Every valid screen pointer lives the life of the application but only the one at the back of the stack is active at a given time. This has served me well for adding game state transitions in custom work.  Input and drawing are engine responsibilities so there is a member pointer to contend with in the manager. Perhaps not the best approach, but I don't know of alternatives other than a hard global or worse, singleton. But a subclass gets this functionality through the manager held engine pointer. Crappy note to end on perhaps, but that's where I am. It works, I called it good and have been reusing a number of times. Tips from the leet are always appreciated assuming my words are better than a hodgepodge of nonsense.      edit log 2019_05_03 Code cleanup

MarkK.

MarkK.

Custom Side Scrolling Game Challenge : Explosive Balls

This is of yet a place holder blog entry which I intend to edit as I progress. This go around I'll be using the software pixel plotter I mentioned in a previous blog entry. At this time of writing, all I have is my napkin design concept, which I think I really have something here.... you know...the whole no weapons thing...We let that simmer for a week. Hop over to the forums for some inspiration. Will my game idea cut it? Shouldn't matter. But it does  you know it. So whats' happening, a partial automatic background simulation running pretty smooth and reliable. I'm testing with some debug visuals, all good. My bodies go stable, perfect..,,whoops it somewhat quickly starts to sink slowly. Slowly, at a speed that appears to be on purpose. Umm...little bug. I go find it, and stop...wait...that's actually pretty cool. I have it trigger when it completely buries itself. So, slap a feature label on it instead. Sha-bang. Now we're talking  Okay that's week one.    And...we're back. Week two and maybe a few. I got distracted, but didn't ignore. My little pixel got tucked in every night and thought about during the day. So, I've always thought this challenge game thing should be played slightly different. Everyone goes off to do one of their next best things, or show off this or that. In the end it's great. But what about the middle...right? <-- Here's my go at it for today. The following listing is my collision code. I'm using this goofy thing that might be worth a share.  Right out of the gate, don't laugh.  trying to write a little game here. Thanks, actually this is one of the 'why do you do it' things right here. Those times when math dances that dance and it's so nice to looks at. If this is one of those times, we'll see  But right now the simplistic approach at its finest and I think per pixel. Check it out, I'll walk though real quick like.   physicsObjs is a std::list of an abstract class. Then inheritance gives me other objects but all in the same list. So we walk it and early out if it's not active. Here we go...now expert I am not, but I want to do something cool. I choose a lite physics sim and first up is that downward force. All right, equals mc what? Telling you... truth. didn't even bother to look. I don't care. This is my game, we're doing it how I want...it's going to be banging...What's that value 9.8 something, size should effect this thing and we'll chop it into a time slice. Nice, let's build our first force direction do-dad. Umm, I appear to be short on some overloads with the framework I choose. No problem, long hand it, mix it with some outside influence and put it in a next desired holding spot. Clamp it for good measure.  Now that we know how fast we want to get there, we calculate where that might put us and hold on to that. So what this is, is a circle against 2D pixel terrain. Terrain is stored on a grid larger than the screen size so we have some indexing math to contend with. I start off with a check against the grid height of the memory terrain so I don't flow into memory that's not mine and I'm scanning down. I want to look at all the pixels in the lower hemisphere of my search grid. So I build that rectangle and walk it bottom up. I assume that against terrain, my objects only care about bouncing and a bounce will never be directly above. Hence only test the bottom half. Object-object collision is a separate pass. What is important to me also is where is this bounce going to take me. So here's what I do. From the bottom center I go horizontal negative per pixel, on hit, I record the vector offsets. No big boy math. If it's terrain, take that raw offset and add it to the reflection accumulator. Found one, okay stop. move up a pixel and go again until another hit or just pass through. Repeat on the positive side. Now we have this ugly 2d vector, so I normalize and then scale it by the magnitude of what we were. Squeeze it with a cheep decay and off we go.  // update physics --------------------------------------------- for (auto &obj : physicsObjs) { if (obj->bDead) continue; // apply force .................................... float gravity = 9.81f * obj->mass * fElapsedTime; vec2 force = { 0.f, 0.f }; // todo : mass / size maybe / etc.. obj->acc obj->nextVel.x += force.x; obj->nextVel.y += (gravity)+force.y; // clamp velocity ................................. if (obj->nextVel.x > 10.f) obj->nextVel.x = 10.f; if (obj->nextVel.x < -10.f) obj->nextVel.x = -10.f; if (obj->nextVel.y > 10.f) obj->nextVel.y = 10.f; if (obj->nextVel.y < -10.f) obj->nextVel.y = -10.f; // calculate desired position obj->nextPos = obj->pos + obj->nextVel; // potential positions established and next desired state set (free form) obj->bHitThisFrame = false; // reset for next run (hmmm...not sure i want or need) // check against terrain (sudo raycast) vec2 reactionAccumulator = { 0.f, 0.f }; // sum all positive hit vectors int numHits = 0; if ((obj->nextPos.y + obj->radius) < terrain.nMapHeight) { // assuming a positive downward velocity (only test lower hemisphere) int xL = int(obj->nextPos.x - obj->radius); // left index int xC = int(xL + obj->radius); // ---- center vertical ---- int xR = int(xC + obj->radius); // right index int yT = int(obj->nextPos.y); // slice center top int yB = int(yT + obj->radius); // bottom index float objR2 = obj->radius * obj->radius; // radius squared for (int i = yB; i > yT; i--) { // scanline search for terrain (bottom up) int scanlineIndex = i * terrain.nMapWidth; for (int j = xC; j > xL; j--) { // scan center to left- break on first hit if (terrain.map[scanlineIndex + j] == 1) { vec2 v = obj->pos - vec2(float(j), float(i)); if ((v.x * v.x) + (v.y * v.y) < objR2) { reactionAccumulator += v; numHits++; break; } } } for (int j = xC; j < xR; j++) { // scan right to center - break on first hit if (terrain.map[scanlineIndex + j] == 1) { vec2 v = obj->pos - vec2(float(j), float(i)); if ((v.x * v.x) + (v.y * v.y) < objR2) { reactionAccumulator += v; numHits++; break; } } } } if (numHits > 0) { vec2 unitReaction = reactionAccumulator.norm(); obj->vel = unitReaction * obj->nextVel.mag(); obj->nextVel = obj->vel * 0.94f; // decay if (numHits >= ((yB - yT - 1) * 2)) PostQuitMessage(0); numHits = 0; } obj->pos = obj->nextPos; //todo : get closet to contact point // object position wrap terrain screen if (obj->pos.x > terrain.nMapWidth) obj->pos.x -= terrain.nMapWidth; if (obj->pos.x < 0) obj->pos.x += terrain.nMapWidth; } else { obj->bDead = true; } }   Okay, that was fun. Last night I shoe horned in my game state manager, adapted it to draw in this fashion with the base menu switch I use to start out with and it should be mostly game play stuff this week... I'm excited to pull out one of my real old sprite sets for this player charcter.      "I'll be back" And a week old screenshot Let's do this thing...still pumped.   Part two continues here.  

MarkK.

MarkK.

Theory GameDev Challenges - Swinging back around

Took an active break with the challenges but happily watched this last year. Pretty cool what's happening in this space on the site and curious now about the challenge fork that has recently appeared. Lots of good things happening on gamedev.net - hope to meet more new people that share the same interest. Alas, I'm in the hobby group which tends to hop me around from technology to technology. This last weekend (and into today) was nice. Minor distractions, work is done, already queued up the next major project to take it to the end of 2019 with dignity. All set, life is good. So I sit down to the youTube and catch up with the who is who and how badly are they doing it. You know...you've been around a while, you recognize what smells right off the .bat Now, I'm not much for wordy blog entries but this must be told. Oh jeebers yes, this was a good smell. So sweet.   A toy 3D triangle renderer in software presented in a 4 part series. The code built is directed to the console but there is available an alternative hardware screen blit approach with the olcPixelGameEngine starter files making it an almost 1:1 follow along. The results are great and really punch home what is going on behind the scenes on our graphics hardware. Point transformations (object space through to screen space), screen clipping of triangles, perspective texture correction at triangle rasterisation. A build up of a 3D environment from his pixel engine. I'm only to part 3 but really happy with the reinforced learning so far. My experience will be different because I refused to take the simple math by function call approach and simply overloaded operators within a couple structs as we went along. The code base is small enough to grasp the majority at once with enough speed to do some interesting future experiments.  So here we are, new software technology in our hands. Is it punchy enough to take the role of gamedev challenge framework?  it's got a real good shot  (screen shot on an old i5 cpu / intel 9300 intergrated graphics) I like it because it takes me back to the old days of a wolfstien like raycaster clone we were learning AI with but this is way faster. It's nice because it's so pure and not bound to a particular target machine. A start code base that can be shared between different os user types. Nothing being done behind your back, it's right there with a couple sprinkles of "actually, yes you can do it", see right here... Thanks for breezing by. MarkK.

MarkK.

MarkK.

 

2D GameDev Challenges - January 2018 (Missile Command)

This challenge was a good run because of Implementing coroutine with IEnumerator and yield return Simple Dictionary based texture manager Basic Particle Engine Ripped GameStateMachine from previous monogame project (joust) had fun making flare particles Did I mention, it's not cool unless it has lightning bolts browsed current scifi google images...nice...inspired. Source : MarkK_LightningCommand.zip    

MarkK.

MarkK.

 

GameDev Challenges - November 2017 (Pong)

This months challenge was a fun distraction.  Interesting features in this source upload include modern opengl basic framework freetype implementation AVI loader for intro screen Stack based game state experimented with raw input  experimented with c++11 smart pointers Source : MarkK_Pong.zip

MarkK.

MarkK.

 

Dragon Attempt

So recently, some dude in the forum was talking about being told dragons cause a technical problem in a games setting. Well, I'm setting out to find out what the real story is. For now...satisfying my artistic side with a mostly finished bust and starting blocking the body... day 2
day3 quickie 10 minute wing test rigging...oh yeah...it's gonna' be a tail slapper...moving on day 4-5 Hit an export snag from blender...typical. Sorted now...it's difficult to get the right settings that play nice between tools. Here is a short clip of the assets in Monogame. Moving to texture and animation. day...umm...not sure anymore. UV's laid, starting paint. Finished Texture on main body
And finally the asset working in game. This last video is a stub of the main menu screen.

MarkK.

MarkK.

 

Female Character Upgrade

Experimenting with the 3D side of Monogame. Animation skinning was fairly easy. I was actually surprised because almost anything exported from Blender turns to failure except going to Unity. The FBX importers in the content pipeline are good. More on that later. Today I show one of the main character upgrades. I'm torn between this look(left) and the older cartoon style(right), but leaning toward the new.

MarkK.

MarkK.

 

Added My Third Hobby Game

So, I sat down to see what this MonoGame thing was all about.
I used to mess with XNA back around version 2.
Well, I have to say...I liked it. Easy to get up and running. Joust was one of my favorite games from the 80's.
[ Here ] is a link to my hobby remake version. (follow to google drive download location)

MarkK.

MarkK.

 

Planet Rendering

Something I've always been interested in is planet rendering.
Although it's been done a few time in Unity already, I'm having a go at it.
It's one of those complicated things that require you to be firing on all cylinders.
Well, I'm not anywhere near close to anything yet, but after playing with noise generation, custom procedural geometry and custom shaders, I have something to show. The image progression below is 3D noise applied to a custom sphere, a detail diffuse added and adjusted color ramp generated from the noise height values, then lastly the first rough pass of generating tangent space and normal maps from the noise height values.

'Kinda fun.
These images would represent the 20,000 km distance.
Now it's getting close to thinking about how to deal with landing on this sucker.






edit: added blended detail normals.
maybe sharpened it up too much...but I'm just going with it.

MarkK.

MarkK.

 

Assimp Output Optimization

The assimp, "makes your program sexier".
If you're anything like me, you have an appreciation for simple.
void Model::loadModel(std::string path){ Assimp::Importer importer; const aiScene* scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); // handle error...}Done. Then to get your hands on the data...
aiNode* node = scene->mRootNode;aiMesh* mesh = scene->mMeshes[node->mMeshes[/*multiple mesh index*/]];for(unsigned int i=0; imNumVertices; i++){ aiVector3D = mesh->mVertices aiVector3D = mesh->mNormals // if not null aiVector3d = mesh->mTextureCoords[0/*first uv set*/] // if not null}for(unsigned int i=0; imNumFaces; i++){ aiFace face = mesh->mFaces; for(unsigned int j=0; jDamn that's tight. Doesn't get any better. Love the assimp.
So we take a closer look with a 5k character model. After import, we're given a 30k character model. Hate the assimp. :)
LOL...yes, we know what you did. Vertex duplication to the extreme. Our little lovely, rewrites to a linear index array and dumps vertices per face making indexed array drawing pointless. Keep hold of the love though, because our program is sexier even though the compile time is now measured in eons. Well, that last part makes her a butt-a-face, so I'm sorry sweetie...you go straight to offline process.
Right, so we have our data...it's bloated but it's there.
Time for a justifiable early data optimization.
// optimize out vertex duplicationmyMesh mesh; // holding extracted data from assimp std::vector vv; // vector std::vector vi; // vector indicesfor (unsigned int i = 0; i ].texcoord.x == vv[j].texcoord.x && mesh.vertices[mesh.indices].texcoord.y == vv[j].texcoord.y) { if (mesh.vertices[mesh.indices].position.x == vv[j].position.x && mesh.vertices[mesh.indices].position.y == vv[j].position.y && mesh.vertices[mesh.indices].position.z == vv[j].position.z) { foundVertex = true; vi.push_back(j); break; } } } if (foundVertex == false) { // add new vertex to list sVertex vertex; vertex.position = mesh.vertices[mesh.indices].position; vertex.normal = mesh.vertices[mesh.indices].normal; vertex.texcoord = mesh.vertices[mesh.indices].texcoord; vv.push_back(vertex); vi.push_back(vv.size() - 1); }}Then we write this out to a binary file.
std::string outPath = "NameYourOwnFileFormat";int numVertices = vv.size();int numIndices = vi.size();std::ofstream outFile(outPath, std::ios::out | std::ios::binary);outFile.write((char*)&numVertices, sizeof(int));outFile.write((char*)&numIndices, sizeof(int));for (int i = 0; i , sizeof(sVertex));}for (int i = 0; i , sizeof(unsigned int));}outFile.close();Did I say I like easy...Back on engine side, it's a simple straight read and direct buffer upload.
No muss, no fuss. On the test mesh, we removed ~70% of the duplicated vertices cutting our asset size to about one third of what we were given.
Pretty sweet for an afternoon's play.

MarkK.

MarkK.

Sign in to follow this  
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!