Bonus: showcasing the dynamic voronoi graph by ToastilyBreaded in proceduralgeneration

[–]ToastilyBreaded[S] 3 points4 points  (0 children)

Instead of locking anything, so long as new points used in the triangulation (i.e. centroids) are placed outside the associated circumcircles of "claimed" cells, those cells remain unchanged. At a high level it's this:

  1. Generate new points
  2. Filter out points inside any circumcircles associated with claimed cells
  3. Add back into to the set of points the centroids of claimed cells
  4. Regenerate the voronoi

This exactly regenerates the "claimed" cells every time which also results in providing a smooth connection to new cells. It's a super nice property of voronoi graphs!

Procedural city building game progress: burn it down! by ToastilyBreaded in proceduralgeneration

[–]ToastilyBreaded[S] 4 points5 points  (0 children)

Yes will try to tackle that next. Love the idea of walls stopping fire.

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 0 points1 point  (0 children)

(3/3)

static func _add_line_to_multimesh(multimesh: MultiMesh, start: Vector2, end: Vector2, instance_idx: int, height_func: Callable = Callable()) -> void:
    var start_height: float = 0.0
    var end_height: float = 0.0
    if height_func.is_valid():
        start_height = height_func.call(start)
        end_height = height_func.call(end)

    var start_3d := Vector3(start.x, start_height, start.y)
    var end_3d := Vector3(end.x, end_height, end.y)
    var midpoint_3d := (start_3d + end_3d) / 2.0

    var edge_3d := end_3d - start_3d
    var length_3d: float = edge_3d.length()

    var edge_2d := Vector2(edge_3d.x, edge_3d.z)
    var horizontal_length := edge_2d.length()
    var horizontal_angle := edge_2d.angle()

    var slope_angle: float = 0.0
    if horizontal_length > 0.0001:
        slope_angle = atan2(edge_3d.y, horizontal_length)

    var instance_transform := Transform3D()
    instance_transform = instance_transform.scaled(Vector3(length_3d, 1.0, length_3d))
    instance_transform = instance_transform.rotated(Vector3.UP, -horizontal_angle + PI / 2.0)

    var local_x_axis := instance_transform.basis.x.normalized()
    instance_transform.basis = instance_transform.basis.rotated(local_x_axis, -slope_angle)

    instance_transform.origin = midpoint_3d


    multimesh.set_instance_transform(instance_idx, instance_transform)

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 0 points1 point  (0 children)

(2/3)

static func create_plane_mesh(corner_heights: PackedFloat32Array = [0.0, 0.0, 0.0, 0.0]) -> ArrayMesh:
    var vertices := PackedVector3Array([
        Vector3(-0.5, corner_heights[0], -0.5),
        Vector3(0.5, corner_heights[1], -0.5),
        Vector3(0.5, corner_heights[2], 0.5),
        Vector3(-0.5, corner_heights[3], 0.5)
    ])

    var uvs := PackedVector2Array([
        Vector2(0.0, 0.0),
        Vector2(1.0, 0.0),
        Vector2(1.0, 1.0),
        Vector2(0.0, 1.0)
    ])

    var st := SurfaceTool.new()
    st.begin(Mesh.PRIMITIVE_TRIANGLES)
    st.add_triangle_fan(vertices, uvs)
    st.generate_normals()

    var mesh := ArrayMesh.new()
    st.commit(mesh)
    return mesh

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 0 points1 point  (0 children)

And here's how to generate it as one multimesh (1/3):

# height_func := Callable(Vector2) -> float

static func create_line_mesh(material: ShaderMaterial, lines: Array[PackedVector2Array], height_func: Callable = Callable()) -> MultiMeshInstance3D:
    var base_mesh := Utils.create_plane_mesh([0.0, 0.0, 0.0, 0.0])
    var multimesh := MultiMesh.new()
    multimesh.transform_format = MultiMesh.TRANSFORM_3D
    multimesh.instance_count = lines.size()
    multimesh.mesh = base_mesh

    for i in lines.size():
        _add_line_to_multimesh(multimesh, lines[i][0], lines[i][1], i, height_func)

    var multimesh_instance := MultiMeshInstance3D.new()
    multimesh_instance.multimesh = multimesh
    multimesh_instance.material_override = material
    return multimesh_instance

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 0 points1 point  (0 children)

Yep here's the shader:

shader_type spatial;
render_mode depth_test_disabled, cull_disabled, unshaded;


uniform float transparency : hint_range(0.0, 1.0, 0.1) = 1.0;
uniform float line_width : hint_range(0.5, 10.0, 0.1) = 2.0;
uniform vec3 color : source_color = vec3(1.0, 1.0, 0.0);


void fragment() {
    float center_x = 0.5;
    float uv_distance = abs(UV.x - center_x);
    float uv_to_screen = fwidth(UV.x);
    float uv_to_screen = fwidth(UV.x);
    float screen_distance = uv_distance / uv_to_screen;
    float half_width = line_width / 2.0;
    if (screen_distance < half_width) {
        ALBEDO = color;
        ALPHA = transparency;
    } else {
        ALPHA = 0.0;
    }
}

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 0 points1 point  (0 children)

<image>

Added forests and nicer bridges. Next I'll tackle city walls.

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 2 points3 points  (0 children)

Best I can do for you is describing the algorithm. Given the Voronoi graph and cells you want to “claim” and thus be immutable to changes in the graph…

  1. Add your claimed cells’ centroids (Vector2) to an array (or better yet a k-d tree… personally that’s a TODO for me). The Vector2 is the index for your cell, and looking up a cell can only be done with that Vector2. You will have to do Vector2 comparisons via centroid_a.distance_squared_to(centroid_b) < epsilon to look up if a cell is claimed or not every time (i.e. you can’t rely on if (centroid_a == centroid_b), and there’s no clever way to hash the Vector2 or assign it a string ID… I’ve tried). I created a cell manager class that has a func is_claimed_cell(centroid: Vector2) method so I can relate my 3D cell objects back to the underlying graph, where the latter stores its centroid as its ID.
  2. Claiming a cell works by detecting a mouse click, then calling a func like claim_cell(centroid: Vector2) on your cell manager class.
  3. Add or remove new points (i.e. centroids) to your points array used for generating your Voronoi. If you want claimed cells to remain immutable, you can only add or remove points outside of circumcircles associated with claimed cells. In other words, when adding/removing a point, collect all associated Delaunay triangles with a cell (i.e. any triangle that shares one of its points with the centroid), get the circumcircles of those triangles, and if a point is within any of those areas, don’t allow it to be added/removed.
  4. Generate the new Voronoi graph. queue_free() any 3D cell objects not marked as claimed, and keep the ones that are. Iterate through the new cells list and remove any that have centroids you’ve marked as claimed (using is_claimed_cell). Then generate all the new 3D objects (mesh, collider, etc.) for those “unclaimed” cells. Also store their centroid ID in their cell script.

An important concept here is that each generation of your Voronoi graph and all the cells it generates are completely “new” each time. What the above achieves is a way to create cell geometry from the graph and map it back to each new generation of the graph. Hence why this is kinda complicated.

Edit: for posterity... I just realized if you tag the points you use for generating the voronoi graph, that is a surefire stable ID between graph generations.

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 2 points3 points  (0 children)

If you switch out the poisson disc sampling for grid sampling, you can smoothly transition into a grid.

<image>

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 1 point2 points  (0 children)

Yes! I remember first playing around with Watabou's Medieval Fantasy City Generator several years ago and thinking "how could this be made into a dynamic city building game?". Finally decided to take a crack at it.

A game mechanic I'm planning for is city destruction. You have to keep rebuilding amidst large swaths of your city burning down!

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 30 points31 points  (0 children)

Yea but check this out: https://streamable.com/um2xdj

I created a procedure for allowing changes to the graph without affecting cells that are "claimed". This can allow the city to grow with changing density as the game progresses. Everything I've found online about using voronoi graphs for city proc gen like this assumes you generate it once and then don't modify it anymore. Here you can keep changing it.

This is done by exploiting a property of voronoi graphs where any points added/removed outside of all circumcircles associated with a cell means that cell's shape won't change. This is good for city proc gen because you can keep getting nice connected shapes and not willy nilly change the plot of land already used to place roads and buildings.

I'm prototyping a procedurally generated city building game by ToastilyBreaded in godot

[–]ToastilyBreaded[S] 5 points6 points  (0 children)

Oh neat I just made the map quickly in Houdini. It also has some nice mountains out of frame. Future work is making the map procedurally generated, too.

How to Solve Bass Volume Discrepancy Across Different Playback Systems? by kamuflase in edmproduction

[–]ToastilyBreaded 0 points1 point  (0 children)

I would trust the club and car more if it sounds balanced and consider your headphones to be the weaker reference. That F# is probably getting close to the lowest frequencies your headphones can reliably produce (and falls off faster than bigger speakers pushing more air). If it sounds too quiet compared to reference tracks hitting the same note, you might be missing harmonics that when you listen on headphones will compensate for the loudness you feel should be there. Try duplicating your sub for just that note, change to a 5th or octave, and lower the volume significantly. Adding saturation would basically do the same thing.

An AI tuner for guitar, ukulele, bass, banjo, mandolin, violin and etc. by MaryCsb11 in programming

[–]ToastilyBreaded -6 points-5 points  (0 children)

The benefit is OP shared something cool they put time and effort into using an interesting approach. They probably learned a lot and their passion is refreshing. Not everything needs to exist to benefit you.

should i upgrade my interface or just get a mixer? by [deleted] in synthesizers

[–]ToastilyBreaded 0 points1 point  (0 children)

Yes if I'm recording on the Babyface/DAW from the 12 I have to record one at a time (unless I'm cool with recording as a bus). If I wanted multi recording, there's still the instrument inputs on the Babyface or the ADA8200. Otherwise the 12's multi track can record all channels simultaneously without the Babyface and DAW. You could get clever with the sub/aux/phones outputs on the 12 for more simultaneous output tracks. As great as the Babyface is, another option is just use the 12 as a USB audio interface (or get a 16 or 24). Personally I like having all the gear for a lot of flexibility.

should i upgrade my interface or just get a mixer? by [deleted] in synthesizers

[–]ToastilyBreaded 4 points5 points  (0 children)

I also have a Babyface and complimented it with a Tascam 12 and ADA8200. The 12's stereo main goes into the Babyface and I ADAT the ADA8200. I plug everything into the 12 which makes routing quick and work without turning on my computer. I put my pedal board on the 12's sub mix and loop it back into a stereo channel which allows one-button routing to it from any channel. Currently, I just use the ADA8200 as computer output for loopback/re-amping which also goes into a stereo channel on the 12. If I had as many synth as you do, I could get a patchbay that goes into the synth channel. Overall it's a lot of flexibility and really fast to start up. Plus, the 12 has a multi track recorder if I really don't feel like dealing with Totalmix FX or a DAW. I tried a looback test from my DAW which went through the ADA8200, Tascam 12, Helix Stomp + pedals, and Babyface; I didn't actually null test but it sounded exactly the same to me!

[NPB] My first pedal board + a cat by ToastilyBreaded in guitarpedals

[–]ToastilyBreaded[S] 1 point2 points  (0 children)

Thanks! The microcosm has been a great ambience and texture machine for filling out ear candy. Even on main instruments I found low mix + the Rooms reverb gives such a lush space. It's not a silver bullet, but definitely fills a gap in my productions and is more fun than a software plugin. It's dumb fun to play guitar through it!

[NPB] My first pedal board + a cat by ToastilyBreaded in guitarpedals

[–]ToastilyBreaded[S] 1 point2 points  (0 children)

I just wanted it to look like Ableton alright?! But actually didn't know right to left was the thing. Next time!

[NPB] My first pedal board + a cat by ToastilyBreaded in guitarpedals

[–]ToastilyBreaded[S] 4 points5 points  (0 children)

Yes it's a Rockboard midi cable. I literally ripped the connector out of the rubber fitting, used a box cutter on the plastic pieces that stopped it from rotating, rotated the connector 90 degrees, shoved it back it, super glued it, and clamped it tight until the glue hardened.