I’m trying to represent orbit guidelines with a series of tube meshes all connected together like a spline. I’m using the Spline Mesh Component, but the points don’t seem to twist correctly.
The code is relatively simple, it plots the orbit first and stores it in an array of FVectors, then for each ‘TorusSplinePoint’ (which is a SplineMeshComponent), it then sets the start and end positions. In the code below I tried calculating tangents manually, but it had no effect.
void AGESGame_Orrery::PostInitializeComponents()
{
Super::PostInitializeComponents();
TorusSplinePoints.SetNum(NumTorusPoints);
TorusSplinePositions.SetNumZeroed(NumTorusPoints);
// Create all points for the Orbit Torus
UWorld* CompWorld = GetWorld();
if (OrbitTorusMesh != nullptr)
{
for (int32 i = 0; i < NumTorusPoints; i++)
{
USplineMeshComponent* NewMeshComp = NewObject<USplineMeshComponent>(this);
if (NewMeshComp)
{
NewMeshComp->SetMobility(EComponentMobility::Movable);
NewMeshComp->RegisterComponentWithWorld(CompWorld);
NewMeshComp->SetAbsolute(true, true, true);
NewMeshComp->SetStaticMesh(OrbitTorusMesh);
NewMeshComp->SetCollisionResponseToAllChannels(ECR_Ignore);
NewMeshComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
TorusSplinePoints* = NewMeshComp;
}
}
}
}
void AGESGame_Orrery::SetTorusForOrbit(const FOrbitParams& InOrbit, float Radius)
{
const FRotator OrbitPlane_Rot = FRotator(0.f, InOrbit.LongitudeAscendingNode, InOrbit.Inclination * -1.0f);
const FVector OrbitPlane_Up = FRotationMatrix(OrbitPlane_Rot).GetScaledAxis(EAxis::Z).GetSafeNormal();
// Plots the Orbit Torus for a given Orbit
const double EAngle = 360.f / NumTorusPoints;
for (int32 i = 0; i < NumTorusPoints; i++)
{
TorusSplinePositions* = PosFromEccAnomaly(FMath::DegreesToRadians(i * EAngle), InOrbit);
}
FVector StartTangent, EndTangent;
FVector PrevPosition, NextPosition, NextNextPosition;
for (int32 i = 0; i < NumTorusPoints; i++)
{
PrevPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i - 1)];
NextPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 1)];
NextNextPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 2)];
StartTangent = FVector(NextPosition - PrevPosition).GetSafeNormal();
EndTangent = FVector(NextNextPosition - NextPosition).GetSafeNormal();
TorusSplinePoints*->SetStartAndEnd(TorusSplinePositions*, StartTangent, NextPosition, EndTangent);
TorusSplinePoints*->SetWorldScale3D(FVector(Radius));
}
}
Another nice approach to this would be a procedural mesh, which would reduce my draw calls and allow for a more circular shape - but I haven’t really got the time to start with that
You can actually combine those approaches.
I did something simmilar for a pipe/duct work system.
A simple function creates an uncapped cylinder along splinepoints.
But in the case of orbits, you can even skip the whole spline thing completely as it can be described parametric as well…
Im not proficient enough to help you in C++, but in blueprints its “relatively” easy…
Proc Mesh would be useful, but more complex of course. I also haven’t done anything with them yet, so not sure where to start even with just a simple cylinder.
I created a set of function that do just simple things and went from there.
One function just creates a quad, relatve to an origin.
That origin is now rotated and new quads are created.
So if you set the origin to (5/0/0),
draw a quad (with -5/0/0 as normal)
rotate the orgin by n/360 deg.
repeat n times
Then you have a cylinder with n side faces
the origin then is the center of the cylinder.
I wrapped that in yet another function that creates cylinders based on a given origin and loop constructed my pipes from there (driven by spline points or simple math parameter)
It should not be that complicated. Neither in C++, nor in BP
That project went overboard with my 4.9 version. (Has been a while).
But if I find the time and passion in the next few days, I might recreate it.
By now I also have more routine ith BP workflows to do some things more effectively.
Will be interesting for myself too to see how the “new” graphs look
Ok, I made my orbits draw itself with the SplineMesh plotted along the ellipse path. There can be any number of spline segments, but if segment mesh has enough longitudial geometry, 4 segmetns is ok, mesh will be curved along the spline. Orbit places its ellipse focus into planet centre. Orbit plane rotation and alignment is done via actor transforms, so I had not to scratch my head to calculate rotations by myself (this is tricky to rotate things without quaterions properly). I rotate spline positions and tangents only around one axis.
First I simply followed this great tutorial
and then made “guide” spline not drawn in editor but generated to follow ellipse path (squashed circle actually). That’s it.
In theory that’s exactly what I’m doing above, just instead of creating a Spline Component to store the tangents/ locations I’m storing all that in two arrays.
EDIT:
Additionally, it seems as though you are transforming the entire spline object when moving it around, rather than just the individual segments. Perhaps I need to ‘rotate’ the spline meshes, or somehow force them to world-space instead of local space.
You could attach your spline meshes to some SceneComponent (that is in turn atteched to root) and then you can rotate this SceneComponent with all children Spline Meshes as a whole orbit.
Tangents were completely wrong, however I have to manually calculate them AND set the upvector of the spline. It all works apart from the very first point which I cannot figure out.
void AGESGame_Orrery::SetTorusForOrbit(const FOrbitParams& InOrbit, float Radius)
{
const FRotator OrbitPlane_Rot = FRotator(0.f, InOrbit.LongitudeAscendingNode, InOrbit.Inclination * -1.0f);
const FVector OrbitPlane_Up = FRotationMatrix(OrbitPlane_Rot).GetScaledAxis(EAxis::Z).GetSafeNormal();
// Plots the Orbit Torus for a given Orbit
const double EAngle = 360.f / NumTorusPoints;
for (int32 i = 0; i < NumTorusPoints; i++)
{
TorusSplinePositions* = UGESGameplayStatics::PosFromEccAnomaly(FMath::DegreesToRadians(i * EAngle), InOrbit);
}
for (int32 i = 0; i < NumTorusPoints; i++)
{
const FVector PrevPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i - 1)];
const FVector StartPosition = TorusSplinePositions*;
const FVector EndPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 1)];
const FVector NextEndPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 2)];
const FVector EndTangent = FVector(StartPosition - NextEndPosition).GetSafeNormal();
const FVector StartTangent = FVector(PrevPosition - EndPosition).GetSafeNormal();
TorusSplinePoints*->SetStartPosition(StartPosition);
TorusSplinePoints*->SetEndPosition(EndPosition);
TorusSplinePoints*->SetSplineUpDir(OrbitPlane_Up);
TorusSplinePoints*->SetStartTangent(StartTangent);
TorusSplinePoints*->SetEndTangent(EndTangent);
TorusSplinePoints*->SetWorldScale3D(FVector(Radius));
DrawDebugString(GetWorld(), StartPosition, FString::Printf(TEXT("%i"), i), NULL, FColor::Green, 5.f, true);
}
}
Okay got it (five mins later), looks like I had to manually update the start tangent again of the first spline point. I have absolutely no idea why, since I already do it once in the loop. I guess the problem is that it’s render state is dirty and it doesn’t get updated correctly. Final code therefore, is this:
void AGESGame_Orrery::SetTorusForOrbit(const FOrbitParams& InOrbit, float Radius)
{
const FRotator OrbitPlane_Rot = FRotator(0.f, InOrbit.LongitudeAscendingNode, InOrbit.Inclination * -1.0f);
const FVector OrbitPlane_Up = FRotationMatrix(OrbitPlane_Rot).GetScaledAxis(EAxis::Z).GetSafeNormal();
// Plots the Orbit Torus for a given Orbit
const double EAngle = 360.f / NumTorusPoints;
for (int32 i = 0; i < NumTorusPoints; i++)
{
TorusSplinePositions* = UGESGameplayStatics::PosFromEccAnomaly(FMath::DegreesToRadians(i * EAngle), InOrbit);
}
for (int32 i = 0; i < NumTorusPoints; i++)
{
const FVector PrevPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i - 1)];
const FVector StartPosition = TorusSplinePositions*;
const FVector EndPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 1)];
const FVector NextEndPosition = TorusSplinePositions[UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 2)];
const FVector EndTangent = FVector(StartPosition - NextEndPosition).GetSafeNormal();
const FVector StartTangent = FVector(PrevPosition - EndPosition).GetSafeNormal();
TorusSplinePoints*->SetStartPosition(StartPosition);
TorusSplinePoints*->SetEndPosition(EndPosition);
TorusSplinePoints*->SetSplineUpDir(OrbitPlane_Up);
TorusSplinePoints*->SetStartTangent(StartTangent);
TorusSplinePoints*->SetEndTangent(EndTangent);
TorusSplinePoints*->SetWorldScale3D(FVector(Radius));
DrawDebugString(GetWorld(), StartPosition, FString::Printf(TEXT("%i"), i), NULL, FColor::Green, 5.f, true);
}
// WHY FFS?!
TorusSplinePoints[0]->SetStartTangent(TorusSplinePoints.Last()->GetEndTangent());
}
Annnd back so soon. Turns out that implementation only works if there’s no interpolation between (extra vertices etc). I gave in and used a spline component to store the tangents and world positions, and it works much better (annoyingly).
You still need to set the up vector, but hell I can live with it… The spline mesh components need some serious usability work.
void AGESGame_Orrery::SetTorusForOrbit(const FOrbitParams& InOrbit, float Radius)
{
const FRotator OrbitPlane_Rot = FRotator(0.f, InOrbit.LongitudeAscendingNode, InOrbit.Inclination * -1.0f);
const FVector OrbitPlane_Up = FRotationMatrix(OrbitPlane_Rot).GetScaledAxis(EAxis::Z).GetSafeNormal();
// Plots the Orbit Torus for a given Orbit
const double EAngle = 360.f / NumTorusPoints;
for (int32 i = 0; i < NumTorusPoints; i++)
{
TorusSpline->SetLocationAtSplinePoint(i, UGESGameplayStatics::PosFromEccAnomaly(FMath::DegreesToRadians(i * EAngle), InOrbit), ESplineCoordinateSpace::World);
}
for (int32 i = 0; i < NumTorusPoints; i++)
{
const FVector StartPosition = TorusSpline->GetWorldLocationAtSplinePoint(i);
const FVector EndPosition = TorusSpline->GetWorldLocationAtSplinePoint(UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 1));
const FVector StartTangent = TorusSpline->GetTangentAtSplinePoint(i, ESplineCoordinateSpace::World);
const FVector EndTangent = TorusSpline->GetTangentAtSplinePoint(UGESGameplayStatics::GetRolloverArrayIndex(NumTorusPoints, i + 1), ESplineCoordinateSpace::World);
TorusSplinePoints*->SetStartPosition(StartPosition);
TorusSplinePoints*->SetEndPosition(EndPosition);
TorusSplinePoints*->SetStartTangent(StartTangent);
TorusSplinePoints*->SetEndTangent(EndTangent);
TorusSplinePoints*->SetSplineUpDir(OrbitPlane_Up);
}
}