/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <utils/Log.h>
#include <SkMatrix.h>
#include "Matrix.h"
namespace android {
namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Defines
///////////////////////////////////////////////////////////////////////////////
static const float EPSILON = 0.0000001f;
///////////////////////////////////////////////////////////////////////////////
// Matrix
///////////////////////////////////////////////////////////////////////////////
const Matrix4& Matrix4::identity() {
static Matrix4 sIdentity;
return sIdentity;
}
void Matrix4::loadIdentity() {
data[kScaleX] = 1.0f;
data[kSkewY] = 0.0f;
data[2] = 0.0f;
data[kPerspective0] = 0.0f;
data[kSkewX] = 0.0f;
data[kScaleY] = 1.0f;
data[6] = 0.0f;
data[kPerspective1] = 0.0f;
data[8] = 0.0f;
data[9] = 0.0f;
data[kScaleZ] = 1.0f;
data[11] = 0.0f;
data[kTranslateX] = 0.0f;
data[kTranslateY] = 0.0f;
data[kTranslateZ] = 0.0f;
data[kPerspective2] = 1.0f;
mType = kTypeIdentity | kTypeRectToRect;
}
static bool isZero(float f) {
return fabs(f) <= EPSILON;
}
uint8_t Matrix4::getType() const {
if (mType & kTypeUnknown) {
mType = kTypeIdentity;
if (data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
data[kPerspective2] != 1.0f) {
mType |= kTypePerspective;
}
if (data[kTranslateX] != 0.0f || data[kTranslateY] != 0.0f) {
mType |= kTypeTranslate;
}
float m00 = data[kScaleX];
float m01 = data[kSkewX];
float m10 = data[kSkewY];
float m11 = data[kScaleY];
float m32 = data[kTranslateZ];
if (m01 != 0.0f || m10 != 0.0f || m32 != 0.0f) {
mType |= kTypeAffine;
}
if (m00 != 1.0f || m11 != 1.0f) {
mType |= kTypeScale;
}
// The following section determines whether the matrix will preserve
// rectangles. For instance, a rectangle transformed by a pure
// translation matrix will result in a rectangle. A rectangle
// transformed by a 45 degrees rotation matrix is not a rectangle.
// If the matrix has a perspective component then we already know
// it doesn't preserve rectangles.
if (!(mType & kTypePerspective)) {
if ((isZero(m00) && isZero(m11) && !isZero(m01) && !isZero(m10)) ||
(isZero(m01) && isZero(m10) && !isZero(m00) && !isZero(m11))) {
mType |= kTypeRectToRect;
}
}
}
return mType;
}
uint8_t Matrix4::getGeometryType() const {
return getType() & sGeometryMask;
}
bool Matrix4::rectToRect() const {
return getType() & kTypeRectToRect;
}
bool Matrix4::positiveScale() const {
return (data[kScaleX] > 0.0f && data[kScaleY] > 0.0f);
}
bool Matrix4::changesBounds() const {
return getType() & (kTypeScale | kTypeAffine | kTypePerspective);
}
bool Matrix4::isPureTranslate() const {
// NOTE: temporary hack to workaround ignoreTransform behavior with Z values
// TODO: separate this into isPure2dTranslate vs isPure3dTranslate
return getGeometryType() <= kTypeTranslate && (data[kTranslateZ] == 0.0f);
}
bool Matrix4::isSimple() const {
return getGeometryType() <= (kTypeScale | kTypeTranslate) && (data[kTranslateZ] == 0.0f);
}
bool Matrix4::isIdentity() const {
return getGeometryType() == kTypeIdentity;
}
bool Matrix4::isPerspective() const {
return getType() & kTypePerspective;
}
void Matrix4::load(const float* v) {
memcpy(data, v, sizeof(data));
mType = kTypeUnknown;
}
void Matrix4::load(const SkMatrix& v) {
memset(data, 0, sizeof(data));
data[kScaleX] = v[SkMatrix::kMScaleX];
data[kSkewX] = v[SkMatrix::kMSkewX];
data[kTranslateX] = v[SkMatrix::kMTransX];
data[kSkewY] = v[SkMatrix::kMSkewY];
data[kScaleY] = v[SkMatrix::kMScaleY];
data[kTranslateY] = v[SkMatrix::kMTransY];
data[kPerspective0] = v[SkMatrix::kMPersp0];
data[kPerspective1] = v[SkMatrix::kMPersp1];
data[kPerspective2] = v[SkMatrix::kMPersp2];
data[kScaleZ] = 1.0f;
// NOTE: The flags are compatible between SkMatrix and this class.
// However, SkMatrix::getType() does not return the flag
// kRectStaysRect. The return value is masked with 0xF
// so we need the extra rectStaysRect() check
mType = v.getType();
if (v.rectStaysRect()) {
mType |= kTypeRectToRect;
}
}
void Matrix4::copyTo(SkMatrix& v) const {
v.reset();
v.set(SkMatrix::kMScaleX, data[kScaleX]);
v.set(SkMatrix::kMSkewX, data[kSkewX]);
v.set(SkMatrix::kMTransX, data[kTranslateX]);
v.set(SkMatrix::kMSkewY, data[kSkewY]);
v.set(SkMatrix::kMScaleY, data[kScaleY]);
v.set(SkMatrix::kMTransY, data[kTranslateY]);
v.set(SkMatrix::kMPersp0, data[kPerspective0]);
v.set(SkMatrix::kMPersp1, data[kPerspective1]);
v.set(SkMatrix::kMPersp2, data[kPerspective2]);
}
void Matrix4::loadInverse(const Matrix4& v) {
// Fast case for common translation matrices
if (v.isPureTranslate()) {
// Reset the matrix
// Unnamed fields are never written to except by
// loadIdentity(), they don't need to be reset
data[kScaleX] = 1.0f;
data[kSkewX] = 0.0f;
data[kScaleY] = 1.0f;
data[kSkewY] = 0.0f;
data[kScaleZ] = 1.0f;
data[kPerspective0] = 0.0f;
data[kPerspective1] = 0.0f;
data[kPerspective2] = 1.0f;
// No need to deal with kTranslateZ because isPureTranslate()
// only returns true when the kTranslateZ component is 0
data[kTranslateX] = -v.data[kTranslateX];
data[kTranslateY] = -v.data[kTranslateY];
data[kTranslateZ] = 0.0f;
// A "pure translate" matrix can be identity or translation
mType = v.getType();
return;
}
double scale = 1.0 / (v.data[kScaleX] * ((double)v.data[kScaleY] * v.data[kPerspective2] -
(double)v.data[kTranslateY] * v.data[kPerspective1]) +
v.data[kSkewX] * ((double)v.data[kTranslateY] * v.data[kPerspective0] -
(double)v.data[kSkewY] * v.data[kPerspective2]) +
v.data[kTranslateX] * ((double)v.data[kSkewY] * v.data[kPerspective1] -
(double)v.data[kScaleY] * v.data[kPerspective0]));
data[kScaleX] = (v.data[kScaleY] * v.data[kPerspective2] -
v.data[kTranslateY] * v.data[kPerspective1]) *
scale;
data[kSkewX] =
(v.data[kTranslateX] * v.data[kPerspective1] - v.data[kSkewX] * v.data[kPerspective2]) *
scale;
data[kTranslateX] =
(v.data[kSkewX] * v.data[kTranslateY] - v.data[kTranslateX] * v.data[kScaleY]) * scale;
data[kSkewY] =
(v.data[kTranslateY] * v.data[kPerspective0] - v.data[kSkewY] * v.data[kPerspective2]) *
scale;
data[kScaleY] = (v.data[kScaleX] * v.data[kPerspective2] -
v.data[kTranslateX] * v.data[kPerspective0]) *
scale;
data[kTranslateY] =
(v.data[kTranslateX] * v.data[kSkewY] - v.data[kScaleX] * v.data[kTranslateY]) * scale;
data[kPerspective0] =
(v.data[kSkewY] * v.data[kPerspective1] - v.data[kScaleY] * v.data[kPerspective0]) *
scale;
data[kPerspective1] =
(v.data[kSkewX] * v.data[kPerspective0] - v.data[kScaleX] * v.data[kPerspective1]) *
scale;
data[kPerspective2] =
(v.data[kScaleX] * v.data[kScaleY] - v.data[kSkewX] * v.data[kSkewY]) * scale;
mType = kTypeUnknown;
}
void Matrix4::copyTo(float* v) const {
memcpy(v, data, sizeof(data));
}
float Matrix4::getTranslateX() const {
return data[kTranslateX];
}
float Matrix4::getTranslateY() const {
return data[kTranslateY];
}
void Matrix4::multiply(float v) {
for (int i = 0; i < 16; i++) {
data[i] *= v;
}
mType = kTypeUnknown;
}
void Matrix4::loadTranslate(float x, float y, float z) {
loadIdentity();
data[kTranslateX] = x;
data[kTranslateY] = y;
data[kTranslateZ] = z;
mType = kTypeTranslate | kTypeRectToRect;
}
void Matrix4::loadScale(float sx, float sy, float sz) {
loadIdentity();
data[kScaleX] = sx;
data[kScaleY] = sy;
data[kScaleZ] = sz;
mType = kTypeScale | kTypeRectToRect;
}
void Matrix4::loadSkew(float sx, float sy) {
loadIdentity();
data[kScaleX] = 1.0f;
data[kSkewX] = sx;
data[kTranslateX] = 0.0f;
data[kSkewY] = sy;
data[kScaleY] = 1.0f;
data[kTranslateY] = 0.0f;
data[kPerspective0] = 0.0f;
data[kPerspective1] = 0.0f;
data[kPerspective2] = 1.0f;
mType = kTypeUnknown;
}
void Matrix4::loadRotate(float angle) {
angle *= float(M_PI / 180.0f);
float c = cosf(angle);
float s = sinf(angle);
loadIdentity();
data[kScaleX] = c;
data[kSkewX] = -s;
data[kSkewY] = s;
data[kScaleY] = c;
mType = kTypeUnknown;
}
void Matrix4::loadRotate(float angle, float x, float y, float z) {
data[kPerspective0] = 0.0f;
data[kPerspective1] = 0.0f;
data[11] = 0.0f;
data[kTranslateX] = 0.0f;
data[kTranslateY] = 0.0f;
data[kTranslateZ] = 0.0f;
data[kPerspective2] = 1.0f;
angle *= float(M_PI / 180.0f);
float c = cosf(angle);
float s = sinf(angle);
const float length = sqrtf(x * x + y * y + z * z);
float recipLen = 1.0f / length;
x *= recipLen;
y *= recipLen;
z *= recipLen;
const float nc = 1.0f - c;
const float xy = x * y;
const float yz = y * z;
const float zx = z * x;
const float xs = x * s;
const float ys = y * s;
const float zs = z * s;
data[kScaleX] = x * x * nc + c;
data[kSkewX] = xy * nc - zs;
data[8] = zx * nc + ys;
data[kSkewY] = xy * nc + zs;
data[kScaleY] = y * y * nc + c;
data[9] = yz * nc - xs;
data[2] = zx * nc - ys;
data[6] = yz * nc + xs;
data[kScaleZ] = z * z * nc + c;
mType = kTypeUnknown;
}
void Matrix4::loadMultiply(const Matrix4& u, const Matrix4& v) {
for (int i = 0; i < 4; i++) {
float x = 0;
float y = 0;
float z = 0;
float w = 0;
for (int j = 0; j < 4; j++) {
const float e = v.get(i, j);
x += u.get(j, 0) * e;
y += u.get(j, 1) * e;
z += u.get(j, 2) * e;
w += u.get(j, 3) * e;
}
set(i, 0, x);
set(i, 1, y);
set(i, 2, z);
set(i, 3, w);
}
mType = kTypeUnknown;
}
void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
loadIdentity();
data[kScaleX] = 2.0f / (right - left);
data[kScaleY] = 2.0f / (top - bottom);
data[kScaleZ] = -2.0f / (far - near);
data[kTranslateX] = -(right + left) / (right - left);
data[kTranslateY] = -(top + bottom) / (top - bottom);
data[kTranslateZ] = -(far + near) / (far - near);
mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
}
float Matrix4::mapZ(const Vector3& orig) const {
// duplicates logic for mapPoint3d's z coordinate
return orig.x * data[2] + orig.y * data[6] + orig.z * data[kScaleZ] + data[kTranslateZ];
}
void Matrix4::mapPoint3d(Vector3& vec) const {
// TODO: optimize simple case
const Vector3 orig(vec);
vec.x = orig.x * data[kScaleX] + orig.y * data[kSkewX] + orig.z * data[8] + data[kTranslateX];
vec.y = orig.x * data[kSkewY] + orig.y * data[kScaleY] + orig.z * data[9] + data[kTranslateY];
vec.z = orig.x * data[2] + orig.y * data[6] + orig.z * data[kScaleZ] + data[kTranslateZ];
}
#define MUL_ADD_STORE(a, b, c) ((a) = (a) * (b) + (c))
void Matrix4::mapPoint(float& x, float& y) const {
if (isSimple()) {
MUL_ADD_STORE(x, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(y, data[kScaleY], data[kTranslateY]);
return;
}
float dx = x * data[kScaleX] + y * data[kSkewX] + data[kTranslateX];
float dy = x * data[kSkewY] + y * data[kScaleY] + data[kTranslateY];
float dz = x * data[kPerspective0] + y * data[kPerspective1] + data[kPerspective2];
if (dz) dz = 1.0f / dz;
x = dx * dz;
y = dy * dz;
}
/**
* Set the contents of the rect to be the bounding rect around each of the corners, mapped by the
* matrix.
*
* NOTE: an empty rect to an arbitrary matrix isn't guaranteed to have an empty output, since that's
* important for conservative bounds estimation (e.g. rotate45Matrix.mapRect of Rect(0, 10) should
* result in non-empty.
*/
void Matrix4::mapRect(Rect& r) const {
if (isIdentity()) return;
if (isSimple()) {
MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
MUL_ADD_STORE(r.top, data[kScaleY], data[kTranslateY]);
MUL_ADD_STORE(r.bottom, data[kScaleY], data[kTranslateY]);
if (r.left > r.right) {
float x = r.left;
r.left = r.right;
r.right = x;
}
if (r.top > r.bottom) {
float y = r.top;
r.top = r.bottom;
r.bottom = y;
}
return;
}
float vertices[] = {r.left, r.top, r.right, r.top, r.right, r.bottom, r.left, r.bottom};
float x, y, z;
for (int i = 0; i < 8; i += 2) {
float px = vertices[i];
float py = vertices[i + 1];
x = px * data[kScaleX] + py * data[kSkewX] + data[kTranslateX];
y = px * data[kSkewY] + py * data[kScaleY] + data[kTranslateY];
z = px * data[kPerspective0] + py * data[kPerspective1] + data[kPerspective2];
if (z) z = 1.0f / z;
vertices[i] = x * z;
vertices[i + 1] = y * z;
}
r.left = r.right = vertices[0];
r.top = r.bottom = vertices[1];
for (int i = 2; i < 8; i += 2) {
x = vertices[i];
y = vertices[i + 1];
if (x < r.left)
r.left = x;
else if (x > r.right)
r.right = x;
if (y < r.top)
r.top = y;
else if (y > r.bottom)
r.bottom = y;
}
}
void Matrix4::decomposeScale(float& sx, float& sy) const {
float len;
len = data[mat4::kScaleX] * data[mat4::kScaleX] + data[mat4::kSkewX] * data[mat4::kSkewX];
sx = copysignf(sqrtf(len), data[mat4::kScaleX]);
len = data[mat4::kScaleY] * data[mat4::kScaleY] + data[mat4::kSkewY] * data[mat4::kSkewY];
sy = copysignf(sqrtf(len), data[mat4::kScaleY]);
}
void Matrix4::dump(const char* label) const {
ALOGD("%s[simple=%d, type=0x%x", label ? label : "Matrix4", isSimple(), getType());
ALOGD(" %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
ALOGD(" %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]);
ALOGD(" %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]);
ALOGD(" %f %f %f %f", data[kPerspective0], data[kPerspective1], data[11], data[kPerspective2]);
ALOGD("]");
}
} // namespace uirenderer
} // namespace android