Feel like I should know this, maybe I'm just over thinking it. What I'm attempting to do is create a bullet hole plane/texture object on top of an existing mesh when a ray cast returns a hit point. I have everything working except rotating the plane to face the same direction as the contact normal.

The way my engine works, every "Actor" (my game object essentially) has a transform. This transform tracks the actor's translation, rotation, and scale, then generates a model matrix that is used to generate an mvp matrix down the road.

The way I am attempting to solve this at the moment is to generate a quaternion that will rotate the bullet plane actor in the same direction as the contact normal. However, I'm unsure how to go about this at the moment and was hoping someone might be able to point me in the right direction.



class Transform
        glm::vec3           translation;
        glm::quat           rotation;
        glm::vec3           scale;

                            Transform(glm::vec3 t, glm::quat r, glm::vec3 s);
        void                setTranslation(glm::vec3 translation);
        void                setRotation(float angle, glm::vec3 axis);
        void                setRotation(glm::quat rotation);
        void                setScale(glm::vec3 scale);
        const glm::vec3&    getTranslation() const;
        const glm::quat&    getRotation() const;
        const glm::vec3&    getScale() const;
        const glm::mat4     getMatrix() const;
        void                print();

        glm::vec3           getRotationEulers();

        static glm::vec3    interpolateTranslations(const Transform& previousTransform, const Transform& currentTransform, float alpha);
        static glm::quat    interpolateRotations(const Transform& previousTransform, const Transform& currentTransform, float alpha);
        static glm::vec3    interpolateScales(const Transform& previousTransform, const Transform& currentTransform, float alpha);
        static glm::mat4    interpolateTransforms(const Transform& previousTransform, const Transform& currentTransform, float alpha);



    Transform::Transform() :
        translation(glm::vec3(0.0f, 0.0f, 0.0f)),
        scale(glm::vec3(1.0f, 1.0f, 1.0f)),

    Transform::Transform(glm::vec3 t, glm::quat r, glm::vec3 s) :

    void Transform::print()
        std::cout << "T:(" << this->translation.x << "," << this->translation.y << "," << this->translation.z << ")\n";
        std::cout << "R:(" << this->rotation.x << "," << this->rotation.y << "," << this->rotation.z << "," << this->rotation.w << ")\n";
        std::cout << "S:(" << this->scale.x << "," << this->scale.y << "," << this->scale.z << ")\n";
        std::cout << "---------------------------------\n";

    glm::vec3 Transform::getRotationEulers()
        auto tmp = glm::eulerAngles(this->rotation);
        return glm::vec3((tmp.x * 180.0f / 3.141592653589793f), (tmp.y * 180.0f / 3.141592653589793f), (tmp.z * 180.0f / 3.141592653589793f));

    void Transform::setTranslation(glm::vec3 translation) 
        this->translation = translation;

    void Transform::setRotation(glm::quat rotation)
        this->rotation = rotation;

    void Transform::setRotation(float angle, glm::vec3 axis)
        this->rotation = glm::angleAxis(glm::radians(angle), axis);

    void Transform::setScale(glm::vec3 scale)
        this->scale = scale;

    const glm::vec3& Transform::getTranslation() const
        return this->translation;

    const glm::quat& Transform::getRotation() const
        return this->rotation;

    const glm::vec3& Transform::getScale() const
        return this->scale;

    const glm::mat4 Transform::getMatrix() const
        glm::mat4 m = glm::mat4(1.0f);
        m = glm::translate(m, this->translation);
        m = m * glm::toMat4(this->rotation);
        m = glm::scale(m, this->scale);
        return m;

    glm::vec3 Transform::interpolateTranslations(const Transform& previousTransform, const Transform& currentTransform, float alpha)
        return glm::lerp(previousTransform.getTranslation(), currentTransform.getTranslation(), alpha);

    glm::quat Transform::interpolateRotations(const Transform& previousTransform, const Transform& currentTransform, float alpha)
        return glm::slerp(previousTransform.getRotation(), currentTransform.getRotation(), alpha);

    glm::vec3 Transform::interpolateScales(const Transform& previousTransform, const Transform& currentTransform, float alpha)
        return glm::lerp(previousTransform.getScale(), currentTransform.getScale(), alpha);

    glm::mat4 Transform::interpolateTransforms(const Transform& previousTransform, const Transform& currentTransform, float alpha)
        Transform t;
        t.setTranslation(glm::lerp(previousTransform.getTranslation(), currentTransform.getTranslation(), alpha));
        t.setRotation(glm::slerp(previousTransform.getRotation(), currentTransform.getRotation(), alpha));
        t.setScale(glm::lerp(previousTransform.getScale(), currentTransform.getScale(), alpha));
        return t.getMatrix();

2 Answers 2


You can do this without the back-and-forth to matrix steps by computing your new quaternion directly.

Here we use the fact that the axis to rotate around must be perpendicular to the start and ending directions (like their cross product), and that the cross product of two unit vectors has length equal to the sine of the angle between them. Then we can construct a quaternion angle-axis style.

// Assumes startDirection and endDirection are unit vectors (length 1)
Quaternion FromToRotation(Vector3 startDirection, Vector3 endDirection) {

    Vector3 crossProduct = Cross(startDirection, endDirection);

    float sineOfAngle = Length(crossProduct);

    float angle = Asin(sineOfAngle);    
    Vector3 axis = crossProduct / sineOfAngle;

    Vector3 imaginary = Sin(angle/2.0f) * axis;

    Quaternion result;
    result.w = Cos(angle/2.0f);
    result.x = imaginary.x;
    result.y = imaginary.y;
    result.z = imaginary.z;

    return result;

You'll want to add a case to handle when the vectors are already parallel - in this situation, the output is either the identity quaternion (w, x, y, z) = (1, 0, 0, 0), or a 180 degree flip around any perpendicular vector if they're pointing opposite one another.

  • \$\begingroup\$ omg, you're a freakin wizard. I have no idea how this works, but it worked for me to fix a problem I'd been having for almost a year! \$\endgroup\$ Commented Jan 26 at 8:20

Was able to resolve this using glm's lookat method. The process was to take the hitpoint from my raycast, then create a second point by adding the product of the normal and a distance vector to the original hit point. After this I used glm::lookat to generate a matrix, transposed that matrix, and then converted to a quaternion that I then set as the rotation for my bullet object. My code is below:

auto hitPoint = vel::helpers::functions::bulletToGlmVec3(raycast.m_hitPointWorld);
auto normal = vel::helpers::functions::bulletToGlmVec3(raycast.m_hitNormalWorld);

auto bulletHolePosition = hitPoint + (normal * glm::vec3(0.001f, 0.001f, 0.001f));

glm::mat4 bulletHoleRotationMatrix = glm::transpose(glm::lookAt(hitPoint, bulletHolePosition, glm::vec3(0.0f, 1.0f, 0.0f)));


You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .