Example #1
0
/// Execute the animation and returns true if the animation is over (can be deleted)
bool AnimationCamera::Execute()
{
    bool isAnimationFinished = false;
    AnimationExecuteWithTime(isAnimationFinished);

    CameraAnimationFrame result;
    switch (mAlgo)
    {
    case ANIMATION_LINEAR:
        result[Position()] = CalculateLinear<CameraAnimationFrame,Position>(this);
        result[Aim()] = CalculateLinear<CameraAnimationFrame,Aim>(this);
        result[UpVector()] = CalculateLinear<CameraAnimationFrame,UpVector>(this);
        break;
    case ANIMATION_BEZIER:
        result[Position()] = CalculateBezier<CameraAnimationFrame,Position>(this);
        result[Aim()] = CalculateBezier<CameraAnimationFrame,Aim>(this);
        result[UpVector()] = CalculateBezier<CameraAnimationFrame,UpVector>(this);
        break;
    default:
        checkf(false, "New algo type not handled by Camera Animations");
        break;
    }

    mCamera->assignerPosition(result[Position()]);
    mCamera->assignerPointVise(result[Aim()]);
    mCamera->assignerDirectionHaut(result[UpVector()]);

    return isAnimationFinished;
}
Example #2
0
void Direct3D::UpdateCamera() //Move the 3D camera, not needed in 2D.
{
	D3DXVECTOR3 EyePos(0, 0, 0);
	D3DXVECTOR3 TargetPos(0, 0, 0);
	D3DXVECTOR3 UpVector(0, 0, 0);

	D3DXMATRIXA16 View;
	D3DXMatrixLookAtLH(&View, &EyePos, &TargetPos, &UpVector);
	D3DDevice->SetTransform(D3DTS_VIEW, &View);
}
Example #3
0
void lcLight::UpdatePosition(lcStep Step)
{
	mPosition = CalculateKey(mPositionKeys, Step);
	mTargetPosition = CalculateKey(mTargetPositionKeys, Step);
	mAmbientColor = CalculateKey(mAmbientColorKeys, Step);
	mDiffuseColor = CalculateKey(mDiffuseColorKeys, Step);
	mSpecularColor = CalculateKey(mSpecularColorKeys, Step);
	mAttenuation = CalculateKey(mAttenuationKeys, Step);
	mSpotCutoff = CalculateKey(mSpotCutoffKeys, Step);
	mSpotExponent = CalculateKey(mSpotExponentKeys, Step);

	if (IsPointLight())
	{
		mWorldLight = lcMatrix44Identity();
		mWorldLight.SetTranslation(-mPosition);
	}
	else
	{
		lcVector3 FrontVector(mTargetPosition - mPosition);
		lcVector3 UpVector(1, 1, 1);

		if (fabs(FrontVector[0]) < fabs(FrontVector[1]))
		{
			if (fabs(FrontVector[0]) < fabs(FrontVector[2]))
				UpVector[0] = -(UpVector[1] * FrontVector[1] + UpVector[2] * FrontVector[2]);
			else
				UpVector[2] = -(UpVector[0] * FrontVector[0] + UpVector[1] * FrontVector[1]);
		}
		else
		{
			if (fabs(FrontVector[1]) < fabs(FrontVector[2]))
				UpVector[1] = -(UpVector[0] * FrontVector[0] + UpVector[2] * FrontVector[2]);
			else
				UpVector[2] = -(UpVector[0] * FrontVector[0] + UpVector[1] * FrontVector[1]);
		}

		mWorldLight = lcMatrix44LookAt(mPosition, mTargetPosition, UpVector);
	}
}
Example #4
0
// Using swept code & direct position update (no physics engine)
// This function is the generic character controller logic, valid for all swept volumes
void SweepTest::MoveCharacter(
					void* user_data,
					void* user_data2,
					SweptVolume& volume,
					const NxVec3& direction,
					NxU32 nb_boxes, const NxExtendedBounds3* boxes, const void** box_user_data,
					NxU32 nb_capsules, const NxExtendedCapsule* capsules, const void** capsule_user_data,
					NxU32 groups, float min_dist,
					NxU32& collision_flags,
					const NxGroupsMask* groupsMask,
					bool constrainedClimbingMode
					 )
{
	mHitNonWalkable = false;
	NxU32 CollisionFlags = 0;
	const NxU32 MaxIter = mMaxIter;	// 1 for "collide and stop"
	const NxU32 MaxIterUp = MaxIter;
	const NxU32 MaxIterSides = MaxIter;
//	const NxU32 MaxIterDown = gWalkExperiment ? MaxIter : 1;
	const NxU32 MaxIterDown = 1;

	// ### this causes the artificial gap on top of chars
	float StepOffset = mStepOffset;	// Default step offset can be cancelled in some cases.

	// Save initial height
	Extended OriginalHeight = volume.mCenter[mUpDirection];
	Extended OriginalBottomPoint = OriginalHeight - volume.mHalfHeight;	// UBI

	// TEST! Disable auto-step when flying. Not sure this is really useful.
	if(direction[mUpDirection]>0.0f)
		StepOffset = 0.0f;

	// Decompose motion into 3 independent motions: up, side, down
	// - if the motion is purely down (gravity only), the up part is needed to fight accuracy issues. For example if the
	// character is already touching the geometry a bit, the down sweep test might have troubles. If we first move it above
	// the geometry, the problems disappear.
	// - if the motion is lateral (character moving forward under normal gravity) the decomposition provides the autostep feature
	// - if the motion is purely up, the down part can be skipped

	NxVec3 UpVector(0.0f, 0.0f, 0.0f);
	NxVec3 DownVector(0.0f, 0.0f, 0.0f);

	if(direction[mUpDirection]<0.0f)	DownVector[mUpDirection] = direction[mUpDirection];
	else								UpVector[mUpDirection] = direction[mUpDirection];

	NxVec3 SideVector = direction;
	SideVector[mUpDirection] = 0.0f;

	// If the side motion is zero, i.e. if the character is not really moving, disable auto-step.
	if(!SideVector.isZero())
		UpVector[mUpDirection] += StepOffset;

	// ==========[ Initial volume query ]===========================
	if(1)
	{
		NxVec3 MotionExtents = UpVector;
		MotionExtents.max(SideVector);
		MotionExtents.max(DownVector);

		NxExtendedBounds3 TemporalBox;
		volume.ComputeTemporalBox(*this, TemporalBox, volume.mCenter, MotionExtents);

		// Gather touched geoms
		UpdateTouchedGeoms(user_data, volume,
							nb_boxes, boxes, box_user_data,
							nb_capsules, capsules, capsule_user_data,
							groups, TemporalBox, groupsMask);
	}

	// ==========[ UP PASS ]===========================

	mCachedTriIndexIndex = 0;
	const bool PerformUpPass = true;
	NxU32 NbCollisions=0;

	if(PerformUpPass)
	{
		// Prevent user callback for up motion. This up displacement is artificial, and only needed for auto-stepping.
		// If we call the user for this, we might eventually apply upward forces to objects resting on top of us, even
		// if we visually don't move. This produces weird-looking motions.
		mValidateCallback = false;

		// In the walk-experiment we explicitely want to ban any up motions, to avoid characters climbing slopes they shouldn't climb.
		// So let's bypass the whole up pass.
		if(!mWalkExperiment)
		{
			// ### MaxIter here seems to "solve" the V bug
			if(DoSweepTest(user_data,
				user_data2,
				nb_boxes, boxes, box_user_data,
				nb_capsules, capsules, capsule_user_data,
				volume, UpVector, MaxIterUp, &NbCollisions, groups, min_dist, groupsMask))
			{
				if(NbCollisions)
				{
					CollisionFlags |= NXCC_COLLISION_UP;

					// Clamp step offset to make sure we don't undo more than what we did
					Extended Delta = volume.mCenter[mUpDirection] - OriginalHeight;
					if(Delta<StepOffset)
					{
						StepOffset=float(Delta);
					}
				}
			}
		}
	}

	// ==========[ SIDE PASS ]===========================

	mCachedTriIndexIndex = 1;
	mValidateCallback = true;
	const bool PerformSidePass = true;

	if(PerformSidePass)
	{
		NbCollisions=0;
		if(DoSweepTest(user_data,
			user_data2,
			nb_boxes, boxes, box_user_data,
			nb_capsules, capsules, capsule_user_data,
			volume, SideVector, MaxIterSides, &NbCollisions, groups, min_dist, groupsMask))
		{
			if(NbCollisions)	CollisionFlags |= NXCC_COLLISION_SIDES;
		}
	}

	// ==========[ DOWN PASS ]===========================

	mCachedTriIndexIndex = 2;
	const bool PerformDownPass = true;

	if(PerformDownPass)
	{
		NbCollisions=0;

		if(!SideVector.isZero())	// We disabled that before so we don't have to undo it in that case
			DownVector[mUpDirection] -= StepOffset;	// Undo our artificial up motion

		mValidTri = false;

		// min_dist actually makes a big difference :(
		// AAARRRGGH: if we get culled because of min_dist here, mValidTri never becomes valid!
		if(DoSweepTest(user_data,
			user_data2,
			nb_boxes, boxes, box_user_data,
			nb_capsules, capsules, capsule_user_data,
			volume, DownVector, MaxIterDown, &NbCollisions, groups, min_dist, groupsMask, true))
		{
			if(NbCollisions)
			{
				CollisionFlags |= NXCC_COLLISION_DOWN;
				if(mHandleSlope)	// PT: I think the following fix shouldn't be performed when mHandleSlope is false.
				{
					// PT: the following code is responsible for a weird capsule behaviour,
					// when colliding against a highly tesselated terrain:
					// - with a large direction vector, the capsule gets stuck against some part of the terrain
					// - with a slower direction vector (but in the same direction!) the capsule manages to move
					// I will keep that code nonetheless, since it seems to be useful for them.

					// constrainedClimbingMode
					if ( constrainedClimbingMode && mContactPointHeight > OriginalBottomPoint + StepOffset)
					{
						mHitNonWalkable = true;
						if(!mWalkExperiment)
							return;
					}
					//~constrainedClimbingMode
				}
			}
		}

		// TEST: do another down pass if we're on a non-walkable poly
		// ### kind of works but still not perfect
		// ### could it be because we zero the Y impulse later?
		// ### also check clamped response vectors
		if(mHandleSlope && mValidTri && direction[mUpDirection]<0.0f)
		{
			NxVec3 Normal;
		#ifdef USE_CONTACT_NORMAL_FOR_SLOPE_TEST
			Normal = mCN;
		#else
			mTouched.normal(Normal);
		#endif
		//	if(fabsf(Normal|NxVec3(0.0f, 1.0f, 0.0f))<cosf(45.0f*DEGTORAD))
			if(Normal[mUpDirection]>=0.0f && Normal[mUpDirection]<mSlopeLimit)
			{
				mHitNonWalkable = true;
				// Early exit if we're going to run this again anyway...
				if(!mWalkExperiment)	return;
		/*		CatchScene()->GetRenderer()->AddLine(mTouched.mVerts[0], mTouched.mVerts[1], ARGB_YELLOW);
				CatchScene()->GetRenderer()->AddLine(mTouched.mVerts[0], mTouched.mVerts[2], ARGB_YELLOW);
				CatchScene()->GetRenderer()->AddLine(mTouched.mVerts[1], mTouched.mVerts[2], ARGB_YELLOW);
		*/

				// ==========[ WALK EXPERIMENT ]===========================

				mNormalizeResponse=true;

				Extended Delta = volume.mCenter[mUpDirection] > OriginalHeight ? volume.mCenter[mUpDirection] - OriginalHeight : 0.0f;
				Delta += fabsf(direction[mUpDirection]);
				Extended Recover = Delta;

				NbCollisions=0;
				const Extended MD = Recover < min_dist ? Recover/float(MaxIter) : min_dist;

				NxVec3 RecoverPoint(0,0,0);
				RecoverPoint[mUpDirection]=-float(Recover);

				if(DoSweepTest(user_data,
					user_data2,
					nb_boxes, boxes, box_user_data,
					nb_capsules, capsules, capsule_user_data,
					volume, RecoverPoint, MaxIter, &NbCollisions, groups, float(MD), groupsMask))
				{
		//			if(NbCollisions)	CollisionFlags |= COLLISION_Y_DOWN;
					// PT: why did we do this ? Removed for now. It creates a bug (non registered event) when we land on a steep poly.
					// However this might have been needed when we were sliding on those polygons, and we didn't want the land anim to
					// start while we were sliding.
		//			if(NbCollisions)	CollisionFlags &= ~NXCC_COLLISION_DOWN;
				}
				mNormalizeResponse=false;
			}
		}
	}

	// Setup new collision flags
	collision_flags = CollisionFlags;
}
	virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
	{
		QUICK_SCOPE_CYCLE_COUNTER( STAT_DrawFrustumSceneProxy_DrawDynamicElements );

		FVector Direction(1,0,0);
		FVector LeftVector(0,1,0);
		FVector UpVector(0,0,1);

		FVector Verts[8];

		// FOVAngle controls the horizontal angle.
		const float HozHalfAngleInRadians = FMath::DegreesToRadians(FrustumAngle * 0.5f);

		float HozLength = 0.0f;
		float VertLength = 0.0f;
		
		if (FrustumAngle > 0.0f)
		{
			HozLength = FrustumStartDist * FMath::Tan(HozHalfAngleInRadians);
			VertLength = HozLength / FrustumAspectRatio;
		}
		else
		{
			const float OrthoWidth = (FrustumAngle == 0.0f) ? 1000.0f : -FrustumAngle;
			HozLength = OrthoWidth * 0.5f;
			VertLength = HozLength / FrustumAspectRatio;
		}

		// near plane verts
		Verts[0] = (Direction * FrustumStartDist) + (UpVector * VertLength) + (LeftVector * HozLength);
		Verts[1] = (Direction * FrustumStartDist) + (UpVector * VertLength) - (LeftVector * HozLength);
		Verts[2] = (Direction * FrustumStartDist) - (UpVector * VertLength) - (LeftVector * HozLength);
		Verts[3] = (Direction * FrustumStartDist) - (UpVector * VertLength) + (LeftVector * HozLength);

		if (FrustumAngle > 0.0f)
		{
			HozLength = FrustumEndDist * FMath::Tan(HozHalfAngleInRadians);
			VertLength = HozLength / FrustumAspectRatio;
		}

		// far plane verts
		Verts[4] = (Direction * FrustumEndDist) + (UpVector * VertLength) + (LeftVector * HozLength);
		Verts[5] = (Direction * FrustumEndDist) + (UpVector * VertLength) - (LeftVector * HozLength);
		Verts[6] = (Direction * FrustumEndDist) - (UpVector * VertLength) - (LeftVector * HozLength);
		Verts[7] = (Direction * FrustumEndDist) - (UpVector * VertLength) + (LeftVector * HozLength);

		for (int32 X = 0; X < 8; ++X)
		{
			Verts[X] = GetLocalToWorld().TransformPosition(Verts[X]);
		}

		for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
		{
			if (VisibilityMap & (1 << ViewIndex))
			{
				FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
				const FSceneView* View = Views[ViewIndex];

				const uint8 DepthPriorityGroup = GetDepthPriorityGroup(View);
				PDI->DrawLine( Verts[0], Verts[1], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[1], Verts[2], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[2], Verts[3], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[3], Verts[0], FrustumColor, DepthPriorityGroup );

				PDI->DrawLine( Verts[4], Verts[5], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[5], Verts[6], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[6], Verts[7], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[7], Verts[4], FrustumColor, DepthPriorityGroup );

				PDI->DrawLine( Verts[0], Verts[4], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[1], Verts[5], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[2], Verts[6], FrustumColor, DepthPriorityGroup );
				PDI->DrawLine( Verts[3], Verts[7], FrustumColor, DepthPriorityGroup );
			}
		}
	}
//! OnAnimate() is called just before rendering the whole scene.
//! nodes may calculate or store animations here, and may do other useful things,
//! dependent on what they are.
void CSceneNodeAnimatorCameraMaya::animateNode(ISceneNode *node, u32 timeMs)
{
	//Alt + LM = Rotate around camera pivot
	//Alt + LM + MM = Dolly forth/back in view direction (speed % distance camera pivot - max distance to pivot)
	//Alt + MM = Move on camera plane (Screen center is about the mouse pointer, depending on move speed)

	if (node->getType() != ESNT_CAMERA)
		return;

	ICameraSceneNode* camera = static_cast<ICameraSceneNode*>(node);

	if (OldCamera != camera)
	{
		OldTarget = camera->getTarget();
		OldCamera = camera;
	}

	Target = camera->getTarget();

	const SViewFrustum* va = camera->getViewFrustum();

	f32 nRotX = RotX;
	f32 nRotY = RotY;
	f32 nZoom = CurrentZoom;

	if ( (isMouseKeyDown(0) && isMouseKeyDown(2)) || isMouseKeyDown(1) )
	{
		if (!Zooming)
		{
			ZoomStartX = MousePos.X;
			ZoomStartY = MousePos.Y;
			Zooming = true;
			nZoom = CurrentZoom;
		}
		else
		{
			f32 old = nZoom;
			nZoom += (ZoomStartX - MousePos.X) * ZoomSpeed;

			f32 targetMinDistance = 0.1f;
			if (nZoom < targetMinDistance) // jox: fixed bug: bounce back when zooming to close
				nZoom = targetMinDistance;

			if (nZoom < 0)
				nZoom = old;
		}
	}
	else
	{
		if (Zooming)
		{
			f32 old = CurrentZoom;
			CurrentZoom = CurrentZoom + (ZoomStartX - MousePos.X ) * ZoomSpeed;
			nZoom = CurrentZoom;

			if (nZoom < 0)
				nZoom = CurrentZoom = old;
		}

		Zooming = false;
	}

	// Translation ---------------------------------

	core::vector3df translate(OldTarget), UpVector(camera->getUpVector());

	core::vector3df tvectX = Pos - Target;
	tvectX = tvectX.crossProduct(UpVector);
	tvectX.normalize();

	core::vector3df tvectY = (va->getFarLeftDown() - va->getFarRightDown());
	tvectY = tvectY.crossProduct(UpVector.Y > 0 ? Pos - Target : Target - Pos);
	tvectY.normalize();
	

	if (isMouseKeyDown(2) && !Zooming)
	{
		if (!Translating)
		{
			TranslateStartX = MousePos.X;
			TranslateStartY = MousePos.Y;
			Translating = true;
		}
		else
		{
			translate +=  tvectX * (TranslateStartX - MousePos.X)*TranslateSpeed + 
			              tvectY * (TranslateStartY - MousePos.Y)*TranslateSpeed;
		}
	}
	else
	{
		if (Translating)
		{
			translate += tvectX * (TranslateStartX - MousePos.X)*TranslateSpeed + 
			             tvectY * (TranslateStartY - MousePos.Y)*TranslateSpeed;
			OldTarget = translate;
		}

		Translating = false;
	}

	// Rotation ------------------------------------

	if (isMouseKeyDown(0) && !Zooming)
	{
		if (!Rotating)
		{
			RotateStartX = MousePos.X;
			RotateStartY = MousePos.Y;
			Rotating = true;
			nRotX = RotX;
			nRotY = RotY;
		}
		else
		{
			nRotX += (RotateStartX - MousePos.X) * RotateSpeed;
			nRotY += (RotateStartY - MousePos.Y) * RotateSpeed;
		}
	}
	else
	{
		if (Rotating)
		{
			RotX = RotX + (RotateStartX - MousePos.X) * RotateSpeed;
			RotY = RotY + (RotateStartY - MousePos.Y) * RotateSpeed;
			nRotX = RotX;
			nRotY = RotY;
		}

		Rotating = false;
	}

	// Set Pos ------------------------------------

	Target = translate;

	Pos.X = nZoom + Target.X;
	Pos.Y = Target.Y;
	Pos.Z = Target.Z;

	Pos.rotateXYBy(nRotY, Target);
	Pos.rotateXZBy(-nRotX, Target);

	// Rotation Error ----------------------------

	// jox: fixed bug: jitter when rotating to the top and bottom of y
	UpVector.set(0,1,0);
	UpVector.rotateXYBy(-nRotY);
	UpVector.rotateXZBy(-nRotX+180.f);

	camera->setPosition(Pos);
	camera->setTarget(Target);
	camera->setUpVector(UpVector);
}
Example #7
0
void lcLight::DrawSpotLight(lcContext* Context, const lcMatrix44& ViewMatrix) const
{
	lcVector3 FrontVector(mTargetPosition - mPosition);
	lcVector3 UpVector(1, 1, 1);

	if (fabs(FrontVector[0]) < fabs(FrontVector[1]))
	{
		if (fabs(FrontVector[0]) < fabs(FrontVector[2]))
			UpVector[0] = -(UpVector[1] * FrontVector[1] + UpVector[2] * FrontVector[2]);
		else
			UpVector[2] = -(UpVector[0] * FrontVector[0] + UpVector[1] * FrontVector[1]);
	}
	else
	{
		if (fabs(FrontVector[1]) < fabs(FrontVector[2]))
			UpVector[1] = -(UpVector[0] * FrontVector[0] + UpVector[2] * FrontVector[2]);
		else
			UpVector[2] = -(UpVector[0] * FrontVector[0] + UpVector[1] * FrontVector[1]);
	}

	lcMatrix44 LightMatrix = lcMatrix44LookAt(mPosition, mTargetPosition, UpVector);
	LightMatrix = lcMatrix44AffineInverse(LightMatrix);
	LightMatrix.SetTranslation(lcVector3(0, 0, 0));

	lcMatrix44 LightViewMatrix = lcMul(LightMatrix, lcMul(lcMatrix44Translation(mPosition), ViewMatrix));
	Context->SetWorldViewMatrix(LightViewMatrix);

	float Verts[(20 + 8 + 2 + 16) * 3];
	float* CurVert = Verts;

	for (int EdgeIdx = 0; EdgeIdx < 8; EdgeIdx++)
	{
		float c = cosf((float)EdgeIdx / 4 * LC_PI) * LC_LIGHT_POSITION_EDGE;
		float s = sinf((float)EdgeIdx / 4 * LC_PI) * LC_LIGHT_POSITION_EDGE;

		*CurVert++ = c;
		*CurVert++ = s;
		*CurVert++ = LC_LIGHT_POSITION_EDGE;
		*CurVert++ = c;
		*CurVert++ = s;
		*CurVert++ = -LC_LIGHT_POSITION_EDGE;
	}

	*CurVert++ = -12.5f; *CurVert++ = -12.5f; *CurVert++ = -LC_LIGHT_POSITION_EDGE;
	*CurVert++ =  12.5f; *CurVert++ = -12.5f; *CurVert++ = -LC_LIGHT_POSITION_EDGE;
	*CurVert++ =  12.5f; *CurVert++ =  12.5f; *CurVert++ = -LC_LIGHT_POSITION_EDGE;
	*CurVert++ = -12.5f; *CurVert++ =  12.5f; *CurVert++ = -LC_LIGHT_POSITION_EDGE;

	float Length = FrontVector.Length();

	*CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE - Length;
	*CurVert++ =  LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE; *CurVert++ = -LC_LIGHT_TARGET_EDGE - Length;

	*CurVert++ = 0.0f; *CurVert++ = 0.0f; *CurVert++ = 0.0f;
	*CurVert++ = 0.0f; *CurVert++ = 0.0f; *CurVert++ = -Length;

	const GLushort Indices[56 + 24 + 2 + 40] = 
	{
		0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
		0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 0,
		1, 3, 3, 5, 5, 7, 7, 9, 9, 11, 11, 13, 13, 15, 15, 1,
		16, 17, 17, 18, 18, 19, 19, 16,
		20, 21, 21, 22, 22, 23, 23, 20,
		24, 25, 25, 26, 26, 27, 27, 24,
		20, 24, 21, 25, 22, 26, 23, 27,
		28, 29,
		30, 31, 31, 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38,
		38, 39, 39, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 30,
		28, 30, 28, 34, 28, 38, 28, 42
	};

	Context->SetVertexBufferPointer(Verts);
	Context->SetVertexFormat(0, 3, 0, 0);

	float LineWidth = lcGetPreferences().mLineWidth;

	if (!IsSelected())
	{
		Context->SetLineWidth(LineWidth);
		lcSetColorLight();

		glDrawElements(GL_LINES, 56 + 24 + 2, GL_UNSIGNED_SHORT, Indices);
	}
	else
	{
		if (IsSelected(LC_LIGHT_SECTION_POSITION))
		{
			Context->SetLineWidth(2.0f * LineWidth);
			if (IsFocused(LC_LIGHT_SECTION_POSITION))
				lcSetColorFocused();
			else
				lcSetColorSelected();
		}
		else
		{
			Context->SetLineWidth(LineWidth);
			lcSetColorLight();
		}

		glDrawElements(GL_LINES, 56, GL_UNSIGNED_SHORT, Indices);

		if (IsSelected(LC_LIGHT_SECTION_TARGET))
		{
			Context->SetLineWidth(2.0f * LineWidth);
			if (IsFocused(LC_LIGHT_SECTION_TARGET))
				lcSetColorFocused();
			else
				lcSetColorSelected();
		}
		else
		{
			Context->SetLineWidth(LineWidth);
			lcSetColorLight();
		}

		glDrawElements(GL_LINES, 24, GL_UNSIGNED_SHORT, Indices + 56);

		Context->SetLineWidth(LineWidth);
		lcSetColorLight();

		float Radius = tanf(LC_DTOR * mSpotCutoff) * Length;

		for (int EdgeIdx = 0; EdgeIdx < 16; EdgeIdx++)
		{
			*CurVert++ = cosf((float)EdgeIdx / 16 * LC_2PI) * Radius;
			*CurVert++ = sinf((float)EdgeIdx / 16 * LC_2PI) * Radius;
			*CurVert++ = -Length;
		}

		glDrawElements(GL_LINES, 2 + 40, GL_UNSIGNED_SHORT, Indices + 56 + 24);
	}

	Context->ClearVertexBuffer(); // context remove
}