Alt BillboardChain method, fixes twist glitches

What it says on the tin: a place to discuss proposed new features.
Post Reply
User avatar
mkultra333
Gold Sponsor
Gold Sponsor
Posts: 1894
Joined: Sun Mar 08, 2009 5:25 am
x 114

Alt BillboardChain method, fixes twist glitches

Post by mkultra333 »

Been working on some electric arcs made of billboard chains. I jitter the element positions to simulate the jittering electricity. Ran into a problem, if viewed from some angles billboard chains will twist up and produce visual glitches. Here's some example images.

Seen from the side, the chain looks fine.

Image

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.

Image


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.

Image

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.

Image

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();


	}
Edit: Oops, pet hate, forgot to mention license. My original code is public domain, change it and license to suit need if added to other projects.
"In theory there is no difference between practice and theory. In practice, there is." - Psychology Textbook.
Post Reply