399 lines
14 KiB
GDScript
399 lines
14 KiB
GDScript
class_name JajceWorld
|
|
extends Node3D
|
|
|
|
enum PresentationQuality {
|
|
HIGH,
|
|
BALANCED,
|
|
LOW,
|
|
}
|
|
|
|
const BALANCED_RENDER_SCALE := 0.85
|
|
const LOW_RENDER_SCALE := 0.7
|
|
const BALANCED_SHADOW_DISTANCE := 120.0
|
|
const LOW_SHADOW_DISTANCE := 80.0
|
|
const BALANCED_GRASS_SPACING := 1.0
|
|
const LOW_GRASS_SPACING := 2.0
|
|
const BALANCED_GRASS_FIXED_FPS := 18
|
|
const LOW_GRASS_FIXED_FPS := 12
|
|
const BALANCED_GRASS_INTERACTORS := 4
|
|
const BALANCED_GRASS_UPDATE_INTERVAL := 0.12
|
|
const HIGH_VOLUMETRIC_FOG_ENABLED := true
|
|
|
|
static var _viewport_scale_states: Dictionary = {}
|
|
|
|
@export_enum("High", "Balanced", "Low") var presentation_quality: int = PresentationQuality.BALANCED
|
|
|
|
@onready var terrain: Terrain3D = $TerrainRoot/Terrain3D
|
|
@onready var world_environment: WorldEnvironment = $WorldEnvironment
|
|
@onready var directional_light: DirectionalLight3D = $DirectionalLight3D
|
|
@onready var grass_field: Node3D = $TerrainRoot/Terrain3D/CozyGrassField
|
|
@onready var grass_controller: Node = $TerrainRoot/GrassInteractionController
|
|
|
|
var _active_presentation_quality := -1
|
|
var _authored_presentation_captured := false
|
|
var _authored_render_scale := 1.0
|
|
var _authored_msaa: int = Viewport.MSAA_DISABLED
|
|
var _authored_glow_enabled := false
|
|
var _authored_volumetric_fog_enabled := HIGH_VOLUMETRIC_FOG_ENABLED
|
|
var _authored_adjustment_enabled := false
|
|
var _authored_shadow_distance := 0.0
|
|
var _authored_shadow_mode := DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
|
|
var _authored_grass_visible := true
|
|
var _authored_grass_physics_enabled := true
|
|
var _authored_grass_spacing := 1.0
|
|
var _authored_grass_fixed_fps := 30
|
|
var _authored_grass_interactors := 1
|
|
var _authored_grass_update_interval := 0.1
|
|
var _authored_grass_controller_enabled := true
|
|
var _last_applied_render_scale := -1.0
|
|
var _last_applied_msaa := -1
|
|
var _viewport_instance_id := 0
|
|
|
|
|
|
func _ready() -> void:
|
|
_isolate_mutable_presentation_resources()
|
|
_capture_authored_presentation()
|
|
apply_presentation_quality(_resolve_requested_quality())
|
|
call_deferred("_initialize_world_presentation")
|
|
|
|
|
|
func _exit_tree() -> void:
|
|
_release_viewport_scale_owner()
|
|
|
|
|
|
func apply_presentation_quality(quality: int) -> bool:
|
|
if quality not in PresentationQuality.values():
|
|
push_warning("JajceWorld: unknown presentation quality %s" % quality)
|
|
return false
|
|
if not _authored_presentation_captured:
|
|
_capture_authored_presentation()
|
|
if not _authored_presentation_captured:
|
|
return false
|
|
if quality == _active_presentation_quality:
|
|
return true
|
|
|
|
match quality:
|
|
PresentationQuality.HIGH:
|
|
_apply_environment_quality(
|
|
_authored_render_scale,
|
|
_authored_glow_enabled,
|
|
_authored_volumetric_fog_enabled,
|
|
_authored_adjustment_enabled,
|
|
_authored_shadow_distance,
|
|
_authored_shadow_mode,
|
|
_authored_msaa
|
|
)
|
|
_apply_grass_quality(
|
|
_authored_grass_visible,
|
|
_authored_grass_physics_enabled,
|
|
_authored_grass_spacing,
|
|
_authored_grass_fixed_fps,
|
|
_authored_grass_interactors,
|
|
_authored_grass_update_interval,
|
|
_authored_grass_controller_enabled
|
|
)
|
|
PresentationQuality.BALANCED:
|
|
_apply_environment_quality(
|
|
minf(_authored_render_scale, BALANCED_RENDER_SCALE),
|
|
_authored_glow_enabled,
|
|
false,
|
|
_authored_adjustment_enabled,
|
|
minf(_authored_shadow_distance, BALANCED_SHADOW_DISTANCE),
|
|
DirectionalLight3D.SHADOW_PARALLEL_2_SPLITS,
|
|
mini(_authored_msaa, Viewport.MSAA_2X)
|
|
)
|
|
_apply_grass_quality(
|
|
_authored_grass_visible,
|
|
_authored_grass_physics_enabled,
|
|
maxf(_authored_grass_spacing, BALANCED_GRASS_SPACING),
|
|
mini(_authored_grass_fixed_fps, BALANCED_GRASS_FIXED_FPS),
|
|
mini(_authored_grass_interactors, BALANCED_GRASS_INTERACTORS),
|
|
maxf(_authored_grass_update_interval, BALANCED_GRASS_UPDATE_INTERVAL),
|
|
_authored_grass_controller_enabled
|
|
)
|
|
PresentationQuality.LOW:
|
|
_apply_environment_quality(
|
|
minf(_authored_render_scale, LOW_RENDER_SCALE),
|
|
false,
|
|
false,
|
|
false,
|
|
minf(_authored_shadow_distance, LOW_SHADOW_DISTANCE),
|
|
DirectionalLight3D.SHADOW_ORTHOGONAL,
|
|
Viewport.MSAA_DISABLED
|
|
)
|
|
_apply_grass_quality(
|
|
false,
|
|
false,
|
|
maxf(_authored_grass_spacing, LOW_GRASS_SPACING),
|
|
mini(_authored_grass_fixed_fps, LOW_GRASS_FIXED_FPS),
|
|
0,
|
|
_authored_grass_update_interval,
|
|
false
|
|
)
|
|
|
|
var meadow := get_node_or_null("PainterlyMeadow")
|
|
if meadow != null:
|
|
meadow.call("apply_presentation_quality", quality)
|
|
_active_presentation_quality = quality
|
|
return true
|
|
|
|
|
|
func get_active_presentation_quality() -> int:
|
|
return _active_presentation_quality
|
|
|
|
|
|
func get_active_presentation_quality_name() -> StringName:
|
|
match _active_presentation_quality:
|
|
PresentationQuality.HIGH:
|
|
return &"high"
|
|
PresentationQuality.BALANCED:
|
|
return &"balanced"
|
|
PresentationQuality.LOW:
|
|
return &"low"
|
|
return &"unknown"
|
|
|
|
|
|
func _capture_authored_presentation() -> void:
|
|
if _authored_presentation_captured:
|
|
return
|
|
var environment := _environment()
|
|
if environment == null or grass_field == null or grass_controller == null:
|
|
return
|
|
_register_viewport_scale_owner()
|
|
_authored_glow_enabled = environment.glow_enabled
|
|
# Keep the serialized scene compatibility-safe; High deliberately opts into
|
|
# this Forward+-only authored effect after the quality profile is resolved.
|
|
_authored_volumetric_fog_enabled = HIGH_VOLUMETRIC_FOG_ENABLED
|
|
_authored_adjustment_enabled = environment.adjustment_enabled
|
|
_authored_shadow_distance = directional_light.directional_shadow_max_distance
|
|
_authored_shadow_mode = directional_light.directional_shadow_mode
|
|
_authored_grass_visible = grass_field.visible
|
|
_authored_grass_physics_enabled = grass_field.is_physics_processing()
|
|
_authored_grass_spacing = float(grass_field.get("instance_spacing"))
|
|
_authored_grass_fixed_fps = int(grass_field.get("process_fixed_fps"))
|
|
_authored_grass_interactors = int(grass_controller.get("max_interactors"))
|
|
_authored_grass_update_interval = float(grass_controller.get("update_interval"))
|
|
_authored_grass_controller_enabled = grass_controller.is_processing()
|
|
_authored_presentation_captured = true
|
|
|
|
|
|
func _isolate_mutable_presentation_resources() -> void:
|
|
var source_environment := _environment()
|
|
if source_environment != null:
|
|
var isolated_environment := source_environment.duplicate(true) as Environment
|
|
if source_environment.sky != null:
|
|
var isolated_sky := source_environment.sky.duplicate(true) as Sky
|
|
if source_environment.sky.sky_material != null:
|
|
isolated_sky.sky_material = source_environment.sky.sky_material.duplicate(true)
|
|
isolated_environment.sky = isolated_sky
|
|
world_environment.environment = isolated_environment
|
|
|
|
var source_grass_material := grass_field.get("mesh_material_override") as ShaderMaterial
|
|
if source_grass_material != null:
|
|
var isolated_grass_material := source_grass_material.duplicate(true) as ShaderMaterial
|
|
grass_field.set("mesh_material_override", isolated_grass_material)
|
|
grass_controller.set("grass_material", isolated_grass_material)
|
|
var source_process_material := grass_field.get("process_material") as ShaderMaterial
|
|
if source_process_material != null:
|
|
var isolated_process_material := source_process_material.duplicate(true) as ShaderMaterial
|
|
grass_field.set("process_material", isolated_process_material)
|
|
var particles: Array = grass_field.get("particle_nodes")
|
|
for value in particles:
|
|
var particle := value as GPUParticles3D
|
|
if particle != null:
|
|
particle.process_material = isolated_process_material
|
|
|
|
|
|
func _resolve_requested_quality() -> int:
|
|
for argument in OS.get_cmdline_user_args():
|
|
if argument.begins_with("--quality="):
|
|
return _quality_from_name(argument.trim_prefix("--quality="))
|
|
if argument.begins_with("--presentation-quality="):
|
|
return _quality_from_name(argument.trim_prefix("--presentation-quality="))
|
|
return presentation_quality
|
|
|
|
|
|
func _quality_from_name(value: String) -> int:
|
|
match value.strip_edges().to_lower():
|
|
"high":
|
|
return PresentationQuality.HIGH
|
|
"low":
|
|
return PresentationQuality.LOW
|
|
"balanced":
|
|
return PresentationQuality.BALANCED
|
|
push_warning("JajceWorld: unknown presentation quality '%s'; using scene default" % value)
|
|
return presentation_quality
|
|
|
|
|
|
func _apply_environment_quality(
|
|
render_scale: float,
|
|
glow_enabled: bool,
|
|
volumetric_fog_enabled: bool,
|
|
adjustment_enabled: bool,
|
|
shadow_distance: float,
|
|
shadow_mode: int,
|
|
msaa: int
|
|
) -> void:
|
|
_claim_viewport_scale_owner()
|
|
get_viewport().scaling_3d_scale = render_scale
|
|
get_viewport().msaa_3d = msaa
|
|
_last_applied_render_scale = render_scale
|
|
_last_applied_msaa = msaa
|
|
var environment := _environment()
|
|
if environment != null:
|
|
environment.glow_enabled = glow_enabled
|
|
environment.volumetric_fog_enabled = (volumetric_fog_enabled and _supports_volumetric_fog())
|
|
environment.adjustment_enabled = adjustment_enabled
|
|
directional_light.directional_shadow_max_distance = shadow_distance
|
|
directional_light.directional_shadow_mode = shadow_mode
|
|
|
|
|
|
func _apply_grass_quality(
|
|
is_visible: bool,
|
|
physics_enabled: bool,
|
|
instance_spacing: float,
|
|
fixed_fps: int,
|
|
max_interactors: int,
|
|
update_interval: float,
|
|
controller_enabled: bool
|
|
) -> void:
|
|
grass_field.set("instance_spacing", instance_spacing)
|
|
grass_field.set("process_fixed_fps", fixed_fps)
|
|
grass_field.visible = is_visible
|
|
grass_field.set_physics_process(physics_enabled)
|
|
grass_controller.set("max_interactors", maxi(max_interactors, 1))
|
|
grass_controller.set("update_interval", update_interval)
|
|
grass_controller.call("set_interaction_enabled", controller_enabled)
|
|
|
|
var particles: Array = grass_field.get("particle_nodes")
|
|
for value in particles:
|
|
var particle := value as GPUParticles3D
|
|
if particle == null:
|
|
continue
|
|
particle.emitting = is_visible and physics_enabled
|
|
if particle.emitting:
|
|
particle.restart(true)
|
|
|
|
var grass_material := grass_field.get("mesh_material_override") as ShaderMaterial
|
|
if grass_material != null and not controller_enabled:
|
|
grass_material.set_shader_parameter("interactor_count", 0)
|
|
if physics_enabled:
|
|
grass_field.set("last_pos", Vector3.INF)
|
|
|
|
|
|
func _environment() -> Environment:
|
|
if world_environment == null:
|
|
return null
|
|
return world_environment.environment
|
|
|
|
|
|
func _supports_volumetric_fog() -> bool:
|
|
return RenderingServer.get_current_rendering_method() == "forward_plus"
|
|
|
|
|
|
func _register_viewport_scale_owner() -> void:
|
|
var viewport := get_viewport()
|
|
_viewport_instance_id = viewport.get_instance_id()
|
|
var state: Dictionary = _viewport_scale_states.get(_viewport_instance_id, {})
|
|
if state.is_empty():
|
|
state = {
|
|
"base_scale": viewport.scaling_3d_scale,
|
|
"base_msaa": viewport.msaa_3d,
|
|
"owners": [],
|
|
}
|
|
_authored_render_scale = float(state["base_scale"])
|
|
_authored_msaa = int(state["base_msaa"])
|
|
_viewport_scale_states[_viewport_instance_id] = state
|
|
_claim_viewport_scale_owner()
|
|
|
|
|
|
func _claim_viewport_scale_owner() -> void:
|
|
if _viewport_instance_id == 0:
|
|
return
|
|
var state: Dictionary = _viewport_scale_states.get(_viewport_instance_id, {})
|
|
if state.is_empty():
|
|
return
|
|
var owners: Array = state["owners"]
|
|
for index in range(owners.size() - 1, -1, -1):
|
|
var owner: Object = (owners[index] as WeakRef).get_ref()
|
|
if owner == null or owner == self:
|
|
owners.remove_at(index)
|
|
owners.append(weakref(self))
|
|
state["owners"] = owners
|
|
_viewport_scale_states[_viewport_instance_id] = state
|
|
|
|
|
|
func _release_viewport_scale_owner() -> void:
|
|
if _viewport_instance_id == 0 or not _viewport_scale_states.has(_viewport_instance_id):
|
|
return
|
|
var viewport := get_viewport()
|
|
var state: Dictionary = _viewport_scale_states[_viewport_instance_id]
|
|
var owners: Array = state["owners"]
|
|
var was_active_owner := false
|
|
for index in range(owners.size() - 1, -1, -1):
|
|
var owner: Object = (owners[index] as WeakRef).get_ref()
|
|
if owner == self:
|
|
was_active_owner = index == owners.size() - 1
|
|
owners.remove_at(index)
|
|
elif owner == null:
|
|
owners.remove_at(index)
|
|
if not owners.is_empty():
|
|
state["owners"] = owners
|
|
_viewport_scale_states[_viewport_instance_id] = state
|
|
if was_active_owner:
|
|
var previous_owner := (owners[-1] as WeakRef).get_ref() as JajceWorld
|
|
if (
|
|
previous_owner != null
|
|
and previous_owner._last_applied_render_scale >= 0.0
|
|
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
|
|
):
|
|
viewport.scaling_3d_scale = previous_owner._last_applied_render_scale
|
|
if (
|
|
previous_owner != null
|
|
and previous_owner._last_applied_msaa >= 0
|
|
and viewport.msaa_3d == _last_applied_msaa
|
|
):
|
|
viewport.msaa_3d = previous_owner._last_applied_msaa
|
|
else:
|
|
_viewport_scale_states.erase(_viewport_instance_id)
|
|
if (
|
|
was_active_owner
|
|
and is_equal_approx(viewport.scaling_3d_scale, _last_applied_render_scale)
|
|
):
|
|
viewport.scaling_3d_scale = float(state["base_scale"])
|
|
if was_active_owner and viewport.msaa_3d == _last_applied_msaa:
|
|
viewport.msaa_3d = int(state["base_msaa"])
|
|
_viewport_instance_id = 0
|
|
_last_applied_render_scale = -1.0
|
|
_last_applied_msaa = -1
|
|
|
|
|
|
func _initialize_world_presentation() -> void:
|
|
var active_camera := get_viewport().get_camera_3d()
|
|
if active_camera != null:
|
|
terrain.set_camera(active_camera)
|
|
|
|
var groups_to_snap: Array[Node] = []
|
|
groups_to_snap.append_array($FoliageRoot.get_children())
|
|
if has_node("FortressBlockout"):
|
|
groups_to_snap.append($FortressBlockout)
|
|
groups_to_snap.append_array($FortressBlockout.get_children())
|
|
for child in $VillageRoot.get_children():
|
|
groups_to_snap.append(child)
|
|
if has_node("WorldObjects"):
|
|
var wo := $WorldObjects
|
|
for group_name in [
|
|
"Animals", "AnimalHabitats", "ResourceNodes", "StorageSites", "ActivitySites"
|
|
]:
|
|
if wo.has_node(group_name):
|
|
groups_to_snap.append_array(wo.get_node(group_name).get_children())
|
|
|
|
for item in groups_to_snap:
|
|
if not item is Node3D:
|
|
continue
|
|
var node3d := item as Node3D
|
|
var pos := node3d.global_position
|
|
var h: float = terrain.data.get_height(pos)
|
|
if not is_nan(h):
|
|
node3d.global_position.y = h
|