mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-11-04 06:32:00 +00:00 
			
		
		
		
	test fix 2
This commit is contained in:
		@@ -209,6 +209,10 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
 | 
			
		||||
			m_IsRotating = true;
 | 
			
		||||
			m_InActionTranslation.y = isSpinNegative ? -number : number;
 | 
			
		||||
			m_PreviousFrameRotation = entity.GetRotation();
 | 
			
		||||
			// compute the absolute rotation target quaternion
 | 
			
		||||
			NiPoint3 deltaEuler = NiPoint3(0.0f, Math::DegToRad(m_InActionTranslation.y), 0.0f);
 | 
			
		||||
			m_RotationTarget = m_PreviousFrameRotation;
 | 
			
		||||
			m_RotationTarget *= NiQuaternion::FromEulerAngles(deltaEuler);
 | 
			
		||||
			// d/vi = t
 | 
			
		||||
			// radians/velocity = time
 | 
			
		||||
			// only care about the time, direction is irrelevant here
 | 
			
		||||
@@ -223,6 +227,9 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
 | 
			
		||||
			m_IsRotating = true;
 | 
			
		||||
			m_InActionTranslation.x = isRotateLeft ? -number : number;
 | 
			
		||||
			m_PreviousFrameRotation = entity.GetRotation();
 | 
			
		||||
			NiPoint3 deltaEuler = NiPoint3(Math::DegToRad(m_InActionTranslation.x), 0.0f, 0.0f);
 | 
			
		||||
			m_RotationTarget = m_PreviousFrameRotation;
 | 
			
		||||
			m_RotationTarget *= NiQuaternion::FromEulerAngles(deltaEuler);
 | 
			
		||||
		}
 | 
			
		||||
	} else if (nextActionType == "Roll" || nextActionType == "RollNegative") {
 | 
			
		||||
		const float radians = Math::DegToRad(number);
 | 
			
		||||
@@ -234,6 +241,9 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
 | 
			
		||||
			m_IsRotating = true;
 | 
			
		||||
			m_InActionTranslation.z = isRotateDown ? -number : number;
 | 
			
		||||
			m_PreviousFrameRotation = entity.GetRotation();
 | 
			
		||||
			NiPoint3 deltaEuler = NiPoint3(0.0f, 0.0f, Math::DegToRad(m_InActionTranslation.z));
 | 
			
		||||
			m_RotationTarget = m_PreviousFrameRotation;
 | 
			
		||||
			m_RotationTarget *= NiQuaternion::FromEulerAngles(deltaEuler);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	/* END Rotate */
 | 
			
		||||
@@ -379,83 +389,37 @@ bool Strip::CheckRotation(float deltaTime, ModelComponent& modelComponent) {
 | 
			
		||||
	LOG("Velocity: x=%f, y=%f, z=%f", Math::RadToDeg(getAngVel.angVelocity.x) * deltaTime, Math::RadToDeg(getAngVel.angVelocity.y) * deltaTime, Math::RadToDeg(getAngVel.angVelocity.z) * deltaTime);
 | 
			
		||||
	m_PreviousFrameRotation = curRotation;
 | 
			
		||||
 | 
			
		||||
	// Convert frame delta (radians) to absolute degrees moved this frame per axis.
 | 
			
		||||
	// Use the reported angular velocity (radians/sec) * deltaTime instead of extracting
 | 
			
		||||
	// Euler angles from the quaternion difference. Extracting Euler angles from a
 | 
			
		||||
	// combined-axis quaternion won't produce per-axis rotations when axes rotate
 | 
			
		||||
	// simultaneously, which caused late stopping. Using angular velocity is consistent
 | 
			
		||||
	// with how velocity is applied in SimplePhysicsComponent.
 | 
			
		||||
	NiPoint3 angMovedDegrees = NiPoint3(std::abs(Math::RadToDeg(getAngVel.angVelocity.x) * deltaTime),
 | 
			
		||||
									   std::abs(Math::RadToDeg(getAngVel.angVelocity.y) * deltaTime),
 | 
			
		||||
									   std::abs(Math::RadToDeg(getAngVel.angVelocity.z) * deltaTime));
 | 
			
		||||
 | 
			
		||||
	const auto [rotateX, rotateY, rotateZ] = m_InActionTranslation;
 | 
			
		||||
	bool rotateFinished = true; // assume finished until an axis proves otherwise
 | 
			
		||||
	NiPoint3 finalRotationAdjustment = NiPoint3Constant::ZERO;
 | 
			
		||||
 | 
			
		||||
	// Use a small epsilon to avoid missing the exact-zero case due to floating point
 | 
			
		||||
	constexpr float EPS_DEG = 1e-3f;
 | 
			
		||||
 | 
			
		||||
	// Handle each axis independently so we can rotate on multiple axes at once.
 | 
			
		||||
	if (rotateX != 0.0f) {
 | 
			
		||||
		m_InActionTranslation.x -= angMovedDegrees.x;
 | 
			
		||||
		// Finished if we crossed zero or are within epsilon
 | 
			
		||||
		if (std::signbit(m_InActionTranslation.x) != std::signbit(rotateX) || std::abs(m_InActionTranslation.x) <= EPS_DEG) {
 | 
			
		||||
			finalRotationAdjustment.x = Math::DegToRad(m_InActionTranslation.x);
 | 
			
		||||
			m_InActionTranslation.x = 0.0f;
 | 
			
		||||
		} else {
 | 
			
		||||
			rotateFinished = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (rotateY != 0.0f) {
 | 
			
		||||
		m_InActionTranslation.y -= angMovedDegrees.y;
 | 
			
		||||
		if (std::signbit(m_InActionTranslation.y) != std::signbit(rotateY) || std::abs(m_InActionTranslation.y) <= EPS_DEG) {
 | 
			
		||||
			finalRotationAdjustment.y = Math::DegToRad(m_InActionTranslation.y);
 | 
			
		||||
			m_InActionTranslation.y = 0.0f;
 | 
			
		||||
		} else {
 | 
			
		||||
			rotateFinished = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (rotateZ != 0.0f) {
 | 
			
		||||
		m_InActionTranslation.z -= angMovedDegrees.z;
 | 
			
		||||
		if (std::signbit(m_InActionTranslation.z) != std::signbit(rotateZ) || std::abs(m_InActionTranslation.z) <= EPS_DEG) {
 | 
			
		||||
			finalRotationAdjustment.z = Math::DegToRad(m_InActionTranslation.z);
 | 
			
		||||
			m_InActionTranslation.z = 0.0f;
 | 
			
		||||
		} else {
 | 
			
		||||
			rotateFinished = false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (rotateFinished && (finalRotationAdjustment != NiPoint3Constant::ZERO)) {
 | 
			
		||||
		LOG("Rotation finished, zeroing angVel for finished axes");
 | 
			
		||||
 | 
			
		||||
		// Zero only the angular velocity channels that have just finished.
 | 
			
		||||
		if (rotateX != 0.0f) getAngVel.angVelocity.x = 0.0f;
 | 
			
		||||
		if (rotateY != 0.0f) getAngVel.angVelocity.y = 0.0f;
 | 
			
		||||
		if (rotateZ != 0.0f) getAngVel.angVelocity.z = 0.0f;
 | 
			
		||||
	// Use quaternion remaining angle to decide completion. Compute the quaternion
 | 
			
		||||
	// that rotates from the current rotation to the target rotation. If the
 | 
			
		||||
	// rotation angle of that quaternion is below an epsilon, we're finished.
 | 
			
		||||
	NiQuaternion remaining = modelComponent.GetParent()->GetRotation().Diff(m_RotationTarget);
 | 
			
		||||
	float w = remaining.w;
 | 
			
		||||
	if (w > 1.0f) w = 1.0f; // clamp
 | 
			
		||||
	if (w < -1.0f) w = -1.0f;
 | 
			
		||||
	// angle (radians) = 2 * acos(w)
 | 
			
		||||
	float angleRemainingRad = 2.0f * acos(w);
 | 
			
		||||
	float angleRemainingDeg = Math::RadToDeg(angleRemainingRad);
 | 
			
		||||
	constexpr float EPS_DEG = 0.1f; // finish when less than 0.1 degree remains
 | 
			
		||||
 | 
			
		||||
	if (angleRemainingDeg <= EPS_DEG) {
 | 
			
		||||
		LOG("Rotation finished by quaternion remaining angle (%f deg)", angleRemainingDeg);
 | 
			
		||||
		// Zero angular velocity on axes that were part of this action (safe to zero all)
 | 
			
		||||
		getAngVel.angVelocity = NiPoint3Constant::ZERO;
 | 
			
		||||
		GameMessages::SetAngularVelocity setAngVel{};
 | 
			
		||||
		setAngVel.target = modelComponent.GetParent()->GetObjectID();
 | 
			
		||||
		setAngVel.angVelocity = getAngVel.angVelocity;
 | 
			
		||||
		setAngVel.Send();
 | 
			
		||||
 | 
			
		||||
		// Do the final adjustment so we will have rotated exactly the requested units
 | 
			
		||||
		auto currentRot = modelComponent.GetParent()->GetRotation();
 | 
			
		||||
		NiQuaternion finalAdjustment = NiQuaternion::FromEulerAngles(finalRotationAdjustment);
 | 
			
		||||
		currentRot *= finalAdjustment;
 | 
			
		||||
		currentRot.Normalize();
 | 
			
		||||
		modelComponent.GetParent()->SetRotation(currentRot);
 | 
			
		||||
 | 
			
		||||
		// If all axes are zeroed out then stop rotating
 | 
			
		||||
		if (m_InActionTranslation == NiPoint3Constant::ZERO) {
 | 
			
		||||
			m_IsRotating = false;
 | 
			
		||||
		}
 | 
			
		||||
		// Snap to exact target to avoid tiny residual error
 | 
			
		||||
		modelComponent.GetParent()->SetRotation(m_RotationTarget);
 | 
			
		||||
		m_InActionTranslation = NiPoint3Constant::ZERO;
 | 
			
		||||
		m_IsRotating = false;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	LOG("angVel: x=%f, y=%f, z=%f", m_InActionTranslation.x, m_InActionTranslation.y, m_InActionTranslation.z);
 | 
			
		||||
	return rotateFinished;
 | 
			
		||||
	// Not finished yet
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,9 @@ private:
 | 
			
		||||
 | 
			
		||||
	NiQuaternion m_PreviousFrameRotation{};
 | 
			
		||||
 | 
			
		||||
	// The absolute target rotation for the current rotation action
 | 
			
		||||
	NiQuaternion m_RotationTarget{};
 | 
			
		||||
 | 
			
		||||
	NiPoint3 m_SavedVelocity{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,47 +7,24 @@
 | 
			
		||||
// Test that rotating a quaternion by 90 degrees on each axis in one frame
 | 
			
		||||
// yields approximately 90 degrees when converted back to Euler angles.
 | 
			
		||||
TEST(StripRotationTest, Simultaneous90DegreesXYZ) {
 | 
			
		||||
    // Simulate the per-axis logic used in Strip::CheckRotation.
 | 
			
		||||
    // Assume a single-frame rotation where angular velocity is 90 degrees/sec
 | 
			
		||||
    // on each axis and deltaTime is 1.0 seconds. The remaining rotation
 | 
			
		||||
    // prior to the frame is 90 degrees on each axis.
 | 
			
		||||
    NiPoint3 remainingRotationDeg(90.0f, 90.0f, 90.0f);
 | 
			
		||||
    NiPoint3 angularVelocityDegPerSec(90.0f, 90.0f, 90.0f);
 | 
			
		||||
    const float deltaTime = 1.0f;
 | 
			
		||||
    // Use quaternion math to verify a single-frame rotation of 90deg on each axis
 | 
			
		||||
    // reaches the composed target. Start rotation is identity.
 | 
			
		||||
    NiQuaternion start = NiQuaternionConstant::IDENTITY;
 | 
			
		||||
    NiPoint3 targetEulerRad(Math::DegToRad(90.0f), Math::DegToRad(90.0f), Math::DegToRad(90.0f));
 | 
			
		||||
    NiQuaternion target = NiQuaternion::FromEulerAngles(targetEulerRad);
 | 
			
		||||
 | 
			
		||||
    // Compute degrees moved this frame per axis
 | 
			
		||||
    NiPoint3 angMovedDegrees(std::abs(angularVelocityDegPerSec.x) * deltaTime,
 | 
			
		||||
                             std::abs(angularVelocityDegPerSec.y) * deltaTime,
 | 
			
		||||
                             std::abs(angularVelocityDegPerSec.z) * deltaTime);
 | 
			
		||||
    // Simulate applying angular velocity of 90deg/sec on each axis for 1 second
 | 
			
		||||
    NiPoint3 appliedEulerRad = targetEulerRad; // angularVel * deltaTime
 | 
			
		||||
    NiQuaternion afterFrame = start;
 | 
			
		||||
    afterFrame *= NiQuaternion::FromEulerAngles(appliedEulerRad);
 | 
			
		||||
 | 
			
		||||
    // Subtract movement from remaining rotation per axis (mirrors Strip logic)
 | 
			
		||||
    bool rotateFinished = true;
 | 
			
		||||
    constexpr float EPS_DEG = 1e-3f;
 | 
			
		||||
    // Remaining quaternion from current to target should be identity (or near it)
 | 
			
		||||
    NiQuaternion remaining = afterFrame.Diff(target);
 | 
			
		||||
    float w = remaining.w;
 | 
			
		||||
    if (w > 1.0f) w = 1.0f;
 | 
			
		||||
    if (w < -1.0f) w = -1.0f;
 | 
			
		||||
    float angleRemainingDeg = Math::RadToDeg(2.0f * acos(w));
 | 
			
		||||
 | 
			
		||||
    // X
 | 
			
		||||
    remainingRotationDeg.x -= angMovedDegrees.x;
 | 
			
		||||
    if (std::signbit(remainingRotationDeg.x) != std::signbit(90.0f) || std::abs(remainingRotationDeg.x) <= EPS_DEG) {
 | 
			
		||||
        remainingRotationDeg.x = 0.0f;
 | 
			
		||||
    } else {
 | 
			
		||||
        rotateFinished = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Y
 | 
			
		||||
    remainingRotationDeg.y -= angMovedDegrees.y;
 | 
			
		||||
    if (std::signbit(remainingRotationDeg.y) != std::signbit(90.0f) || std::abs(remainingRotationDeg.y) <= EPS_DEG) {
 | 
			
		||||
        remainingRotationDeg.y = 0.0f;
 | 
			
		||||
    } else {
 | 
			
		||||
        rotateFinished = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Z
 | 
			
		||||
    remainingRotationDeg.z -= angMovedDegrees.z;
 | 
			
		||||
    if (std::signbit(remainingRotationDeg.z) != std::signbit(90.0f) || std::abs(remainingRotationDeg.z) <= EPS_DEG) {
 | 
			
		||||
        remainingRotationDeg.z = 0.0f;
 | 
			
		||||
    } else {
 | 
			
		||||
        rotateFinished = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ASSERT_TRUE(rotateFinished);
 | 
			
		||||
    ASSERT_EQ(remainingRotationDeg, NiPoint3(0.0f, 0.0f, 0.0f));
 | 
			
		||||
    // Allow a small residual due to floating point and composition order
 | 
			
		||||
    ASSERT_LE(angleRemainingDeg, 0.2f);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user