Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concepts and general discussion #183

Open
FishOfTheNorthStar opened this issue Sep 6, 2024 · 21 comments
Open

Concepts and general discussion #183

FishOfTheNorthStar opened this issue Sep 6, 2024 · 21 comments
Labels
question Further information is requested

Comments

@FishOfTheNorthStar
Copy link

( for those just tuning in, this thread is a continuation of a discussion started here #177 )

Hey thanks for the detailed response. So last night I started on a couple precursor classes I expect to use for some basic tests here, in C#. Basically I expect almost everything road-like that might get generated will derive from some common class interface I'm calling a 'i_DRAGGABLE' so far (names may change). For now I'm putting anything I do within a PAVED namespace, and all of the classes involved start with "Paved".

So far I anticipate 5 main categories of 'draggable' subclasses: The Manager, Roads, Highways, Parking Lots, and (eventually) Intersections - So there's PavedRoadManager, PavedHighway, PavedRoad, PavedParkingLot, and eventually PavedIntersection. And those all derive from i_DRAGGABLE.

Internally i_DRAGGABLE's track 3 main objects: REFS (which contains references to generated mesh and child helper objects within the scene), OPTS (options for the draggable, established at initialization by passing in a hard-coded Struct which is then discarded and it's data stored in the OPTS class so it can be stored and passed around as a pointer), and RUNTIME, which is a Resource subclass that tracks user specified data that might change during the input / generation cycle - like a specific list of points a curvy path is generated from, or how many lanes wide a road is at a certain point along it's length, etc).

The OPTS and RUNTIME classes sound very similar but I picture OPTS as mostly static and unchanging per instance, but RUNTIME might change a lot, by the user or the generative process. Also RUNTIME variables can be changed in the inspector with immediate feedback, but OPTS do not, they are created one time at startup and persist until the object is destroyed or reset.

Any names I've picked out are just temporary place holders. Ultimately I'll change them around to better match the current format but for now I wanted to avoid naming conflicts, for clarity.

Each subclass of i_DRAGGABLE would itself have custom subclasses of REFS, OPTS, and RUNTIME. So roads/highways might have certain data elements in RUNTIME that not all i_DRAGGABLES do, etc. At a minimum, any i_DRAGGABLE at least tracks two data members, POINT_A and POINT_B. for road segments that's the end points. For lots/intersections (and the manager), it's the opposing corners of the defined square/rectangle. The manager's rectangle basically defines the world bounding rectangle of the road system.

Internally I expect that intersections will ultimately be considered 'special parking lots', in that they share a lot of the same operating conditions (they're flat, basically square/rectangular, and roads can connect to any edge of that square up to two times per side). That's my thinking going forward but details might shift around a bit.

I'm thinking of parking lots and intersections as 'terminators', and perhaps roads themselves could require at least two terminators to connect? Seems reasonable to me, but the workflow might be a little different than current. Basically I picture that before a person makes a road, first they'd make, say... two distant parking lots, then maybe they'd add an intersection or two in between at important points, and once those key locations are defined, roads can be created between terminator edges, so the user would click one edge of the first parking lot, drag a line over to the nearest edge of the first intersection, if any, and that would create a road segment. Then repeat from another side of that intersection, towards either the next intersection or the end goal parking lot.

Then once a road is basically defined, it's manipulated on a segment level, so you select a segment and can either drag a mid-point handle around to specify the curve of the road, or click a 'subdivide' button which splits the segment into two segments which themselves behave the same way, until the road is appropriately twisty etc.

So I feel like this post went super long, I'll just chop it here. That's basically what I have so far though. Thoughts?

@TheDuckCow
Copy link
Owner

Thanks for sharing the commentary, happy to pepper in some of my own. I have no concerns over naming at all, we'll figure out what makes the most sense once we get to welding the parts together.

So far I anticipate 5 main categories of 'draggable' subclasses: The Manager, Roads, Highways, Parking Lots, and (eventually) Intersections

I'm curious what you have in mind, separating Roads & Highways out. Unless you are thinking of Highways a sort of composite of multiple roads? Or more in terms that you'd need to treat them differently being wider and slower bending?

For further context, while I had originally intended to include the intersection class here in the upcoming v0.5.0 release, the generalized functionality of RoadContainers basically made it unnecessary (for now). Eventually, it might be that the intersection is more of a mesh generation helper rather than its own toplevel class (some thoughts over here).

At the moment, a RoadContainers is the building block I like to think the most about. A RoadContainer simply contains driveable roads; for now, just RoadPoints, but they could also be used as the parent container and controllers for parking lots. Basically, anything within governs its own methods of geometry generation, while the RoadContainer is meant to be the interface between one element and another. A container self calculates its own open edges (which other containers can connect to), and references to other containers when connected (to help traffic flow from one to the other). An intersection just happens to be a RoadContainer with more than two edge roadpoints. Any roadpoints that end internally (as multiple roads joining into a crossroads would) simply need to be marked with the "terminated=true" export var, so they are excluding from the external-facing edge list.

I'm thinking of parking lots and intersections as 'terminators', and perhaps roads themselves could require at least two terminators to connect?

Yup per above - would suggest thinking of it in terms of just working with RoadContainer, where all you care about are the edge interfaces (which will always be RoadPoints, since they define the "profile" of the egde). If anything, we could give additional meta dat properties about RoadContainers to indicate how they are intended to be used, or what mode governs the way the roadpoints within are used to generate the area. Or at the very least, a parking lot can can be a RoadContainer that has only one open edge (assuming one entrance), and then just ends up with other internally generated geometry and appropriate curves to represent.

Hope that helps - curious to see where this goes, and happy to help out if you get stuck anywhere!

@FishOfTheNorthStar
Copy link
Author

Awesome thanks, yeah I think we're on the same page about most of that, it's real similar to what I have here in my early tests.

Regarding difference between highways and roads, with my sketched out layout:

Roads sit on the surface directly. They also grade the ground below them, but they'll always be connected to the ground at all points. Also roads can intersect other roads, and a general angle of 90 degrees +/- 30 degrees seems appropriate for those intersections. However if a road connects to a highway, it's a different kind of intersection, the angle is basically the opposite, it would have to be more like 10->30 degrees or 150-170 degrees (like highway onramps, more gradual). Since the angle is so severe, the road/lane width is no longer valid as an intersection width , instead it would need the length of line segment that passes through a lane if that lane was turned to that severe angle (so significantly longer). So highway on-ramps are like intersections except they'll be much longer (as aligned with the highway it's a part of), and the actual on-ramps may have very long merging sections where they join the main highway mesh.

Highways would be a bit different geometry too, in that I picture them as being able to go over and under each other, and they might have support columns, larger curb sidewalls and/or even more spaced off distant walls that run alongside the highway, So highways can sit on the ground and usually do but they don't have to and may stack up several layers thick to get past each other.

What I'm calling an i_DRAGGABLE so far (name will change) is basically comparable to RoadContainer I think, maybe. Very similar. The difference is primarily that i_DRAGGABLE is an interface, like a class decorator that I put on everything else in the road systems. It establishes a standard format of runtime data, static options, dynamically populated mesh references, and provides a static observer that keeps track of them all and acts as a hub so they can interact. Also by sharing a common interface, everything in the road system can basically be stored in the same array/collection, with no real concern for what specific kind of i_DRAGGABLE they are. For now, all direct interaction with the i_DRAGGABLE static observer happens through the PavedRoadManager, which has it's own collections of highways, roads, lots and whatever else. probably an intersections collection but those may end up being children of roads, not sure yet. This is just for organization and code clarity, by making the Manager the only class thats allowed to mess with the important static collections, i can better insure that when it's time to solve many roads all at once across multiple threads, that there is a central orchestrator / scheduler of the work queue that can keep it happening in the right order and delaying certains tasks until precursor tasks are ready and done.

I've setup parking lots with 2 ports on each side that you can interconnect very simple curved roads between. It's ugly as sin but you can drag and drop all the points around in a reasonably intuitive way, for the roads it moves the roads around and for the parking lots the drag/drop is to move their sizing corners. Moving a parking lot also re-solves all it's connected roads.

image

@TheDuckCow
Copy link
Owner

Highways would be a bit different geometry too, in that I picture them as being able to go over and under each other

Got it, yes that makes sense from a terrain perspective. Roads themselves would still be the same primitives but yes, would be allowed to be handled in any such way. I keep it more general in my mind, as you could always have a normal road briefly turn into an overpass (though in such examples, I think those are also ripe for dedicated RoadContainers to represent the "bridge" portion to have barriers or supports like you say).

Regarding the different intersection configurations, makes sense too. When we thought about it on our team previously, we previously broke it down into something like this. As we pictured it, 4 & 5 and higher level intersections could be grouped further together, 1 & 2 are like your highway onramp as mentioned (where the lane it splits from continues on), and then 3 is a general purpose splitter which in could be used for exit-only lane off ramps (if the split is allowed to be assymmetric) and other cases of roads coming together. Roundabotus would probably be another type unique to the others. The solid color areas are the parts where most of the special geo creation and texturing needs to be done (before thinking about other elements like cross walk decals)

Screen Shot 2024-09-08 at 9 42 14 PM

Then, for higher level thinking, we had the idea that more complex intersections like clover exchanges are ultimately just breakdowns of the above ones (see for instance my post here). I think you're already on the same page here probably, just sharing the visualizations in case it triggers any ideas.

For elements of any given road: I've thought a fair bit about it, and am somewhat torn I'll admit on UX. Features like having support columns, barriers on the side, different profiles, sidewalks etc. All of those seem like they'd be ripe for an array of resource structure which belongs probably on the RoadContainer, to apply to all road segments within the container. That way you can configure on the fly or potentially (depending on how flexible the resource setup is) define your own. So the difference between highways and roads as you would define would be these resources definitions. I could imagine one resource that's even dedicated to telling the terrain system how much or little to deform. Ooo, a tunnel resource could be pretty awesome too, automatically building walls with some profile (perhaps thinking too far out, but fun to think about). So, sounds pretty similar to the classes you are talking about. We don't have anything in place in the road generator yet on this front.

The reason I've been torn on the approach: it also kind of locks you into a specific structure, instead of working within the primitive curves already existing. One key shortcoming I've read about what people find in other road generation systems is that it gets too inflexible or forces you to do things a specific way without giving an avenue to manually intervene if you need to do something manually/more custom (like import your own geometry for some intersection). In the end, something that works but then lets you manually take over geo inputs or import static meshes while retaining the AI lanes/virtual connections is the main thing.

I've setup parking lots with 2 ports on each side that you can interconnect very simple curved roads between.

Already making great progress! Having simple rectangular parking lots like that certainly could be a good starting place. Curious how you are thinking about decal/texturing, though maybe that's an entire follow on topic for later.

@FishOfTheNorthStar
Copy link
Author

Hey thanks for the notes, that intersection break down is very interesting. So at first when I setup a basic extruded path with CsgPolygon3D, it was functional but of course those are kind of limited. I couldn't find much in the way of existing projects that allow you to specify multiple extrusion cross sections per path, so I made one yesterday and it seems to be working pretty well. For now different cross section paths have to have the same number of total points. Also I added a basic 'simplify output' option very similar to CsgPolygon's. I'm going to try putting a short clip onto this post to see how well video works out with these Github forums, I crunched it down with Handbrake so it's pretty small, about 5mb.

If it's too big, or you'd prefer we don't utilize any video, let me know and I'll remove it and stick with still images.

paved_road_gen_clip_2024.09.10.mp4

So each path cross section shape is it's own resource, and you can specify a different shape at any normalized point along the path. It doesn't use those normalized points in the actual path solving though, that remains stepped through at a constant rate regardless of how many cross sections are defined. Each shape segment of those cross sections can each have it's own material and smoothing group, and when it's forming the mesh it groups them under one MeshInstance3D with multiple materials.

My plan for decals is to basically have on asphalt shader that by default just shows basic road texture, but if the vertex color is set to some flag value, it can blend in an overlay layer for things like road surface irregularities or painted stripes. Then the painted stripes would unpack a 32 bit integer out of one of the vertex color channels, and use a few bits of that information to determine a dashing pattern, and that dashing pattern will be applied along the length of the road. So if we wanted, say, one solid yellow line next to a dashed yellow line, that would be, basically, three segments of a road cross section, one with special vertex color saying 'overlay road paint, solid / no dashing', then another next to it with no such vertex color so it's just normal road, then another next to that one that's same as first but with dashing pattern. Then ultimately it's all the same material but vertex variations in the mesh will make it look like several.

That same system could also be used for things like 'fire lanes' where there's broad diagonal stripes, by having some of those color bits signify if the dashing pattern should shear/skew along the width of the road. Then those paint pattern segments would be quite a bit wider than normal paint lines, but otherwise still just a special stripe running down the road (just wider), and the shader will make the diagonal dashing happen.

Let's see if this little video clip worked out...

@FishOfTheNorthStar
Copy link
Author

image

dashing strategy test, seems to work pretty well. nice sharp edges.

@TheDuckCow
Copy link
Owner

Wow some really awesome prototyping there! And video is really nice to have, since it's clear how to see the interactions. It doesn't count towards any quota on my side, so it's more about just uploading what fits.

So each path cross section shape is it's own resource, and you can specify a different shape at any normalized point along the path.

Longwinded tangential comment, bear with me :)

One of my nits with Godot in generate is working with curves and curve points is really cumbersome (so far as I created this addon as a part of a jam at one point as a result). That's why in the current Road Generator addon project the basic puzzle pieces are RoadPoints which are ultimately spatials which can be controlled with the native 3D gizmos (instead of trying to hack build our own), use the transform dialogue, etc. In your demo, i take it your control handles are virtual? Just some thoughts, depending on how grounds-up. Reason I bring it up is right now witht the road generator adodn, by design, the RoadPoint spatials are what define things like the cross sections. Of course for your quick prototyping do whatever you like and makes sense! When it comes to integration together, we can see how the philosophies can meld together.

As for extrusions/lane management: same experience here, we also started with csg geo for simplicity. But we had to sign up to making our own addon since it was a core requirement to make more interesting roads with variable lanes, adding and removing on a whim. It's also one of the first widget interactions we made, old demo here. The way we crudely deal with this is by making each lane be its own set of loop cuts - lanes which are in an adding or removing mode are always on the outside, never on the inside (for simplicity), and for a given RoadSegment (between two RoadPoints), the number of lanes remains constant. The way we actually do the transition is just by pinching the outside lane inwards one or more lane widths.

In case you're curious, though it gets real into the weeds: There's quite a bit of (cumbersome!) logic to make sure the lane counts on one RoadPoint lines up with that on the other. Imagine road points with lane direction setups: RP1=[rev, rev, fwd] connecting to PR2=[rev, rev, fwd, fwd] means the actual loops we have are [rev, rev, fwd, adding] on both sides, and the "adding" lane just means that the geo on that side is expanding out one lane's worth. If we have something like [rev, rev, fwd] -> [rev, fwd, fwd, fwd] which result in a lane sequence of [removing, rev, fwd, adding, addin]. Check out this gnarly unit test here :)

We also used this to make it easy to manage which textures to show on the lanes, as each lane has its own UV space. But as I said, I'm sure there are better ways we could manage that can result in a more flexible material and trimsheet. So very open to what you are coming up with. Since each lane gets shifted it doesn't work well unless the texture makes the UVs seamless left right lane-wise, I was presuming we'd use a detail/second UV layer for more general asphalt textures (or using the lane markings as more of a mask). So they're all pretty intertwined.

32 bit integer out of one of the vertex color channels

Love the idea of making the most of bitmaps. Only concern over using vertex coloring like that, is that we have to tie the geometry (loops and edges and so forth) to the intended features we want to visualize. I don't have a problem with that on a technical level, if you're confident you can make it work for the scenarios intended, I'm all for it, but it's the reason why we shied away from a more complex solution. Also I should probably know this, but would be curious to cross check if there's any compatibility limitations for different platforms to take in vertex coloring for materials (e.g. web or mobile exports). But the upsides do sound quite "up" for flexibility!

Anyhow, all just 2c comments above, really exciting to see what you're working through. I'll say this on a self humbling note, my team and I don't have that much experience with proc gen (hmm yet we decided to tackle this effort heh), and so we've had to simplify some problems to make them feasible. It's very invigorating to see your work so far, and I do truly hope we can find a way to bring it all together! And any existing design decisions are certainly open to be challenged or changed, with right ratioanle.

@TheDuckCow
Copy link
Owner

Thinking about your response a little more, it is definitely pretty cool to have variable profiles/widths/sidewalks without forcing the user to split the curve into mroe "roadpoints" spatials 🤔 I'd just need to ponder what that means from a usability space, since we also need to generate RoadLanes for AI traffic and also need to have the logic of what lane count end profiles there are for connection purposes (especially between separate, preloaded scenes that need to be snapped together based on a common "edge profile"). Anyhow, UX tradeoffs can come later I suppose

@FishOfTheNorthStar
Copy link
Author

Cool thanks for checking it out. Some notes I scribbled here just trying to think some things though, thought I'd see what you think. Apologies for the MS Paint production quality on this, just rough sketch kinda thing:

image

Basically what I'm doing in this one is trying to come up with a strategy to connect the curb of Road 1 (at marked point P1) to the curb of Road 2 at point P2, so that the connection segment travels through Parking Lot corner C1. Along this path, the cross sectional shape should start (in this example) as 'wide sidewalk' at P1, remain that same shape for some portion of the connector segment, and at some point shifts into 'thin curb, no sidewalk' cross section where it connects with Road 2 (p2).

Then the idea would be for every road that connects to a port, travel clockwise from that port connector edge to the next utilized port and it's closer curb, with transitioning between different cross sectional shapes between them.

In some cases, it might be good to have this process also strip C1 out of the curb segment path, so it just goes straight from P1 -> P2. I picture this as particularly good for highway entrance/exit ramps, for more gradual intersections.

So this is my plan so far, haven't implemented it yet but the pieces are in place. Order of point resolution is something like:

Parking Lot provides Port A/B/etc locations (via it's Edges and where along each each the ports are). Road(s) connect to Ports and once connected, can provide Curb profile location (and facing vectors) ala P1, P2, etc. Then the Parking lot provides some points in between P1 and P2 (C1, possibly a few others along the path). And Road1 would, in this case, provide the starting cross section shape, and Road2 would provide the ending one.

Then repeat for both sides of all connected roads. In some cases, like a parking lot with only one road connected to one port, it's expected that P1 and P2 would both be provided by the same road, and P1 would be one side of the road, P2 would be the other, and multiple Corner points would be provided to encircle the entire lot between them.

And then here you can see a multi-layered road pretzel that's testing out how well they stack and could concievably for traffic 'clover leaf' patterns etc. Looks like it'll handle overpasses and elevated highway onramps etc pretty well

image

@FishOfTheNorthStar
Copy link
Author

FishOfTheNorthStar commented Sep 12, 2024

About the cross sectional shape data format so far:

Originally, it was just a list of Vector2's. X and Y, etc. Which is fine, but they needed a little more information, like what material ID that point should be, what it's UV.X should be at that point, and some other stuff. So it gradually became Vector3's, then Vector4's, and now it's a Basis, basically a Vector9 (or Matrix3x3). Internally, all of these shapes are stored as an array of Basis's, but since that's weird to work with and keeping track of which Basis index equates to which shape data component could get clunky and weird, I setup a 'data exchanger struct' which is what you actually get when you request a certain shape point. So if you request Shape Point #3, it gets Basis index #3, provides that as a creation parameter to a new Shape Data Exchanger struct, which stores it as well as a reference to the Shape Point List resource the basis came from, and what index in the array that Basis was stored in, so the Data Exchanger can put it's data back into the source array if required (usually not though, usually it'll just read the data and provide it). Then the Exchanger has numerous Getter's setup (with matching Setters that generally go unused, except within the editor), and those Getters are things like "POS", or "MATID", or various boolean flags which are stored as Uint32 bits in one of the Basis index entries.

So the POS getter, for example, would return a Vector2 that's composed of TheBasis[0][0] and TheBasis[0][1]. Then if you Set POS, it provides value.X back into TheBasis[0][0] and Y into [0][1]. Or for the MATID, it returns TheBasis[1][0] (I think? gotta check my notes), converted to an Int, and it's setter puts a provided int back into same location, converted to a float. Then one Basis index is only for boolean flags, and you get 32 flags per point to use for stuff. So far there's flags for 'BreakShape' (ie, don't output this segment, so a shape can be composed of multiple sub shapes), 'SmoothBefore' and 'SmoothAfter', for smoothing group management, and 'FlatEdge', which is a special flag that shoudl be applied to asphalt/road surface points so when those points are connecting to a parking lot (ie at start and end of path segment), it changes their local Y value from whatever it normally is to just Zero. Then the parking lots sit at local Y zero and the asphalt will smoothly connect between.

More flags and properties will utilize remaining unused Basis members as needed. Despite how weirdly indirect this sounds, it's pretty painless to work with, all of the weird data packing and unpacking is abstracted away from the coder.

edit: Future Point Flags TO-DO:

  • Add a 'segment is double sided' flag, so for just that segment it outputs double sided quad.
  • Add a 'point is the left-most asphalt road edge' and same for 'right-most'. There should only be one of each provided for a cross section, generally provided by the asphalt road portion, and they can act as anchor point references for assembling a fusion of 'left curb' + 'road surface' + 'right curb' sub-shapes, dynamically.

@FishOfTheNorthStar
Copy link
Author

FishOfTheNorthStar commented Sep 12, 2024

How road segments are specified, so far:

Here's a code sample (c#)

            override public void REGENERATE_MESH() {
                GENERATED_ROAD.SHAPES.Clear();

                GENERATED_ROAD.SHAPES.Add(new _PATH_EXTRUSION_SHAPE_INSTANCE().SETUP(AtNorm: 0.0f, Shape: ROAD_SHAPES_V2.TEST1));
                GENERATED_ROAD.SHAPES.Add(new _PATH_EXTRUSION_SHAPE_INSTANCE().SETUP(AtNorm: 0.2f, Shape: ROAD_SHAPES_V2.TEST1));
                GENERATED_ROAD.SHAPES.Add(new _PATH_EXTRUSION_SHAPE_INSTANCE().SETUP(AtNorm: 0.3f, Shape: ROAD_SHAPES_V2.TEST2));
                GENERATED_ROAD.SHAPES.Add(new _PATH_EXTRUSION_SHAPE_INSTANCE().SETUP(AtNorm: 0.7f, Shape: ROAD_SHAPES_V2.TEST2));
                GENERATED_ROAD.SHAPES.Add(new _PATH_EXTRUSION_SHAPE_INSTANCE().SETUP(AtNorm: 0.8f, Shape: ROAD_SHAPES_V2.TEST1));
                GENERATED_ROAD.SHAPES.Add(new _PATH_EXTRUSION_SHAPE_INSTANCE().SETUP(AtNorm: 1.0f, Shape: ROAD_SHAPES_V2.TEST1));

                _SET_ROAD_PATH_CURVE_POINTS();
                GENERATED_ROAD.NotifyPropertyListChanged();  }

So for this simple test, you can see it's starting and ending with TEST1 shape (accessed as a static within helper class ROAD_SHAPES), and in from either end it remains TEST1 for 20% of the road length. Then over 10% of the length it changes to TEST2, which it remains from 30% -> 70%.

I picture us as setting up a few different version of these short scripts for any given type of road, then user can select a drop down to select between them per road segment. Of course the user could also just specify their own arrangement of these, but for the most part I expect we could identify 99% of the situations they'd want to setup and provide that as a preset.

Where it goes next is splitting each road shape instance (ie, the shape is X at norm length position Y) so it actually specifies 3 shapes, the road itself, the left curb, and the right curb. Then each curb will adjust its position to line up with the road width at any given point, which will reflect how many lanes exist at that point.

@FishOfTheNorthStar
Copy link
Author

cropped_roadpretzel_drag.mp4

yay video. The road pretzel looks delicious.

@TheDuckCow
Copy link
Owner

Indeed looks very delicious! Love how responsive you have the controls there. Are the white spheres just virtual widgets, or actual spatial with controls on top?

Will respond more later, I've been getting some real world reference (read: traveling)

PXL_20240913_202140022

@mkay-dev
Copy link

mkay-dev commented Oct 1, 2024

There is an older pull request godotengine/godot#61424 that would solve a lot of the problems mentioned here. I think CSG might become more viable as we are now also able to bake CSG meshes godotengine/godot@6455810.
There are another couple of open issues mentioned in the pull request asking for similar features. The demand is there so I am hopeful that it will get merged eventually.

@TheDuckCow
Copy link
Owner

Thanks for sharing that @mkay-dev, yeah I was also eyeing those branches. Though for the project goals of the Road Generator here, we'll still likely want to end up having control over the generation steps since we need to do rather bespoke lane UV texturing and also ensure AI 3D curves placed on top match the same interpolation of the sectionals themselves. But for sure will be a good thing that Godot users will be able to make at least a little more customized roads before havign to go full self procgen (or using a plugin like ours)

Hey @FishOfTheNorthStar how's it been going? Clearly I'm back from my trip now, wasn't actually that long 😅 Curious what you see as the next steps and how it would make sense to bring in your contributions. I know you're balancing bringing updates to the terrain plugin at the same time, so let me know what you think is feasible to do in the meantime

@FishOfTheNorthStar
Copy link
Author

Hey welcome back. I haven't been able to make much progress the last couple weeks because of life stuff. I've started a new job and it's keeping me pretty busy, and I'll be moving soon so I've been out looking for housing. So I may be ghost for a while, but when I can I'll be posting more updates on what I've been working on.

As far as integration goes, I think a big next step for me will be reviewing and unpacking your driving-AI code, to make sure anything I do can work with it in a comparable/compatible way. I just need to find time to dig into that.

@TheDuckCow
Copy link
Owner

Good luck with the transitions, that's a lot all at once!

And no pressure at all, I just didn't want to be the one holding you up. If it ever helps to do a live chat to walk through it, I'd be happy to - though the AI pathing is relatively straightforward, you can probably learn most of what you need based on this demo scene (in particular, the car script). The main convenience is that the road generator is the one creating the paths and hooking them up so one follows to the next (along with pathing to left/right lanes for easy lane switching).

@FishOfTheNorthStar
Copy link
Author

Great thanks that's very helpful. I have some of my own pathing stuff I use too, for travelling between major locations (the parking lots, in our example here). Maybe I can get the two somewhat interoperable.

(tl;dr everything below: Just thinking out loud to get some notes down I'll use as a general plan going forward, since I can't spend a ton of time at one time on it for now, this will help me)

I think my mid-term project goals are, in order:

Understand and integrate your existing roadgen pathing solutions, so any further development I do doesn't need that retro-fit into place later.

Then create major/minor variant roads, like a highway, that connects to surface level streets, via entrance/exit ramps. With the highway variant being more disconnected from the surrounding terrain, or modifying it significantly, and wider, with slower turns, and sidewalls. Then the ramps are another variant like a single lane road but with highway-class turning radius (much slower than normal single width surface level road). Then two to four lane street level roads with very tight turning radius allowed (from lower vehicle speeds), intersections, traffic signals, and occasional side barriers. And short one lane roads for alleys, driveways, and dirt roads. Then alleys can connect to street level, street level can connect to ramps (via lane side exit or at intersections), and ramps are the only thing that can connect to highways. Highways will elevate themselves as necessary to travel over clusters of street level roads (cities), or individual street level roads will form short overpass segments that disconnect from the ground to pass over highways.

Then start on road damage/destruction support, so it takes the road and shatters it into a lot of little pieces and scatters them, but can take a normalized 0->1 value and gradually shift from no damage -> completely obliterated (but not necessarily in real time). Ultimately this is what I need for my game, and I think it'll be a work in progress for a while, but I'll consider it usable at early functionality and just improve it over time, after we get other stuff more developed.

Once those three core priorities are well developed and stable, I'll post that work-in-progress c# version for some review, and we can start tightly integrating it's workflow, general look, code structure, icons, and class/variable names with the roadgen project proper. So it should look the same and do all the same stuff, in roughly the same way, plus some new stuff. And I'll go through the c# and dress it up a little to read as much as possible like the gd-script version of same. Overall the code is very similar, between c# and gdscript.

Then we'll have to see the best way to get all the c-sharp stuff back into the current gd-script version. If nothing else, we can just do a complete translation of the c-sharp version into gd-script and hey, there it is. But I'd rather find a way that more better inherits all your existing code as much as possible, so it's less of a big change overall. Thoughts on this part? Maybe you'd prefer a total rewrite anyways, sometimes those are nice, kinda dusts off the code a little.

Topic for future consideration: trains and/or conveyor belts. hmm...

@TheDuckCow
Copy link
Owner

Sounds like a solid plan @FishOfTheNorthStar

As for a total rewrite, I won't say I'm totally opposed to it - but like anything, will just need to balance the outcome with the time taken (and impact to users who are continuing to use the project, though of course any major rework would constitute a new major version anyways). But I think all of that can be clearer and be conversations once we get closer to that stage, so nothing to fret too much over right now. Understood it might be quite some time before the integration happens, but I'll be more than happy to keep chatting here and sharing ideas as you get further along and think ahead.

For using the pathing solution, yeah if you just took the RoadLane class as a starting point you'd be in good shape. As I said, it's intentionally very simple - a good chunk is visualization of the lane's direction for use in debugging in-game or -editor, and then providing some methods to let vehicles "register" themselves to a given lane, which will make non-physics/raycast-based traffic feasible. I guess the *_prior_tag variables take a moment to understand but the comment there describes the intent.

Indeed, trains systems could be a good extension as well, presuming some explicit support for railroad crossings and so forth.

For the road obliteration, (just since it's interesting to think about), curious what approaches you are tinkering with. Say if you selecting one road segment to destroy, I suppose you need to interpolate between the destruction levels on both end - if both sides are 0% destroyed but the central one is 100%, you're doing some envelope weight for the destroyed amount. Maybe this is the factor influence is defines now much to transform the parts of (post voronoi cell split) chunks with some random amounts. Though if you're going to try and have 'realistic' fracturing, with rebarb poking out and chunks of concrete behaving differently from flat hunks of asphalt, could end up being that you need to generate several layers of meshes just defined by the profile of the base surface road mech.

@TheDuckCow
Copy link
Owner

Since the topic of this thread is "concepts and general discussion", felt worth pasting this talk down - I have only scrubbed through so far, I want to sit down and watch the whole the thing and then share back some personal takeaways. This is a behind the scenes of this unreal engine experience, made in part with proceduralism of houdini - but seems like there would be a lot of takeaways we could learn from. Topics from my own scanning through I found interesting I'll comment more on later:

  • Hierarchy of road layouts
  • Does talk about road intersections and their strategies
  • Their approach to a traffic system, including stoplights/intersections
  • Reusable road geo (some sections of surface streets) vs "megachunks" (organic highway shapes that are always unique)
  • Decals, and interestingly how they use die-cut-like geo for the decals themselves while using a shared common material
  • Touches on parking lots at a certain point
  • Touches on the material and what attributes are stored there, including use of 2x UV layers and vertex coloring

https://www.youtube.com/watch?v=p570CXrCmDQ

@mkay-dev
Copy link

@TheDuckCow I think the bespoke texturing could be done with a parametric shader much more easily than the method currently being used. It would also allow you to separate textures like those of the lines and the road itself. Most of all though it might make more complex intersection line generation much easier. I did find some tutorials for generating road markings in unreal but sadly none that translate well. You might also want to look at things like "Intersection Marking Tool" for Cities Skylines as it deals with exactly these problems.
Might also be worth mentioning that there are edge-cases that almost all road systems I've seen are unable to solve well and require unique solutions. Namely overpasses cause problems and most system are just not set up for them. You can check the Syklines mod "Vanilla Overpass Project" as an example.
I'm not sure about the AI lanes but I would ask @stebulba if he is perhaps motivated to enable more direct access during mesh generation. He was kind enough to provide me an updated patch for godot 4.3.
There seem to be far too many godot projects trying to enable procedural geometry generation that I think it would be better to pool resources here. There is another project currently going called Hoodie (https://github.com/GreenCrowDev/hoodie) that is again trying to solve the same problem. Too many wheels and too many people trying to build them by themselves.
@FishOfTheNorthStar You would also benefit from official procedural geometry support in godot as making something like a shatter node might be relatively easy in something like hoodie.

@TheDuckCow
Copy link
Owner

Hey @mkay-dev thanks for chiming in, good to multiple people weighing in. My thoughts on your comments:

  • edge-cases that almost all road systems are unable to solve:
    • Big agree here. I'm not assuming we'll be able to solve all cases, which is why this repo is also designed to make it easy to export out a chunk of at least starter geometry to then edit further in other applications. We won't be able to solve all cases - but we can hopefully lower the barrier to entry with at least the common cases for simple cases, or "good enough" geometry to serve as a starting point for users fixing it up in 3D software (more below)
  • parametric shader:
    • I would agree, it would make it more flexible for an out of the box solution.
    • A core tenant of this current road gen project is to not lock users into the limits of what the project can or can't do out of the box, and a good example of that is for users needing to completely customize their materials (I've already had a few very stylized projects which have needed to do this). Now, not that a parametric shader wouldn't allow for that (arguably, could be even easier depending on the setup), but I would need to see how it could work in practice and how easy or not it'd be for someone to substitute in their own pbr/multi map materials etc, and how easily it translates to different road styles in different parts of the world. I wouldn't want to bake region-centric logic for how we can apply marking that don't translate to other places in the world or styles someone might want to accomplish.
    • A very nice pro of the current material system is that exporting geometry out to do touch-up work in a 3d software like Blender works perfectly, since we're using UV tiling that carries through gLTF. I already do that when I run into mesh or material limits, such as creating intersections right now before we have proper support. Now, should new upsides greatly outweigh this convenience, sure we could provide a different option. One day, we'll probably want options for baking for LOD levels anyways, so could also argue that just becomes part of some export routine too, negating this bullet point's con.
    • Trailing thought: I'm also not a shading wizard, so maybe I just need a skilled contributor to help show what's possible :)
  • Enable more direct access during mesh generation:
    • Not sure I follow this, you mean in the context of creating CSG geometry?
    • If built in CSG, or a geometry nodes-solution like Hoodie were in place, then sure - we could build on top of that to also generate additional nodes used for AI paths. Would only be relevant though if such generation methods were capable of generation additional objects, or being able to export out data via meta/private export vars so that a plugin could read the output to then do additional steps afterwards
  • far too many godot projects trying to enable procedural geometry generation:
    • I'm definitely with you in terms of it being better to join forces rather than reinvent the wheel. (contextually, a pun!)
    • That being said, there are always going to be differences from what generalized solution offers compared to fit-for-purpose solutions. Best case scenario, one uses the other as a building block.
    • I'd be happy to contribute any outcomes of road generation techniques and especially techniques for intersections, but it'll always have to be a layer on top since the very concepts of traffic, lane counts and transitions, and network management are more specific than a geo-based tool on its own would be able to offer. Again, using common building blocks would indeed be most ideal.
    • In the case of Hoodie: I saw this project and definitely find it interesting. I hope one day it, or some equivalent, becomes part of Godot core much like in Blender. Or if not core, maybe more of a common dependable library for others to build on top of for tailored experiences (e.g. for road generator users, they likely have no need to be exposed to the actual node graph, but if premade graphs were running in the background to handle geo, then the road generator could handle the UX workflow on top which would call such graphs in as needed contexts).
    • As it is, not sure exactly how we could integrate in the near term since our use cases and dependencies are different enough - it's also very tough to be in the position of a plugin depending on another plugin. But one can hope that as both build momentum and get enough adoption, it would be feasible defer some responsible of one onto the other.

@TheDuckCow TheDuckCow added the question Further information is requested label Oct 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants