diff --git a/j3d/j3d.c b/j3d/j3d.c index d35f1a9..3256d48 100644 --- a/j3d/j3d.c +++ b/j3d/j3d.c @@ -49,11 +49,17 @@ segment "j3d"; // Module global data -static jint16 clipMinX = 0; -static jint16 clipMinY = 0; -static jint16 clipMaxX = 319; -static jint16 clipMaxY = 199; -static jint16 viewDistance = 250; +static jint16 clipMinX = 0; +static jint16 clipMinY = 0; +static jint16 clipMinZ = 100; +static jint16 clipMaxX = 319; +static jint16 clipMaxY = 199; +static jint16 clipMaxZ = 3000; +static jint16 viewDistance = 250; +static float ambientLight = 6; +static j3VertexT cameraLocation; +static j3FacingT cameraAngle; +static j3VertexT sunLocation; #define ASPECT_RATIO (float)0.8 @@ -63,6 +69,280 @@ static jint16 viewDistance = 250; #define HALF_SCREEN_HEIGHT 100 +void _j3DrawSolid(j3ObjectT *o) { + jint16 t; + juint16 vertex1; + juint16 vertex2; + juint16 vertex3; + float x1; + float y1; + float z1; + float x2; + float y2; + float z2; + float x3; + float y3; + float z3; + + for (t=0; ttriangleCount; t++) { + + // Is this triangle even visible? + if (!o->triangles[t].visible) { + continue; + } + + vertex1 = o->triangles[t].index[0]; + vertex2 = o->triangles[t].index[1]; + vertex3 = o->triangles[t].index[2]; + + z1 = o->verticies[vertex1].camera.z; + z2 = o->verticies[vertex2].camera.z; + z3 = o->verticies[vertex3].camera.z; + + // Perform z clipping + if ((z1 < clipMinZ && z2 < clipMinZ && z3 < clipMinZ) || (z1 > clipMaxZ && z2 > clipMaxZ && z3 > clipMaxZ)) { + continue; + } + + // Extract points of triangle + x1 = o->verticies[vertex1].camera.x; + y1 = o->verticies[vertex1].camera.y; + x2 = o->verticies[vertex2].camera.x; + y2 = o->verticies[vertex2].camera.y; + x3 = o->verticies[vertex3].camera.x; + y3 = o->verticies[vertex3].camera.y; + + // Screen position of points + x1 = HALF_SCREEN_WIDTH + x1 * viewDistance / z1; + y1 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y1 * viewDistance / z1; + x2 = HALF_SCREEN_WIDTH + x2 * viewDistance / z2; + y2 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y2 * viewDistance / z2; + x3 = HALF_SCREEN_WIDTH + x3 * viewDistance / z3; + y3 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y3 * viewDistance / z3; + + j3DrawTriangle2D((jint16)x1, (jint16)y1, (jint16)x2, (jint16)y2, (jint16)x3, (jint16)y3, o->triangles[t].shade); + } +} + + +void j3DrawTriangle2D(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3, jint16 color) { + jint16 tempX; + jint16 tempY; + jint16 newX; + + // Horizontal or vertical lines? + if ((x1 == x2 && x2 == x3) || (y1 == y2 && y2 == y3)) { + return; + } + + // Sort points in ascending Y order + if (y2 < y1) { + tempX = x2; + tempY = y2; + x2 = x1; + y2 = y1; + x1 = tempX; + y1 = tempY; + } + // p1 and p2 are now in order + if (y3 < y1) { + tempX = x3; + tempY = y3; + x3 = x1; + y3 = y1; + x1 = tempX; + y1 = tempY; + } + // Finally check y3 against y2 + if (y3 < y2) { + tempX = x3; + tempY = y3; + x3 = x2; + y3 = y2; + x2 = tempX; + y2 = tempY; + } // end if + + // Trivial rejection tests + if (y3 < clipMinY || y1 > clipMaxY || (x1 < clipMinX && x2 < clipMinX && x3 < clipMinX) || (x1 > clipMaxX && x2 > clipMaxX && x3 > clipMaxX)) { + return; + } + + jlDrawColor((byte)color); + + // Is top of triangle flat? + if (y1 == y2) { + _j3DrawTriangleTop(x1, y1, x2, y2, x3, y3); + } else if (y2 == y3) { + _j3DrawTriangleBottom(x1, y1, x2, y2, x3, y3); + } else { + // General triangle that's needs to be broken up along long edge + newX = x1 + (jint16)((float)(y2 - y1) * (float)(x3 - x1) / (float)(y3 - y1)); + // Draw each sub-triangle + _j3DrawTriangleBottom(x1, y1, newX, y2, x2, y2); + _j3DrawTriangleTop(x2, y2, newX, y2, x3, y3); + } +} + + +void _j3DrawTriangleBottom(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3) { + float dxRight; + float dxLeft; + float xs; + float xe; + float height; + jint16 tempX; + jint16 tempY; + jint16 right; + jint16 left; + + (void)y2; + + // Test order of x1 and x2 + if (x3 < x2) { + tempX = x2; + x2 = x3; + x3 = tempX; + } + + // Compute deltas + height = y3 - y1; + dxLeft = (x2 - x1) / height; + dxRight = (x3 - x1) / height; + + // Set starting points + xs = (float)x1; + xe = (float)x1 + (float)0.5; + + // Perform y clipping + if (y1 < clipMinY) { + // Compute new xs and ys + xs = xs + dxLeft * (float)(-y1 + clipMinY); + xe = xe + dxRight * (float)(-y1 + clipMinY); + // Reset y1 + y1 = clipMinY; + } + if (y3 > clipMaxY) { + y3 = clipMaxY; + } + + // Test if x clipping is needed + if (x1 >= clipMinX && x1 <= clipMaxX && x2 >= clipMinX && x2 <= clipMaxX && x3 >= clipMinX && x3 <= clipMaxX) { + // Draw the triangle + for (tempY=y1; tempY<=y3; tempY++) { + jlDrawLine((jint16)xs, tempY, (jint16)xe, tempY); + // Adjust starting point and ending point + xs += dxLeft; + xe += dxRight; + } + } else { + // Clip x axis with slower version + for (tempY=y1; tempY<=y3; tempY++) { + // Do x clip + left = (jint16)xs; + right = (jint16)xe; + // Adjust starting point and ending point + xs += dxLeft; + xe += dxRight; + // Clip line + if (left < clipMinX) { + left = clipMinX; + if (right < clipMinX) { + continue; + } + } + if (right > clipMaxX) { + right = clipMaxX; + if (left > clipMaxX) { + continue; + } + } + // Draw + jlDrawLine(left, tempY, right, tempY); + } + } +} + + +void _j3DrawTriangleTop(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3) { + float dxRight; + float dxLeft; + float xs; + float xe; + float height; + jint16 tempX; + jint16 tempY; + jint16 right; + jint16 left; + + (void)y2; + + // Test order of x1 and x2 + if (x2 < x1) { + tempX = x2; + x2 = x1; + x1 = tempX; + } + + // Compute deltas + height = y3 - y1; + dxLeft = (x3 - x1) / height; + dxRight = (x3 - x2) / height; + + // Set starting points + xs = (float)x1; + xe = (float)x2 + (float)0.5; + + // Perform y clipping + if (y1 < clipMinY) { + // Compute new xs and ys + xs = xs + dxLeft * (float)(-y1 + clipMinY); + xe = xe + dxRight * (float)(-y1 + clipMinY); + // Reset y1 + y1 = clipMinY; + } + if (y3 > clipMaxY) { + y3=clipMaxY; + } + + // Test if x clipping is needed + if (x1 >= clipMinX && x1 <= clipMaxX && x2 >= clipMinX && x2 <= clipMaxX && x3 >= clipMinX && x3 <= clipMaxX) { + // Draw the triangle + for (tempY=y1; tempY<=y3; tempY++) { + jlDrawLine((jint16)xs, tempY, (jint16)xe, tempY); + // Adjust starting point and ending point + xs += dxLeft; + xe += dxRight; + } + } else { + // Clip x axis + for (tempY=y1; tempY<=y3; tempY++) { + // Do x clip + left = (jint16)xs; + right = (jint16)xe; + // Adjust starting point and ending point + xs += dxLeft; + xe += dxRight; + // Clip line + if (left < clipMinX) { + left = clipMinX; + if (right < clipMinX) { + continue; + } + } + if (right > clipMaxX) { + right = clipMaxX; + if (left > clipMaxX) { + continue; + } + } + // Draw + jlDrawLine((jint16)left, tempY, (jint16)right, tempY); + } + } +} + + void _j3DrawWireframePair(j3ObjectT *o, juint16 v1, juint16 v2) { float x1; float y1; @@ -114,6 +394,20 @@ void _j3DrawWireframe(j3ObjectT *o) { } +void j3MathCrossProduct3D(j3Vector3DT *u, j3Vector3DT *v, j3Vector3DT *normal) { + // Compute the cross product between two vectors + normal->x = (u->y*v->z - u->z*v->y); + normal->y = -(u->x*v->z - u->z*v->x); + normal->z = (u->x*v->y - u->y*v->x); +} + + +float j3MathDotProduct3D(j3Vector3DT *u, j3Vector3DT *v) { + // Compute the dot product of two vectors + return( (u->x * v->x) + (u->y * v->y) + (u->z * v->z)); +} + + void j3MathMatrix4x4Identity(j3Matrix4x4T result) { result[0][0] = 1; result[0][1] = 0; result[0][2] = 0; result[0][3] = 0; result[1][0] = 0; result[1][1] = 1; result[1][2] = 0; result[1][3] = 0; @@ -145,6 +439,20 @@ void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result) { } +void j3MathMakeVector3D(j3VertexT *init, j3VertexT *term, j3Vector3DT *result) { + // Create a vector from two points in 3D space + result->x = term->x - init->x; + result->y = term->y - init->y; + result->z = term->z - init->z; +} + + +float j3MathVectorMagnatude3D(j3Vector3DT *v) { + // Compute the magnitude of a vector + return((float)sqrt((double)(v->x * v->x + v->y * v->y + v->z * v->z))); +} + + void _j3ObjectReset(j3ObjectT *o) { o->position.x = 0; o->position.y = 0; @@ -165,7 +473,16 @@ void _j3ObjectUpdate(j3ObjectT *o) { jint16 y; jint16 z; jint16 i; - byte axis = 0; + juint16 vertex0; + juint16 vertex1; + juint16 vertex2; + float dot; + float intensity; + j3Vector3DT u; + j3Vector3DT v; + j3Vector3DT normal; + j3Vector3DT sight; + byte axis = 0; j3Matrix4x4T rotateX; j3Matrix4x4T rotateY; j3Matrix4x4T rotateZ; @@ -326,6 +643,103 @@ void _j3ObjectUpdate(j3ObjectT *o) { for (i=0; ivertexCount; i++) { o->verticies[i].camera = o->verticies[i].world; } + + // === REMOVE BACKFACES & LIGHT === + + for (i=0; itriangleCount; i++) { + // If it's two sided, there are no backfaces + if (!o->triangles[i].twoSided) { + + // Compute two vectors on polygon that have the same intial points + vertex0 = o->triangles[i].index[0]; + vertex1 = o->triangles[i].index[1]; + vertex2 = o->triangles[i].index[2]; + // Vector u = v0->v1 + j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex1].world, (j3Vector3DT *)&u); + // Vector v = v0->v2 + j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex2].world, (j3Vector3DT *)&v); + // Normal v x u + j3MathCrossProduct3D((j3Vector3DT *)&v, (j3Vector3DT *)&u, (j3Vector3DT *)&normal); + // Compute the line of sight vector, since all coordinates are world all + // object vertices are already relative to (0,0,0), thus + sight.x = cameraLocation.x - o->verticies[vertex0].world.x; + sight.y = cameraLocation.y - o->verticies[vertex0].world.y; + sight.z = cameraLocation.z - o->verticies[vertex0].world.z; + // Compute the dot product between line of sight vector and normal to surface + dot = j3MathDotProduct3D((j3Vector3DT *)&normal, (j3Vector3DT *)&sight); + + // Is the surface visible + if (dot > 0) { + // Set visible flag + o->triangles[i].visible = true; + + // Compute light intensity if needed + if (o->triangles[i].lit) { + // Compute the dot product between the light source vector and normal vector to surface + dot = j3MathDotProduct3D((j3Vector3DT *)&normal, (j3Vector3DT *)&sunLocation); + // Test if light ray is reflecting off surface + if (dot > 0) { + intensity = ambientLight + (dot * (o->triangles[i].normalLength)); + // Test if intensity has overflowed + if (intensity > 15) { + intensity = 15; + } + // Intensity now varies from 0-1, 0 being black or grazing and 1 being + // totally illuminated. use the value to index into color table + o->triangles[i].shade = o->triangles[i].color - (jint16)intensity; + } else { + o->triangles[i].shade = o->triangles[i].color - (jint16)ambientLight; + } + } else { + // Constant shading - simply assign color to shade + o->triangles[i].shade = o->triangles[i].color; + } + } else { + o->triangles[i].visible = false; + } + + } else { + + // Triangle is always visible i.e. two sided + o->triangles[i].visible = true; + + // Perform shading calculation + if (o->triangles[i].lit) { + + // Compute two vectors on polygon that have the same intial points + vertex0 = o->triangles[i].index[0]; + vertex1 = o->triangles[i].index[1]; + vertex2 = o->triangles[i].index[2]; + // Vector u = v0->v1 + j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex1].world, (j3Vector3DT *)&u); + // Vector v = v0->v2 + j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex2].world, (j3Vector3DT *)&v); + // Normal v x u + j3MathCrossProduct3D((j3Vector3DT *)&v, (j3Vector3DT *)&u, (j3Vector3DT *)&normal); + // Compute the dot product between the light source vector and normal vector to surface + dot = j3MathDotProduct3D((j3Vector3DT *)&normal, (j3Vector3DT *)&sunLocation); + + // Is light ray is reflecting off surface + if (dot > 0) { + // cos 0 = (u.v)/|u||v| or + intensity = ambientLight + (dot * (o->triangles[i].normalLength)); + // test if intensity has overflowed + if (intensity > 15) { + intensity = 15; + } + // intensity now varies from 0-1, 0 being black or grazing and 1 being + // totally illuminated. use the value to index into color table + o->triangles[i].shade = o->triangles[i].color - (jint16)intensity; + } else { + o->triangles[i].shade = o->triangles[i].color - (jint16)ambientLight; + } + } else { + // Constant shading and simply assign color to shade + o->triangles[i].shade = o->triangles[i].color; + } + } + + } } @@ -524,7 +938,17 @@ void j3UtilShutdown(void) { void j3UtilStartup(void) { - // Nothing yet + cameraLocation.x = 0; + cameraLocation.y = 0; + cameraLocation.z = 0; + + cameraAngle.x = 0; + cameraAngle.y = 0; + cameraAngle.z = 0; + + sunLocation.x = (float)-0.913913; + sunLocation.y = (float) 0.389759; + sunLocation.z = (float)-0.113369; } @@ -552,6 +976,12 @@ bool _j3WorldLoad(j3WorldT **world, char *file) { jint16 x; jint16 y; jint16 z; + juint16 vertex0; + juint16 vertex1; + juint16 vertex2; + j3Vector3DT u; + j3Vector3DT v; + j3Vector3DT normal; byte buffer[4]; FILE *in; bool failed = false; @@ -585,21 +1015,19 @@ bool _j3WorldLoad(j3WorldT **world, char *file) { if (fread(&(*world)->objectCount, sizeof(juint16), 1, in) == 1) { // Allocate memory for objects - (*world)->objects = (j3ObjectT *)jlMalloc(sizeof(j3ObjectT)); + (*world)->objects = (j3ObjectT *)jlMalloc(sizeof(j3ObjectT) * (*world)->objectCount); if ((*world)->objects) { // Iterate across objects in file for (x=0; x<(*world)->objectCount; x++) { - // Allocate memory for object elements - (*world)->objects->verticies = (j3CoordinatesT *)jlMalloc(sizeof(j3CoordinatesT)); - (*world)->objects->triangles = (j3TriangleT *)jlMalloc(sizeof(j3TriangleT)); - if ((*world)->objects->verticies && (*world)->objects->triangles) { + // Get vertex count + if (fread(&(*world)->objects[x].vertexCount, sizeof(juint16), 1, in) == 1) { - _j3ObjectReset(&(*world)->objects[x]); + // Allocate memory for vertex data + (*world)->objects->verticies = (j3CoordinatesT *)jlMalloc(sizeof(j3CoordinatesT) * (*world)->objects[x].vertexCount); + if ((*world)->objects->verticies) { - // Get vertex count - if (fread(&(*world)->objects[x].vertexCount, sizeof(juint16), 1, in) == 1) { // Iterate and read verticies for (y=0; y<(*world)->objects[x].vertexCount; y++) { // Read one at a time in case the struct gets padded @@ -618,9 +1046,19 @@ bool _j3WorldLoad(j3WorldT **world, char *file) { } } - if (!failed) { - // Get triangle count - if (fread(&(*world)->objects[x].triangleCount, sizeof(juint16), 1, in) == 1) { + } else { + // Failed to get vertex memory + failed = true; + } + + if (!failed) { + // Get triangle count + if (fread(&(*world)->objects[x].triangleCount, sizeof(juint16), 1, in) == 1) { + + // Allocate memory for triangle data + (*world)->objects->triangles = (j3TriangleT *)jlMalloc(sizeof(j3TriangleT) * (*world)->objects[x].triangleCount); + if ((*world)->objects->triangles) { + // Iterate and read triangles for (y=0; y<(*world)->objects[x].triangleCount; y++) { // Read one at a time in case the struct gets padded @@ -631,11 +1069,38 @@ bool _j3WorldLoad(j3WorldT **world, char *file) { } } if (failed) break; - } - } - } - } // vertex & triangle alloc + // Compute length of the two co-planer edges of the polygon, since they will be used in the computation of the dot-product later + vertex0 = (*world)->objects[x].triangles[y].index[0]; + vertex1 = (*world)->objects[x].triangles[y].index[1]; + vertex2 = (*world)->objects[x].triangles[y].index[2]; + j3MathMakeVector3D((j3VertexT *)&(*world)->objects[x].verticies[vertex0].local, (j3VertexT *)&(*world)->objects[x].verticies[vertex1].local, (j3Vector3DT *)&u); + j3MathMakeVector3D((j3VertexT *)&(*world)->objects[x].verticies[vertex0].local, (j3VertexT *)&(*world)->objects[x].verticies[vertex2].local, (j3Vector3DT *)&v); + j3MathCrossProduct3D((j3Vector3DT *)&v, (j3Vector3DT *)&u, (j3Vector3DT *)&normal); + + // Compute magnitude of normal, take its inverse and multiply it by + // 15, this will change the shading calculation of 15*dp/normal into + // dp*normal_length, removing one division + (*world)->objects[x].triangles[y].normalLength = (float)15.0 / j3MathVectorMagnatude3D((j3Vector3DT *)&normal); + + //***TODO*** All triangles are one-sided for now + (*world)->objects[x].triangles[y].twoSided = false; + } + + } else { + // Failed to get triangle memory + failed = true; + } + + } + } + + if (!failed) { + _j3ObjectReset(&(*world)->objects[x]); + } else { + break; // Stop iterating + } + } // object iterator } // objects alloc } // Object count diff --git a/j3d/j3d.h b/j3d/j3d.h index 8e1fc99..5f51393 100644 --- a/j3d/j3d.h +++ b/j3d/j3d.h @@ -27,13 +27,22 @@ #include "joey.h" +//***TODO*** verticies is vertices + + typedef float j3Matrix4x4T[4][4]; +typedef struct { + jint16 x; + jint16 y; + jint16 z; +} j3FacingT; + typedef struct { float x; float y; float z; -} j3VertexT; +} j3VertexT, j3Vector3DT; typedef struct { j3VertexT local; // Original vertex positions @@ -42,7 +51,13 @@ typedef struct { } j3CoordinatesT; typedef struct { - juint16 index[3]; // We do this instead of just a typedef so it works with stretch_buffer + jint16 color; // Assigned color of this face + jint16 shade; // Color of this face after lighting + bool lit; // Do we care about lighting? + bool visible; // Can we see this triangle? + bool twoSided; // Are both sides visible? + float normalLength; // Magnatude of surface normal + juint16 index[3]; // We do this instead of just a typedef so it works with stretch_buffer } j3TriangleT; typedef struct { @@ -107,6 +122,7 @@ typedef struct { // Syntatic sugar +#define j3DrawSolid(o) _j3DrawSolid(&(o)) #define j3DrawWireframe(o) _j3DrawWireframe(&(o)) #define j3ObjectReset(o) _j3ObjectReset(&(o)) #define j3ObjectUpdate(o) _j3ObjectUpdate(&(o)) @@ -115,13 +131,21 @@ typedef struct { // Prototypes -void j3MathMatrix4x4Identity(j3Matrix4x4T result); -void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result); -void j3UtilShutdown(void); -void j3UtilStartup(void); +void j3DrawTriangle2D(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3, jint16 color); +void j3MathCrossProduct3D(j3Vector3DT *u, j3Vector3DT *v, j3Vector3DT *normal); +float j3MathDotProduct3D(j3Vector3DT *u, j3Vector3DT *v); +void j3MathMatrix4x4Identity(j3Matrix4x4T result); +void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result); +void j3MathMakeVector3D(j3VertexT *init, j3VertexT *term, j3Vector3DT *result); +float j3MathVectorMagnatude3D(j3Vector3DT *v); +void j3UtilShutdown(void); +void j3UtilStartup(void); // Private Prototypes +void _j3DrawSolid(j3ObjectT *o); +void _j3DrawTriangleBottom(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3); +void _j3DrawTriangleTop(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3); void _j3DrawWireframePair(j3ObjectT *o, juint16 v1, juint16 v2); void _j3DrawWireframe(j3ObjectT *o); void _j3ObjectReset(j3ObjectT *o); diff --git a/j3d/main.c b/j3d/main.c index 2fcc3c6..f5c692d 100644 --- a/j3d/main.c +++ b/j3d/main.c @@ -59,6 +59,8 @@ void printAt(jlStaT *font, jint16 cx, jint16 cy, const char *what, ...) { int main(void) { jint16 x; + jint16 y; + jint16 c; jlStaT *font = NULL; j3WorldT *world = NULL; bool r; @@ -81,8 +83,18 @@ int main(void) { printAt(font, 1, 1, "Object loading: Success!"); jlDisplayPresent(); + // Assign fake colors until we can read them from the J3D + c = 1; + for (x=0; xobjectCount; x++) { + for (y=0; yobjects[x].triangleCount; y++) { + world->objects[x].triangles->color = c++; + if (c > 15) { + c = 1; + } + } + } + #ifdef JOEY_LINUX - jint16 y; printf("Objects: %d\n", world->objectCount); for (x=0; xobjectCount; x++) { printf("Object %d:\n", x); @@ -121,7 +133,8 @@ int main(void) { for (x=0; xobjectCount; x++) { j3ObjectRotate(world->objects[x], 2, 4, 6); // Matching values in code I'm studying j3ObjectUpdate(world->objects[x]); - j3DrawWireframe(world->objects[x]); + //j3DrawWireframe(world->objects[x]); + j3DrawSolid(world->objects[x]); } printAt(font, 1, 1, "Verticies: %d", world->objects[0].vertexCount);