added CSG toolkit
This commit is contained in:
31
addons/csg_toolkit/scripts/patterns/circular_pattern.gd
Normal file
31
addons/csg_toolkit/scripts/patterns/circular_pattern.gd
Normal file
@@ -0,0 +1,31 @@
|
||||
@tool
|
||||
class_name CSGCircularPattern
|
||||
extends CSGPattern
|
||||
|
||||
@export var radius: float = 5.0
|
||||
@export var points: int = 8
|
||||
@export var layers: int = 1
|
||||
## If 0 use template_size.y
|
||||
@export var layer_height: float = 0.0
|
||||
## Additional gap added per layer beyond base height
|
||||
@export var layer_spacing: float = 0.0
|
||||
|
||||
func _generate(ctx: Dictionary) -> Array:
|
||||
var positions: Array = []
|
||||
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
|
||||
var rad: float = max(0.0, radius)
|
||||
var count: int = max(1, points)
|
||||
if count <= 1:
|
||||
return [Vector3.ZERO]
|
||||
var lyr_count = max(1, layers)
|
||||
var base_y = layer_height if layer_height > 0.0 else template_size.y
|
||||
var step_y = base_y + max(0.0, layer_spacing)
|
||||
for i in range(count):
|
||||
var angle = (i * TAU) / count
|
||||
var base_pos = Vector3(cos(angle) * rad, 0, sin(angle) * rad)
|
||||
for layer in range(lyr_count):
|
||||
positions.append(base_pos + Vector3(0, layer * step_y, 0))
|
||||
return positions
|
||||
|
||||
func get_estimated_count(ctx: Dictionary) -> int:
|
||||
return max(1, points) * max(1, layers)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b3ws8vwtsqmjt
|
||||
19
addons/csg_toolkit/scripts/patterns/csg_pattern.gd
Normal file
19
addons/csg_toolkit/scripts/patterns/csg_pattern.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
@tool
|
||||
@abstract
|
||||
class_name CSGPattern
|
||||
extends Resource
|
||||
|
||||
## Base pattern interface. Subclasses implement _generate(RepeaterContext) returning Array[Vector3].
|
||||
|
||||
# Common interface call
|
||||
func generate(ctx: Dictionary) -> Array:
|
||||
# ctx expected keys: repeat: Vector3i, spacing: Vector3, rng: RandomNumberGenerator, step_spacing: Vector3, user: Node (repeater)
|
||||
return _generate(ctx)
|
||||
|
||||
func _generate(_ctx: Dictionary) -> Array:
|
||||
return []
|
||||
|
||||
func get_estimated_count(ctx: Dictionary) -> int:
|
||||
# Default: fallback to generating (may be overridden for performance/accuracy)
|
||||
var arr = _generate(ctx)
|
||||
return arr.size()
|
||||
1
addons/csg_toolkit/scripts/patterns/csg_pattern.gd.uid
Normal file
1
addons/csg_toolkit/scripts/patterns/csg_pattern.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dbavf0pl65chb
|
||||
33
addons/csg_toolkit/scripts/patterns/grid_pattern.gd
Normal file
33
addons/csg_toolkit/scripts/patterns/grid_pattern.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
@tool
|
||||
class_name CSGGridPattern
|
||||
extends CSGPattern
|
||||
|
||||
@export var count_x: int = 2
|
||||
@export var count_y: int = 1
|
||||
@export var count_z: int = 1
|
||||
@export var spacing: Vector3 = Vector3.ZERO
|
||||
|
||||
## If true, automatically adds template AABB size to spacing for proper object separation
|
||||
@export var use_template_size: bool = true
|
||||
|
||||
func _generate(ctx: Dictionary) -> Array:
|
||||
var positions: Array = []
|
||||
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
|
||||
var jitter: float = ctx.get("position_jitter", 0.0)
|
||||
var rng: RandomNumberGenerator = ctx.rng
|
||||
var cx = max(1, count_x)
|
||||
var cy = max(1, count_y)
|
||||
var cz = max(1, count_z)
|
||||
var base_step: Vector3 = (template_size if use_template_size else Vector3.ZERO) + spacing
|
||||
for x in range(cx):
|
||||
for y in range(cy):
|
||||
for z in range(cz):
|
||||
var position = Vector3(x * base_step.x, y * base_step.y, z * base_step.z)
|
||||
if jitter > 0.0:
|
||||
position += Vector3(
|
||||
rng.randf_range(-jitter, jitter),
|
||||
rng.randf_range(-jitter, jitter),
|
||||
rng.randf_range(-jitter, jitter)
|
||||
)
|
||||
positions.append(position)
|
||||
return positions
|
||||
1
addons/csg_toolkit/scripts/patterns/grid_pattern.gd.uid
Normal file
1
addons/csg_toolkit/scripts/patterns/grid_pattern.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bc1sxg4vy464o
|
||||
124
addons/csg_toolkit/scripts/patterns/noise_pattern.gd
Normal file
124
addons/csg_toolkit/scripts/patterns/noise_pattern.gd
Normal file
@@ -0,0 +1,124 @@
|
||||
@tool
|
||||
class_name CSGNoisePattern
|
||||
extends CSGPattern
|
||||
|
||||
## Generates instance positions based on noise sampling in a 3D volume
|
||||
## Instances are placed where noise value exceeds the threshold
|
||||
|
||||
##
|
||||
@export var bounds: Vector3 = Vector3(10, 10, 10)
|
||||
|
||||
##
|
||||
@export var sample_density: Vector3i = Vector3i(20, 1, 20)
|
||||
|
||||
##
|
||||
@export_range(0.0, 1.0) var noise_threshold: float = 0.5
|
||||
|
||||
##
|
||||
@export var noise_seed: int = 0
|
||||
|
||||
##
|
||||
@export_range(0.01, 100) var noise_frequency: float = 0.1
|
||||
|
||||
##
|
||||
@export_enum("Simplex", "Simplex Smooth", "Cellular", "Perlin", "Value Cubic", "Value") var noise_type: int = 0
|
||||
|
||||
##
|
||||
@export_enum("None", "OpenSimplex2", "OpenSimplex2S", "Cellular", "Perlin", "Value Cubic", "Value") var fractal_type: int = 0
|
||||
|
||||
##
|
||||
@export_range(1, 8) var fractal_octaves: int = 3
|
||||
|
||||
##
|
||||
@export var use_template_size: bool = false
|
||||
|
||||
var noise: FastNoiseLite
|
||||
|
||||
func _init():
|
||||
noise = FastNoiseLite.new()
|
||||
_update_noise()
|
||||
|
||||
func _update_noise():
|
||||
if not noise:
|
||||
noise = FastNoiseLite.new()
|
||||
|
||||
noise.seed = noise_seed
|
||||
noise.frequency = noise_frequency
|
||||
noise.fractal_octaves = fractal_octaves
|
||||
|
||||
# Map noise_type enum to FastNoiseLite types
|
||||
match noise_type:
|
||||
0: noise.noise_type = FastNoiseLite.TYPE_SIMPLEX
|
||||
1: noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
2: noise.noise_type = FastNoiseLite.TYPE_CELLULAR
|
||||
3: noise.noise_type = FastNoiseLite.TYPE_PERLIN
|
||||
4: noise.noise_type = FastNoiseLite.TYPE_VALUE_CUBIC
|
||||
5: noise.noise_type = FastNoiseLite.TYPE_VALUE
|
||||
|
||||
# Map fractal_type enum to FastNoiseLite fractal types
|
||||
match fractal_type:
|
||||
0: noise.fractal_type = FastNoiseLite.FRACTAL_NONE
|
||||
1: noise.fractal_type = FastNoiseLite.FRACTAL_FBM
|
||||
2: noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
|
||||
3: noise.fractal_type = FastNoiseLite.FRACTAL_PING_PONG
|
||||
|
||||
func _generate(ctx: Dictionary) -> Array:
|
||||
_update_noise()
|
||||
|
||||
var positions: Array = []
|
||||
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE) if use_template_size else Vector3.ZERO
|
||||
var jitter: float = ctx.get("position_jitter", 0.0)
|
||||
var rng: RandomNumberGenerator = ctx.get("rng", RandomNumberGenerator.new())
|
||||
|
||||
var effective_bounds = bounds
|
||||
var sample_count = sample_density
|
||||
|
||||
# Calculate step size for sampling
|
||||
var step = Vector3(
|
||||
effective_bounds.x / max(1, sample_count.x),
|
||||
effective_bounds.y / max(1, sample_count.y),
|
||||
effective_bounds.z / max(1, sample_count.z)
|
||||
)
|
||||
|
||||
# Start from negative half to center the pattern around origin
|
||||
var start_pos = -effective_bounds * 0.5
|
||||
|
||||
# Sample noise at regular intervals
|
||||
for x in range(sample_count.x):
|
||||
for y in range(sample_count.y):
|
||||
for z in range(sample_count.z):
|
||||
var sample_pos = start_pos + Vector3(
|
||||
x * step.x + step.x * 0.5,
|
||||
y * step.y + step.y * 0.5,
|
||||
z * step.z + step.z * 0.5
|
||||
)
|
||||
|
||||
# Get noise value at this position (normalized to 0-1)
|
||||
var noise_value = (noise.get_noise_3d(sample_pos.x, sample_pos.y, sample_pos.z) + 1.0) * 0.5
|
||||
|
||||
# Only place instance if noise exceeds threshold
|
||||
if noise_value >= noise_threshold:
|
||||
var final_pos = sample_pos
|
||||
|
||||
# Apply template size offset if enabled
|
||||
if use_template_size:
|
||||
final_pos += template_size * Vector3(x, y, z)
|
||||
|
||||
# Apply jitter
|
||||
if jitter > 0.0:
|
||||
final_pos += Vector3(
|
||||
rng.randf_range(-jitter, jitter),
|
||||
rng.randf_range(-jitter, jitter),
|
||||
rng.randf_range(-jitter, jitter)
|
||||
)
|
||||
|
||||
positions.append(final_pos)
|
||||
|
||||
return positions
|
||||
|
||||
func get_estimated_count(ctx: Dictionary) -> int:
|
||||
# Rough estimate: total samples * (1 - threshold)
|
||||
# Higher threshold = fewer instances
|
||||
var total_samples = max(1, sample_density.x) * max(1, sample_density.y) * max(1, sample_density.z)
|
||||
var estimated = int(total_samples * (1.0 - noise_threshold))
|
||||
return max(1, estimated)
|
||||
1
addons/csg_toolkit/scripts/patterns/noise_pattern.gd.uid
Normal file
1
addons/csg_toolkit/scripts/patterns/noise_pattern.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://3il6xs7cr7gj
|
||||
39
addons/csg_toolkit/scripts/patterns/spiral_pattern.gd
Normal file
39
addons/csg_toolkit/scripts/patterns/spiral_pattern.gd
Normal file
@@ -0,0 +1,39 @@
|
||||
@tool
|
||||
class_name CSGSpiralPattern
|
||||
extends CSGPattern
|
||||
|
||||
@export var turns: float = 2.0
|
||||
@export var start_radius: float = 0.5
|
||||
@export var end_radius: float = 5.0
|
||||
## If > 0 overrides vertical spread based on repeat & step
|
||||
@export var total_height: float = 0.0
|
||||
@export var use_radius_curve: bool = false
|
||||
@export var radius_curve: Curve
|
||||
@export var points: int = 32
|
||||
|
||||
func _generate(ctx: Dictionary) -> Array:
|
||||
var positions: Array = []
|
||||
var template_size: Vector3 = ctx.get("template_size", Vector3.ONE)
|
||||
var t_turns: float = max(0.1, turns)
|
||||
var r_start: float = max(0.0, start_radius)
|
||||
var r_end: float = max(r_start, end_radius)
|
||||
var total: int = max(2, points)
|
||||
if total <= 1:
|
||||
return [Vector3.ZERO]
|
||||
for i in range(total):
|
||||
var t: float = float(i) / float(total - 1)
|
||||
var angle = t * t_turns * TAU
|
||||
var curve_t = t
|
||||
if use_radius_curve and radius_curve and radius_curve.get_point_count() > 0:
|
||||
curve_t = clamp(radius_curve.sample(t), 0.0, 1.0)
|
||||
var radius = lerp(r_start, r_end, curve_t)
|
||||
var y_pos: float = t * (total_height if total_height > 0.0 else template_size.y * 1.0)
|
||||
positions.append(Vector3(
|
||||
cos(angle) * radius,
|
||||
y_pos,
|
||||
sin(angle) * radius
|
||||
))
|
||||
return positions
|
||||
|
||||
func get_estimated_count(ctx: Dictionary) -> int:
|
||||
return max(2, points)
|
||||
@@ -0,0 +1 @@
|
||||
uid://belgcjd0ys212
|
||||
Reference in New Issue
Block a user