C

Qt Quick Ultralite perspective_transforms Example

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial

import QtQuick 2.12
import Constants 1.0

Rectangle {
    id: cover

    property string texture
    property int coverIndex
    property CoverFlowState state

    property matrix4x4 coverTransform: calcCoverTransform()
    property matrix4x4 postMatrix
    property matrix4x4 preMatrix
    property matrix4x4 reflectionTransform
    z: calcFinalZ()

    Image {
        id: coverImageBase
        source: cover.texture
        transform: Matrix4x4 { matrix: coverTransform }
        opacity: 1.0
    }

    Image {
        id: mirrorImageBase
        visible: cover.state.showReflection
        source: cover.texture
        transform: [
            Matrix4x4 { matrix: reflectionTransform },
            Matrix4x4 { matrix: coverTransform }
        ]

        opacity: 0.1
    }

    function mtxTranslate(x : real, y : real, z : real) : matrix4x4 {
        return Qt.matrix4x4(1, 0, 0, x,
                            0, 1, 0, y,
                            0, 0, 1, z,
                            0, 0, 0, 1)
    }

    function mtxScale(x : real, y : real, z : real) : matrix4x4 {
        return Qt.matrix4x4(x, 0, 0, 0,
                            0, y, 0, 0,
                            0, 0, z, 0,
                            0, 0, 0, 1)
    }

    function mtxRotX(angle : real) : matrix4x4 {
        var radians = angle * (Math.PI / 180)
        var sinAngle = Math.sin(radians)
        var cosAngle = Math.cos(radians)
        return Qt.matrix4x4(1,        0,         0, 0,
                            0, cosAngle, -sinAngle, 0,
                            0, sinAngle,  cosAngle, 0,
                            0,        0,         0, 1)
    }

    function mtxRotY(angle : real) : matrix4x4 {
        var radians = angle * (Math.PI / 180)
        var sinAngle = Math.sin(radians)
        var cosAngle = Math.cos(radians)

        return Qt.matrix4x4( cosAngle, 0, sinAngle, 0,
                                    0, 1,        0, 0,
                            -sinAngle, 0, cosAngle, 0,
                                    0, 0,        0, 1)
    }

    property matrix4x4 carouselMatrixPost: {
        var r1 = mtxRotX(state.viewAngle)
        var t2 = mtxTranslate(0, state.cameraHeight, 0)

        return postMatrix.times(r1.times(t2))
    }

    property matrix4x4 carouselMatrixPre: {
        var t4 = mtxTranslate(0, 0, state.carouselZ)
        var r5 = mtxRotX(-state.viewAngle/4)
        var s6 = mtxScale(state.coverScaling, state.coverScaling, 1)

        return t4.times(r5.times(s6)).times(preMatrix)
    }

    // Calculate transforms for individual transform types
    function calcPosMatrixCarousel() : matrix4x4 {
        var angle = (coverIndex - state.selectedCover) / state.numberOfCovers * 360;

        var r3 = mtxRotY(angle)

        return carouselMatrixPost.times(r3).times(carouselMatrixPre)
    }

    property matrix4x4 circlePost: {
        var r1 = mtxRotX(state.viewAngle)
        var t2 = mtxTranslate(0, state.cameraHeight, 0)

        return postMatrix.times(r1.times(t2))
    }

    function calcPosMatrixCircle() : matrix4x4 {
        var angle = (coverIndex - state.selectedCover) / state.numberOfCovers * 360;
        var radians = angle * (Math.PI / 180)
        var sinAngle = Math.sin(radians)
        var cosAngle = Math.cos(radians)

        // var t3 = mtxTranslate(state.circleRadius * sinAngle, 0, state.circleRadius * cosAngle)
        // var s4 = mtxScale(state.coverScaling, state.coverScaling, 1)
        // the below is equivalent to t3.times(s4)
        var cs = state.coverScaling
        var dx = state.circleRadius * sinAngle
        var dz = state.circleRadius * cosAngle
        var m3 = Qt.matrix4x4(cs,  0, 0, dx,
                               0, cs, 0,  0,
                               0,  0, 1, dz,
                               0,  0, 0,  1)

        return circlePost.times(m3).times(preMatrix)
    }

    property matrix4x4 circle2DPost: {
        var r1 = mtxRotX(state.viewAngle)
        var t2 = mtxTranslate(0, state.cameraHeight, 0)
        return postMatrix.times(r1.times(t2))
    }

    property matrix4x4 circle2DPre: {
        var r4 = mtxRotX(-state.viewAngle)
        var s5 = mtxScale(state.coverScaling, state.coverScaling, 1)
        return r4.times(s5).times(preMatrix)
    }

    function calcPosMatrixCircle2D() : matrix4x4 {
        var angle = (coverIndex - state.selectedCover) / state.numberOfCovers * 360;
        var radians = angle * (Math.PI / 180)
        var sinAngle = Math.sin(radians)
        var cosAngle = Math.cos(radians)

        var t3 = mtxTranslate(state.circleRadius * sinAngle, 0, state.circleRadius * cosAngle)

        return circle2DPost.times(t3.times(circle2DPre))
    }

    function calcPosMatrixPerspective() : matrix4x4 {
        var fract = Math.min(Math.abs(coverIndex - state.selectedCover), 1)
        var dir = coverIndex <= state.selectedCover ? 1 : -1

        var ta1 = mtxTranslate(0, 0, (1 - fract) * state.perspectiveViewCenterZ)
        var ta2 = mtxTranslate(fract * ((coverIndex - state.selectedCover) * state.perspectiveViewCoverDist - dir * state.perspectiveViewCenterDist), 0, 0)
        var ra3 = mtxRotY(dir * fract * state.perspectiveViewMaxRot)
        return postMatrix.times(ta1.times(ta2.times(ra3))).times(preMatrix)
    }

    // Calculates transform matrix for a given cover flow type
    function calcMatrixForType(type : int) : matrix4x4 {
        switch (type)
        {
         case CoverFlowType.Carousel:
             return calcPosMatrixCarousel()
         case CoverFlowType.Circle:
             return calcPosMatrixCircle()
         case CoverFlowType.Circle2D:
             return calcPosMatrixCircle2D()
         case CoverFlowType.Perspective:
         default:
             return calcPosMatrixPerspective()
        }
    }

    // Calculate final cover transformation considering morphRatio
    function calcCoverTransform() : matrix4x4 {
        var previousViewType = state.previousViewType
        var currentViewType = state.currentViewType
        if (state.morphRatio == 0) {
            currentViewType = previousViewType
        } else if (state.morphRatio == 1) {
            previousViewType = currentViewType
        }

        if (currentViewType == previousViewType) {
            return calcMatrixForType(currentViewType)
        } else {
            var currentShape = calcMatrixForType(currentViewType)
            var oldShape = calcMatrixForType(previousViewType)
            return currentShape.times(state.morphRatio).plus(oldShape.times(1 - state.morphRatio))
        }
    }

    // Calculate cover depth along z-axis
    function calcFinalZ() : real {
        var coverTransform = cover.coverTransform

        var x = state.coverSize/2
        var y = state.coverSize/2

        var inv_d = 1.0 / (coverTransform.m41 * x + coverTransform.m42 * y + coverTransform.m44)
        var fX = (coverTransform.m11 * x + coverTransform.m12 * y + coverTransform.m14) * inv_d
        var fZ = (coverTransform.m31 * x + coverTransform.m32 * y + coverTransform.m34) * inv_d

        var littleX = Math.abs(fX - state.coverFlowX - state.coverFlowW * 0.5)
        return -fZ * 100000 - littleX
    }
}