Compare commits
No commits in common. 'main' and 'texture' have entirely different histories.
@ -1,21 +0,0 @@ |
||||
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. |
||||
@ -1,145 +0,0 @@ |
||||
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) |
||||
@ -1,132 +0,0 @@ |
||||
[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"] |
||||
@ -1,25 +0,0 @@ |
||||
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() |
||||
|
||||
@ -1,23 +0,0 @@ |
||||
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() |
||||
@ -1,32 +0,0 @@ |
||||
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 |
||||
@ -1,12 +0,0 @@ |
||||
[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 ) |
||||
@ -1,137 +0,0 @@ |
||||
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