Seen from the side, the chain looks fine.
But viewed from a sharper angle, glitchy spikes are introduced. You might think glitchy spikes would be expected on electricity, but these are not what I want.
Reducing the jitter to just a tiny fraction, the problem becomes clearer. This chain should look like an almost straight line with just a slight shake to it, but instead it has a big, horrible spike.
I went into the ogre billboard code and changed the way it calculates the billboards to an alternative method. In initial tests it seems to work fine. Here's an example, again with a very slight jitter viewed from a sharp angle.
In my alternative method, I don't calculate the perpendicular per element, I just do it once for the whole chain. Imagine a triangle formed by the camera position, the first element position, and the last element position. I take the normal of such a triangle and then use that as the perpendicular for every chain element. Doesn't seem to have any negative side effect so far, although I really haven't tested it very heavily.
Here's the code I changed, sorry, don't know how to submit patches and stuff. It might be handy if this method was offered as an alternative setting for billboard chains. I implemented it as a change to just one function, obviously a proper implementation would require getters, setters and both methods as options.
Code: Select all
void BillboardChain::updateVertexBuffer(Camera* cam)
{
setupBuffers();
HardwareVertexBufferSharedPtr pBuffer =
mVertexData->vertexBufferBinding->getBuffer(0);
void* pBufferStart = pBuffer->lock(HardwareBuffer::HBL_DISCARD);
const Vector3& camPos = cam->getDerivedPosition();
Vector3 eyePos = mParentNode->_getDerivedOrientation().Inverse() *
(camPos - mParentNode->_getDerivedPosition()) / mParentNode->_getDerivedScale();
Vector3 chainTangent;
for (ChainSegmentList::iterator segi = mChainSegmentList.begin();
segi != mChainSegmentList.end(); ++segi)
{
ChainSegment& seg = *segi;
// Skip 0 or 1 element segment counts
if (seg.head != SEGMENT_EMPTY && seg.head != seg.tail)
{
size_t laste = seg.head;
// mkultra333 create tangent based on normal of triangle made from eye pos, first elem pos and last elem pos
Vector3 CamToElemPosHead=mChainElementList[seg.head + seg.start].position-eyePos ;
Vector3 CamToElemPosTail=mChainElementList[seg.tail + seg.start].position-eyePos ;
Vector3 AltTangent=CamToElemPosHead.crossProduct(CamToElemPosTail) ;
AltTangent.normalise() ;
AltTangent*=0.5 ; // halve it, saves doing it over and over in the chain
for (size_t e = seg.head; ; ++e) // until break
{
// Wrap forwards
if (e == mMaxElementsPerChain)
e = 0;
Element& elem = mChainElementList[e + seg.start];
assert (((e + seg.start) * 2) < 65536 && "Too many elements!");
uint16 baseIdx = static_cast<uint16>((e + seg.start) * 2);
// Determine base pointer to vertex #1
void* pBase = static_cast<void*>(
static_cast<char*>(pBufferStart) +
pBuffer->getVertexSize() * baseIdx);
/* mkultra333 disabling chainTangent calculation, use my AltTangent instead
// Get index of next item
size_t nexte = e + 1;
if (nexte == mMaxElementsPerChain)
nexte = 0;
if (e == seg.head)
{
// No laste, use next item
chainTangent = mChainElementList[nexte + seg.start].position - elem.position;
}
else if (e == seg.tail)
{
// No nexte, use only last item
chainTangent = elem.position - mChainElementList[laste + seg.start].position;
}
else
{
// A mid position, use tangent across both prev and next
chainTangent = mChainElementList[nexte + seg.start].position - mChainElementList[laste + seg.start].position;
}
Vector3 vP1ToEye = eyePos - elem.position;
Vector3 vPerpendicular = chainTangent.crossProduct(vP1ToEye);
vPerpendicular.normalise();
vPerpendicular *= (elem.width * 0.5f);
Vector3 pos0 = elem.position - vPerpendicular;
Vector3 pos1 = elem.position + vPerpendicular;
*/
// mkultra333 alternative tangent
Vector3 vPerpendicular=AltTangent*elem.width;
Vector3 pos0 = elem.position - vPerpendicular;
Vector3 pos1 = elem.position + vPerpendicular;
float* pFloat = static_cast<float*>(pBase);
// pos1
*pFloat++ = pos0.x;
*pFloat++ = pos0.y;
*pFloat++ = pos0.z;
pBase = static_cast<void*>(pFloat);
if (mUseVertexColour)
{
RGBA* pCol = static_cast<RGBA*>(pBase);
Root::getSingleton().convertColourValue(elem.colour, pCol);
pCol++;
pBase = static_cast<void*>(pCol);
}
if (mUseTexCoords)
{
pFloat = static_cast<float*>(pBase);
if (mTexCoordDir == TCD_U)
{
*pFloat++ = elem.texCoord;
*pFloat++ = mOtherTexCoordRange[0];
}
else
{
*pFloat++ = mOtherTexCoordRange[0];
*pFloat++ = elem.texCoord;
}
pBase = static_cast<void*>(pFloat);
}
// pos2
pFloat = static_cast<float*>(pBase);
*pFloat++ = pos1.x;
*pFloat++ = pos1.y;
*pFloat++ = pos1.z;
pBase = static_cast<void*>(pFloat);
if (mUseVertexColour)
{
RGBA* pCol = static_cast<RGBA*>(pBase);
Root::getSingleton().convertColourValue(elem.colour, pCol);
pCol++;
pBase = static_cast<void*>(pCol);
}
if (mUseTexCoords)
{
pFloat = static_cast<float*>(pBase);
if (mTexCoordDir == TCD_U)
{
*pFloat++ = elem.texCoord;
*pFloat++ = mOtherTexCoordRange[1];
}
else
{
*pFloat++ = mOtherTexCoordRange[1];
*pFloat++ = elem.texCoord;
}
pBase = static_cast<void*>(pFloat);
}
if (e == seg.tail)
break; // last one
laste = e;
} // element
} // segment valid?
} // each segment
pBuffer->unlock();
}