Draw lines in models with triangles
parent
b21a5fd404
commit
0c6ef0d0d6
|
@ -37,14 +37,14 @@ public:
|
|||
{
|
||||
for (auto vboId : vbos)
|
||||
{
|
||||
if (vboId != 0)
|
||||
if (vboId.second)
|
||||
{
|
||||
glDeleteBuffers(1, &vboId);
|
||||
glDeleteBuffers(1, &vboId.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GLuint> vbos; // vertex buffer objects
|
||||
std::map<const void*, GLuint> vbos; // vertex buffer objects
|
||||
};
|
||||
|
||||
|
||||
|
@ -99,10 +99,27 @@ ModelGeometry::render(RenderContext& rc, double /* t */)
|
|||
mesh->getVertexData(),
|
||||
GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
m_glData->vbos[mesh->getVertexData()] = vboId;
|
||||
}
|
||||
}
|
||||
|
||||
m_glData->vbos.push_back(vboId);
|
||||
for (unsigned int groupIndex = 0; groupIndex < mesh->getGroupCount(); ++groupIndex)
|
||||
{
|
||||
const Mesh::PrimitiveGroup* group = mesh->getGroup(groupIndex);
|
||||
if (group->vertexOverride != nullptr && group->vertexCountOverride * group->vertexDescriptionOverride.stride > MinVBOSize)
|
||||
{
|
||||
glGenBuffers(1, &vboId);
|
||||
if (vboId != 0)
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
group->vertexCountOverride * group->vertexDescriptionOverride.stride,
|
||||
group->vertexOverride, GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
m_glData->vbos[group->vertexOverride] = vboId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,30 +130,42 @@ ModelGeometry::render(RenderContext& rc, double /* t */)
|
|||
for (unsigned int meshIndex = 0; meshIndex < m_model->getMeshCount(); ++meshIndex)
|
||||
{
|
||||
Mesh* mesh = m_model->getMesh(meshIndex);
|
||||
GLuint vboId = 0;
|
||||
|
||||
if (meshIndex < m_glData->vbos.size())
|
||||
{
|
||||
vboId = m_glData->vbos[meshIndex];
|
||||
}
|
||||
|
||||
if (vboId != 0)
|
||||
{
|
||||
// Bind the vertex buffer object.
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||
rc.setVertexArrays(mesh->getVertexDescription(), nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No vertex buffer object; just use normal vertex arrays
|
||||
rc.setVertexArrays(mesh->getVertexDescription(), mesh->getVertexData());
|
||||
}
|
||||
const void* currentData = nullptr;
|
||||
GLuint currentVboId = 0;
|
||||
|
||||
// Iterate over all primitive groups in the mesh
|
||||
for (unsigned int groupIndex = 0; groupIndex < mesh->getGroupCount(); ++groupIndex)
|
||||
{
|
||||
const Mesh::PrimitiveGroup* group = mesh->getGroup(groupIndex);
|
||||
rc.updateShader(mesh->getVertexDescription(), group->prim);
|
||||
bool useOverrideValue = group->vertexOverride != nullptr && rc.shouldDrawLineAsTriangles();
|
||||
|
||||
const void* data = useOverrideValue ? group->vertexOverride : mesh->getVertexData();
|
||||
auto vertexDescription = useOverrideValue ? group->vertexDescriptionOverride : mesh->getVertexDescription();
|
||||
|
||||
if (currentData != data)
|
||||
{
|
||||
GLuint vboId = m_glData->vbos[data];
|
||||
if (vboId != 0)
|
||||
{
|
||||
// Bind the vertex buffer object.
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||
rc.setVertexArrays(vertexDescription, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (currentVboId != 0)
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
// No vertex buffer object; just use normal vertex arrays
|
||||
rc.setVertexArrays(vertexDescription, data);
|
||||
}
|
||||
currentData = data;
|
||||
currentVboId = vboId;
|
||||
}
|
||||
|
||||
rc.updateShader(vertexDescription, group->prim);
|
||||
|
||||
// Set up the material
|
||||
const Material* material = nullptr;
|
||||
|
@ -147,11 +176,11 @@ ModelGeometry::render(RenderContext& rc, double /* t */)
|
|||
}
|
||||
|
||||
rc.setMaterial(material);
|
||||
rc.drawGroup(*group);
|
||||
rc.drawGroup(*group, useOverrideValue);
|
||||
}
|
||||
|
||||
// If we set a VBO, unbind it.
|
||||
if (vboId != 0)
|
||||
if (currentVboId != 0)
|
||||
{
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
|
|
@ -122,6 +122,13 @@ RenderContext::setMaterial(const Material* newMaterial)
|
|||
}
|
||||
|
||||
|
||||
bool
|
||||
RenderContext::shouldDrawLineAsTriangles() const
|
||||
{
|
||||
return renderer->shouldDrawLineAsTriangles();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
RenderContext::setPointScale(float _pointScale)
|
||||
{
|
||||
|
@ -151,7 +158,7 @@ RenderContext::getCameraOrientation() const
|
|||
|
||||
|
||||
void
|
||||
RenderContext::drawGroup(const Mesh::PrimitiveGroup& group)
|
||||
RenderContext::drawGroup(const Mesh::PrimitiveGroup& group, bool useOverride)
|
||||
{
|
||||
// Skip rendering if this is the emissive pass but there's no
|
||||
// emissive texture.
|
||||
|
@ -175,10 +182,10 @@ RenderContext::drawGroup(const Mesh::PrimitiveGroup& group)
|
|||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
glDrawElements(GLPrimitiveModes[(int) group.prim],
|
||||
group.nIndices,
|
||||
glDrawElements(GLPrimitiveModes[(int)(useOverride ? group.primOverride : group.prim)],
|
||||
useOverride ? group.nIndicesOverride : group.nIndices,
|
||||
GL_UNSIGNED_INT,
|
||||
group.indices);
|
||||
useOverride ? group.indicesOverride : group.indices);
|
||||
#ifndef GL_ES
|
||||
if (drawPoints)
|
||||
{
|
||||
|
@ -208,19 +215,22 @@ RenderContext::updateShader(const cmod::Mesh::VertexDescription& desc, Mesh::Pri
|
|||
bool useNormalsNow = (desc.getAttribute(Mesh::Normal).format == Mesh::Float3);
|
||||
bool useColorsNow = (desc.getAttribute(Mesh::Color0).format != Mesh::InvalidFormat);
|
||||
bool useTexCoordsNow = (desc.getAttribute(Mesh::Texture0).format != Mesh::InvalidFormat);
|
||||
bool drawLineNow = (desc.getAttribute(Mesh::NextPosition).format == Mesh::Float3);
|
||||
bool useStaticPointSizeNow = primType == Mesh::PointList;
|
||||
|
||||
if (usePointSizeNow != usePointSize ||
|
||||
useStaticPointSizeNow != useStaticPointSize ||
|
||||
useNormalsNow != useNormals ||
|
||||
useColorsNow != useColors ||
|
||||
useTexCoordsNow != useTexCoords)
|
||||
useTexCoordsNow != useTexCoords ||
|
||||
drawLineNow != drawLine)
|
||||
{
|
||||
usePointSize = usePointSizeNow;
|
||||
useStaticPointSize = useStaticPointSizeNow;
|
||||
useNormals = useNormalsNow;
|
||||
useColors = useColorsNow;
|
||||
useTexCoords = useTexCoordsNow;
|
||||
drawLine = drawLineNow;
|
||||
if (getMaterial() != nullptr)
|
||||
makeCurrent(*getMaterial());
|
||||
}
|
||||
|
@ -353,6 +363,40 @@ setExtendedVertexArrays(const Mesh::VertexDescription& desc,
|
|||
glDisableVertexAttribArray(CelestiaGLProgram::PointSizeAttributeIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
const Mesh::VertexAttribute& nextPos = desc.getAttribute(Mesh::NextPosition);
|
||||
switch (nextPos.format)
|
||||
{
|
||||
case Mesh::Float3:
|
||||
glEnableVertexAttribArray(CelestiaGLProgram::NextVCoordAttributeIndex);
|
||||
glVertexAttribPointer(CelestiaGLProgram::NextVCoordAttributeIndex,
|
||||
GLComponentCounts[(int) nextPos.format],
|
||||
GLComponentTypes[(int) nextPos.format],
|
||||
GL_FALSE,
|
||||
desc.stride,
|
||||
vertices + nextPos.offset);
|
||||
break;
|
||||
default:
|
||||
glDisableVertexAttribArray(CelestiaGLProgram::NextVCoordAttributeIndex);
|
||||
break;
|
||||
}
|
||||
|
||||
const Mesh::VertexAttribute& scaleFac = desc.getAttribute(Mesh::ScaleFactor);
|
||||
switch (scaleFac.format)
|
||||
{
|
||||
case Mesh::Float1:
|
||||
glEnableVertexAttribArray(CelestiaGLProgram::ScaleFactorAttributeIndex);
|
||||
glVertexAttribPointer(CelestiaGLProgram::ScaleFactorAttributeIndex,
|
||||
GLComponentCounts[(int) scaleFac.format],
|
||||
GLComponentTypes[(int) scaleFac.format],
|
||||
GL_FALSE,
|
||||
desc.stride,
|
||||
vertices + scaleFac.offset);
|
||||
break;
|
||||
default:
|
||||
glDisableVertexAttribArray(CelestiaGLProgram::ScaleFactorAttributeIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -551,6 +595,9 @@ GLSL_RenderContext::makeCurrent(const Material& m)
|
|||
else if (useStaticPointSize)
|
||||
shaderProps.texUsage |= ShaderProperties::StaticPointSize;
|
||||
|
||||
if (drawLine)
|
||||
shaderProps.texUsage |= ShaderProperties::LineAsTriangles;
|
||||
|
||||
if (useColors)
|
||||
shaderProps.texUsage |= ShaderProperties::VertexColors;
|
||||
|
||||
|
@ -638,6 +685,12 @@ GLSL_RenderContext::makeCurrent(const Material& m)
|
|||
prog->pointScale = renderer->getScreenDpi() / 96.0f;
|
||||
}
|
||||
|
||||
if (drawLine)
|
||||
{
|
||||
prog->lineWidthX = renderer->getLineWidthX();
|
||||
prog->lineWidthY = renderer->getLineWidthY();
|
||||
}
|
||||
|
||||
// Ring shadow parameters
|
||||
if ((shaderProps.texUsage & ShaderProperties::RingShadowTexture) != 0)
|
||||
{
|
||||
|
@ -783,6 +836,9 @@ GLSLUnlit_RenderContext::makeCurrent(const Material& m)
|
|||
else if (useStaticPointSize)
|
||||
shaderProps.texUsage |= ShaderProperties::StaticPointSize;
|
||||
|
||||
if (drawLine)
|
||||
shaderProps.texUsage |= ShaderProperties::LineAsTriangles;
|
||||
|
||||
if (useColors)
|
||||
shaderProps.texUsage |= ShaderProperties::VertexColors;
|
||||
|
||||
|
@ -817,6 +873,12 @@ GLSLUnlit_RenderContext::makeCurrent(const Material& m)
|
|||
prog->pointScale = renderer->getScreenDpi() / 96.0f;
|
||||
}
|
||||
|
||||
if (drawLine)
|
||||
{
|
||||
prog->lineWidthX = renderer->getLineWidthX();
|
||||
prog->lineWidthY = renderer->getLineWidthY();
|
||||
}
|
||||
|
||||
Material::BlendMode newBlendMode = Material::InvalidBlend;
|
||||
if (m.opacity != 1.0f ||
|
||||
m.blend == Material::AdditiveBlend ||
|
||||
|
|
|
@ -30,13 +30,14 @@ class RenderContext
|
|||
virtual void setVertexArrays(const cmod::Mesh::VertexDescription& desc,
|
||||
const void* vertexData);
|
||||
virtual void updateShader(const cmod::Mesh::VertexDescription& desc, cmod::Mesh::PrimitiveGroupType primType);
|
||||
virtual void drawGroup(const cmod::Mesh::PrimitiveGroup& group);
|
||||
virtual void drawGroup(const cmod::Mesh::PrimitiveGroup& group, bool useOverride);
|
||||
|
||||
const cmod::Material* getMaterial() const;
|
||||
void setMaterial(const cmod::Material*);
|
||||
void lock() { locked = true; }
|
||||
void unlock() { locked = false; }
|
||||
bool isLocked() const { return locked; }
|
||||
bool shouldDrawLineAsTriangles() const;
|
||||
|
||||
enum RenderPass
|
||||
{
|
||||
|
@ -70,6 +71,7 @@ class RenderContext
|
|||
bool useNormals{ true };
|
||||
bool useColors{ false };
|
||||
bool useTexCoords{ true };
|
||||
bool drawLine { false };
|
||||
const Eigen::Matrix4f *modelViewMatrix;
|
||||
const Eigen::Matrix4f *projectionMatrix;
|
||||
};
|
||||
|
|
|
@ -109,6 +109,23 @@ Mesh::VertexDescription::validate() const
|
|||
return true;
|
||||
}
|
||||
|
||||
Mesh::VertexDescription Mesh::VertexDescription::appendingAttributes(const VertexAttribute* newAttributes, int count) const
|
||||
{
|
||||
unsigned int totalAttributeCount = nAttributes + count;
|
||||
VertexAttribute* allAttributes = new VertexAttribute[totalAttributeCount];
|
||||
memcpy(allAttributes, attributes, sizeof(VertexAttribute) * nAttributes);
|
||||
unsigned int newStride = stride;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
VertexAttribute attribute = newAttributes[i];
|
||||
allAttributes[nAttributes + i] = attribute;
|
||||
newStride += Mesh::getVertexAttributeSize(attribute.format);
|
||||
}
|
||||
memcpy(allAttributes + nAttributes, newAttributes, sizeof(VertexAttribute) * count);
|
||||
VertexDescription desc = VertexDescription(newStride, totalAttributeCount, allAttributes);
|
||||
delete[] allAttributes;
|
||||
return desc;
|
||||
}
|
||||
|
||||
Mesh::VertexDescription::~VertexDescription()
|
||||
{
|
||||
|
@ -236,17 +253,113 @@ Mesh::addGroup(PrimitiveGroup* group)
|
|||
}
|
||||
|
||||
|
||||
Mesh::PrimitiveGroup* Mesh::createLinePrimitiveGroup(bool lineStrip, unsigned int nIndices, index32* indices)
|
||||
{
|
||||
// Transform LINE_STRIP/LINES to triangle vertices
|
||||
int transformedVertCount = lineStrip ? (nIndices - 1) * 4 : nIndices * 2;
|
||||
// Get information of the position attributes
|
||||
auto positionAttributes = vertexDesc.getAttribute(Position);
|
||||
int positionSize = getVertexAttributeSize(positionAttributes.format);
|
||||
int positionOffset = positionAttributes.offset;
|
||||
|
||||
int originalStride = vertexDesc.stride;
|
||||
// Add another position (next line end), and scale factor
|
||||
// ORIGINAL ATTRIBUTES | NextVCoordAttributeIndex | ScaleFactorAttributeIndex
|
||||
int stride = originalStride + positionSize + sizeof(float);
|
||||
|
||||
// Get line count
|
||||
int lineCount = lineStrip ? nIndices - 1 : nIndices / 2;
|
||||
int lineIndexCount = 6 * lineCount;
|
||||
int lineVertexCount = 4 * lineCount;
|
||||
|
||||
// Create buffer to hold the transformed vertices/indices
|
||||
unsigned char* data = new unsigned char[stride * lineVertexCount];
|
||||
index32* newIndices = new index32[lineIndexCount];
|
||||
|
||||
unsigned char* originalData = (unsigned char *)vertices;
|
||||
auto ptr = data;
|
||||
for (int i = 0; i < lineCount; ++i)
|
||||
{
|
||||
int thisIndex = indices[lineStrip ? i : i * 2];
|
||||
int nextIndex = indices[lineStrip ? i + 1 : i * 2 + 1];
|
||||
|
||||
unsigned char* origThisVertLoc = originalData + thisIndex * originalStride;
|
||||
unsigned char* origNextVertLoc = originalData + nextIndex * originalStride;
|
||||
float *ff = (float *)origThisVertLoc;
|
||||
float *ffn = (float *)origNextVertLoc;
|
||||
|
||||
// Fill the info for the 4 vertices
|
||||
memcpy(ptr, origThisVertLoc, originalStride);
|
||||
memcpy(ptr + originalStride, origNextVertLoc + positionOffset, positionSize);
|
||||
*(float *)&ptr[originalStride + positionSize] = -0.5f;
|
||||
ptr += stride;
|
||||
|
||||
memcpy(ptr, origThisVertLoc, originalStride);
|
||||
memcpy(ptr + originalStride, origNextVertLoc + positionOffset, positionSize);
|
||||
*(float *)&ptr[originalStride + positionSize] = 0.5f;
|
||||
ptr += stride;
|
||||
|
||||
memcpy(ptr, origNextVertLoc, originalStride);
|
||||
memcpy(ptr + originalStride, origThisVertLoc + positionOffset, positionSize);
|
||||
*(float *)&ptr[originalStride + positionSize] = -0.5f;
|
||||
ptr += stride;
|
||||
|
||||
memcpy(ptr, origNextVertLoc, originalStride);
|
||||
memcpy(ptr + originalStride, origThisVertLoc + positionOffset, positionSize);
|
||||
*(float *)&ptr[originalStride + positionSize] = 0.5f;
|
||||
ptr += stride;
|
||||
|
||||
int lineIndex = 6 * i;
|
||||
index32 newIndex = 4 * i;
|
||||
|
||||
// Fill info for the 6 indices
|
||||
newIndices[lineIndex] = newIndex;
|
||||
newIndices[lineIndex + 1] = newIndex + 1;
|
||||
newIndices[lineIndex + 2] = newIndex + 2;
|
||||
newIndices[lineIndex + 3] = newIndex + 2;
|
||||
newIndices[lineIndex + 4] = newIndex + 3;
|
||||
newIndices[lineIndex + 5] = newIndex;
|
||||
}
|
||||
|
||||
array<VertexAttribute, 2> newAttributes = {
|
||||
VertexAttribute(NextPosition, positionAttributes.format, originalStride),
|
||||
VertexAttribute(ScaleFactor, Float1, originalStride + positionSize),
|
||||
};
|
||||
auto* g = new PrimitiveGroup();
|
||||
g->vertexOverride = data;
|
||||
g->vertexCountOverride = lineVertexCount;
|
||||
g->vertexDescriptionOverride = vertexDesc.appendingAttributes(newAttributes.data(), 2);
|
||||
g->indicesOverride = newIndices;
|
||||
g->nIndicesOverride = lineIndexCount;
|
||||
g->primOverride = PrimitiveGroupType::TriList;
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
unsigned int
|
||||
Mesh::addGroup(PrimitiveGroupType prim,
|
||||
unsigned int materialIndex,
|
||||
unsigned int nIndices,
|
||||
index32* indices)
|
||||
{
|
||||
auto* g = new PrimitiveGroup();
|
||||
g->prim = prim;
|
||||
g->materialIndex = materialIndex;
|
||||
PrimitiveGroup* g;
|
||||
if (prim == LineStrip || prim == LineList)
|
||||
{
|
||||
g = createLinePrimitiveGroup(prim == LineStrip, nIndices, indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
g = new PrimitiveGroup;
|
||||
g->vertexOverride = nullptr;
|
||||
g->vertexCountOverride = 0;
|
||||
g->indicesOverride = nullptr;
|
||||
g->nIndicesOverride = 0;
|
||||
g->primOverride = prim;
|
||||
}
|
||||
g->nIndices = nIndices;
|
||||
g->indices = indices;
|
||||
g->prim = prim;
|
||||
g->materialIndex = materialIndex;
|
||||
|
||||
return addGroup(g);
|
||||
}
|
||||
|
@ -518,6 +631,27 @@ Mesh::transform(const Vector3f& translation, float scale)
|
|||
Map<Vector3f>(reinterpret_cast<float*>(vdata)) = tv;
|
||||
}
|
||||
|
||||
// Scale and translate the overriden vertex values
|
||||
for (i = 0; i < getGroupCount(); i++)
|
||||
{
|
||||
PrimitiveGroup* group = getGroup(i);
|
||||
char* vdata = reinterpret_cast<char*>(group->vertexOverride);
|
||||
if (!vdata)
|
||||
return;
|
||||
|
||||
auto vertexDesc = group->vertexDescriptionOverride;
|
||||
int positionOffset = vertexDesc.getAttribute(Position).offset;
|
||||
int nextPositionOffset = vertexDesc.getAttribute(NextPosition).offset;
|
||||
for (int j = 0; j < group->vertexCountOverride; j++, vdata += vertexDesc.stride)
|
||||
{
|
||||
Vector3f tv = (Map<Vector3f>(reinterpret_cast<float*>(vdata + positionOffset)) + translation) * scale;
|
||||
Map<Vector3f>(reinterpret_cast<float*>(vdata + positionOffset)) = tv;
|
||||
|
||||
tv = (Map<Vector3f>(reinterpret_cast<float*>(vdata + nextPositionOffset)) + translation) * scale;
|
||||
Map<Vector3f>(reinterpret_cast<float*>(vdata + nextPositionOffset)) = tv;
|
||||
}
|
||||
}
|
||||
|
||||
// Point sizes need to be scaled as well
|
||||
if (vertexDesc.getAttribute(PointSize).format == Float1)
|
||||
{
|
||||
|
|
|
@ -42,7 +42,9 @@ class Mesh
|
|||
Texture2 = 7,
|
||||
Texture3 = 8,
|
||||
PointSize = 9,
|
||||
SemanticMax = 10,
|
||||
NextPosition = 10,
|
||||
ScaleFactor = 11,
|
||||
SemanticMax = 12,
|
||||
InvalidSemantic = -1,
|
||||
};
|
||||
|
||||
|
@ -93,6 +95,8 @@ class Mesh
|
|||
return semanticMap[semantic];
|
||||
}
|
||||
|
||||
VertexDescription appendingAttributes(const VertexAttribute* newAttributes, int count) const;
|
||||
|
||||
bool validate() const;
|
||||
|
||||
VertexDescription& operator=(const VertexDescription&);
|
||||
|
@ -134,6 +138,12 @@ class Mesh
|
|||
unsigned int materialIndex;
|
||||
index32* indices;
|
||||
unsigned int nIndices;
|
||||
PrimitiveGroupType primOverride;
|
||||
void* vertexOverride;
|
||||
unsigned int vertexCountOverride;
|
||||
index32* indicesOverride;
|
||||
unsigned int nIndicesOverride;
|
||||
VertexDescription vertexDescriptionOverride { 0, 0, nullptr };
|
||||
};
|
||||
|
||||
class PickResult
|
||||
|
@ -154,6 +164,7 @@ class Mesh
|
|||
bool setVertexDescription(const VertexDescription& desc);
|
||||
const VertexDescription& getVertexDescription() const;
|
||||
|
||||
PrimitiveGroup* createLinePrimitiveGroup(bool lineStrip, unsigned int nIndices, Mesh::index32* indices);
|
||||
const PrimitiveGroup* getGroup(unsigned int index) const;
|
||||
PrimitiveGroup* getGroup(unsigned int index);
|
||||
unsigned int addGroup(PrimitiveGroup* group);
|
||||
|
|
Loading…
Reference in New Issue