parent
0c4c474e5d
commit
2523700b09
@ -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,28 @@ |
||||
extends Control |
||||
|
||||
export var default_width = 500 |
||||
export var default_height = 500 |
||||
export var default_spacing = 5 |
||||
var width = default_width |
||||
var height = default_height |
||||
var spacing = default_spacing |
||||
|
||||
var poisson_disc_sampling: PoissonDiscSampling = PoissonDiscSampling.new() |
||||
var points = [] |
||||
|
||||
signal new_poisson(points) |
||||
|
||||
func _ready(): |
||||
$HBoxContainer/Properties/width/width_edit.text = str(default_width) |
||||
$HBoxContainer/Properties/height/height_edit.text = str(default_height) |
||||
$HBoxContainer/Properties/spacing/spacing_edit.text = str(default_spacing) |
||||
pass |
||||
|
||||
func _on_Button_pressed(): |
||||
width = int($HBoxContainer/Properties/width/width_edit.text) |
||||
height = int($HBoxContainer/Properties/height/height_edit.text) |
||||
spacing = int($HBoxContainer/Properties/spacing/spacing_edit.text) |
||||
var rect = Rect2(Vector2(0, 0), Vector2(width, height)) |
||||
points = poisson_disc_sampling.generate_points(spacing, rect, 20) |
||||
emit_signal("new_poisson", points) |
||||
|
||||
@ -1,5 +1,93 @@ |
||||
[gd_scene format=2] |
||||
[gd_scene load_steps=3 format=2] |
||||
|
||||
[node name="Control" type="Control"] |
||||
[ext_resource path="res://Poisson.gd" type="Script" id=1] |
||||
[ext_resource path="res://Previsualisation.gd" type="Script" id=2] |
||||
|
||||
[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 = 152.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="open" type="Button" parent="HBoxContainer/Properties"] |
||||
margin_top = 108.0 |
||||
margin_right = 121.0 |
||||
margin_bottom = 128.0 |
||||
text = "Ouvrir" |
||||
|
||||
[node name="save" type="Button" parent="HBoxContainer/Properties"] |
||||
margin_top = 132.0 |
||||
margin_right = 121.0 |
||||
margin_bottom = 152.0 |
||||
text = "Enregistrer" |
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="."] |
||||
margin_left = 125.0 |
||||
margin_right = 653.0 |
||||
margin_bottom = 502.0 |
||||
|
||||
[node name="Previsualisation" type="TextureRect" parent="ScrollContainer"] |
||||
script = ExtResource( 2 ) |
||||
|
||||
[connection signal="new_poisson" from="." to="ScrollContainer/Previsualisation" method="_on_Control_new_poisson"] |
||||
[connection signal="pressed" from="HBoxContainer/Properties/generate" to="." method="_on_Button_pressed"] |
||||
|
||||
@ -0,0 +1,23 @@ |
||||
extends TextureRect |
||||
|
||||
var image |
||||
|
||||
func create_image(points): |
||||
image = Image.new() |
||||
image.create(get_parent().get_parent().width, get_parent().get_parent().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_Control_new_poisson(points): |
||||
create_image(points) |
||||
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) |
||||
Loading…
Reference in new issue