mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Refactoring of projectile controller
- projectiles now separated based on type - each type has its own rendering method - refactoring of CShootingAnimation
This commit is contained in:
		| @@ -723,7 +723,8 @@ CRangedAttackAnimation::CRangedAttackAnimation(CBattleInterface * owner_, const | ||||
|  | ||||
| CShootingAnimation::CShootingAnimation(CBattleInterface * _owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool _catapult, int _catapultDmg) | ||||
| 	: CRangedAttackAnimation(_owner, attacker, _dest, _attacked), | ||||
| 	catapultDamage(_catapultDmg) | ||||
| 	catapultDamage(_catapultDmg), | ||||
| 	projectileEmitted(false) | ||||
| { | ||||
| 	logAnim->debug("Created shooting anim for %s", stack->getName()); | ||||
| } | ||||
| @@ -733,10 +734,9 @@ bool CShootingAnimation::init() | ||||
| 	if( !CAttackAnimation::checkInitialConditions() ) | ||||
| 		return false; | ||||
|  | ||||
| 	const CStack * shooter = attackingStack; | ||||
|  | ||||
| 	if(!shooter || myAnim->isDead()) | ||||
| 	if(!attackingStack || myAnim->isDead()) | ||||
| 	{ | ||||
| 		//FIXME: how is this possible? | ||||
| 		endAnim(); | ||||
| 		return false; | ||||
| 	} | ||||
| @@ -753,80 +753,81 @@ bool CShootingAnimation::init() | ||||
| 	//if (attackingStack && attackedStack && owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) | ||||
| 	//	return false; | ||||
|  | ||||
| 	setAnimationGroup(); | ||||
| 	shooting = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void CShootingAnimation::setAnimationGroup() | ||||
| { | ||||
| 	Point shooterPos = stackAnimation(attackingStack)->pos.topLeft(); | ||||
| 	Point shotTarget = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225); | ||||
|  | ||||
| 	//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value) | ||||
| 	static const double straightAngle = 0.2; | ||||
|  | ||||
| 	// Get further info about the shooter e.g. relative pos of projectile to unit. | ||||
| 	// If the creature id is 149 then it's a arrow tower which has no additional info so get the | ||||
| 	// actual arrow tower shooter instead. | ||||
| 	const CCreature *shooterInfo = shooter->getCreature(); | ||||
| 	double projectileAngle = atan2(shotTarget.y - shooterPos.y, std::abs(shotTarget.x - shooterPos.x)); | ||||
|  | ||||
| 	// Calculate projectile start position. Offsets are read out of the CRANIM.TXT. | ||||
| 	if (projectileAngle > straightAngle) | ||||
| 		group = CCreatureAnim::SHOOT_UP; | ||||
| 	else if (projectileAngle < -straightAngle) | ||||
| 		group = CCreatureAnim::SHOOT_DOWN; | ||||
| 	else | ||||
| 		group = CCreatureAnim::SHOOT_FRONT; | ||||
| } | ||||
|  | ||||
| void CShootingAnimation::initializeProjectile() | ||||
| { | ||||
| 	const CCreature *shooterInfo = attackingStack->getCreature(); | ||||
|  | ||||
| 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 		shooterInfo = owner->siegeController->getTurretCreature(); | ||||
|  | ||||
| 	Point shooterPos; | ||||
| 	Point shotPos; | ||||
| 	Point destPos; | ||||
| 	Point shotTarget = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225); | ||||
| 	Point shotOrigin = stackAnimation(attackingStack)->pos.topLeft() + Point(222, 265); | ||||
| 	int multiplier = stackFacingRight(attackingStack) ? 1 : -1; | ||||
|  | ||||
| 	// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise | ||||
| 	shooterPos = stackAnimation(shooter)->pos.topLeft(); | ||||
| 	//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); | ||||
|  | ||||
| 	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225); | ||||
|  | ||||
| 	// to properly translate coordinates when shooter is rotated | ||||
| 	int multiplier = 0; | ||||
| 	if (shooter) | ||||
| 		multiplier = stackFacingRight(shooter) ? 1 : -1; | ||||
| 	else | ||||
| 	if (group == CCreatureAnim::SHOOT_UP) | ||||
| 	{ | ||||
| 		assert(false); // unreachable? | ||||
| 		multiplier = shooter->getCreature()->idNumber == CreatureID::ARROW_TOWERS ? -1 : 1; | ||||
| 		shotOrigin.x += ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; | ||||
| 		shotOrigin.y += shooterInfo->animation.upperRightMissleOffsetY; | ||||
| 	} | ||||
|  | ||||
| 	double projectileAngle = atan2(fabs((double)destPos.y - shooterPos.y), fabs((double)destPos.x - shooterPos.x)); | ||||
| 	if(shooter->getPosition() < dest) | ||||
| 		projectileAngle = -projectileAngle; | ||||
|  | ||||
| 	// Calculate projectile start position. Offsets are read out of the CRANIM.TXT. | ||||
| 	if (projectileAngle > straightAngle) | ||||
| 	else if (group == CCreatureAnim::SHOOT_DOWN) | ||||
| 	{ | ||||
| 		//upper shot | ||||
| 		shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; | ||||
| 		shotPos.y = shooterPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY; | ||||
| 		shotOrigin.x += ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; | ||||
| 		shotOrigin.y += shooterInfo->animation.lowerRightMissleOffsetY; | ||||
| 	} | ||||
| 	else if (projectileAngle < -straightAngle) | ||||
| 	else if (group == CCreatureAnim::SHOOT_FRONT) | ||||
| 	{ | ||||
| 		//lower shot | ||||
| 		shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; | ||||
| 		shotPos.y = shooterPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY; | ||||
| 		shotOrigin.x += ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; | ||||
| 		shotOrigin.y += shooterInfo->animation.rightMissleOffsetY; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		//straight shot | ||||
| 		shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; | ||||
| 		shotPos.y = shooterPos.y + 265 + shooterInfo->animation.rightMissleOffsetY; | ||||
| 		assert(0); | ||||
| 	} | ||||
|  | ||||
| 	owner->projectilesController->createProjectile(attackingStack, attackedStack, shotPos, destPos); | ||||
| 	owner->projectilesController->createProjectile(attackingStack, attackedStack, shotOrigin, shotTarget); | ||||
| } | ||||
|  | ||||
| 	//attack animation | ||||
|  | ||||
| 	shooting = true; | ||||
|  | ||||
| 	if(projectileAngle > straightAngle) | ||||
| 		group = CCreatureAnim::SHOOT_UP; | ||||
| 	else if(projectileAngle < -straightAngle) | ||||
| 		group = CCreatureAnim::SHOOT_DOWN; | ||||
| 	else //straight shot | ||||
| 		group = CCreatureAnim::SHOOT_FRONT; | ||||
|  | ||||
| 	return true; | ||||
| void CShootingAnimation::emitProjectile() | ||||
| { | ||||
| 	//owner->projectilesController->fireStackProjectile(attackingStack); | ||||
| 	projectileEmitted = true; | ||||
| } | ||||
|  | ||||
| void CShootingAnimation::nextFrame() | ||||
| { | ||||
| 	if (owner->projectilesController->hasActiveProjectile(attackingStack)) | ||||
| 	for(auto & it : pendingAnimations()) | ||||
| 	{ | ||||
| 		CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first); | ||||
| 		CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it.first); | ||||
| 		if( (anim && anim->stack->ID == stack->ID) || (anim2 && anim2->stack->ID == stack->ID && anim2->priority ) ) | ||||
| 			return; | ||||
| 	} | ||||
|  | ||||
| 	if (!projectileEmitted) | ||||
| 	{ | ||||
| 		const CCreature *shooterInfo = attackingStack->getCreature(); | ||||
|  | ||||
| @@ -836,18 +837,15 @@ void CShootingAnimation::nextFrame() | ||||
| 		// animation should be paused if there is an active projectile | ||||
| 		if ( stackAnimation(attackingStack)->getCurrentFrame() >= shooterInfo->animation.attackClimaxFrame ) | ||||
| 		{ | ||||
| 			owner->projectilesController->fireStackProjectile(attackingStack);//FIXME: should only be called once | ||||
| 			initializeProjectile(); | ||||
| 			emitProjectile(); | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for(auto & it : pendingAnimations()) | ||||
| 	{ | ||||
| 		CMovementStartAnimation * anim = dynamic_cast<CMovementStartAnimation *>(it.first); | ||||
| 		CReverseAnimation * anim2 = dynamic_cast<CReverseAnimation *>(it.first); | ||||
| 		if( (anim && anim->stack->ID == stack->ID) || (anim2 && anim2->stack->ID == stack->ID && anim2->priority ) ) | ||||
| 			return; | ||||
| 	} | ||||
| 	if (projectileEmitted && owner->projectilesController->hasActiveProjectile(attackingStack)) | ||||
| 		return; // projectile in air, pause animation | ||||
|  | ||||
|  | ||||
| 	CAttackAnimation::nextFrame(); | ||||
| } | ||||
| @@ -855,7 +853,11 @@ void CShootingAnimation::nextFrame() | ||||
| void CShootingAnimation::endAnim() | ||||
| { | ||||
| 	// FIXME: is this possible? Animation is over but we're yet to fire projectile? | ||||
| 	owner->projectilesController->fireStackProjectile(attackingStack); | ||||
| 	if (!projectileEmitted) | ||||
| 	{ | ||||
| 		initializeProjectile(); | ||||
| 		emitProjectile(); | ||||
| 	} | ||||
|  | ||||
| 	// play wall hit/miss sound for catapult attack | ||||
| 	if(!attackedStack) | ||||
|   | ||||
| @@ -207,7 +207,12 @@ protected: | ||||
| class CShootingAnimation : public CRangedAttackAnimation | ||||
| { | ||||
| private: | ||||
| 	bool projectileEmitted; | ||||
| 	int catapultDamage; | ||||
|  | ||||
| 	void setAnimationGroup(); | ||||
| 	void initializeProjectile(); | ||||
| 	void emitProjectile(); | ||||
| public: | ||||
| 	bool init() override; | ||||
| 	void nextFrame() override; | ||||
|   | ||||
| @@ -30,6 +30,7 @@ | ||||
| #include "../CVideoHandler.h" | ||||
| #include "../Graphics.h" | ||||
| #include "../gui/CAnimation.h" | ||||
| #include "../gui/CCanvas.h" | ||||
| #include "../gui/CCursorHandler.h" | ||||
| #include "../gui/CGuiHandler.h" | ||||
| #include "../gui/SDL_Extensions.h" | ||||
| @@ -919,6 +920,8 @@ void CBattleInterface::showAll(SDL_Surface *to) | ||||
|  | ||||
| void CBattleInterface::show(SDL_Surface *to) | ||||
| { | ||||
| 	auto canvas = std::make_shared<CCanvas>(to); | ||||
|  | ||||
| 	assert(to); | ||||
|  | ||||
| 	SDL_Rect buf; | ||||
| @@ -942,7 +945,7 @@ void CBattleInterface::show(SDL_Surface *to) | ||||
| 	fieldController->showHighlightedHexes(to); | ||||
|  | ||||
| 	showBattlefieldObjects(to); | ||||
| 	projectilesController->showProjectiles(to); | ||||
| 	projectilesController->showProjectiles(canvas); | ||||
|  | ||||
| 	if(battleActionsStarted) | ||||
| 		stacksController->updateBattleAnimations(); | ||||
|   | ||||
| @@ -14,14 +14,15 @@ | ||||
| #include "../../lib/mapObjects/CGTownInstance.h" | ||||
| #include "../CGameInfo.h" | ||||
| #include "../gui/CAnimation.h" | ||||
| #include "../gui/CCanvas.h" | ||||
| #include "CBattleInterface.h" | ||||
| #include "CBattleSiegeController.h" | ||||
| #include "CBattleStacksController.h" | ||||
| #include "CCreatureAnimation.h" | ||||
|  | ||||
| CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest) | ||||
| static double calculateCatapultParabolaY(const Point & from, const Point & dest, int x) | ||||
| { | ||||
| 	facA = 0.005; // seems to be constant | ||||
| 	double facA = 0.005; // seems to be constant | ||||
|  | ||||
| 	// system of 2 linear equations, solutions of which are missing coefficients | ||||
| 	// for quadratic equation a*x*x + b*x + c | ||||
| @@ -35,163 +36,205 @@ CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &d | ||||
| 	double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1]; | ||||
| 	double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2]; | ||||
|  | ||||
| 	facB = detB / det; | ||||
| 	facC = detC / det; | ||||
| 	double facB = detB / det; | ||||
| 	double facC = detC / det; | ||||
|  | ||||
| 	// make sure that parabola is correct e.g. passes through from and dest | ||||
| 	assert(fabs(calculateY(from.x) - from.y) < 1.0); | ||||
| 	assert(fabs(calculateY(dest.x) - dest.y) < 1.0); | ||||
| 	return facA *pow(x, 2.0) + facB *x + facC; | ||||
| } | ||||
|  | ||||
| double CatapultProjectileInfo::calculateY(double x) | ||||
| void ProjectileMissile::show(std::shared_ptr<CCanvas> canvas) | ||||
| { | ||||
| 	return facA *pow(x, 2.0) + facB *x + facC; | ||||
| 	size_t group = reverse ? 1 : 0; | ||||
| 	auto image = animation->getImage(frameNum, group, true); | ||||
|  | ||||
| 	if(image) | ||||
| 	{ | ||||
| 		float progress = float(step) / steps; | ||||
|  | ||||
| 		Point pos { | ||||
| 			CSDL_Ext::lerp(from.x, dest.x, progress) - image->width() / 2, | ||||
| 			CSDL_Ext::lerp(from.y, dest.y, progress) - image->height() / 2, | ||||
| 		}; | ||||
|  | ||||
| 		canvas->draw(image, pos); | ||||
| 	} | ||||
|  | ||||
| 	++step; | ||||
| } | ||||
|  | ||||
| void ProjectileCatapult::show(std::shared_ptr<CCanvas> canvas) | ||||
| { | ||||
| 	size_t group = reverse ? 1 : 0; | ||||
| 	auto image = animation->getImage(frameNum, group, true); | ||||
|  | ||||
| 	if(image) | ||||
| 	{ | ||||
| 		float progress = float(step) / steps; | ||||
|  | ||||
| 		int posX = CSDL_Ext::lerp(from.x, dest.x, progress); | ||||
| 		int posY = calculateCatapultParabolaY(from, dest, posX); | ||||
| 		Point pos(posX, posY); | ||||
|  | ||||
| 		canvas->draw(image, pos); | ||||
|  | ||||
| 		frameNum = (frameNum + 1) % animation->size(0); | ||||
|  | ||||
| 		if (step == steps) | ||||
| 		{ | ||||
| 			//TODO: re-enable. Move to ShootingAnimation? What about spells? | ||||
| 			// last step - explosion effect | ||||
| 			//Point explosion_pos = pos + image->dimensions() / 2 - Point(126, 105); | ||||
|  | ||||
| 			//owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y)); | ||||
| 		} | ||||
| 	} | ||||
| 	++step; | ||||
| } | ||||
|  | ||||
| void ProjectileRay::show(std::shared_ptr<CCanvas> canvas) | ||||
| { | ||||
| 	float progress = float(step) / steps; | ||||
|  | ||||
| 	Point curr { | ||||
| 		CSDL_Ext::lerp(from.x, dest.x, progress), | ||||
| 		CSDL_Ext::lerp(from.y, dest.y, progress), | ||||
| 	}; | ||||
|  | ||||
| 	Point length = curr - from; | ||||
|  | ||||
| 	//select axis to draw ray on, we want angle to be less than 45 degrees so individual sub-rays won't overlap each other | ||||
|  | ||||
| 	if (std::abs(length.x) > std::abs(length.y)) // draw in horizontal axis | ||||
| 	{ | ||||
| 		int y1 =  from.y - rayConfig.size() / 2; | ||||
| 		int y2 =  curr.y - rayConfig.size() / 2; | ||||
|  | ||||
| 		int x1 = from.x; | ||||
| 		int x2 = curr.x; | ||||
|  | ||||
| 		for (size_t i = 0; i < rayConfig.size(); ++i) | ||||
| 		{ | ||||
| 			auto ray = rayConfig[i]; | ||||
| 			SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1}; | ||||
| 			SDL_Color endColor  { ray.r2, ray.g2, ray.b2, ray.a2}; | ||||
|  | ||||
| 			canvas->drawLine(Point(x1, y1 + i), Point(x2, y2+i), beginColor, endColor); | ||||
| 		} | ||||
| 	} | ||||
| 	else // draw in vertical axis | ||||
| 	{ | ||||
| 		int x1 = from.x - rayConfig.size() / 2; | ||||
| 		int x2 = curr.x - rayConfig.size() / 2; | ||||
|  | ||||
| 		int y1 = from.y; | ||||
| 		int y2 = curr.y; | ||||
|  | ||||
| 		for (size_t i = 0; i < rayConfig.size(); ++i) | ||||
| 		{ | ||||
| 			auto ray = rayConfig[i]; | ||||
| 			SDL_Color beginColor{ ray.r1, ray.g1, ray.b1, ray.a1}; | ||||
| 			SDL_Color endColor  { ray.r2, ray.g2, ray.b2, ray.a2}; | ||||
|  | ||||
| 			canvas->drawLine(Point(x1 + i, y1), Point(x2 + i, y2), beginColor, endColor); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| CBattleProjectileController::CBattleProjectileController(CBattleInterface * owner): | ||||
| 	owner(owner) | ||||
| { | ||||
| {} | ||||
|  | ||||
| const CCreature * CBattleProjectileController::getShooter(const CStack * stack) | ||||
| { | ||||
| 	const CCreature * creature = stack->getCreature(); | ||||
|  | ||||
| 	if(creature->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 		creature = owner->siegeController->getTurretCreature(); | ||||
|  | ||||
| 	if(creature->animation.missleFrameAngles.empty()) | ||||
| 	{ | ||||
| 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->nameSing); | ||||
| 		creature = CGI->creh->objects[CreatureID::ARCHER]; | ||||
| 	} | ||||
|  | ||||
| 	return creature; | ||||
| } | ||||
|  | ||||
| bool CBattleProjectileController::stackUsesRayProjectile(const CStack * stack) | ||||
| { | ||||
| 	return !getShooter(stack)->animation.projectileRay.empty(); | ||||
| } | ||||
|  | ||||
| bool CBattleProjectileController::stackUsesMissileProjectile(const CStack * stack) | ||||
| { | ||||
| 	return !getShooter(stack)->animation.projectileImageName.empty(); | ||||
| } | ||||
|  | ||||
| void CBattleProjectileController::initStackProjectile(const CStack * stack) | ||||
| { | ||||
| 	const CCreature * creature;//creature whose shots should be loaded | ||||
| 	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 		creature = owner->siegeController->getTurretCreature(); | ||||
| 	if (!stackUsesMissileProjectile(stack)) | ||||
| 		return; | ||||
|  | ||||
| 	const CCreature * creature = getShooter(stack); | ||||
|  | ||||
| 	std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName); | ||||
| 	projectile->preload(); | ||||
|  | ||||
| 	if(projectile->size(1) != 0) | ||||
| 		logAnim->error("Expected empty group 1 in stack projectile"); | ||||
| 	else | ||||
| 		creature = stack->getCreature(); | ||||
| 		projectile->createFlippedGroup(0, 1); | ||||
|  | ||||
| 	if (creature->animation.projectileRay.empty()) | ||||
| 	{ | ||||
| 		std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName); | ||||
| 		projectile->preload(); | ||||
|  | ||||
| 		if(projectile->size(1) != 0) | ||||
| 			logAnim->error("Expected empty group 1 in stack projectile"); | ||||
| 		else | ||||
| 			projectile->createFlippedGroup(0, 1); | ||||
|  | ||||
| 		idToProjectile[stack->getCreature()->idNumber] = projectile; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay; | ||||
| 	} | ||||
| 	projectilesCache[creature->animation.projectileImageName] = projectile; | ||||
| } | ||||
|  | ||||
| void CBattleProjectileController::fireStackProjectile(const CStack * stack) | ||||
| std::shared_ptr<CAnimation> CBattleProjectileController::getProjectileImage(const CStack * stack) | ||||
| { | ||||
| 	for (auto it = projectiles.begin(); it!=projectiles.end(); ++it) | ||||
| 	{ | ||||
| 		if ( !it->shotDone && it->stackID == stack->ID) | ||||
| 		{ | ||||
| 			it->shotDone = true; | ||||
| 			return; | ||||
| 		} | ||||
| 	} | ||||
| 	const CCreature * creature = getShooter(stack); | ||||
| 	std::string imageName = creature->animation.projectileImageName; | ||||
|  | ||||
| 	if (!projectilesCache.count(imageName)) | ||||
| 		initStackProjectile(stack); | ||||
|  | ||||
| 	return projectilesCache[imageName]; | ||||
| } | ||||
|  | ||||
| void CBattleProjectileController::showProjectiles(SDL_Surface *to) | ||||
| { | ||||
| 	assert(to); | ||||
| //void CBattleProjectileController::fireStackProjectile(const CStack * stack) | ||||
| //{ | ||||
| //	for (auto it = projectiles.begin(); it!=projectiles.end(); ++it) | ||||
| //	{ | ||||
| //		if ( !it->shotDone && it->stackID == stack->ID) | ||||
| //		{ | ||||
| //			it->shotDone = true; | ||||
| //			return; | ||||
| //		} | ||||
| //	} | ||||
| //} | ||||
|  | ||||
| 	std::list< std::list<ProjectileInfo>::iterator > toBeDeleted; | ||||
| 	for (auto it = projectiles.begin(); it!=projectiles.end(); ++it) | ||||
|  | ||||
| void CBattleProjectileController::showProjectiles(std::shared_ptr<CCanvas> canvas) | ||||
| { | ||||
| 	for (auto projectile : projectiles) | ||||
| 	{ | ||||
| 		// Check if projectile is already visible (shooter animation did the shot) | ||||
| 		if (!it->shotDone) | ||||
| 			continue; | ||||
| 		//if (!it->shotDone) | ||||
| 		//	continue; | ||||
|  | ||||
| 		if (idToProjectile.count(it->creID)) | ||||
| 		{ | ||||
| 			size_t group = it->reverse ? 1 : 0; | ||||
| 			auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true); | ||||
| 		projectile->show(canvas); | ||||
|  | ||||
| 			if(image) | ||||
| 			{ | ||||
| 				SDL_Rect dst; | ||||
| 				dst.h = image->height(); | ||||
| 				dst.w = image->width(); | ||||
| 				dst.x = static_cast<int>(it->x - dst.w / 2); | ||||
| 				dst.y = static_cast<int>(it->y - dst.h / 2); | ||||
|  | ||||
| 				image->draw(to, &dst, nullptr); | ||||
| 			} | ||||
| 		} | ||||
| 		if (idToRay.count(it->creID)) | ||||
| 		{ | ||||
| 			auto const & ray = idToRay[it->creID]; | ||||
|  | ||||
| 			if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis | ||||
| 			{ | ||||
| 				int y1 =  it->y0 - ray.size() / 2; | ||||
| 				int y2 =  it->y - ray.size() / 2; | ||||
|  | ||||
| 				int x1 = it->x0; | ||||
| 				int x2 = it->x; | ||||
|  | ||||
| 				for (size_t i = 0; i < ray.size(); ++i) | ||||
| 				{ | ||||
| 					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; | ||||
| 					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; | ||||
|  | ||||
| 					CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor); | ||||
| 				} | ||||
| 			} | ||||
| 			else // draw in vertical axis | ||||
| 			{ | ||||
| 				int x1 = it->x0 - ray.size() / 2; | ||||
| 				int x2 = it->x - ray.size() / 2; | ||||
|  | ||||
| 				int y1 =  it->y0; | ||||
| 				int y2 =  it->y; | ||||
|  | ||||
| 				for (size_t i = 0; i < ray.size(); ++i) | ||||
| 				{ | ||||
| 					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; | ||||
| 					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; | ||||
|  | ||||
| 					CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Update projectile | ||||
| 		++it->step; | ||||
| 		if (it->step > it->lastStep) | ||||
| 		{ | ||||
| 			toBeDeleted.insert(toBeDeleted.end(), it); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (it->catapultInfo) | ||||
| 			{ | ||||
| 				// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c | ||||
| 				it->x += it->dx; | ||||
| 				it->y = it->catapultInfo->calculateY(it->x); | ||||
|  | ||||
| 				++(it->frameNum); | ||||
| 				it->frameNum %= idToProjectile[it->creID]->size(0); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// Normal projectile, just add the calculated "deltas" to the x and y positions. | ||||
| 				it->x += it->dx; | ||||
| 				it->y += it->dy; | ||||
| 			} | ||||
| 		} | ||||
| 		// finished flying | ||||
| 		if ( projectile->step > projectile->steps) | ||||
| 			projectile.reset(); | ||||
| 	} | ||||
|  | ||||
| 	for (auto & elem : toBeDeleted) | ||||
| 		projectiles.erase(elem); | ||||
| 	boost::range::remove( projectiles, std::shared_ptr<ProjectileBase>()); | ||||
| } | ||||
|  | ||||
| bool CBattleProjectileController::hasActiveProjectile(const CStack * stack) | ||||
| { | ||||
| 	for(auto const & instance : projectiles) | ||||
| 	{ | ||||
| 		if(instance.creID == stack->getCreature()->idNumber) | ||||
| 		if(instance->shooterID == stack->getCreature()->idNumber) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| @@ -199,117 +242,87 @@ bool CBattleProjectileController::hasActiveProjectile(const CStack * stack) | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
|  | ||||
| void CBattleProjectileController::createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest) | ||||
| { | ||||
| 	// Get further info about the shooter e.g. relative pos of projectile to unit. | ||||
| 	// If the creature id is 149 then it's a arrow tower which has no additional info so get the | ||||
| 	// actual arrow tower shooter instead. | ||||
| 	const CCreature *shooterInfo = shooter->getCreature(); | ||||
|  | ||||
| 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 		shooterInfo = owner->siegeController->getTurretCreature(); | ||||
| 	std::shared_ptr<ProjectileBase> projectile; | ||||
|  | ||||
| 	if(!shooterInfo->animation.missleFrameAngles.size()) | ||||
| 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..." | ||||
| 			, shooterInfo->nameSing); | ||||
|  | ||||
| 	auto & angles = shooterInfo->animation.missleFrameAngles.size() | ||||
| 		? shooterInfo->animation.missleFrameAngles | ||||
| 		: CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles; | ||||
|  | ||||
| 	// recalculate angle taking in account offsets | ||||
| 	//projectileAngle = atan2(fabs(destPos.y - spi.y), fabs(destPos.x - spi.x)); | ||||
| 	//if(shooter->position < dest) | ||||
| 	//	projectileAngle = -projectileAngle; | ||||
|  | ||||
| 	ProjectileInfo spi; | ||||
| 	spi.shotDone = false; | ||||
| 	spi.creID = shooter->getCreature()->idNumber; | ||||
| 	spi.stackID = shooter->ID; | ||||
| 	// reverse if creature is facing right OR this is non-existing stack that is not tower (war machines) | ||||
| 	spi.reverse = shooter ? !owner->stacksController->facingRight(shooter) : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS; | ||||
|  | ||||
| 	spi.step = 0; | ||||
| 	spi.frameNum = 0; | ||||
|  | ||||
| 	spi.x0 = from.x; | ||||
| 	spi.y0 = from.y; | ||||
|  | ||||
| 	spi.x = from.x; | ||||
| 	spi.y = from.y; | ||||
|  | ||||
| 	if (target) | ||||
| 	if (!target) | ||||
| 	{ | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile | ||||
| 		double distanceSquared = (dest.x - spi.x) * (dest.x - spi.x) + (dest.y - spi.y) * (dest.y - spi.y); | ||||
| 		double distance = sqrt(distanceSquared); | ||||
| 		spi.lastStep = std::round(distance / animSpeed); | ||||
| 		if(spi.lastStep == 0) | ||||
| 			spi.lastStep = 1; | ||||
| 		spi.dx = (dest.x - spi.x) / spi.lastStep; | ||||
| 		spi.dy = (dest.y - spi.y) / spi.lastStep; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Catapult attack | ||||
| 		spi.catapultInfo.reset(new CatapultProjectileInfo(Point((int)spi.x, (int)spi.y), dest)); | ||||
| 		auto catapultProjectile= new ProjectileCatapult(); | ||||
| 		projectile.reset(catapultProjectile); | ||||
|  | ||||
| 		catapultProjectile->animation = getProjectileImage(shooter); | ||||
| 		catapultProjectile->wallDamageAmount = 0; //FIXME - receive from caller | ||||
| 		catapultProjectile->frameNum = 0; | ||||
| 		catapultProjectile->reverse = false; | ||||
| 		catapultProjectile->step = 0; | ||||
| 		catapultProjectile->steps = 0; | ||||
|  | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed() / 10; | ||||
| 		spi.lastStep = static_cast<int>(std::abs((dest.x - spi.x) / animSpeed)); | ||||
| 		spi.dx = animSpeed; | ||||
| 		spi.dy = 0; | ||||
|  | ||||
| 		auto img = idToProjectile[spi.creID]->getImage(0); | ||||
|  | ||||
| 		// Add explosion anim | ||||
| 		Point animPos(dest.x - 126 + img->width() / 2, | ||||
| 					  dest.y - 105 + img->height() / 2); | ||||
|  | ||||
| 		//owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y)); | ||||
| 	} | ||||
| 	double pi = std::atan(1)*4; | ||||
|  | ||||
| 	//in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized | ||||
| 	if (!idToProjectile.count(spi.creID) && !idToRay.count(spi.creID)) | ||||
| 		initStackProjectile(shooter); | ||||
|  | ||||
| 	if (idToProjectile.count(spi.creID)) | ||||
| 	{ | ||||
| 		// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used | ||||
| 		size_t maxFrame = std::min<size_t>(angles.size(), idToProjectile.at(spi.creID)->size(0)); | ||||
|  | ||||
| 		assert(maxFrame > 0); | ||||
| 		double projectileAngle = atan2(fabs((double)dest.y - from.y), fabs((double)dest.x - from.x)); | ||||
| 		//if(shooter->getPosition() < dest) | ||||
| 		//	projectileAngle = -projectileAngle; | ||||
|  | ||||
| 		// values in angles array indicate position from which this frame was rendered, in degrees. | ||||
| 		// find frame that has closest angle to one that we need for this shot | ||||
| 		size_t bestID = 0; | ||||
| 		double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle ); | ||||
|  | ||||
| 		for (size_t i=1; i<maxFrame; i++) | ||||
| 		{ | ||||
| 			double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle ); | ||||
| 			if (currentDiff < bestDiff) | ||||
| 			{ | ||||
| 				bestID = i; | ||||
| 				bestDiff = currentDiff; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		spi.frameNum = static_cast<int>(bestID); | ||||
| 	} | ||||
| 	else if (idToRay.count(spi.creID)) | ||||
| 	{ | ||||
| 		// no-op | ||||
| 		catapultProjectile->steps = std::round(std::abs((dest.x - from.x) / animSpeed)); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID); | ||||
| 		if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter)) | ||||
| 		{ | ||||
| 			logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo->nameSing); | ||||
| 		} | ||||
|  | ||||
| 		if (stackUsesRayProjectile(shooter)) | ||||
| 		{ | ||||
| 			auto rayProjectile = new ProjectileRay(); | ||||
| 			projectile.reset(rayProjectile); | ||||
|  | ||||
| 			rayProjectile->rayConfig = shooterInfo->animation.projectileRay; | ||||
| 		} | ||||
| 		else if (stackUsesMissileProjectile(shooter)) | ||||
| 		{ | ||||
| 			auto missileProjectile = new ProjectileMissile(); | ||||
| 			projectile.reset(missileProjectile); | ||||
|  | ||||
| 			auto & angles = shooterInfo->animation.missleFrameAngles; | ||||
|  | ||||
| 			missileProjectile->animation = getProjectileImage(shooter); | ||||
| 			missileProjectile->reverse  = owner->stacksController->facingRight(shooter); | ||||
|  | ||||
|  | ||||
| 			// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used | ||||
| 			size_t maxFrame = std::min<size_t>(angles.size(), missileProjectile->animation->size(0)); | ||||
|  | ||||
| 			assert(maxFrame > 0); | ||||
| 			double projectileAngle = atan2(dest.y - from.y, std::abs(dest.x - from.x)); | ||||
|  | ||||
| 			// values in angles array indicate position from which this frame was rendered, in degrees. | ||||
| 			// find frame that has closest angle to one that we need for this shot | ||||
| 			int bestID = 0; | ||||
| 			double bestDiff = fabs( angles[0] / 180 * M_PI - projectileAngle ); | ||||
|  | ||||
| 			for (int i=1; i<maxFrame; i++) | ||||
| 			{ | ||||
| 				double currentDiff = fabs( angles[i] / 180 * M_PI - projectileAngle ); | ||||
| 				if (currentDiff < bestDiff) | ||||
| 				{ | ||||
| 					bestID = i; | ||||
| 					bestDiff = currentDiff; | ||||
| 				} | ||||
| 			} | ||||
| 			missileProjectile->frameNum = bestID; | ||||
| 		} | ||||
|  | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile | ||||
| 		double distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y); | ||||
| 		double distance = sqrt(distanceSquared); | ||||
| 		projectile->steps = std::round(distance / animSpeed); | ||||
| 		if(projectile->steps == 0) | ||||
| 			projectile->steps = 1; | ||||
| 	} | ||||
|  | ||||
| 	// Set projectile animation start delay which is specified in frames | ||||
| 	projectiles.push_back(spi); | ||||
| 	projectile->from     = from; | ||||
| 	projectile->dest     = dest; | ||||
| 	projectile->shooterID = shooter->ID; | ||||
| 	projectile->step     = 0; | ||||
|  | ||||
| 	projectiles.push_back(projectile); | ||||
| } | ||||
|   | ||||
| @@ -10,56 +10,80 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "../../lib/CCreatureHandler.h" | ||||
| #include "../gui/Geometries.h" | ||||
|  | ||||
| struct Point; | ||||
| struct SDL_Surface; | ||||
| class CAnimation; | ||||
| class CCanvas; | ||||
| class CStack; | ||||
| class CBattleInterface; | ||||
|  | ||||
| /// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon | ||||
| struct CatapultProjectileInfo | ||||
| /// Small struct which contains information about the position and the velocity of a projectile | ||||
| struct ProjectileBase | ||||
| { | ||||
| 	CatapultProjectileInfo(const Point &from, const Point &dest); | ||||
| 	virtual ~ProjectileBase() = default; | ||||
| 	virtual void show(std::shared_ptr<CCanvas> canvas) =  0; | ||||
|  | ||||
| 	double facA, facB, facC; | ||||
| 	Point from; // initial position on the screen | ||||
| 	Point dest; // target position on the screen | ||||
|  | ||||
| 	double calculateY(double x); | ||||
| 	int step;      // current step counter | ||||
| 	int steps;     // total number of steps/frames to show | ||||
| 	int shooterID; // ID of shooter stack | ||||
| }; | ||||
|  | ||||
| /// Small struct which contains information about the position and the velocity of a projectile | ||||
| struct ProjectileInfo | ||||
| struct ProjectileMissile : ProjectileBase | ||||
| { | ||||
| 	double x0, y0; //initial position on the screen | ||||
| 	double x, y; //position on the screen | ||||
| 	double dx, dy; //change in position in one step | ||||
| 	int step, lastStep; //to know when finish showing this projectile | ||||
| 	int creID; //ID of creature that shot this projectile | ||||
| 	int stackID; //ID of stack | ||||
| 	int frameNum; //frame to display form projectile animation | ||||
| 	//bool spin; //if true, frameNum will be increased | ||||
| 	bool shotDone; // actual shot already done, projectile is flying | ||||
| 	bool reverse; //if true, projectile will be flipped by vertical asix | ||||
| 	std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon | ||||
| 	void show(std::shared_ptr<CCanvas> canvas) override; | ||||
|  | ||||
| 	std::shared_ptr<CAnimation> animation; | ||||
| 	int frameNum;  // frame to display from projectile animation | ||||
| 	bool reverse;  // if true, projectile will be flipped by vertical axis | ||||
| }; | ||||
|  | ||||
| struct ProjectileCatapult : ProjectileMissile | ||||
| { | ||||
| 	void show(std::shared_ptr<CCanvas> canvas) override; | ||||
|  | ||||
| 	int wallDamageAmount; | ||||
| }; | ||||
|  | ||||
| struct ProjectileRay : ProjectileBase | ||||
| { | ||||
| 	void show(std::shared_ptr<CCanvas> canvas) override; | ||||
|  | ||||
| 	std::vector<CCreature::CreatureAnimation::RayColor> rayConfig; | ||||
| }; | ||||
|  | ||||
| class CBattleProjectileController | ||||
| { | ||||
| 	CBattleInterface * owner; | ||||
|  | ||||
| 	std::map<int, std::shared_ptr<CAnimation>> idToProjectile; | ||||
| 	std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay; | ||||
| 	/// all projectiles loaded during current battle | ||||
| 	std::map<std::string, std::shared_ptr<CAnimation>> projectilesCache; | ||||
|  | ||||
| 	std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield | ||||
| //	std::map<int, std::shared_ptr<CAnimation>> idToProjectile; | ||||
| //	std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay; | ||||
|  | ||||
| 	/// projectiles currently flying on battlefield | ||||
| 	std::vector<std::shared_ptr<ProjectileBase>> projectiles; | ||||
|  | ||||
| 	std::shared_ptr<CAnimation> getProjectileImage(const CStack * stack); | ||||
| 	void initStackProjectile(const CStack * stack); | ||||
|  | ||||
| 	bool stackUsesRayProjectile(const CStack * stack); | ||||
| 	bool stackUsesMissileProjectile(const CStack * stack); | ||||
|  | ||||
| 	void showProjectile(std::shared_ptr<CCanvas> canvas, std::shared_ptr<ProjectileBase> projectile); | ||||
|  | ||||
| 	const CCreature * getShooter(const CStack * stack); | ||||
| public: | ||||
| 	CBattleProjectileController(CBattleInterface * owner); | ||||
|  | ||||
| 	void showProjectiles(SDL_Surface *to); | ||||
| 	void initStackProjectile(const CStack * stack); | ||||
| 	void fireStackProjectile(const CStack * stack); | ||||
| 	void showProjectiles(std::shared_ptr<CCanvas> canvas); | ||||
| 	//void fireStackProjectile(const CStack * stack); | ||||
|  | ||||
| 	bool hasActiveProjectile(const CStack * stack); | ||||
|  | ||||
| 	void createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest); | ||||
| }; | ||||
|   | ||||
| @@ -199,12 +199,6 @@ void CBattleStacksController::stackAdded(const CStack * stack) | ||||
| 	creAnims[stack->ID]->pos.y = coords.y; | ||||
| 	creAnims[stack->ID]->pos.w = creAnims[stack->ID]->getWidth(); | ||||
| 	creAnims[stack->ID]->setType(CCreatureAnim::HOLDING); | ||||
|  | ||||
| 	//loading projectiles for units | ||||
| 	if(stack->isShooter()) | ||||
| 	{ | ||||
| 		owner->projectilesController->initStackProjectile(stack); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CBattleStacksController::setActiveStack(const CStack *stack) | ||||
|   | ||||
| @@ -40,6 +40,11 @@ void CCanvas::draw(std::shared_ptr<CCanvas> image, const Point & pos) | ||||
| 	image->copyTo(surface, pos); | ||||
| } | ||||
|  | ||||
| void CCanvas::drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest) | ||||
| { | ||||
| 	CSDL_Ext::drawLine(surface, from.x, from.y, dest.x, dest.y, colorFrom, colorDest); | ||||
| } | ||||
|  | ||||
| void CCanvas::copyTo(SDL_Surface * to, const Point & pos) | ||||
| { | ||||
| 	blitAt(to, pos.x, pos.y, surface); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| struct SDL_Color; | ||||
| struct SDL_Surface; | ||||
| struct Point; | ||||
| class IImage; | ||||
| @@ -30,6 +31,9 @@ public: | ||||
| 	// renders another canvas onto this canvas | ||||
| 	void draw(std::shared_ptr<CCanvas> image, const Point & pos); | ||||
|  | ||||
| 	// renders continuous, 1-pixel wide line with color gradient | ||||
| 	void drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest); | ||||
|  | ||||
| 	// for compatibility, copies content of this canvas onto SDL_Surface | ||||
| 	void copyTo(SDL_Surface * to, const Point & pos); | ||||
| }; | ||||
|   | ||||
| @@ -362,23 +362,17 @@ void CSDL_Ext::update(SDL_Surface * what) | ||||
| 		logGlobal->error("%s SDL_UpdateTexture %s", __FUNCTION__, SDL_GetError()); | ||||
| } | ||||
|  | ||||
| template<typename Int> | ||||
| Int lerp(Int a, Int b, float f) | ||||
| { | ||||
| 	return a + std::round((b - a) * f); | ||||
| } | ||||
|  | ||||
| static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2) | ||||
| { | ||||
| 	for(int x = x1; x <= x2; x++) | ||||
| 	{ | ||||
| 		float f = float(x - x1) / float(x2 - x1); | ||||
| 		int y = lerp(y1, y2, f); | ||||
| 		int y = CSDL_Ext::lerp(y1, y2, f); | ||||
|  | ||||
| 		uint8_t r = lerp(color1.r, color2.r, f); | ||||
| 		uint8_t g = lerp(color1.g, color2.g, f); | ||||
| 		uint8_t b = lerp(color1.b, color2.b, f); | ||||
| 		uint8_t a = lerp(color1.a, color2.a, f); | ||||
| 		uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f); | ||||
| 		uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f); | ||||
| 		uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f); | ||||
| 		uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f); | ||||
|  | ||||
| 		Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y); | ||||
| 		ColorPutter<4, 0>::PutColor(p, r,g,b,a); | ||||
| @@ -390,12 +384,12 @@ static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S | ||||
| 	for(int y = y1; y <= y2; y++) | ||||
| 	{ | ||||
| 		float f = float(y - y1) / float(y2 - y1); | ||||
| 		int x = lerp(x1, x2, f); | ||||
| 		int x = CSDL_Ext::lerp(x1, x2, f); | ||||
|  | ||||
| 		uint8_t r = lerp(color1.r, color2.r, f); | ||||
| 		uint8_t g = lerp(color1.g, color2.g, f); | ||||
| 		uint8_t b = lerp(color1.b, color2.b, f); | ||||
| 		uint8_t a = lerp(color1.a, color2.a, f); | ||||
| 		uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f); | ||||
| 		uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f); | ||||
| 		uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f); | ||||
| 		uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f); | ||||
|  | ||||
| 		Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y); | ||||
| 		ColorPutter<4, 0>::PutColor(p, r,g,b,a); | ||||
|   | ||||
| @@ -49,6 +49,12 @@ inline bool isShiftKeyDown() | ||||
| } | ||||
| namespace CSDL_Ext | ||||
| { | ||||
| 	template<typename Int> | ||||
| 	Int lerp(Int a, Int b, float f) | ||||
| 	{ | ||||
| 		return a + std::round((b - a) * f); | ||||
| 	} | ||||
|  | ||||
| 	//todo: should this better be assignment operator? | ||||
| 	STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source) | ||||
| 	{ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user