Jump to content

  • Log In with Google      Sign In   
  • Create Account


Member Since 22 Aug 2001
Offline Last Active Yesterday, 10:24 PM

#5029301 Rule of Three, and const-correctness questions

Posted by Trienco on 05 February 2013 - 11:26 PM

Since const correctness still comes across as something that's "nice but not mandatory", let's look at things that will fail if you don't write const correct code.


First, the one thing that can really get me mad: people writing interfaces/APIs using char* instead of const char*.

void parseString(char* str) { ... }
std::string text;
parseString(text.c_str()); //Fail, as c_str() returns const char*



While these have a simple workaround, it can still be annoying

void analyseObject(MyClass& obj) { ... }
analyseObject( MyClass(a,b,c) ); //Fail, temporary variables are r-values and const


struct MyClass {
   void print() {...}
MyClass(a,b,c).print(); //Fail, temporary = const



To clarify the observable effects of ignoring the Rule of Three, because unlike missing const correctness, this won't conveniently fail to compile in the first place:


struct MyClass
    int* data;
    MyClass() : data(new int) {}
    ~MyClass() { delete data; }
vector<MyClass> v;
v.push_back(MyClass()); //Fuse is lit
cout << v.front().data; //Fail
v.clear(); //Fail
void function(MyClass a) { ... }


Let's ignore compilers optimizing out the temp variable or using move semantics, since you can't rely on it.


First example creates a temporary instance, then creates a copy in the vector (which just stupidly copies the pointer). The temporary goes out of scope and deletes data. The next line accesses data that was just deleted. You will see either garbage (kind of helpful), crash with an access violation (very helpful) or it will just seem to "work fine" (dangerous), as the memory will most likely not have been overwritten yet.


Then we clear the vector and delete the instance in it. Now data is deleted a second time. If you are lucky, your application will crash.



The second example looks harmless, but does pretty much the same. To call the function, you create a copy. When the function returns, this copy is destroyed and deletes your data. The original object is now in an invalid state with data pointing to deleted data. Any access will hopefully crash and when the original object goes out of scope, the memory is attempted to be deleted a second time.



Why is crashing the best case scenario? Because it is hard to miss and clearly tells you there is a bug. The same bug could result in working "just fine" most of the time, but returning strange values on every odd Thursday during full moon. This can cost you days or weeks just trying to reproduce the bug and tracking it down.

#5028540 First person camera and matrix transforms

Posted by Trienco on 03 February 2013 - 11:02 PM

You already are doing the inversion in the last block of your lookAt function, when you transpose the rotation part and invert the translation part. This method only works for special cases, but unless you "break" things by adding scaling to your matrix, you have that special case.

#5028277 Timing seconds per frame in C++

Posted by Trienco on 03 February 2013 - 02:13 AM

Using microseconds gives even more accuracy, however there are not any standard microsecond timers in C++, so you have to use third party or OS-specific timers, unless you are using the new C++11 standard's <chrono> library, which has a high precision timer that might be microsecond or better, but falls back to milliseconds if microsecond precision isn't available.


If you are using VS 2012, you should use the boost implementation of the chrono library (unless they updated it in the meantime). Why? I basically picture the conversation like this:


Marketing: "We already put chrono support on the website."

Devs: "But we can't make it in time!"

Marketing: "We don't give a damn."

Devs: "Let's base all clocks on the standard timer, pretend we implemented it and just deal with the bug reports later."

Marketing: "See, was that so hard?"


I'd definitely use some chrono implementation, though. It's the new standard, it's platform independent and you don't have to worry about the potential pitfalls of using QueryPerformanceCounter on multi-core CPUs.

#5028270 First person camera and matrix transforms

Posted by Trienco on 03 February 2013 - 01:40 AM

The problem is that by storing a target position, you are making something that is conceptually really simple a lot more messy. For one, you can't simply rotate a target position, because it would rotate around the origin instead of the camera position. You can't just linearly move it around either, as you might have noticed from things not working. So stop dealing with "which point am I looking at" and think about "which direction am I looking in".

For an fps camera, you probably want to limit your up/down rotation to +/-89°, so in your case, the otherwise ugly Euler Angles are the easiest approach. Why not 90° you might ask? Because of something many people don't quite understand about the lookAt helper function. "Up" must be roughly your local up (ie. imagine sticking a pencil in the top of your head, that's the "up" lookAt needs. Now, simply using (0,1,0) works great, until you rotate exactly +/-90° up/down (where it should be (0,0,1) or (0,0,-1)) or more than 90° (where it has to be (0,-1,0)).

So what you really need for lookAt is your position, a point to look at (or the direction to look in) and a vector pointing "up". That just happens to be the 4th, 3rd and 2nd column of the cameras transformation matrix, but we just want fps style and take some shortcuts:


//Don't try to use this for a flight simulator without stocking up on aspirin first
float pitch = 0;
float yaw = 0;
if (left) yaw -= rotationSpeed * mouseDelta;
if (right) yaw += rotationSpeed * mouseDelta;
if (up) pitch -= ...
while (yaw < 0) yaw += 360;
while (yaw > 360) yaw -= 360;
if (pitch > 89) pitch = 89;
matrix rotation = createRotationMatrix(pitch, 1,0,0) * createRotationMatrix(yaw, 0,1,0);
//At this point, you can either transform some default view direction or just have the camera look right by default and extract the columns of this matrix.
//For simplicity, we use lookAt
viewMatrix = lookAt(cameraPosition, cameraPosition + rotation.3rdColumn, rotation.2ndColumn);


Probably a lot more reusable and not really much more complicated:


matrix rotation;
vector translation;
//Note that left/right and up/down are a different order to use the global "up" and the local "right"
if (left/right) rotation = rotation * createRotation(mouseDelta, 0,1,0);
if (up/down) rotation = createRotation(mouseDelta, 1,0,0) * rotation;
if (forward/backward) translation += distance * rotation.3rdColumn;
if (strafeLeft/Right) translation += distance * rotation.1stColumn;
matrix transformation = rotation;transformation.4thColumn = translation;
viewMatrix = inverse(transformation); //Use the simple version (same as in lookAt)

You can also keep it all in one matrix, which makes translation easier, but requires extra steps when rotating around a global axis.


matrix transformation;
if (left/right) {
     vector position = transformation.4thColumn;
     transformation = transformation * createRotation(yaw, 0,1,0); //This will also rotate our position around the origin
     transformation.4thColumn = position;
if (up/down) transformation = createRotation(mouseDelta, 1,0,0) * transformation;  //This will rotate around our current position
if (movement) transformation = createTranslation(x,y,z) * transformation; //This will use local x,y,z
if (gettingPushed) transformation = transformation * createTranslation(x,y,z); //This will use local x,y,z
viewMatrix = inverse(transformation);


This is obviously pseudo code. Since 3D graphics is basically nothing but vector and matrix math, there simply is no way to get around learning and embracing them. The absolute minimum to start out is getting familiar with rotation and translation matrices and getting an intuitive grasp of what the components represent and what multiplication will do to them.

You might notice that the last versions will also work just fine for all your other game objects and won't fall apart when you start turning things upside down. So it's pretty much the most generic way if you stay away from scaling (or keep it separated). There is also no lookAt, because most of what that does would be completely redundant.

One thing to keep in mind is that you need to occasionally reorthonormalize the rotation/transformation matrix, as floating point errors will accumulate and start creating some trippy effects. When using lookAt, that happens automatically, since it is reconstructing the whole matrix every frame.

Eventually people will tell you how quaternions are essentially the "one true way of rotation" or even the second coming, but in the end they are completely equivalent to rotation matrices and the only important thing is to know when they are more efficient. There is however absolutely no reason to worry about them at this point (and no, they are not the "only solution to gimbal lock", that's "don't use frigging Euler angles").


edit: ok, great. Never edit a post with code, unless you want every single line break to disappear...

#5028034 First person camera and matrix transforms

Posted by Trienco on 01 February 2013 - 11:25 PM

Judging from your code the very simple reason why your rotation isn't working is because you aren't rotating at any point. Shifting the point to look at left and right has nothing to do with rotation and a very simple question should make it obvious: "how do you expect to look behind your position?"


Rotation involves trigonometry, either used directly (I wouldn't) or hidden away (constructing a rotation matrix). Look at the graphs for sin and cos, remember that an angle of 0° is straight to the right and you should notice how x = cos and y = sin for any given angle.

#5027242 First person camera and matrix transforms

Posted by Trienco on 30 January 2013 - 11:56 AM

Personally I would absolutely avoid storing angles, though it works fine for a simple FPS style camera (just make sure you always rotate around "up" first and "right" second). The simple fact is that the order in which your rotations are applied is important and completely lost if you just accumulate angles.


Also, pleaaaase don't abuse scaling to zoom. Zooming is a perspective effect and the result of focusing your lens to show a smaller area, ie. what you get by reducing your field of view. Scaling will scale your objects, can make them grow beyond your near plane or even behind your camera and suddenly zooming turns into a wall hack.


My usual camera class is using my own vector/matrix stuff, so it would be mostly useless to you, but the basic setup is that it stores a transformation matrix (4x4). All rotations and translations are directly applied and accumulated in that matrix when processing the users input. Since matrix math is neat, applying a rotation around (1,0,0) will always look up/down around your current "right", just like a translation along (1,0,0) will always strafe left/right. For the rare situations where you want to move/rotate using the world axes, you just multiply the new transformation from the other side (to visualize it, one way the rotation happens after the previous transformations, the other way it happens before... while all local axes are still aligned with the world axes).


Btw., your typical lookAt function will just build a transformation matrix from the passed vectors and then invert it, so it becomes extremely superfluous if you store the transformation matrix in the first place. Inverting it is simple, as long as you stay away from scaling.


viewMatrix = Matrix44(
        Transform[0], Transform[4], Transform[8], 0,
        Transform[1], Transform[5], Transform[9], 0,
        Transform[2], Transform[6], Transform[10], 0,
        -(Transform[0]*Transform[12] + Transform[1]*Transform[13] + Transform[2]*Transform[14]),
        -(Transform[4]*Transform[12] + Transform[5]*Transform[13] +    Transform[6]*Transform[14]),                        
        -(Transform[8]*Transform[12] + Transform[9]*Transform[13] + Transform[10]*Transform[14]), 1);

#5027085 First person camera and matrix transforms

Posted by Trienco on 29 January 2013 - 11:38 PM

Most of the "easy, convenient and beginner friendly" stuff was removed to clean up the API, because it was kind of silly to have three or more ways of doing the same thing. They are still around through the compatibility extension, though.


The easiest way to handle any camera to me is to treat it like any other object. Give it a transformation matrix, understand that the columns of that matrix are right/up/forward/position vectors (depending on your setup) and that to turn it into a "view matrix" you just have to invert it. Every frame you add the new transformations depending on your input and set the inverted matrix as view matrix.


It also means that "attaching" a camera to an object or moving it for cut scenes is literally straight "forward", compared to trying to keep the inverted matrix and apply everything "in reverse".

#5025988 Clashing Enemies

Posted by Trienco on 27 January 2013 - 12:56 AM

I'd very much go with the AI approach, because trying to solve this with physics in a tight crowd sounds like an awful mess. Yes, it's trivial to have _two_ objects and resolve their collision by moving one to the side. Except that in a crowd it might then hit someone else and you end up with a recursion that takes forever to resolve. The only solution would then be "if your movement would cause a collision, don't try to move at all".

#5024606 A star

Posted by Trienco on 22 January 2013 - 11:23 PM

What's worrying me even more is that I can't tell if this is a header or source file (the former would basically result in a lot of crying and forehead to table contact, because of the "using" and all the static variables). And even worse is the fact that the code looks like you really need to take a step back and understand memory management in C++. It's not just the abuse of the word "garbage collection" (which is a concept that is basically the exact opposite of what you're doing), but all the pointless new and delete calls in combination with leaking memory like crazy and apparently thinking that somehow "push(*pointer)" would not still create a copy of your object, making it very pointless to do an expensive allocation on the heap.

#5022435 Problem with creating a class for drawing....

Posted by Trienco on 16 January 2013 - 10:59 PM

One kind of obvious thing that jumps at me is your constructor. Take a good look at it and if you don't notice it, describe to me in detail what it does ,-)

#5022059 Problem with creating a class for drawing....

Posted by Trienco on 15 January 2013 - 11:20 PM

The code for all the setup isn't shown. What does your viewport, view and projection look like?


Since you don't clear the depth buffer, did you make sure that depth testing is disabled?


Are you sure that based on your view/projection "front" is where you think it is?


Why are you setting alpha to 0 (100% transparent, depending on blend mode)?

#5021124 VS2010 problem with multiple declaration in for loop

Posted by Trienco on 13 January 2013 - 10:31 AM

You still have the problem of increasing your iterator twice when you erase an object.


Common ways are either if/else (if -> erase, else -> increment) or erasing a tmp iterator (auto tmp = iter++).

#5016643 I have two functions that do the same thing. Which one is faster?

Posted by Trienco on 02 January 2013 - 05:17 AM

Am I the only one being very confused by the fact that passing a the Entity hero as a parameter to its own member function seems extremely weird and pointless? This basically feels less like it should be "which is faster" and more "somebody please explain the point of member functions".


Your member function already has access to all the members of hero, so why would you pass it at all? Take the second one, remove every last "hero->" (or replace with "this->", depending on preference) and stop passing the object as a parameter to itself.

#5016582 Game crashes for no obvious reason, help please

Posted by Trienco on 01 January 2013 - 11:58 PM

I'm sure I posted this a lot, but: just because something "seems" to be working just fine, doesn't mean it's actually right. C++ is a language where half the standard seems to say "this is undefined behavior, and this, and this, and this". It is absolutely the last language you should be using for trial and error coding, because stuff will work just fine when you "try something" and will blow up in your face two days later. Why? Because somewhere something completed unrelated in your code will affect the memory layout or eventually overwrite the objects memory you have so happily been accessing and executing after you already deleted it.


The language itself does little to no error checking for you, the compiler will mostly point out syntax errors and many things won't trigger a warning. There is no hand holding and you are expected to know what you're doing. That's also why most people on this forum will tell beginners to stay away from it and start with another language. Not because it is so hard to make something compile. Not because the syntax is difficult or the standard library is so complex. Because you can write horribly bad and bug ridden code and it will still let you believe that everything is working perfectly fine... until it doesn't.


So the usual reply to 99% of all "but it was working before" is "yes, because you have been incredibly lucky".


Reasons why it worked before? Maybe you never called it with a different state. Maybe it kept using the deleted state and you never noticed. Maybe you were incredibly lucky that your new state was placed at exactly the location of your previous state. There's lots of reasons why something happened to work in C++ when it never should have.

#5015331 std::string and erase() to get data I need from string

Posted by Trienco on 28 December 2012 - 11:37 PM

If you can rely on your input being formatted like that, just dump it in a stringstream and read it back.

const string res = "640x480x32";
int height, width, depth;
char delimiter;  

stringstream stream(res);
stream >> height >> delimiter >> width >> delimiter >> depth;



You could use getline and define 'x' as a delimiter, but then you get each number as a string and still need to convert to int.

You could use .ignore(1) instead of the dummy character, but it will look horrible to read.