Jump to content
  • Advertisement
Gnollrunner

C++ Any less ugly and more standard way of doing this?

Recommended Posts

So if you look at the last post I wrote, here it is the actual main function implementation:

//main.cpp

#include "Voxel.h"

int main()
{
	Voxel voxel_map(1,32,324,45,2,0,8,10);
}

 

Share this post


Link to post
Share on other sites
Advertisement
On 6/27/2019 at 10:48 PM, Gnollrunner said:
On 6/27/2019 at 10:02 PM, Magogan said:

Or, you know, you could have just allocated an array of children.

Won't work in this case.  Each child is a separate class.

Usually i shy away from discussion about programming itself, but to me this thread sounds you're running into some kind of hassle because you are used to combine data and function into classes. 

Wouldn't this be easier if you had just one class to group functions that operate on tree data, instead one class per node?

This could go so far you have some classes without member data at all and just static member functions. This leads to longer parameter lists, but pointer arithmetic would become simple index math; data layout and memory management becomes independent from class logic; code might be easier to read and reuse ...and stuff like that.

Share this post


Link to post
Share on other sites
Posted (edited)
5 hours ago, Green_Baron said:

@Gnollrunner, (Edit: i was ninja'd by @Acosix while saving so the direct address :-)) now you've underestimated me. I know all that. I am just trying to distract you from hardened views in the hope the long sought for flash of insight might come upon you 😎 Your problem should be solvable imo.

I have a LOD system (height map extrusion only) with adaptive LODing capable of rendering a planet (see my git rep orf_n, under Source/Applications/TerrainCDLOD, most of the LOD magic is in the vertex shader, and it is after P. Strugar's published CDLOD algorithm, you probably know it). But there i don't subdivide the spacial data structure (a quadtree) down the vertex but to a (for now pre-set) leaf node size of let's say 8*8 or 16*16, even 64*64. Depends. Node selection for rendering takes place every frame, based on view frustum and depth of view, so the actually rendered part is quite small (without graceful fog and celestial body curvature and occlusion culling which will be added in the future, maybe 2million triangles, framerate high (~2000/s) with basic shading. Currently, i am struggling with exactly the streaming part for my terrain tiles, that is why i am so interested in that. Theoretically the only limiting factor should be the hard disk (or network server or whatever is at the other end of the cable), and a little precalculation for the orbital overview, and maybe a transition between high orbit, low orbit and ground/flight fully LODed, but to load  a tile and build the quadtree takes only little time. But seamlessly zooming in and out shouldn't be much of a problem. All do that 🙂

I am aware that i can't compare this to your constantly changing voxels of a bubbling and boiling planet, but are you completely sure you cannot swap part of your terrain out, and do you really need any LOD level at any height, can't you really apply part of the above (or other) techniques with adaptations for your use case ? That sounds like a pretty hard restriction to me.

 

Edit: i have never played No Man's Sky, from what i read it has its flaws. But i know for example Orbiter (which renders real world planets from published height data) and Kerbal Space Program (small planets, like your size). And there are a whole lot of planet renderers, open or closed, out there. All these don't do voxels, so a dimension of difficulty is missing. There's the challenge 🙂

My specific problem is solvable. I already have a solution for it.  I opted to just have a 2 byte offset in the children that I pack in with other flags and stuff. It gets me to the enclosing class.  I'm sure it breaks many people's ideas about proper coding, but it works. This discussion got way beyond my actual initial question 😛 I guess that tends to happen.

In any case I guess my point was, however you do your LOD, the data has to come from somewhere. For an entire planet if it's not at least partially from a function, it's going to be huge. It needs to be stored locally or perhaps streamed to a player for a multiplayer game. That's part of the equation when doing LOD.   Until you've solved the entire problem including the problems of scale, physics, pathing, so forth,  you haven't really solved the problem.  That goes for me too. 

Also I see a lot of barren planets with no trees, or buildings rivers etc. A games needs those too.  Even for those with trees, it's often just a repeated sparsely placed tree. There isn't a lot of variation in the biome.  I note that there are a lot of planet renderers yet relatively few games that support high resolution worlds, at least not as of yet.  Hopefully soon there will be.

You are right in that height mapped terrain is not such a big deal.  I did an early very crude planet generator a few years ago where I implemented height mapped LOD.

NorthPoll-1.JPG.c5ba86da4f984836d914c9c9e71d9c37.JPG

 

At least it had collision. I re-wrote it recently for my sun implementation which is on my blog.  There, It's just doing a sphere, but it accepts height data so it could be anything height mapped.  I won't use it for planets however, since as I said I want to support underground terrain.  

My personal preference is to do stuff on the CPU even though the GPU should be somewhat faster, for a couple of reasons.  For one, the code isn't tied to any specific shader language and isn't dependent on having a high end graphics card. The second reason is that I'm always thinking that I need to do collision and pathing.  The physics model has to match closely the graphics model, otherwise you get stuff like feet sticking in the terrain and so forth. For me it's conceptually easier to have a unified world coordinate system, so I prefer double precision.

I'm sure there are a lot of different ways to handle all this stuff, but again for a game one needs to handle everything and I think that's where the challenge comes it.

 

Edited by Gnollrunner

Share this post


Link to post
Share on other sites
7 hours ago, Green_Baron said:

@Gnollrunner, (Edit: i was ninja'd by @Acosix while saving so the direct address :-)) now you've underestimated me. 

BTW. I wouldn't say that. I had you pegged as one of the sharper guys here.  It's simply I didn't know your background and what you are working on, so I probably when to to more detail than necessary.

Share this post


Link to post
Share on other sites
6 hours ago, JoeJ said:

Usually i shy away from discussion about programming itself, but to me this thread sounds you're running into some kind of hassle because you are used to combine data and function into classes. 

Wouldn't this be easier if you had just one class to group functions that operate on tree data, instead one class per node?

This could go so far you have some classes without member data at all and just static member functions. This leads to longer parameter lists, but pointer arithmetic would become simple index math; data layout and memory management becomes independent from class logic; code might be easier to read and reuse ...and stuff like that.

I suppose there are many different ways of handling this. I use a lot of virtual functions templates. Some have a fair number of parameters....

template<class P, class G,
   class C1, class C2, class C3, class C4,
   class C5, class C6, class C7, class C8,
   class CF1, class CF23, class CF4, class CFT, class CFQ,
   EDLFaceSides FSC1, EDLFaceSides FSP_C23, EDLFaceSides FSC4, int IPOINT>
   CDLObjectOctree *TDLSpherePrism<P, G, C1, C2, C3, C4, C5, C6, C7, C8, CF1, CF23, CF4, CFT, CFQ,  FSC1, FSP_C23, FSC4, IPOINT>::Subdivide(CDLGhostTree *pGST)
{
     .....
}

It lets me have a single function that does basically the same thing but with slight variations such that I can avoid a lot of if statements while it's running.  It makes the decision about how handle things up front and then calls the exact routine for that case. I guess I could still do this by implementing the polymorphism myself, but I'd rather let the compiler do it. In any case that's a side issue. I can still address them as an array with some pointer arithmetic.

The main thing is getting from a member class object, to the one that encloses it the most efficient way possible without hacking up the rest of the code too much. What I've kind of settled on is a two byte offset in the member class, that gets me to the enclosing class.  I can squeeze it in with a bunch of other small flags and stuff without going to the next 8 byte alignment (which the architecture/compiler enforces in most cases). I actually comment my code sometimes with "// this takes 1 byte" ... "// This takes 2 bytes" .... etc. It helps me keep track of my much memory objects are taking and when changeling something will push it to the next alignment boundary.

I was an assembly language programmer for several years before learning C++. So I'm always thinking in terms of memory layout and how things work at a low level,  This always seems to annoy a lot of other C++ programmers 😀. But whatever. I don't expect my code to win a beauty contest.

Share this post


Link to post
Share on other sites
3 hours ago, Gnollrunner said:

I suppose there are many different ways of handling this. I use a lot of virtual functions templates. Some have a fair number of parameters....

Ok, you beat me in having long parameter lists :D

Btw, what sucks about templates is the need to include lots of implementation files in the header, so compile times sky rocket. Often i end up duplicating some code with minor changes, although using templates would require the function written only once :(

If someone thinks i'm missing something here let me know...

 

Share this post


Link to post
Share on other sites
2 hours ago, JoeJ said:

Btw, what sucks about templates is the need to include lots of implementation files in the header, so compile times sky rocket. Often i end up duplicating some code with minor changes, although using templates would require the function written only once :(

Well, you can just write your template function as usual and your specialized version just calls the template. If the implementation of the specialized function (and all the necessary includes) is in a .cpp file, the compile time issues should disappear without the need of code duplication.

 

Greetings

Share this post


Link to post
Share on other sites
34 minutes ago, DerTroll said:

Well, you can just write your template function as usual and your specialized version just calls the template. If the implementation of the specialized function (and all the necessary includes) is in a .cpp file, the compile time issues should disappear without the need of code duplication.

Yeah, that's a point and likely all you can do. But still i have to include stuff to the header file where the teplate function is.

This is an example:

Spoiler

#pragma once

#include "MeshProcessing.h"
#include "MeshConnectivity.h"
#include "CotanWeights.h"

namespace MeshProcessing
{
	//class MeshConnectivity;
	//class CotanWeights;

	namespace Diffusion // only triangle meshes
	{


		template <typename T>
		static void Diffusion::Integrate (std::vector<T> &out,
			const std::vector<T> &in, 
			const std::vector<float> &area,
			const std::vector<float> &weights,
			const std::vector< std::array<int,2> > &edgeAdjacency,
			const int edgeCount,
			const float timestep)
		{
			out = in;

			for (int eI=0; eI<edgeCount; eI++)
			{
				int i0 = edgeAdjacency[eI][0];
				int i1 = edgeAdjacency[eI][1];
				
				float area0 = area[i0];
				float area1 = area[i1];
					
				float factor = weights[eI] * timestep * 0.5f; 
				T diff = (in[i1] - in[i0]);
				out[i0] += diff * factor / area0;
				out[i1] -= diff * factor / area1;
			}
		}



		template <typename T>
		static void Diffusion::Diffuse (std::vector<T> &out,
			const std::vector<T> &in, const bool isVertexMap, const float duration, 
			const MeshConnectivity &conn, const CotanWeights &weights, 
			const int maxIterations = 10000)
		{
		uint64_t profileTime = SystemTools::GetTimeNS();

			out = in;
			float maxTimestep = weights.GetMaxTimestep (isVertexMap);
			int iterations = 1 + int (ceil (duration / maxTimestep));
			iterations = min(iterations, maxIterations);
			if (iterations&1) iterations++;
			float dt = duration / float(iterations);

		ImGui::Text("maxTimestep %f  dt %f  iterations %i", maxTimestep, dt, iterations);

			const std::vector<float> &area = weights.GetAreas(isVertexMap);
			const std::vector<float> &cotWeights = weights.GetWeights(isVertexMap);
			const std::vector< std::array<int,2> > &edgeAdjacency = conn.GetEdgeAdjacency(isVertexMap);
			const int edgeCount = (isVertexMap ? conn.GetEdgeCount() : conn.GetFirstBoundaryEdge()); // ignore boundary edges for polys

			std::vector<T> result;
			result.resize(in.size());
 
			// simple forward integration with minimal timestep
			for (int iter=0; iter<iterations; iter+=2)
			{
				Integrate (result, out, area, cotWeights, edgeAdjacency, edgeCount, dt);
				Integrate (out, result, area, cotWeights, edgeAdjacency, edgeCount, dt);
			}

		ImGui::Text("Diffuse() time: %i ms", (SystemTools::GetTimeNS() - profileTime) / 1000000);
		}

 

No need to read the code in detail, what it is doing is this:

Diffuse either a scalar or vector field over a mesh using edge weights, and using template to have the code just once for both (float / vec3).

To make this work i have to include the mesh connectivity class header files because i use them to know the edges in the mesh graph. This way every header that includes diffusion namespace also includes those things too.

What i want would be to write the template function implementation in the cpp file and only the decalration in the header (as usual). Then i could use just forward declaration of mesh class in the header without a need to include it.

It seems that's impossible, but having no good understanding of how templates work i just ask - sorry for hijacking the thread.

(It's no problem in practice because i include diffusion in cpp files anyways, but with heavy template usage the dependencies would sum up...)

 

 

Share this post


Link to post
Share on other sites
51 minutes ago, JoeJ said:

 

What i want would be to write the template function implementation in the cpp file and only the decalration in the header (as usual). Then i could use just forward declaration of mesh class in the header without a need to include it.

It seems that's impossible, but having no good understanding of how templates work i just ask - sorry for hijacking the thread.

You can sort of do this in many cases. If you know how you want your template to be instantiated, you can do explicit instantiation at the bottom of your cpp file. Then you include your header file at the top as normal, and also the cpp gets compiled as normal.  I do this all time time as it just seems easier in most cases.  Here's one instantiation for the class containing the function declaration I posted before.

Quote

template class TDLSpherePrism<CDLSpherePrismViewUp, CDLSpherePrismViewData,
   CDLSpherePrismViewUpFW2C1, CDLSpherePrismViewUpBW2C2, CDLSpherePrismViewUpBW2C3, CDLSpherePrismViewDnBW2C4,
   CDLSpherePrismViewUpFW2C5, CDLSpherePrismViewUpBW2C6, CDLSpherePrismViewUpBW2C7, CDLSpherePrismViewDnBW2C8, 
   CDLSphereFaceTriViewUpCW2, CDLSphereFaceTriViewUpCC2, CDLSphereFaceTriViewDnCC2, CDLSphereFaceTriView     ,
   CDLSphereFaceQuadView    ,  EDLFaceSides::_FBFFB_   , EDLFaceSides::_FBFBB_    , EDLFaceSides::_FBFBB_, 0>;

 

 

Share this post


Link to post
Share on other sites
Posted (edited)
5 hours ago, Gnollrunner said:

You can sort of do this in many cases. If you know how you want your template to be instantiated, you can do explicit instantiation at the bottom of your cpp file. Then you include your header file at the top as normal, and also the cpp gets compiled as normal.

Not sure if i get this - it keeps me puzzling how the body of declaration in upper post looks like.

I assume the classes like class C1, class C2, etc. inherit frome some base, and you only need to include this light weight base class declaration in the header, while in the cpp file you include the inherited stuff with all the code?

Would make sense to me :)

 

 

Edited by JoeJ

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!