plant-builder working
This commit is contained in:
@@ -1,19 +1,31 @@
|
||||
extends Node
|
||||
|
||||
const IMAGE_WIDTH := 1024
|
||||
const IMAGE_HEIGHT := 1048
|
||||
const IMAGE_WIDTH := 2048
|
||||
const IMAGE_HEIGHT := 2048
|
||||
|
||||
const PLACEHOLDER_SEED_TEXTURE: Texture = preload("res://entities/plants/assets/sprites/default/seed.png")
|
||||
const PLACEHOLDER_MATURE_TEXTURE: Texture = preload("res://entities/plants/assets/sprites/default/mature.png")
|
||||
const PLACEHOLDER_GROWING_TEXTURE: Texture = preload("res://entities/plants/assets/sprites/default/growing.png")
|
||||
|
||||
enum OriginType {BRANCH_ORIGIN, MUTATION_ORIGIN, BASE_LEAF_ORIGIN}
|
||||
|
||||
# @export var parts_archetype_associations: Dictionary[PlantArchetype, PartArchetypeAssociation] TODO:: have the archetypes
|
||||
@export var bases: Array[PlantPart]
|
||||
@export var baby_bases: Array[PlantPart]
|
||||
@export var branches: Array[PlantPart]
|
||||
@export var n_branches: int = 2
|
||||
@export var base_leaves: Array[PlantPart]
|
||||
@export var parts_mutation_associations: Dictionary[String, PartMutationAssociation]
|
||||
@export var parts_mutation_associations: Dictionary[String, PartMutationAssociation] # Array[PlantPart]
|
||||
|
||||
@export var chance_to_have_part := 0.75;
|
||||
|
||||
@export var origin_weights_base: Dictionary[OriginType, int] = {OriginType.BRANCH_ORIGIN: 10, OriginType.MUTATION_ORIGIN: 10, OriginType.BASE_LEAF_ORIGIN: 10}
|
||||
@export var origin_weight_loss := 3
|
||||
@export var origin_weight_gain := 1
|
||||
|
||||
@export var mutation_weight_base := 10
|
||||
@export var mutation_weight_loss := 3
|
||||
@export var mutation_weight_gain := 1
|
||||
|
||||
var rng := RandomNumberGenerator.new()
|
||||
var image: Image = Image.create_empty(IMAGE_WIDTH, IMAGE_HEIGHT, false, Image.FORMAT_RGBA8)
|
||||
@@ -33,105 +45,127 @@ func shuffle(array: Array):
|
||||
array[available_ind] = array[picked_ind]
|
||||
array[picked_ind] = temp
|
||||
|
||||
func shuffle_weighted(array: Array, weights: Array[int]):
|
||||
assert(array.size() == weights.size(), "Suffle with weights not same size")
|
||||
|
||||
var indices := range(array.size())
|
||||
var random_values: Array[int] = []
|
||||
for i in range(array.size()):
|
||||
random_values.append(rng.randi_range(0, weights[i]))
|
||||
|
||||
indices.sort_custom(func(a, b): return random_values[a] > random_values[b])
|
||||
|
||||
var originalArray := array.duplicate()
|
||||
var originalWeights := weights.duplicate()
|
||||
|
||||
for i in range(array.size()):
|
||||
array[i] = originalArray[indices[i]]
|
||||
weights[i] = originalWeights[indices[i]]
|
||||
|
||||
func build_seed_texture(_random_seed: int) -> Texture:
|
||||
return PLACEHOLDER_SEED_TEXTURE
|
||||
|
||||
func build_plant_texture(plant_data: PlantData) -> Texture:
|
||||
rng.seed = plant_data.random_seed
|
||||
|
||||
|
||||
var mature_texture: Texture = PLACEHOLDER_MATURE_TEXTURE
|
||||
var texture: Texture
|
||||
var base_part: PlantPart
|
||||
var base_image_coord: Vector2i
|
||||
var available_attaches: Array[PlantAttach]
|
||||
var parent_image_coords: Array[Vector2]
|
||||
var parts_to_place: Array[PlantPart]
|
||||
|
||||
match plant_data.get_state():
|
||||
PlantData.State.MATURE:
|
||||
# print("Build mature texture")
|
||||
# var plant_archetype := plant_data.archetype
|
||||
if bases.size() == 0:
|
||||
printerr("No base in archetype")
|
||||
return mature_texture
|
||||
# var base_part: PlantPart = pick_random(parts_archetype_associations[plant_archetype].bases)
|
||||
texture = PLACEHOLDER_MATURE_TEXTURE
|
||||
base_part = pick_random(bases)
|
||||
|
||||
var base_image := base_part.image
|
||||
var base_image_center: Vector2i = 0.5 * base_image.get_size()
|
||||
base_image_coord = image_center - Vector2i(base_part.root.position)
|
||||
image.blend_rect(base_image, Rect2i(Vector2i.ZERO, base_image.get_size()), base_image_coord - base_image_center)
|
||||
|
||||
if branches.size() == 0:
|
||||
printerr("No branches in archetype")
|
||||
# var branch_parts: Array[PlantPart] = parts_archetype_associations[plant_archetype].branches
|
||||
for i in n_branches:
|
||||
parts_to_place.append(pick_random(branches))
|
||||
|
||||
for m in plant_data.mutations:
|
||||
# print("mutations: ", m.id)
|
||||
var association: PartMutationAssociation = parts_mutation_associations[m.id]
|
||||
var mutation_possible_parts := association.parts
|
||||
for p in association.part_amount:
|
||||
parts_to_place.append(pick_random(mutation_possible_parts))
|
||||
|
||||
PlantData.State.GROWING:
|
||||
# print("Build growing texture")
|
||||
# var plant_archetype := plant_data.archetype
|
||||
if baby_bases.size() == 0:
|
||||
printerr("No baby base in archetype")
|
||||
return mature_texture
|
||||
# var base_part: PlantPart = pick_random(parts_archetype_associations[plant_archetype].baby_bases)
|
||||
texture = PLACEHOLDER_GROWING_TEXTURE
|
||||
base_part = pick_random(baby_bases)
|
||||
|
||||
var base_image := base_part.image
|
||||
var base_image_center: Vector2i = 0.5 * base_image.get_size()
|
||||
base_image_coord = image_center - Vector2i(base_part.root.position)
|
||||
image.blend_rect(base_image, Rect2i(Vector2i.ZERO, base_image.get_size()), base_image_coord - base_image_center)
|
||||
|
||||
for m in plant_data.mutations:
|
||||
# print("mutations: ", m.id)
|
||||
var association: PartMutationAssociation = parts_mutation_associations[m.id]
|
||||
var mutation_possible_parts := association.parts
|
||||
for p in ceil(0.5 * association.part_amount):
|
||||
parts_to_place.append(pick_random(mutation_possible_parts))
|
||||
_:
|
||||
print("Not handled state")
|
||||
return null
|
||||
|
||||
available_attaches = base_part.attaches.duplicate()
|
||||
parent_image_coords.resize(available_attaches.size())
|
||||
parent_image_coords.fill(base_image_coord)
|
||||
var weight_per_origin_type: Array[int] = origin_weights_base.values().duplicate()
|
||||
|
||||
for part: PlantPart in parts_to_place:
|
||||
# print("Add part : ", part.resource_name)
|
||||
var ind := find_random_matching_attach_ind(part.root, available_attaches)
|
||||
if ind == -1:
|
||||
printerr("No attach found")
|
||||
continue
|
||||
var parts_to_place: Dictionary[OriginType, Array] # BRANCH_ORIGIN : Array[PlantPart], MUTATION_ORIGIN : Array[Array[PlantPart]], BASE_LEAF_ORIGIN : Array[PlantPart]
|
||||
parts_to_place[OriginType.BRANCH_ORIGIN] = branches
|
||||
parts_to_place[OriginType.MUTATION_ORIGIN] = []
|
||||
parts_to_place[OriginType.BASE_LEAF_ORIGIN] = base_leaves
|
||||
var mutation_weights: Array[int] = []
|
||||
for mutation in plant_data.mutations:
|
||||
parts_to_place[OriginType.MUTATION_ORIGIN].append(parts_mutation_associations[mutation.id].parts)
|
||||
mutation_weights.append(mutation_weight_base)
|
||||
|
||||
var attach: PlantAttach = available_attaches.pop_at(ind)
|
||||
var base_image_coord = blend_part(image_center, Vector2.ZERO, base_part)
|
||||
populate_part(parts_to_place, weight_per_origin_type, mutation_weights, base_part, base_image_coord)
|
||||
|
||||
var parent_image_coord: Vector2i = parent_image_coords.pop_at(ind)
|
||||
|
||||
var part_image: Image = part.image
|
||||
var part_image_center: Vector2i = 0.5 * part_image.get_size()
|
||||
var part_image_coord: Vector2i = parent_image_coord + Vector2i(attach.position - part.root.position)
|
||||
image.blend_rect(part_image, Rect2i(Vector2i.ZERO, part.image.get_size()), part_image_coord - part_image_center)
|
||||
|
||||
for sub_attach in part.attaches:
|
||||
available_attaches.append(sub_attach)
|
||||
parent_image_coords.append(part_image_coord)
|
||||
|
||||
mature_texture = ImageTexture.create_from_image(image)
|
||||
texture = ImageTexture.create_from_image(image)
|
||||
image.fill(Color.TRANSPARENT)
|
||||
return mature_texture
|
||||
return texture
|
||||
|
||||
## returns -1 if not found
|
||||
func find_random_matching_attach_ind(attach_to_match: PlantAttach, array: Array[PlantAttach]) -> int:
|
||||
var indices: Array = range(array.size())
|
||||
func find_random_matching_attach_ind(attach_to_match: PlantAttach, array: Array[PlantPart]) -> int:
|
||||
var indices := range(array.size())
|
||||
shuffle(indices)
|
||||
for i in indices:
|
||||
if array[i].attach_types.any(func(type): return attach_to_match.attach_types.has(type)):
|
||||
if array[i].root.attach_types.any(func(type): return attach_to_match.attach_types.has(type)):
|
||||
return i
|
||||
return -1
|
||||
|
||||
func populate_part(all_parts: Dictionary[OriginType, Array], weight_per_origin_type: Array[int], mutation_weights: Array[int], parent_part: PlantPart, parent_image_coord: Vector2i):
|
||||
var part_placed: Array[PlantPart] # same ind as their corresponding attach
|
||||
var part_image_coords: Array[Vector2i] # idem
|
||||
|
||||
# first find and blend parts per attach
|
||||
for attach in parent_part.attaches:
|
||||
# get part to place
|
||||
var part_to_place := get_part(all_parts, weight_per_origin_type, mutation_weights, attach)
|
||||
part_placed.append(part_to_place)
|
||||
|
||||
# blend part
|
||||
if part_to_place:
|
||||
var part_image_coord := blend_part(parent_image_coord, attach.position, part_to_place)
|
||||
part_image_coords.append(part_image_coord)
|
||||
else:
|
||||
part_image_coords.append(Vector2i.ZERO)
|
||||
|
||||
# then populate them
|
||||
for i in range(part_placed.size()):
|
||||
if part_placed[i] != null:
|
||||
populate_part(all_parts, weight_per_origin_type, mutation_weights, part_placed[i], part_image_coords[i])
|
||||
|
||||
|
||||
func get_part(all_parts: Dictionary[OriginType, Array], weight_per_origin_type: Array[int], mutation_weights: Array[int], attach: PlantAttach) -> PlantPart:
|
||||
var rand := rng.randf()
|
||||
if rand <= chance_to_have_part:
|
||||
var origins: Array[int] = all_parts.keys().duplicate()
|
||||
var weight_copy: Array[int] = weight_per_origin_type.duplicate()
|
||||
shuffle_weighted(origins, weight_copy)
|
||||
for originIndInd: int in range(origins.size()):
|
||||
var originInd: int = origins[originIndInd]
|
||||
var origin := originInd as OriginType
|
||||
if origin == OriginType.BRANCH_ORIGIN || origin == OriginType.BASE_LEAF_ORIGIN:
|
||||
var ind := find_random_matching_attach_ind(attach, all_parts[origin])
|
||||
if ind >= 0:
|
||||
weight_per_origin_type[origin] -= origin_weight_loss
|
||||
weight_per_origin_type[((origin + 1) % 3) as OriginType] += origin_weight_gain
|
||||
weight_per_origin_type[((origin + 2) % 3) as OriginType] += origin_weight_gain
|
||||
return all_parts[origin][ind]
|
||||
else: # find a mutation part to place
|
||||
var parts_per_mutations: Array = all_parts[origin]
|
||||
shuffle_weighted(parts_per_mutations, mutation_weights)
|
||||
for mutation_parts_ind in range(parts_per_mutations.size()):
|
||||
var ind := find_random_matching_attach_ind(attach, parts_per_mutations[mutation_parts_ind])
|
||||
if ind >= 0:
|
||||
for i in range(mutation_weights.size()):
|
||||
mutation_weights[i] += mutation_weight_gain
|
||||
mutation_weights[mutation_parts_ind] -= mutation_weight_gain + mutation_weight_loss
|
||||
weight_per_origin_type[origin] -= origin_weight_loss
|
||||
weight_per_origin_type[(origin + 1) % 3] += origin_weight_gain
|
||||
weight_per_origin_type[(origin + 2) % 3] += origin_weight_gain
|
||||
return parts_per_mutations[mutation_parts_ind][ind]
|
||||
return null
|
||||
|
||||
func blend_part(parent_image_coord: Vector2i, attach_position: Vector2, part_to_blend: PlantPart) -> Vector2i:
|
||||
var part_image: Image = part_to_blend.image
|
||||
var part_image_center: Vector2i = 0.5 * part_image.get_size()
|
||||
var part_image_coord: Vector2i = parent_image_coord + Vector2i(attach_position - part_to_blend.root.position)
|
||||
image.blend_rect(part_image, Rect2i(Vector2i.ZERO, part_to_blend.image.get_size()), part_image_coord - part_image_center)
|
||||
return part_image_coord
|
||||
|
||||
Reference in New Issue
Block a user