struct mat33 {
vec3 v[3];
inline const vec3& operator [] (int i) const { return v[i]; }
inline vec3& operator [] (int i) { return v[i]; }
};
mat33 mMatrix;
mutable uint32_t mType;
废话不多说直接看代码,表示变换的类就包含上面两个数据,一个矩阵用一个3*3的数组表示。另一个变量mType则表示变换的类型。
看完数据结构后我们该看一下它所包含的方法
要弄清楚变换操作首先要看下Transform的无参构造函数
Transform::Transform() {
reset();
}
void Transform::reset() {
mType = IDENTITY;
for(int i=0 ; i<3 ; i++) {
vec3& v(mMatrix[i]);
for (int j=0 ; j<3 ; j++)
v[j] = ((i==j) ? 1.0f : 0.0f);
}
}
无参构造函数构造了一个主对角线为1的矩阵,数学上叫做单位矩阵,单位矩阵的特性就是点乘一个向量,不会使这个向量产生变换,如下公式
1 平移
void Transform::set(float tx, float ty)
{
mMatrix[2][0] = tx;
mMatrix[2][1] = ty;
mMatrix[2][2] = 1.0f;
if (isZero(tx) && isZero(ty)) {
mType &= ~TRANSLATE;
} else {
mType |= TRANSLATE;
}
}
这个函数是用于设置偏移,后面判断如果tx,ty不是0,则设置mType |= TRANSLATE,表示这个变换类包含位移的变换。我们来看下如何使用矩阵进行变换
对于二维变换,考虑到点成(0,0,0)坐标的情况,要进行齐次坐标变换,所以要变换二维向量,要使用3*3矩阵。 变换完成再进行投影转变为二维就可以了。
根据理论反推我们可以知道Transform的mMatrix所保存的矩阵是用来变换二维向量的,另外坐标mMatrix[2][0],mMatrix[2][1],mMatrix[2][2] 表示的是矩阵的第三列。由此可见矩阵Matrix[i][j] 中i表示的为列,j表示为行。
看完平移变换,它的type标志为TRANSLATE,我们再看下有没有其他的变换类型。
enum type_mask {
IDENTITY = 0,
TRANSLATE = 0x1,
ROTATE = 0x2,
SCALE = 0x4,
UNKNOWN = 0x8
};
在看其他变换之前我们先看一下Transform的乘法操作
,这里我们看一下如何重载乘法操作
Transform Transform::operator * (const Transform& rhs) const
{
if (CC_LIKELY(mType == IDENTITY))
return rhs;
Transform r(*this);
if (rhs.mType == IDENTITY)
return r;
// TODO: we could use mType to optimize the matrix multiply
const mat33& A(mMatrix);
const mat33& B(rhs.mMatrix);
mat33& D(r.mMatrix);
for (int i=0 ; i<3 ; i++) {
const float v0 = A[0][i];
const float v1 = A[1][i];
const float v2 = A[2][i];
D[0][i] = v0*B[0][0] + v1*B[0][1] + v2*B[0][2];
D[1][i] = v0*B[1][0] + v1*B[1][1] + v2*B[1][2];
D[2][i] = v0*B[2][0] + v1*B[2][1] + v2*B[2][2];
}
r.mType |= rhs.c;
// TODO: we could recompute this value from r and rhs
r.mType &= 0xFF;
r.mType |= UNKNOWN_TYPE;
return r;
}
首先对于 if (rhs.mType == IDENTITY)请情况,不会对坐标产生任何影响,所以直接返回自身。剩下的部分实现了简单的矩阵点乘,之后对mType进行设置,并清除高位,然后返回计算后的Transform。
看了上面的两个函数就可以看其他的变换了
除了平移还有旋转和缩放,这三种操作都可以通过下面函数完成
status_t Transform::set(uint32_t flags, float w, float h)
{
if (flags & ROT_INVALID) {
// that's not allowed!
reset();
return BAD_VALUE;
}
Transform H, V, R;
if (flags & ROT_90) {
// w & h are inverted when rotating by 90 degrees
swap(w, h);
}
if (flags & FLIP_H) {
H.mType = (FLIP_H << 8) | SCALE;
H.mType |= isZero(w) ? IDENTITY : TRANSLATE;
mat33& M(H.mMatrix);
M[0][0] = -1;
M[2][0] = w;
}
if (flags & FLIP_V) {
V.mType = (FLIP_V << 8) | SCALE;
V.mType |= isZero(h) ? IDENTITY : TRANSLATE;
mat33& M(V.mMatrix);
M[1][1] = -1;
M[2][1] = h;
}
if (flags & ROT_90) {
const float original_w = h;
R.mType = (ROT_90 << 8) | ROTATE;
R.mType |= isZero(original_w) ? IDENTITY : TRANSLATE;
mat33& M(R.mMatrix);
M[0][0] = 0; M[1][0] =-1; M[2][0] = original_w;
M[0][1] = 1; M[1][1] = 0;
}
*this = (R*(H*V));
return NO_ERROR;
}
2 旋转
旋转主要通过flags表示(不需要该表位置和大小),在Android 中,支持的屏幕旋转角度有4个(注意这里是指沿坐标原点旋转):0(360),90,180,270。为了实现这四个角度的旋转,又引入了两个操作FLIP_H横向翻转和FLIP_V纵向翻转,这二者的含义都不难理解,就是沿y轴旋转180度和沿着x旋转180度。所以我们可以通过沿坐标轴旋转实现沿着原点旋转。沿着坐标轴旋转被表示成如下四种模式:
ROT_90
R0T_0
ROT_180 = FLIP_H|FLIP_V,
ROT_270 = ROT_180|ROT_90,
回到前面的set函数,如果设置了旋转90度,可能是真的旋转90度也可能是旋转270度,因为ROT_270 = ROT_180|ROT_90,所以先将hw对调(这里的w,h是用于调整屏幕不要跑到坐标为负值的地方)
(1)然后如果包含FLIP_H操作,则设置变换矩阵为
变换过程如下
(2)FLIP_V操作,则设置变换矩阵为
变换过程如下
(3)旋转90度
变换过程如下
上拉w防止跑到负轴。
这样就可以完成变换了,另外读者可能比较疑惑的地方,横轴或者纵轴翻转的时候为什么设置了V.mType = (FLIP_V << 8) | SCALE,仔细想一下,翻转的时候相当于x,y都发生的变化,类似是一种缩放操作
3 对Region进行变换操作
Region Transform::transform(const Region& reg) const
{
Region out;
if (CC_UNLIKELY(type() > TRANSLATE)) {
if (CC_LIKELY(preserveRects())) {
Region::const_iterator it = reg.begin();
Region::const_iterator const end = reg.end();
while (it != end) {
out.orSelf(transform(*it++));
}
} else {
out.set(transform(reg.bounds()));
}
} else {
int xpos = floorf(tx() + 0.5f);
int ypos = floorf(ty() + 0.5f);
out = reg.translate(xpos, ypos);
}
return out;
}
type()函数是用于根据矩阵推算出变换的,由于太晚了,这里就不分析了,上面的函数比较简单,发现如果只有TRANSLATE操作的死后直接位移x,y。 如果有其他变换操作则利用上述矩阵,对每个Region中的Rect的四个点进行矩阵点乘。
Transform函数就分析到这里