Files
2025-09-18 17:55:52 -04:00

2932 lines
106 KiB
C++

#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<FileMeshData> 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<SpecialShape>())
{
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<CylinderMesh>())
{
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<BlockMesh>())
{
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<int>(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<FileMesh>(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<SpecialShape>())
{
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<bool vertical> Vector3 verticalRotate(const Vector3& pos)
{
return vertical ? Vector3(pos.y, -pos.x, pos.z) : pos;
}
template<bool vertical> Vector2 verticalRotateDecal(const Vector2& pos)
{
return vertical ? Vector2(-pos.y, pos.x) : pos;
}
template<bool vertical> 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<bool vertical> Vector2 getDecalUVVertical(Decal* decal, const Vector3& size, bool ignoreSurfaceType)
{
if (!ignoreSurfaceType)
{
if (DecalTexture* texture = decal->fastDynamicCast<DecalTexture>())
{
switch (verticalRotate<vertical>(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<false>(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>(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<bool vertical> 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<vertical>(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<vertical>(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<vertical>(decal, genSize, ignoreMaterialsStuds) : Vector2(primaryDimension,primaryDimension);
Vector2 normal2d;
Vector3 localPos = computeCylinderPosition(fanPoint, genSize, radius, normal2d);
Vector3 outPos = options.cframe.pointToWorldSpace(verticalRotate<vertical>(localPos) + center);
Vector3 normal = options.cframe.vectorToWorldSpace(verticalRotate<vertical>(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<vertical>(localPos) + center);
Vector2 mainUV = (verticalRotateDecal<vertical>(normal2d.yx())*capDesc.normal2dToUV +Vector2(1,1))*0.5f * mainMultiplier;
Vector3 normal = options.cframe.vectorToWorldSpace(verticalRotate<vertical>(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<vertical>(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<vertical>(localPos) + center);
Vector3 worldNormal = options.cframe.vectorToWorldSpace(verticalRotate<vertical>(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<bool corner> 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<int>(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<int>(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<int>(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<SpecialShape>())
{
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<false>(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<false>(part->getPartSizeXml() * abs(shape->getScale()), offset, part, decal, options, randomSeed, ignoreMaterialsStuds);
default:
// mesh type not supported yet
return;
}
}
else if (BlockMesh* shape = specialShape->fastDynamicCast<BlockMesh>())
{
return addBlock(part->getPartSizeXml() * abs(shape->getScale()), shape->getOffset(), part, decal, options, randomSeed, /* ignoreMaterialsStuds= */ true);
}
else if (CylinderMesh* shape = specialShape->fastDynamicCast<CylinderMesh>())
{
return addCylinder<true>(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<false>(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds);
case TRUSS_PART:
return addTruss(static_cast<ExtrudedPartInstance*>(part)->getVisualTrussStyle(), part->getPartSizeXml(), part, decal, options, randomSeed);
case WEDGE_PART:
return addWedge<false>(part->getPartSizeXml(), Vector3::zero(), part, decal, options, randomSeed, ignoreMaterialsStuds);
case CORNERWEDGE_PART:
return addWedge<true>(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 <class AssetProvider, class AssetType, class AssetIdType>
static boost::shared_ptr<AssetType> fetchMesh(const AssetIdType& meshFile, Instance* instance, AsyncResult* asyncResult)
{
if (meshFile.isNull())
return boost::shared_ptr<AssetType>();
ContentProvider* contentProvider = ServiceProvider::find<ContentProvider>(instance);
CacheableContentProvider* mcp = ServiceProvider::find<AssetProvider>(contentProvider);
if (!mcp)
{
asyncResult->returnResult(AsyncHttpQueue::Failed);
return boost::shared_ptr<AssetType>();
}
AsyncHttpQueue::RequestResult reqResult;
boost::shared_ptr<AssetType> meshData = boost::static_pointer_cast<AssetType>(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<AssetType>();
default:
asyncResult->returnResult(reqResult);
return boost::shared_ptr<AssetType>();
}
}
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> fileMeshData = fetchMesh<MeshContentProvider, FileMeshData, MeshId>(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> fileMeshData = fetchMesh<MeshContentProvider, FileMeshData, MeshId>(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> fileMeshData = fetchMesh<MeshContentProvider, FileMeshData, MeshId>(MeshId("rbxasset://fonts/head.mesh"), part, asyncResult);
return Resources(fileMeshData ? fileMeshData : kDummyMeshData);
}
if (FFlag::StudioCSGAssets && !FFlag::CSGLoadBlocking)
{
if (PartOperation* operation = part->fastDynamicCast<PartOperation>())
{
if (operation->hasAsset())
{
shared_ptr<PartOperationAsset> partOperationAsset = fetchMesh<SolidModelContentProvider, PartOperationAsset, ContentId>(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<DecalTexture>())
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<DecalTexture>()
? 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<PartOperation>();
if (!operation)
return;
shared_ptr<CSGMesh> 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<CSGVertex>& meshVertices = modelData->getVertices();
const std::vector<unsigned int>& 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<NegateOperation>() != 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<DecalTexture>();
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<DecalTexture>();
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<unsigned>& 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<unsigned>(reinterpret_cast<long>(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<CSGConvex> 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<unsigned>(reinterpret_cast<long>(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);
}
}
}