Base scene + player

This commit is contained in:
Vojtěch Struhár 2025-06-18 12:59:24 +02:00
commit 7243d74d01
1021 changed files with 73514 additions and 0 deletions

4
.editorconfig Normal file
View File

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

61
addons/portals/README.md Normal file
View File

@ -0,0 +1,61 @@
# Portals 3D
This plugin enables you to easily create seamless plugins.
## Documentation
For documentation about `Portal3D`, see the portal script itself. Everything is properly documented
and viewable in the default documentation window. Go to the script editor, click _Search Help_ in
the top bar and search for "Portal3D".
For everything else, there is this README.
## Guides
### Customize portals in the editor
The portal mesh has a custom shader material assigned to it at runtime (defined in
`materials/portal_shader.gdshader`), but in editor, it uses a regular material -- find it at
`materials/editor-preview-portal-material.tres`. You can edit this material to customize how
portals look in the editor (in case the default gray color blends in too much).
### Smooth teleportation
The Portal3D script provides a mechanism for smooth teleportation. In order to be able to create
smooth portal transitions, you need to put a clipping shader onto all meshes that are supposed to
participate in the smooth teleportation.
**How to convert a regular mesh to a clippable one?** Like this:
1. On your material, click the downward arrow menu and select _Convert to ShaderMaterial_
2. Include the shader macros and use them to inject clipping uniforms, the vertex logic
and the fragment logic.
```c
shader_type spatial;
// ...
#include "res://addons/portals/materials/portalclip_mesh.gdshaderinc"
PORTALCLIP_UNIFORMS
void vertex() {
// ...
PORTALCLIP_VERTEX
}
void fragment() {
// ...
PORTALCLIP_FRAGMENT
}
```
And that's it! Now look for `DUPLICATE_MESHES_CALLBACK` in the Portal3D script, you are ready to
get going with smooth teleportation!
## Gizmos
This plugin includes couple of custom gizmos. One gives a connected portal an outline and the
second one visualizes portal's front direction. You can configure the color of both gizmos in
_Project Settings / Addons / Portals_ or turn them off altogether.

View File

@ -0,0 +1,58 @@
extends EditorNode3DGizmoPlugin
func _init() -> void:
var exit_outline_color = PortalSettings.get_setting("gizmo_exit_outline_color")
create_material("outline", exit_outline_color, false, true, false)
func _get_gizmo_name() -> String:
return "PortalExitOutlineGizmo"
func _has_gizmo(for_node_3d: Node3D) -> bool:
return for_node_3d is Portal3D
func _redraw(gizmo: EditorNode3DGizmo) -> void:
var portal = gizmo.get_node_3d() as Portal3D
assert(portal != null, "This gizmo works only for Portal3D")
gizmo.clear()
if portal not in EditorInterface.get_selection().get_selected_nodes():
return
var ep: Portal3D = portal.exit_portal
if ep == null:
return
var extents = Vector3(ep.portal_size.x, ep.portal_size.y, ep._portal_thickness) / 2
var lines: Array[Vector3] = [
# Front rect
extents, extents * Vector3(1, -1, 1),
extents, extents * Vector3(-1, 1, 1),
extents * Vector3(1, -1, 1), extents * Vector3(-1, -1, 1),
extents * Vector3(-1, 1, 1), extents * Vector3(-1, -1, 1),
# Back rect
- extents, -extents * Vector3(1, -1, 1),
- extents, -extents * Vector3(-1, 1, 1),
- extents * Vector3(1, -1, 1), -extents * Vector3(-1, -1, 1),
- extents * Vector3(-1, 1, 1), -extents * Vector3(-1, -1, 1),
# Short Z connections
extents * Vector3(1, 1, 1), extents * Vector3(1, 1, -1),
extents * Vector3(1, -1, 1), extents * Vector3(1, -1, -1),
extents * Vector3(-1, 1, 1), extents * Vector3(-1, 1, -1),
extents * Vector3(-1, -1, 1), extents * Vector3(-1, -1, -1),
]
# Double each line for visual thickness
#for i in range(lines.size()):
#lines.append(lines[i] + (lines[i].normalized() * 0.005))
for i in range(lines.size()):
lines[i] = portal.to_local(ep.to_global(lines[i]))
gizmo.add_lines(
PackedVector3Array(lines),
get_material("outline", gizmo)
)

View File

@ -0,0 +1 @@
uid://pk5ua52g54m1

View File

@ -0,0 +1,39 @@
extends EditorNode3DGizmoPlugin
func _init() -> void:
var forward_color = PortalSettings.get_setting("gizmo_forward_color")
create_material("forward", forward_color, false, false, false)
func _get_gizmo_name() -> String:
return "PortalForwardDirectionGizmo"
func _has_gizmo(for_node_3d: Node3D) -> bool:
return for_node_3d is Portal3D
func _redraw(gizmo: EditorNode3DGizmo) -> void:
var portal = gizmo.get_node_3d() as Portal3D
assert(portal != null, "This gizmo works only for Portal3D")
var active: bool = portal in EditorInterface.get_selection().get_selected_nodes()
gizmo.clear()
var lines: Array[Vector3] = [
Vector3.ZERO, Vector3(0, 0, 1)
]
if active:
var arrow_spread = 0.05
lines.append_array([
Vector3(0, 0, 1), Vector3(arrow_spread, -arrow_spread, 0.9),
Vector3(0, 0, 1), Vector3(-arrow_spread, arrow_spread, 0.9),
])
var offset = 0.005
for i in range(lines.size()):
var p = lines[i]
lines.append(Vector3(p.x + offset, p.y + offset, p.z))
gizmo.add_lines(
PackedVector3Array(lines),
get_material("forward", gizmo)
)

View File

@ -0,0 +1 @@
uid://cacoywhcpn4ja

View File

@ -0,0 +1,5 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://dcfkcyddxkglf"]
[resource]
cull_mode = 2
albedo_color = Color(0.849621, 0.849621, 0.849621, 1)

View File

@ -0,0 +1 @@
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" viewBox="0 0 67 67" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#fc7f7f" stroke-width="8.33"><path d="m4.167 33.333h37.5l-16.667-16.666m16.667 16.666-16.667 16.667"/><path d="m41.667 58.333h16.666v-50h-16.666" stroke-linecap="round"/></g></svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ct62bsuel5hyc"
path="res://.godot/imported/portal3d-icon.svg-a34538e0e6bdf86bd50ffe0190100a72.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/portals/materials/portal3d-icon.svg"
dest_files=["res://.godot/imported/portal3d-icon.svg-a34538e0e6bdf86bd50ffe0190100a72.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,16 @@
shader_type spatial;
render_mode unshaded;
uniform sampler2D albedo: hint_default_black, source_color;
void fragment() {
// The portal color is simply the screen-space color of the exit camera render target.
// This is because the exit camera views the exit portal from the perspective of the player watching
// the entrance portal, meaning the exit portal will occupy the same screen-space as the entrance portal.
vec3 portal_color = texture(albedo, SCREEN_UV).rgb;
ALBEDO = portal_color;
// FOR DEBUG - make portals slightly red
// ALBEDO = mix(portal_color, vec3(1, 0, 0), 0.1);
}

View File

@ -0,0 +1 @@
uid://bhdb2skdxehes

View File

@ -0,0 +1,11 @@
#define PORTALCLIP_UNIFORMS \
instance uniform bool portal_clip_active = false;\
instance uniform vec3 portal_clip_point = vec3(0, 0, 0);\
instance uniform vec3 portal_clip_normal = vec3(0, 1, 0);\
varying vec3 portal_clip_vertex_position;\
#define PORTALCLIP_VERTEX \
portal_clip_vertex_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;\
#define PORTALCLIP_FRAGMENT \
if (portal_clip_active && dot(portal_clip_vertex_position - portal_clip_point, portal_clip_normal) < 0.0) ALPHA = 0.0;

View File

@ -0,0 +1 @@
uid://cpxsita6rqndc

View File

@ -0,0 +1,7 @@
[plugin]
name="Portals 3D"
description="Seamless portals plugin in 3D"
author="Vojtech Struhar"
version="1.0"
script="plugin.gd"

39
addons/portals/plugin.gd Normal file
View File

@ -0,0 +1,39 @@
@tool
extends EditorPlugin
const ExitOutlinesGizmo = preload("uid://pk5ua52g54m1") # gizmos/portal_exit_outline.gd
var exit_outline_gizmo
const ForwardDirGizmo = preload("uid://cacoywhcpn4ja") # gizmos/portal_forward_direction.gd
var forward_dir_gizmo
func _enter_tree() -> void:
PortalSettings.init_setting("gizmo_exit_outline_active", true, true)
PortalSettings.add_info(AtExport.bool_("gizmo_exit_outline_active"))
PortalSettings.init_setting("gizmo_exit_outline_color", Color.DEEP_SKY_BLUE, true)
PortalSettings.add_info(AtExport.color_no_alpha("gizmo_exit_outline_color"))
PortalSettings.init_setting("gizmo_forward_active", true, true)
PortalSettings.add_info(AtExport.bool_("gizmo_forward_active"))
PortalSettings.init_setting("gizmo_forward_color", Color.HOT_PINK, true)
PortalSettings.add_info(AtExport.color_no_alpha("gizmo_forward_color"))
PortalSettings.init_setting("portals_group_name", "portals")
PortalSettings.add_info(AtExport.string("portals_group_name"))
if PortalSettings.get_setting("gizmo_exit_outline_active"):
exit_outline_gizmo = ExitOutlinesGizmo.new()
add_node_3d_gizmo_plugin(exit_outline_gizmo)
if PortalSettings.get_setting("gizmo_forward_active"):
forward_dir_gizmo = ForwardDirGizmo.new()
add_node_3d_gizmo_plugin(forward_dir_gizmo)
func _exit_tree() -> void:
if exit_outline_gizmo:
remove_node_3d_gizmo_plugin(exit_outline_gizmo)
if forward_dir_gizmo:
remove_node_3d_gizmo_plugin(forward_dir_gizmo)

View File

@ -0,0 +1 @@
uid://ev8ej7qedyih

View File

@ -0,0 +1,171 @@
class_name AtExport extends Object
## Helper class for defining custom export inspector.
##
## Intended usage is when using [method Object._get_property_list] to define a custom editor
## inspector. The list not exhaustive, as I didn't need every single export annotation. [br]
## [codeblock]
## @export var foo: int = 0
## [/codeblock]
## becomes
## [codeblock]
## var foo: int = 0
##
## func _get_property_list() -> void:
## return [
## AtExport.int_("health")
## ]
## [/codeblock]
## Coincidentally, the dictionaries used to register [ProjectSettings] are very similar,
## too.
static func _base(propname: String, type: int) -> Dictionary:
return {
"name": propname,
"type": type,
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE
}
## Replacement for [annotation @GDScript.@export_tool_button]
static func button(propname: String, button_text: String, button_icon: String = "Callable") -> Dictionary:
var result := _base(propname, TYPE_CALLABLE)
assert(not button_text.contains(","), "Button text cannot contain a comma")
result["hint"] = PROPERTY_HINT_TOOL_BUTTON
result["hint_string"] = button_text + "," + button_icon
return result
## [annotation @GDScript.@export] bool variables
static func bool_(propname: String) -> Dictionary:
return _base(propname, TYPE_BOOL)
## [annotation @GDScript.@export] [Color] variables
static func color(propname: String) -> Dictionary:
return _base(propname, TYPE_COLOR)
## Replacement for [annotation @GDScript.@export_color_no_alpha]
static func color_no_alpha(propname: String) -> Dictionary:
var result := _base(propname, TYPE_COLOR)
result["hint"] = PROPERTY_HINT_COLOR_NO_ALPHA
return result
## Exporting an enum variable.[br]Example:
## [codeblock]
## var view_direction: ViewDirection
## # ...
## AtExport.enum_("view_direction", &"Portal3D.ViewDirection", ViewDirection)
## [/codeblock]
static func enum_(propname: String, parent_and_enum: StringName, enum_class: Variant) -> Dictionary:
var result := int_(propname)
result["class_name"] = parent_and_enum
result["hint"] = PROPERTY_HINT_ENUM
result["hint_string"] = ",".join(enum_class.keys())
result["usage"] |= PROPERTY_USAGE_CLASS_IS_ENUM
return result
## [annotation @GDScript.@export] float variables
static func float_(propname: String) -> Dictionary:
return _base(propname, TYPE_FLOAT)
## Replacement for [annotation @GDScript.@export_range] with float variables.
## Also see [method int_range]
static func float_range(propname: String, min: float, max: float, step: float = 0.01, extra_hints: Array[String] = []) -> Dictionary:
var result := float_(propname)
var hint_string = "%f,%f,%f" % [min, max, step]
if extra_hints.size() > 0:
for h in extra_hints:
hint_string += ("," + h)
result["hint"] = PROPERTY_HINT_RANGE
result["hint_string"] = hint_string
return result
## [annotation @GDScript.@export] integer variables
static func int_(propname: String) -> Dictionary:
return _base(propname, TYPE_INT)
## Replacement for [annotation @GDScript.@export_flags]
static func int_flags(propname: String, options: Array) -> Dictionary:
var result := int_(propname)
result["hint"] = PROPERTY_HINT_FLAGS
result["hint_string"] = ",".join(options)
return result
## Replacement for [annotation @GDScript.@export_flags_3d_physics]
static func int_physics_3d(propname: String) -> Dictionary:
var result := int_(propname)
result["hint"] = PROPERTY_HINT_LAYERS_3D_PHYSICS
return result
## Replacement for [annotation @GDScript.@export_range] with integer variables.
## Also see [method float_range]
static func int_range(propname: String, min: int, max: int, step: int = 1, extra_hints: Array[String] = []) -> Dictionary:
var result := float_range(propname, min, max, step, extra_hints)
result["type"] = TYPE_INT
return result
## Replacement for [annotation @GDScript.@export_flags_3d_render]
static func int_render_3d(propname: String) -> Dictionary:
var result := int_(propname)
result["hint"] = PROPERTY_HINT_LAYERS_3D_RENDER
return result
## Replacement for [annotation @GDScript.@export_group].
static func group(group_name: String, prefix: String = "") -> Dictionary:
var result := _base(group_name, TYPE_NIL)
# Overwrite the usage!
result["usage"] = PROPERTY_USAGE_GROUP
result["hint_string"] = prefix
return result
## Close the group that began with [method group]. If you've supplied a prefix to [method group],
## it should close itself.
static func group_end() -> Dictionary:
return group("")
## [annotation @GDScript.@export] NodePath variables. Variables of [i]node type[/i] also only store
## [NodePath]s.
## [codeblock]
## var mesh: MeshInstance3D
## # inside _get_property_list
## AtExport.node("mesh", "MeshInstance3D")
## [/codeblock]
static func node(propname: String, node_class: StringName) -> Dictionary:
var result = _base(propname, TYPE_OBJECT)
result["hint"] = PROPERTY_HINT_NODE_TYPE
result["class_name"] = node_class
result["hint_string"] = node_class
return result
## [annotation @GDScript.@export] for [String] variables
static func string(propname: String) -> Dictionary:
return _base(propname, TYPE_STRING)
## Replacement for [annotation @GDScript.@export_subgroup]. Only works when nested inside
## [method group].
static func subgroup(subgroup_name: String, prefix: String = "") -> Dictionary:
var result := _base(subgroup_name, TYPE_NIL)
# Overwrite the usage!
result["usage"] = PROPERTY_USAGE_SUBGROUP
result["hint_string"] = prefix
return result
## Closes a subgroup created with [method subgroup]. Also see [method group_end]
static func subgroup_end() -> Dictionary:
return subgroup("")
## [annotation @GDScript.@export] for [Vector2] variables
static func vector2(propname: String) -> Dictionary:
return _base(propname, TYPE_VECTOR2)
## [annotation @GDScript.@export] for [Vector3] variables
static func vector3(propname: String) -> Dictionary:
return _base(propname, TYPE_VECTOR3)

View File

@ -0,0 +1 @@
uid://d2ufiv5n1dcdr

View File

@ -0,0 +1,967 @@
@tool
@icon("uid://ct62bsuel5hyc")
class_name Portal3D extends Node3D
## Seamless 3D portal
##
## To get started, create two Portal3D instances and set their [member exit_portal] to each other.
## This creates a linked portal pair that you can look through. Make your player to collide with
## [member teleport_collision_mask] and you will be able to walk back and forth through the portal.
## [br][br]
## To integrate portals into your game, you can make use of the [signal on_teleport] and
## [signal on_teleport_receive] signals. You can link a portal a different one by chaning its
## [member exit_portal] during gameplay. The next level is to make use of the portal's callbacks,
## mainly the [member ON_TELEPORT_CALLBACK]. If you need to raycast through a portal, then the
## [method forward_raycast] method might come in handy! When it comes to optimization, you can use
## the [method activate] and [method deactivate] methods to control which portals are consuming
## resources.
## [br][br]
## [b]TIP:[/b] If you change the default value of some property, it will not get synchronized into existing
## portal instances due to how Godot handles custom inspectors. For easier defaults management,
## I recommend creating a scene with Portal3D as a root and re-using that.
#region Public API
## Emitted when this portal teleports something. Also see [signal on_teleport_receive]
signal on_teleport(node: Node3D)
## Emitted when this portal [i]receives[/i] a teleported node. Whoever had [b]this[/b] portal as
## its [member exit_portal] triggered a teleport!
signal on_teleport_receive(node: Node3D)
## Activates the portal, making it visible and teleporting again. THe assumption is that it was
## previously deactivated by [method deactivate] or [member start_deactivated]. Recreates internal
## viewports if needed.
func activate() -> void:
process_mode = Node.PROCESS_MODE_INHERIT
if portal_viewport == null:
# Viewports have been destroyed
_setup_cameras()
show()
## Disables all processing (this includes teleportation) and hides the portal. Optionally destroys
## the viewports, freeing up memory. [br][br]
## Setting [member start_deactivated] to [code]true[/code] avoid viewport allocation at the start of
## the game. [br][br]
## Deactivated portal has to be explicitly activated by calling [method activate].
func deactivate(destroy_viewports: bool = false) -> void:
hide()
_watchlist_teleportables.clear()
if destroy_viewports:
if portal_viewport:
portal_viewport.queue_free()
portal_viewport = null
portal_camera = null
process_mode = Node.PROCESS_MODE_DISABLED
## Helper method for checking for raycast collisions through portals. If your [RayCast3D] node hits
## a portal collider, pass the [RayCast3D] node to this function to find out what's on the other
## side of the portal! [br][br]
## Uses [method PhysicsDirectSpaceState3D.intersect_ray] under the hood.[br][br]
## Also see [method forward_raycast_query].
func forward_raycast(raycast: RayCast3D) -> Dictionary:
var start := to_exit_position(raycast.get_collision_point())
var goal := to_exit_position(raycast.to_global(raycast.target_position))
var query = PhysicsRayQueryParameters3D.create(
start,
goal,
raycast.collision_mask,
[self.teleport_area, exit_portal.teleport_area]
)
query.collide_with_areas = raycast.collide_with_areas
query.collide_with_bodies = raycast.collide_with_bodies
query.hit_back_faces = raycast.hit_back_faces
query.hit_from_inside = raycast.hit_from_inside
return get_world_3d().direct_space_state.intersect_ray(query)
## When doing raycasts with [method PhysicsDirectSpaceState3D.intersect_ray] and you hit a portal
## that you want to go through, pass the [PhysicsRayQueryParameters3D] you are using to this
## function. It will calculate the ray's continuation and execute the raycast again, returning the
## result dictionary. [br][br]
## If you are using [RayCast3D] for raycasting, see [method forward_raycast].
func forward_raycast_query(params: PhysicsRayQueryParameters3D) -> Dictionary:
var start := to_exit_position(params.from)
var end := to_exit_position(params.to)
start = exit_portal.line_intersection(start, end)
var excludes = [self.teleport_area, exit_portal.teleport_area]
excludes.append_array(params.exclude)
var query = PhysicsRayQueryParameters3D.create(
start, end, params.collision_mask, excludes
)
query.collide_with_areas = params.collide_with_areas
query.collide_with_bodies = params.collide_with_bodies
query.hit_back_faces = params.hit_back_faces
query.hit_from_inside = params.hit_from_inside
return get_world_3d().direct_space_state.intersect_ray(query)
## This method will be called on a teleported node if [member TeleportInteractions.CALLBACK]
## is checked in [member teleport_interactions]. The portal will try to call the method
## [code]on_teleport[/code] on any object being teleported by it.[br][br]
## Example:
## [codeblock]
## func on_teleport(portal: Portal3D) -> void:
## print("Teleported by %s!" % portal.name)
## [/codeblock]
const ON_TELEPORT_CALLBACK: StringName = &"on_teleport"
## This method will be called on a node that will get into close proximity of a portal that has
## [member TeleportInteractions.DUPLICATE_MESHES] turned on. The method is expected to return an
## array of [MeshInstance3D]s.[br][br]
## Example:
## [codeblock]
## @onready var character_mesh: MeshInstance = $CharacterMesh
##
## func get_teleportable_meshes() -> Array[MeshInstance3D]:
## return [character_mesh]
## [/codeblock]
##
## The returned meshes require a special material. Check out the plugin's README for more
## information!
const DUPLICATE_MESHES_CALLBACK: StringName = &"get_teleportable_meshes"
## By default, object triggering the teleport gets teleported. You can override this with a
## metadata property that contains a [NodePath]. If the metadata property is set, then the node at
## the node path will be teleported instead. Setting this to ancestor nodes is recommended.[br][br]
## Example:
## [codeblock]
## func _ready() -> void:
## self.set_meta("teleport_root", ^"..") # parent
## [/codeblock]
## Or you can set the metadata property via the inspector!
const TELEPORT_ROOT_META: StringName = &"teleport_root"
#endregion
## Size of the portal rectangle, height and width.
var portal_size: Vector2 = Vector2(2.0, 2.5):
set(v):
portal_size = v
if _caused_by_user_interaction():
_on_portal_size_changed()
update_configuration_warnings()
if exit_portal:
exit_portal.update_configuration_warnings()
## The exit of this particular portal. Portal camera renders what it sees through this
## [member exit_portal] and teleports take you here. This is a [b]required[/b] property, it
## can never be [code]null[/code].
## [br][br]
## You can change this property during gameplay to switch the portal to a different destination.
## To disable a portal, see [method deactivate].
## [br][br]
## [b]TIP:[/b] Commonly, two portals have set each other as [member exit_portal], which
## allows you to travel back and forth. But you can experiment with one-way portals too!
var exit_portal: Portal3D:
set(v):
exit_portal = v
update_configuration_warnings()
notify_property_list_changed()
var _tb_pair_portals: Callable = _editor_pair_portals.bind()
var _tb_sync_portal_sizes: Callable = _editor_sync_portal_sizes.bind()
## Manually override what's the main camera of the scene. By default it's inferred as the camera
## rendering the parent viewport of the portal. You might have to specify this, if your game uses
## multiple [SubViewport]s.
var player_camera: Camera3D
## The portal camera sets its [member Camera3D.near] as close to the portal as possible, in an
## effort to clip objects close behind the portal. This value offsets the [member portal_camera]'s
## near clip plane. Might be useful, if the portal has a thick frame around it.
var portal_frame_width: float = 0
## Options for different sizes of the internal viewports. It helps to reduce the memory usage
## by not rendering the portals at full resolution. Viewports are resized on window resize.
enum PortalViewportSizeMode {
## Render at full window resolution.
FULL,
## The portal will be [b]at most[/b] this wide. Height is calculated from window aspect ratio.
MAX_WIDTH_ABSOLUTE,
## Portal viewport will be a fraction of full window size.
FRACTIONAL
}
## Size mode to use for the portal viewport size. Only set this via the inspector.
var viewport_size_mode: PortalViewportSizeMode = PortalViewportSizeMode.FULL:
set(v):
viewport_size_mode = v
notify_property_list_changed()
var _viewport_size_max_width_absolute: int = ProjectSettings.get_setting("display/window/size/viewport_width")
var _viewport_size_fractional: float = 0.5
## Hints the direction from which you expect the portal to be viewed.[br][br]
## Use cases: one-way portals, visual-only portals (with [member is_teleport] set to
## [code]false[/code]), or portals that are flush with a wall.
enum ViewDirection {
## Portal is expected to be viewed from either side (default)
FRONT_AND_BACK,
## Corresponds to portal's FORWARD direction (-Z)
ONLY_FRONT,
## Corresponds to portal's BACK direction (+Z)
ONLY_BACK,
}
## The direction from which you expect the portal to be viewed. Restricting this restricts the
## way the portal mesh is shifted around when player looks at the portal from different sides.[br]
## Restrict this if the portal can be seen from the sides and has no portal frame around it to
## cover the shifting mesh.[br][br]
## Also see [member teleport_direction]
var view_direction: ViewDirection = ViewDirection.FRONT_AND_BACK
## The [member portal_mesh] setting for [member VisualInstance3D.layers], so that the portal
## cameras don't see other portals.
var portal_render_layer: int = 1 << 19:
set(v):
portal_render_layer = v
if _caused_by_user_interaction():
portal_mesh.layers = v
## If [code]true[/code], the portal is also a teleport. If [code]false[/code], the portal is
## visual-only.
## [br][br]
## You are expected to toggle this in the editor. For runtime teleport toggling, see
## [method activate] and [method deactivate].
var is_teleport: bool = true:
set(v):
is_teleport = v
if _caused_by_user_interaction():
_setup_teleport()
notify_property_list_changed()
## Dictates from which direction an object has to enter the portal to be teleported.
enum TeleportDirection {
## Corresponds to portal's FORWARD direction (-Z)
FRONT,
## Corresponds to portal's BACK direction (+Z)
BACK,
## Teleports stuff coming from either side. (default)
FRONT_AND_BACK
}
## Portal will only teleport things coming from this direction.
var teleport_direction: TeleportDirection = TeleportDirection.FRONT_AND_BACK
## When a [RigidBody3D] goes through the portal, give its new normalized velocity a
## little boost. Makes stuff flying out of portals more fun. [br][br]
## Recommended values: 1 to 3
var rigidbody_boost: float = 0.0
## When teleporting, the portal checks if the teleported object is less than [b]this[/b] near.
## Prevents false negatives when multiple portals are on top of each other.
var teleport_tolerance: float = 0.5
## Flags for everything that happens when a something is teleported.
enum TeleportInteractions {
## The portal will try to call [constant ON_TELEPORT_CALLBACK] method on the teleported
## node. You need to implement this function with a script.
CALLBACK = 1 << 0,
## When the player is teleported, his X and Z rotations are tweened to zero. Resets unwanted
## from going through a tilted portal. If checked, this will happen BEFORE the callback.
PLAYER_UPRIGHT = 1 << 1,
## Duplicate meshes present on the teleported object, resulting in a [i]smooth teleport[/i]
## from a 3rd point of view. [br]
## To use this feature, implement a method named [constant DUPLICATE_MESHES_CALLBACK] on the
## teleported body, which returns an array of mesh instances that should be duplicated.
## Every one of those meshes also needs to implement a special shader material to clip it along
## the portal plane.
## See shaderinclude at [code]addons/portals/materials/portalclip_mesh.gdshaderinc[/code]
DUPLICATE_MESHES = 1 << 2
}
## See [enum TeleportInteractions] for options.
var teleport_interactions: int = TeleportInteractions.CALLBACK \
| TeleportInteractions.PLAYER_UPRIGHT
## Any [CollisionObject3D]s detected by this mask will be registered by the portal and teleported,
## when they cross the portal boundary.
var teleport_collision_mask: int = 1 << 15
## If the portal is not immediately visible on scene start, you can start it in [i]disabled
## mode[/i]. This just means it will not create the appropriate subviewports, saving memory.
## It will also not be processed.[br][br]
## You have to call [method activate] on it to wake it up! Also see [method disable]
var start_deactivated: bool = false
#region INTERNALS
@export_storage var _portal_thickness: float = 0.05:
set(v):
_portal_thickness = v
if _caused_by_user_interaction(): _on_portal_size_changed()
@export_storage var _portal_mesh_path: NodePath
## Mesh used to visualize the portal surface. Created when the portal is added to the scene
## [b]in the editor[/b].
var portal_mesh: MeshInstance3D:
get():
return get_node(_portal_mesh_path) if _portal_mesh_path else null
set(v): assert(false, "Proxy variable, use '_portal_mesh_path' instead")
@export_storage var _teleport_area_path: NodePath
## When a teleportable object comes near the portal, it's registered by this area and watched
## every frame to trigger the teleport. [br][br] Created by toggling [member is_teleport] in editor.
var teleport_area: Area3D:
get():
return get_node(_teleport_area_path) if _teleport_area_path else null
set(v): assert(false, "Proxy variable, use '_teleport_area_path' instead")
@export_storage var _teleport_collider_path: NodePath
## Collider for [member teleport_area].
var teleport_collider: CollisionShape3D:
get():
return get_node(_teleport_collider_path) if _teleport_collider_path else null
set(v): assert(false, "Proxy variable, use '_teleport_collider_path' instead")
## Camera that looks through the exit portal and renders to [member portal_viewport].
## Created in [method Node._ready]
var portal_camera: Camera3D = null
## Viewport that supplies the albedo texture to portal mesh. Rendered by [member portal_camera].
## Created in [method Node._ready]
var portal_viewport: SubViewport = null
## Metadata about teleported objects.
##
## When the portal detects a teleportable body (or area) nearby, it gathers this metadata and
## starts watching it every frame for teleportation.
class TeleportableMeta:
## Forward distance from the portal
var forward: float = 0
## Meshes that the object gave for duplication. Retrieved by the
## [constant Portal3D.DUPLICATE_MESHES_CALLBACK] callback.
var meshes: Array[MeshInstance3D] = []
## Cloned [member Portal3D.TeleportableMeta.meshes] with [method Node.duplicate]
var mesh_clones: Array[MeshInstance3D] = []
# These physics bodies are being watched by the portal. They are registered with their instance IDs
# as the keys of the dictionary. Registering them by their object references becomes unreliable
# when the teleport candidate gets freed.
var _watchlist_teleportables: Dictionary[int, TeleportableMeta] = {}
#endregion
#region Editor Configuration
const _PORTAL_SHADER: Shader = preload("uid://bhdb2skdxehes")
const _EDITOR_PREVIEW_PORTAL_MATERIAL: StandardMaterial3D = preload("uid://dcfkcyddxkglf")
# _ready(), but only in editor.
func _editor_ready() -> void:
add_to_group(PortalSettings.get_setting("portals_group_name"), true)
set_notify_transform(true)
process_priority = 100
process_physics_priority = 100
_setup_mesh()
_setup_teleport()
self._group_node(self)
func _notification(what: int) -> void:
match what:
NOTIFICATION_TRANSFORM_CHANGED:
update_gizmos()
func _editor_pair_portals() -> void:
assert(exit_portal != null, "My own exit has to be set!")
exit_portal.exit_portal = self
notify_property_list_changed()
func _editor_sync_portal_sizes() -> void:
assert(exit_portal != null, "My own exit has to be set!")
portal_size = exit_portal.portal_size
notify_property_list_changed()
func _setup_teleport():
if is_teleport == false:
if teleport_area:
teleport_area.queue_free()
_teleport_area_path = NodePath("")
if teleport_collider:
teleport_collider.queue_free()
_teleport_collider_path = NodePath("")
return
# Teleport is already set up
if teleport_area and teleport_collider:
return
var area = Area3D.new()
area.name = "TeleportArea"
_add_child_in_editor(self, area)
_teleport_area_path = get_path_to(area)
var collider = CollisionShape3D.new()
collider.name = "Collider"
var box = BoxShape3D.new()
box.size.x = portal_size.x
box.size.y = portal_size.y
collider.shape = box
_add_child_in_editor(teleport_area, collider)
_teleport_collider_path = get_path_to(collider)
func _on_portal_size_changed() -> void:
if portal_mesh == null:
push_error("Failed to update portal size, portal has no mesh")
return
var p: PortalBoxMesh = portal_mesh.mesh
p.size = Vector3(portal_size.x, portal_size.y, 1)
portal_mesh.scale.z = _portal_thickness
if is_teleport and teleport_collider:
var box: BoxShape3D = teleport_collider.shape
box.size.x = portal_size.x
box.size.y = portal_size.y
#endregion
#region GAMEPLAY LOGIC
func _ready() -> void:
if Engine.is_editor_hint():
_editor_ready.call_deferred()
return
if player_camera == null:
player_camera = get_viewport().get_camera_3d()
assert(player_camera != null, "Player camera is missing!")
var mat: ShaderMaterial = ShaderMaterial.new()
mat.shader = _PORTAL_SHADER
portal_mesh.material_override = mat
if not start_deactivated:
_setup_cameras()
get_viewport().size_changed.connect(_on_window_resize)
else:
deactivate.call_deferred(true)
if is_teleport:
assert(teleport_area, "Teleport area should be already set up from editor")
teleport_area.area_entered.connect(self._on_teleport_area_entered)
teleport_area.area_exited.connect(self._on_teleport_area_exited)
teleport_area.body_entered.connect(self._on_teleport_body_entered)
teleport_area.body_exited.connect(self._on_teleport_body_exited)
teleport_area.collision_mask = teleport_collision_mask
func _process(delta: float) -> void:
if Engine.is_editor_hint():
return
if is_teleport:
_process_teleports()
_process_cameras()
func _process_cameras() -> void:
if portal_camera == null:
push_error("%s: No portal camera" % name)
return
if player_camera == null:
push_error("%s: No player camera" % name)
return
if exit_portal == null:
push_error("%s: No exit portal" % name)
return
# Update camera
portal_camera.global_transform = self.to_exit_transform(player_camera.global_transform)
portal_camera.near = _calculate_near_plane()
portal_camera.fov = player_camera.fov
# Prevent flickering
var pv_size: Vector2i = portal_viewport.size
var half_height: float = player_camera.near * tan(deg_to_rad(player_camera.fov * 0.5))
var half_width: float = half_height * pv_size.x / float(pv_size.y)
var near_diagonal: float = Vector3(half_width, half_height, player_camera.near).length()
portal_mesh.scale.z = near_diagonal
var player_in_front_of_portal: bool = forward_distance(player_camera) > 0
var portal_shift: float = 0
match view_direction:
ViewDirection.ONLY_FRONT:
portal_shift = 1
ViewDirection.ONLY_BACK:
portal_shift = -1
ViewDirection.FRONT_AND_BACK:
portal_shift = 1 if player_in_front_of_portal else -1
portal_mesh.scale.z *= signf(portal_shift) # Turn the portal towards the player
func _process_teleports() -> void:
for body_id: int in _watchlist_teleportables.keys():
if not is_instance_id_valid(body_id): # Watched body has been freed
_erase_tp_metadata(body_id)
continue
var tp_meta: TeleportableMeta = _watchlist_teleportables.get(body_id)
var body = instance_from_id(body_id) as Node3D
var last_fw_angle: float = tp_meta.forward
var current_fw_angle: float = forward_distance(body)
var should_teleport: bool = false
match teleport_direction:
TeleportDirection.FRONT:
should_teleport = last_fw_angle > 0 and current_fw_angle <= 0
TeleportDirection.BACK:
should_teleport = last_fw_angle < 0 and current_fw_angle >= 0
TeleportDirection.FRONT_AND_BACK:
should_teleport = sign(last_fw_angle) != sign(current_fw_angle)
_:
assert(false, "This match statement should be exhaustive")
if should_teleport and abs(current_fw_angle) < teleport_tolerance:
var teleportable_path = body.get_meta(TELEPORT_ROOT_META, ".")
var teleportable: Node3D = body.get_node(teleportable_path)
teleportable.global_transform = self.to_exit_transform(teleportable.global_transform)
if teleportable is RigidBody3D:
teleportable.linear_velocity = to_exit_direction(teleportable.linear_velocity)
teleportable.apply_central_impulse(
teleportable.linear_velocity.normalized() * rigidbody_boost
)
on_teleport.emit(teleportable)
exit_portal.on_teleport_receive.emit(teleportable)
# Force the cameras to refresh if we just teleported a player
var was_player := not str(teleportable.get_path_to(player_camera)).begins_with(".")
if was_player:
_process_cameras()
exit_portal._process_cameras()
# Resolve teleport interactions
if was_player and _check_tp_interaction(TeleportInteractions.PLAYER_UPRIGHT):
get_tree().create_tween().tween_property(teleportable, "rotation:x", 0, 0.3)
get_tree().create_tween().tween_property(teleportable, "rotation:z", 0, 0.3)
if _check_tp_interaction(TeleportInteractions.CALLBACK):
if teleportable.has_method(ON_TELEPORT_CALLBACK):
teleportable.call(ON_TELEPORT_CALLBACK, self)
# transfer the thing to exit portal
_transfer_tp_metadata_to_exit(body)
else:
tp_meta.forward = current_fw_angle
for i in tp_meta.mesh_clones.size():
var mesh = tp_meta.meshes[i]
var clone = tp_meta.mesh_clones[i]
clone.global_transform = to_exit_transform(mesh.global_transform)
func _calculate_near_plane() -> float:
# Adjustment for cube portals. This AABB is basically a plane.
var _aabb: AABB = AABB(
Vector3(-exit_portal.portal_size.x / 2, -exit_portal.portal_size.y / 2, 0),
Vector3(exit_portal.portal_size.x, exit_portal.portal_size.y, 0)
)
var _pos := _aabb.position
var _size := _aabb.size
var corner_1: Vector3 = exit_portal.to_global(Vector3(_pos.x, _pos.y, 0))
var corner_2: Vector3 = exit_portal.to_global(Vector3(_pos.x + _size.x, _pos.y, 0))
var corner_3: Vector3 = exit_portal.to_global(Vector3(_pos.x + _size.x, _pos.y + _size.y, 0))
var corner_4: Vector3 = exit_portal.to_global(Vector3(_pos.x, _pos.y + _size.y, 0))
# Calculate the distance along the exit camera forward vector at which each of the portal
# corners projects
var camera_forward: Vector3 = - portal_camera.global_transform.basis.z.normalized()
var d_1: float = (corner_1 - portal_camera.global_position).dot(camera_forward)
var d_2: float = (corner_2 - portal_camera.global_position).dot(camera_forward)
var d_3: float = (corner_3 - portal_camera.global_position).dot(camera_forward)
var d_4: float = (corner_4 - portal_camera.global_position).dot(camera_forward)
# The near clip distance is the shortest distance which still contains all the corners
return max(0.01, min(d_1, d_2, d_3, d_4) - exit_portal.portal_frame_width)
func _setup_mesh() -> void:
if portal_mesh:
return
var mi = MeshInstance3D.new()
mi = MeshInstance3D.new()
mi.name = self.name + "_Mesh"
mi.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
mi.layers = portal_render_layer
var p := PortalBoxMesh.new()
p.size = Vector3(portal_size.x, portal_size.y, 1)
mi.mesh = p
mi.scale.z = _portal_thickness
# Editor-only material. Will be replaced when game starts.
mi.material_override = _EDITOR_PREVIEW_PORTAL_MATERIAL
_add_child_in_editor(self, mi)
_portal_mesh_path = get_path_to(mi)
func _setup_cameras() -> void:
assert(not Engine.is_editor_hint(), "This should never run in editor")
assert(portal_camera == null)
assert(portal_viewport == null)
if exit_portal != null:
portal_viewport = SubViewport.new()
portal_viewport.name = self.name + "_SubViewport"
portal_viewport.size = _calculate_viewport_size()
self.add_child(portal_viewport, true)
# Disable tonemapping on portal cameras
var adjusted_env: Environment = player_camera.environment.duplicate() \
if player_camera.environment \
else player_camera.get_world_3d().environment.duplicate()
adjusted_env.tonemap_mode = Environment.TONE_MAPPER_LINEAR
adjusted_env.tonemap_exposure = 1
portal_camera = Camera3D.new()
portal_camera.name = self.name + "_Camera3D"
portal_camera.environment = adjusted_env
# Ensure that portals don't see other portals.
portal_camera.cull_mask = portal_camera.cull_mask ^ portal_render_layer
portal_viewport.add_child(portal_camera, true)
portal_camera.global_position = exit_portal.global_position
# Connect the viewport to the mesh. Mesh material setup has to run BEFORE this
portal_mesh.material_override.set_shader_parameter("albedo", portal_viewport.get_texture())
else:
push_error("%s has no exit_portal! Failed to setup cameras." % name)
#endregion
#region Event handlers
func _on_teleport_area_entered(area: Area3D) -> void:
if _watchlist_teleportables.has(area.get_instance_id()):
# Already on watchlist
return
_construct_tp_metadata(area)
func _on_teleport_body_entered(body: Node3D) -> void:
if _watchlist_teleportables.has(body.get_instance_id()):
# Already on watchlist
return
_construct_tp_metadata(body)
func _on_teleport_area_exited(area: Area3D) -> void:
_erase_tp_metadata(area.get_instance_id())
func _on_teleport_body_exited(body: Node3D) -> void:
_erase_tp_metadata(body.get_instance_id())
func _on_window_resize() -> void:
if portal_viewport:
portal_viewport.size = _calculate_viewport_size()
#endregion
#region UTILS
func _construct_tp_metadata(node: Node3D) -> void:
var meta = TeleportableMeta.new()
meta.forward = forward_distance(node)
if _check_tp_interaction(TeleportInteractions.DUPLICATE_MESHES) and \
node.has_method(DUPLICATE_MESHES_CALLBACK):
meta.meshes = node.call(DUPLICATE_MESHES_CALLBACK)
for m: MeshInstance3D in meta.meshes:
var dupe = m.duplicate(0)
dupe.name = m.name + "_Clone"
meta.mesh_clones.append(dupe)
self.add_child(dupe, true)
_enable_mesh_clipping(meta, self)
_watchlist_teleportables.set(node.get_instance_id(), meta)
func _erase_tp_metadata(node_id: int) -> void:
var meta = _watchlist_teleportables.get(node_id)
if meta != null:
meta = meta as TeleportableMeta
for m in meta.meshes: _disable_mesh_clipping(m)
for c in meta.mesh_clones: c.queue_free()
_watchlist_teleportables.erase(node_id)
func _enable_mesh_clipping(meta: TeleportableMeta, along_portal: Portal3D) -> void:
for mi: MeshInstance3D in meta.meshes:
var clip_normal = signf(meta.forward) * along_portal.global_basis.z
mi.set_instance_shader_parameter("portal_clip_active", true)
mi.set_instance_shader_parameter("portal_clip_point", along_portal.global_position)
mi.set_instance_shader_parameter("portal_clip_normal", clip_normal)
var exit = along_portal.exit_portal
for clone: MeshInstance3D in meta.mesh_clones:
var clip_normal = signf(meta.forward) * exit.global_basis.z
clone.set_instance_shader_parameter("portal_clip_active", true)
clone.set_instance_shader_parameter("portal_clip_point", exit.global_position)
clone.set_instance_shader_parameter("portal_clip_normal", clip_normal)
func _disable_mesh_clipping(mi: MeshInstance3D) -> void:
mi.set_instance_shader_parameter("portal_clip_active", false)
func _transfer_tp_metadata_to_exit(for_body: Node3D) -> void:
if not exit_portal.is_teleport:
return # One-way teleport scenario
var body_id = for_body.get_instance_id()
var tp_meta = _watchlist_teleportables[body_id]
if tp_meta == null:
push_error("Attempted to trasfer teleport metadata for a node that is not being watched.")
return
tp_meta.forward = exit_portal.forward_distance(for_body)
_enable_mesh_clipping(tp_meta, exit_portal) # Switch, the main mesh is clipped by exit portal!
exit_portal._watchlist_teleportables.set(body_id, tp_meta)
# NOTE: Not using '_erase_tp_metadata' here, as it also frees the cloned meshes!
_watchlist_teleportables.erase(body_id)
## [b]Crucial[/b] piece of a portal - transforming where objects should appear
## on the other side. Used for both cameras and teleports.
func to_exit_transform(g_transform: Transform3D) -> Transform3D:
var relative_to_portal: Transform3D = global_transform.affine_inverse() * g_transform
var flipped: Transform3D = relative_to_portal.rotated(Vector3.UP, PI)
var relative_to_target = exit_portal.global_transform * flipped
return relative_to_target
## Similar to [method to_exit_transform], but this one uses [member global_basis] for calculations,
## so it [b]only transforms rotation[/b], since portal scale should aways be 1. Use for transforming
## directions.
func to_exit_direction(real: Vector3) -> Vector3:
var relative_to_portal: Vector3 = global_basis.inverse() * real
var flipped: Vector3 = relative_to_portal.rotated(Vector3.UP, PI)
var relative_to_target: Vector3 = exit_portal.global_basis * flipped
return relative_to_target
## Similar to [method to_exit_transform], but expects a global position.
func to_exit_position(g_pos: Vector3) -> Vector3:
var local: Vector3 = global_transform.affine_inverse() * g_pos
var rotated = local.rotated(Vector3.UP, PI)
var local_at_exit: Vector3 = exit_portal.global_transform * rotated
return local_at_exit
## Calculates the dot product of portal's forward vector with the global
## position of [param node] relative to the portal. Used for detecting teleports.
## [br]
## The result is positive when the node is in front of the portal. The value measures how far in
## front (or behind) the other node is compared to the portal.
func forward_distance(node: Node3D) -> float:
var portal_front: Vector3 = self.global_transform.basis.z.normalized()
var node_relative: Vector3 = (node.global_transform.origin - self.global_transform.origin)
return portal_front.dot(node_relative)
# Helper function meant to be used in editor. Adds [param node] as a child to
# [param parent]. Forces a readable name and sets the child's owner to the same
# as parent's.
func _add_child_in_editor(parent: Node, node: Node) -> void:
parent.add_child(node, true)
# self.owner is null if this node is the scene root. Supply self.
node.owner = self if self.owner == null else self.owner
# Used to conditionally run property setters.
# [br]
# Setters fire both on editor set and when the scene starts up (the engine is
# assigning exported members). This should prevent the second case.
func _caused_by_user_interaction() -> bool:
return Engine.is_editor_hint() and is_node_ready()
# Editor helper function. Groups nodes in 3D editor view.
func _group_node(node: Node) -> void:
node.set_meta("_edit_group_", true)
func _calculate_viewport_size() -> Vector2i:
var vp_size: Vector2i = get_viewport().size
var aspect_ratio: float = float(vp_size.x) / float(vp_size.y)
match viewport_size_mode:
PortalViewportSizeMode.FULL:
return vp_size
PortalViewportSizeMode.MAX_WIDTH_ABSOLUTE:
var width = min(_viewport_size_max_width_absolute, vp_size.x)
return Vector2i(width, int(width / aspect_ratio))
PortalViewportSizeMode.FRACTIONAL:
return Vector2i(vp_size * _viewport_size_fractional)
push_error("Failed to determine desired viewport size")
return Vector2i(
ProjectSettings.get_setting("display/window/size/viewport_width"),
ProjectSettings.get_setting("display/window/size/viewport_height")
)
func _check_tp_interaction(flag: int) -> bool:
return (teleport_interactions & flag) > 0
## Get a point where the portal plane intersects a line. Line [param start] and [param end]
## are in global coordinates and so is the result. Used for forwarding raycast queries.
func line_intersection(start: Vector3, end: Vector3) -> Vector3:
var plane_normal = - global_basis.z
var plane_point = global_position
var line_dir = end - start
var denom = plane_normal.dot(line_dir)
if abs(denom) < 1e-6:
return Vector3.ZERO # No intersection, line is parallel to the plane
var t = plane_normal.dot(plane_point - start) / denom
return start + line_dir * t
#endregion
#region GODOT EDITOR INTEGRATIONS
func _get_configuration_warnings() -> PackedStringArray:
var warnings: Array[String] = []
var global_scale = global_basis.get_scale()
if not global_scale.is_equal_approx(Vector3.ONE):
warnings.append(
("Portals should NOT be scaled. Global portal scale is %v, " % global_scale) +
"but should be (1.0, 1.0, 1.0). Make sure the portal and any of portal parents " +
"aren't scaled."
)
if exit_portal == null:
warnings.append("Exit portal is null")
if exit_portal != null:
if not portal_size.is_equal_approx(exit_portal.portal_size):
warnings.append(
"Portal size should be the same as exit portal's (it's %s, but should be %s)" %
[portal_size, exit_portal.portal_size]
)
return PackedStringArray(warnings)
func _get_property_list() -> Array[Dictionary]:
var config: Array[Dictionary] = []
config.append(AtExport.vector2("portal_size"))
if exit_portal != null and not portal_size.is_equal_approx(exit_portal.portal_size):
config.append(
AtExport.button("_tb_sync_portal_sizes", "Take Exit Portal's Size", "Vector2"))
config.append(AtExport.node("exit_portal", "Portal3D"))
if exit_portal != null and exit_portal.exit_portal == null:
config.append(AtExport.button("_tb_pair_portals", "Pair Portals", "SliderJoint3D"))
config.append(AtExport.group("Rendering"))
config.append(AtExport.node("player_camera", "Camera3D"))
config.append(AtExport.float_range("portal_frame_width", 0.0, 10.0, 0.01))
config.append(AtExport.enum_(
"viewport_size_mode", &"Portal3D.PortalViewportSizeMode", PortalViewportSizeMode))
if viewport_size_mode == PortalViewportSizeMode.MAX_WIDTH_ABSOLUTE:
config.append(AtExport.int_range("_viewport_size_max_width_absolute", 2, 4096))
elif viewport_size_mode == PortalViewportSizeMode.FRACTIONAL:
config.append(AtExport.float_range("_viewport_size_fractional", 0, 1))
config.append(AtExport.enum_("view_direction", &"Portal3D.ViewDirection", ViewDirection))
config.append(AtExport.int_render_3d("portal_render_layer"))
config.append(AtExport.group_end())
config.append(AtExport.bool_("is_teleport"))
if is_teleport:
config.append(AtExport.group("Teleport"))
config.append(
AtExport.enum_("teleport_direction", &"Portal3D.TeleportDirection", TeleportDirection))
config.append(AtExport.float_range("rigidbody_boost", 0, 5, 0.1, ["or_greater"]))
config.append(AtExport.float_range("teleport_tolerance", 0.0, 5.0, 0.1, ["or_greater"]))
var opts: Array = TeleportInteractions.keys().map(func(s): return s.capitalize())
config.append(AtExport.int_flags("teleport_interactions", opts))
config.append(AtExport.int_physics_3d("teleport_collision_mask"))
config.append(AtExport.group_end())
config.append(AtExport.group("Advanced"))
config.append(AtExport.bool_("start_deactivated"))
return config
func _property_can_revert(property: StringName) -> bool:
return property in [
&"portal_size",
&"player_camera",
&"portal_frame_width",
&"_viewport_size_max_width_absolute",
&"view_direction",
&"portal_render_layer",
&"teleport_direction",
&"rigidbody_boost",
&"teleport_tolerance",
&"teleport_interactions",
&"teleport_collision_mask",
&"start_deactivated",
]
func _property_get_revert(property: StringName) -> Variant:
match property:
&"portal_size":
return Vector2(2, 2.5)
&"portal_frame_width":
return 0.0
&"_viewport_size_max_width_absolute":
return ProjectSettings.get_setting("display/window/size/viewport_width")
&"view_direction":
return ViewDirection.FRONT_AND_BACK
&"portal_render_layer":
return 1 << 19
&"teleport_direction":
return TeleportDirection.FRONT_AND_BACK
&"rigidbody_boost":
return 0.0
&"teleport_tolerance":
return 0.5
&"teleport_interactions":
return TeleportInteractions.CALLBACK | TeleportInteractions.PLAYER_UPRIGHT
&"teleport_collision_mask":
return 1 << 15
&"start_deactivated":
return false
return null
#endregion

View File

@ -0,0 +1 @@
uid://cw1r4c1d7beyv

View File

@ -0,0 +1,96 @@
@tool
extends ArrayMesh
class_name PortalBoxMesh
## Inverted box with a flipped front side
##
## This mesh class generates a mesh similar to [BoxMesh]. However, its sides are all facing
## [i]inwards[/i], except for the fron side, which is facing outwards. The origin point of this
## mesh is in the middle of its front face, instead of in the center of its volume (like you'd
## expect with a box).[br]
## It is a special mesh built for portal surfaces. The front face provides a nice flat surface and
## the other sides try to reduce clipping issues when traveling through portals. See [Portal3D]
@export var size: Vector3 = Vector3(1, 1, 1):
set(v):
size = v
generate_portal_mesh()
func _init() -> void:
if Engine.is_editor_hint():
generate_portal_mesh()
func generate_portal_mesh() -> void:
var _start_time: int = Time.get_ticks_usec()
clear_surfaces() # Reset
var surface_array: Array = []
surface_array.resize(Mesh.ARRAY_MAX)
var verts: PackedVector3Array = PackedVector3Array()
var uvs: PackedVector2Array = PackedVector2Array()
var normals: PackedVector3Array = PackedVector3Array()
var indices: PackedInt32Array = PackedInt32Array()
# Just to save some chars
var w: float = size.x / 2
var h: float = size.y / 2
var depth: Vector3 = Vector3(0, 0, -size.z)
# Outside rect
var TOP_LEFT: Vector3 = Vector3(-w, h, 0)
var TOP_RIGHT: Vector3 = Vector3(w, h, 0)
var BOTTOM_LEFT: Vector3 = Vector3(-w, -h, 0)
var BOTTOM_RIGHT: Vector3 = Vector3(w, -h, 0)
verts.append_array([
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,
TOP_LEFT + depth, TOP_RIGHT + depth, BOTTOM_LEFT + depth, BOTTOM_RIGHT + depth,
])
uvs.append_array([
Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), # Front UVs
Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1), # Back UVs (the same)
])
# We are going for a flat-surface look here. Portals should be unshaded anyways.
normals.append_array([
Vector3.BACK, Vector3.BACK, Vector3.BACK, Vector3.BACK,
Vector3.BACK, Vector3.BACK, Vector3.BACK, Vector3.BACK
])
# 0 ----------- 1
# | \ / |
# | 4-------5 |
# | | | |
# | | | |
# | 6-------7 |
# | / \ |
# 2 ----------- 3
# Triangles are clockwise!
indices.append_array([
0, 1, 4,
4, 1, 5, # Top section done
1, 3, 5,
5, 3, 7, # right section done
3, 2, 7,
7, 2, 6, # bottom section done
2, 0, 6,
6, 0, 4, # left section done
4, 5, 6,
6, 5, 7, # back section done
0, 1, 2,
2, 1, 3, # front section done
])
surface_array[Mesh.ARRAY_VERTEX] = verts
surface_array[Mesh.ARRAY_TEX_UV] = uvs
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_INDEX] = indices
add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)

View File

@ -0,0 +1 @@
uid://bxcel82b180o3

View File

@ -0,0 +1,38 @@
class_name PortalSettings extends Object
## Static helper class for portal project settings.
##
## Features helper methods for inserting addon-related settings into [ProjectSettings].
## Used mainly in plugin initialization and for getting defaults in [Portal3D]
static func _qual_name(setting: String) -> String:
return "addons/portals/" + setting
## Initializes a setting, it it's not present already. The setting is [i]basic[/i] by default.
static func init_setting(setting: String,
default_value: Variant,
requires_restart: bool = false) -> void:
setting = _qual_name(setting)
# This would mean the setting is already overriden
if not ProjectSettings.has_setting(setting):
ProjectSettings.set_setting(setting, default_value)
ProjectSettings.set_initial_value(setting, default_value)
ProjectSettings.set_restart_if_changed(setting, requires_restart)
ProjectSettings.set_as_basic(setting, true)
## See companion class [class AtExport], it has some utilities which might be helpful!
static func add_info(config: Dictionary) -> void:
var qual_name = _qual_name(config["name"])
config["name"] = qual_name
# In case this is coming from AtExport, which is geared towards inspector properties
config.erase("usage")
ProjectSettings.add_property_info(config)
## Calls [method ProjectSettings.get_setting]
static func get_setting(setting: String) -> Variant:
setting = _qual_name(setting)
return ProjectSettings.get_setting(setting)

View File

@ -0,0 +1 @@
uid://yb4p7f7n5gid

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 HungryProton
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.

View File

@ -0,0 +1,23 @@
[gd_scene load_steps=5 format=3 uid="uid://b4ted6l27vuyd"]
[ext_resource type="PackedScene" uid="uid://d1d1fag0m04yc" path="res://addons/proton_scatter/demos/assets/models/brick.glb" id="1_bkmk2"]
[ext_resource type="Texture2D" uid="uid://dqa2jfs1jy0hq" path="res://addons/proton_scatter/demos/assets/textures/t_rock.jpg" id="2_235bd"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_nwvh2"]
albedo_color = Color(0.678431, 0.596078, 0.466667, 1)
albedo_texture = ExtResource("2_235bd")
uv1_scale = Vector3(0.75, 0.75, 0.75)
uv1_triplanar = true
[sub_resource type="BoxShape3D" id="BoxShape3D_0rrnn"]
size = Vector3(0.4, 0.4, 0.4)
[node name="brick" instance=ExtResource("1_bkmk2")]
[node name="Cube" parent="." index="0"]
material_override = SubResource("StandardMaterial3D_nwvh2")
[node name="StaticBody3D" type="StaticBody3D" parent="." index="1"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" index="0"]
shape = SubResource("BoxShape3D_0rrnn")

View File

@ -0,0 +1,10 @@
[gd_scene load_steps=3 format=3 uid="uid://b8abs8me7ckgo"]
[ext_resource type="PackedScene" uid="uid://dbb4culid55v5" path="res://addons/proton_scatter/demos/assets/models/bush.glb" id="1_kv8tm"]
[ext_resource type="Material" uid="uid://bn3fr3m3glrnp" path="res://addons/proton_scatter/demos/assets/materials/m_bush.tres" id="2_bkwoq"]
[node name="bush" instance=ExtResource("1_kv8tm")]
[node name="Bush" parent="." index="0"]
material_override = ExtResource("2_bkwoq")
instance_shader_parameters/camera_bend_strength = 0.0

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://ctkii8aivl17n"]
[ext_resource type="PackedScene" uid="uid://cmqlv88xp71tw" path="res://addons/proton_scatter/demos/assets/models/dead_branch.glb" id="1_5foyv"]
[ext_resource type="Material" uid="uid://d01d0h08lqqn6" path="res://addons/proton_scatter/demos/assets/materials/m_trunk.tres" id="2_tldro"]
[node name="dead_branch" instance=ExtResource("1_5foyv")]
[node name="DeadBranch" parent="." index="0"]
surface_material_override/0 = ExtResource("2_tldro")

View File

@ -0,0 +1,18 @@
[gd_scene load_steps=4 format=3 uid="uid://bfcjigq0vdl4d"]
[ext_resource type="PackedScene" uid="uid://6gxiul1pw13t" path="res://addons/proton_scatter/demos/assets/models/fence_planks.glb" id="1"]
[ext_resource type="Material" path="res://addons/proton_scatter/demos/assets/materials/m_fence.tres" id="2"]
[sub_resource type="BoxShape3D" id="BoxShape3D_fesk1"]
size = Vector3(1, 0.5, 0.1)
[node name="fence_planks" instance=ExtResource("1")]
[node name="fence_planks2" parent="." index="0"]
surface_material_override/0 = ExtResource("2")
[node name="StaticBody3D" type="StaticBody3D" parent="." index="1"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" index="0"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.197684, 0.0236663)
shape = SubResource("BoxShape3D_fesk1")

View File

@ -0,0 +1,16 @@
[gd_scene load_steps=3 format=3 uid="uid://bmglbfn5jaubp"]
[ext_resource type="PackedScene" uid="uid://d3f4d3m7n8tpr" path="res://addons/proton_scatter/demos/assets/models/gobot.glb" id="1_gfyx7"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_rhuea"]
albedo_color = Color(0.278431, 0.54902, 0.74902, 1)
metallic = 0.4
metallic_specular = 0.2
roughness = 0.15
rim_enabled = true
rim = 0.3
[node name="gobot" instance=ExtResource("1_gfyx7")]
[node name="Sphere001" parent="." index="0"]
surface_material_override/0 = SubResource("StandardMaterial3D_rhuea")

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://c3c76je2y6vfj"]
[ext_resource type="PackedScene" uid="uid://018flajgtx7t" path="res://addons/proton_scatter/demos/assets/models/grass.glb" id="1_203fe"]
[ext_resource type="Material" uid="uid://c4mot1fo3siox" path="res://addons/proton_scatter/demos/assets/materials/m_grass.tres" id="2_sv1ar"]
[node name="grass" instance=ExtResource("1_203fe")]
[node name="Plane_011" parent="." index="0"]
surface_material_override/0 = ExtResource("2_sv1ar")

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://cia3jakp3wj1d"]
[ext_resource type="PackedScene" uid="uid://dcnm2ijk7hj4j" path="res://addons/proton_scatter/demos/assets/models/grass_2.glb" id="1_xyqky"]
[ext_resource type="Material" uid="uid://c4mot1fo3siox" path="res://addons/proton_scatter/demos/assets/materials/m_grass.tres" id="2_63qe5"]
[node name="grass_2" instance=ExtResource("1_xyqky")]
[node name="Grass" parent="." index="0"]
surface_material_override/0 = ExtResource("2_63qe5")

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,62 @@
shader_type spatial;
render_mode depth_draw_opaque, cull_disabled;
// Texture settings
uniform sampler2D texture_albedo : hint_default_white, repeat_disable;
uniform sampler2D texture_gradient : hint_default_white, repeat_disable;
uniform sampler2D texture_noise : hint_default_white;
uniform float alpha_scissor_threshold : hint_range(0.0, 1.0);
uniform vec4 transmission : source_color;
uniform vec4 secondary_color : source_color;
uniform float secondary_attenuation = 0.2;
uniform float grass_height = 1.0;
// Wind settings
uniform vec2 wind_direction = vec2(1, -0.5);
uniform float wind_speed = 1.0;
uniform float wind_strength = 2.0;
uniform float noise_scale = 20.0;
instance uniform float camera_bend_strength : hint_range(0.0, 3.0) = 0.2;
varying float color;
varying float height;
void vertex() {
height = VERTEX.y;
float influence = smoothstep(0, 1, height / 2.0);
vec4 world_pos = MODEL_MATRIX * vec4(VERTEX, 1.0);
vec2 uv = world_pos.xz / (noise_scale + 1e-2);
vec2 panning_uv = uv + fract(TIME * wind_direction * wind_speed);
float wind = texture(texture_noise, panning_uv).r * 2.0 - 0.4;
color = texture(texture_noise, uv).r;
vec2 wind_offset = -wind_direction * wind_strength * influence * wind;
world_pos.xz += wind_offset;
world_pos.y -= wind * influence * smoothstep(0.0, height, wind_strength);
//Push the top vertex away from the camera to bend the grass clump
float ndotv = 1.0 - dot(vec3(0.0, 1.0, 0.0), normalize(INV_VIEW_MATRIX[1].xyz));
world_pos.xz += INV_VIEW_MATRIX[1].xz * camera_bend_strength * height * ndotv;
vec4 local_pos = inverse(MODEL_MATRIX) * world_pos;
local_pos.x += wind_strength * influence * cos(TIME * 1.0) / 8.0;
local_pos.z += wind_strength * influence * sin(TIME * 1.5) / 8.0;
VERTEX = local_pos.xyz;
//NORMAL = vec3(0.0, 1.0, 0.0);
}
void fragment() {
vec4 tex = texture(texture_albedo, UV);
if (tex.a < alpha_scissor_threshold) {
discard;
}
BACKLIGHT = transmission.rgb;
vec4 gradient = texture(texture_gradient, vec2(height / grass_height, 0.0));
float secondary_weight = smoothstep(0.0, 1.0, color - secondary_attenuation);
ALBEDO = tex.rbg * gradient.rgb;
//ALBEDO = mix(ALBEDO, secondary_color.rgb, secondary_weight);
}

View File

@ -0,0 +1 @@
uid://cktbgarifkbc8

View File

@ -0,0 +1,51 @@
shader_type spatial;
render_mode depth_draw_opaque, cull_disabled;
// Texture settings
uniform sampler2D texture_albedo : hint_default_white, repeat_disable;
uniform sampler2D texture_gradient : hint_default_white;
uniform sampler2D texture_noise : hint_default_white;
uniform float alpha_scissor_threshold : hint_range(0.0, 1.0);
uniform vec4 transmission : source_color;
uniform float total_height = 1.0;
// Wind settings
uniform vec2 wind_direction = vec2(1, -0.5);
uniform float wind_speed = 1.0;
uniform float wind_strength = 2.0;
uniform float noise_scale = 20.0;
varying float color;
varying float height;
void vertex() {
height = VERTEX.y;
vec4 world_pos = MODEL_MATRIX * vec4(VERTEX, 1.0);
vec2 uv = (world_pos.xz + VERTEX.yy) / (noise_scale + 1e-2) ;
vec2 panning_uv = uv + fract(TIME * wind_direction * wind_speed);
float wind = texture(texture_noise, panning_uv).r * 2.0 - 0.4;
color = texture(texture_noise, uv).r;
float wind_influence = smoothstep(0, 1, 1.0 - UV.y);
vec2 wind_offset = -wind_direction * wind_strength * wind_influence * wind;
world_pos.xz += wind_offset;
world_pos.y -= wind * wind_influence * wind_strength * 0.45;
vec4 local_pos = inverse(MODEL_MATRIX) * world_pos;
VERTEX = local_pos.xyz;
//NORMAL = vec3(0.0, 1.0, 0.0);
}
void fragment() {
vec4 tex = texture(texture_albedo, UV);
if (tex.a < alpha_scissor_threshold) {
discard;
}
BACKLIGHT = transmission.rgb;
vec4 gradient = texture(texture_gradient, vec2(height / total_height, 0.0));
ALBEDO = tex.rbg * gradient.rgb;
}

View File

@ -0,0 +1 @@
uid://bv00dvm0n7u46

View File

@ -0,0 +1,37 @@
[gd_resource type="ShaderMaterial" load_steps=7 format=3 uid="uid://bn3fr3m3glrnp"]
[ext_resource type="Shader" uid="uid://cktbgarifkbc8" path="res://addons/proton_scatter/demos/assets/materials/grass.gdshader" id="1_peshr"]
[ext_resource type="Texture2D" uid="uid://b2a6ylo2enm4g" path="res://addons/proton_scatter/demos/assets/textures/t_bush.png" id="2_mbhvd"]
[sub_resource type="Gradient" id="Gradient_122hb"]
offsets = PackedFloat32Array(0, 0.5, 1)
colors = PackedColorArray(0.179688, 0.0759602, 0.0183228, 1, 0.386532, 0.390625, 0.0230687, 1, 1, 0.693237, 0.0687054, 1)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_i0bw2"]
gradient = SubResource("Gradient_122hb")
[sub_resource type="FastNoiseLite" id="FastNoiseLite_eeqpx"]
seed = 1
frequency = 0.002
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_7l0n1"]
in_3d_space = true
seamless = true
seamless_blend_skirt = 0.65
noise = SubResource("FastNoiseLite_eeqpx")
[resource]
render_priority = 0
shader = ExtResource("1_peshr")
shader_parameter/texture_albedo = ExtResource("2_mbhvd")
shader_parameter/texture_gradient = SubResource("GradientTexture1D_i0bw2")
shader_parameter/texture_noise = SubResource("NoiseTexture2D_7l0n1")
shader_parameter/alpha_scissor_threshold = 0.25
shader_parameter/transmission = Color(0.619608, 0.541176, 0.101961, 1)
shader_parameter/secondary_color = Color(0, 0, 0, 1)
shader_parameter/secondary_attenuation = 0.2
shader_parameter/grass_height = 0.829
shader_parameter/wind_direction = Vector2(1, -0.5)
shader_parameter/wind_speed = 0.5
shader_parameter/wind_strength = 0.15
shader_parameter/noise_scale = 6.0

View File

@ -0,0 +1,6 @@
[gd_resource type="SpatialMaterial" format=2]
[resource]
resource_name = "wood"
vertex_color_use_as_albedo = true
albedo_color = Color( 0.568627, 0.466667, 0.372549, 1 )

View File

@ -0,0 +1,37 @@
[gd_resource type="ShaderMaterial" load_steps=7 format=3 uid="uid://c4mot1fo3siox"]
[ext_resource type="Shader" uid="uid://cktbgarifkbc8" path="res://addons/proton_scatter/demos/assets/materials/grass.gdshader" id="1_fntgl"]
[ext_resource type="Texture2D" uid="uid://d23p13yi7asw0" path="res://addons/proton_scatter/demos/assets/textures/t_grass_2.png" id="2_1odx0"]
[sub_resource type="Gradient" id="Gradient_122hb"]
offsets = PackedFloat32Array(0, 0.473451, 1)
colors = PackedColorArray(0.179688, 0.0855483, 0.00322032, 1, 0.251693, 0.390625, 0.0117187, 1, 1, 0.964706, 0.129412, 1)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_i0bw2"]
gradient = SubResource("Gradient_122hb")
[sub_resource type="FastNoiseLite" id="FastNoiseLite_eeqpx"]
seed = 1
frequency = 0.002
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_7l0n1"]
in_3d_space = true
seamless = true
seamless_blend_skirt = 0.65
noise = SubResource("FastNoiseLite_eeqpx")
[resource]
render_priority = 0
shader = ExtResource("1_fntgl")
shader_parameter/texture_albedo = ExtResource("2_1odx0")
shader_parameter/texture_gradient = SubResource("GradientTexture1D_i0bw2")
shader_parameter/texture_noise = SubResource("NoiseTexture2D_7l0n1")
shader_parameter/alpha_scissor_threshold = 0.3
shader_parameter/transmission = Color(0.737255, 0.72549, 0, 1)
shader_parameter/secondary_color = Color(0, 0, 0, 1)
shader_parameter/secondary_attenuation = 0.2
shader_parameter/grass_height = 0.6
shader_parameter/wind_direction = Vector2(1, -0.5)
shader_parameter/wind_speed = 0.5
shader_parameter/wind_strength = 0.15
shader_parameter/noise_scale = 6.0

View File

@ -0,0 +1,38 @@
[gd_resource type="ShaderMaterial" load_steps=7 format=3 uid="uid://djo80ucamk643"]
[ext_resource type="Shader" path="res://addons/proton_scatter/demos/assets/materials/grass.gdshader" id="1_8py1k"]
[ext_resource type="Texture2D" uid="uid://cgenco43aneod" path="res://addons/proton_scatter/demos/assets/textures/t_leaves_1.png" id="2_l2uea"]
[sub_resource type="Gradient" id="Gradient_yy7fg"]
offsets = PackedFloat32Array(0, 0.726872, 0.934272)
colors = PackedColorArray(0.333333, 0.486275, 0.556863, 1, 0.496467, 0.55, 0.1485, 1, 0.898039, 0.670588, 0.0196078, 1)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_rwvaq"]
gradient = SubResource("Gradient_yy7fg")
[sub_resource type="FastNoiseLite" id="FastNoiseLite_wpihy"]
seed = 1
frequency = 0.002
[sub_resource type="NoiseTexture2D" id="NoiseTexture_tgrrr"]
in_3d_space = true
seamless = true
seamless_blend_skirt = 0.65
noise = SubResource("FastNoiseLite_wpihy")
[resource]
render_priority = 0
shader = ExtResource("1_8py1k")
shader_parameter/alpha_scissor_threshold = 0.5
shader_parameter/camera_bend_strength = 0.0
shader_parameter/grass_height = 1.0
shader_parameter/noise_scale = 12.0
shader_parameter/secondary_attenuation = 0.2
shader_parameter/secondary_color = Color(0, 0.305882, 0.211765, 1)
shader_parameter/texture_albedo = ExtResource("2_l2uea")
shader_parameter/texture_gradient = SubResource("GradientTexture1D_rwvaq")
shader_parameter/texture_noise = SubResource("NoiseTexture_tgrrr")
shader_parameter/transmission = Color(1, 0.975296, 0.943663, 1)
shader_parameter/wind_direction = Vector2(1, -0.5)
shader_parameter/wind_speed = 0.5
shader_parameter/wind_strength = 0.05

View File

@ -0,0 +1,6 @@
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://ds2hjlo70hglg"]
[ext_resource type="Texture2D" uid="uid://bjdgw8o5tr1a3" path="res://addons/proton_scatter/demos/assets/textures/mushroom.png" id="1_y0tuv"]
[resource]
albedo_texture = ExtResource("1_y0tuv")

View File

@ -0,0 +1,35 @@
[gd_resource type="ShaderMaterial" load_steps=7 format=3 uid="uid://d28lq2qtgdyie"]
[ext_resource type="Shader" uid="uid://bv00dvm0n7u46" path="res://addons/proton_scatter/demos/assets/materials/leaves.gdshader" id="1_hlncd"]
[ext_resource type="Texture2D" uid="uid://ctpb1w0cr8tqc" path="res://addons/proton_scatter/demos/assets/textures/t_pine_branch.png" id="2_yef44"]
[sub_resource type="Gradient" id="Gradient_pookg"]
offsets = PackedFloat32Array(0.38342, 0.694301, 1)
colors = PackedColorArray(0.059375, 0.078125, 0.07, 1, 0.628287, 0.73, 0.1752, 1, 0.897921, 1, 0, 1)
[sub_resource type="GradientTexture1D" id="GradientTexture1D_n86jv"]
gradient = SubResource("Gradient_pookg")
[sub_resource type="FastNoiseLite" id="FastNoiseLite_t7o5y"]
seed = 1
frequency = 0.002
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_03p8g"]
in_3d_space = true
seamless = true
seamless_blend_skirt = 0.65
noise = SubResource("FastNoiseLite_t7o5y")
[resource]
render_priority = 0
shader = ExtResource("1_hlncd")
shader_parameter/texture_albedo = ExtResource("2_yef44")
shader_parameter/texture_gradient = SubResource("GradientTexture1D_n86jv")
shader_parameter/texture_noise = SubResource("NoiseTexture2D_03p8g")
shader_parameter/alpha_scissor_threshold = 0.3
shader_parameter/transmission = Color(0.745098, 0.741176, 0, 1)
shader_parameter/total_height = 4.046
shader_parameter/wind_direction = Vector2(1, -0.5)
shader_parameter/wind_speed = 0.2
shader_parameter/wind_strength = 0.05
shader_parameter/noise_scale = 12.0

View File

@ -0,0 +1,10 @@
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://i0jgjmbbl2m5"]
[ext_resource type="Texture2D" uid="uid://drdh36j6mu3ah" path="res://addons/proton_scatter/demos/assets/textures/t_rock_dirty.png" id="1_hx37f"]
[resource]
albedo_color = Color(0.439216, 0.407843, 0.388235, 1)
albedo_texture = ExtResource("1_hx37f")
metallic_specular = 0.3
uv1_triplanar = true
uv1_world_triplanar = true

View File

@ -0,0 +1,7 @@
[gd_resource type="StandardMaterial3D" load_steps=2 format=3 uid="uid://d01d0h08lqqn6"]
[ext_resource type="Texture2D" uid="uid://c7pop5xgpxtiv" path="res://addons/proton_scatter/demos/assets/textures/t_tree_bark_rough.png" id="1_g4son"]
[resource]
albedo_color = Color(0.470588, 0.376471, 0.309804, 1)
albedo_texture = ExtResource("1_g4son")

View File

@ -0,0 +1,90 @@
// Source: https://godotshaders.com/shader/toon-water-shader/
shader_type spatial;
const float SMOOTHSTEP_AA = 0.01;
uniform sampler2D surfaceNoise;
uniform sampler2D distortNoise;
uniform sampler2D DEPTH_TEXTURE : hint_depth_texture, filter_linear_mipmap;
uniform float beer_factor = 0.8;
uniform float foam_distance = 0.01;
uniform float foam_max_distance = 0.4;
uniform float foam_min_distance = 0.04;
uniform vec4 foam_color: source_color = vec4(1.0);
uniform vec2 surface_noise_tiling = vec2(1.0, 4.0);
uniform vec3 surface_noise_scroll = vec3(0.03, 0.03, 0.0);
uniform float surface_noise_cutoff: hint_range(0, 1) = 0.777;
uniform float surface_distortion_amount: hint_range(0, 1) = 0.27;
uniform vec4 _DepthGradientShallow: source_color = vec4(0.325, 0.807, 0.971, 0.725);
uniform vec4 _DepthGradientDeep: source_color = vec4(0.086, 0.407, 1, 0.749);
uniform float _DepthMaxDistance: hint_range(0, 1) = 1.0;
uniform float _DepthFactor = 1.0;
uniform float roughness = 0.25;
uniform float specular = 0.75;
varying vec2 noiseUV;
varying vec2 distortUV;
varying vec3 viewNormal;
vec4 alphaBlend(vec4 top, vec4 bottom)
{
vec3 color = (top.rgb * top.a) + (bottom.rgb * (1.0 - top.a));
float alpha = top.a + bottom.a * (1.0 - top.a);
return vec4(color, alpha);
}
void vertex() {
viewNormal = (MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
noiseUV = UV * surface_noise_tiling;
distortUV = UV;
}
void fragment(){
// https://www.youtube.com/watch?v=Jq3he9Lbj7M
float depthVal = texture(DEPTH_TEXTURE, SCREEN_UV).r;
float depth = PROJECTION_MATRIX[3][2] / (depthVal + PROJECTION_MATRIX[2][2]);
depth = depth + VERTEX.z;
depth = exp(-depth * beer_factor);
depth = 1.0 - depth;
// Still unsure how to get properly the NORMAL from the camera
// This was generated by ChatGPT xD
vec4 view_pos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depthVal, 1.0);
view_pos /= view_pos.w;
vec3 existingNormal = normalize(cross( dFdx(view_pos.xyz), dFdy(view_pos.xyz)));
float normalDot = clamp(dot(existingNormal.xyz, viewNormal), 0.0, 1.0);
float foamDistance = mix(foam_max_distance, foam_min_distance, normalDot);
float foamDepth = clamp(depth / foamDistance, 0.0, 1.0);
float surfaceNoiseCutoff = foamDepth * surface_noise_cutoff;
vec4 distortNoiseSample = texture(distortNoise, distortUV);
vec2 distortAmount = (distortNoiseSample.xy * 2.0 -1.0) * surface_distortion_amount;
vec2 noise_uv = vec2(
(noiseUV.x + TIME * surface_noise_scroll.x) + distortAmount.x ,
(noiseUV.y + TIME * surface_noise_scroll.y + distortAmount.y)
);
float surfaceNoiseSample = texture(surfaceNoise, noise_uv).r;
float surfaceNoiseAmount = smoothstep(surfaceNoiseCutoff - SMOOTHSTEP_AA, surfaceNoiseCutoff + SMOOTHSTEP_AA, surfaceNoiseSample);
float waterDepth = clamp(depth / _DepthMaxDistance, 0.0, 1.0) * _DepthFactor;
vec4 waterColor = mix(_DepthGradientShallow, _DepthGradientDeep, waterDepth);
vec4 surfaceNoiseColor = foam_color;
surfaceNoiseColor.a *= surfaceNoiseAmount;
vec4 color = alphaBlend(surfaceNoiseColor, waterColor);
ALBEDO = color.rgb;
ALPHA = color.a;
ROUGHNESS = roughness;
SPECULAR = specular;
}

View File

@ -0,0 +1 @@
uid://d0qg1ivc7h7j8

View File

@ -0,0 +1,40 @@
[gd_resource type="ShaderMaterial" load_steps=6 format=3 uid="uid://c7mw5tryqfggw"]
[ext_resource type="Shader" path="res://addons/proton_scatter/demos/assets/materials/m_water.gdshader" id="1_j8rl3"]
[sub_resource type="FastNoiseLite" id="FastNoiseLite_7bjdc"]
noise_type = 2
fractal_type = 3
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_wxuht"]
seamless = true
noise = SubResource("FastNoiseLite_7bjdc")
[sub_resource type="FastNoiseLite" id="FastNoiseLite_dx86n"]
noise_type = 2
domain_warp_enabled = true
[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_1j0ob"]
seamless = true
noise = SubResource("FastNoiseLite_dx86n")
[resource]
render_priority = 0
shader = ExtResource("1_j8rl3")
shader_parameter/beer_factor = 4.0
shader_parameter/foam_distance = 0.01
shader_parameter/foam_max_distance = 0.345
shader_parameter/foam_min_distance = 0.05
shader_parameter/foam_color = Color(1, 1, 1, 0.784314)
shader_parameter/surface_noise_tiling = Vector2(1, 4)
shader_parameter/surface_noise_scroll = Vector3(0.03, 0.03, 0)
shader_parameter/surface_noise_cutoff = 0.875
shader_parameter/surface_distortion_amount = 0.65
shader_parameter/_DepthGradientShallow = Color(0.435294, 0.647059, 0.972549, 0.72549)
shader_parameter/_DepthGradientDeep = Color(0.0823529, 0.392157, 0.701961, 0.862745)
shader_parameter/_DepthMaxDistance = 1.0
shader_parameter/_DepthFactor = 1.0
shader_parameter/roughness = 0.001
shader_parameter/specular = 0.5
shader_parameter/surfaceNoise = SubResource("NoiseTexture2D_1j0ob")
shader_parameter/distortNoise = SubResource("NoiseTexture2D_wxuht")

Binary file not shown.

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://d1d1fag0m04yc"
path="res://.godot/imported/brick.glb-d79404ecf88b29143e6e07e77bacb44c.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/brick.glb"
dest_files=["res://.godot/imported/brick.glb-d79404ecf88b29143e6e07e77bacb44c.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

Binary file not shown.

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dbb4culid55v5"
path="res://.godot/imported/bush.glb-28e0128066fe8d913839a6b96204b1c6.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/bush.glb"
dest_files=["res://.godot/imported/bush.glb-28e0128066fe8d913839a6b96204b1c6.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cmqlv88xp71tw"
path="res://.godot/imported/dead_branch.glb-e4e41ce877f1ef0b2d20a7b89af5de7b.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/dead_branch.glb"
dest_files=["res://.godot/imported/dead_branch.glb-e4e41ce877f1ef0b2d20a7b89af5de7b.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://6gxiul1pw13t"
path="res://.godot/imported/fence_planks.glb-4cee642c3e514323763ee9631fb323e9.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/fence_planks.glb"
dest_files=["res://.godot/imported/fence_planks.glb-4cee642c3e514323763ee9631fb323e9.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

Binary file not shown.

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://d3f4d3m7n8tpr"
path="res://.godot/imported/gobot.glb-36505aa16090f2bc2f34fbe5362f44e8.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/gobot.glb"
dest_files=["res://.godot/imported/gobot.glb-36505aa16090f2bc2f34fbe5362f44e8.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

Binary file not shown.

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://018flajgtx7t"
path="res://.godot/imported/grass.glb-0ef73576363e4c601b9f45b1787e1487.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/grass.glb"
dest_files=["res://.godot/imported/grass.glb-0ef73576363e4c601b9f45b1787e1487.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

Binary file not shown.

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://dcnm2ijk7hj4j"
path="res://.godot/imported/grass_2.glb-2dc56a32acf64077863c701e8b94ea02.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/grass_2.glb"
dest_files=["res://.godot/imported/grass_2.glb-2dc56a32acf64077863c701e8b94ea02.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bxqkgplyoipsx"
path="res://.godot/imported/large_rock.glb-f7a7a73f49167cee4ed84e7342d1f507.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/large_rock.glb"
dest_files=["res://.godot/imported/large_rock.glb-f7a7a73f49167cee4ed84e7342d1f507.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://c38uugpgw7hjm"
path="res://.godot/imported/mushrooms.glb-64c83b02a53711f9983c978d53ab0f12.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/mushrooms.glb"
dest_files=["res://.godot/imported/mushrooms.glb-64c83b02a53711f9983c978d53ab0f12.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bhums0j31gm5n"
path="res://.godot/imported/pine_tree.glb-662cc3d34707ccadde24f89b98fadf88.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/pine_tree.glb"
dest_files=["res://.godot/imported/pine_tree.glb-662cc3d34707ccadde24f89b98fadf88.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,43 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b81l25tbebki4"
path="res://.godot/imported/small_rock.glb-9b9690e480edfa6e23f0243045338de9.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/small_rock.glb"
dest_files=["res://.godot/imported/small_rock.glb-9b9690e480edfa6e23f0243045338de9.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={
"materials": {
"@MATERIAL:0": {
"use_external/path": "uid://i0jgjmbbl2m5"
}
}
}
gltf/naming_version=0
gltf/embedded_image_handling=1

Binary file not shown.

View File

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://c3mfolo7c5uvh"
path="res://.godot/imported/tree.glb-86fae6fb2428df7d74097b1a7c75b288.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/models/tree.glb"
dest_files=["res://.godot/imported/tree.glb-86fae6fb2428df7d74097b1a7c75b288.scn"]
[params]
nodes/root_type="Node3D"
nodes/root_name="Scene Root"
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=0
gltf/embedded_image_handling=1

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://bodkixm8bubes"]
[ext_resource type="PackedScene" uid="uid://c38uugpgw7hjm" path="res://addons/proton_scatter/demos/assets/models/mushrooms.glb" id="1_spmys"]
[ext_resource type="Material" uid="uid://ds2hjlo70hglg" path="res://addons/proton_scatter/demos/assets/materials/m_mushroom.tres" id="2_y6jw1"]
[node name="mushrooms" instance=ExtResource("1_spmys")]
[node name="Sphere001" parent="." index="0"]
surface_material_override/0 = ExtResource("2_y6jw1")

View File

@ -0,0 +1,31 @@
[gd_scene load_steps=6 format=3 uid="uid://caqxfqurbp3ku"]
[ext_resource type="PackedScene" uid="uid://bhums0j31gm5n" path="res://addons/proton_scatter/demos/assets/models/pine_tree.glb" id="1_hw1e5"]
[ext_resource type="Material" uid="uid://d01d0h08lqqn6" path="res://addons/proton_scatter/demos/assets/materials/m_trunk.tres" id="2_cgtpc"]
[ext_resource type="Material" uid="uid://d28lq2qtgdyie" path="res://addons/proton_scatter/demos/assets/materials/m_pine_leaves.tres" id="2_xnytt"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_2xqpo"]
radius = 0.0750397
height = 1.3553
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_uhp33"]
radius = 0.101768
height = 0.489166
[node name="pine_tree" instance=ExtResource("1_hw1e5")]
[node name="Trunk" parent="." index="0"]
surface_material_override/0 = ExtResource("2_cgtpc")
[node name="Leaves" parent="." index="1"]
surface_material_override/0 = ExtResource("2_xnytt")
[node name="StaticBody3D" type="StaticBody3D" parent="." index="2"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" index="0"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.661681, 0)
shape = SubResource("CapsuleShape3D_2xqpo")
[node name="CollisionShape3D2" type="CollisionShape3D" parent="StaticBody3D" index="1"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.015189, 0)
shape = SubResource("CapsuleShape3D_uhp33")

View File

@ -0,0 +1,9 @@
[gd_scene load_steps=3 format=3 uid="uid://bltmr2xgs8nq1"]
[ext_resource type="PackedScene" uid="uid://b81l25tbebki4" path="res://addons/proton_scatter/demos/assets/models/small_rock.glb" id="1_e2qk6"]
[ext_resource type="Material" uid="uid://i0jgjmbbl2m5" path="res://addons/proton_scatter/demos/assets/materials/m_rock.tres" id="2_clsfy"]
[node name="small_rock" instance=ExtResource("1_e2qk6")]
[node name="SmallRock" parent="." index="0"]
surface_material_override/0 = ExtResource("2_clsfy")

Binary file not shown.

View File

@ -0,0 +1,53 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://c45jyo5mdo5pj"
path="res://.godot/imported/source.blend-6553bbdea542ba64489fdff7990920e8.scn"
[deps]
source_file="res://addons/proton_scatter/demos/assets/source.blend"
dest_files=["res://.godot/imported/source.blend-6553bbdea542ba64489fdff7990920e8.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://6xc5b38d25gf"
path.s3tc="res://.godot/imported/grid.png-491581bd748087c94a4b25c27dcb904c.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/grid.png"
dest_files=["res://.godot/imported/grid.png-491581bd748087c94a4b25c27dcb904c.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bjdgw8o5tr1a3"
path.s3tc="res://.godot/imported/mushroom.png-36c0c492b0f6a79e2aa68780d9a86c03.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/mushroom.png"
dest_files=["res://.godot/imported/mushroom.png-36c0c492b0f6a79e2aa68780d9a86c03.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bgc5rl13dopuj"
path.s3tc="res://.godot/imported/sky_2.png-3246d9ba45b69131effdf515c69428b4.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/sky_2.png"
dest_files=["res://.godot/imported/sky_2.png-3246d9ba45b69131effdf515c69428b4.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b2a6ylo2enm4g"
path.s3tc="res://.godot/imported/t_bush.png-644d0e155c07db6d89949c275e110f2a.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_bush.png"
dest_files=["res://.godot/imported/t_bush.png-644d0e155c07db6d89949c275e110f2a.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c08mm3p2ehvr6"
path="res://.godot/imported/t_grass.png-2144df75763a0a189eba3035fc0b94aa.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_grass.png"
dest_files=["res://.godot/imported/t_grass.png-2144df75763a0a189eba3035fc0b94aa.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d23p13yi7asw0"
path.s3tc="res://.godot/imported/t_grass_2.png-e3f17c2ee365553e0f39f2b5865e73de.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_grass_2.png"
dest_files=["res://.godot/imported/t_grass_2.png-e3f17c2ee365553e0f39f2b5865e73de.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drmmcy11y7mho"
path.s3tc="res://.godot/imported/t_leaves_1.png-1d55b008d9a51575d696e027028d7b90.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_leaves_1.png"
dest_files=["res://.godot/imported/t_leaves_1.png-1d55b008d9a51575d696e027028d7b90.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ctpb1w0cr8tqc"
path.s3tc="res://.godot/imported/t_pine_branch.png-912fabf99bebd2eee6af2f445a54650e.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_pine_branch.png"
dest_files=["res://.godot/imported/t_pine_branch.png-912fabf99bebd2eee6af2f445a54650e.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dqa2jfs1jy0hq"
path.s3tc="res://.godot/imported/t_rock.jpg-ae52c049ee9fab72f1ddf136050fd9ee.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_rock.jpg"
dest_files=["res://.godot/imported/t_rock.jpg-ae52c049ee9fab72f1ddf136050fd9ee.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drdh36j6mu3ah"
path.s3tc="res://.godot/imported/t_rock_dirty.png-da395e5af8ffe9e04730e7e21eb6a86a.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://addons/proton_scatter/demos/assets/textures/t_rock_dirty.png"
dest_files=["res://.godot/imported/t_rock_dirty.png-da395e5af8ffe9e04730e7e21eb6a86a.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Some files were not shown because too many files have changed in this diff Show More