Я расскажу, как я сделал такую заставку. Мои трубы вписаны в 3d сетку, каждая ячейка - это куб:
Y^
|_______
/ 2 /| 0 vs 3
/______/ | 1 vs 4
| |1| 2 vs 5
| 3 | /
|______|/ -->X
/
-Z/
Конфигурация канала в каждой ячейке описывается шестью битами - один бит для каждой стороны. 0 означает, что ячейка пуста, 63 означает, что она имеет шесть сегментов, идущих в центр. Если разбить комбинации на типы, их будет мало:
- короткие палочки, х6
- длинные палки, х3 (0-3, 1-4, 2-5)
- арок, х12 (2 - 1, 1 - 3, 2 - 3)
- Т-образные перекрестки, х12 (1 - 3 - 4)
- углов, х8 (2 - 1 - 3, 2 - 3 - 4)
- крестов, х3 (3 - 1 - 2 - 4)
- четырехсегментный кластер, x12 (1 - 2 - 3 - 4)
- пятисегментный кластер, x6
- шестисегментная звезда, х1.
Я использовал четверть тора для арок (самосинтез), патч сферического треугольника для углов и цилиндры для всего остального. Звезда, кресты и пять сегментов - это просто пересекающиеся цилиндры в моей модели.
Редактировать: некоторый код в C # (я просто надеюсь, что он будет полезен в некотором роде).
Все на сцене объединено из трех моделей - арка, цилиндр и сферический треугольный патч, которые многократно вращаются и визуализируются. Все модели представлены в массивах вершин. Очень скоро я столкнулся с проблемой производительности и внедрил простой LOD на основе расстояния для борьбы с ним. Итак, я создаю не одну модель каждого вида, а серию моделей с разным количеством сегментов.
/// <summary>
/// Generates full row of arch models and lod map
/// to render them.
/// </summary>
/// <param name="radius">Pipe radius</param>
/// <returns>Model with lod</returns>
Model GenerateArches(double radius)
{
//Determine total number of vertices for full row
LodEntry[] lod = new LodEntry[slicesLod.Length];
int totalVertices = 0;
int totalIndices = 0;
for (int level = 0; level < slicesLod.Length; ++level)
{
int sl = slicesLod[level];
int st = archStacksLod[level];
if (st < 3) st = 3;
int vertices = (sl + 1)*(st + 1);
int indices = ((sl + 1)*2 + 4)*(st) - 4;
lod[level].start = totalIndices;
totalVertices += vertices;
totalIndices += indices;
lod[level].count = indices;
}
int[] indexArray = new int[totalIndices];
VertexAttributes[] va = new VertexAttributes[totalVertices];
int vCounter = 0; //index for vertices
int iCounter = 0; //indices counter
for (int level = 0; level < slicesLod.Length; ++level)
{
int iOffset = vCounter;
int slices = slicesLod[level];
int stacks = archStacksLod[level];
if (stacks < 3) stacks = 3;
for (int st = 0; st <= stacks; ++st)
{
double a = Math.PI*0.5*st/stacks;
float texCoordS = st/(float) stacks;
for (int sl = 0; sl <= slices; ++sl)
{
double b = Math.PI*2*sl/slices;
float texCoordT = sl/(float) slices;
va[vCounter].S = texCoordS;
va[vCounter].T = texCoordT;
//point on central arch
double x0 = 0.5*Math.Sin(a);
double y0 = 0.5*Math.Cos(a);
const double z0 = 0;
//point displacement
double rx = radius*Math.Sin(a)*Math.Sin(b);
double ry = radius*Math.Cos(a)*Math.Sin(b);
double rz = radius*Math.Cos(b);
//normal factor
double nf = 1.0/Math.Sqrt(rx*rx + ry*ry + rz*rz);
va[vCounter].NX = (float)(rx * nf);
va[vCounter].NY = (float)(ry * nf);
va[vCounter].NZ = (float)(rz * nf);
//position
va[vCounter].X = (float) (x0 + rx);
va[vCounter].Y = (float) (y0 + ry);
va[vCounter].Z = (float) (z0 + rz);
++vCounter;
}
}
for (int stack = 0; stack < stacks; ++stack)
{
for (int slice = 0; slice <= slices; ++slice)
{
indexArray[iCounter++] = iOffset + stack * slices + slice + stack;
indexArray[iCounter++] = iOffset + (stack + 1) * slices + slice + 1 + stack;
}
if (stack < stacks - 1)
{
indexArray[iCounter++] = iOffset + stack * slices + slices + stack;
indexArray[iCounter++] = iOffset + stack * slices + slices + stack;
indexArray[iCounter++] = iOffset + (stack + 1) * slices + slices + 2 * (stack + 1) - stack;
indexArray[iCounter++] = iOffset + (stack + 1) * slices + slices + 2 * (stack + 1) - stack;
}
}
}
return new Model(va, indexArray, lod);
}
/// <summary>
/// Generates indices for rendering of vertex array,
/// representing a cylinder section.
/// Vertices assumed to be stored slice by slice:
/// 0 1 2 3 ................... cylStacks-1,
/// cylStacks .................. 2*cylStacks-1,
/// ....................................,
/// (cylSlices-1)*cylStacks .. cylSlices*cylStacks-1.
/// </summary>
/// <param name="radius"></param>
private Model GenerateCylinders(double radius)
{
LodEntry[] lod = new LodEntry[slicesLod.Length];
int totalVertices = 0;
int totalIndices = 0;
for (int level = 0; level < slicesLod.Length; ++level)
{
int sl = slicesLod[level];
int st = cylStacksLod[level];
int vertices = (sl + 1)*(st + 1);
int indices = ((sl+1)*2 + 4)*st - 4;
lod[level].start = totalIndices;
totalVertices += vertices;
totalIndices += indices;
lod[level].count = indices;
}
int[] indexArray = new int[totalIndices];
VertexAttributes[] va = new VertexAttributes[totalVertices];
int vCounter = 0; //index for vertex attributes
int iCounter = 0; //indices counter
for (int level = 0; level < slicesLod.Length; ++level)
{
int iOffset = vCounter;
int slices = slicesLod[level];
int stacks = cylStacksLod[level];
for (int st = 0; st <= stacks; ++st)
{
double i = 0.5 - 0.5 * st / stacks;
float texCoordS = st / (float)stacks;
for (int sl = 0; sl <= slices; ++sl)
{
double b = Math.PI * 2 * sl / slices;
//tex coords
float texCoordT = sl / (float)slices;
va[vCounter].S = 0.5f * texCoordS;
va[vCounter].T = texCoordT;
//point on central axis
const double x0 = 0;
const double y0 = 0;
double z0 = i;
//point displacement
double rx = radius*Math.Cos(b);
double ry = radius*Math.Sin(b);
const double rz = 0;
//normal factor
double nf = 1.0/Math.Sqrt(ry*ry + rx*rx);
va[vCounter].NX = (float)(rx * nf);
va[vCounter].NY = (float)(ry * nf);
va[vCounter].NZ = 0.0f;
va[vCounter].X = (float)(x0 + rx);
va[vCounter].Y = (float)(y0 + ry);
va[vCounter].Z = (float)(z0 + rz);
++vCounter;
}
}
for (int stack = 0; stack < stacks; ++stack)
{
for (int slice = 0; slice <= slices; ++slice)
{
indexArray[iCounter++] = iOffset + stack*slices + slice + stack;
indexArray[iCounter++] = iOffset + (stack + 1)*slices + slice + 1 + stack;
}
if (stack < stacks - 1)
{
indexArray[iCounter++] = iOffset + stack * slices + slices + stack;
indexArray[iCounter++] = iOffset + stack * slices + slices + stack;
indexArray[iCounter++] = iOffset + (stack + 1) * slices + slices + 2 * (stack + 1) - stack;
indexArray[iCounter++] = iOffset + (stack + 1) * slices + slices + 2 * (stack + 1) - stack;
}
}
}
return new Model(va, indexArray, lod);
}
static int R0(int _slices, int _level)
{
return _level * (_slices+2) - (int)(0.5 * _level * (_level + 1));
}
static int RL(int _slices, int _level)
{
return _slices - _level + 1;
}
private Model GenerateSphereSegment(double radius)
{
//Determine total number of vertices for full row
LodEntry[] lod = new LodEntry[slicesLod.Length];
int totalVertices = 0;
int totalIndices = 0;
for (int level = 0; level < slicesLod.Length; ++level)
{
int sl = slicesLod[level] >> 2;
int vertices = (((2 + sl) * (sl + 1)) >> 1);
int indices = sl * (sl + 3);
lod[level].start = totalIndices;
totalVertices += vertices;
totalIndices += indices;
lod[level].count = indices;
}
int[] indexArray = new int[totalIndices];
VertexAttributes[] va = new VertexAttributes[totalVertices];
int vCounter = 0; //index for vertices
int iCounter = 0; //indices counter
for (int level = 0; level < slicesLod.Length; ++level)
{
int sphSlices = slicesLod[level]>>2;
int iOffset = vCounter; //index offset for level
for (int sl = 0; sl <= sphSlices; ++sl)
{
double a = Math.PI*sl*0.5/sphSlices;
double Y = radius*Math.Sin(a);
double Ry = radius*Math.Cos(a);
for (int st = 0; st <= sphSlices - sl; ++st)
{
double X, Z, b;
if (sphSlices > sl)
{
b = Math.PI*0.5*st/(sphSlices - sl);
X = Ry*Math.Sin(b);
Z = Ry*Math.Cos(b);
}
else
{
X = 0;
Z = 0;
b = 0;
}
va[vCounter].S = (float)(0.5 / 3 * a);
va[vCounter].T = (float)(0.14 * b);
double coeff = 1/Math.Sqrt(X*X + Y*Y + Z*Z);
va[vCounter].NX = (float)(X * coeff);
va[vCounter].NY = (float)(Y * coeff);
va[vCounter].NZ = (float)(Z * coeff);
va[vCounter].X = (float)(va[vCounter].NX * radius);
va[vCounter].Y = (float)(va[vCounter].NY * radius);
va[vCounter].Z = (float)(va[vCounter].NZ * radius);
++vCounter;
}
}
for (int k = 0; k < sphSlices; ++k)
{
int lastS = RL(sphSlices, k);
for (int s = 0; s < lastS - 1; ++s)
{
int c0 = R0(sphSlices, k) + s;
int cn = R0(sphSlices, k) + s + RL(sphSlices, k);
indexArray[iCounter++] = cn + iOffset;
indexArray[iCounter++] = c0 + iOffset;
}
int tail = R0(sphSlices, k) + lastS - 1;
indexArray[iCounter++] = tail + iOffset;
indexArray[iCounter++] = tail + iOffset;
}
}
return new Model(va, indexArray, lod);
}