Qt Quick 3D - Custom Morphing Animation▲
Sélectionnez
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include
"morphgeometry.h"
#include <qmath.h>
struct
Vertex {
QVector3D position;
QVector3D normal;
QVector3D targetPosition;
QVector3D targetNormal;
}
;
Q_STATIC_ASSERT((sizeof
(Vertex)/
16
)*
16
==
sizeof
(Vertex)); // must be 4-float aligned
MorphGeometry::
MorphGeometry(QQuick3DObject *
parent)
:
QQuick3DGeometry(parent)
{
updateData();
}
void
MorphGeometry::
setGridSize(int
gridSize)
{
if
(m_gridSize ==
gridSize)
return
;
m_gridSize =
gridSize;
emit gridSizeChanged();
updateData();
update();
}
void
MorphGeometry::
updateData()
{
clear();
calculateGeometry();
addAttribute(QQuick3DGeometry::Attribute::
PositionSemantic, 0
,
QQuick3DGeometry::Attribute::ComponentType::
F32Type);
addAttribute(QQuick3DGeometry::Attribute::
NormalSemantic, 3
*
sizeof
(float
),
QQuick3DGeometry::Attribute::ComponentType::
F32Type);
addAttribute(QQuick3DGeometry::Attribute::
TargetPositionSemantic, 6
*
sizeof
(float
),
QQuick3DGeometry::Attribute::ComponentType::
F32Type);
addAttribute(QQuick3DGeometry::Attribute::
TargetNormalSemantic, 9
*
sizeof
(float
),
QQuick3DGeometry::Attribute::ComponentType::
F32Type);
addAttribute(QQuick3DGeometry::Attribute::
IndexSemantic, 0
,
QQuick3DGeometry::Attribute::ComponentType::
U32Type);
const
int
numVertexes =
m_positions.size();
m_vertexBuffer.resize(numVertexes *
sizeof
(Vertex));
Vertex *
vert =
reinterpret_cast
&
lt;Vertex *&
gt;(m_vertexBuffer.data());
for
(int
i =
0
; i &
lt; numVertexes; ++
i) {
Vertex &
amp;v =
vert[i];
v.position =
m_positions[i];
v.normal =
m_normals[i];
v.targetPosition =
m_targetPositions[i];
v.targetNormal =
m_targetNormals[i];
}
setStride(sizeof
(Vertex));
setVertexData(m_vertexBuffer);
setPrimitiveType(QQuick3DGeometry::PrimitiveType::
Triangles);
setBounds(boundsMin, boundsMax);
m_indexBuffer =
QByteArray(reinterpret_cast
&
lt;char
*&
gt;(m_indexes.data()), m_indexes.size() *
sizeof
(quint32));
setIndexData(m_indexBuffer);
}
void
MorphGeometry::
calculateGeometry()
{
float
dw =
10.0
; // width/2
float
dh =
10.0
;
int
iw =
m_gridSize;
int
ih =
m_gridSize;
float
wf =
dw *
2
/
iw; //factor grid coord => position
float
hf =
dh *
2
/
ih;
m_positions.clear();
m_indexes.clear();
m_targetPositions.clear();
m_targetNormals.clear();
constexpr
float
maxFloat =
std::
numeric_limits&
lt;float
&
gt;::
max();
boundsMin =
QVector3D(maxFloat, maxFloat, maxFloat);
boundsMax =
QVector3D(-
maxFloat, -
maxFloat, -
maxFloat);
// We construct a rectangular grid of iw times ih vertices;
// ix and iy are indices into the grid. x, y, and z are the spatial
// coordinates we calculate for each vertex. tx, ty, and tz are the
// coordinates for the morph target.
for
(int
iy =
0
; iy &
lt; ih; ++
iy) {
for
(int
ix =
0
; ix &
lt; iw; ++
ix) {
float
x =
ix *
wf -
dw;
float
z =
iy *
hf -
dh;
// The base shape is a cosine wave, and the corresponding normal
// vectors are calculated from the partial derivatives.
float
y =
2
*
qCos(x) +
3.0
;
QVector3D normal{
2
*
qSin(x), 2
, 0
}
;
float
tx =
x *
1.2
;
float
tz =
z *
1.2
;
constexpr
float
R =
16
;
QVector3D targetPosition;
QVector3D targetNormal;
// The morph target shape is a hemisphere. Therefore we don't have
// to do complex math to calculate the normal vector, since all vectors
// from the center are normal to the surface of a sphere.
if
(tx*
tx +
tz*
tz &
lt; R*
R) {
float
ty =
0.4
f *
qSqrt(R*
R -
tx*
tx -
tz*
tz);
targetPosition =
{
tx, ty, tz}
;
targetNormal =
targetPosition;
}
else
{
targetPosition =
{
tx, -
3
, tz}
;
targetNormal =
{
0
, 1
, 0
}
;
}
// Finally, we make the outside edges of the shapes vertical, so they look nicer.
if
(ix ==
0
||
iy ==
0
||
ix ==
iw-
1
||
iy ==
ih-
1
) {
int
iix =
qMin(iw-
2
, qMax(1
, ix));
int
iiy =
qMin(ih-
2
, qMax(1
, iy));
x =
iix *
wf -
dw;
z =
iiy *
hf -
dh;
y =
-
3.0
;
targetPosition.setY(-
3
);
}
if
(iy &
gt;=
ih-
2
)
normal =
{
0
, 0
, 1
}
;
else
if
(iy &
lt;=
1
)
normal =
{
0
, 0
, -
1
}
;
m_positions.append({
x, y, z}
);
m_normals.append(normal.normalized());
m_targetPositions.append(targetPosition);
m_targetNormals.append(targetNormal.normalized());
// Note: We only use the bounds of the target positions since they are strictly
// bigger than the original
boundsMin.setX(std::
min(boundsMin.x(), targetPosition.x()));
boundsMin.setY(std::
min(boundsMin.y(), targetPosition.y()));
boundsMin.setZ(std::
min(boundsMin.z(), targetPosition.z()));
boundsMax.setX(std::
max(boundsMax.x(), targetPosition.x()));
boundsMax.setY(std::
max(boundsMax.y(), targetPosition.y()));
boundsMax.setZ(std::
max(boundsMax.z(), targetPosition.z()));
}
}
for
(int
ix =
0
; ix &
lt; iw -
1
; ++
ix) {
for
(int
iy =
0
; iy &
lt; ih -
1
; ++
iy) {
int
idx =
ix +
iy *
ih;
m_indexes &
lt;&
lt; idx &
lt;&
lt; idx +
iw &
lt;&
lt; idx +
iw +
1
&
lt;&
lt; idx &
lt;&
lt; idx +
iw +
1
&
lt;&
lt; idx +
1
;
}
}
}