#include "stdafx.h" #include "GeometryGenerator.h" #include "GfxBase/AsyncResult.h" #include "GfxBase/PartIdentifier.h" #include "humanoid/Humanoid.h" #include "VisualEngine.h" #include "GfxBase/RenderCaps.h" #include "GfxCore/Device.h" #include "util/MeshId.h" #include "Util.h" #include "util/Lcmrand.h" #include "util/NormalId.h" #include "v8datamodel/CharacterMesh.h" #include "v8datamodel/SpecialMesh.h" #include "v8datamodel/FileMesh.h" #include "v8datamodel/ContentProvider.h" #include "v8datamodel/MeshContentProvider.h" #include "v8datamodel/BlockMesh.h" #include "v8datamodel/CylinderMesh.h" #include "v8datamodel/Decal.h" #include "v8datamodel/ExtrudedPartInstance.h" #include "v8datamodel/PartCookie.h" #include "v8datamodel/Lighting.h" #include "v8world/Primitive.h" #include "V8DataModel/PartOperation.h" #include "V8DataModel/PartOperationAsset.h" #include "V8DataModel/SolidModelContentProvider.h" #include "g3d/g3dmath.h" #include "GfxBase/FileMeshData.h" #include "GfxBase/GfxPart.h" #include "MaterialGenerator.h" #include "V8World/TriangleMesh.h" #include "BulletCollision/CollisionShapes/btConvexHullShape.h" #include "BulletCollision/CollisionShapes/btShapeHull.h" #include "BulletCollision/CollisionShapes/btConvexPolyhedron.h" #include "Extras/GIMPACTUtils/btGImpactConvexDecompositionShape.h" FASTFLAGVARIABLE(NoRandomColorsWithoutOutlines, true) FASTFLAGVARIABLE(FixMeshOffset, false) FASTFLAG(FixGlowingCSG) FASTFLAG(StudioCSGAssets) FASTFLAG(CSGLoadBlocking) FASTFLAG(CSGPhysicsLevelOfDetailEnabled) #define TESSELATION_PIECE 8.0f #define MAX_TESSELATION 50.0f #define NO_OUTLINES 10.0f namespace RBX { namespace Graphics { static const boost::shared_ptr kDummyMeshData(new FileMeshData()); static const float kNormalOffsetScale = 0.02f; static const Vector3 kNormalOffsetTable[32] = { Vector3(-0.5237f, +0.7208f, -0.5878f) * kNormalOffsetScale, Vector3(-0.5590f, +0.7694f, -0.5878f) * kNormalOffsetScale, Vector3(-0.2668f, +0.3673f, -0.5878f) * kNormalOffsetScale, Vector3(+0.1816f, -0.2500f, -0.5878f) * kNormalOffsetScale, Vector3(-0.8800f, +0.1394f, -0.9877f) * kNormalOffsetScale, Vector3(-0.9393f, +0.1488f, -0.9877f) * kNormalOffsetScale, Vector3(-0.4484f, +0.0710f, -0.9877f) * kNormalOffsetScale, Vector3(+0.3052f, -0.0483f, -0.9877f) * kNormalOffsetScale, Vector3(-0.7208f, -0.5237f, -0.8090f) * kNormalOffsetScale, Vector3(-0.7694f, -0.5590f, -0.8090f) * kNormalOffsetScale, Vector3(-0.3673f, -0.2668f, -0.8090f) * kNormalOffsetScale, Vector3(+0.2500f, +0.1816f, -0.8090f) * kNormalOffsetScale, Vector3(-0.1394f, -0.8800f, -0.1564f) * kNormalOffsetScale, Vector3(-0.1488f, -0.9393f, -0.1564f) * kNormalOffsetScale, Vector3(-0.0710f, -0.4484f, -0.1564f) * kNormalOffsetScale, Vector3(+0.0483f, +0.3052f, -0.1564f) * kNormalOffsetScale, Vector3(+0.5237f, -0.7208f, +0.5878f) * kNormalOffsetScale, Vector3(+0.5590f, -0.7694f, +0.5878f) * kNormalOffsetScale, Vector3(+0.2668f, -0.3673f, +0.5878f) * kNormalOffsetScale, Vector3(-0.1816f, +0.2500f, +0.5878f) * kNormalOffsetScale, Vector3(+0.8800f, -0.1394f, +0.9877f) * kNormalOffsetScale, Vector3(+0.9393f, -0.1488f, +0.9877f) * kNormalOffsetScale, Vector3(+0.4484f, -0.0710f, +0.9877f) * kNormalOffsetScale, Vector3(-0.3052f, +0.0483f, +0.9877f) * kNormalOffsetScale, Vector3(+0.7208f, +0.5237f, +0.8090f) * kNormalOffsetScale, Vector3(+0.7694f, +0.5590f, +0.8090f) * kNormalOffsetScale, Vector3(+0.3673f, +0.2668f, +0.8090f) * kNormalOffsetScale, Vector3(-0.2500f, -0.1816f, +0.8090f) * kNormalOffsetScale, Vector3(+0.1394f, +0.8800f, +0.1564f) * kNormalOffsetScale, Vector3(+0.1488f, +0.9393f, +0.1564f) * kNormalOffsetScale, Vector3(+0.0710f, +0.4484f, +0.1564f) * kNormalOffsetScale, Vector3(-0.0483f, -0.3052f, +0.1564f) * kNormalOffsetScale, }; static const float kUniformRandomTable[64] = { 0.827323f, 0.526485f, 0.916254f, 0.225426f, 0.969743f, 0.069476f, 0.423928f, 0.794976f, 0.629325f, 0.707109f, 0.997080f, 0.371298f, 0.537122f, 0.692187f, 0.309150f, 0.207611f, 0.347299f, 0.680269f, 0.854916f, 0.321542f, 0.848045f, 0.825441f, 0.699820f, 0.365232f, 0.391758f, 0.982131f, 0.961126f, 0.854716f, 0.847131f, 0.427982f, 0.901692f, 0.705821f, 0.230473f, 0.367524f, 0.324393f, 0.797571f, 0.048550f, 0.245296f, 0.805075f, 0.484867f, 0.740669f, 0.375044f, 0.792494f, 0.275678f, 0.915932f, 0.376178f, 0.625879f, 0.648950f, 0.213511f, 0.454967f, 0.172181f, 0.734572f, 0.339370f, 0.280913f, 0.053203f, 0.001882f, 0.826664f, 0.551022f, 0.833667f, 0.987611f, 0.108350f, 0.569212f, 0.947845f, 0.201343f, }; struct UVAtlasInfo { bool tileX; float scaleY, offsetY; UVAtlasInfo(bool tileX, float scaleY, float offsetY): tileX(tileX), scaleY(scaleY), offsetY(offsetY) { } }; struct FACEDESC { unsigned start0, start1; unsigned end0, end1; bool dirZ; bool tangentZ; }; inline float transformUV(float v, float scale, float offset) { const float threshold = 1e-2f; if (v < -threshold || v > 1.f + threshold) return (v - floorf(v)) * scale + offset; else return v * scale + offset; } inline Vector2 transformUV(const Vector2& uv, const GeometryGenerator::Options& options) { return (options.flags & GeometryGenerator::Flag_TransformUV) ? Vector2(transformUV(uv.x, options.uvframe.z, options.uvframe.x), transformUV(uv.y, options.uvframe.w, options.uvframe.y)) : uv; } inline UVAtlasInfo getStudsAtlasInfo(SurfaceType type) { switch(type) { case STUDS: return UVAtlasInfo(true, 0.25f, 0.0f); break; case WELD: return UVAtlasInfo(true, 0.25f, 0.25f); break; case INLET: return UVAtlasInfo(true, 0.25f, 0.5f); break; case UNIVERSAL: return UVAtlasInfo(true, 0.25f, 0.75f); break; case GLUE: return UVAtlasInfo(true, 0.25f, 0.25f); break; default: return UVAtlasInfo(false, 0.0f, 0.0f); } } #ifdef _MSC_VER __forceinline #else inline #endif void fillVertex( GeometryGenerator::Vertex& r, const Vector3& pos, const Vector3& normal, const Vector2& uv, const Vector2& uvStuds, const Color4uint8& color, const Color4uint8& extra, const Vector3& tangent, const Vector4& edgeDistances = Vector4(NO_OUTLINES, NO_OUTLINES, NO_OUTLINES, NO_OUTLINES)) { r.pos = pos; r.normal = normal; r.uv = uv; r.uvStuds = uvStuds; r.color = color; r.extra = extra; r.tangent = tangent; r.edgeDistances = edgeDistances; } inline void fillQuadIndices(unsigned short* r, size_t offset, unsigned int i00, unsigned int i10, unsigned int i01, unsigned int i11) { r[0] = offset + i00; r[1] = offset + i10; r[2] = offset + i01; r[3] = offset + i01; r[4] = offset + i10; r[5] = offset + i11; } inline void extendBounds(Vector3& boundsMin, Vector3& boundsMax, const Vector3& pos) { boundsMin = boundsMin.min(pos); boundsMax = boundsMax.max(pos); } inline void extendBoundsBlock(Vector3& boundsMin, Vector3& boundsMax, const Vector3& center, const Vector3& size, const Vector3& axisX, const Vector3& axisY, const Vector3& axisZ ) { Vector3 extent = size * 0.5f; Vector3 boundsExtent = Vector3( fabsf(axisX.x) * extent.x + fabsf(axisY.x) * extent.y + fabsf(axisZ.x) * extent.z, fabsf(axisX.y) * extent.x + fabsf(axisY.y) * extent.y + fabsf(axisZ.y) * extent.z, fabsf(axisX.z) * extent.x + fabsf(axisY.z) * extent.y + fabsf(axisZ.z) * extent.z); boundsMin = boundsMin.min(center - boundsExtent); boundsMax = boundsMax.max(center + boundsExtent); } // We have 1 default non-mesh head and 16 non-mesh heads that you can buy // All future heads will be mesh heads; but we have to support legacy heads as well // They were baked once into mesh files (with decal UVs and some vertex welding). // Here's a table of cylinder head meshes: id, bevel, bevel roundness, bulge, LODx, LODy, offset, scale // A 0 0 0.5 1 2 0,0.1,0 1.25,1.25,1.25 // C 0.1 0 0.5 1 2 0,0.1,0 1.25,1.25,1.25 // D 0.4 0 0 2 2 0,0.05,0 1.4,1.4,1.35 // E 0.66 0 0.5 2 2 0,0.05,0 1.4,1.6,1.35 // F 0 0 0 1 0 0,0.1,0 1.25,1.25,1.25 // G 0 0 1 2 2 0,0.1,0 1.25,1.25,1.25 // H 0.2 0 1 2 2 0,0.1,0 1.3,1.3,1.3 // N 0.4 1 0 2 2 0,0.05,0 1.4,1.4,1.35 // O 0.2 0 0.5 1 2 0,0.1,0 1.3,1.3,1.3 // P 0.1 0 0 2 2 0,0.1,0 1.25,1.25,1.25 // Here's a table of block head meshes: id, bevel, bevel roundness, bulge, LODx, LODy, offset, scale // B 0 0 0 2 2 0,0.05,0 0.56,1.1,1.1 // I 0.5 0 0 2 2 0,0.05,0 0.8,1.3,1.2 // J 0.3 0 0 2 2 0,0.1,0 0.63,1.3,1.1 // M 0.05 0 0 2 2 0,0.05,0 0.56,1.1,1.1 static MeshId getHeadMeshId(PartInstance* part, DataModelMesh* specialShape) { // Note that bevel, roundness and bulge are unique enough to determine the cylinder/block variant, so we'll use just that #define LOOKUP(ifBevel, ifRoundness, ifBulge, head) \ if (G3D::fuzzyEq(bevel, ifBevel) && G3D::fuzzyEq(roundness, ifRoundness) && G3D::fuzzyEq(bulge, ifBulge)) return MeshId("rbxasset://fonts/head" head ".mesh") if (specialShape) { if (SpecialShape* shape = specialShape->fastDynamicCast()) { if (shape->getMeshType() == SpecialShape::HEAD_MESH) { // Return the default head; scaling is taken care of in getMeshScale return MeshId("rbxasset://fonts/head.mesh"); } else if (shape->getMeshType() == SpecialShape::SPHERE_MESH) { // Return the sphere head; scaling is taken care of in getMeshScale return MeshId("rbxasset://fonts/headL.mesh"); } } else if (CylinderMesh* shape = specialShape->fastDynamicCast()) { float bevel = shape->getBevel(), roundness = shape->getRoundness(), bulge = shape->getBulge(); LOOKUP(0.0, 0.0, 0.5, "A"); LOOKUP(0.1, 0.0, 0.5, "C"); LOOKUP(0.4, 0.0, 0.0, "D"); LOOKUP(0.66, 0.0, 0.5, "E"); LOOKUP(0.0, 0.0, 0.0, "F"); LOOKUP(0.0, 0.0, 1.0, "G"); LOOKUP(0.2, 0.0, 1.0, "H"); LOOKUP(0.4, 1.0, 0.0, "N"); LOOKUP(0.2, 0.0, 0.5, "O"); LOOKUP(0.1, 0.0, 0.0, "P"); } else if (BlockMesh* shape = specialShape->fastDynamicCast()) { float bevel = shape->getBevel(), roundness = shape->getRoundness(), bulge = shape->getBulge(); LOOKUP(0.0, 0.0, 0.0, "B"); LOOKUP(0.5, 0.0, 0.0, "I"); LOOKUP(0.3, 0.0, 0.0, "J"); LOOKUP(0.05, 0.0, 0.0, "M"); } } // fallback for all other head types return MeshId("rbxasset://fonts/head.mesh"); #undef LOOKUP } static MeshId getMeshIdForBodyPart(const HumanoidIdentifier& hi, PartInstance* part, DataModelMesh* specialShape, unsigned int flags) { if (CharacterMesh* charmesh = hi.getRelevantMesh(part)) return charmesh->getMeshId(); else if (part == hi.torso && (flags & GeometryGenerator::Resource_SubstituteBodyParts)) return MeshId("rbxasset://fonts/torso.mesh"); else if (part == hi.leftArm && (flags & GeometryGenerator::Resource_SubstituteBodyParts)) return MeshId("rbxasset://fonts/leftarm.mesh"); else if (part == hi.rightArm && (flags & GeometryGenerator::Resource_SubstituteBodyParts)) return MeshId("rbxasset://fonts/rightarm.mesh"); else if (part == hi.leftLeg && (flags & GeometryGenerator::Resource_SubstituteBodyParts)) return MeshId("rbxasset://fonts/leftleg.mesh"); else if (part == hi.rightLeg && (flags & GeometryGenerator::Resource_SubstituteBodyParts)) return MeshId("rbxasset://fonts/rightleg.mesh"); else if (part == hi.head && (flags & GeometryGenerator::Resource_SubstituteBodyParts)) return getHeadMeshId(part, specialShape); else return MeshId(); } static float getPartTransparency(PartInstance* part) { // Reflectance affects Transparency for plastic parts - part with Transparency at 0.99 and Reflectance at 1 should still be somewhat visible return (part->getRenderMaterial() == PLASTIC_MATERIAL) ? part->getTransparencyUi() * (1 - getPartReflectance(part)) : part->getTransparencyUi(); } static Color4uint8 getBaseColor(PartInstance* part, Decal* decal, DataModelMesh* specialShape, const GeometryGenerator::Options& options) { float transparency = decal ? decal->getTransparencyUi() : getPartTransparency(part); unsigned char alpha = 255 - static_cast(G3D::clamp(transparency, 0.f, 1.f) * 255.f); // Decal never takes part/mesh colors if (decal) return Color4uint8(255, 255, 255, alpha); // Requested colors from mesh if (options.flags & GeometryGenerator::Flag_VertexColorMesh) { FileMesh* fileMesh = Instance::fastDynamicCast(specialShape); if (fileMesh && !fileMesh->getTextureId().isNull()) return Color4uint8(Color3uint8(Color3(specialShape->getVertColor())), alpha); else return Color4uint8(255, 255, 255, alpha); } // Requested colors from part if (options.flags & GeometryGenerator::Flag_VertexColorPart) return Color4uint8(part->getColor().color3uint8(), alpha); return Color4uint8(255, 255, 255, alpha); } static Color4uint8 getColor(PartInstance* part, Decal* decal, DataModelMesh* specialShape, const GeometryGenerator::Options& options, unsigned int randomSeed, bool isBlock) { Color4uint8 diffuse = getBaseColor(part, decal, specialShape, options); Color4uint8 color = (isBlock && (options.flags & GeometryGenerator::Flag_RandomizeBlockAppearance)) ? Color4uint8( (diffuse.r & 0xf8) | (randomSeed & 7), (diffuse.g & 0xf8) | (randomSeed & 7), (diffuse.b & 0xf8) | (randomSeed & 7), diffuse.a) : diffuse; return (options.flags & GeometryGenerator::Flag_DiffuseColorBGRA) ? Color4uint8(color.b, color.g, color.r, color.a) : color; } static Color4uint8 getExtra(PartInstance* part, const GeometryGenerator::Options& options) { Vector2int16 specular = MaterialGenerator::getSpecular(part->getRenderMaterial()); int reflectance = getPartReflectance(part) * 255; return (options.flags & GeometryGenerator::Flag_ExtraIsZero) ? Color4uint8(0, 0, 0, 0) : Color4uint8(options.extra, specular.x, specular.y, reflectance); } static Vector3 getMeshScale(PartInstance* part, DataModelMesh* specialShape, const HumanoidIdentifier* humanoidIdentifier) { if (specialShape) { // File meshes don't inherit part size and just use custom scale if (part->getCookie() & PartCookie::HAS_FILEMESH) return specialShape->getScale(); // If it's not a file mesh, it must be a substituted body part. // These have a constant scale with the exception of the head mesh and sphere mesh if (SpecialShape* shape = specialShape->fastDynamicCast()) { if (shape->getMeshType() == SpecialShape::HEAD_MESH) { // For heads, the scale works separately for XZ and Y Vector3 size = part->getPartSizeXml() * shape->getScale(); float scaleXZ = std::min(size.x, size.z); float scaleY = size.y; // Default head is baked with uniform scale 1.25 return Vector3(scaleXZ, scaleY, scaleXZ) / 1.25f; } else if (shape->getMeshType() == SpecialShape::SPHERE_MESH) { Vector3 size = part->getPartSizeXml() * shape->getScale(); // Default head is baked with scale 1.5 1.45 1.45 return size / Vector3(1.5f, 1.45f, 1.45f); } } } if (humanoidIdentifier && humanoidIdentifier->getBodyPartType(part) != HumanoidIdentifier::PartType_Unknown) { if (humanoidIdentifier->isPartHead(part)) { Vector3 size = part->getPartSizeXml(); float scaleXZ = std::min(size.x, size.z); return Vector3(scaleXZ, size.y, scaleXZ); } else { Vector3 standardPartSize = humanoidIdentifier->getBodyPartScale(part); Vector3 partSize = part->getPartSizeXml(); return partSize / standardPartSize; } } return Vector3::one(); } template Vector3 verticalRotate(const Vector3& pos) { return vertical ? Vector3(pos.y, -pos.x, pos.z) : pos; } template Vector2 verticalRotateDecal(const Vector2& pos) { return vertical ? Vector2(-pos.y, pos.x) : pos; } template NormalId verticalRotate(NormalId id) { if (!vertical) return id; switch(id) { case NORM_X: return NORM_Y; case NORM_Y: return NORM_X_NEG; case NORM_X_NEG: return NORM_Y_NEG; case NORM_Y_NEG: return NORM_X; default: return id; } } template Vector2 getDecalUVVertical(Decal* decal, const Vector3& size, bool ignoreSurfaceType) { if (!ignoreSurfaceType) { if (DecalTexture* texture = decal->fastDynamicCast()) { switch (verticalRotate(texture->getFace())) { case NORM_X: return Vector2(size.z, size.y) / texture->getStudsPerTile(); case NORM_Y: return Vector2(-size.x, size.z) / texture->getStudsPerTile(); case NORM_Z: return Vector2(size.x, size.y) / texture->getStudsPerTile(); case NORM_X_NEG: return Vector2(-size.z, size.y) / texture->getStudsPerTile(); case NORM_Y_NEG: return Vector2(size.x, -size.z) / texture->getStudsPerTile(); case NORM_Z_NEG: return Vector2(-size.x, size.y) / texture->getStudsPerTile(); default: return Vector2(); } } } return Vector2(1.0f, 1.0f); } inline Vector2 getDecalUV(Decal* decal, const Vector3& size, bool ignoreSurfaceType) { return getDecalUVVertical(decal, size, ignoreSurfaceType); } static Vector2 getSurfaceOffset(const Vector3& size, unsigned int randomSeed, PartMaterial material) { int randomU = (randomSeed >> 3) & 63; int randomV = (randomSeed >> 9) & 63; if (material == BRICK_MATERIAL || material == WOODPLANKS_MATERIAL) { // We want the stud-aligned bricks to roughly match between parts, so we're only shifting the tiling in 1 stud increments return Vector2(randomU, randomV); } else { float maxOffset = (size.x + size.y + size.z) / 2; return maxOffset * Vector2(kUniformRandomTable[randomU] * 2 - 1, kUniformRandomTable[randomV] * 2 - 1); } } static float getSurfaceTiling(PartMaterial material) { return MaterialGenerator::getTiling(material); } static Vector4 computeOutlineOffsetPart(NormalId face, PartInstance* part) { if(part->getSurfaceType(face) == NO_SURFACE_NO_OUTLINES) return Vector4(NO_OUTLINES, NO_OUTLINES, NO_OUTLINES, NO_OUTLINES); static const NormalId adjTable [6][4] = { { NORM_Z_NEG, NORM_Z, NORM_Y, NORM_Y_NEG }, // NormX { NORM_X_NEG, NORM_X, NORM_Z, NORM_Z_NEG }, // NormY { NORM_X, NORM_X_NEG, NORM_Y, NORM_Y_NEG }, // NormZ { NORM_Z, NORM_Z_NEG, NORM_Y, NORM_Y_NEG }, // NormX_Neg { NORM_X_NEG, NORM_X, NORM_Z_NEG, NORM_Z }, // NormY_Neg { NORM_X_NEG, NORM_X, NORM_Y, NORM_Y_NEG }, // NormZ_Neg }; Vector4 result; result.x = part->getSurfaceType(adjTable[face][0]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.y = part->getSurfaceType(adjTable[face][1]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.z = part->getSurfaceType(adjTable[face][2]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.w = part->getSurfaceType(adjTable[face][3]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; return result; } static Vector4 computeOutlineOffsetWedge(NormalId face, PartInstance* part) { if(part->getSurfaceType(face) == NO_SURFACE_NO_OUTLINES) return Vector4(NO_OUTLINES, NO_OUTLINES, NO_OUTLINES, NO_OUTLINES); static const NormalId adjTable [6][4] = { { NORM_Z, NORM_Z_NEG, NORM_Z_NEG, NORM_Y_NEG }, // NormX { NORM_Y, NORM_Y, NORM_Y, NORM_Y }, // NormY { NORM_X, NORM_X_NEG, NORM_Z_NEG, NORM_Y_NEG }, // NormZ { NORM_Z, NORM_Z_NEG, NORM_Z_NEG, NORM_Y_NEG }, // NormX_Neg { NORM_X_NEG, NORM_X, NORM_Z_NEG, NORM_Z }, // NormY_Neg { NORM_X_NEG, NORM_X, NORM_Z, NORM_Y_NEG }, // NormZ_Neg }; Vector4 result; result.x = part->getSurfaceType(adjTable[face][0]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.y = part->getSurfaceType(adjTable[face][1]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.z = part->getSurfaceType(adjTable[face][2]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.w = part->getSurfaceType(adjTable[face][3]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; return result; } static Vector4 computeOutlineOffsetCornerWedge(NormalId face, PartInstance* part) { if(part->getSurfaceType(face) == NO_SURFACE_NO_OUTLINES) return Vector4(NO_OUTLINES, NO_OUTLINES, NO_OUTLINES, NO_OUTLINES); static const NormalId adjTable [6][4] = { { NORM_Z_NEG, NORM_Z, NORM_Z_NEG, NORM_Y_NEG }, // NormX { NORM_Y, NORM_Y, NORM_Y, NORM_Y }, // NormY { NORM_X, NORM_X_NEG, NORM_X_NEG, NORM_Y_NEG }, // NormZ { NORM_Z_NEG, NORM_Z, NORM_Z_NEG, NORM_Y_NEG }, // NormX_Neg { NORM_X_NEG, NORM_X, NORM_Z_NEG, NORM_Z }, // NormY_Neg { NORM_X_NEG, NORM_X, NORM_X_NEG, NORM_Y_NEG }, // NormZ_Neg }; Vector4 result; result.x = part->getSurfaceType(adjTable[face][0]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.y = part->getSurfaceType(adjTable[face][1]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.z = part->getSurfaceType(adjTable[face][2]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; result.w = part->getSurfaceType(adjTable[face][3]) == NO_SURFACE_NO_OUTLINES ? NO_OUTLINES : 0; return result; } void GeometryGenerator::addFileMesh(FileMeshData* data, DataModelMesh* specialShape, PartInstance* part, Decal* decal, const HumanoidIdentifier* humanoidIdentifier, const Options& options) { size_t vertexCount = data->vnts.size(); size_t faceCount = data->faces.size(); size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; mVertexCount += vertexCount; mIndexCount += faceCount * 3; if (!mVertices) return; Color4uint8 color = getColor(part, decal, specialShape, options, 0, false); Color4uint8 extra = getExtra(part, options); Vector3 scale = getMeshScale(part, specialShape, humanoidIdentifier); const CoordinateFrame& cframe = options.cframe; Vertex* vertices = mVertices; Vector3 offset = specialShape ? specialShape->getOffset() : Vector3::zero(); SpecialShape* shape = Instance::fastDynamicCast(specialShape); bool isHead = shape && shape->getMeshType() == SpecialShape::HEAD_MESH; if (isHead) { // Head mesh scale is not uniform - XZ scale scales the caps (beveled portion), and Y scale just extrudes the cylinder portion // Since head mesh is centered around origin, with 0.625 as Y extents (maxY ~= 0.625, minY ~= -0.625), we scale vertices by scale.x // along Y axis and extrude by headCylinderExtents, that is set to keep the cumulative extents at 0.625 * scale.y float headCylinderExtents = 0.625 * (scale.y - scale.x); for (size_t i = 0; i < vertexCount; ++i) { Vertex& v = vertices[vertexOffset + i]; const FileMeshVertexNormalTexture3d& sv = data->vnts[i]; float headY = (sv.vy > 0) ? (sv.vy * scale.x + headCylinderExtents) : (sv.vy * scale.x - headCylinderExtents); Vector3 pos; if (FFlag::FixMeshOffset) pos = cframe.pointToWorldSpace(Vector3(sv.vx * scale.x, headY, sv.vz * scale.z) + offset); else pos = cframe.pointToWorldSpace(Vector3(sv.vx * scale.x, headY, sv.vz * scale.z)) + offset; fillVertex(v, pos, cframe.vectorToWorldSpace(Vector3(sv.nx, sv.ny, sv.nz)), transformUV(Vector2(sv.tu, sv.tv), options), Vector2(0.0f,0.0f), color, extra, Vector3()); } } else { for (size_t i = 0; i < vertexCount; ++i) { Vertex& v = vertices[vertexOffset + i]; const FileMeshVertexNormalTexture3d& sv = data->vnts[i]; Vector3 pos; if (FFlag::FixMeshOffset) pos = cframe.pointToWorldSpace(Vector3(sv.vx * scale.x, sv.vy * scale.y, sv.vz * scale.z) + offset); else pos = cframe.pointToWorldSpace(Vector3(sv.vx * scale.x, sv.vy * scale.y, sv.vz * scale.z)) + offset; fillVertex(v, pos, cframe.vectorToWorldSpace(Vector3(sv.nx, sv.ny, sv.nz)), transformUV(Vector2(sv.tu, sv.tv), options), Vector2(0.0f,0.0f), color, extra, Vector3()); } } if (vertexCount > 0) { AABox aabb = cframe.AABBtoWorldSpace(AABox(data->aabb.low() * scale + offset, data->aabb.high() * scale + offset)); extendBounds(mBboxMin, mBboxMax, aabb.low()); extendBounds(mBboxMin, mBboxMax, aabb.high()); } unsigned short* indices = mIndices; for (size_t i = 0; i < faceCount; ++i) { indices[indexOffset + i * 3 + 0] = vertexOffset + data->faces[i].a; indices[indexOffset + i * 3 + 1] = vertexOffset + data->faces[i].b; indices[indexOffset + i * 3 + 2] = vertexOffset + data->faces[i].c; } } bool isPartSmooth(PartInstance* p) { for(unsigned normal = 0; normal < NORM_UNDEFINED; normal++) if(p->getSurfaceType((NormalId)normal) != NO_SURFACE) return false; return true; } Vector3 computeCylinderPosition(const Vector3& localUnitPos, const Vector3& size, float radius, Vector2& normal2d) { normal2d = Vector2(localUnitPos.y, localUnitPos.z); normal2d = normal2d.isZero() ? normal2d : normal2d.direction(); Vector3 localPos = Vector3(localUnitPos.x*size.x/2.0f,normal2d.x*radius,normal2d.y*radius); return localPos; } template void GeometryGenerator::addCylinder(const Vector3& size, const Vector3& center, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed, bool ignoreMaterialsStuds) { size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; bool isSmooth = isPartSmooth(part) || ignoreMaterialsStuds; Vector3 genSize = vertical ? size.yxz() : size; float primaryDimension = std::min(genSize.y, genSize.z); float primaryDimensionStuds = std::min(primaryDimension, MAX_TESSELATION*(TESSELATION_PIECE-1)); unsigned minimumTesselationFactor = 6; float quadNumberF = isSmooth ? 1 : ceil(primaryDimensionStuds / (TESSELATION_PIECE-1)); unsigned quadNumberI = (unsigned)quadNumberF; if (quadNumberI == 0) return; unsigned tesselationPerQuad = (minimumTesselationFactor + quadNumberI - 1) / quadNumberI; float invTesselationFactor = 1.0f / (quadNumberF*tesselationPerQuad); unsigned tesselationFactor = quadNumberI*tesselationPerQuad; unsigned totalVerts = 4*quadNumberI*2*(tesselationPerQuad+1) + 2*(1 + (tesselationFactor+1)*4); unsigned quads = 4*tesselationFactor; unsigned tris = 4*tesselationFactor*2; if(decal) { if(verticalRotate(decal->getFace()) % 3 == 0) { // Cap decal totalVerts = (1 + (tesselationFactor+1)*4); quads = 0; tris = 4*tesselationFactor; } else { // Side decal totalVerts = quadNumberI*2*(tesselationPerQuad+1); quads = tesselationFactor; tris = 0; } } mVertexCount += totalVerts; mIndexCount += quads*6 + tris*3; if (!mVertices) return; Color4uint8 color = getColor(part, decal, NULL, options, randomSeed, true); Color4uint8 extra = getExtra(part, options); float radius = primaryDimension/2; Vector3 axisX = Vector3(1.0f, 0.0f, 0.0f); Vector3 axisY = Vector3(0.0f, 1.0f, 0.0f); Vector3 axisZ = Vector3(0.0f, 0.0f, 1.0f); Vector3 worldAxisX = Vector3(options.cframe.rotation[0][0], options.cframe.rotation[1][0], options.cframe.rotation[2][0]); Vector3 worldAxisY = Vector3(options.cframe.rotation[0][1], options.cframe.rotation[1][1], options.cframe.rotation[2][1]); Vector3 worldAxisZ = Vector3(options.cframe.rotation[0][2], options.cframe.rotation[1][2], options.cframe.rotation[2][2]); Vector3 cornerO = -axisX - axisY - axisZ; Vector3 cornerX = axisX * 2; Vector3 cornerY = axisY * 2; Vector3 cornerZ = axisZ * 2; Vector2 surfaceOffset = decal ? Vector2() : getSurfaceOffset(genSize, randomSeed, part->getRenderMaterial()); float surfaceTiling = decal ? 1 : getSurfaceTiling(part->getRenderMaterial()); Vector3 corners[8] = { cornerO, cornerO + cornerZ, cornerO + cornerY, cornerO + cornerY + cornerZ, cornerO + cornerX, cornerO + cornerX + cornerZ, cornerO + cornerX + cornerY, cornerO + cornerX + cornerY + cornerZ, }; Vector3 facesDirU[] = { -axisY, -axisZ, -axisY, -axisY, axisZ, -axisY }; Vector3 facesDirV[] = { axisZ, axisX, -axisX, -axisZ, axisX, axisX }; Vector3 tangents[6]; if (vertical) { tangents[0] = worldAxisX; tangents[1] = worldAxisY; tangents[2] = -worldAxisY; tangents[3] = -worldAxisX; tangents[4] = worldAxisY; tangents[5] = worldAxisY; } else { tangents[0] = -worldAxisZ; tangents[1] = -worldAxisX; tangents[2] = worldAxisX; tangents[3] = worldAxisZ; tangents[4] = -worldAxisX; tangents[5] = -worldAxisX; } unsigned vertexCounter = 0; unsigned faceCounter = 0; Vertex* vertices = mVertices; unsigned short* indices = mIndices; struct CAPDESC { Vector3 start[4]; Vector3 direction[4]; Vector3 fanPoint; Vector2 normal2dToUV; }; CAPDESC capsDesc[] = { { { corners[6], corners[4], corners[5], corners[7] }, { -axisY, axisZ, axisY, -axisZ }, Vector3(1,0,0), Vector2(-1,-1), }, { { corners[3], corners[1], corners[0], corners[2] }, { -axisY, -axisZ, axisY, axisZ }, Vector3(-1,0,0), Vector2(1,-1), } }; static const FACEDESC facesDesc[] = { { 6,7,4,5, false, true, }, // Face 0 { 3,7,2,6, true, false, }, // Face 1 { 7,3,5,1, false, false, }, // Face 2 { 3,2,1,0, false, true, }, // Face 3 { 0,4,1,5, true, false, }, // Face 4 { 2,6,0,4, false, false, }, // Face 5 }; float capRightOutlineOffset = (part->getConstPartPrimitive()->getSurfaceType(NORM_X) == NO_SURFACE_NO_OUTLINES) ? NO_OUTLINES : 0; float capLeftOutlineOffset = (part->getConstPartPrimitive()->getSurfaceType(NORM_X_NEG) == NO_SURFACE_NO_OUTLINES) ? NO_OUTLINES : 0; for (int face = 0; face < 6; ++face) { if(decal && verticalRotate(decal->getFace()) != face) continue; // Caps if(face % 3 == 0) { const CAPDESC& capDesc = capsDesc[face == 0 ? 0 : 1]; Vector3 fanPoint = capDesc.fanPoint; unsigned fanPointIndex = vertexOffset+vertexCounter; Vector2 mainMultiplier = decal ? getDecalUVVertical(decal, genSize, ignoreMaterialsStuds) : Vector2(primaryDimension,primaryDimension); Vector2 normal2d; Vector3 localPos = computeCylinderPosition(fanPoint, genSize, radius, normal2d); Vector3 outPos = options.cframe.pointToWorldSpace(verticalRotate(localPos) + center); Vector3 normal = options.cframe.vectorToWorldSpace(verticalRotate(fanPoint)); Vector2 mainUV = 0.5f * mainMultiplier; float outlineOffset = face == 0 ? capRightOutlineOffset : capLeftOutlineOffset; fillVertex(vertices[vertexOffset + vertexCounter], outPos, normal, (mainUV + surfaceOffset) * surfaceTiling, Vector2::zero(), color, extra, tangents[face], Vector4(NO_OUTLINES,NO_OUTLINES, NO_OUTLINES, radius + outlineOffset)); vertexCounter++; unsigned prevVertexCounter = vertexCounter; for(unsigned adjFace = 0; adjFace < 4; adjFace++) { Vector3 unitPos = capDesc.start[adjFace]; Vector3 unitDirV = capDesc.direction[adjFace]*2*invTesselationFactor; for(unsigned tess = 0; tess < tesselationFactor+1; tess++) { Vector2 normal2d; Vector3 localPos = computeCylinderPosition(unitPos, genSize, radius, normal2d); Vector3 outPos = options.cframe.pointToWorldSpace(verticalRotate(localPos) + center); Vector2 mainUV = (verticalRotateDecal(normal2d.yx())*capDesc.normal2dToUV +Vector2(1,1))*0.5f * mainMultiplier; Vector3 normal = options.cframe.vectorToWorldSpace(verticalRotate(fanPoint)); fillVertex(vertices[vertexOffset + vertexCounter], outPos, normal, (mainUV + surfaceOffset) * surfaceTiling, Vector2::zero(), color, extra, tangents[face], Vector4(NO_OUTLINES,NO_OUTLINES, NO_OUTLINES, outlineOffset)); vertexCounter++; unitPos += unitDirV; } } unsigned tessIndex = vertexOffset+prevVertexCounter; for(unsigned adjFace = 0; adjFace < 4; adjFace++) { for(unsigned tess = 0; tess < tesselationFactor; tess++) { indices[indexOffset + faceCounter*3] = fanPointIndex; indices[indexOffset + faceCounter*3+1] = tessIndex+tess+1; indices[indexOffset + faceCounter*3+2] = tessIndex+tess; faceCounter++; } tessIndex += tesselationFactor+1; } } else { const FACEDESC& desc = facesDesc[face]; Vector3 start0 = corners[desc.start0]; //, start1 = corners[desc.start1]; Vector3 unitDirU = facesDirU[face] * invTesselationFactor * 2; Vector3 unitDirV = facesDirV[face] * 2; Vector3 unitPos = start0; UVAtlasInfo studsAtlasInfo = getStudsAtlasInfo((ignoreMaterialsStuds || (options.flags & Flag_IgnoreStuds)) ? NO_SURFACE : part->getConstPartPrimitive()->getSurfaceType((NormalId)face)); float studOffsetU = studsAtlasInfo.tileX ? genSize.x / 2.0f : 0.0f; float studOffsetV = studsAtlasInfo.scaleY * primaryDimensionStuds * invTesselationFactor / TESSELATION_PIECE; Vector2 mainOffsetOrig = decal ? getDecalUVVertical(decal, genSize, ignoreMaterialsStuds) : Vector2(genSize.x,primaryDimension); Vector2 mainCoords = Vector2(mainOffsetOrig.x,0.0f) + surfaceOffset; Vector2 mainOffset = mainOffsetOrig*Vector2(1,invTesselationFactor); for (unsigned quad = 0; quad < quadNumberI; quad++) { unsigned prevVertexCounter = vertexCounter; Vector2 studStart = Vector2(studOffsetU, studsAtlasInfo.offsetY + frac(quad*primaryDimensionStuds / quadNumberF) / TESSELATION_PIECE * studsAtlasInfo.scaleY); // Generate vertices for(unsigned iU = 0; iU < tesselationPerQuad+1; iU++) { Vector3 unitPosV = unitPos; Vector2 studsV = studStart; Vector2 mainCoordsV = mainCoords; for(unsigned iV = 0; iV < 2; iV++) { Vector2 normal2d; Vector3 localPos = computeCylinderPosition(unitPosV, genSize, radius, normal2d); Vector3 outPos = options.cframe.pointToWorldSpace(verticalRotate(localPos) + center); Vector3 worldNormal = options.cframe.vectorToWorldSpace(verticalRotate(Vector3(0, normal2d.x, normal2d.y))); Vector2 mainUV = mainCoordsV; if(decal && vertical) { mainUV = Vector2(mainUV.y, mainOffsetOrig.y - mainUV.x); } Vector4 edgeDistances = Vector4(NO_OUTLINES,NO_OUTLINES, capLeftOutlineOffset + (iV == 0 ? 0 : genSize.x), capRightOutlineOffset + (iV == 0 ? genSize.x : 0)); fillVertex(vertices[vertexOffset + vertexCounter], outPos, worldNormal, mainUV * surfaceTiling, studsV, color, extra, tangents[face], edgeDistances); vertexCounter++; unitPosV += unitDirV; studsV.x -= studOffsetU; mainCoordsV.x -= mainOffset.x; } if(iU < tesselationPerQuad) { unitPos += unitDirU; mainCoords.y += mainOffset.y; } studStart.y += studOffsetV; } // Generate faces for(unsigned iU = 0; iU < tesselationPerQuad; iU++) { fillQuadIndices(&indices[indexOffset + faceCounter*3], vertexOffset + prevVertexCounter, iU*2, iU*2+1, (iU+1)*2, (iU+1)*2+1); faceCounter+=2; } } } } RBXASSERT(faceCounter == 2*quads+tris); RBXASSERT(vertexCounter == totalVerts); Vector3 boundsCenter = options.cframe.pointToWorldSpace(center); extendBoundsBlock(mBboxMin, mBboxMax, boundsCenter, size, worldAxisX, worldAxisY, worldAxisZ); } void GeometryGenerator::addSphere(const Vector3& size, const Vector3& center, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed, bool ignoreMaterialsStuds) { size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; bool isSmooth = isPartSmooth(part) || ignoreMaterialsStuds; float primaryDimension = size.min(); float primaryDimensionStuds = std::min(primaryDimension, MAX_TESSELATION*(TESSELATION_PIECE-1)); unsigned minimumTesselationFactor = 6; float quadNumberF = isSmooth ? 1 : ceil(primaryDimensionStuds / (TESSELATION_PIECE-1)); unsigned quadNumberI = (unsigned)quadNumberF; if (quadNumberI == 0) return; unsigned tesselationPerQuad = (minimumTesselationFactor + quadNumberI - 1) / quadNumberI; float invTesselationFactor = 1.0f / (quadNumberF*tesselationPerQuad); unsigned tesselationFactor = quadNumberI*tesselationPerQuad; unsigned totalVerts = quadNumberI*(tesselationFactor+1)*(tesselationPerQuad+1); unsigned quads = tesselationFactor*tesselationFactor; if(!decal) { totalVerts *= 6; quads *= 6; } mVertexCount += totalVerts; mIndexCount += quads*6; if (!mVertices) return; Color4uint8 color = getColor(part, decal, NULL, options, randomSeed, true); Color4uint8 extra = getExtra(part, options); float radius = primaryDimension/2; static const Vector3 axisX = Vector3(1.0f, 0.0f, 0.0f); static const Vector3 axisY = Vector3(0.0f, 1.0f, 0.0f); static const Vector3 axisZ = Vector3(0.0f, 0.0f, 1.0f); static const Vector3 cornerO = -axisX - axisY - axisZ; static const Vector3 cornerX = axisX * 2; static const Vector3 cornerY = axisY * 2; static const Vector3 cornerZ = axisZ * 2; static const Vector3 corners[8] = { cornerO, cornerO + cornerZ, cornerO + cornerY, cornerO + cornerY + cornerZ, cornerO + cornerX, cornerO + cornerX + cornerZ, cornerO + cornerX + cornerY, cornerO + cornerX + cornerY + cornerZ, }; static const Vector3 facesDirU[] = { -axisY, -axisZ, -axisY, -axisY, axisZ, -axisY }; static const Vector3 facesDirV[] = { axisZ, axisX, -axisX, -axisZ, axisX, axisX }; Vector3 worldAxisX = Vector3(options.cframe.rotation[0][0], options.cframe.rotation[1][0], options.cframe.rotation[2][0]); Vector3 worldAxisY = Vector3(options.cframe.rotation[0][1], options.cframe.rotation[1][1], options.cframe.rotation[2][1]); Vector3 worldAxisZ = Vector3(options.cframe.rotation[0][2], options.cframe.rotation[1][2], options.cframe.rotation[2][2]); Vector2 surfaceOffset = decal ? Vector2() : getSurfaceOffset(size, randomSeed, part->getRenderMaterial()); float surfaceTiling = decal ? 1 : getSurfaceTiling(part->getRenderMaterial()); Vector3 tangents[] = { worldAxisZ, worldAxisX, -worldAxisX, -worldAxisZ, worldAxisX, worldAxisX }; static const FACEDESC facesDesc[] = { { 6,7,4,5, false, true, }, // Face 0 { 3,7,2,6, true, false, }, // Face 1 { 7,3,5,1, false, false, }, // Face 2 { 3,2,1,0, false, true, }, // Face 3 { 0,4,1,5, true, false, }, // Face 4 { 2,6,0,4, false, false, }, // Face 5 }; unsigned faceCounter = 0; Vertex* vertices = mVertices; unsigned short* indices = mIndices; Vector3 scale = size * (radius / primaryDimension); unsigned vertexCounter = 0; for (int face = 0; face < 6; ++face) { if(decal && decal->getFace() != face) continue; const FACEDESC& desc = facesDesc[face]; Vector3 start0 = corners[desc.start0]; //, start1 = corners[desc.start1]; Vector3 worldDirU = facesDirU[face] * invTesselationFactor * 2; Vector3 worldDirV = facesDirV[face] * invTesselationFactor * 2; Vector3 worldPos = start0; UVAtlasInfo studsAtlasInfo = getStudsAtlasInfo((ignoreMaterialsStuds || (options.flags & Flag_IgnoreStuds)) ? NO_SURFACE : part->getConstPartPrimitive()->getSurfaceType((NormalId)face)); float studOffsetU = studsAtlasInfo.tileX ? primaryDimensionStuds * invTesselationFactor / 2.0f : 0.0f; float studOffsetV = studsAtlasInfo.scaleY * primaryDimensionStuds * invTesselationFactor / TESSELATION_PIECE; Vector2 mainOffset = decal ? getDecalUV(decal, size, ignoreMaterialsStuds) : abs(Vector2(dot(facesDirV[face], size),dot(facesDirU[face], size))); Vector2 mainCoords = surfaceOffset + Vector2(mainOffset.x, 0); mainOffset *= invTesselationFactor; for (unsigned quad = 0; quad < quadNumberI; quad++) { unsigned prevVertexCounter = vertexCounter; Vector2 studStart = Vector2(studOffsetU * tesselationPerQuad, studsAtlasInfo.offsetY + frac(quad*primaryDimensionStuds / quadNumberF) / TESSELATION_PIECE * studsAtlasInfo.scaleY); // Generate vertices for(unsigned iU = 0; iU < tesselationPerQuad+1; iU++) { Vector3 worlsPosV = worldPos; Vector2 studsV = studStart; Vector2 mainCoordsV = mainCoords; for(unsigned iV = 0; iV < tesselationFactor+1; iV++) { Vector3 normal = worlsPosV.direction(); Vector3 localPos = scale*normal; Vector3 worldOffset = options.cframe.vectorToWorldSpace(localPos); Vector3 worldNormal = worldOffset.direction(); Vector3 tangent = (worldNormal*dot(tangents[face],worldNormal) - tangents[face]).direction(); fillVertex(vertices[vertexOffset + vertexCounter], options.cframe.translation + worldOffset + center, worldNormal, mainCoordsV * surfaceTiling, studsV, color, extra, tangent); vertexCounter++; worlsPosV += worldDirV; studsV.x -= studOffsetU; mainCoordsV.x -= mainOffset.x; } if(iU < tesselationPerQuad) { worldPos += worldDirU; mainCoords.y += mainOffset.y; } studStart.y += studOffsetV; } // Generate faces for(unsigned iU = 0; iU < tesselationPerQuad; iU++) { for(unsigned iV = 0; iV < tesselationFactor; iV++) { fillQuadIndices(&indices[indexOffset + faceCounter*6], vertexOffset + prevVertexCounter, iU*(tesselationFactor+1)+iV, iU*(tesselationFactor+1)+iV+1, (iU+1)*(tesselationFactor+1)+iV, (iU+1)*(tesselationFactor+1)+iV+1); faceCounter++; } } } } RBXASSERT(faceCounter == quads); RBXASSERT(vertexCounter == totalVerts); extendBoundsBlock(mBboxMin, mBboxMax, options.cframe.translation + center, size, worldAxisX, worldAxisY, worldAxisZ); } template void GeometryGenerator::addWedge(const Vector3& size, const Vector3& center, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed, bool ignoreMaterialsStuds) { size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; bool facesIgnoreTiling[NORM_UNDEFINED] = { 0 }; Vector3 localExtentsSize = size; float wedgeFaceLength[6]; if (!corner) { float diagLenZY = sqrt(size.y*size.y+size.z*size.z); wedgeFaceLength[0] = size.y; wedgeFaceLength[1] = size.z; wedgeFaceLength[2] = size.y; wedgeFaceLength[3] = size.y; wedgeFaceLength[4] = size.z; wedgeFaceLength[5] = diagLenZY; } else { float diagLenXY = sqrt(size.y*size.y+size.x*size.x); float diagLenZY = sqrt(size.y*size.y+size.z*size.z); wedgeFaceLength[0] = size.y; wedgeFaceLength[1] = size.z; wedgeFaceLength[2] = diagLenZY; wedgeFaceLength[3] = diagLenXY; wedgeFaceLength[4] = size.z; wedgeFaceLength[5] = size.y; } bool noBlanks = true; unsigned quads = 1; if(!decal) { quads = 0; for (int face = 0; face < NORM_UNDEFINED; face++) { SurfaceType surface = part->getConstPartPrimitive()->getSurfaceType((NormalId)face); if(surface == NO_SURFACE_NO_OUTLINES) noBlanks = false; if(face == NORM_Y) continue; facesIgnoreTiling[face] = IsNoSurface(surface) || ignoreMaterialsStuds; if(facesIgnoreTiling[face]) quads += 1; else { quads += fastCeilPositiveApprox(wedgeFaceLength[face] / TESSELATION_PIECE); } } } if (quads > 32768 / 4) { // We do not allow a part to generate more than 32768 vertices // This is both to ensure that we never exceed 16-bit index limit even if we had some vertices before, and to avoid potential issues with negative size components RBXASSERT(false); return; } mVertexCount += quads*4; mIndexCount += quads*6; if (!mVertices) return; Color4uint8 color = getColor(part, decal, NULL, options, randomSeed, true); Color4uint8 extra = getExtra(part, options); const CoordinateFrame& cframe = options.cframe; Vector3 axisX = Vector3(cframe.rotation[0][0], cframe.rotation[1][0], cframe.rotation[2][0]); Vector3 axisY = Vector3(cframe.rotation[0][1], cframe.rotation[1][1], cframe.rotation[2][1]); Vector3 axisZ = Vector3(cframe.rotation[0][2], cframe.rotation[1][2], cframe.rotation[2][2]); Vector3 extent = size * 0.5f; Vector3 cornerO = cframe.pointToWorldSpace(Vector3(center - extent)); Vector3 cornerX = axisX * size.x; Vector3 cornerY = axisY * size.y; Vector3 cornerZ = axisZ * size.z; Vector3 corners[8] = { cornerO, cornerO + cornerZ, cornerO + cornerY, cornerO + cornerY + cornerZ, cornerO + cornerX, cornerO + cornerX + cornerZ, cornerO + cornerX + cornerY, cornerO + cornerX + cornerY + cornerZ, }; float zyProportion = size.z/size.y; float xyProportion = size.x/size.y; Vector3 adjustedAxisZ = axisZ*zyProportion; Vector3 adjustedAxisX = axisX*xyProportion; Vector2 surfaceOffset = getSurfaceOffset(size, randomSeed, part->getRenderMaterial()); float surfaceTiling = decal ? 1 : getSurfaceTiling(part->getRenderMaterial()); Vector3 normalOffset = (options.flags & Flag_RandomizeBlockAppearance) ? kNormalOffsetTable[(randomSeed >> 3) & 31] : Vector3(0, 0, 0); Vector3 normals[6]; if(!corner) { normals[0] = normalize(normalOffset + axisX); normals[1] = normalize(normalOffset + axisY); normals[2] = normalize(normalOffset + axisZ); normals[3] = normalize(normalOffset - axisX); normals[4] = normalize(normalOffset - axisY); normals[5] = normalize(normalOffset - axisZ + axisY*zyProportion); } else { Vector3 diag = -size.x*axisX - size.y*axisY + size.z*axisZ; Vector3 zside = -size.y*axisY + size.z*axisZ; Vector3 xside = -size.x*axisX - size.y*axisY; normals[0] = normalize(normalOffset + axisX); normals[1] = normalize(normalOffset + axisY); normals[2] = normalize(normalOffset + diag.cross(zside)); normals[3] = normalize(normalOffset + xside.cross(diag)); normals[4] = normalize(normalOffset - axisY); normals[5] = normalize(normalOffset - axisZ); } Vector3 tangents[6] = { -axisZ, -axisX, axisX, axisZ, -axisX, -axisX, }; Vertex* vertices = mVertices; unsigned short* indices = mIndices; if (decal) { // Note: this table is almost identical to the facesDesc below, with the exception of bottom face. // This is needed to preserve the decal orientation compared to legacy code. static const unsigned int wedgeFaces[6][4] = { {7, 7, 4, 5}, // Face 0 +X {3, 7, 0, 4}, // Face 1 +Y {7, 3, 5, 1}, // Face 2 +Z {3, 3, 1, 0}, // Face 3 -X {5, 1, 4, 0}, // Face 4 -Y {3, 7, 0, 4}, // Face 5 -Z }; static const unsigned int cornerWedgeFaces[6][4] = { { 6,6,4,5}, // Face 0 +X { 0,1,6,5}, // Face 1 +Y { 6,6,5,1}, // Face 2 +Z { 6,6,1,0}, // Face 3 -X { 5,1,4,0}, // Face 4 -Y { 6,6,0,4}, // Face 5 -Z }; const unsigned int (*faces)[4] = corner ? cornerWedgeFaces : wedgeFaces; int face = decal->getFace(); // If the block ignores studs (block meshes), then the Texture UV generation should not take size into account as well. Vector2 uv = getDecalUV(decal, size, /* ignoreSurfaceType= */ ignoreMaterialsStuds); Vector2 outUvs[4]; if (corner && face == NORM_Y) { // when projecting from top to corner wedge, make decal to "stand" on base part outUvs[3] = Vector2(uv.x, uv.y); outUvs[2] = Vector2(uv.x, 0); outUvs[1] = Vector2(0, uv.y); outUvs[0] = Vector2(0, 0); } else { outUvs[0] = Vector2(uv.x, 0); outUvs[1] = Vector2(0, 0); outUvs[2] = Vector2(uv.x, uv.y); outUvs[3] = Vector2(0, uv.y); } if(!corner) { if(face == NORM_X_NEG) // Fix for wedges outUvs[1] = outUvs[0]; } else { if(face == NORM_X || face == NORM_Z) // Fix for triangular part of corner wedge part outUvs[1] = outUvs[0]; } fillVertex(vertices[vertexOffset + 0], corners[faces[face][0]], normals[face], outUvs[0], Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 1], corners[faces[face][1]], normals[face], outUvs[1], Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 2], corners[faces[face][2]], normals[face], outUvs[2], Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 3], corners[faces[face][3]], normals[face], outUvs[3], Vector2(), color, extra, Vector3()); fillQuadIndices(&indices[indexOffset], vertexOffset, 0, 1, 2, 3); for (int i = 0; i < 4; ++i) extendBounds(mBboxMin, mBboxMax, corners[faces[face][i]]); } else { struct WEDGESTEPDESC { Vector3 faceDir0, faceDir1; float studOffset0, studOffset1; inline WEDGESTEPDESC(const Vector3& faceDir0, const Vector3& faceDir1, float studOffset0, float studOffset1) : faceDir0(faceDir0), faceDir1(faceDir1), studOffset0(studOffset0), studOffset1(studOffset1) {} inline WEDGESTEPDESC() {} }; WEDGESTEPDESC wedgeStepDesc[6]; if(!corner) { Vector3 wedgeDirection = (-axisY*size.y - axisZ*size.z).direction();; wedgeStepDesc[0] = WEDGESTEPDESC(-axisY-adjustedAxisZ, -axisY, zyProportion, 0.0f); wedgeStepDesc[1] = WEDGESTEPDESC(Vector3(0,0,0), Vector3(0,0,0), 0.0f, 0.0f); wedgeStepDesc[2] = WEDGESTEPDESC(-axisY, -axisY, 0.0f, 0.0f); wedgeStepDesc[3] = WEDGESTEPDESC(-axisY,-axisY-adjustedAxisZ, 0.0f, zyProportion); wedgeStepDesc[4] = WEDGESTEPDESC(axisZ, axisZ, 0.0f, 0.0f); wedgeStepDesc[5] = WEDGESTEPDESC(wedgeDirection, wedgeDirection, 0.0f, 0.0f); } else { Vector3 sideZDirection = (-size.y*axisY + size.z*axisZ).direction(); Vector3 sideXDirection = (-size.x*axisX - size.y*axisY).direction(); float xAspect = size.x/wedgeFaceLength[2]; float zAspect = size.z/wedgeFaceLength[3]; wedgeStepDesc[0] = WEDGESTEPDESC(-axisY, -axisY+adjustedAxisZ, 0.0f, zyProportion); wedgeStepDesc[1] = WEDGESTEPDESC(Vector3(0,0,0), Vector3(0,0,0), 0.0f, 0.0f); wedgeStepDesc[2] = WEDGESTEPDESC(sideZDirection, sideZDirection-axisX*xAspect, 0.0f, xAspect); wedgeStepDesc[3] = WEDGESTEPDESC(sideXDirection+axisZ*zAspect, sideXDirection, zAspect, 0.0f); wedgeStepDesc[4] = WEDGESTEPDESC(axisZ, axisZ, 0.0f, 0.0f); wedgeStepDesc[5] = WEDGESTEPDESC(-axisY -adjustedAxisX, -axisY, xyProportion, 0.0f); } static const FACEDESC wedgeFacesDesc[] = { { 7,7,4,5, false, true, }, // Face 0 { 3,7,3,7, true, false, }, // Face 1 { 7,3,5,1, false, false, }, // Face 2 { 3,3,1,0, false, true, }, // Face 3 { 0,4,1,5, true, false, }, // Face 4 { 3,7,0,4, false, false, }, // Face 5 }; static const FACEDESC cornerWedgeFacesDesc[] = { { 6,6,4,5, false, true, }, // Face 0 { 6,6,6,6, true, false, }, // Face 1 { 6,6,5,1, false, false, }, // Face 2 { 6,6,1,0, false, true, }, // Face 3 { 0,4,1,5, true, false, }, // Face 4 { 6,6,0,4, false, false, }, // Face 5 }; const FACEDESC* facesDesc = corner ? cornerWedgeFacesDesc : wedgeFacesDesc; unsigned faceCounter = 0; Vector2 uvs[4]; for (int face = 0; face < ARRAYSIZE(wedgeFacesDesc); ++face) { if(face == NORM_Y) // Wedge only continue; const FACEDESC& desc = facesDesc[face]; const WEDGESTEPDESC& wdesc = wedgeStepDesc[face]; Vector3 start0 = corners[desc.start0], start1 = corners[desc.start1]; int quadNumber = fastCeilPositiveApprox(wedgeFaceLength[face] / TESSELATION_PIECE); float quadLength = wedgeFaceLength[face]; if(facesIgnoreTiling[face]) quadNumber = 1; Vector3 offset0 = Vector3::zero(),offset1 = Vector3::zero(); float uTiling = (desc.tangentZ ? localExtentsSize.z : localExtentsSize.x); uvs[0] = surfaceOffset; uvs[0].x += uTiling; uvs[1] = surfaceOffset; uvs[2] = surfaceOffset; uvs[2].x += uTiling; uvs[2].y += TESSELATION_PIECE; uvs[3] = surfaceOffset; uvs[3].y += TESSELATION_PIECE; UVAtlasInfo studsAtlasInfo = getStudsAtlasInfo((ignoreMaterialsStuds || (options.flags & Flag_IgnoreStuds)) ? NO_SURFACE : part->getConstPartPrimitive()->getSurfaceType((NormalId)face)); float uTilingStuds = studsAtlasInfo.tileX ? uTiling * 0.5f : 0; float uStudsIncrement = studsAtlasInfo.tileX ? TESSELATION_PIECE * 0.5f : 0; Vector2 uvStuds[4] = { Vector2(uTilingStuds, studsAtlasInfo.offsetY), Vector2(0, studsAtlasInfo.offsetY), Vector2(uTilingStuds, studsAtlasInfo.offsetY + studsAtlasInfo.scaleY), Vector2(0, studsAtlasInfo.offsetY + studsAtlasInfo.scaleY), }; Vector4 startEdgeOffset = Vector4::zero(); if(!noBlanks) { if(corner) startEdgeOffset =computeOutlineOffsetCornerWedge((NormalId)face, part); else startEdgeOffset =computeOutlineOffsetWedge((NormalId)face, part); } Vector4 edge0 = Vector4(0,uTiling,0,quadLength) + startEdgeOffset; Vector4 edge1 = Vector4(uTiling,0,0,quadLength) + startEdgeOffset; if(wdesc.studOffset1 != 0) { uvStuds[1].x = uTilingStuds; uvStuds[3].x = uTilingStuds; uvs[1].x = uvs[0].x; uvs[3].x = uvs[2].x; edge1.x = edge0.x; edge0.y = edge1.y; } if(wdesc.studOffset0 != 0) { uvStuds[0].x = 0; uvStuds[2].x = 0; uvs[0].x = uvs[1].x; uvs[2].x = uvs[3].x; edge1.x = edge0.x; edge0.y = edge1.y; } Vector3 worldDir0 = wdesc.faceDir0 * TESSELATION_PIECE; Vector3 worldDir1 = wdesc.faceDir1 * TESSELATION_PIECE; for (int quad = 0; quad < quadNumber - 1; quad++) { Vector3 nextOffset0 = offset0 + worldDir0; Vector3 nextOffset1 = offset1 + worldDir1; Vector4 nextEdge0 = edge0 + Vector4(0,0,TESSELATION_PIECE, -TESSELATION_PIECE); Vector4 nextEdge1 = edge1 + Vector4(0,0,TESSELATION_PIECE, -TESSELATION_PIECE); if(wdesc.studOffset1 != 0) { uvStuds[3].x -= wdesc.studOffset1*uStudsIncrement; uvs[3].x -= wdesc.studOffset1*TESSELATION_PIECE; nextEdge0.y += wdesc.studOffset1*TESSELATION_PIECE; nextEdge1.x += wdesc.studOffset1*TESSELATION_PIECE; } if(wdesc.studOffset0 != 0) { uvStuds[2].x += wdesc.studOffset0*uStudsIncrement; uvs[2].x += wdesc.studOffset0*TESSELATION_PIECE; nextEdge0.x += wdesc.studOffset0*TESSELATION_PIECE; nextEdge1.y += wdesc.studOffset0*TESSELATION_PIECE; } fillVertex(vertices[vertexOffset + faceCounter * 4 + 0], start0 + offset0, normals[face], uvs[0] * surfaceTiling, uvStuds[0], color, extra, tangents[face], edge0); fillVertex(vertices[vertexOffset + faceCounter * 4 + 1], start1 + offset1, normals[face], uvs[1] * surfaceTiling, uvStuds[1], color, extra, tangents[face], edge1); fillVertex(vertices[vertexOffset + faceCounter * 4 + 2], start0 + nextOffset0, normals[face], uvs[2] * surfaceTiling, uvStuds[2], color, extra, tangents[face], nextEdge0); fillVertex(vertices[vertexOffset + faceCounter * 4 + 3], start1 + nextOffset1, normals[face], uvs[3] * surfaceTiling, uvStuds[3], color, extra, tangents[face], nextEdge1); uvs[0].y = uvs[2].y; uvs[1].y = uvs[3].y; uvs[2].y += TESSELATION_PIECE; uvs[3].y += TESSELATION_PIECE; if(wdesc.studOffset1 != 0) { uvStuds[1].x = uvStuds[3].x; uvs[1].x = uvs[3].x; } if(wdesc.studOffset0 != 0) { uvStuds[0].x = uvStuds[2].x; uvs[0].x = uvs[2].x; } offset0 = nextOffset0; offset1 = nextOffset1; edge0 = nextEdge0; edge1 = nextEdge1; fillQuadIndices(&indices[indexOffset + faceCounter * 6], vertexOffset + faceCounter * 4, 0, 1, 2, 3); faceCounter++; } if (quadNumber > 0) { // Last quad float texRemainderFraction = wedgeFaceLength[face] - (quadNumber - 1)*TESSELATION_PIECE; float texMultiplier = saturate(texRemainderFraction / TESSELATION_PIECE); Vector4 nextEdge0 = edge0 + Vector4(0,0,texRemainderFraction, -texRemainderFraction); Vector4 nextEdge1 = edge1 + Vector4(0,0,texRemainderFraction, -texRemainderFraction); if(wdesc.studOffset1 != 0) { uvStuds[3].x -= wdesc.studOffset1*texMultiplier*uStudsIncrement; uvs[3].x -= wdesc.studOffset1*texRemainderFraction; nextEdge0.y += wdesc.studOffset1*texRemainderFraction; nextEdge1.x += wdesc.studOffset1*texRemainderFraction; } if(wdesc.studOffset0 != 0) { uvStuds[2].x += wdesc.studOffset0*texMultiplier*uStudsIncrement; uvs[2].x += wdesc.studOffset0*texRemainderFraction; nextEdge0.x += wdesc.studOffset0*texRemainderFraction; nextEdge1.y += wdesc.studOffset0*texRemainderFraction; } Vector2 uv2 = Vector2(uvs[2].x, uvs[0].y + texRemainderFraction); Vector2 uv3 = Vector2(uvs[3].x, uvs[1].y + texRemainderFraction); Vector2 uvStuds2 = Vector2(uvStuds[2].x, studsAtlasInfo.offsetY + texMultiplier * studsAtlasInfo.scaleY); Vector2 uvStuds3 = Vector2(uvStuds[3].x, studsAtlasInfo.offsetY + texMultiplier * studsAtlasInfo.scaleY); fillVertex(vertices[vertexOffset + faceCounter * 4 + 0], start0 + offset0, normals[face], uvs[0] * surfaceTiling, uvStuds[0], color, extra, tangents[face], edge0); fillVertex(vertices[vertexOffset + faceCounter * 4 + 1], start1 + offset1, normals[face], uvs[1] * surfaceTiling, uvStuds[1], color, extra, tangents[face], edge1); fillVertex(vertices[vertexOffset + faceCounter * 4 + 2], corners[desc.end0], normals[face], uv2 * surfaceTiling, uvStuds2, color, extra, tangents[face], nextEdge0); fillVertex(vertices[vertexOffset + faceCounter * 4 + 3], corners[desc.end1], normals[face], uv3 * surfaceTiling, uvStuds3, color, extra, tangents[face], nextEdge1); fillQuadIndices(&indices[indexOffset + faceCounter * 6], vertexOffset + faceCounter * 4, 0, 1, 2, 3); faceCounter++; } } RBXASSERT(faceCounter == quads); Vector3 boundsCenter = cframe.pointToWorldSpace(center); extendBoundsBlock(mBboxMin, mBboxMax, boundsCenter, size, axisX, axisY, axisZ); } } void GeometryGenerator::addBlock(const Vector3& size, const Vector3& center, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed, bool ignoreMaterialsStuds) { size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; bool facesIgnoreTiling[NORM_UNDEFINED] = { 0 }; Vector3 localExtentsSize = size; Vector3int16 tesselatedExtents = Vector3CeilPositiveApprox(localExtentsSize / TESSELATION_PIECE); static const FACEDESC facesDesc[] = { { 6,7,4,5, false, true, }, // Face 0 { 3,7,2,6, true, false, }, // Face 1 { 7,3,5,1, false, false, }, // Face 2 { 3,2,1,0, false, true, }, // Face 3 { 0,4,1,5, true, false, }, // Face 4 { 2,6,0,4, false, false, }, // Face 5 }; unsigned quads = 1; bool noBlanks = true; if(!decal) { quads = 0; for (int face = 0; face < NORM_UNDEFINED; face++) { SurfaceType surface = part->getConstPartPrimitive()->getSurfaceType((NormalId)face); facesIgnoreTiling[face] = IsNoSurface(surface) || ignoreMaterialsStuds; if(facesIgnoreTiling[face]) quads += 1; else { const FACEDESC& desc = facesDesc[face]; quads += desc.dirZ ? tesselatedExtents.z : tesselatedExtents.y; } if(surface == NO_SURFACE_NO_OUTLINES) noBlanks = false; } } if (quads > 32768 / 4) { // We do not allow a part to generate more than 32768 vertices // This is both to ensure that we never exceed 16-bit index limit even if we had some vertices before, and to avoid potential issues with negative size components RBXASSERT(false); return; } mVertexCount += quads*4; mIndexCount += quads*6; if (!mVertices) return; Color4uint8 color = getColor(part, decal, NULL, options, randomSeed, true); Color4uint8 extra = getExtra(part, options); const CoordinateFrame& cframe = options.cframe; Vector3 axisX = Vector3(cframe.rotation[0][0], cframe.rotation[1][0], cframe.rotation[2][0]); Vector3 axisY = Vector3(cframe.rotation[0][1], cframe.rotation[1][1], cframe.rotation[2][1]); Vector3 axisZ = Vector3(cframe.rotation[0][2], cframe.rotation[1][2], cframe.rotation[2][2]); Vector3 extent = size * 0.5f; Vector3 cornerO = cframe.pointToWorldSpace(Vector3(center - extent)); Vector3 cornerX = axisX * size.x; Vector3 cornerY = axisY * size.y; Vector3 cornerZ = axisZ * size.z; Vector3 corners[8] = { cornerO, cornerO + cornerZ, cornerO + cornerY, cornerO + cornerY + cornerZ, cornerO + cornerX, cornerO + cornerX + cornerZ, cornerO + cornerX + cornerY, cornerO + cornerX + cornerY + cornerZ, }; Vector2 surfaceOffset = getSurfaceOffset(size, randomSeed, part->getRenderMaterial()); float surfaceTiling = decal ? 1 : getSurfaceTiling(part->getRenderMaterial()); Vector3 normalOffset = (options.flags & Flag_RandomizeBlockAppearance) ? kNormalOffsetTable[(randomSeed >> 3) & 31] : Vector3(0, 0, 0); Vector3 normals[6] = { normalize(normalOffset + axisX), normalize(normalOffset + axisY), normalize(normalOffset + axisZ), normalize(normalOffset - axisX), normalize(normalOffset - axisY), normalize(normalOffset - axisZ), }; Vector3 tangents[6] = { -axisZ, -axisX, axisX, axisZ, -axisX, -axisX, }; Vertex* vertices = mVertices; unsigned short* indices = mIndices; if (decal) { // Note: this table is almost identical to the facesDesc below, with the exception of bottom face. // This is needed to preserve the decal orientation compared to legacy code. static const unsigned int faces[6][4] = { {6, 7, 4, 5}, {3, 7, 2, 6}, {7, 3, 5, 1}, {3, 2, 1, 0}, {5, 1, 4, 0}, {2, 6, 0, 4}, }; int face = decal->getFace(); // If the block ignores studs (block meshes), then the Texture UV generation should not take size into account as well. Vector2 uv = getDecalUV(decal, size, /* ignoreSurfaceType= */ ignoreMaterialsStuds); fillVertex(vertices[vertexOffset + 0], corners[faces[face][0]], normals[face], Vector2(uv.x, 0), Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 1], corners[faces[face][1]], normals[face], Vector2(0, 0), Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 2], corners[faces[face][2]], normals[face], Vector2(uv.x, uv.y), Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 3], corners[faces[face][3]], normals[face], Vector2(0, uv.y), Vector2(), color, extra, Vector3()); fillQuadIndices(&indices[indexOffset], vertexOffset, 0, 1, 2, 3); for (int i = 0; i < 4; ++i) extendBounds(mBboxMin, mBboxMax, corners[faces[face][i]]); } else { Vector3 facesDir[] = { -axisY, -axisZ, -axisY, -axisY, axisZ, -axisY }; unsigned faceCounter = 0; Vector2 uvs[4]; for (int face = 0; face < ARRAYSIZE(facesDesc); ++face) { const FACEDESC& desc = facesDesc[face]; Vector3 start0 = corners[desc.start0], start1 = corners[desc.start1]; int quadNumber = desc.dirZ ? tesselatedExtents.z : tesselatedExtents.y; float quadLength = desc.dirZ ? size.z : size.y; if(facesIgnoreTiling[face]) quadNumber = 1; Vector3 offset = Vector3::zero(); float uTiling = (desc.tangentZ ? localExtentsSize.z : localExtentsSize.x); uvs[0] = surfaceOffset; uvs[0].x += uTiling; uvs[1] = surfaceOffset; uvs[2] = surfaceOffset; uvs[2].x += uTiling; uvs[2].y += TESSELATION_PIECE; uvs[3] = surfaceOffset; uvs[3].y += TESSELATION_PIECE; UVAtlasInfo studsAtlasInfo = getStudsAtlasInfo((ignoreMaterialsStuds || (options.flags & Flag_IgnoreStuds)) ? NO_SURFACE : part->getConstPartPrimitive()->getSurfaceType((NormalId)face)); float uTilingStuds = studsAtlasInfo.tileX ? uTiling * 0.5f : 0; Vector2 uvStuds[4] = { Vector2(uTilingStuds, studsAtlasInfo.offsetY), Vector2(0, studsAtlasInfo.offsetY), Vector2(uTilingStuds, studsAtlasInfo.offsetY + studsAtlasInfo.scaleY), Vector2(0, studsAtlasInfo.offsetY + studsAtlasInfo.scaleY), }; Vector4 startEdgeOffset = noBlanks ? Vector4() : computeOutlineOffsetPart((NormalId)face, part); Vector4 edgeOffset = startEdgeOffset; Vector4 edge0 = Vector4(0,uTiling,0,quadLength); Vector4 edge1 = Vector4(uTiling,0,0,quadLength); Vector3 worldDir = facesDir[face] * TESSELATION_PIECE; for (int quad = 0; quad < quadNumber - 1; quad++) { Vector3 nextOffset = offset + worldDir; Vector4 nextEdgeOffset = edgeOffset + Vector4(0,0,TESSELATION_PIECE, -TESSELATION_PIECE); fillVertex(vertices[vertexOffset + faceCounter * 4 + 0], start0 + offset, normals[face], uvs[0] * surfaceTiling, uvStuds[0], color, extra, tangents[face], edge0 + edgeOffset); fillVertex(vertices[vertexOffset + faceCounter * 4 + 1], start1 + offset, normals[face], uvs[1] * surfaceTiling, uvStuds[1], color, extra, tangents[face], edge1 + edgeOffset); fillVertex(vertices[vertexOffset + faceCounter * 4 + 2], start0 + nextOffset, normals[face], uvs[2] * surfaceTiling, uvStuds[2], color, extra, tangents[face], edge0 + nextEdgeOffset); fillVertex(vertices[vertexOffset + faceCounter * 4 + 3], start1 + nextOffset, normals[face], uvs[3] * surfaceTiling, uvStuds[3], color, extra, tangents[face], edge1 + nextEdgeOffset); uvs[0].y = uvs[2].y; uvs[1].y = uvs[3].y; uvs[2].y += TESSELATION_PIECE; uvs[3].y += TESSELATION_PIECE; offset = nextOffset; edgeOffset = nextEdgeOffset; fillQuadIndices(&indices[indexOffset + faceCounter * 6], vertexOffset + faceCounter * 4, 0, 1, 2, 3); faceCounter++; } if (quadNumber > 0) { // Last quad float texRemainderFraction = (desc.dirZ ? localExtentsSize.z : localExtentsSize.y) - (quadNumber - 1)*TESSELATION_PIECE; float texMultiplier = saturate(texRemainderFraction / TESSELATION_PIECE); Vector2 uv2 = Vector2(uvs[2].x, uvs[0].y + texRemainderFraction); Vector2 uv3 = Vector2(uvs[3].x, uvs[1].y + texRemainderFraction); Vector2 uvStuds2 = Vector2(uvStuds[2].x, studsAtlasInfo.offsetY + texMultiplier * studsAtlasInfo.scaleY); Vector2 uvStuds3 = Vector2(uvStuds[3].x, studsAtlasInfo.offsetY + texMultiplier * studsAtlasInfo.scaleY); Vector4 finalEdgeOffset = startEdgeOffset + Vector4(0,0, quadLength, -quadLength); fillVertex(vertices[vertexOffset + faceCounter * 4 + 0], start0 + offset, normals[face], uvs[0] * surfaceTiling, uvStuds[0], color, extra, tangents[face], edge0 + edgeOffset); fillVertex(vertices[vertexOffset + faceCounter * 4 + 1], start1 + offset, normals[face], uvs[1] * surfaceTiling, uvStuds[1], color, extra, tangents[face], edge1 + edgeOffset); fillVertex(vertices[vertexOffset + faceCounter * 4 + 2], corners[desc.end0], normals[face], uv2 * surfaceTiling, uvStuds2, color, extra, tangents[face], edge0 + finalEdgeOffset); fillVertex(vertices[vertexOffset + faceCounter * 4 + 3], corners[desc.end1], normals[face], uv3 * surfaceTiling, uvStuds3, color, extra, tangents[face], edge1 + finalEdgeOffset); fillQuadIndices(&indices[indexOffset + faceCounter * 6], vertexOffset + faceCounter * 4, 0, 1, 2, 3); faceCounter++; } } RBXASSERT(faceCounter == quads); Vector3 boundsCenter = cframe.pointToWorldSpace(center); extendBoundsBlock(mBboxMin, mBboxMax, boundsCenter, size, axisX, axisY, axisZ); } } void GeometryGenerator::addTorso(const Vector3& size, const Vector3& center, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed) { int quads = decal ? 1 : 6; size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; mVertexCount += quads*4; mIndexCount += quads*6; if (!mVertices) return; Color4uint8 color = getColor(part, decal, NULL, options, randomSeed, false); Color4uint8 extra = getExtra(part, options); const CoordinateFrame& cframe = options.cframe; Vector3 extent = size * 0.5f; float shoulderInset = std::min(extent.x, size.z * 0.3f); Vector3 corners[8] = { Vector3(-extent.x, -extent.y, -extent.z) + center, Vector3(-extent.x, -extent.y, +extent.z) + center, Vector3(-extent.x + shoulderInset, +extent.y, -extent.z) + center, Vector3(-extent.x + shoulderInset, +extent.y, +extent.z) + center, Vector3(+extent.x, -extent.y, -extent.z) + center, Vector3(+extent.x, -extent.y, +extent.z) + center, Vector3(+extent.x - shoulderInset, +extent.y, -extent.z) + center, Vector3(+extent.x - shoulderInset, +extent.y, +extent.z) + center, }; Vector2 surfaceOffset = getSurfaceOffset(size, randomSeed, part->getRenderMaterial()); float surfaceTiling = decal ? 1 : getSurfaceTiling(part->getRenderMaterial()); Vector3 normals[6] = { normalize(Vector3( size.y, shoulderInset, 0)), Vector3(0, 1, 0), Vector3(0, 0, 1), normalize(Vector3(-size.y, shoulderInset, 0)), Vector3(0, -1, 0), Vector3(0, 0, -1), }; Vector3 tangents[6] = { -Vector3(0, 0, 1), -Vector3(1, 0, 0), Vector3(1, 0, 0), Vector3(0, 0, 1), -Vector3(1, 0, 0), -Vector3(1, 0, 0), }; Vertex* vertices = mVertices; unsigned short* indices = mIndices; static const FACEDESC facesDesc[] = { { 6,7,4,5, false, true, }, // Face 0 { 3,7,2,6, true, false, }, // Face 1 { 7,3,5,1, false, false, }, // Face 2 { 3,2,1,0, false, true, }, // Face 3 { 0,4,1,5, true, false, }, // Face 4 { 2,6,0,4, false, false, }, // Face 5 }; if (decal) { // Note: this table is almost identical to the facesDesc below, with the exception of bottom face. // This is needed to preserve the decal orientation compared to legacy code. static const unsigned int faces[6][4] = { {6, 7, 4, 5}, {3, 7, 2, 6}, {7, 3, 5, 1}, {3, 2, 1, 0}, {5, 1, 4, 0}, {2, 6, 0, 4}, }; int face = decal->getFace(); // If the block ignores studs (block meshes), then the Texture UV generation should not take size into account as well. Vector2 uv = getDecalUV(decal, size, /* ignoreSurfaceType= */ false); Vector3 normal = cframe.vectorToWorldSpace(normals[face]); fillVertex(vertices[vertexOffset + 0], cframe.pointToWorldSpace(corners[faces[face][0]]), normal, Vector2(uv.x, 0), Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 1], cframe.pointToWorldSpace(corners[faces[face][1]]), normal, Vector2(0, 0), Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 2], cframe.pointToWorldSpace(corners[faces[face][2]]), normal, Vector2(uv.x, uv.y), Vector2(), color, extra, Vector3()); fillVertex(vertices[vertexOffset + 3], cframe.pointToWorldSpace(corners[faces[face][3]]), normal, Vector2(0, uv.y), Vector2(), color, extra, Vector3()); fillQuadIndices(&indices[indexOffset], vertexOffset, 0, 1, 2, 3); for (int i = 0; i < 4; ++i) extendBounds(mBboxMin, mBboxMax, cframe.pointToWorldSpace(corners[faces[face][i]])); } else { unsigned faceCounter = 0; for (int face = 0; face < ARRAYSIZE(facesDesc); ++face) { const FACEDESC& desc = facesDesc[face]; Vector3 start0 = corners[desc.start0], start1 = corners[desc.start1]; Vector3 end0 = corners[desc.end0], end1 = corners[desc.end1]; float uTiling = (desc.tangentZ ? size.z : size.x); float vTiling = (desc.dirZ ? size.z : size.y); Vector3 normal = cframe.vectorToWorldSpace(normals[face]); Vector3 tangent = cframe.vectorToWorldSpace(tangents[face]); fillVertex(vertices[vertexOffset + faceCounter * 4 + 0], cframe.pointToWorldSpace(start0), normal, (surfaceOffset + Vector2(uTiling, 0)) * surfaceTiling, Vector2(), color, extra, tangent); fillVertex(vertices[vertexOffset + faceCounter * 4 + 1], cframe.pointToWorldSpace(start1), normal, surfaceOffset * surfaceTiling, Vector2(), color, extra, tangent); fillVertex(vertices[vertexOffset + faceCounter * 4 + 2], cframe.pointToWorldSpace(end0), normal, (surfaceOffset + Vector2(uTiling, vTiling)) * surfaceTiling, Vector2(), color, extra, tangent); fillVertex(vertices[vertexOffset + faceCounter * 4 + 3], cframe.pointToWorldSpace(end1), normal, (surfaceOffset + Vector2(0, vTiling)) * surfaceTiling, Vector2(), color, extra, tangent); fillQuadIndices(&indices[indexOffset + faceCounter * 6], vertexOffset + faceCounter * 4, 0, 1, 2, 3); faceCounter++; } for (int i = 0; i < 8; ++i) extendBounds(mBboxMin, mBboxMax, cframe.pointToWorldSpace(corners[i])); RBXASSERT(faceCounter == quads); } } struct TrussQuadBuilder { GeometryGenerator::Vertex* vertices; unsigned short* indices; unsigned int vertexOffset; unsigned int indexOffset; Vector3* bboxMin; Vector3* bboxMax; Color4uint8 color; Color4uint8 extra; CoordinateFrame cframe; Vector2 surfaceOffset; float surfaceTiling; TrussQuadBuilder(GeometryGenerator::Vertex* vertices, unsigned short* indices, unsigned int vertexOffset, unsigned int indexOffset, Vector3* bboxMin, Vector3* bboxMax, const Vector3& size, PartInstance* part, const GeometryGenerator::Options& options, unsigned int randomSeed) { this->vertices = vertices; this->indices = indices; this->vertexOffset = vertexOffset; this->indexOffset = indexOffset; this->bboxMin = bboxMin; this->bboxMax = bboxMax; color = getColor(part, NULL, NULL, options, randomSeed, false); extra = getExtra(part, options); cframe = options.cframe; surfaceOffset = getSurfaceOffset(size, randomSeed, part->getRenderMaterial()); surfaceTiling = getSurfaceTiling(part->getRenderMaterial()); } void emit(const Vector3& pos, const Vector3& udir, float usize, const Vector3& vdir, float vsize) { if (vertices) { Vector3 corner0 = pos + udir * std::max(0.f, usize) + vdir * std::max(0.f, vsize); Vector3 corner1 = pos + udir * std::min(0.f, usize) + vdir * std::max(0.f, vsize); Vector3 corner2 = pos + udir * std::max(0.f, usize) + vdir * std::min(0.f, vsize); Vector3 corner3 = pos + udir * std::min(0.f, usize) + vdir * std::min(0.f, vsize); Vector3 localNormal = cross(udir, vdir); Vector3 normal = cframe.vectorToWorldSpace(localNormal); Vector3 tangent = cframe.vectorToWorldSpace(udir); fillVertex(vertices[vertexOffset + 0], cframe.pointToWorldSpace(corner0), normal, (surfaceOffset + Vector2(dot(corner0, udir), dot(corner0, vdir))) * surfaceTiling, Vector2(), color, extra, tangent); fillVertex(vertices[vertexOffset + 1], cframe.pointToWorldSpace(corner1), normal, (surfaceOffset + Vector2(dot(corner1, udir), dot(corner1, vdir))) * surfaceTiling, Vector2(), color, extra, tangent); fillVertex(vertices[vertexOffset + 2], cframe.pointToWorldSpace(corner2), normal, (surfaceOffset + Vector2(dot(corner2, udir), dot(corner2, vdir))) * surfaceTiling, Vector2(), color, extra, tangent); fillVertex(vertices[vertexOffset + 3], cframe.pointToWorldSpace(corner3), normal, (surfaceOffset + Vector2(dot(corner3, udir), dot(corner3, vdir))) * surfaceTiling, Vector2(), color, extra, tangent); fillQuadIndices(&indices[indexOffset], vertexOffset, 0, 1, 2, 3); extendBounds(*bboxMin, *bboxMax, cframe.pointToWorldSpace(corner0)); extendBounds(*bboxMin, *bboxMax, cframe.pointToWorldSpace(corner1)); extendBounds(*bboxMin, *bboxMax, cframe.pointToWorldSpace(corner2)); extendBounds(*bboxMin, *bboxMax, cframe.pointToWorldSpace(corner3)); } vertexOffset += 4; indexOffset += 6; } }; void GeometryGenerator::addTruss(int style, const Vector3& size, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed) { if (decal) return; switch (size.primaryAxis()) { case Vector3::X_AXIS: return addTrussY(Vector3(-size.x / 2, 0, 0), Vector3(0, 0, -1), Vector3(1, 0, 0), Vector3(0, -1, 0), static_cast(size.x / 2 + 0.005f), style, size, part, options, randomSeed); case Vector3::Y_AXIS: return addTrussY(Vector3(0, -size.y / 2, 0), Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1), static_cast(size.y / 2 + 0.005f), style, size, part, options, randomSeed); case Vector3::Z_AXIS: return addTrussY(Vector3(0, 0, -size.z / 2), Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(0, -1, 0), static_cast(size.z / 2 + 0.005f), style, size, part, options, randomSeed); default: break; } } void GeometryGenerator::addTrussY(const Vector3& origin, const Vector3& axisX, const Vector3& axisY, const Vector3& axisZ, int segments, int style, const Vector3& size, PartInstance* part, const Options& options, unsigned int randomSeed) { if (segments == 0) return; int styleAlt = (style == ExtrudedPartInstance::BRIDGE_STYLE_CROSS_BEAM) ? ExtrudedPartInstance::NO_CROSS_BEAM : style; addTrussSideX(origin - axisX - axisZ, axisX, axisY, axisZ, false, segments, styleAlt, size, part, options, randomSeed); addTrussSideX(origin - axisX + axisZ, -axisZ, axisY, axisX, true, segments, style, size, part, options, randomSeed); addTrussSideX(origin + axisX + axisZ, -axisX, axisY, -axisZ, false, segments, styleAlt, size, part, options, randomSeed); addTrussSideX(origin + axisX - axisZ, axisZ, axisY, -axisX, false, segments, style, size, part, options, randomSeed); } void GeometryGenerator::addTrussSideX(const Vector3& origin, const Vector3& axisX, const Vector3& axisY, const Vector3& axisZ, bool reverseBridge, int segments, int style, const Vector3& size, PartInstance* part, const Options& options, unsigned int randomSeed) { TrussQuadBuilder builder(mVertices, mIndices, mVertexCount, mIndexCount, &mBboxMin, &mBboxMax, size, part, options, randomSeed); // Build vertical frame builder.emit(origin, -axisX, -0.25f, axisY, segments * 2); builder.emit(origin, axisZ, 0.25f, axisY, segments * 2); builder.emit(origin + axisX * 0.25f, -axisZ, -0.25f, axisY, segments * 2); builder.emit(origin + axisZ * 0.25f, axisX, 0.25f, axisY, segments * 2); // Build horizontal frames for (int i = 0; i <= segments; ++i) { Vector3 frameOrigin = origin + axisY * (i == 0 ? 0 : 2 * i - 0.25f); float frameHeight = (i == 0 || i == segments) ? 0.25f : 0.5f; float bottomStart = (i == 0) ? 0.f : 0.25f; float topStart = (i == segments) ? 0.f : 0.25f; builder.emit(frameOrigin + axisX * 0.25f, -axisX, -1.5f, axisY, frameHeight); builder.emit(frameOrigin + axisX * 0.25f + axisZ * 0.25f, axisX, 1.5f, axisY, frameHeight); builder.emit(frameOrigin + axisX * bottomStart, axisX, 1.75f - bottomStart, axisZ, 0.25f); builder.emit(frameOrigin + axisX * topStart + axisY * frameHeight, -axisX, -(1.75f - topStart), axisZ, 0.25f); } // Build supports if (style != ExtrudedPartInstance::NO_CROSS_BEAM && segments > 1) { for (int i = 0; i < segments; ++i) { bool reverse = style == ExtrudedPartInstance::FULL_ALTERNATING_CROSS_BEAM ? (i % 2 == 1) : (reverseBridge ? i >= segments / 2 : i < segments / 2); Vector3 frameAxisY = reverse ? -axisY : axisY; Vector3 frameOrigin = origin + axisY * 2 * i + (reverse ? axisY * 2 : Vector3()); Vector3 supportAxisU = (frameAxisY - axisX) * 0.707106f * (reverse ? -1 : 1); Vector3 supportAxisV = (axisX + frameAxisY) * 0.707106f; float supportLength = 2.474873f; // sqrt(sqr(1.75) * 2) Vector3 supportOrigin = frameOrigin + (frameAxisY + axisX) * 0.125f + axisZ * 0.0625f - supportAxisU * 0.125f; builder.emit(supportOrigin, supportAxisU, 0.25f, supportAxisV, supportLength); builder.emit(supportOrigin + axisZ * 0.125f, -supportAxisU, -0.25f, supportAxisV, supportLength); builder.emit(supportOrigin, -axisZ, -0.125f, supportAxisV, supportLength); builder.emit(supportOrigin + axisZ * 0.125f + supportAxisU * 0.25f, axisZ, -0.125f, supportAxisV, supportLength); } } mVertexCount = builder.vertexOffset; mIndexCount = builder.indexOffset; } void GeometryGenerator::addPartImpl(PartInstance* part, Decal* decal, const Options& options, const Resources& resources, const HumanoidIdentifier* hi, bool ignoreMaterialsStuds) { DataModelMesh* specialShape = getSpecialShape(part); unsigned int randomSeed = boost::hash_value(part); // handle mesh parts if (resources.fileMeshData) { return addFileMesh(resources.fileMeshData.get(), specialShape, part, decal, hi, options); } // handle parts with special shape if (specialShape) { if (SpecialShape* shape = specialShape->fastDynamicCast()) { Vector3 offset = shape->getOffset(); switch (shape->getMeshType()) { case SpecialShape::BRICK_MESH: return addBlock(part->getPartSizeXml() * abs(shape->getScale()), offset, part, decal, options, randomSeed, ignoreMaterialsStuds); case SpecialShape::SPHERE_MESH: return addSphere(part->getPartSizeXml() * abs(shape->getScale()), offset, part, decal, options, randomSeed, ignoreMaterialsStuds); case SpecialShape::CYLINDER_MESH: return addCylinder(part->getPartSizeXml() * abs(shape->getScale()), offset, part, decal, options, randomSeed, ignoreMaterialsStuds); case SpecialShape::TORSO_MESH: return addTorso(part->getPartSizeXml() * abs(shape->getScale()), offset, part, decal, options, randomSeed); case SpecialShape::WEDGE_MESH: return addWedge(part->getPartSizeXml() * abs(shape->getScale()), offset, part, decal, options, randomSeed, ignoreMaterialsStuds); default: // mesh type not supported yet return; } } else if (BlockMesh* shape = specialShape->fastDynamicCast()) { return addBlock(part->getPartSizeXml() * abs(shape->getScale()), shape->getOffset(), part, decal, options, randomSeed, /* ignoreMaterialsStuds= */ true); } else if (CylinderMesh* shape = specialShape->fastDynamicCast()) { return addCylinder(part->getPartSizeXml() * abs(shape->getScale()), shape->getOffset(), part, decal, options, randomSeed, /* ignoreMaterialsStuds= */ true); } // Unknown shape type, or a file mesh with an empty meshData (which means that the mesh data could not be loaded) return; } // handle parts switch(part->getPartType()) { case BLOCK_PART: return addBlock(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds); case BALL_PART: return addSphere(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds); case CYLINDER_PART: return addCylinder(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds); case TRUSS_PART: return addTruss(static_cast(part)->getVisualTrussStyle(), part->getPartSizeXml(), part, decal, options, randomSeed); case WEDGE_PART: return addWedge(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds); case CORNERWEDGE_PART: return addWedge(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds); case OPERATION_PART: return addOperation(part, decal, options, resources, randomSeed); default: // unknown geometry type, skip it return; } } template static boost::shared_ptr fetchMesh(const AssetIdType& meshFile, Instance* instance, AsyncResult* asyncResult) { if (meshFile.isNull()) return boost::shared_ptr(); ContentProvider* contentProvider = ServiceProvider::find(instance); CacheableContentProvider* mcp = ServiceProvider::find(contentProvider); if (!mcp) { asyncResult->returnResult(AsyncHttpQueue::Failed); return boost::shared_ptr(); } AsyncHttpQueue::RequestResult reqResult; boost::shared_ptr meshData = boost::static_pointer_cast(mcp->requestContent(meshFile, ContentProvider::PRIORITY_MESH, true, reqResult)); switch (reqResult) { case AsyncHttpQueue::Succeeded: return meshData; case AsyncHttpQueue::Waiting: asyncResult->returnWaitingFor(meshFile); return boost::shared_ptr(); default: asyncResult->returnResult(reqResult); return boost::shared_ptr(); } } GeometryGenerator::Resources GeometryGenerator::fetchResources(PartInstance* part, const HumanoidIdentifier* hi, unsigned int flags, AsyncResult* asyncResult) { DataModelMesh* specialShape = getSpecialShape(part); // handle parts with file mesh if (FileMesh* shape = getFileMesh(specialShape)) { boost::shared_ptr fileMeshData = fetchMesh(shape->getMeshId(), part, asyncResult); return Resources(fileMeshData ? fileMeshData : kDummyMeshData); } // handle body parts if (Humanoid* humanoid = getHumanoid(part)) { RBXASSERT(hi && hi->humanoid == humanoid); MeshId meshId = getMeshIdForBodyPart(*hi, part, specialShape, flags); if (!meshId.isNull()) { boost::shared_ptr fileMeshData = fetchMesh(meshId, part, asyncResult); return Resources(fileMeshData ? fileMeshData : kDummyMeshData); } } // handle head mesh if (part->getCookie() & PartCookie::HAS_HEADMESH) { // Return the default head; scaling is taken care of in getMeshScale boost::shared_ptr fileMeshData = fetchMesh(MeshId("rbxasset://fonts/head.mesh"), part, asyncResult); return Resources(fileMeshData ? fileMeshData : kDummyMeshData); } if (FFlag::StudioCSGAssets && !FFlag::CSGLoadBlocking) { if (PartOperation* operation = part->fastDynamicCast()) { if (operation->hasAsset()) { shared_ptr partOperationAsset = fetchMesh(operation->getAssetId(), part, asyncResult); if (partOperationAsset) return Resources(partOperationAsset->getRenderMesh()); } } } // no mesh return Resources(); } GeometryGenerator::GeometryGenerator() : mVertices(NULL) , mIndices(NULL) , mVertexCount(0) , mIndexCount(0) , mBboxMin(Vector3::maxFinite()) , mBboxMax(Vector3::minFinite()) { } GeometryGenerator::GeometryGenerator(Vertex* vertices, unsigned short* indices, unsigned int vertexOffset) : mVertices(vertices) , mIndices(indices) , mVertexCount(vertexOffset) , mIndexCount(0) , mBboxMin(Vector3::maxFinite()) , mBboxMax(Vector3::minFinite()) { RBXASSERT(vertices && indices); } void GeometryGenerator::reset() { mVertexCount = 0; mIndexCount = 0; resetBounds(); } void GeometryGenerator::resetBounds() { mBboxMin = Vector3::maxFinite(); mBboxMax = Vector3::minFinite(); } void GeometryGenerator::resetBounds(const Vector3& min, const Vector3& max) { mBboxMin = min; mBboxMax = max; } void GeometryGenerator::addInstance(PartInstance* part, Decal* decal, const Options& options, const Resources& resources, const HumanoidIdentifier* humanoidIdentifier /*=NULL*/) { addPartImpl(part, decal, options, resources, humanoidIdentifier, /* ignoreMaterialsStuds= */ false); // check that our indices did not overflow (caller should ensure this before calling addDecal) RBXASSERT(!mVertices || mVertexCount < (1 << 16)); } static bool shouldRandomizeColor(VisualEngine* visualEngine, const PartInstance& part, Decal* decal) { bool randomizeColor = false; if (Lighting* lighting = visualEngine->getLighting()) randomizeColor = lighting->getOutlines(); if (randomizeColor) { if (decal && decal->isA()) randomizeColor = false; else { randomizeColor = false; for (unsigned i = 0; i < NORM_UNDEFINED; ++i) if (part.getSurfaceType((NormalId)i) != NO_SURFACE_NO_OUTLINES) { randomizeColor = true; break; } } } return randomizeColor; } GeometryGenerator::Options::Options(VisualEngine* visualEngine, const PartInstance& part, Decal* decal, const CoordinateFrame& localTransform, unsigned int materialFlags, const Vector4& uvOffsetScale, unsigned int extra) { unsigned int randomizeFlag = 0; if (FFlag::NoRandomColorsWithoutOutlines) { randomizeFlag = shouldRandomizeColor(visualEngine, part, decal) ? GeometryGenerator::Flag_RandomizeBlockAppearance : 0; } else { randomizeFlag = decal && decal->isA() ? 0 : GeometryGenerator::Flag_RandomizeBlockAppearance; } unsigned int colorFlag = (materialFlags & MaterialGenerator::Result_UsesCompositTexture) ? GeometryGenerator::Flag_VertexColorMesh // composited parts w/meshes and accoutrements need vertex color, but part color is baked in : (materialFlags & MaterialGenerator::Result_UsesTexture) ? GeometryGenerator::Flag_VertexColorMesh // meshes w/texture need vertex color, part color does not apply : GeometryGenerator::Flag_VertexColorPart; // no texture means that vertex color does not have any effect unsigned int colorOrderFlag = visualEngine->getDevice()->getCaps().colorOrderBGR ? GeometryGenerator::Flag_DiffuseColorBGRA : 0; unsigned int studFlag = (visualEngine->getSettings()->getDrawConnectors() || part.getRenderMaterial() == RBX::PLASTIC_MATERIAL) ? 0 : GeometryGenerator::Flag_IgnoreStuds; unsigned int extraFlag = visualEngine->getRenderCaps()->getSkinningBoneCount() > 0 ? 0 : GeometryGenerator::Flag_ExtraIsZero; unsigned int flags = randomizeFlag | colorFlag | colorOrderFlag | studFlag | extraFlag; this->flags = flags; this->extra = extra; if (uvOffsetScale != G3D::Vector4(0.f, 0.f, 1.f, 1.f)) { this->flags |= GeometryGenerator::Flag_TransformUV; this->uvframe = uvOffsetScale; } this->cframe = localTransform; } void GeometryGenerator::addCSGPrimitive(PartInstance* part) { Graphics::GeometryGenerator::Options options; options.flags |= Graphics::GeometryGenerator::Flag_VertexColorPart; // Setting the gfx cookie artificially so that the special mesh will // be searched for. We need to do this because this part may not // have been through the renderer yet to receive the cookie. unsigned int oldCookie = part->getCookie(); part->setCookie(oldCookie | PartCookie::HAS_SPECIALSHAPE); // Trusses are not manifold so skip them for now. if (part->getPartType() == TRUSS_PART) { return; } addPartImpl(part, NULL, options, Resources(), /*humanoidIdentifier=*/ NULL, /* ignoreMaterialsStuds= */ true); part->setCookie(oldCookie); } void GeometryGenerator::addOperation(PartInstance* part, Decal* decal, const Options& options, const Resources& resources, unsigned int randomSeed) { bool renderCollision = PartOperation::renderCollisionData; PartOperation* operation = part->fastDynamicCast(); if (!operation) return; shared_ptr csgMesh = resources.csgMeshData; if (!csgMesh) { if (!FFlag::StudioCSGAssets || FFlag::CSGLoadBlocking) { if (!operation->getMesh()) return; if (operation->getMesh()->isBadMesh()) return; csgMesh = operation->getMesh(); } else { if (operation->getRenderMesh() && !operation->getRenderMesh()->isBadMesh()) { csgMesh = operation->getRenderMesh(); } } } if (!csgMesh) return; if (renderCollision) { addOperationDecompositionDebug(operation, part, decal, options, randomSeed); return; } size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; CSGMesh* modelData = csgMesh.get(); const std::vector& meshVertices = modelData->getVertices(); const std::vector& meshIndices = modelData->getIndices(); if (!FFlag::FixGlowingCSG || !decal) { mVertexCount += meshVertices.size(); mIndexCount += meshIndices.size(); } else { mVertexCount += csgMesh->getVertexRemap(decal->getFace()).size(); mIndexCount += csgMesh->getIndexRemap(decal->getFace()).size(); } if (!mVertices) return; Vertex* vertices = mVertices; unsigned short* indices = mIndices; Vector3 size = part->getPartSizeXml(); RBX::Color4uint8 partColor = getColor(part, decal, NULL, options, randomSeed, true); const RBX::Color4uint8& extra = getExtra(part, options); bool isNegate = part->fastDynamicCast() != 0; bool usePartColor = operation->getUsePartColor(); if (isNegate) { partColor = RBX::Color4uint8(255, 154, 161, 150); if ((options.flags & GeometryGenerator::Flag_DiffuseColorBGRA) != 0) std::swap(partColor.r, partColor.b); } Vector3 scale = operation->getSizeDifference(); const CoordinateFrame& cframe = options.cframe; Vector2 surfaceOffset = getSurfaceOffset(size, randomSeed, part->getRenderMaterial()); float surfaceTiling = decal ? 1 : getSurfaceTiling(part->getRenderMaterial()); Vector3 invSize = Vector3(1.0f/size.x, 1.0f/size.y, 1.0f/size.z); bool isScaled = operation->getInitialSize() != operation->getPartSizeXml(); Vector2 uvStuds; // blank for now if (!FFlag::FixGlowingCSG) { for (size_t vi = 0; vi < meshVertices.size(); vi++) { const CSGVertex& vert = meshVertices[vi]; Vector3 pos = vert.pos * scale; Vector3 normal = vert.normal; Vector3 tangent = vert.tangent; Color4uint8 vertColor = partColor; if (!isNegate && !usePartColor && !decal) { vertColor = meshVertices[vi].color; vertColor.a = partColor.a; if ((options.flags & GeometryGenerator::Flag_DiffuseColorBGRA) != 0) std::swap(vertColor.r, vertColor.b); } Vector2 uv = vert.uv; if (isScaled) uv = vert.generateUv(pos); if (decal) { if (vert.extra.r - 1 != decal->getFace()) { uv = Vector2(); vertColor.a = 0.0f; } else { DecalTexture* texture = decal->fastDynamicCast(); if (texture) { uv /= texture->getStudsPerTile(); } else { switch (decal->getFace()) { case NORM_X: case NORM_X_NEG: uv = uv * Vector2(invSize.z, invSize.y) + Vector2(0.5f, 0.5f); break; case NORM_Y: case NORM_Y_NEG: uv = uv * Vector2(invSize.x, invSize.z) + Vector2(0.5f, 0.5f); break; case NORM_Z: case NORM_Z_NEG: uv = uv * Vector2(invSize.x, invSize.y) + Vector2(0.5f, 0.5f); break; default: break; } } } } else { uv = Vector2(uv.x * surfaceTiling + surfaceOffset.x, uv.y * surfaceTiling + surfaceOffset.y); } pos = cframe.pointToWorldSpace(pos); normal = cframe.vectorToWorldSpace(normal); tangent = cframe.vectorToWorldSpace(tangent); fillVertex(vertices[vertexOffset + vi], pos, normal, uv, uvStuds, vertColor /*Color4uint8(rand() % 255, rand() % 255, rand() % 255, 255)*/, extra, tangent); } for (size_t ii = 0; ii < meshIndices.size(); ii++) { indices[indexOffset + ii] = vertexOffset + meshIndices[ii]; } } else { unsigned numberOfVerts = decal ? csgMesh->getVertexRemap(decal->getFace()).size() : meshVertices.size(); unsigned int vertId = 0; for (size_t vi = 0; vi < numberOfVerts; vi++) { const CSGVertex& vert = decal ? meshVertices[csgMesh->getVertexRemap(decal->getFace())[vi]] : meshVertices[vi]; Vector3 pos = vert.pos * scale; Vector3 normal = vert.normal; Vector3 tangent = vert.tangent; Color4uint8 vertColor = partColor; if (!isNegate && !usePartColor && !decal) { vertColor = meshVertices[vi].color; vertColor.a = partColor.a; if ((options.flags & GeometryGenerator::Flag_DiffuseColorBGRA) != 0) std::swap(vertColor.r, vertColor.b); } Vector2 uv = vert.uv; if (isScaled) uv = vert.generateUv(pos); if (decal) { DecalTexture* texture = decal->fastDynamicCast(); if (texture) { uv /= texture->getStudsPerTile(); } else { switch (decal->getFace()) { case NORM_X: case NORM_X_NEG: uv = uv * Vector2(invSize.z, invSize.y) + Vector2(0.5f, 0.5f); break; case NORM_Y: case NORM_Y_NEG: uv = uv * Vector2(invSize.x, invSize.z) + Vector2(0.5f, 0.5f); break; case NORM_Z: case NORM_Z_NEG: uv = uv * Vector2(invSize.x, invSize.y) + Vector2(0.5f, 0.5f); break; default: break; } } } else { uv = Vector2(uv.x * surfaceTiling + surfaceOffset.x, uv.y * surfaceTiling + surfaceOffset.y); } pos = cframe.pointToWorldSpace(pos); normal = cframe.vectorToWorldSpace(normal); tangent = cframe.vectorToWorldSpace(tangent); fillVertex(vertices[vertexOffset + vertId], pos, normal, uv, uvStuds, vertColor , extra, tangent); vertId++; } if (!decal) { for (size_t ii = 0; ii < meshIndices.size(); ii++) indices[indexOffset + ii] = vertexOffset + meshIndices[ii]; } else { const std::vector& indexFaceRamep = csgMesh->getIndexRemap(decal->getFace()); for (size_t ii = 0; ii < indexFaceRamep.size(); ii++) indices[indexOffset + ii] = vertexOffset + indexFaceRamep[ii]; } } Vector3 boundsCenter = cframe.pointToWorldSpace(RBX::Vector3::zero()); Vector3 axisX = Vector3(cframe.rotation[0][0], cframe.rotation[1][0], cframe.rotation[2][0]); Vector3 axisY = Vector3(cframe.rotation[0][1], cframe.rotation[1][1], cframe.rotation[2][1]); Vector3 axisZ = Vector3(cframe.rotation[0][2], cframe.rotation[1][2], cframe.rotation[2][2]); extendBoundsBlock(mBboxMin, mBboxMax, boundsCenter, part->getPartSizeXml(), axisX, axisY, axisZ); } void GeometryGenerator::addOperationDecompositionDebug(PartOperation* operation, PartInstance* part, Decal* decal, const Options& options, unsigned int randomSeed) { if (FFlag::CSGPhysicsLevelOfDetailEnabled && operation->getCollisionFidelity() == CollisionFidelity_Box) { fillBlockShapeDebug(part, options); return; } fillDecompShapeDebug(operation, part, options); } void GeometryGenerator::fillBlockShapeDebug(PartInstance* part, const Options &options) { size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; LcmRand randGen; randGen.setSeed(static_cast(reinterpret_cast(part))); unsigned int numfaces = 6; mVertexCount += numfaces*4; mIndexCount += numfaces*6; Color4uint8 color = RBX::Color4uint8(randGen.value() % 255, randGen.value() % 255, randGen.value() % 255, 100); Color4uint8 extra = getExtra(part, options); const Vector3 size = part->getPartSizeUi(); const CoordinateFrame& cframe = options.cframe; const Vector3 center = Vector3::zero(); if (!mVertices) return; Vertex* vertices = mVertices; unsigned short* indices = mIndices; Vector3 axisX = Vector3(cframe.rotation[0][0], cframe.rotation[1][0], cframe.rotation[2][0]); Vector3 axisY = Vector3(cframe.rotation[0][1], cframe.rotation[1][1], cframe.rotation[2][1]); Vector3 axisZ = Vector3(cframe.rotation[0][2], cframe.rotation[1][2], cframe.rotation[2][2]); Vector3 extent = size * 0.5f; Vector3 cornerO = cframe.pointToWorldSpace(Vector3(center - extent)); Vector3 cornerX = axisX * size.x; Vector3 cornerY = axisY * size.y; Vector3 cornerZ = axisZ * size.z; Vector3 corners[8] = { cornerO, cornerO + cornerZ, cornerO + cornerY, cornerO + cornerY + cornerZ, cornerO + cornerX, cornerO + cornerX + cornerZ, cornerO + cornerX + cornerY, cornerO + cornerX + cornerY + cornerZ, }; Vector3 normal; // Set Normals to Z normal.x = 0; normal.y = 0; normal.z = 1; Vector3 tangent; tangent.x = 0; tangent.y = 0; tangent.z = 1; // Set Normals to Z Vector2 uv; uv.x = 0; uv.y = 0; static const unsigned int faces[6][4] = { {6, 7, 4, 5}, {3, 7, 2, 6}, {7, 3, 5, 1}, {3, 2, 1, 0}, {5, 1, 4, 0}, {2, 6, 0, 4}, }; for (unsigned int face = 0; face < numfaces; face++) { unsigned int internalOffset = face*4; fillVertex(vertices[internalOffset + vertexOffset + 0], corners[faces[face][0]], normal, uv,uv, color, extra, Vector3()); fillVertex(vertices[internalOffset + vertexOffset + 1], corners[faces[face][1]], normal, uv,uv, color, extra, Vector3()); fillVertex(vertices[internalOffset + vertexOffset + 2], corners[faces[face][2]], normal, uv,uv, color, extra, Vector3()); fillVertex(vertices[internalOffset + vertexOffset + 3], corners[faces[face][3]], normal, uv,uv, color, extra, Vector3()); unsigned int internalIndexOffset = face*6; fillQuadIndices(&indices[internalIndexOffset + indexOffset], internalOffset + vertexOffset, 0, 1, 2, 3); } Vector3 boundsCenter = cframe.pointToWorldSpace(RBX::Vector3::zero()); extendBoundsBlock(mBboxMin, mBboxMax, boundsCenter, part->getPartSizeXml(), axisX, axisY, axisZ); } void GeometryGenerator::fillDecompShapeDebug(PartOperation* operation, PartInstance* part, const Options &options) { Vector3 scale; int currentVersion = 0; if (operation->getPrimitive(operation)->getGeometry()->getGeometryType() != RBX::Geometry::GEOMETRY_TRI_MESH) return; size_t vertexOffset = mVertexCount; size_t indexOffset = mIndexCount; int childIndexOffset = 0; int childVertexOffset = 0; if (operation->getNonKeyPhysicsData() == "") return; btVector3 btScale; std::vector meshConvexes = TriangleMesh::getDecompConvexes(operation->getNonKeyPhysicsData(), currentVersion, btScale, false); if (currentVersion == 0) return; for (unsigned int i = 0; i < meshConvexes.size(); i++) { mVertexCount += meshConvexes[i].vertices.size(); mIndexCount += meshConvexes[i].indices.size(); } if (!mVertices) return; Vertex* vertices = mVertices; unsigned short* indices = mIndices; const RBX::Color4uint8& extra = getExtra(part, options); const CoordinateFrame& cframe = options.cframe; Vector3 axisX = Vector3(cframe.rotation[0][0], cframe.rotation[1][0], cframe.rotation[2][0]); Vector3 axisY = Vector3(cframe.rotation[0][1], cframe.rotation[1][1], cframe.rotation[2][1]); Vector3 axisZ = Vector3(cframe.rotation[0][2], cframe.rotation[1][2], cframe.rotation[2][2]); scale = operation->calculateSizeDifference(operation->getPartSizeXml()); LcmRand randGen; randGen.setSeed(static_cast(reinterpret_cast(operation))); for (unsigned int i = 0; i < meshConvexes.size(); i++) { const RBX::Color4uint8 color = RBX::Color4uint8(randGen.value() % 255, randGen.value() % 255, randGen.value() % 255, 100); for (unsigned int j = 0; j < meshConvexes[i].vertices.size(); j++) { Vector3 pos; btVector3 offsetVertex = meshConvexes[i].transform * meshConvexes[i].vertices[j]; pos.x = offsetVertex.x(); pos.y = offsetVertex.y(); pos.z = offsetVertex.z(); Vector3 normal; // Set Normals to Z normal.x = 0; normal.y = 0; normal.z = 1; Vector3 tangent; tangent.x = 0; tangent.y = 0; tangent.z = 1; // Set Normals to Z Vector2 uv; uv.x = 0; uv.y = 0; pos *= scale; pos = cframe.pointToWorldSpace(pos); normal = cframe.vectorToWorldSpace(normal); tangent = cframe.vectorToWorldSpace(tangent); fillVertex(vertices[vertexOffset + childVertexOffset + j], pos, normal, uv,uv, color, extra, tangent); } for (unsigned int j = 0; j < meshConvexes[i].indices.size(); j++) { indices[indexOffset + childIndexOffset + j] = vertexOffset + childVertexOffset + meshConvexes[i].indices[j]; } childIndexOffset += meshConvexes[i].indices.size(); childVertexOffset += meshConvexes[i].vertices.size(); } Vector3 boundsCenter = cframe.pointToWorldSpace(RBX::Vector3::zero()); extendBoundsBlock(mBboxMin, mBboxMax, boundsCenter, part->getPartSizeXml(), axisX, axisY, axisZ); } } }