my solutions

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: my solutions

Post by albinopapa » June 27th, 2018, 6:10 pm

Code: Select all


// the unsigned char alpha parameter would be a value from 0 to 255
void Graphics::DrawSpriteTransparent( int x, int y, RectI srcRect, const RectI& clip, const Surface& s, unsigned char alpha, Color chroma )
{
    ...
    for(int sy = srcRect.top; sy < srcRect.bottom; ++sy)
    {
        for(int sx = srcRect.left; sx < srcRect.right; ++sx)
        {
            // Cache destination so as not to recalculate each loop
            int dstX = x + (sx - srcRect.left);
            int dstY = y + (sy - srcRect.top);

            // Get the source and destination pixels
            const Color dst = getPixel(dstX,dstY);
            const Color src = surf.GetPixel(sx,sy);
            
            // invAlpha is inverse alpha, which is the max value minus the current value
            const int invAlpha = 255 - alpha;

            // Linear interpolation from source to destination.  
            // Let's say your alpha is 64 and your Red channels are 255 for the src and 24 for the dst.
            // 64 * 255 = 16320
            // (255 - 64) * 24 = 4584
            // 16,320 + 4,584 = 20,904
            // Obviously, 20,904 is too large to fit in an unsigned char ( max value is 255 ) so divide by 255
            // 20,904 / 256 = 81 ( 81.65625, but no decimals in integers )

            // Let's compare: 
            // 64 is about 1/4 of 255 so a quarter of the src ( 255 / 4 ) = 63
            // The remainder of the color comes from 3/4 of dst ( 24 / 4 * 3 ) = 18
            // 63 + 18 = 81

            const unsigned char r = unsigned char( ( (src.GetR() * alpha) + (dst.GetR() * invAlpha) ) / 255 );
            const unsigned char g = unsigned char( ( (src.GetG() * alpha) + (dst.GetG() * invAlpha) ) / 255 ); 
            const unsigned char b = unsigned char( ( (src.GetB() * alpha) + (dst.GetB() * invAlpha) ) / 255 );

            PutPixel( dstX, dstY, { r, g, b } );
        }
    }
}

// Another form of linear interpolation is c = a + ( ( b - a ) * t )
// a = 24
// b = 255
// t = 64
// c = 24 + ( ( ( 255 - 24 ) * 64 ) / 255 );
// Reversing the order of a and b would yield a value approaching 24 as opposed to 255
// Still have to divide by 255 so the value is in the range of 0 to 255 before adding 24.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: my solutions

Post by albinopapa » June 27th, 2018, 6:20 pm

Sorry for the other response, it's been awhile and I thought chili had gone over sprite effects using templates and functors at that point. They should be coming up in episode 14
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

randomGuy
Posts: 32
Joined: May 31st, 2018, 11:45 am

Re: my solutions

Post by randomGuy » July 23rd, 2018, 10:55 am

I have some trouble understanding shifting bits.

Code: Select all

	constexpr Color( unsigned char r,unsigned char g,unsigned char b )
		:
		dword( (r << 16u) | (g << 8u) | b )
	{}
How are we able to shift 'r' with 16 bits to the left when chars can only store 255 bits.
Won't shifting to the left overflow the char variable and lose 16 bits of itself?

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: my solutions

Post by albinopapa » July 23rd, 2018, 7:22 pm

In this case, ( r << 16 ) creates a temporary int, then ORs that with the temporary int ( g << 8u ) then ORs that with the implicitly promoted int b then the result of all that is implicitly promoted to the unsigned int ( dword ).

It's the same as:

Code: Select all

const int r_temp = static_cast<int>( r ) << 16u;
const int g_temp = static_cast<int>( g ) << 8u;
const int b_temp = static_cast<int>( b );
const int rg_temp = r_temp | g_temp;
dword = static_cast<unsigned int>( rg_temp | b_temp );
It's just the compiler does this implicitly behind the scenes. It knows that operations on byte sized chars is slower than operations on 4 byte ints.

I think the CPU reads 4 bytes anyway and just masks out the upper 24 bits either during the two reads ( left and right values ) or after the operation and before the write ( storing the result of char_a + char_b for example ). I could be wrong about this, I have no references to back this up. The same might be happening with long long ( 64 bit int ) when compiling in x64 mode, everything smaller than 64 bit ( 8 byte ) data types get masked to fit in the smaller type in memory without leaving garbage in the upper bits.

Here's an example of what I mean using bit manipulation:

Code: Select all

// Create an array of chars
char rgba[4] = { 255, 128, 64, 255 };

// Change elements in the array using code
rgba[0] = 128;
rgba[1] = 64;
rgba[2] = 32;

// You can access each element in code by going rgba[0] for the red channel, but if my statement 
// above is true, the four bytes of the char array rgba gets read in as an int then the CPU does 
// something like this
{
    // Read as int
    int* temp_0 = ( int*)rgba;
    
    // Store right hand side of operation in memory ( or cpu register ) shifted to the left by 24
    const int temp_rhs0 = 128 << 24;

    // Assign result back into position
    *temp_0 |= ( temp_rhs0 & 0xFF000000 );

    const int temp_rhs1 = 64 << 16;
    *temp_0 |= ( temp_rhs1 & 0x00FF0000 );
    
    const int temp_rhs2 = 32 << 8;
    *temp_0 |= ( temp_rhs2 & 0x0000FF00 );
}
Now there are some things that I'm not clear on as far as how the CPU actually handles cache. For instance, since the variable temp_0 is a pointer which is just the memory address of rgba, I am not sure if it has to reload that data each time it needs to read the value in order to do the operations.

It's possible that the four byte reads are read into a register, the calculations are performed then the result is written to memory. Then, the four bytes are read into a register again to perform the next step since you would be writing back to the same location in memory.

It could also be possible that the compiler is smart enough during it's optimization stage that it reads in the four bytes into a register, performs the calculations and stores the results into temporary registers then and only then does it perform the final calculations and writes the result back to memory.

Code: Select all

    // Read in four bytes
    int reg0 = (int)( *rgba );

    // Assign new values to temporary registers
    int reg1 = 128 << 24;
    int reg2 = 64 << 16;
    int reg3 = 32 << 8;

    // Combine (OR) results to single four byte register
    reg1 = reg1 | reg2;
    reg1 = reg1 | reg3;
    reg1 = reg1 | reg4;

    // Finally, store ( write to memory ) the result
    *rgba = reg1;
However, this is just speculation and it is possible that the compiler won't be smart enough and the values at rgba would have to be loaded from memory, masked, assigned, then stored back to memory since you have three assignment operations, disregarding the fact they are consecutive. The reason for this is the fact that reassigning means the CPU can't make any assumptions about the state the data was in from the last time the values were read in.


Probably more than you needed, but hope it was enlightening.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

randomGuy
Posts: 32
Joined: May 31st, 2018, 11:45 am

Re: my solutions

Post by randomGuy » July 24th, 2018, 10:27 am

I appreciate your detailed answer. I might have to watch Chili's CPU Design playlist to fully understand it but I got what I needed for now.

randomGuy
Posts: 32
Joined: May 31st, 2018, 11:45 am

Re: my solutions

Post by randomGuy » August 1st, 2018, 3:41 pm

This is my solution to the last homework on the Intermediate series:

in game.h:

Code: Select all

	void splitBox(std::unique_ptr<Box> boxPtr);
	void replace(std::unique_ptr<Box> tempBox);
	void remove(std::unique_ptr<Box> tempBox);
	std::unordered_map<std::string, void (Game::*)(std::unique_ptr<Box> box)> actions;
in the game constructor

Code: Select all

	actions["split"] = &Game::splitBox;
	actions["replace"] = &Game::replace;
	actions["remove"] = &Game::remove;


	class Listener : public b2ContactListener
	{
	public:
		void BeginContact( b2Contact* contact ) override
		{
			 b2Body* bodyPtrs[] = { contact->GetFixtureA()->GetBody(),contact->GetFixtureB()->GetBody() };
			if( bodyPtrs[0]->GetType() == b2BodyType::b2_dynamicBody &&
				bodyPtrs[1]->GetType() == b2BodyType::b2_dynamicBody )
			{
				
				Box* boxPtrs[] = { 
					reinterpret_cast<Box*>(bodyPtrs[0]->GetUserData()),
					reinterpret_cast<Box*>(bodyPtrs[1]->GetUserData())
				};
				auto& tid0 = typeid(boxPtrs[0]->GetColorTrait());
				auto& tid1 = typeid(boxPtrs[1]->GetColorTrait());

				if (boxPtrs[0]->GetColorTrait().GetColor() == Colors::Blue && boxPtrs[1]->GetColorTrait().GetColor() == Colors::White || 
					boxPtrs[1]->GetColorTrait().GetColor() == Colors::Blue && boxPtrs[0]->GetColorTrait().GetColor() == Colors::White) {
				std::stringstream msg;
				msg << "Collision between " << tid0.name() << " and " << tid1.name() << std::endl;
				//std::cout << msg.str().c_str() << std::endl;

				if (boxPtrs[0]->GetSize() > boxPtrs[1]->GetSize()) {
					boxScheduledForRemoval.emplace(boxPtrs[0], "split");
				}
				else {
					boxScheduledForRemoval.emplace(boxPtrs[1], "split");
				}
				
				}
				if ((boxPtrs[0]->GetColorTrait().GetColor() == Colors::Red && boxPtrs[1]->GetColorTrait().GetColor() == Colors::Blue) ||
					(boxPtrs[1]->GetColorTrait().GetColor() == Colors::Red && boxPtrs[0]->GetColorTrait().GetColor() == Colors::Blue)) {
					if (boxPtrs[0]->GetColorTrait().GetColor() == Colors::Red) {
						boxScheduledForRemoval.emplace(boxPtrs[0], "replace");
					}
					else {
						boxScheduledForRemoval.emplace(boxPtrs[1], "replace");
					}
					
				}

				if ((boxPtrs[0]->GetColorTrait().GetColor() == Colors::Red && boxPtrs[1]->GetColorTrait().GetColor() == Colors::White) ||
					(boxPtrs[1]->GetColorTrait().GetColor() == Colors::Red && boxPtrs[0]->GetColorTrait().GetColor() == Colors::White)) {
					if (boxPtrs[0]->GetColorTrait().GetColor() == Colors::Red) {
						boxScheduledForRemoval.emplace(boxPtrs[0], "remove");
					}
					else {
						boxScheduledForRemoval.emplace(boxPtrs[1], "remove");
					}
				}
			}
		}
	};
boxScheduledForRemoval will have a key with pointer to the box we want to perform the action on, and a string which is going to be the name of the action. If boxScheduledForRemoval is a member to game.h I get this error "a nonstatic member reference must be relative to a specific object" which I couldn't figure out what it means so I've made it a global variable.

this is our action functions defined in game.cpp:

Code: Select all

void Game::splitBox(std::unique_ptr<Box> tempBox)
{	
	float x = tempBox->GetPosition().x;
	float yPos = tempBox->GetPosition().y;
	float halfSize = tempBox->GetSize() / 2;
	float offset = 0.1;

	if (tempBox->GetSize() > 0.125) {
		for (int i = 0; i < 2; i++) {
			float xPos = x;
			for (int j = 0; j < 2; j++) {
				boxPtrs.emplace_back(std::make_unique<Box>(std::move(tempBox->GetColorTrait().Clone()), world, Vec2{ xPos , yPos },
					tempBox->GetSize() / 2, tempBox->GetAngle(), tempBox->GetVelocity(), tempBox->GetAngularVelocity()));
				xPos += offset;
			}
			yPos += offset;
		}

	}
}

void Game::replace(std::unique_ptr<Box> tempBox)
{
	std::unique_ptr<Box::ColorTrait > blueTrait = std::make_unique<Box::BlueTrait>();

	boxPtrs.emplace_back(std::make_unique<Box>(std::move(blueTrait), world,tempBox->GetPosition(),
		tempBox->GetSize(), tempBox->GetAngle(), tempBox->GetVelocity(), tempBox->GetAngularVelocity()));

}

void Game::remove(std::unique_ptr<Box> tempBox) {

}
and the updateModel:

Code: Select all

void Game::UpdateModel()
{
	const float dt = ft.Mark();
	world.Step( dt,8,3 );

	for (auto it : boxScheduledForRemoval) {

		Box* dyingBox = it.first;
		std::string action = it.second;

		std::vector<std::unique_ptr<Box>>::iterator it = std::find_if(boxPtrs.begin(), boxPtrs.end(), [&](std::unique_ptr<Box>& p) { return p.get() == dyingBox; });
		
		bool foundBox = false;
		std::unique_ptr <Box> tempBox;
		if (it != boxPtrs.end()) {
			if (dyingBox->GetSize() > 0.125) {
				foundBox = true;

				tempBox = std::move(*it);
				boxPtrs.erase(it);
			}
		}
		//perform action outside iterator because it will invalidate it when we add a box
		if (foundBox) {
			(this->*actions[action])(std::move(tempBox));
		}
	}

	//clear this list for next time
	boxScheduledForRemoval.clear();
}


This last homework totally made up for the lack of homeworks in the last couple of videos.
Loved it.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: my solutions

Post by albinopapa » August 1st, 2018, 8:37 pm

"a nonstatic member reference must be relative to a specific object"

This means that you need to do: object.member or object->member. In this case, you are missing the Game object:

game.boxScheduledForRemoval.emplace( boxPtrs[1], "remove" ); // or
game->boxScheduledForRemoval.emplace( boxPtrs[1], "remove" );

You would have to pass a reference or pointer to *this or this to Listener::Listener() and store it, in order to work.

Code: Select all

   for (auto it : boxScheduledForRemoval) {

      Box* dyingBox = it.first;
      std::string action = it.second;

      std::vector<std::unique_ptr<Box>>::iterator it = std::find_if(boxPtrs.begin(), boxPtrs.end(), [&](std::unique_ptr<Box>& p) { return p.get() == dyingBox; });
You should try avoiding naming two variables with the same name. You have 'auto it' in the for loop and 'std::vector<std::unique_ptr<Box>>::iterator it' inside the body of the for loop. The second one is used when erasing from boxPtrs because C++ always uses the most recent scope to resolve which variable you use. However, this can lead to confusion and bugs if you aren't careful. BTW, I would have used 'auto it' again instead of typing out 'std::vector<std::unique_ptr<Box>>::iterator it'.

Code: Select all

void Game::UpdateModel()
{
   const float dt = ft.Mark();
   world.Step( dt,8,3 );

   // Probably would have named boxScheduledForRemoval, boxRemoveList
   // and it would have just been boxAction, also would have taken by const reference 
   // instead of making copies
   for( const auto& boxAction : boxRemoveList )
   {
      Box* dyingBox = it.first;

      // Move instead of making a copy
      std::string action = std::move( it.second ); 
      
      // Would have used auto, and passed a const std::unique_ptr<Box>& since you aren't 
      // going to be changing the contents of pBox
      auto findit = std::find_if( boxPtrs.begin(), boxPtrs.end(),
         [&]( const std::unique_ptr<Box>& pBox ){ return pBox.get() == dyingBox; } )
         
      if( findit != boxPtrs.end() ) 
      {
         if( dyingBox->GetSize() > 0.125 ) 
         {
            ( this->*actions[ action ] )( std::move( *findit ) );
            boxPtrs.erase( findit );  // Erasing also invalidates iterators
         }
      }
   }

   //clear this list for next time
   boxRemovalList.clear();
}
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

randomGuy
Posts: 32
Joined: May 31st, 2018, 11:45 am

Re: my solutions

Post by randomGuy » August 2nd, 2018, 10:56 am

Thank you albinopapa.

In bodyPtr.h

Code: Select all

class BodyPtr : public std::unique_ptr<b2Body,std::function<void(b2Body*)>>
I don't understand how can a unique_ptr point to both b2Body object and std::function<void(b2Body*) function pointer.
Isn't a pointer suposed to point to only one thing?

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: my solutions

Post by albinopapa » August 2nd, 2018, 4:48 pm

The second template parameter is the deleter function, it is used to customize the behavior of unique_ptr allowing you to provide custom behavior when freeing memory. So the unique_ptr class has a T* ptr and a Deleter* delFunc member. The T* ptr is what you use as the pointer, and when you release the T*, delFunc is called passing in ptr as it's parameter.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

randomGuy
Posts: 32
Joined: May 31st, 2018, 11:45 am

Re: my solutions

Post by randomGuy » August 4th, 2018, 11:53 am

Thanks again, I appreciate your help.

In Chili's solution Box.cpp file there is a split function

Code: Select all

std::vector<std::unique_ptr<Box>> Box::Split( b2World& world )
{
	std::vector<std::unique_ptr<Box>> boxes;
.
..
...
	return boxes;
}
How come we make a vector of unique pointers and than copy it when we return it. The copy operator is deleted. Is this one of those cases when the compiler understand what we want to do and call std::move behind the scenes?

Post Reply