1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-08-01 19:00:34 +02:00

#16 transform meshes by quaternion & pos instead of matrix

This commit is contained in:
XProger
2016-12-28 01:18:12 +03:00
parent d1bd39abfd
commit 687db8a01a
12 changed files with 113 additions and 59 deletions

Binary file not shown.

View File

@@ -200,46 +200,46 @@ struct Animation {
return lerpAngle(frameA->getAngle(joint), frameB->getAngle(joint), delta); return lerpAngle(frameA->getAngle(joint), frameB->getAngle(joint), delta);
} }
mat4 getJoints(mat4 matrix, int joint, bool postRot = false, mat4 *joints = NULL) { Basis getJoints(Basis basis, int joint, bool postRot = false, Basis *joints = NULL) {
ASSERT(model); ASSERT(model);
vec3 offset = isPrepareToNext ? this->offset : vec3(0.0f); vec3 offset = isPrepareToNext ? this->offset : vec3(0.0f);
matrix.translate(((vec3)frameA->pos).lerp(offset + frameB->pos, delta)); basis.translate(((vec3)frameA->pos).lerp(offset + frameB->pos, delta));
TR::Node *node = (int)model->node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model->node] : NULL; TR::Node *node = (int)model->node < level->nodesDataSize ? (TR::Node*)&level->nodesData[model->node] : NULL;
int sIndex = 0; int sIndex = 0;
mat4 stack[16]; Basis stack[16];
for (int i = 0; i < model->mCount; i++) { for (int i = 0; i < model->mCount; i++) {
if (i > 0 && node) { if (i > 0 && node) {
TR::Node &t = node[i - 1]; TR::Node &t = node[i - 1];
if (t.flags & 0x01) matrix = stack[--sIndex]; if (t.flags & 0x01) basis = stack[--sIndex];
if (t.flags & 0x02) stack[sIndex++] = matrix; if (t.flags & 0x02) stack[sIndex++] = basis;
ASSERT(sIndex >= 0 && sIndex < 16); ASSERT(sIndex >= 0 && sIndex < 16);
matrix.translate(vec3((float)t.x, (float)t.y, (float)t.z)); basis.translate(vec3((float)t.x, (float)t.y, (float)t.z));
} }
if (i == joint && !postRot) if (i == joint && !postRot)
return matrix; return basis;
quat q; quat q;
if (overrideMask & (1 << i)) if (overrideMask & (1 << i))
q = overrides[i]; q = overrides[i];
else else
q = getJointRot(i); q = getJointRot(i);
matrix = matrix * mat4(q, vec3(0.0f)); basis.rotate(q);
if (i == joint && postRot) if (i == joint && postRot)
return matrix; return basis;
if (joints) if (joints)
joints[i] = matrix; joints[i] = basis;
} }
return matrix; return basis;
} }
Box getBoundingBox(const vec3 &pos, int dir) { Box getBoundingBox(const vec3 &pos, int dir) {

View File

@@ -22,7 +22,7 @@ struct Controller {
vec3 pos; vec3 pos;
vec3 angle; vec3 angle;
mat4 *joints; Basis *joints;
int frameIndex; int frameIndex;
vec3 ambient[6]; vec3 ambient[6];
@@ -49,7 +49,7 @@ struct Controller {
pos = vec3((float)e.x, (float)e.y, (float)e.z); pos = vec3((float)e.x, (float)e.y, (float)e.z);
angle = vec3(0.0f, e.rotation, 0.0f); angle = vec3(0.0f, e.rotation, 0.0f);
TR::Model *m = getModel(); TR::Model *m = getModel();
joints = m ? new mat4[m->mCount] : NULL; joints = m ? new Basis[m->mCount] : NULL;
frameIndex = -1; frameIndex = -1;
specular = 0.0f; specular = 0.0f;
} }
@@ -85,8 +85,8 @@ struct Controller {
Box box = ((Controller*)e.controller)->getBoundingBox(); Box box = ((Controller*)e.controller)->getBoundingBox();
vec3 t = (box.min + box.max) * 0.5f; vec3 t = (box.min + box.max) * 0.5f;
mat4 m = animation.getJoints(getMatrix(), joint); Basis b = animation.getJoints(Basis(getMatrix()), joint);
vec3 delta = (m.inverse() * t).normal(); vec3 delta = (b.inverse() * t).normal();
float angleY = clampAngle(atan2f(delta.x, delta.z)); float angleY = clampAngle(atan2f(delta.x, delta.z));
float angleX = clampAngle(asinf(delta.y)); float angleX = clampAngle(asinf(delta.y));
@@ -99,7 +99,7 @@ struct Controller {
rot = ay * ax; rot = ay * ax;
if (rotAbs) if (rotAbs)
*rotAbs = m.getRot() * rot; *rotAbs = b.rot * rot;
return true; return true;
} }
} }
@@ -470,13 +470,13 @@ struct Controller {
mask |= layers[i].mask; mask |= layers[i].mask;
// set meshes visibility // set meshes visibility
for (int j = 0; j < model->mCount; j++) for (int j = 0; j < model->mCount; j++)
joints[j].e33 = (vmask & (1 << j)) ? 1.0f : -1.0f; joints[j].w = (vmask & (1 << j)) ? 1.0f : -1.0f; // AHAHA
// render // render
Core::active.shader->setParam(uModel, joints[0], model->mCount); Core::active.shader->setParam(uBasis, joints[0], model->mCount);
mesh->renderModel(layers[i].model); mesh->renderModel(layers[i].model);
} }
} else { } else {
Core::active.shader->setParam(uModel, joints[0], model->mCount); Core::active.shader->setParam(uBasis, joints[0], model->mCount);
mesh->renderModel(entity.modelIndex - 1); mesh->renderModel(entity.modelIndex - 1);
} }

View File

@@ -135,7 +135,8 @@ namespace Core {
int width, height; int width, height;
int frameIndex; int frameIndex;
float deltaTime; float deltaTime;
mat4 mView, mProj, mViewProj, mViewInv, mModel, mLightProj; mat4 mView, mProj, mViewProj, mViewInv, mLightProj;
Basis basis;
vec3 viewPos; vec3 viewPos;
vec3 lightPos[MAX_LIGHTS]; vec3 lightPos[MAX_LIGHTS];
vec4 lightColor[MAX_LIGHTS]; vec4 lightColor[MAX_LIGHTS];

View File

@@ -211,7 +211,7 @@ struct Wolf : Enemy {
if ((state == STATE_JUMP || state == STATE_BITE) && !bitten) { if ((state == STATE_JUMP || state == STATE_BITE) && !bitten) {
float dist; float dist;
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f) if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).getPos(), state == STATE_BITE ? 100 : 50); bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).pos, state == STATE_BITE ? 100 : 50);
} }
if (state == STATE_JUMP) if (state == STATE_JUMP)
@@ -300,7 +300,7 @@ struct Bear : Enemy {
if ((state == STATE_ATTACK || state == STATE_BITE) && !bitten) { if ((state == STATE_ATTACK || state == STATE_BITE) && !bitten) {
float dist; float dist;
if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f) if (getTargetInfo(0, NULL, NULL, NULL, &dist) && dist < 256.0f)
bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).getPos(), state == STATE_BITE ? 100 : 50); bite(animation.getJoints(getMatrix(), JOINT_HEAD, true).pos, state == STATE_BITE ? 100 : 50);
} }
return state; return state;

View File

@@ -300,7 +300,7 @@ struct Lara : Character {
*/ */
updateEntity(); updateEntity();
#endif #endif
chestOffset = animation.getJoints(getMatrix(), 7).getPos(); chestOffset = animation.getJoints(getMatrix(), 7).pos;
if (getRoom().flags.water) if (getRoom().flags.water)
animation.setAnim(ANIM_UNDERWATER); animation.setAnim(ANIM_UNDERWATER);
} }
@@ -561,7 +561,7 @@ struct Lara : Character {
int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8); int joint = wpnCurrent == Weapon::SHOTGUN ? 8 : (i ? 11 : 8);
vec3 p = animation.getJoints(getMatrix(), joint, false).getPos(); vec3 p = animation.getJoints(getMatrix(), joint, false).pos;
vec3 d = arm->rotAbs * vec3(0, 0, 1); vec3 d = arm->rotAbs * vec3(0, 0, 1);
vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f; vec3 t = p + d * (24.0f * 1024.0f) + ((vec3(randf(), randf(), randf()) * 2.0f) - vec3(1.0f)) * 1024.0f;
@@ -582,7 +582,7 @@ struct Lara : Character {
} }
} }
Core::lightPos[1 + armIndex] = animation.getJoints(getMatrix(), armIndex == 0 ? 10 : 13, false).getPos(); Core::lightPos[1 + armIndex] = animation.getJoints(getMatrix(), armIndex == 0 ? 10 : 13, false).pos;
Core::lightColor[1 + armIndex] = FLASH_LIGHT_COLOR; Core::lightColor[1 + armIndex] = FLASH_LIGHT_COLOR;
} }
@@ -1050,7 +1050,7 @@ struct Lara : Character {
} }
vec3 getViewPoint() { vec3 getViewPoint() {
vec3 offset = chestOffset = animation.getJoints(getMatrix(), 7).getPos(); vec3 offset = chestOffset = animation.getJoints(getMatrix(), 7).pos;
if (stand != STAND_UNDERWATER) if (stand != STAND_UNDERWATER)
offset.y -= 256.0f; offset.y -= 256.0f;
if (!emptyHands()) if (!emptyHands())
@@ -1768,23 +1768,23 @@ struct Lara : Character {
} }
void renderMuzzleFlash(MeshBuilder *mesh, const mat4 &matrix, const vec3 &offset, float time) { void renderMuzzleFlash(MeshBuilder *mesh, const Basis &basis, const vec3 &offset, float time) {
ASSERT(level->extra.muzzleFlash); ASSERT(level->extra.muzzleFlash);
if (time > MUZZLE_FLASH_TIME) return; if (time > MUZZLE_FLASH_TIME) return;
float alpha = min(1.0f, (0.1f - time) * 20.0f); float alpha = min(1.0f, (0.1f - time) * 20.0f);
float lum = 3.0f; float lum = 3.0f;
mat4 m(matrix); Basis b(basis);
m.rotateX(-PI * 0.5f); b.rotate(quat(vec3(1, 0, 0), -PI * 0.5f));
m.translate(offset); b.translate(offset);
Core::active.shader->setParam(uColor, vec4(lum, lum, lum, alpha)); Core::active.shader->setParam(uColor, vec4(lum, lum, lum, alpha));
Core::active.shader->setParam(uModel, m); Core::active.shader->setParam(uBasis, b);
mesh->renderModel(level->extra.muzzleFlash); mesh->renderModel(level->extra.muzzleFlash);
} }
virtual void render(Frustum *frustum, MeshBuilder *mesh) { virtual void render(Frustum *frustum, MeshBuilder *mesh) {
Controller::render(frustum, mesh); Controller::render(frustum, mesh);
chestOffset = animation.getJoints(getMatrix(), 7).getPos(); // TODO: move to update func chestOffset = animation.getJoints(getMatrix(), 7).pos; // TODO: move to update func
if (wpnCurrent != Weapon::SHOTGUN && Core::pass != Core::passShadow && (arms[0].shotTimer < MUZZLE_FLASH_TIME || arms[1].shotTimer < MUZZLE_FLASH_TIME)) { if (wpnCurrent != Weapon::SHOTGUN && Core::pass != Core::passShadow && (arms[0].shotTimer < MUZZLE_FLASH_TIME || arms[1].shotTimer < MUZZLE_FLASH_TIME)) {
mat4 matrix = getMatrix(); mat4 matrix = getMatrix();

View File

@@ -557,14 +557,13 @@ struct Level {
if (Core::pass != Core::passShadow) { if (Core::pass != Core::passShadow) {
setRoomParams(room, intensityf(room.ambient)); setRoomParams(room, intensityf(room.ambient));
Basis qTemp = Core::basis;
Core::basis.translate(offset);
Shader *sh = Core::active.shader; Shader *sh = Core::active.shader;
sh->setParam(uType, Shader::ROOM); sh->setParam(uType, Shader::ROOM);
sh->setParam(uBasis, Core::basis);
mat4 mTemp = Core::mModel;
Core::mModel.translate(offset);
sh->setParam(uModel, Core::mModel);
// render room geometry // render room geometry
mesh->renderRoomGeometry(roomIndex); mesh->renderRoomGeometry(roomIndex);
@@ -577,7 +576,7 @@ struct Level {
mesh->renderRoomSprites(roomIndex); mesh->renderRoomSprites(roomIndex);
} }
Core::mModel = mTemp; Core::basis = qTemp;
} }
} }
@@ -728,7 +727,7 @@ struct Level {
sh->setParam(uAnimTexRanges, mesh->animTexRanges[0], mesh->animTexRangesCount); sh->setParam(uAnimTexRanges, mesh->animTexRanges[0], mesh->animTexRangesCount);
sh->setParam(uAnimTexOffsets, mesh->animTexOffsets[0], mesh->animTexOffsetsCount); sh->setParam(uAnimTexOffsets, mesh->animTexOffsets[0], mesh->animTexOffsetsCount);
Core::mModel.identity(); Core::basis.identity();
// clear visibility flag for rooms // clear visibility flag for rooms
for (int i = 0; i < level.roomsCount; i++) for (int i = 0; i < level.roomsCount; i++)
@@ -786,7 +785,7 @@ struct Level {
} }
bool setupLightCamera() { bool setupLightCamera() {
vec3 pos = (lara->animation.getJoints(lara->getMatrix(), 0, false, NULL)).getPos(); vec3 pos = (lara->animation.getJoints(lara->getMatrix(), 0, false, NULL)).pos;
// omni-spot light shadows // omni-spot light shadows
int room = lara->getRoomIndex(); int room = lara->getRoomIndex();

View File

@@ -211,7 +211,8 @@ struct MeshBuilder {
TR::Model &model = level.models[i]; TR::Model &model = level.models[i];
for (int j = 0; j < model.mCount; j++) { for (int j = 0; j < model.mCount; j++) {
int index = level.meshOffsets[model.mStart + j]; int index = level.meshOffsets[model.mStart + j];
if (!index && model.mCount == 1) continue; if (!index && model.mStart + j > 0)
continue;
aCount++; aCount++;
TR::Mesh &mesh = level.meshes[index]; TR::Mesh &mesh = level.meshes[index];
iCount += mesh.rCount * 6 + mesh.tCount * 3; iCount += mesh.rCount * 6 + mesh.tCount * 3;
@@ -337,7 +338,7 @@ struct MeshBuilder {
for (int j = 0; j < model.mCount; j++) { for (int j = 0; j < model.mCount; j++) {
int index = level.meshOffsets[model.mStart + j]; int index = level.meshOffsets[model.mStart + j];
if (!index && model.mCount == 1) continue; if (!index && model.mStart + j > 0) continue;
TR::Mesh &mesh = level.meshes[index]; TR::Mesh &mesh = level.meshes[index];
buildMesh(mesh, level, indices, vertices, iCount, vCount, vStart, j, 0, 0, 0, 0); buildMesh(mesh, level, indices, vertices, iCount, vCount, vStart, j, 0, 0, 0, 0);
@@ -725,9 +726,9 @@ struct MeshBuilder {
Core::active.shader->setParam(uColor, color); Core::active.shader->setParam(uColor, color);
//text = "a: b"; //text = "a: b";
mat4 m; Basis basis;
m.identity(); basis.identity();
m.translate(vec3(pos.x, pos.y, 0.0f)); basis.translate(vec3(pos.x, pos.y, 0.0f));
// text = "A"; // text = "A";
while (char c = *text++) { while (char c = *text++) {
@@ -749,13 +750,13 @@ struct MeshBuilder {
} }
*/ */
if (c == ' ' || c == '_') { if (c == ' ' || c == '_') {
m.translate(vec3(char_width[0], 0.0f, 0.0f)); basis.translate(vec3(char_width[0], 0.0f, 0.0f));
continue; continue;
} }
Core::active.shader->setParam(uModel, m); Core::active.shader->setParam(uBasis, basis);
renderSprite(15, frame); renderSprite(15, frame);
m.translate(vec3(char_width[frame], 0.0f, 0.0f)); basis.translate(vec3(char_width[frame], 0.0f, 0.0f));
} }
} }
}; };

View File

@@ -26,7 +26,7 @@ uniform int uType;
#ifdef VERTEX #ifdef VERTEX
uniform mat4 uViewProj; uniform mat4 uViewProj;
uniform mat4 uModel[32]; uniform vec4 uBasis[32 * 2];
#ifndef PASS_AMBIENT #ifndef PASS_AMBIENT
uniform mat4 uViewInv; uniform mat4 uViewInv;
@@ -51,11 +51,21 @@ uniform int uType;
#endif #endif
#define TEXCOORD_SCALE (1.0 / 32767.0) #define TEXCOORD_SCALE (1.0 / 32767.0)
vec3 mulQuat(vec4 q, vec3 v) {
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w);
}
vec3 mulBasis(vec4 rot, vec4 pos, vec3 v) {
return mulQuat(rot, v) + pos.xyz;
}
void main() { void main() {
mat4 rJoint = uModel[int(aCoord.w)]; int index = int(aCoord.w) * 2;
vec4 rBasisRot = uBasis[index];
vec4 rBasisPos = uBasis[index + 1];
vec4 coord = rJoint * vec4(aCoord.xyz, 1.0); vec4 coord = vec4(mulBasis(rBasisRot, rBasisPos, aCoord.xyz), rBasisPos.w);
#ifdef PASS_COMPOSE #ifdef PASS_COMPOSE
if (uType != TYPE_SPRITE) { if (uType != TYPE_SPRITE) {
@@ -66,7 +76,7 @@ uniform int uType;
vec2 offset = uAnimTexOffsets[int(range.x + f)]; // texCoord offset from first frame vec2 offset = uAnimTexOffsets[int(range.x + f)]; // texCoord offset from first frame
vTexCoord = (aTexCoord.xy + offset) * TEXCOORD_SCALE; // first frame + offset * isAnimated vTexCoord = (aTexCoord.xy + offset) * TEXCOORD_SCALE; // first frame + offset * isAnimated
vNormal = vec4((rJoint * vec4(aNormal.xyz, 0.0)).xyz, aNormal.w); vNormal = vec4(mulQuat(rBasisRot, aNormal.xyz), aNormal.w);
} else { } else {
coord.xyz += uViewInv[0].xyz * aTexCoord.z - uViewInv[1].xyz * aTexCoord.w; coord.xyz += uViewInv[0].xyz * aTexCoord.z - uViewInv[1].xyz * aTexCoord.w;
vTexCoord = aTexCoord.xy * TEXCOORD_SCALE; vTexCoord = aTexCoord.xy * TEXCOORD_SCALE;

View File

@@ -5,11 +5,11 @@
enum AttribType { aCoord, aTexCoord, aNormal, aColor, aMAX }; enum AttribType { aCoord, aTexCoord, aNormal, aColor, aMAX };
enum SamplerType { sDiffuse, sShadow, sEnvironment, sMAX }; enum SamplerType { sDiffuse, sShadow, sEnvironment, sMAX };
enum UniformType { uType, uCaustics, uTime, uViewProj, uViewInv, uModel, uLightProj, uColor, uAmbient, uViewPos, uLightsCount, uLightPos, uLightColor, uAnimTexRanges, uAnimTexOffsets, uMAX }; enum UniformType { uType, uCaustics, uTime, uViewProj, uViewInv, uBasis, uLightProj, uColor, uAmbient, uViewPos, uLightsCount, uLightPos, uLightColor, uAnimTexRanges, uAnimTexOffsets, uMAX };
const char *AttribName[aMAX] = { "aCoord", "aTexCoord", "aNormal", "aColor" }; const char *AttribName[aMAX] = { "aCoord", "aTexCoord", "aNormal", "aColor" };
const char *SamplerName[sMAX] = { "sDiffuse", "sShadow", "sEnvironment" }; const char *SamplerName[sMAX] = { "sDiffuse", "sShadow", "sEnvironment" };
const char *UniformName[uMAX] = { "uType", "uCaustics", "uTime", "uViewProj", "uViewInv", "uModel", "uLightProj", "uColor", "uAmbient", "uViewPos", "uLightsCount", "uLightPos", "uLightColor", "uAnimTexRanges", "uAnimTexOffsets" }; const char *UniformName[uMAX] = { "uType", "uCaustics", "uTime", "uViewProj", "uViewInv", "uBasis", "uLightProj", "uColor", "uAmbient", "uViewPos", "uLightsCount", "uLightPos", "uLightColor", "uAnimTexRanges", "uAnimTexOffsets" };
struct Shader { struct Shader {
GLuint ID; GLuint ID;
@@ -103,6 +103,11 @@ struct Shader {
if (uID[uType] != -1) if (uID[uType] != -1)
glUniformMatrix4fv(uID[uType], count, false, (GLfloat*)&value); glUniformMatrix4fv(uID[uType], count, false, (GLfloat*)&value);
} }
void setParam(UniformType uType, const Basis &value, int count = 1) {
if (uID[uType] != -1)
glUniform4fv(uID[uType], count * 2, (GLfloat*)&value);
}
}; };
#endif #endif

View File

@@ -60,9 +60,9 @@ struct Sprite : Controller {
} }
virtual void render(Frustum *frustum, MeshBuilder *mesh) { virtual void render(Frustum *frustum, MeshBuilder *mesh) {
mat4 m(Core::mModel); Basis basis(Core::basis);
m.translate(pos); basis.translate(pos);
Core::active.shader->setParam(uModel, m); Core::active.shader->setParam(uBasis, basis);
mesh->renderSprite(-(getEntity().modelIndex + 1), frame); mesh->renderSprite(-(getEntity().modelIndex + 1), frame);
} }
}; };

View File

@@ -126,7 +126,8 @@ struct vec3 {
vec3 operator + (const vec3 &v) const { return vec3(x+v.x, y+v.y, z+v.z); } vec3 operator + (const vec3 &v) const { return vec3(x+v.x, y+v.y, z+v.z); }
vec3 operator - (const vec3 &v) const { return vec3(x-v.x, y-v.y, z-v.z); } vec3 operator - (const vec3 &v) const { return vec3(x-v.x, y-v.y, z-v.z); }
vec3 operator * (const vec3 &v) const { return vec3(x*v.x, y*v.y, z*v.z); } vec3 operator * (const vec3 &v) const { return vec3(x*v.x, y*v.y, z*v.z); }
vec3 operator * (float s) const { return vec3(x*s, y*s, z*s); } vec3 operator * (float s) const { return vec3(x*s, y*s, z*s); }
vec3 operator - () const { return vec3(-x, -y, -z); }
float dot(const vec3 &v) const { return x*v.x + y*v.y + z*v.z; } float dot(const vec3 &v) const { return x*v.x + y*v.y + z*v.z; }
vec3 cross(const vec3 &v) const { return vec3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x); } vec3 cross(const vec3 &v) const { return vec3(y*v.z - z*v.y, z*v.x - x*v.z, x*v.y - y*v.x); }
@@ -509,6 +510,43 @@ struct mat4 {
} }
}; };
struct Basis {
quat rot;
vec3 pos;
float w;
Basis() {}
Basis(const quat &rot, const vec3 &pos) : rot(rot), pos(pos), w(1.0f) {}
Basis(const mat4 &matrix) : rot(matrix.getRot()), pos(matrix.getPos()), w(1.0f) {}
void identity() {
rot = quat(0, 0, 0, 1);
pos = vec3(0, 0, 0);
w = 1.0f;
}
Basis operator * (const Basis &basis) const {
return Basis(rot * basis.rot, pos + rot * basis.pos);
}
vec3 operator * (const vec3 &v) const {
return rot * v + pos;
}
Basis inverse() const {
quat q = rot.conjugate();
return Basis(q, -(q * pos));
}
void translate(const vec3 &v) {
pos += rot * v;
}
void rotate(const quat &q) {
rot = rot * q;
}
};
struct ubyte2 { struct ubyte2 {
uint8 x, y; uint8 x, y;
}; };