1
0
mirror of https://github.com/XProger/OpenLara.git synced 2025-04-22 03:51:58 +02:00

#23 room render optimization for mobile devices; contact ambient occlusion (WIP); #11 inventory touch button

This commit is contained in:
XProger 2017-07-27 04:09:49 +03:00
parent e2643cd78a
commit 5739de6044
12 changed files with 175 additions and 137 deletions

View File

@ -131,7 +131,7 @@ struct ShaderCache {
typ = typeNames[type];
int animTexRangesCount = game->getMesh()->animTexRangesCount;
int animTexOffsetsCount = game->getMesh()->animTexOffsetsCount;
sprintf(def, "%s#define PASS_%s\n#define TYPE_%s\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define FOG_DIST (1.0/%d.0)\n#define WATER_FOG_DIST (1.0/%d.0)\n#define SHADOW_TEX_SIZE %d.0\n", ext, passNames[pass], typ, MAX_LIGHTS, animTexRangesCount, animTexOffsetsCount, FOG_DIST, WATER_FOG_DIST, SHADOW_TEX_SIZE);
sprintf(def, "%s#define PASS_%s\n#define TYPE_%s\n#define MAX_LIGHTS %d\n#define MAX_RANGES %d\n#define MAX_OFFSETS %d\n#define MAX_CONTACTS %d\n#define FOG_DIST (1.0/%d.0)\n#define WATER_FOG_DIST (1.0/%d.0)\n#define SHADOW_TEX_SIZE %d.0\n", ext, passNames[pass], typ, MAX_LIGHTS, animTexRangesCount, animTexOffsetsCount, MAX_CONTACTS, FOG_DIST, WATER_FOG_DIST, SHADOW_TEX_SIZE);
if (fx & FX_UNDERWATER) strcat(def, "#define UNDERWATER\n" UNDERWATER_COLOR);
if (fx & FX_ALPHA_TEST) strcat(def, "#define ALPHA_TEST\n");
if (fx & FX_CLIP_PLANE) strcat(def, "#define CLIP_PLANE\n");
@ -139,6 +139,7 @@ struct ShaderCache {
if (Core::settings.detail.lighting) strcat(def, "#define OPT_LIGHTING\n");
if (Core::settings.detail.shadows) strcat(def, "#define OPT_SHADOW\n");
if (Core::settings.detail.water) strcat(def, "#define OPT_WATER\n");
if (Core::settings.detail.contact) strcat(def, "#define OPT_CONTACT\n");
break;
}
case Core::passWater : {
@ -181,7 +182,6 @@ struct ShaderCache {
// TODO: bindable uniform block
shader->setParam(uViewProj, Core::mViewProj);
shader->setParam(uLightProj, Core::mLightProj);
shader->setParam(uViewInv, Core::mViewInv);
shader->setParam(uViewPos, Core::viewPos);
shader->setParam(uParam, Core::params);
MeshBuilder *mesh = game->getMesh();

View File

@ -213,6 +213,7 @@ namespace Core {
#define MAX_LIGHTS 4
#define MAX_CACHED_LIGHTS 3
#define MAX_RENDER_BUFFERS 32
#define MAX_CONTACTS 15
struct Shader;
struct Texture;
@ -322,6 +323,7 @@ namespace Core {
vec3 lightPos[MAX_LIGHTS];
vec4 lightColor[MAX_LIGHTS];
vec4 params;
vec4 contacts[MAX_CONTACTS];
Texture *blackTex, *whiteTex;
@ -391,6 +393,7 @@ namespace Core {
bool lighting;
bool shadows;
bool water;
bool contact;
} detail;
struct {

View File

@ -60,6 +60,7 @@ namespace Debug {
glUseProgram(0);
Core::active.shader = NULL;
Core::active.textures[0] = NULL;
Core::validateRenderState();
}
void end() {

View File

@ -23,6 +23,7 @@ namespace Game {
Core::settings.detail.lighting = true;
Core::settings.detail.shadows = true;
Core::settings.detail.water = Core::support.texFloat || Core::support.texHalf;
Core::settings.detail.contact = false;
Core::settings.controls.retarget = true;

View File

@ -111,7 +111,7 @@ namespace Input {
}
} head;
enum TouchButton { bNone, bWeapon, bWalk, bAction, bJump, bMAX };
enum TouchButton { bNone, bWeapon, bWalk, bAction, bJump, bInventory, bMAX };
enum TouchZone { zMove, zLook, zButton, zMAX };
float touchTimerVis, touchTimerTap;
@ -245,11 +245,12 @@ namespace Input {
float radius = offset;
vec2 center = vec2(Core::width - offset * 0.7f, Core::height - offset * 0.7f);
btnPos[bWeapon] = center;
btnPos[bJump] = center + vec2(cosf(-PI * 0.5f), sinf(-PI * 0.5f)) * radius;
btnPos[bAction] = center + vec2(cosf(-PI * 3.0f / 4.0f), sinf(-PI * 3.0f / 4.0f)) * radius;
btnPos[bWalk] = center + vec2(cosf(-PI), sinf(-PI)) * radius;
btnRadius = Core::height * (25.0f / 1080.0f);
btnRadius = Core::height * (25.0f / 1080.0f);
btnPos[bWeapon] = center;
btnPos[bJump] = center + vec2(cosf(-PI * 0.5f), sinf(-PI * 0.5f)) * radius;
btnPos[bAction] = center + vec2(cosf(-PI * 3.0f / 4.0f), sinf(-PI * 3.0f / 4.0f)) * radius;
btnPos[bWalk] = center + vec2(cosf(-PI), sinf(-PI)) * radius;
btnPos[bInventory] = vec2(Core::width - btnRadius * 2.0f, btnRadius * 2.0f);
// touch update
if (checkTouchZone(zMove))
@ -305,11 +306,12 @@ namespace Input {
}
switch (btn) {
case bWeapon : state[cWeapon] = true; break;
case bWalk : state[cWalk] = true; break;
case bAction : state[cAction] = true; break;
case bJump : state[cJump] = true; break;
default : ;
case bWeapon : state[cWeapon] = true; break;
case bWalk : state[cWalk] = true; break;
case bAction : state[cAction] = true; break;
case bJump : state[cJump] = true; break;
case bInventory : state[cInventory] = true; break;
default : ;
}
}

View File

@ -383,10 +383,12 @@ struct Inventory {
bool ready = active && phaseRing == 1.0f && phasePage == 1.0f;
if (index == targetIndex && targetPage == page && ready && !chosen) {
if (Input::state[cLeft]) { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; }
if (Input::state[cRight]) { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; }
if (Input::state[cUp] && page < PAGE_ITEMS && getItemsCount(page + 1)) { phasePage = 0.0f; targetPage = Page(page + 1); }
if (Input::state[cDown] && page > PAGE_OPTION && getItemsCount(page - 1)) { phasePage = 0.0f; targetPage = Page(page - 1); }
float s = Input::touchTimerVis > 0.0f ? -1.0f : 1.0f;
if (Input::state[cLeft] || Input::joy.L.x < -0.5f || Input::joy.R.x > 0.5f) { phaseSelect = 0.0f; targetIndex = (targetIndex - 1 + count) % count; }
if (Input::state[cRight] || Input::joy.L.x > 0.5f || Input::joy.R.x < -0.5f) { phaseSelect = 0.0f; targetIndex = (targetIndex + 1) % count; }
if ((Input::state[cUp] || Input::joy.L.y < -0.5f || Input::joy.R.y > 0.5f) && page < PAGE_ITEMS && getItemsCount(page + 1)) { phasePage = 0.0f; targetPage = Page(page + 1); }
if ((Input::state[cDown] || Input::joy.L.y > 0.5f || Input::joy.R.y < -0.5f) && page > PAGE_OPTION && getItemsCount(page - 1)) { phasePage = 0.0f; targetPage = Page(page - 1); }
if (index != targetIndex) {
vec3 p;

View File

@ -408,7 +408,7 @@ struct Level : IGame {
data[i * 1024 + j].b = data[i * 1024 + j].g = ((i % 8 == 0) || (j % 8 == 0)) ? 0 : 255;
*/
atlas = new Texture(1024, 1024, Texture::RGBA, false, data);
atlas = new Texture(1024, 1024, Texture::RGBA, false, data, true, false); // TODO: generate mips
PROFILE_LABEL(TEXTURE, atlas->ID, "atlas");
uint32 whitePix = 0xFFFFFFFF;
@ -490,61 +490,97 @@ struct Level : IGame {
}
Core::active.shader->setParam(uMaterial, vec4(diffuse, ambient, specular, alpha));
if (Core::settings.detail.contact)
Core::active.shader->setParam(uContacts, Core::contacts[0], MAX_CONTACTS);
}
void renderRoom(int roomIndex) {
ASSERT(roomIndex >= 0 && roomIndex < level.roomsCount);
PROFILE_MARKER("ROOM");
TR::Room &room = level.rooms[roomIndex];
vec3 offset = vec3(float(room.info.x), 0.0f, float(room.info.z));
room.flags.visible = true;
// room geometry & sprites
if (Core::pass != Core::passShadow) {
Basis qTemp = Core::basis;
Core::basis.translate(offset);
MeshBuilder::RoomRange &range = mesh->rooms[roomIndex];
for (int transp = 0; transp < 2; transp++) {
if (!range.geometry[transp].iCount)
continue;
Core::setBlending(transp ? bmAlpha : bmNone);
setRoomParams(roomIndex, Shader::ROOM, 1.0f, intensityf(room.ambient), 0.0f, 1.0f, transp > 0);
Shader *sh = Core::active.shader;
sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS);
sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS);
sh->setParam(uBasis, Core::basis);
// render room geometry
mesh->renderRoomGeometry(roomIndex, transp > 0);
}
// render room sprites
if (range.sprites.iCount) {
Core::setBlending(bmAlpha);
setRoomParams(roomIndex, Shader::SPRITE, 1.0f, 1.0f, 0.0f, 1.0f, true);
Shader *sh = Core::active.shader;
sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS);
sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS);
sh->setParam(uBasis, Core::basis);
mesh->renderRoomSprites(roomIndex);
}
Core::basis = qTemp;
}
Core::setBlending(bmNone);
}
void setMainLight(Controller *controller) {
Core::lightPos[0] = controller->mainLightPos;
Core::lightColor[0] = vec4(controller->mainLightColor.xyz, 1.0f / controller->mainLightColor.w);
}
void renderRooms(int *roomsList, int roomsCount) {
PROFILE_MARKER("ROOMS");
for (int i = 0; i < level.roomsCount; i++)
level.rooms[i].flags.visible = false;
for (int i = 0; i < roomsCount; i++)
level.rooms[roomsList[i]].flags.visible = true;
if (Core::pass == Core::passShadow)
return;
if (Core::settings.detail.contact) {
Sphere spheres[MAX_CONTACTS];
int spheresCount;
lara->getSpheres(spheres, spheresCount);
for (int i = 0; i < MAX_CONTACTS; i++)
if (i < spheresCount)
Core::contacts[i] = vec4(spheres[i].center, PI * spheres[i].radius * spheres[i].radius * 0.25f);
else
Core::contacts[i] = vec4(0.0f);
}
setMainLight(lara);
bool hasGeom[2], hasSprite;
hasGeom[0] = hasGeom[1] = hasSprite = false;
Basis basis;
basis.identity();
Core::setBlending(bmNone);
for (int transp = 0; transp < 2; transp++) {
for (int i = 0; i < roomsCount; i++) {
int roomIndex = roomsList[i];
MeshBuilder::RoomRange &range = mesh->rooms[roomIndex];
if (!range.geometry[transp].iCount)
continue;
setRoomParams(roomIndex, Shader::ROOM, 1.0f, intensityf(level.rooms[roomIndex].ambient), 0.0f, 1.0f, transp > 0);
Shader *sh = Core::active.shader;
if (!hasGeom[transp]) {
hasGeom[transp] = true;
sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS);
sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS);
}
basis.pos = level.rooms[roomIndex].getOffset();
sh->setParam(uBasis, basis);
mesh->renderRoomGeometry(roomIndex, transp > 0);
}
Core::setBlending(bmAlpha);
}
basis.rot = Core::mViewInv.getRot();
for (int i = 0; i < roomsCount; i++) {
level.rooms[roomsList[i]].flags.visible = true;
int roomIndex = roomsList[i];
MeshBuilder::RoomRange &range = mesh->rooms[roomIndex];
if (!range.sprites.iCount)
continue;
setRoomParams(roomIndex, Shader::SPRITE, 1.0f, 1.0f, 0.0f, 1.0f, true);
Shader *sh = Core::active.shader;
if (!hasSprite) {
sh->setParam(uLightColor, Core::lightColor[0], MAX_LIGHTS);
sh->setParam(uLightPos, Core::lightPos[0], MAX_LIGHTS);
}
basis.pos = level.rooms[roomIndex].getOffset();
sh->setParam(uBasis, basis);
mesh->renderRoomSprites(roomIndex);
}
Core::setBlending(bmNone);
}
void renderEntity(const TR::Entity &entity) {
//if (entity.room != lara->getRoomIndex()) return;
if (entity.type == TR::Entity::NONE || !entity.modelIndex) return;
@ -568,8 +604,6 @@ struct Level : IGame {
type = Shader::MIRROR;
int roomIndex = entity.room;
// if (entity.type == TR::Entity::LARA && ((Lara*)entity.controller)->state == Lara::STATE_WATER_OUT)
// roomIndex = ((Lara*)entity.controller)->roomPrev;
setRoomParams(roomIndex, type, 1.0f, intensityf(lum), controller->specular, 1.0f, isModel ? !mesh->models[entity.modelIndex - 1].opaque : true);
@ -648,27 +682,6 @@ struct Level : IGame {
setupBinding();
}
void renderRooms(int *roomsList, int roomsCount) {
PROFILE_MARKER("ROOMS");
for (int i = 0; i < level.roomsCount; i++)
level.rooms[i].flags.visible = false;
setMainLight(lara);
#ifdef LEVEL_EDITOR
for (int i = 0; i < level.roomsCount; i++)
renderRoom(i);
#else
if (!camera->cutscene) {
for (int i = 0; i < roomsCount; i++)
renderRoom(roomsList[i]);
} else // TODO: use brain (track the camera room)
for (int i = 0; i < level.roomsCount; i++)
renderRoom(i);
#endif
}
void renderEntities() {
PROFILE_MARKER("ENTITIES");
for (int i = 0; i < level.entitiesCount; i++)
@ -756,6 +769,13 @@ struct Level : IGame {
}
void getVisibleRooms(int *roomsList, int &roomsCount, int from, int to, const vec4 &viewPort, bool water, int count = 0) {
if (camera->cutscene) {
roomsCount = level.roomsCount;
for (int i = 0; i < roomsCount; i++)
roomsList[i] = i;
return;
}
if (count > 16) {
ASSERT(false);
return;

View File

@ -22,7 +22,6 @@
E( uParam ) \
E( uTexParam ) \
E( uViewProj ) \
E( uViewInv ) \
E( uBasis ) \
E( uLightProj ) \
E( uMaterial ) \
@ -33,7 +32,8 @@
E( uAnimTexRanges ) \
E( uAnimTexOffsets ) \
E( uRoomSize ) \
E( uPosScale )
E( uPosScale ) \
E( uContacts )
enum AttribType { SHADER_ATTRIBS(DECL_ENUM) aMAX };
enum SamplerType { SHADER_SAMPLERS(DECL_ENUM) sMAX };

View File

@ -4,15 +4,32 @@ R"====(
precision highp float;
#endif
#ifdef OPT_CONTACT
varying vec3 vCoord;
#endif
varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
#if defined(OPT_WATER) && defined(UNDERWATER)
varying vec2 vCausticsCoord; // - xy caustics texture coord
#endif
#ifndef PASS_SHADOW
uniform vec3 uViewPos;
//uniform vec4 data[MAX_RANGES + MAX_OFFSETS + 4 + 4 + 1 + 1 + MAX_LIGHTS + MAX_LIGHTS + 1 + 6 + 1 + 32 * 2];
uniform vec2 uAnimTexRanges[MAX_RANGES];
uniform vec2 uAnimTexOffsets[MAX_OFFSETS];
uniform mat4 uLightProj;
uniform mat4 uViewProj;
uniform vec3 uViewPos;
uniform vec4 uParam; // x - time, y - water height, z - clip plane sign, w - clip plane height
uniform vec3 uLightPos[MAX_LIGHTS];
uniform vec4 uLightColor[MAX_LIGHTS]; // xyz - color, w - radius * intensity
uniform vec4 uRoomSize; // xy - minXZ, zw - maxXZ
uniform vec3 uAmbient[6];
uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha
uniform vec4 uBasis[32 * 2];
#ifndef PASS_SHADOW
varying vec4 vViewVec; // xyz - dir * dist, w - coord.y
varying vec4 vDiffuse;
@ -25,20 +42,11 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
#ifdef OPT_SHADOW
varying vec3 vAmbient;
#endif
uniform vec3 uLightPos[MAX_LIGHTS];
uniform vec4 uLightColor[MAX_LIGHTS]; // xyz - color, w - radius * intensity
#endif
varying vec4 vLight; // lights intensity (MAX_LIGHTS == 4)
#if defined(OPT_WATER) && defined(UNDERWATER)
uniform vec4 uRoomSize; // xy - minXZ, zw - maxXZ
#endif
#if defined(OPT_AMBIENT) && defined(TYPE_ENTITY)
uniform vec3 uAmbient[6];
vec3 calcAmbient(vec3 n) {
vec3 sqr = n * n;
vec3 pos = step(0.0, n);
@ -48,31 +56,9 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
}
#endif
#endif
uniform vec4 uParam; // x - time, y - water height, z - clip plane sign, w - clip plane height
uniform vec4 uMaterial; // x - diffuse, y - ambient, z - specular, w - alpha
#endif
#ifdef VERTEX
uniform mat4 uViewProj;
uniform mat4 uViewInv;
#ifndef PASS_AMBIENT
uniform mat4 uLightProj;
#endif
#ifdef PASS_COMPOSE
uniform vec2 uAnimTexRanges[MAX_RANGES];
uniform vec2 uAnimTexOffsets[MAX_OFFSETS];
#endif
#ifdef TYPE_ENTITY
uniform vec4 uBasis[32 * 2];
#else
uniform vec4 uBasis[2];
#endif
attribute vec4 aCoord;
attribute vec4 aTexCoord;
attribute vec4 aParam;
@ -85,8 +71,6 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
attribute vec4 aColor;
#endif
#define TEXCOORD_SCALE 32767.0
vec3 mulQuat(vec4 q, vec3 v) {
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + v * q.w);
}
@ -105,10 +89,12 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
vec4 rBasisPos = uBasis[1];
#endif
vec4 coord = vec4(mulBasis(rBasisRot, rBasisPos, aCoord.xyz), rBasisPos.w);
vec4 coord;
coord.w = rBasisPos.w; // visible flag
#ifdef TYPE_SPRITE
coord.xyz += (uViewInv[0].xyz * aTexCoord.z - uViewInv[1].xyz * aTexCoord.w) * TEXCOORD_SCALE;
coord.xyz = mulBasis(rBasisRot, rBasisPos + aCoord.xyz, vec3(aTexCoord.z, -aTexCoord.w, 0.0) * 32767.0);
#else
coord.xyz = mulBasis(rBasisRot, rBasisPos, aCoord.xyz);
#endif
#ifndef PASS_SHADOW
@ -137,6 +123,9 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
vLightVec.w = clamp(1.0 / exp(fog), 0.0, 1.0);
#endif
#ifdef OPT_CONTACT
vCoord = coord.xyz;
#endif
return coord;
}
@ -374,7 +363,7 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
#if defined(OPT_WATER) && defined(UNDERWATER)
float calcCaustics(vec3 n) {
vec2 cc = vCausticsCoord.xy;
vec2 cc = vCausticsCoord.xy;
vec2 border = vec2(256.0) / (uRoomSize.zw - uRoomSize.xy);
vec2 fade = smoothstep(vec2(0.0), border, cc) * (1.0 - smoothstep(vec2(1.0) - border, vec2(1.0), cc));
return texture2D(sReflect, cc).g * max(0.0, -n.y) * fade.x * fade.y;
@ -382,6 +371,21 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
#endif
#endif
#ifdef OPT_CONTACT
uniform vec4 uContacts[MAX_CONTACTS];
float getContactAO(vec3 p, vec3 n) {
float res = 1.0;
for (int i = 0; i < MAX_CONTACTS; i++) {
vec3 v = uContacts[i].xyz - p;
float a = uContacts[i].w;
float o = a * clamp(dot(n, v), 0.0, 1.0) / dot(v, v);
res *= clamp(1.0 - o, 0.0, 1.0);
}
return res;
}
#endif
void main() {
#ifdef PASS_COMPOSE
#ifdef CLIP_PLANE
@ -439,10 +443,16 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - trapezoidal correction
#endif
#ifdef TYPE_ROOM
light += mix(vAmbient.x, vLight.x, getShadow());
#if defined(OPT_WATER) && defined(UNDERWATER)
light += calcCaustics(n);
#endif
#ifdef OPT_CONTACT
light *= getContactAO(vCoord, n) * 0.5 + 0.5;
#endif
#endif
#ifdef TYPE_SPRITE

View File

@ -62,9 +62,7 @@ struct Sprite : Controller {
}
virtual void render(Frustum *frustum, MeshBuilder *mesh, Shader::Type type, bool caustics) {
Basis basis(Core::basis);
basis.translate(pos);
Core::active.shader->setParam(uBasis, basis);
Core::active.shader->setParam(uBasis, Basis(Core::mViewInv.getRot(), pos));
mesh->renderSprite(-(getEntity().modelIndex + 1), frame);
}
};

View File

@ -11,7 +11,7 @@ struct Texture {
Format format;
bool cube;
Texture(int width, int height, Format format, bool cube, void *data = NULL, bool filter = true) : cube(cube) {
Texture(int width, int height, Format format, bool cube, void *data = NULL, bool filter = true, bool mips = false) : cube(cube) {
if (!Core::support.texNPOT) {
width = nextPow2(width);
height = nextPow2(height);
@ -65,8 +65,9 @@ struct Texture {
float color[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, color);
}
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter ? (mips ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR ) : ( mips ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST ));
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter ? GL_LINEAR : GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter ? GL_LINEAR : GL_NEAREST);
struct FormatDesc {
GLuint ifmt, fmt;

View File

@ -602,7 +602,7 @@ struct mat4 {
quat getRot() const {
float t, s;
t = 1.0f + e00 + e11 + e22;
if (t > EPS) {
if (t > 0.0001f) {
s = 0.5f / sqrtf(t);
return quat((e21 - e12) * s, (e02 - e20) * s, (e10 - e01) * s, 0.25f / s);
} else
@ -657,7 +657,7 @@ struct mat4 {
};
struct Basis {
quat rot;
quat rot;
vec3 pos;
float w;