diff --git a/.gitignore b/.gitignore
index 3e9301f6..4a68f4d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,3 +62,4 @@ godot-cpp/CMakeFiles/godot-cpp.dir/gen/src/classes
build-*/
/.codex
+/.antigravitycli
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index 61103b4e..ed993018 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -16,6 +16,7 @@ Code generation:
* GDSCRIPT: do not replace normal singleton/global access with `/root/...` lookups as a workaround
* GDSCRIPT: do not add `is_connected()` guard clutter for signal lifecycle issues; keep one direct `connect` and one matching direct `disconnect`
* keep guards minimal; do not generate guard bloat or defensive condition chains when one necessary condition is enough
+* do not useset/get/has_meta for accessing/saving/loading node state
General guidelines:
@@ -26,7 +27,7 @@ General guidelines:
Custom nodes and Godot Editor:
* place editor related code in addons/libmaszyna/editor
-* use libmaszyna.gd just for bootstraping and proxying to editor plugins
+* use libmaszyna.gd just for bootstrapping and proxying to editor plugins
* make sure C++ singletons never inherit from RefCounted
Documentation:
diff --git a/addons/libmaszyna/e3d/e3d_instancer.gd b/addons/libmaszyna/e3d/e3d_instancer.gd
index 678f0891..31a03f53 100644
--- a/addons/libmaszyna/e3d/e3d_instancer.gd
+++ b/addons/libmaszyna/e3d/e3d_instancer.gd
@@ -17,12 +17,20 @@ func sync(target_node: E3DModelInstance) -> void:
push_error("%s.sync() must be implemented" % get_script().resource_path)
+## Synchronizes the visibility of light nodes based on the target node's light state.
+## Implementations should evaluate [method E3DModelInstance.get_lights_state] and enable/disable
+## appropriate visual nodes.
+func sync_lights(target_node: E3DModelInstance) -> void:
+ push_error("%s.sync_lights() must be implemented" % get_script().resource_path)
+
+
func _is_submodel_valid(target_node: E3DModelInstance, submodel: E3DSubModel) -> bool:
if submodel.skip_rendering:
return false
match submodel.submodel_type:
- E3DSubModel.SubModelType.SUBMODEL_TRANSFORM:
+ E3DSubModel.SubModelType.SUBMODEL_TRANSFORM, \
+ E3DSubModel.SubModelType.SUBMODEL_FREE_SPOTLIGHT:
return true
E3DSubModel.SubModelType.SUBMODEL_GL_TRIANGLES:
return not target_node.exclude_node_names.any(
@@ -35,6 +43,14 @@ func _is_submodel_valid(target_node: E3DModelInstance, submodel: E3DSubModel) ->
func _get_material_override(target_node: E3DModelInstance, submodel: E3DSubModel) -> Material:
var unprefixed_model_path: String = "/".join(target_node.data_path.split("/").slice(1))
+ var options = MaterialManager.MaterialOptions.new()
+
+ # TODO: handle more material options here (selfillum, diffuse_color, etc)
+ options.force_transparent = submodel.material_transparent
+ options.selfillum_energy = options.selfillum_color.a # legacy renderer
+ options.selfillum_color = submodel.self_illumination
+ options.selfillum_enabled = options.selfillum_energy > 0.0 and submodel.lights_on_threshold >= 1.0 # legacy renderer logic
+
if submodel.dynamic_material:
if target_node.skins.size() < submodel.dynamic_material_index + 1:
push_warning(
@@ -49,7 +65,7 @@ func _get_material_override(target_node: E3DModelInstance, submodel: E3DSubModel
else MaterialManager.Transparency.Disabled
)
var skin: Variant = target_node.skins[submodel.dynamic_material_index]
- return MaterialManager.get_material(unprefixed_model_path, skin, dynamic_transparency)
+ return MaterialManager.get_material(unprefixed_model_path, skin, options)
if submodel.material_colored:
return _colored_material
@@ -63,8 +79,7 @@ func _get_material_override(target_node: E3DModelInstance, submodel: E3DSubModel
return MaterialManager.get_material(
unprefixed_model_path,
submodel.material_name,
- named_transparency,
- submodel.diffuse_color,
+ options,
)
return null
diff --git a/addons/libmaszyna/e3d/e3d_light.gd b/addons/libmaszyna/e3d/e3d_light.gd
new file mode 100644
index 00000000..e9afe19e
--- /dev/null
+++ b/addons/libmaszyna/e3d/e3d_light.gd
@@ -0,0 +1,28 @@
+@tool
+extends SpotLight3D
+
+var meshes_on: Array[Node3D] = []
+var meshes_off: Array[Node3D] = []
+
+@export var enabled: bool = true:
+ set(v):
+ enabled = v
+ _update_state()
+
+func _ready():
+ _update_state()
+
+func _update_state():
+ var parent = get_parent()
+ if parent == null:
+ return
+ var light_root = parent.get_parent()
+ var is_end = parent.name.begins_with("end")
+ if (!is_end):
+ var base_name = parent.name.trim_suffix("_on")
+ var lamp_off_node = light_root.get_node_or_null(NodePath(base_name + "_off"))
+ parent.visible = enabled
+ if lamp_off_node != null:
+ lamp_off_node.visible = !enabled
+ else:
+ parent.visible = enabled
diff --git a/addons/libmaszyna/e3d/e3d_light.gd.uid b/addons/libmaszyna/e3d/e3d_light.gd.uid
new file mode 100644
index 00000000..a6f05578
--- /dev/null
+++ b/addons/libmaszyna/e3d/e3d_light.gd.uid
@@ -0,0 +1 @@
+uid://d0k5foqnxfdhu
diff --git a/addons/libmaszyna/e3d/e3d_model_instance.gd b/addons/libmaszyna/e3d/e3d_model_instance.gd
index 49c6c02a..ccba13f8 100644
--- a/addons/libmaszyna/e3d/e3d_model_instance.gd
+++ b/addons/libmaszyna/e3d/e3d_model_instance.gd
@@ -29,6 +29,13 @@ var _e3d_loaded: bool = false
var _current_instancer: E3DInstancer
var _current_editable: bool = false
+@export var lights_state: Dictionary[String, bool] = {}:
+ set(x):
+ lights_state = x
+ if is_inside_tree() and e3d_loaded:
+ _current_instancer.sync_lights(self)
+
+
var default_aabb_size: Vector3 = Vector3(1, 1, 1)
## E3DModel to instantiate (leave empty, if you want to lazy load with [member model_filename])
@@ -132,19 +139,35 @@ func _ready() -> void:
func _enter_tree() -> void:
- if _model and _current_instancer:
- _current_instancer.instantiate(self, _model, _current_editable)
-
+ pass
func _exit_tree() -> void:
if _current_instancer:
_current_instancer.clear(self)
-func is_e3d_loaded():
+func is_e3d_loaded() -> bool:
return _e3d_loaded
+func get_lights_state() -> Dictionary[String, bool]:
+ return lights_state
+
+
+func set_light_enabled(light_name: String, state: bool) -> void:
+ if not _model.has_light(light_name):
+ push_error("Light not found: "+light_name)
+ return
+
+ if lights_state.has(light_name):
+ if lights_state[light_name] == state:
+ return
+
+ lights_state[light_name] = state
+ if _current_instancer:
+ _current_instancer.sync_lights(self)
+
+
func _resolve_instancer():
match instancer:
Instancer.NODES, Instancer.EDITABLE_NODES:
diff --git a/addons/libmaszyna/e3d/e3d_nodes_instancer.gd b/addons/libmaszyna/e3d/e3d_nodes_instancer.gd
index 32ad606b..21f76fb1 100644
--- a/addons/libmaszyna/e3d/e3d_nodes_instancer.gd
+++ b/addons/libmaszyna/e3d/e3d_nodes_instancer.gd
@@ -1,12 +1,83 @@
@tool
extends E3DInstancer
+const DEFAULT_LIGHT_ENERGY := 10
+const DEFAULT_END_LIGHT_ENERGY := 5
+
+class LightNodeInfo:
+ var light_name: String = ""
+ var light_on_node:NodePath = NodePath("")
+ var light_off_node:NodePath = NodePath("")
+ var spotlight_node:NodePath = NodePath("")
+
+var instances_lights : Dictionary = {}
func instantiate(target_node: E3DModelInstance, model: E3DModel, editable: bool = false) -> void:
- _do_add_submodels(target_node, target_node, model.submodels, editable)
+ var lights: Array = [] # first pass
+ _do_add_submodels(target_node, model, target_node, model.submodels, editable, lights)
+
+ # prebuild LightNodeInfo dictionary
+ var _instance_lights:Dictionary = {}
+ var _spotlights = []
+
+ for entry in lights:
+ var light_name = entry[0]
+ var entry_type = entry[1]
+ var node = entry[2]
+ if light_name:
+ var light_info:LightNodeInfo = _instance_lights.get(light_name)
+ if not light_info:
+ light_info = LightNodeInfo.new()
+ light_info.light_name = light_name
+ _instance_lights[light_name] = light_info
+ match entry_type:
+ "on":
+ light_info.light_on_node = target_node.get_path_to(node)
+ "off":
+ light_info.light_off_node = target_node.get_path_to(node)
+ else:
+ if entry_type == "spotlight":
+ _spotlights.append(node)
+
+ var _light_on_nodes = _instance_lights.values().map(func(x): return x.light_on_node)
+
+ # assign instance lights to the global registry
+ instances_lights[target_node] = _instance_lights
+
+ for spotlight:SpotLight3D in _spotlights:
+ while true:
+ var parent = spotlight.get_parent()
+ if not parent:
+ break
+ var found_idx = _light_on_nodes.find(target_node.get_path_to(parent))
+ if found_idx > -1:
+ _instance_lights.values()[found_idx].spotlight_node = target_node.get_path_to(spotlight)
+ break
+
+ sync_lights(target_node)
+ #print(target_node.get_lights_state())
+
+
+func sync_lights(target_node: E3DModelInstance) -> void:
+ var state = target_node.get_lights_state()
+ for light_name in state.keys():
+ var light_info:LightNodeInfo = instances_lights[target_node].get(light_name)
+ if light_info:
+ var enabled = state[light_name]
+ var on_node = target_node.get_node_or_null(light_info.light_on_node)
+ var off_node = target_node.get_node_or_null(light_info.light_off_node)
+ var spotlight:SpotLight3D = target_node.get_node_or_null(light_info.spotlight_node)
+
+ if on_node:
+ on_node.visible = enabled
+ if off_node:
+ off_node.visible = not enabled
+ if spotlight:
+ spotlight.visible = enabled
func clear(target_node: E3DModelInstance) -> void:
+ instances_lights.erase(target_node)
for child: Node in target_node.get_children(true):
target_node.remove_child(child)
child.queue_free()
@@ -18,13 +89,29 @@ func sync(target_node: E3DModelInstance) -> void:
func _do_add_submodels(
target_node:E3DModelInstance,
+ model: E3DModel,
parent,
- submodels,
- editable:bool
+ submodels: Array[E3DSubModel],
+ editable: bool,
+ lights: Array
) -> void:
for submodel in submodels:
if _is_submodel_valid(target_node, submodel):
- var child:Node = _create_submodel_instance(target_node, submodel)
+ var child:Node = _create_submodel_instance(target_node, submodel, model, lights)
+ if not child:
+ continue
+ if (
+ submodel.resource_name.ends_with("_on")
+ or submodel.resource_name.ends_with("_off")
+ or submodel.resource_name.ends_with("_xon")
+ ):
+ for light_name in model.lights.keys():
+ var light_info:E3DModelLightDefinition = model.lights[light_name]
+ if submodel == light_info.on_submodel:
+ lights.append([light_name, "on", child])
+ elif submodel == light_info.off_submodel:
+ lights.append([light_name, "off", child])
+
_update_submodel_material(target_node, child, submodel)
var internal = InternalMode.INTERNAL_MODE_DISABLED if editable else InternalMode.INTERNAL_MODE_BACK
parent.add_child(child, false, internal)
@@ -33,15 +120,20 @@ func _do_add_submodels(
# Applying transform before adding may cause issues (especially on windows)
if child is Node3D and submodel.transform:
var child_node:Node3D = child as Node3D
- child_node.transform = submodel.transform
+ if submodel.submodel_type == E3DSubModel.SUBMODEL_FREE_SPOTLIGHT:
+ # Do not scale SpotLight3D to avoid configuration warnings
+ child_node.position = submodel.transform.origin
+ child_node.basis = submodel.transform.basis.orthonormalized()
+ else:
+ child_node.transform = submodel.transform
if Engine.is_editor_hint():
child.owner = target_node.owner if editable else target_node
if submodel.submodels:
- _do_add_submodels(target_node, child, submodel.submodels, editable)
+ _do_add_submodels(target_node, model, child, submodel.submodels, editable, lights)
-func _create_submodel_instance(target_node: E3DModelInstance, submodel: E3DSubModel):
+func _create_submodel_instance(target_node: E3DModelInstance, submodel: E3DSubModel, model: E3DModel, lights):
var obj
match submodel.submodel_type:
@@ -56,6 +148,28 @@ func _create_submodel_instance(target_node: E3DModelInstance, submodel: E3DSubMo
obj.visibility_range_begin = submodel.visibility_range_begin
obj.visibility_range_end = submodel.visibility_range_end
+ E3DSubModel.SubModelType.SUBMODEL_FREE_SPOTLIGHT:
+ obj = SpotLight3D.new()
+ obj.name = submodel.resource_name
+ obj.light_color = submodel.diffuse_color
+ obj.light_color.a = 1.0
+ if submodel.light_energy == 0:
+ if submodel.resource_name.begins_with("end"):
+ obj.light_energy = DEFAULT_END_LIGHT_ENERGY
+ else:
+ obj.light_energy = DEFAULT_LIGHT_ENERGY
+ else:
+ obj.light_energy = submodel.light_energy
+
+ obj.light_volumetric_fog_energy = 4.0 # FIXME: guessing
+ obj.shadow_enabled = true
+ obj.distance_fade_enabled = true
+ obj.spot_range = submodel.light_range
+ obj.spot_angle = submodel.light_angle
+ obj.spot_attenuation = submodel.light_attenuation
+ obj.distance_fade_begin = submodel.near_attenuation_start
+ lights.append([null, "spotlight", obj])
+
if obj:
obj.visible = submodel.visible
return obj
diff --git a/addons/libmaszyna/materials/maszyna_material.gd b/addons/libmaszyna/materials/maszyna_material.gd
index d7fec71f..54c2df61 100644
--- a/addons/libmaszyna/materials/maszyna_material.gd
+++ b/addons/libmaszyna/materials/maszyna_material.gd
@@ -13,14 +13,14 @@ const WEATHER_RAIN_KEY = "rain"
const WEATHER_SNOW_KEY = "snow"
-const SEASONS_MAP: Dictionary[MaterialManager.Season, String] = {
+var SEASONS_MAP: Dictionary[MaterialManager.Season, String] = {
MaterialManager.Season.SEASON_WINTER: SEASON_WINTER_KEY,
MaterialManager.Season.SEASON_SPRING: SEASON_SPRING_KEY,
MaterialManager.Season.SEASON_SUMMER: SEASON_SUMMER_KEY,
MaterialManager.Season.SEASON_AUTUMN: SEASON_AUTUMN_KEY,
}
-const WEATHER_MAP: Dictionary[MaterialManager.Weather, String] = {
+var WEATHER_MAP: Dictionary[MaterialManager.Weather, String] = {
MaterialManager.Weather.WEATHER_CLEAR: WEATHER_CLEAR_KEY,
MaterialManager.Weather.WEATHER_CLOUDY: WEATHER_CLOUDY_KEY,
MaterialManager.Weather.WEATHER_RAIN: WEATHER_RAIN_KEY,
diff --git a/addons/libmaszyna/materials/material_factory.gd b/addons/libmaszyna/materials/material_factory.gd
index d5c42884..4f8aba1a 100644
--- a/addons/libmaszyna/materials/material_factory.gd
+++ b/addons/libmaszyna/materials/material_factory.gd
@@ -81,8 +81,7 @@ func create(
model_path: String = "",
season: MaterialManager.Season = MaterialManager.Season.SEASON_SUMMER,
weather: MaterialManager.Weather = MaterialManager.Weather.WEATHER_CLEAR,
- force_transparent: bool = false,
- diffuse_color: Color = Color.WHITE
+ options: MaterialManager.MaterialOptions = MaterialManager.MaterialOptions.new(),
) -> Material:
var variant: MaszynaMaterial.MaszynaMaterialVariant = mmat.get_variant(season, weather)
var shader_meta:MaszynaShaderMeta = _get_shader_meta(variant.shader)
@@ -94,8 +93,7 @@ func create(
shader_meta,
shader_meta.texture_map,
model_path,
- force_transparent,
- diffuse_color,
+ options,
)
return material
@@ -106,8 +104,7 @@ func apply(
model_path: String,
season: MaterialManager.Season,
weather: MaterialManager.Weather,
- force_transparent: bool,
- diffuse_color: Color = Color.WHITE
+ options: MaterialManager.MaterialOptions = MaterialManager.MaterialOptions.new(),
) -> void:
var variant: MaszynaMaterial.MaszynaMaterialVariant = mmat.get_variant(season, weather)
var shader_meta:MaszynaShaderMeta = _get_shader_meta(variant.shader)
@@ -118,8 +115,7 @@ func apply(
shader_meta,
shader_meta.texture_map,
model_path,
- force_transparent,
- diffuse_color
+ options,
)
@@ -138,8 +134,7 @@ func _apply(
shader_meta: MaszynaShaderMeta,
texture_map: TextureMap,
model_path: String,
- force_transparent: bool,
- diffuse_color: Color,
+ options: MaterialManager.MaterialOptions,
) -> void:
if not material is ShaderMaterial or not shader_meta.base_material is ShaderMaterial:
@@ -151,12 +146,15 @@ func _apply(
if property_name == "shader" or property_name == "render_priority" or property_name.begins_with("shader_parameter/"):
target_shader_material.set(property_name, source_shader_material.get(property_name))
- shader_meta.factory.call(mmat, variant, material, texture_map, model_path, diffuse_color)
+ shader_meta.factory.call(mmat, variant, material, texture_map, model_path, options)
var transparency: MaterialManager.Transparency = MaterialManager.Transparency.Disabled
- if mmat.transparent or force_transparent:
+ if mmat.transparent or options.force_transparent:
transparency = MaterialManager.Transparency.AlphaScissor
target_shader_material.set_shader_parameter("transparency", transparency)
target_shader_material.set_shader_parameter("alpha_scissor_threshold", 0.5)
+ target_shader_material.set_shader_parameter("emission_enabled", options.selfillum_enabled)
+ target_shader_material.set_shader_parameter("emission_color", options.selfillum_color if options.selfillum_color else Color(1.0, 1.0, 1.0, 1.0))
+ target_shader_material.set_shader_parameter("emission_energy", options.selfillum_energy)
func _apply_default_material(
@@ -165,7 +163,7 @@ func _apply_default_material(
material: ShaderMaterial,
texture_map: TextureMap,
model_path: String,
- diffuse_color: Color,
+ options: MaterialManager.MaterialOptions,
) -> void:
var diffuse_texture: String = variant.get_texture_path(texture_map.albedo)
var normalmap_texture: String = variant.get_texture_path(texture_map.normalmap)
@@ -183,7 +181,7 @@ func _apply_default_material(
1.0
))
else:
- material.set_shader_parameter("albedo", diffuse_color)
+ material.set_shader_parameter("albedo", options.diffuse_color)
if normalmap_texture:
material.set_shader_parameter("texture_normal", MaterialManager.load_texture(model_path, normalmap_texture, true))
@@ -197,6 +195,9 @@ func _apply_default_material(
if variant.has_parameter("reflection"):
material.set_shader_parameter("metallic", variant.get_parameter("reflection"))
+ material.set_shader_parameter("emission_enabled", options.selfillum_enabled)
+ material.set_shader_parameter("emission_color", options.selfillum_color)
+ material.set_shader_parameter("emission_energy", options.selfillum_energy)
func _apply_parallax(
mmat: MaszynaMaterial,
@@ -204,7 +205,7 @@ func _apply_parallax(
material: ShaderMaterial,
texture_map: TextureMap,
model_path: String,
- diffuse_color: Color,
+ options: MaterialManager.MaterialOptions,
) -> void:
var diffuse_texture_path: String = variant.get_texture_path(texture_map.albedo)
var normalmap_texture_path: String = variant.get_texture_path(texture_map.normalmap)
@@ -228,7 +229,7 @@ func _apply_parallax(
var albedo_multiplier:Color = Color(1.0, 1.0, 1.0, 1.0)
if not diffuse_texture_path:
- albedo_multiplier = diffuse_color
+ albedo_multiplier = options.diffuse_color
if variant.has_parameter("diffuse"):
albedo_multiplier = Color(
@@ -267,7 +268,7 @@ func _apply_water(
material: ShaderMaterial,
texture_map: TextureMap,
model_path: String,
- diffuse_color: Color,
+ options: MaterialManager.MaterialOptions,
) -> void:
var diffuse_texture_path: String = variant.get_texture_path(texture_map.albedo)
var normalmap_texture_path: String = variant.get_texture_path(texture_map.normalmap)
diff --git a/addons/libmaszyna/materials/material_manager.gd b/addons/libmaszyna/materials/material_manager.gd
index f0284dce..6fe6a195 100644
--- a/addons/libmaszyna/materials/material_manager.gd
+++ b/addons/libmaszyna/materials/material_manager.gd
@@ -12,11 +12,15 @@ var _managed_materials: Dictionary = {}
enum Transparency { Disabled, Alpha, AlphaScissor }
-const _transparency_codes = {
- Transparency.Disabled: "0",
- Transparency.Alpha: "a",
- Transparency.AlphaScissor: "s",
-}
+
+class MaterialOptions:
+ var diffuse_color: Color = Color.WHITE
+ var selfillum_color: Color = Color.WHITE
+ var selfillum_energy: float = 1.0
+ var selfillum_enabled: bool = false
+ var force_transparent: bool = false # TODO: AphaCut/Alpha modes support
+ var alpha_scissor_threshold: float = 0.5
+
@export var season: Season = Season.SEASON_SUMMER:
set(x):
@@ -41,10 +45,9 @@ func load_material(model_path:String, material_name:String) -> MaszynaMaterial:
func get_material(
model_path:String,
material_path:String,
- transparent:Transparency = Transparency.Disabled,
- diffuse_color: Color = Color(1.0, 1.0, 1.0)
+ options: MaterialOptions = MaterialOptions.new(),
) -> Material:
- var cache_hash: String = _compute_cache_hash(model_path, material_path, transparent, diffuse_color)
+ var cache_hash: String = _compute_cache_hash(model_path, material_path, options)
var managed_material: Dictionary = _managed_materials.get(cache_hash, {})
if managed_material:
var material_ref: WeakRef = managed_material.get("material_ref") as WeakRef
@@ -52,20 +55,19 @@ func get_material(
if material:
return material
_managed_materials.erase(cache_hash)
- var force_transparent := not transparent == Transparency.Disabled # TODO: ALPHA
+ var force_transparent = options.force_transparent # TODO: ALPHA
var output: ShaderMaterial = _materials_cache.get(cache_hash) as ShaderMaterial
if not output:
var mmat: MaszynaMaterial = load_material(model_path, material_path)
- output = MaterialFactory.create(mmat, model_path, season, weather, force_transparent, diffuse_color)
+ output = MaterialFactory.create(mmat, model_path, season, weather, options)
else:
var mmat: MaszynaMaterial = load_material(model_path, material_path)
- MaterialFactory.apply(output, mmat, model_path, season, weather, force_transparent, diffuse_color)
+ MaterialFactory.apply(output, mmat, model_path, season, weather, options)
_managed_materials[cache_hash] = {
"material_ref": weakref(output),
"model_path": model_path,
"material_path": material_path,
- "force_transparent": force_transparent,
- "diffuse_color": diffuse_color,
+ "options": options,
}
_materials_cache.set(cache_hash, output)
return output
@@ -107,14 +109,18 @@ func load_texture(model_path:String, material_name:String, normal:bool = false)
func _compute_cache_hash(
model_path: String,
material_path: String,
- transparent: Transparency,
- diffuse_color: Color,
+ options: MaterialOptions,
) -> String:
- return model_path.path_join(("%s_t%s_%s.res" % [
- material_path,
- _transparency_codes[transparent],
- "%x%x%x" % [diffuse_color.r8, diffuse_color.g8, diffuse_color.b8],
- ]))
+ var options_hash = ":".join([
+ options.force_transparent,
+ options.diffuse_color.to_html(true),
+ options.alpha_scissor_threshold,
+ options.selfillum_enabled,
+ options.selfillum_color.to_html(true),
+ options.selfillum_energy,
+ ].map(str)).md5_text()
+ return model_path.path_join("%s_%s.res" % [material_path, options_hash])
+
func _refresh_managed_materials() -> void:
var cache_hashes: Array = _managed_materials.keys()
@@ -134,7 +140,8 @@ func _refresh_managed_material(cache_hash: String) -> void:
var material_path: String = managed_material.get("material_path", "")
var force_transparent: Transparency = managed_material.get("force_transparent", false)
var diffuse_color: Color = managed_material.get("diffuse_color", Color.WHITE)
+ var options:MaterialOptions = managed_material.get("options")
var mmat: MaszynaMaterial = load_material(model_path, material_path)
mmat.transparent = mmat.transparent or force_transparent
- MaterialFactory.apply(material, mmat, model_path, season, weather, force_transparent, diffuse_color)
+ MaterialFactory.apply(material, mmat, model_path, season, weather, options)
_materials_cache.set(cache_hash, material)
diff --git a/addons/libmaszyna/materials/types/default.gdshader b/addons/libmaszyna/materials/types/default.gdshader
index 3654b7e0..b7ab8337 100644
--- a/addons/libmaszyna/materials/types/default.gdshader
+++ b/addons/libmaszyna/materials/types/default.gdshader
@@ -21,6 +21,10 @@ uniform float metallic : hint_range(0.0, 1.0, 0.01);
uniform sampler2D texture_normal : hint_roughness_normal, filter_linear_mipmap, repeat_enable;
uniform float normal_scale : hint_range(-16.0, 16.0);
+uniform bool emission_enabled = false;
+uniform vec4 emission_color;
+uniform float emission_energy : hint_range(0.0, 16.0);
+
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
@@ -50,4 +54,9 @@ void fragment() {
// Normal Map: Enabled
NORMAL_MAP = texture(texture_normal, base_uv).rgb;
NORMAL_MAP_DEPTH = normal_scale;
+
+ if(emission_enabled) {
+ EMISSION = emission_color.rgb * albedo_tex.rgb * emission_energy;
+ }
+
}
diff --git a/addons/libmaszyna/materials/types/normalmap.gdshader b/addons/libmaszyna/materials/types/normalmap.gdshader
index fe558b7a..99d1943c 100644
--- a/addons/libmaszyna/materials/types/normalmap.gdshader
+++ b/addons/libmaszyna/materials/types/normalmap.gdshader
@@ -21,6 +21,10 @@ uniform float metallic : hint_range(0.0, 1.0, 0.01);
uniform sampler2D texture_normal : hint_roughness_normal, filter_linear_mipmap, repeat_enable;
uniform float normal_scale : hint_range(-16.0, 16.0);
+uniform bool emission_enabled = false;
+uniform vec4 emission_color;
+uniform float emission_energy : hint_range(0.0, 16.0);
+
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
@@ -50,4 +54,10 @@ void fragment() {
// Normal Map: Enabled
NORMAL_MAP = texture(texture_normal, base_uv).rgb;
NORMAL_MAP_DEPTH = normal_scale;
+
+ if(emission_enabled) {
+ EMISSION = emission_color.rgb * albedo_tex.rgb * emission_energy;
+ }
+
+
}
diff --git a/addons/libmaszyna/materials/types/normalmap_specgloss.gdshader b/addons/libmaszyna/materials/types/normalmap_specgloss.gdshader
index 8ca3ca1c..76a73728 100644
--- a/addons/libmaszyna/materials/types/normalmap_specgloss.gdshader
+++ b/addons/libmaszyna/materials/types/normalmap_specgloss.gdshader
@@ -21,6 +21,10 @@ uniform float metallic : hint_range(0.0, 1.0, 0.01);
uniform sampler2D texture_normal : hint_roughness_normal, filter_linear_mipmap, repeat_enable;
uniform float normal_scale : hint_range(-16.0, 16.0);
+uniform bool emission_enabled = false;
+uniform vec4 emission_color;
+uniform float emission_energy : hint_range(0.0, 16.0);
+
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
@@ -50,4 +54,10 @@ void fragment() {
// Normal Map: Enabled
NORMAL_MAP = texture(texture_normal, base_uv).rgb;
NORMAL_MAP_DEPTH = normal_scale;
+
+ if(emission_enabled) {
+ EMISSION = emission_color.rgb * albedo_tex.rgb * emission_energy;
+ }
+
+
}
diff --git a/addons/libmaszyna/materials/types/parallax.gdshader b/addons/libmaszyna/materials/types/parallax.gdshader
index f1c1602d..f392d3d6 100644
--- a/addons/libmaszyna/materials/types/parallax.gdshader
+++ b/addons/libmaszyna/materials/types/parallax.gdshader
@@ -149,5 +149,6 @@ void fragment() {
NORMAL_MAP = decode_normal_from_rg(normal_sample);
NORMAL_MAP_DEPTH = 1.0;
+
}
}
diff --git a/addons/libmaszyna/materials/types/shadowlessnormalmap.gdshader b/addons/libmaszyna/materials/types/shadowlessnormalmap.gdshader
index ccc14383..24c339fe 100644
--- a/addons/libmaszyna/materials/types/shadowlessnormalmap.gdshader
+++ b/addons/libmaszyna/materials/types/shadowlessnormalmap.gdshader
@@ -18,6 +18,11 @@ uniform sampler2D texture_roughness : hint_roughness_r, filter_linear_mipmap, re
uniform float specular : hint_range(0.0, 1.0, 0.01);
uniform float metallic : hint_range(0.0, 1.0, 0.01);
+uniform bool emission_enabled = false;
+uniform vec4 emission_color;
+uniform float emission_energy : hint_range(0.0, 16.0);
+
+
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
@@ -43,4 +48,10 @@ void fragment() {
vec4 roughness_texture_channel = vec4(1.0, 0.0, 0.0, 0.0);
float roughness_tex = dot(texture(texture_roughness, base_uv), roughness_texture_channel);
ROUGHNESS = roughness_tex * roughness;
+
+ if(emission_enabled) {
+ EMISSION = emission_color.rgb * albedo_tex.rgb * emission_energy;
+ }
+
+
}
diff --git a/addons/libmaszyna/materials/types/sunlessnormalmap.gdshader b/addons/libmaszyna/materials/types/sunlessnormalmap.gdshader
index 8ca3ca1c..4f0b2171 100644
--- a/addons/libmaszyna/materials/types/sunlessnormalmap.gdshader
+++ b/addons/libmaszyna/materials/types/sunlessnormalmap.gdshader
@@ -21,6 +21,11 @@ uniform float metallic : hint_range(0.0, 1.0, 0.01);
uniform sampler2D texture_normal : hint_roughness_normal, filter_linear_mipmap, repeat_enable;
uniform float normal_scale : hint_range(-16.0, 16.0);
+uniform bool emission_enabled = false;
+uniform vec4 emission_color;
+uniform float emission_energy : hint_range(0.0, 16.0);
+
+
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
@@ -50,4 +55,10 @@ void fragment() {
// Normal Map: Enabled
NORMAL_MAP = texture(texture_normal, base_uv).rgb;
NORMAL_MAP_DEPTH = normal_scale;
+
+ if(emission_enabled) {
+ EMISSION = emission_color.rgb * albedo_tex.rgb * emission_energy;
+ }
+
+
}
diff --git a/addons/libmaszyna/rail_vehicle_3d.gd b/addons/libmaszyna/rail_vehicle_3d.gd
index b2bb6de4..28aecda3 100644
--- a/addons/libmaszyna/rail_vehicle_3d.gd
+++ b/addons/libmaszyna/rail_vehicle_3d.gd
@@ -2,7 +2,25 @@
extends Node3D
class_name RailVehicle3D
+const EMPTY_LIGHTS:Dictionary[String, bool] = {}
+
# FIXME: Head Display implementation is experimental and only for demo purposes
+@export_node_path("E3DModelInstance") var model_instance_path:NodePath = NodePath(""):
+ set(x):
+ if not x == model_instance_path:
+ model_instance_path = x
+ lights = {}
+ if is_inside_tree() and model_instance_path:
+ _model_node = get_node_or_null(model_instance_path)
+ lights = _model_node.lights_state if _model_node else EMPTY_LIGHTS
+ _dirty = true
+
+@export var lights:Dictionary[String, bool] = EMPTY_LIGHTS:
+ set(x):
+ if not x == lights:
+ lights = x
+ if is_inside_tree() and _model_node:
+ _model_node.lights_state = lights
@export_node_path("TrainController") var controller_path:NodePath = NodePath(""):
set(x):
@@ -40,6 +58,7 @@ var _head_display_e3d:E3DModelInstance
var _cabin:Cabin3D
var _camera:FreeCamera3D
var _controller:TrainController
+var _model_node:E3DModelInstance
var _t:float = 0.0
@@ -163,8 +182,11 @@ func _process(delta):
if _head_display_e3d:
_head_display_e3d.e3d_loaded.connect(func(): _needs_head_display_update = true)
- if controller_path and is_inside_tree():
- _controller = get_node(controller_path)
+ if is_inside_tree():
+ if controller_path:
+ _controller = get_node_or_null(controller_path)
+ if model_instance_path:
+ _model_node = get_node_or_null(model_instance_path)
_t += delta
if _t > 0.25 and _needs_head_display_update:
@@ -184,3 +206,7 @@ func _ready() -> void:
for instance:E3DModelInstance in find_children("", "E3DModelInstance", true, false):
instance.e3d_loaded.connect(_schedule_head_display_update)
+
+ var model_node = get_node_or_null(model_instance_path)
+ if model_node:
+ model_node.lights_state = lights
diff --git a/demo/demo_3d.tscn b/demo/demo_3d.tscn
index b69cbe5f..78861541 100644
--- a/demo/demo_3d.tscn
+++ b/demo/demo_3d.tscn
@@ -25,6 +25,7 @@
[ext_resource type="E3DModel" uid="uid://buxg17oldnjq6" path="res://e3d_models/dzik.e3d" id="21_sa6xs"]
[ext_resource type="Script" uid="uid://do35jm13yiccm" path="res://addons/libmaszyna/traction/maszyna_traction_3d.gd" id="24_6m450"]
[ext_resource type="Script" uid="uid://dotmfqnin31lo" path="res://addons/libmaszyna/traction/maszyna_traction_line.gd" id="25_jchga"]
+[ext_resource type="PackedScene" uid="uid://fv4an7lb63lu" path="res://vehicles/ep09/ep09.tscn" id="26_jchga"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_s18ef"]
shader = ExtResource("2_ydn4r")
@@ -53,10 +54,10 @@ glow_enabled = true
fog_enabled = true
fog_sky_affect = 0.4
volumetric_fog_enabled = true
-volumetric_fog_density = 0.029
-volumetric_fog_anisotropy = 0.9
-volumetric_fog_length = 0.5
-volumetric_fog_detail_spread = 5.598199
+volumetric_fog_density = 0.003
+volumetric_fog_anisotropy = 0.0
+volumetric_fog_length = 6.5
+volumetric_fog_detail_spread = 1.0
volumetric_fog_sky_affect = 0.405
adjustment_enabled = true
@@ -102,7 +103,6 @@ script = ExtResource("1_ut343")
[node name="DebugMenuFPS" parent="." unique_id=28357571 instance=ExtResource("2_qg2pq")]
[node name="TopBar" type="Container" parent="." unique_id=1102380660]
-top_level = true
clip_contents = true
anchors_preset = 10
anchor_right = 1.0
@@ -226,6 +226,7 @@ clip_contents = true
layout_mode = 2
[node name="DeveloperConsole" parent="." unique_id=495569075 instance=ExtResource("2_ig825")]
+visible = null
[node name="UserSettingsPanel" parent="." unique_id=1798306963 instance=ExtResource("12_eihw1")]
@@ -248,7 +249,7 @@ size = Vector3(5000, 0.1, 5000)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=786641912]
transform = Transform3D(-0.846557, 0.01871, 0.531969, 0.393589, 0.694835, 0.601906, -0.358369, 0.718925, -0.595582, 5.83027, -6.14961, 0)
light_color = Color(0.929688, 0.848771, 0.817108, 1)
-light_energy = 1.12
+light_energy = 1.2
light_indirect_energy = 2.0
light_volumetric_fog_energy = 11.811
shadow_enabled = true
@@ -263,6 +264,18 @@ environment = SubResource("Environment_jrns0")
[node name="SM42-099" parent="." unique_id=1125812614 instance=ExtResource("11_8gidn")]
transform = Transform3D(-0.9996828, 0, 0.02518662, 0, 1, 0, -0.02518662, 0, -0.9996828, -25.735577, 0.16, 180.73814)
+lights = Dictionary[String, bool]({
+"endsignal12": false,
+"endsignal13": false,
+"endsignal22": true,
+"endsignal23": true,
+"headlamp11": true,
+"headlamp12": true,
+"headlamp13": true,
+"headlamp21": false,
+"headlamp22": false,
+"headlamp23": false
+})
[node name="Impuls" parent="." unique_id=2111644575 instance=ExtResource("12_hsov8")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.021647027, -0.019251347, -4.3020163)
@@ -1149,5 +1162,8 @@ model_filename = "tra/-3d"
skins = ["tra/betonrelief1"]
metadata/_custom_type_script = "uid://dgifxvb2e4hww"
+[node name="Node3D" parent="." unique_id=1451239513 instance=ExtResource("26_jchga")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -29.282572, -4.7683716e-07, 169.09251)
+
[connection signal="index_pressed" from="TopBar/HBoxContainer/MenuBar/PopupMenu" to="." method="_on_popup_menu_index_pressed"]
[connection signal="toggled" from="TopBar/HBoxContainer/ToggleAllControls" to="." method="_on_show_all_controls_button_toggled"]
diff --git a/demo/project.godot b/demo/project.godot
index 0daf87cb..9ee2d3fd 100644
--- a/demo/project.godot
+++ b/demo/project.godot
@@ -35,13 +35,13 @@ TractionRenderingServer="*res://addons/libmaszyna/servers/traction_rendering_ser
NodebankLibraryBuilder="*uid://dv25nc25tcanw"
MaszynaEnvironment="*uid://3nkx0u4dgdws"
Console="*uid://d046w0soh2iou"
+MaterialManager="*uid://wsbd7kr83ncg"
MaterialParser="*uid://7b48fl428yqa"
+MaterialFactory="*uid://v144f3irjg7m"
E3DModelManager="*uid://b2v733o52i40x"
E3DNodesInstancer="*uid://dl44jll5gimwi"
E3DModelTool="*uid://df7a5f7f35wd1"
AudioStreamManager="*uid://cp8sgfk334adm"
-MaterialManager="*uid://wsbd7kr83ncg"
-MaterialFactory="*uid://v144f3irjg7m"
[debug]
@@ -52,10 +52,6 @@ file_logging/log_path="user://logs/app.log"
window/size/mode=3
window/size/initial_position_type=3
-[editor_overrides]
-
-text_editor/behavior/files/trim_trailing_whitespace_on_save=true
-
[editor_plugins]
enabled=PackedStringArray("res://addons/gut/plugin.cfg", "res://addons/libmaszyna/editor/e3d_toolbar/plugin.cfg", "res://addons/libmaszyna/editor/nodebank/plugin.cfg", "res://addons/libmaszyna/editor/user_settings_dock/plugin.cfg", "res://addons/libmaszyna/plugin.cfg")
diff --git a/demo/vehicles/ep09/ep09.tscn b/demo/vehicles/ep09/ep09.tscn
new file mode 100644
index 00000000..f19ba43e
--- /dev/null
+++ b/demo/vehicles/ep09/ep09.tscn
@@ -0,0 +1,63 @@
+[gd_scene format=3 uid="uid://fv4an7lb63lu"]
+
+[ext_resource type="Script" uid="uid://dgifxvb2e4hww" path="res://addons/libmaszyna/e3d/e3d_model_instance.gd" id="1_1nw4k"]
+
+[node name="Node3D" type="Node3D" unique_id=1451239513]
+
+[node name="ep09_lowpoly_interior" type="VisualInstance3D" parent="." unique_id=742765134]
+unique_name_in_owner = false
+process_mode = 0
+process_priority = 0
+process_physics_priority = 0
+process_thread_group = 0
+physics_interpolation_mode = 0
+auto_translate_mode = 0
+editor_description = ""
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+rotation_edit_mode = 0
+rotation_order = 2
+top_level = false
+visible = true
+visibility_parent = NodePath("")
+layers = 1
+script = ExtResource("1_1nw4k")
+data_path = "/dynamic/pkp/ep09_v1/"
+model_filename = "low_poly_int/lowpoly]elektro"
+skins = ["104e-035", ""]
+exclude_node_names = ["cien"]
+metadata/_custom_type_script = "uid://dgifxvb2e4hww"
+
+[node name="ep09" type="VisualInstance3D" parent="." unique_id=1498765359]
+unique_name_in_owner = false
+process_mode = 0
+process_priority = 0
+process_physics_priority = 0
+process_thread_group = 0
+physics_interpolation_mode = 0
+auto_translate_mode = 0
+editor_description = ""
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0)
+rotation_edit_mode = 0
+rotation_order = 2
+top_level = false
+visible = true
+visibility_parent = NodePath("")
+layers = 1
+script = ExtResource("1_1nw4k")
+lights_state = Dictionary[String, bool]({
+"endsignal12": true,
+"endsignal13": true,
+"endsignal22": false,
+"endsignal23": false,
+"headlamp11": false,
+"headlamp12": false,
+"headlamp13": false,
+"headlamp21": true,
+"headlamp22": true,
+"headlamp23": true
+})
+data_path = "/dynamic/pkp/ep09_v1/"
+model_filename = "104e_6"
+skins = ["104e-035", ""]
+exclude_node_names = ["cien"]
+metadata/_custom_type_script = "uid://dgifxvb2e4hww"
diff --git a/demo/vehicles/impuls/impuls.tscn b/demo/vehicles/impuls/impuls.tscn
index 7419e2a3..a83c6086 100644
--- a/demo/vehicles/impuls/impuls.tscn
+++ b/demo/vehicles/impuls/impuls.tscn
@@ -39,6 +39,18 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("1_pes3o")
+lights_state = Dictionary[String, bool]({
+"endsignal12": false,
+"endsignal13": false,
+"endsignal22": false,
+"endsignal23": false,
+"headlamp11": true,
+"headlamp12": true,
+"headlamp13": true,
+"headlamp21": false,
+"headlamp22": false,
+"headlamp23": false
+})
data_path = "/dynamic/pkp/impuls_v1"
model_filename = "main/36wea-a_kd"
skins = ["36wea-019a,1", "36wea-019a,2", "36wea-019a,3", "36wea-019a,4"]
@@ -242,6 +254,18 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("1_pes3o")
+lights_state = Dictionary[String, bool]({
+"endsignal12": false,
+"endsignal13": false,
+"endsignal22": true,
+"endsignal23": true,
+"headlamp11": false,
+"headlamp12": false,
+"headlamp13": false,
+"headlamp21": false,
+"headlamp22": false,
+"headlamp23": false
+})
data_path = "/dynamic/pkp/impuls_v1"
model_filename = "main/31wea-d_kd"
skins = ["36wea-019a,1", "36wea-019a,2", "36wea-019a,3", "36wea-019a,4"]
diff --git a/demo/vehicles/sm42/sm_42.tscn b/demo/vehicles/sm42/sm_42.tscn
index 8b852858..8b28d7c2 100644
--- a/demo/vehicles/sm42/sm_42.tscn
+++ b/demo/vehicles/sm42/sm_42.tscn
@@ -11,6 +11,7 @@ size = Vector3(4.5332, 4.72223, 17.0549)
[node name="SM42-099" type="Node3D" unique_id=1967816975]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
script = ExtResource("1_iiqbf")
+model_instance_path = NodePath("SM42-099")
controller_path = NodePath("SM42v1")
cabin_scene = ExtResource("2_htu7b")
low_poly_cabin_path = NodePath("SM42-099-LowPolyCab")
@@ -34,6 +35,18 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("4_c88oy")
+lights_state = Dictionary[String, bool]({
+"endsignal12": false,
+"endsignal13": false,
+"endsignal22": false,
+"endsignal23": false,
+"headlamp11": true,
+"headlamp12": true,
+"headlamp13": true,
+"headlamp21": false,
+"headlamp22": false,
+"headlamp23": false
+})
data_path = "/dynamic/pkp/sm42_v1"
model_filename = "6da"
skins = ["6d-907"]
@@ -85,6 +98,10 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("4_c88oy")
+lights_state = Dictionary[String, bool]({
+"EndTab1": false,
+"EndTab2": false
+})
data_path = "/dynamic/pkp/401w_v2"
model_filename = "main/401w"
skins = ["5217,1", "5217,2"]
@@ -107,6 +124,10 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("4_c88oy")
+lights_state = Dictionary[String, bool]({
+"EndTab1": false,
+"EndTab2": false
+})
data_path = "/dynamic/pkp/401w_v2"
model_filename = "main/401w"
skins = ["5217,1", "5217,2"]
@@ -129,6 +150,10 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("4_c88oy")
+lights_state = Dictionary[String, bool]({
+"EndTab1": false,
+"EndTab2": false
+})
data_path = "/dynamic/pkp/401w_v2"
model_filename = "main/401w"
skins = ["5217,1", "5217,2"]
@@ -151,6 +176,10 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("4_c88oy")
+lights_state = Dictionary[String, bool]({
+"EndTab1": false,
+"EndTab2": false
+})
data_path = "/dynamic/pkp/401w_v2"
model_filename = "main/401w"
skins = ["5217,1", "5217,2"]
@@ -173,6 +202,10 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("4_c88oy")
+lights_state = Dictionary[String, bool]({
+"EndTab1": false,
+"EndTab2": false
+})
data_path = "/dynamic/pkp/401w_v2"
model_filename = "main/401w"
skins = ["5217,1", "5217,2"]
diff --git a/demo/vehicles/tem2/tem2.tscn b/demo/vehicles/tem2/tem2.tscn
index 2f48d8f5..6dc4dc65 100644
--- a/demo/vehicles/tem2/tem2.tscn
+++ b/demo/vehicles/tem2/tem2.tscn
@@ -153,6 +153,18 @@ visible = true
visibility_parent = NodePath("")
layers = 1
script = ExtResource("2_q4wpc")
+lights_state = Dictionary[String, bool]({
+"endsignal12": true,
+"endsignal13": true,
+"endsignal22": false,
+"endsignal23": false,
+"headlamp11": false,
+"headlamp12": false,
+"headlamp13": false,
+"headlamp21": true,
+"headlamp22": true,
+"headlamp23": true
+})
data_path = "/dynamic/pkp/tem2_v2"
model_filename = "tem2-122a"
skins = ["tem2-122"]
diff --git a/doc_classes/E3DSubModel.xml b/doc_classes/E3DSubModel.xml
index c37ab2e5..a0961027 100644
--- a/doc_classes/E3DSubModel.xml
+++ b/doc_classes/E3DSubModel.xml
@@ -21,6 +21,33 @@
The index mapping for the dynamic material state.
+
+ The cone angle of the spotlight in degrees.
+
+
+ The attenuation factor of the light source.
+
+
+ The maximum range of the light source.
+
+
+ Distance where the near attenuation starts (used for halo/aureola effects).
+
+
+ Distance where the near attenuation ends.
+
+
+ If [code]true[/code], near attenuation (halo effects) is enabled for this light source.
+
+
+ The type of distance attenuation (0: none, 1 or 2: inverse distance powers).
+
+
+ Cosine of the inner hotspot cone angle.
+
+
+ Cosine of the current viewing angle (used for visibility calculation).
+
The ambient light level threshold at which the submodel's self-illumination becomes active.
diff --git a/src/e3d/E3DModel.cpp b/src/e3d/E3DModel.cpp
index 57ba4385..fbd77b39 100644
--- a/src/e3d/E3DModel.cpp
+++ b/src/e3d/E3DModel.cpp
@@ -6,13 +6,8 @@ namespace godot {
}
void E3DModel::clear() {
- for (int i = 0; i < submodels.size(); i++) {
- Ref sm = submodels.get(i);
- if (sm.is_valid()) {
- sm->clear();
- }
- }
submodels.clear();
+ lights.clear();
}
void E3DModel::_bind_methods() {
@@ -20,6 +15,13 @@ namespace godot {
BIND_PROPERTY_W_HINT_RES_ARRAY(
Variant::ARRAY, "submodels", "submodels", &E3DModel::set_submodels, &E3DModel::get_submodels,
"p_submodels", PROPERTY_HINT_ARRAY_TYPE, "E3DSubModel");
+ BIND_PROPERTY(
+ Variant::DICTIONARY, "lights", "lights", &E3DModel::set_lights, &E3DModel::get_lights, "p_lights");
+ ClassDB::bind_method(D_METHOD("register_light", "p_name", "p_entry"), &E3DModel::register_light);
+ }
+
+ void E3DModel::register_light(const String &p_name, const Ref &p_entry) {
+ lights[p_name] = p_entry;
}
void E3DModel::add_child(const Ref &p_sub_model) {
diff --git a/src/e3d/E3DModel.hpp b/src/e3d/E3DModel.hpp
index e6919e7d..b910edd9 100644
--- a/src/e3d/E3DModel.hpp
+++ b/src/e3d/E3DModel.hpp
@@ -1,21 +1,25 @@
#pragma once
+#include "E3DModelLightDefinition.hpp"
#include "E3DSubModel.hpp"
#include
+#include
#include
namespace godot {
class E3DModel : public Resource {
GDCLASS(E3DModel, Resource)
public:
- static constexpr int FORMAT_VERSION = 20260524; // must be incremented when public API of E3DModel or
+ static constexpr int FORMAT_VERSION = 20260526; // must be incremented when public API of E3DModel or
// E3DSubModel is changing
~E3DModel() override;
protected:
static void _bind_methods();
MAKE_MEMBER_GS_NR_NO_DEF(TypedArray, submodels);
+ MAKE_MEMBER_GS_NR_NO_DEF(Dictionary, lights);
void add_child(const Ref &p_sub_model);
+ void register_light(const String &p_name, const Ref &p_entry);
void clear();
};
} // namespace godot
diff --git a/src/e3d/E3DModelLightDefinition.cpp b/src/e3d/E3DModelLightDefinition.cpp
new file mode 100644
index 00000000..f9494b43
--- /dev/null
+++ b/src/e3d/E3DModelLightDefinition.cpp
@@ -0,0 +1,18 @@
+#include "E3DModelLightDefinition.hpp"
+
+namespace godot {
+ void E3DModelLightDefinition::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_on_submodel", "p_model"), &E3DModelLightDefinition::set_on_submodel);
+ ClassDB::bind_method(D_METHOD("get_on_submodel"), &E3DModelLightDefinition::get_on_submodel);
+ ADD_PROPERTY(
+ PropertyInfo(Variant::OBJECT, "on_submodel", PROPERTY_HINT_RESOURCE_TYPE, "E3DSubModel"),
+ "set_on_submodel", "get_on_submodel");
+
+ ClassDB::bind_method(D_METHOD("set_off_submodel", "p_model"), &E3DModelLightDefinition::set_off_submodel);
+ ClassDB::bind_method(D_METHOD("get_off_submodel"), &E3DModelLightDefinition::get_off_submodel);
+ ADD_PROPERTY(
+ PropertyInfo(Variant::OBJECT, "off_submodel", PROPERTY_HINT_RESOURCE_TYPE, "E3DSubModel"),
+ "set_off_submodel", "get_off_submodel");
+ }
+
+} // namespace godot
diff --git a/src/e3d/E3DModelLightDefinition.hpp b/src/e3d/E3DModelLightDefinition.hpp
new file mode 100644
index 00000000..0afddf8a
--- /dev/null
+++ b/src/e3d/E3DModelLightDefinition.hpp
@@ -0,0 +1,17 @@
+#pragma once
+#include
+#include
+
+#include "E3DSubModel.hpp"
+#include "macros.hpp"
+
+namespace godot {
+ class E3DModelLightDefinition : public Resource {
+ GDCLASS(E3DModelLightDefinition, Resource)
+
+ MAKE_MEMBER_GS_NR_NO_DEF(Ref, on_submodel)
+ MAKE_MEMBER_GS_NR_NO_DEF(Ref, off_submodel)
+ protected:
+ static void _bind_methods();
+ };
+} // namespace godot
diff --git a/src/e3d/E3DSubModel.cpp b/src/e3d/E3DSubModel.cpp
index 08ab028c..ddbcd582 100644
--- a/src/e3d/E3DSubModel.cpp
+++ b/src/e3d/E3DSubModel.cpp
@@ -6,12 +6,6 @@ namespace godot {
}
void E3DSubModel::clear() {
- for (int i = 0; i < submodels.size(); i++) {
- Ref sm = submodels.get(i);
- if (sm.is_valid()) {
- sm->clear();
- }
- }
submodels.clear();
mesh.unref();
parent = nullptr;
@@ -128,6 +122,38 @@ namespace godot {
BIND_PROPERTY(
Variant::BOOL, "skip_rendering", "skip_rendering", &E3DSubModel::set_skip_rendering,
&E3DSubModel::get_skip_rendering, "p_skip_rendering");
+
+ BIND_PROPERTY(
+ Variant::FLOAT, "light_range", "light_range", &E3DSubModel::set_light_range,
+ &E3DSubModel::get_light_range, "p_light_range");
+ BIND_PROPERTY(
+ Variant::FLOAT, "light_attenuation", "light_attenuation", &E3DSubModel::set_light_attenuation,
+ &E3DSubModel::get_light_attenuation, "p_light_attenuation");
+ BIND_PROPERTY(
+ Variant::FLOAT, "light_angle", "light_angle", &E3DSubModel::set_light_angle,
+ &E3DSubModel::get_light_angle, "p_light_angle");
+ BIND_PROPERTY(
+ Variant::FLOAT, "near_attenuation_start", "near_attenuation_start",
+ &E3DSubModel::set_near_attenuation_start, &E3DSubModel::get_near_attenuation_start,
+ "p_near_attenuation_start");
+ BIND_PROPERTY(
+ Variant::FLOAT, "near_attenuation_end", "near_attenuation_end", &E3DSubModel::set_near_attenuation_end,
+ &E3DSubModel::get_near_attenuation_end, "p_near_attenuation_end");
+ BIND_PROPERTY(
+ Variant::BOOL, "use_near_attenuation", "use_near_attenuation", &E3DSubModel::set_use_near_attenuation,
+ &E3DSubModel::get_use_near_attenuation, "p_use_near_attenuation");
+ BIND_PROPERTY(
+ Variant::INT, "far_attenuation_decay", "far_attenuation_decay", &E3DSubModel::set_far_attenuation_decay,
+ &E3DSubModel::get_far_attenuation_decay, "p_far_attenuation_decay");
+ BIND_PROPERTY(
+ Variant::FLOAT, "cos_hotspot_angle", "cos_hotspot_angle", &E3DSubModel::set_cos_hotspot_angle,
+ &E3DSubModel::get_cos_hotspot_angle, "p_cos_hotspot_angle");
+ BIND_PROPERTY(
+ Variant::FLOAT, "cos_view_angle", "cos_view_angle", &E3DSubModel::set_cos_view_angle,
+ &E3DSubModel::get_cos_view_angle, "p_cos_view_angle");
+ BIND_PROPERTY(
+ Variant::FLOAT, "light_energy", "light_energy", &E3DSubModel::set_light_energy,
+ &E3DSubModel::get_light_energy, "p_light_energy");
}
void E3DSubModel::add_child(const Ref &p_sub_model) {
diff --git a/src/e3d/E3DSubModel.hpp b/src/e3d/E3DSubModel.hpp
index aff061e4..b82db709 100644
--- a/src/e3d/E3DSubModel.hpp
+++ b/src/e3d/E3DSubModel.hpp
@@ -80,6 +80,17 @@ namespace godot {
MAKE_MEMBER_GS_NR(bool, visible, true)
MAKE_MEMBER_GS_NR(bool, skip_rendering, false)
+ MAKE_MEMBER_GS_NR(float, light_range, 0.0)
+ MAKE_MEMBER_GS_NR(float, light_attenuation, 1.0)
+ MAKE_MEMBER_GS_NR(float, light_angle, 45.0)
+ MAKE_MEMBER_GS_NR(float, near_attenuation_start, 0.0)
+ MAKE_MEMBER_GS_NR(float, near_attenuation_end, 0.0)
+ MAKE_MEMBER_GS_NR(bool, use_near_attenuation, false)
+ MAKE_MEMBER_GS_NR(int, far_attenuation_decay, 0)
+ MAKE_MEMBER_GS_NR(float, cos_hotspot_angle, 0.0)
+ MAKE_MEMBER_GS_NR(float, cos_view_angle, 0.0)
+ MAKE_MEMBER_GS_NR(float, light_energy, 0.0)
+
void add_child(const Ref &p_sub_model);
void set_parent(E3DSubModel *p_sub_model);
void clear();
diff --git a/src/lighting/TrainLighting.cpp b/src/lighting/TrainLighting.cpp
index d215d61f..1e941a2d 100644
--- a/src/lighting/TrainLighting.cpp
+++ b/src/lighting/TrainLighting.cpp
@@ -32,7 +32,7 @@ namespace godot {
BIND_PROPERTY_W_HINT_RES_ARRAY(
Variant::ARRAY, "light_position_list", "lights/list", &TrainLighting::set_light_position_list,
&TrainLighting::get_light_position_list, "light_position_list", PROPERTY_HINT_TYPE_STRING,
- "LighListItem");
+ "LightListItem");
BIND_PROPERTY_W_HINT(
Variant::INT, "light_source", "light/source", &TrainLighting::set_light_source,
&TrainLighting::get_light_source, "source", PROPERTY_HINT_ENUM,
diff --git a/src/parsers/e3d_parser.cpp b/src/parsers/e3d_parser.cpp
index 964b8912..8868468f 100644
--- a/src/parsers/e3d_parser.cpp
+++ b/src/parsers/e3d_parser.cpp
@@ -1,13 +1,20 @@
+#include "e3d/E3DModelLightDefinition.hpp"
#include "parsers/e3d_parser.hpp"
#include
#include
#include
#include