Compare commits

..

8 Commits

  1. 21
      LICENSE
  2. 145
      Poisson.gd
  3. 132
      Poisson.tscn
  4. 25
      Previsualisation.gd
  5. 23
      Previsualisation2.gd
  6. 32
      addons/PoissonDiscSampling/Demo/PolygonTester.gd
  7. 12
      addons/PoissonDiscSampling/Demo/PolygonTester.tscn
  8. 137
      addons/PoissonDiscSampling/PoissonDiscSampling.gd
  9. 10
      project.godot

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Udit Parmar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,145 @@
extends Control
export var default_width = 500
export var default_height = 500
export var default_spacing = 5
var data = {
width = default_width,
height = default_height,
spacing = default_spacing
}
var polygon_data = {
width = 500,
height = 500,
spacing = 1.5
}
var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new()
var points = PoolByteArray()
var poissons = []
var truncs = []
signal new_poisson(points)
signal new_truncs(truncs)
func _ready():
reset_options(default_width, default_height, default_spacing)
func reset_options(width, height, spacing):
$HBoxContainer/Properties/width/width_edit.text = str(width)
$HBoxContainer/Properties/height/height_edit.text = str(height)
$HBoxContainer/Properties/spacing/spacing_edit.text = str(spacing)
func set_data(width, height, spacing):
data.width = width
data.height = height
data.spacing = spacing
func init_points():
points.resize(data.width * data.height)
points.fill(0)
func _on_Button_pressed():
set_data(
int($HBoxContainer/Properties/width/width_edit.text),
int($HBoxContainer/Properties/height/height_edit.text),
int($HBoxContainer/Properties/spacing/spacing_edit.text)
)
init_points()
var rect = Rect2(Vector2(0, 0), Vector2(data.width, data.height))
poissons = poisson_disc_sampling.generate_points(data.spacing, rect, 20)
for poisson in poissons:
points.set(int(poisson.x) + int(poisson.y) * data.width, 1)
emit_signal("new_poisson", points)
func _on_save_pressed():
$SaveFileDialog.popup()
func _on_SaveFileDialog_file_selected(path):
var file = File.new()
file.open(path, 2)
file.store_var(data)
file.store_var(points)
func _on_open_pressed():
$OpenFileDialog.popup()
func _on_OpenFileDialog_file_selected(path):
var file = File.new()
file.open(path, 1)
var new_data = file.get_var()
set_data(new_data.width, new_data.height, new_data.spacing)
reset_options(new_data.width, new_data.height, new_data.spacing)
points = file.get_var()
emit_signal("new_poisson", points)
func _on_trunc_pressed():
truncs = []
# Création d'un polygon
var polygon = PoolVector2Array([
Vector2(0, 0),
Vector2(polygon_data.width, 0),
Vector2(polygon_data.width,
polygon_data.height),
Vector2(0, polygon_data.height)
])
# Exemple d'hexagone
polygon = PoolVector2Array([
Vector2(200, 0),
Vector2(400, 133),
Vector2(400, 266),
Vector2(200, 400),
Vector2(0, 266),
Vector2(0, 133),
])
# Spacing
# Si sup à celui de base, le polygon doit se rétrécir proportionnellement
# Les positions des points augmentent à la fin
# Et inversement
var factor = float(polygon_data.spacing) / float(data.spacing)
for i in polygon.size():
var vector = Vector2(polygon[i].x / factor, polygon[i].y / factor)
polygon.set(i, vector)
var max_point = Vector2(0, 0)
for point in polygon:
if point.x > max_point.x:
max_point.x = point.x
if point.y > max_point.y:
max_point.y = point.y
# Déplacement du polygon de telle sorte qu'il reste dans le carré original.
var rng = RandomNumberGenerator.new()
rng.randomize()
var offset = Vector2(rng.randi_range(0, data.width - max_point.x), rng.randi_range(0, data.height - max_point.y))
polygon = Transform2D(0, offset).xform(polygon)
max_point += offset
# On récupère les points à l'intérieur du polygon
for i in range(offset.x, max_point.x):
for j in range(offset.y, max_point.y):
var trunc = Vector2(i, j)
if Geometry.is_point_in_polygon(trunc, polygon):
if points[i + j * data.width]:
trunc -= offset
trunc *= factor
truncs.append(trunc)
emit_signal("new_truncs", truncs)

@ -0,0 +1,132 @@
[gd_scene load_steps=4 format=2]
[ext_resource path="res://Poisson.gd" type="Script" id=1]
[ext_resource path="res://Previsualisation.gd" type="Script" id=2]
[ext_resource path="res://Previsualisation2.gd" type="Script" id=3]
[node name="Poisson" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource( 1 )
[node name="HBoxContainer" type="HBoxContainer" parent="."]
margin_right = 40.0
margin_bottom = 40.0
[node name="Properties" type="VBoxContainer" parent="HBoxContainer"]
margin_right = 121.0
margin_bottom = 176.0
[node name="width" type="HBoxContainer" parent="HBoxContainer/Properties"]
margin_right = 121.0
margin_bottom = 24.0
[node name="width_label" type="Label" parent="HBoxContainer/Properties/width"]
margin_top = 5.0
margin_right = 55.0
margin_bottom = 19.0
text = "Largeur :"
[node name="width_edit" type="LineEdit" parent="HBoxContainer/Properties/width"]
margin_left = 59.0
margin_right = 117.0
margin_bottom = 24.0
[node name="height" type="HBoxContainer" parent="HBoxContainer/Properties"]
margin_top = 28.0
margin_right = 121.0
margin_bottom = 52.0
[node name="height_label" type="Label" parent="HBoxContainer/Properties/height"]
margin_top = 5.0
margin_right = 59.0
margin_bottom = 19.0
text = "Hauteur :"
[node name="height_edit" type="LineEdit" parent="HBoxContainer/Properties/height"]
margin_left = 63.0
margin_right = 121.0
margin_bottom = 24.0
[node name="spacing" type="HBoxContainer" parent="HBoxContainer/Properties"]
margin_top = 56.0
margin_right = 121.0
margin_bottom = 80.0
[node name="spacing_label" type="Label" parent="HBoxContainer/Properties/spacing"]
margin_top = 5.0
margin_right = 58.0
margin_bottom = 19.0
text = "Densité :"
[node name="spacing_edit" type="LineEdit" parent="HBoxContainer/Properties/spacing"]
margin_left = 62.0
margin_right = 120.0
margin_bottom = 24.0
[node name="generate" type="Button" parent="HBoxContainer/Properties"]
margin_top = 84.0
margin_right = 121.0
margin_bottom = 104.0
text = "Générer"
[node name="trunc" type="Button" parent="HBoxContainer/Properties"]
margin_top = 108.0
margin_right = 121.0
margin_bottom = 128.0
text = "Tronquer"
[node name="open" type="Button" parent="HBoxContainer/Properties"]
margin_top = 132.0
margin_right = 121.0
margin_bottom = 152.0
text = "Ouvrir"
[node name="save" type="Button" parent="HBoxContainer/Properties"]
margin_top = 156.0
margin_right = 121.0
margin_bottom = 176.0
text = "Enregistrer"
[node name="ScrollContainer" type="ScrollContainer" parent="."]
margin_left = 125.0
margin_right = 640.0
margin_bottom = 503.0
[node name="Previsualisation" type="TextureRect" parent="ScrollContainer"]
script = ExtResource( 2 )
[node name="ScrollContainer2" type="ScrollContainer" parent="."]
margin_left = 678.0
margin_top = 2.0
margin_right = 1193.0
margin_bottom = 505.0
[node name="Previsualisation" type="TextureRect" parent="ScrollContainer2"]
script = ExtResource( 3 )
[node name="SaveFileDialog" type="FileDialog" parent="."]
margin_left = 83.0
margin_top = 133.0
margin_right = 649.0
margin_bottom = 505.0
window_title = "Enregistrer un fichier"
access = 2
[node name="OpenFileDialog" type="FileDialog" parent="."]
margin_left = 83.0
margin_top = 133.0
margin_right = 649.0
margin_bottom = 505.0
window_title = "Ouvrir un fichier"
mode = 0
access = 2
[connection signal="new_poisson" from="." to="ScrollContainer/Previsualisation" method="_on_Control_new_poisson"]
[connection signal="new_truncs" from="." to="ScrollContainer2/Previsualisation" method="_on_Poisson_new_truncs"]
[connection signal="pressed" from="HBoxContainer/Properties/generate" to="." method="_on_Button_pressed"]
[connection signal="pressed" from="HBoxContainer/Properties/trunc" to="." method="_on_trunc_pressed"]
[connection signal="pressed" from="HBoxContainer/Properties/open" to="." method="_on_open_pressed"]
[connection signal="pressed" from="HBoxContainer/Properties/save" to="." method="_on_save_pressed"]
[connection signal="file_selected" from="SaveFileDialog" to="." method="_on_SaveFileDialog_file_selected"]
[connection signal="file_selected" from="OpenFileDialog" to="." method="_on_OpenFileDialog_file_selected"]

@ -0,0 +1,25 @@
extends TextureRect
var image
func create_image(points):
var width = get_parent().get_parent().data.width
var height = get_parent().get_parent().data.height
image = Image.new()
image.create(width, height, false, Image.FORMAT_RGBA8)
image.fill(Color.white)
image.lock()
for i in width * height:
if(points[i]):
image.set_pixel(i % width, i / height, Color.black)
image.unlock()
func update_texture():
var texture = ImageTexture.new()
texture.create_from_image(image)
set_texture(texture)
func _on_Control_new_poisson(points):
create_image(points)
update_texture()

@ -0,0 +1,23 @@
extends TextureRect
var image
func create_image(points):
var width = get_parent().get_parent().polygon_data.width
var height = get_parent().get_parent().polygon_data.height
image = Image.new()
image.create(width, height, false, Image.FORMAT_RGBA8)
image.fill(Color.white)
image.lock()
for point in points:
image.set_pixel(point.x, point.y, Color.black)
image.unlock()
func update_texture():
var texture = ImageTexture.new()
texture.create_from_image(image)
set_texture(texture)
func _on_Poisson_new_truncs(truncs):
create_image(truncs)
update_texture()

@ -0,0 +1,32 @@
extends Node2D
onready var polygon: Array = $Polygon2D.polygon
onready var n = polygon.size()
var radius: int = 20
var k: int = 0
var points := []
func _draw() -> void:
for i in n:
draw_line(polygon[i], polygon[(i+1)%n], Color(1,1,0), 2, 1)
draw_circle(points[k], radius / 2, Color( 1, 0, 0, 1 ))
draw_circle(points[k], 2, Color( 1, 1, 0, 1 ))
func _ready() -> void:
var pds = PoissonDiscSampling.new()
var start_time = OS.get_ticks_msec()
points = pds.generate_points(radius, $Polygon2D.polygon, 30)
print(points.size(), " points generated in ", OS.get_ticks_msec() - start_time, " miliseconds" )
get_viewport().render_target_clear_mode = Viewport.UPDATE_ONCE
func _process(delta: float) -> void:
if k < points.size() - 1:
update()
k += 1

@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/PoissonDiscSampling/Demo/PolygonTester.gd" type="Script" id=1]
[node name="PolygonTester" type="Node2D"]
script = ExtResource( 1 )
[node name="Polygon2D" type="Polygon2D" parent="."]
position = Vector2( -0.568787, 0 )
color = Color( 0.921569, 1, 0, 0 )
antialiased = true
polygon = PoolVector2Array( 1652.09, 404.164, 1020.9, 114.599, 791.346, 514.627, 474.889, 192.896, 357.097, 495.288, 277.983, 75.1039, 70.5279, 184.106, 35.3661, 625.387, 239.305, 918.988, 156.674, 609.564, 126.787, 249.155, 300.838, 609.564, 581.675, 703.491, 506.535, 345.85, 833.54, 739.663, 1089.23, 313.065, 1499.18, 501.771, 1495.92, 836.886, 1356.02, 862.915, 1050.19, 882.436, 1144.54, 583.11, 1277.93, 599.377, 1398.32, 758.801, 1378.79, 573.349, 1125.02, 443.207, 907.03, 934.493, 1609.8, 983.296 )

@ -0,0 +1,137 @@
class_name PoissonDiscSampling
var _radius: float
var _sample_region_shape
var _retries: int
var _start_pos: Vector2
var _sample_region_rect: Rect2
var _cell_size: float
var _rows: int
var _cols: int
var _cell_size_scaled: Vector2
var _grid: Array = []
var _points: Array = []
var _spawn_points: Array = []
var _transpose: Vector2
# radius - minimum distance between points
# sample_region_shape - takes any of the following:
# -a Rect2 for rectangular region
# -an array of Vector2 for polygon region
# -a Vector3 with x,y as the position and z as the radius of the circle
# retries - maximum number of attempts to look around a sample point, reduce this value to speed up generation
# start_pos - optional parameter specifying the starting point
#
# returns an Array of Vector2D with points in the order of their discovery
func generate_points(radius: float, sample_region_shape, retries: int, start_pos := Vector2(INF, INF)) -> Array:
_radius = radius
_sample_region_shape = sample_region_shape
_retries = retries
_start_pos = start_pos
_init_vars()
while _spawn_points.size() > 0:
var spawn_index: int = randi() % _spawn_points.size()
var spawn_centre: Vector2 = _spawn_points[spawn_index]
var sample_accepted: bool = false
for i in retries:
var angle: float = 2 * PI * randf()
var sample: Vector2 = spawn_centre + Vector2(cos(angle), sin(angle)) * (radius + radius * randf())
if _is_valid_sample(sample):
_grid[int((_transpose.x + sample.x) / _cell_size_scaled.x)][int((_transpose.y + sample.y) / _cell_size_scaled.y)] = _points.size()
_points.append(sample)
_spawn_points.append(sample)
sample_accepted = true
break
if not sample_accepted:
_spawn_points.remove(spawn_index)
return _points
func _is_valid_sample(sample: Vector2) -> bool:
if _is_point_in_sample_region(sample):
var cell := Vector2(int((_transpose.x + sample.x) / _cell_size_scaled.x), int((_transpose.y + sample.y) / _cell_size_scaled.y))
var cell_start := Vector2(max(0, cell.x - 2), max(0, cell.y - 2))
var cell_end := Vector2(min(cell.x + 2, _cols - 1), min(cell.y + 2, _rows - 1))
for i in range(cell_start.x, cell_end.x + 1):
for j in range(cell_start.y, cell_end.y + 1):
var search_index: int = _grid[i][j]
if search_index != -1:
var dist: float = _points[search_index].distance_to(sample)
if dist < _radius:
return false
return true
return false
func _is_point_in_sample_region(sample: Vector2) -> bool:
if _sample_region_rect.has_point(sample):
match typeof(_sample_region_shape):
TYPE_RECT2:
return true
TYPE_VECTOR2_ARRAY, TYPE_ARRAY:
if Geometry.is_point_in_polygon(sample, _sample_region_shape):
return true
TYPE_VECTOR3:
if Geometry.is_point_in_circle(sample, Vector2(_sample_region_shape.x, _sample_region_shape.y), _sample_region_shape.z):
return true
_:
return false
return false
func _init_vars() -> void:
randomize()
# identify the type of shape and it's bounding rectangle and starting point
match typeof(_sample_region_shape):
TYPE_RECT2:
_sample_region_rect = _sample_region_shape
if _start_pos.x == INF:
_start_pos.x = _sample_region_rect.position.x + _sample_region_rect.size.x * randf()
_start_pos.y = _sample_region_rect.position.y + _sample_region_rect.size.y * randf()
TYPE_VECTOR2_ARRAY, TYPE_ARRAY:
var start: Vector2 = _sample_region_shape[0]
var end: Vector2 = _sample_region_shape[0]
for i in range(1, _sample_region_shape.size()):
start.x = min(start.x, _sample_region_shape[i].x)
start.y = min(start.y, _sample_region_shape[i].y)
end.x = max(end.x, _sample_region_shape[i].x)
end.y = max(end.y, _sample_region_shape[i].y)
_sample_region_rect = Rect2(start, end - start)
if _start_pos.x == INF:
var n: int = _sample_region_shape.size()
var i: int = randi() % n
_start_pos = _sample_region_shape[i] + (_sample_region_shape[(i + 1) % n] - _sample_region_shape[i]) * randf()
TYPE_VECTOR3:
var x = _sample_region_shape.x
var y = _sample_region_shape.y
var r = _sample_region_shape.z
_sample_region_rect = Rect2(x - r, y - r, r * 2, r * 2)
if _start_pos.x == INF:
var angle: float = 2 * PI * randf()
_start_pos = Vector2(x, y) + Vector2(cos(angle), sin(angle)) * r * randf()
_:
_sample_region_shape = Rect2(0, 0, 0, 0)
push_error("Unrecognized shape!!! Please input a valid shape")
_cell_size = _radius / sqrt(2)
_cols = max(floor(_sample_region_rect.size.x / _cell_size), 1)
_rows = max(floor(_sample_region_rect.size.y / _cell_size), 1)
# scale the cell size in each axis
_cell_size_scaled.x = _sample_region_rect.size.x / _cols
_cell_size_scaled.y = _sample_region_rect.size.y / _rows
# use tranpose to map points starting from origin to calculate grid position
_transpose = -_sample_region_rect.position
_grid = []
for i in _cols:
_grid.append([])
for j in _rows:
_grid[i].append(-1)
_points = []
_spawn_points = []
_spawn_points.append(_start_pos)

@ -8,6 +8,16 @@
config_version=4 config_version=4
_global_script_classes=[ {
"base": "Reference",
"class": "PoissonDiscSampling",
"language": "GDScript",
"path": "res://addons/PoissonDiscSampling/PoissonDiscSampling.gd"
} ]
_global_script_class_icons={
"PoissonDiscSampling": ""
}
[application] [application]
config/name="Societer Utils" config/name="Societer Utils"

Loading…
Cancel
Save