614 lines
15 KiB
Plaintext
614 lines
15 KiB
Plaintext
[gd_scene load_steps=27 format=2]
|
|
|
|
[ext_resource path="res://material_maker/tools/map_renderer/curvature_generator.gd" type="Script" id=1]
|
|
[ext_resource path="res://material_maker/tools/map_renderer/map_renderer.gd" type="Script" id=2]
|
|
[ext_resource path="res://material_maker/tools/map_renderer/bvh_generator.gd" type="Script" id=3]
|
|
|
|
[sub_resource type="World" id=1]
|
|
|
|
[sub_resource type="Shader" id=2]
|
|
code = "shader_type spatial;
|
|
render_mode depth_test_disable,depth_draw_always,unshaded,cull_disabled,world_vertex_coords;
|
|
|
|
varying vec3 p;
|
|
|
|
void vertex() {
|
|
p = NORMAL;
|
|
VERTEX=vec3(UV, 0.5);
|
|
}
|
|
|
|
void fragment() {
|
|
ALBEDO = 0.5*normalize(p)+vec3(0.5);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=3]
|
|
shader = SubResource( 2 )
|
|
|
|
[sub_resource type="Shader" id=4]
|
|
code = "shader_type spatial;
|
|
render_mode depth_test_disable,depth_draw_always,unshaded,cull_disabled,world_vertex_coords;
|
|
|
|
uniform vec3 position = vec3(0.0);
|
|
uniform vec3 size = vec3(1.0);
|
|
|
|
varying vec3 p;
|
|
|
|
void vertex() {
|
|
p = (VERTEX-position)/size;
|
|
VERTEX=vec3(UV, 0.5);
|
|
}
|
|
|
|
|
|
void fragment() {
|
|
ALBEDO = p;
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=5]
|
|
shader = SubResource( 4 )
|
|
shader_param/position = Vector3( 0, 0, 0 )
|
|
shader_param/size = Vector3( 1, 1, 1 )
|
|
|
|
[sub_resource type="Shader" id=6]
|
|
code = "shader_type spatial;
|
|
render_mode depth_test_disable,depth_draw_always,unshaded,cull_disabled,world_vertex_coords;
|
|
|
|
uniform vec3 position = vec3(0.0);
|
|
uniform vec3 size = vec3(1.0);
|
|
|
|
void vertex() {
|
|
VERTEX=vec3(UV, 0.5);
|
|
}
|
|
|
|
void fragment() {
|
|
ALBEDO = vec3(1.0);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=7]
|
|
shader = SubResource( 6 )
|
|
shader_param/position = Vector3( 0, 0, 0 )
|
|
shader_param/size = Vector3( 1, 1, 1 )
|
|
|
|
[sub_resource type="Shader" id=8]
|
|
code = "shader_type spatial;
|
|
render_mode depth_test_disable,depth_draw_always,unshaded,cull_disabled,world_vertex_coords;
|
|
|
|
varying float curvature;
|
|
|
|
void vertex() {
|
|
curvature = dot(COLOR, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0));
|
|
VERTEX=vec3(UV, 0.5);
|
|
}
|
|
|
|
void fragment() {
|
|
ALBEDO = vec3(curvature);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=9]
|
|
shader = SubResource( 8 )
|
|
|
|
[sub_resource type="Shader" id=10]
|
|
code = "shader_type spatial;
|
|
render_mode cull_disabled, unshaded;
|
|
|
|
uniform sampler2D bvh_data;
|
|
uniform float max_dist;
|
|
uniform int iteration = 1;
|
|
|
|
uniform sampler2D prev_iteration_tex;
|
|
|
|
varying vec3 normal;
|
|
varying vec3 model_vert;
|
|
|
|
void vertex() {
|
|
model_vert = VERTEX;
|
|
normal = NORMAL;
|
|
VERTEX = vec3(UV, 0.5);
|
|
}
|
|
|
|
float intersect_aabb(vec3 ray_origin, vec3 ray_dir, vec3 box_min, vec3 box_max, bool solid) {
|
|
vec3 tMin = (box_min - ray_origin) / ray_dir;
|
|
vec3 tMax = (box_max - ray_origin) / ray_dir;
|
|
vec3 t1 = min(tMin, tMax);
|
|
vec3 t2 = max(tMin, tMax);
|
|
float tNear = max(max(t1.x, t1.y), t1.z);
|
|
float tFar = min(min(t2.x, t2.y), t2.z);
|
|
|
|
if(tNear > tFar || (tFar < 0.0 && tNear < 0.0)) {
|
|
return -1.0;
|
|
}
|
|
if(tNear < 0.0) {
|
|
float temp = tNear;
|
|
tNear = tFar;
|
|
tFar = temp;
|
|
tNear *= float(!solid);
|
|
}
|
|
|
|
return tNear;
|
|
}
|
|
|
|
float intersects_triangle(vec3 ray_start, vec3 ray_dir, vec3 v0, vec3 v1, vec3 v2) {
|
|
vec3 e1 = v1 - v0;
|
|
vec3 e2 = v2 - v0;
|
|
vec3 h = cross(ray_dir, e2);
|
|
float a = dot(e1, h);
|
|
if (abs(a) < 0.000001) { // Parallel test.
|
|
return -1.0;
|
|
}
|
|
|
|
float f = 1.0 / a;
|
|
|
|
vec3 s = ray_start - v0;
|
|
float u = f * dot(s, h);
|
|
|
|
if (u < 0.0 || u > 1.0) {
|
|
return -1.0;
|
|
}
|
|
|
|
vec3 q = cross(s, e1);
|
|
float v = f * dot(ray_dir, q);
|
|
|
|
if (v < 0.0 || u + v > 1.0) {
|
|
return -1.0;
|
|
}
|
|
|
|
// At this stage we can compute t to find out where
|
|
// the intersection point is on the line.
|
|
float t = f * dot(e2, q);
|
|
return t > 0.0000001 ? t : -1.0;
|
|
}
|
|
|
|
vec4 get_data(int index) {
|
|
ivec2 data_size = textureSize(bvh_data, 0);
|
|
return texelFetch(bvh_data, ivec2(
|
|
index % data_size.x,
|
|
index / data_size.x
|
|
), 0);
|
|
}
|
|
|
|
float intersects_bvh(vec3 ray_start, vec3 ray_dir, out vec3 normal_hit) {
|
|
int offset_to_nodes = int(get_data(0)[0]);
|
|
vec4 root_data_0 = get_data(int(get_data(1)[0]) + offset_to_nodes);
|
|
vec4 root_data_1 = get_data(int(get_data(1)[0]) + offset_to_nodes + 1);
|
|
|
|
float t = intersect_aabb(ray_start, ray_dir, root_data_0.xyz, root_data_1.xyz, false);
|
|
if(t == -1.0) {
|
|
return 65536.0;
|
|
}
|
|
|
|
float prev_hit = t;
|
|
t = 65536.0; // Set to large number
|
|
vec3 tri[3] = vec3[];
|
|
int min_node_idx = 0;
|
|
|
|
int stack_point = 0;
|
|
ivec3 node_stack[128] = ivec3[]; // ivec3
|
|
|
|
int curr_node_idx = 0;
|
|
bool moving_up = false;
|
|
|
|
for(int i = 0; i < 256; i++) {
|
|
if(moving_up && stack_point <= 0) {
|
|
break;
|
|
}
|
|
int node_data_off = int(get_data(1 + curr_node_idx)[0]) + offset_to_nodes;
|
|
vec4 node_data_0 = get_data(node_data_off);
|
|
vec4 node_data_1 = get_data(node_data_off + 1);
|
|
int level = int(node_data_0[3]);
|
|
|
|
if(!moving_up) { // Moving down node hierarchy
|
|
if(node_data_1[3] > 0.0) { // Is a leaf node
|
|
for(int j = node_data_off + 2; j < node_data_off + 2 + int(node_data_1[3]) * 3; j+=3) {
|
|
vec3 tri_a = get_data(j).xyz;
|
|
vec3 tri_b = get_data(j + 1).xyz;
|
|
vec3 tri_c = get_data(j + 2).xyz;
|
|
float tri_t = intersects_triangle(ray_start, ray_dir,
|
|
tri_a, tri_b, tri_c
|
|
);
|
|
if(tri_t != -1.0) {
|
|
if(tri_t < t) {
|
|
tri[0] = tri_a;
|
|
tri[1] = tri_b;
|
|
tri[2] = tri_c;
|
|
// print(curr_node_idx)
|
|
min_node_idx = curr_node_idx;
|
|
}
|
|
t = min(t, tri_t);
|
|
}
|
|
}
|
|
|
|
stack_point -= 1;
|
|
if(stack_point <= 0) {
|
|
break;
|
|
}
|
|
if(node_stack[stack_point][1] == level) { // next node in stack is sibling
|
|
if(t < intBitsToFloat(node_stack[stack_point][0])) { // no chance to get better hit from sibling
|
|
stack_point -= 1;
|
|
moving_up = true;
|
|
}
|
|
} else {
|
|
moving_up = true;
|
|
}
|
|
prev_hit = intBitsToFloat(node_stack[stack_point][0]);
|
|
curr_node_idx = node_stack[stack_point][2];
|
|
} else {
|
|
|
|
// Push self onto stack
|
|
node_stack[stack_point] = ivec3(floatBitsToInt(prev_hit), level, curr_node_idx);
|
|
stack_point += 1;
|
|
|
|
ivec2 child_indices = ivec2(get_data(node_data_off + 2).xy);
|
|
int left_data_off = int(get_data(1 + child_indices[0])[0]) + offset_to_nodes;
|
|
vec4 left_data_0 = get_data(left_data_off);
|
|
vec4 left_data_1 = get_data(left_data_off + 1);
|
|
int right_data_off = int(get_data(1 + child_indices[1])[0]) + offset_to_nodes;
|
|
vec4 right_data_0 = get_data(right_data_off);
|
|
vec4 right_data_1 = get_data(right_data_off + 1);
|
|
|
|
float t_left = intersect_aabb(ray_start, ray_dir, left_data_0.xyz, left_data_1.xyz, true);
|
|
float t_right = intersect_aabb(ray_start, ray_dir, right_data_0.xyz, right_data_1.xyz, true);
|
|
|
|
if(t_right == -1.0 && t_left != -1.0) { // only left node hit
|
|
prev_hit = t_left;
|
|
curr_node_idx = child_indices[0];
|
|
} else if(t_left == -1.0 && t_right != -1.0) { // only right node hit
|
|
prev_hit = t_right;
|
|
curr_node_idx = child_indices[1];
|
|
} else if(t_left < t_right && t_left != -1.0) { // left node hits closer
|
|
node_stack[stack_point] = ivec3(floatBitsToInt(t_right), int(right_data_0[3]), child_indices[1]);
|
|
stack_point += 1;
|
|
prev_hit = t_left;
|
|
curr_node_idx = child_indices[0];
|
|
} else if(t_right <= t_left && t_right != -1.0) { // right node hits closer
|
|
node_stack[stack_point] = ivec3(floatBitsToInt(t_left), int(left_data_0[3]), child_indices[0]);
|
|
stack_point += 1;
|
|
prev_hit = t_right;
|
|
curr_node_idx = child_indices[1];
|
|
} else { // no hit
|
|
stack_point -= 2;
|
|
if(stack_point <= 0) {
|
|
break;
|
|
}
|
|
if(node_stack[stack_point][1] == level) { // next node in stack is sibling
|
|
if(t < intBitsToFloat(node_stack[stack_point][0])) { // no chance to get better hit from sibling
|
|
stack_point -= 1;
|
|
moving_up = true;
|
|
}
|
|
} else {
|
|
moving_up = true;
|
|
}
|
|
prev_hit = intBitsToFloat(node_stack[max(stack_point, 0)][0]);
|
|
curr_node_idx = node_stack[max(stack_point, 0)][2];
|
|
}
|
|
}
|
|
} else { // Moving up hierarchy
|
|
stack_point -= 1;
|
|
if(stack_point <= 0) {
|
|
break;
|
|
}
|
|
if(node_stack[stack_point][1] == level) { // next node in stack is sibling
|
|
if(t < intBitsToFloat(node_stack[stack_point][0])) { // no chance to get better hit from sibling
|
|
stack_point -= 1;
|
|
} else {
|
|
moving_up = false;
|
|
}
|
|
}
|
|
prev_hit = intBitsToFloat(node_stack[max(stack_point, 0)][0]);
|
|
curr_node_idx = node_stack[max(stack_point, 0)][2];
|
|
}
|
|
}
|
|
|
|
normal_hit = normalize(cross(tri[2] - tri[0], tri[1] - tri[0]));
|
|
return t;
|
|
}
|
|
|
|
vec3 random_hemi_point(vec3 rand, vec3 norm) {
|
|
const float PI = 3.141592653;
|
|
|
|
float ang1 = (rand.x + 1.0) * PI; // [-1..1) -> [0..2*PI)
|
|
float u = rand.y; // [-1..1), cos and acos(2v-1) cancel each other out, so we arrive at [-1..1)
|
|
float u2 = u * u;
|
|
float sqrt1MinusU2 = sqrt(1.0 - u2);
|
|
float x = sqrt1MinusU2 * cos(ang1);
|
|
float y = sqrt1MinusU2 * sin(ang1);
|
|
float z = u;
|
|
vec3 v = vec3(x, y, z);
|
|
|
|
return v * sign(dot(v, norm));
|
|
}
|
|
|
|
float rand(vec2 co){
|
|
return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
|
|
}
|
|
|
|
void fragment() {
|
|
vec3 model_norm = normalize(normal) * sign(max_dist);
|
|
vec3 ddx_vert = dFdx(model_vert);
|
|
vec3 ddy_vert = dFdy(model_vert);
|
|
vec3 true_normal = normalize(cross(ddy_vert, ddx_vert)) * sign(max_dist);
|
|
|
|
vec3 ray_start = model_vert + model_norm * 0.0001;
|
|
|
|
vec3 ray_dir;
|
|
for(int i = 0; i < 5; i++) {
|
|
ray_dir = random_hemi_point(vec3(
|
|
rand(SCREEN_UV + float(iteration)),
|
|
rand(-SCREEN_UV.yx + float(iteration)),
|
|
0.0
|
|
), model_norm);
|
|
if(dot(ray_dir, true_normal) > 0.0) {
|
|
break;
|
|
}
|
|
}
|
|
float hit = intersects_bvh(ray_start, ray_dir, NORMAL);
|
|
if(hit == 65536.0 || hit < 0.0) {
|
|
hit = abs(max_dist);//ALPHA = 0.0;
|
|
}
|
|
hit /= abs(max_dist);
|
|
// float weight = dot(model_norm, ray_dir);
|
|
|
|
ALBEDO = mix(texture(prev_iteration_tex, SCREEN_UV).rgb, vec3(hit), 1.0/float(iteration));
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ViewportTexture" id=11]
|
|
viewport_path = NodePath(".")
|
|
|
|
[sub_resource type="ShaderMaterial" id=12]
|
|
resource_local_to_scene = true
|
|
shader = SubResource( 10 )
|
|
shader_param/max_dist = -2.0
|
|
shader_param/iteration = 1
|
|
shader_param/prev_iteration_tex = SubResource( 11 )
|
|
|
|
[sub_resource type="Shader" id=13]
|
|
code = "shader_type spatial;
|
|
render_mode unshaded, cull_disabled;
|
|
|
|
uniform sampler2D tex;
|
|
uniform int radius = 3;
|
|
uniform float size = 512.0;
|
|
|
|
void vertex() {
|
|
VERTEX = vec3(UV, 0.5);
|
|
}
|
|
|
|
void fragment() {
|
|
if(texture(tex, SCREEN_UV).a == 0.0) {
|
|
discard;
|
|
}
|
|
|
|
vec3 color = vec3(0.0);
|
|
float weight = 0.0;
|
|
for(int y = -radius; y < radius+1; y++) {
|
|
for(int x = -radius; x < radius+1; x++) {
|
|
vec4 col = texture(tex, SCREEN_UV + vec2(ivec2(x, y)) / size, 0);
|
|
color += col.rgb * col.a;
|
|
weight += col.a;
|
|
}
|
|
}
|
|
ALBEDO = color / weight;
|
|
}"
|
|
|
|
[sub_resource type="ShaderMaterial" id=14]
|
|
resource_local_to_scene = true
|
|
shader = SubResource( 13 )
|
|
shader_param/radius = 0
|
|
shader_param/size = 512.0
|
|
shader_param/tex = SubResource( 11 )
|
|
|
|
[sub_resource type="Shader" id=15]
|
|
code = "shader_type canvas_item;
|
|
render_mode blend_disabled;
|
|
|
|
uniform sampler2D tex;
|
|
uniform float size = 512.0;
|
|
|
|
const int STEPS = 64;
|
|
|
|
vec4 dilate_distance_h(vec2 uv) {
|
|
vec2 e = vec2(1.0/size, 0.0);
|
|
float d = float(STEPS)/size;
|
|
float rv = 0.0;
|
|
vec2 source_uv;
|
|
vec4 source_color;
|
|
for (int i = 0; i < STEPS; ++i) {
|
|
source_uv = uv+float(i)*e;
|
|
source_color = texture(tex, source_uv);
|
|
if (source_color.a >= 1.0) {
|
|
rv = 1.0-float(i)*e.x/d;
|
|
break;
|
|
}
|
|
source_uv = uv-float(i)*e;
|
|
source_color = texture(tex, source_uv);
|
|
if (source_color.a >= 1.0) {
|
|
rv = 1.0-float(i)*e.x/d;
|
|
break;
|
|
}
|
|
}
|
|
return vec4(source_color.rgb, rv);
|
|
}
|
|
|
|
void fragment() {
|
|
COLOR = dilate_distance_h(UV);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=16]
|
|
shader = SubResource( 15 )
|
|
shader_param/size = 512.0
|
|
|
|
[sub_resource type="Shader" id=17]
|
|
code = "shader_type canvas_item;
|
|
render_mode blend_disabled;
|
|
|
|
uniform sampler2D tex;
|
|
uniform float size = 512.0;
|
|
|
|
const int STEPS = 64;
|
|
|
|
vec4 dilate_distance_v(vec2 uv) {
|
|
vec2 e = vec2(0.0, 1.0/size);
|
|
float d = float(STEPS)/size;
|
|
vec4 p = texture(tex, uv);
|
|
for (int i = 0; i < STEPS; ++i) {
|
|
vec2 dx = float(i)*e;
|
|
vec4 p2 = texture(tex, uv+dx);
|
|
if (p2.a > p.a) {
|
|
p2.a = 1.0-sqrt((1.0-p2.a)*(1.0-p2.a)+dx.y*dx.y/d/d);
|
|
p = mix(p, p2, step(p.a, p2.a));
|
|
}
|
|
p2 = texture(tex, uv-dx);
|
|
if (p2.a > p.a) {
|
|
p2.a = 1.0-sqrt((1.0-p2.a)*(1.0-p2.a)+dx.y*dx.y/d/d);
|
|
p = mix(p, p2, step(p.a, p2.a));
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void fragment() {
|
|
COLOR = dilate_distance_v(UV);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=18]
|
|
shader = SubResource( 17 )
|
|
shader_param/size = 512.0
|
|
|
|
[sub_resource type="Shader" id=19]
|
|
code = "shader_type canvas_item;
|
|
render_mode blend_disabled;
|
|
|
|
uniform sampler2D tex;
|
|
uniform float size = 512.0;
|
|
|
|
const int STEPS = 125;
|
|
|
|
vec4 dilate_distance_h(vec2 uv) {
|
|
vec2 e = vec2(1.0/size, 0.0);
|
|
float d = size/float(STEPS);
|
|
float rv = 0.0;
|
|
float found = 0.0;
|
|
vec2 source_uv;
|
|
vec4 source_color;
|
|
int steps = min(STEPS, int(size)/2);
|
|
for (int i = 0; i < steps; ++i) {
|
|
source_uv = uv+float(i)*e;
|
|
source_color = texture(tex, source_uv);
|
|
if (source_color.a >= 0.5) {
|
|
rv = float(i);
|
|
found = 1.0;
|
|
break;
|
|
}
|
|
source_uv = uv-float(i)*e;
|
|
source_color = texture(tex, source_uv);
|
|
if (source_color.a >= 0.5) {
|
|
rv = -float(i);
|
|
found = 1.0;
|
|
break;
|
|
}
|
|
}
|
|
return vec4(vec3(0.5+0.5*rv*e.x*d), found);
|
|
}
|
|
|
|
void fragment() {
|
|
COLOR = dilate_distance_h(UV);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=20]
|
|
shader = SubResource( 19 )
|
|
shader_param/size = 512.0
|
|
|
|
[sub_resource type="Shader" id=21]
|
|
code = "shader_type canvas_item;
|
|
render_mode blend_disabled;
|
|
|
|
uniform sampler2D tex;
|
|
uniform float size = 512.0;
|
|
|
|
const int STEPS = 64;
|
|
|
|
vec4 best(vec4 last, vec2 uv, float dy) {
|
|
vec2 p = texture(tex, uv+dy).ar;
|
|
float d = size/float(STEPS);
|
|
float dx = (p.y-0.5)/d;
|
|
if (p.x > 0.5) {
|
|
float d_2 = dx*dx+dy*dy;
|
|
if (d_2 < last.y) {
|
|
last = vec4(1.0, d_2, p.y, dy);
|
|
}
|
|
}
|
|
return last;
|
|
}
|
|
|
|
vec4 dilate_distance_v(vec2 uv) {
|
|
float e = 1.0/size;
|
|
float d = size/float(STEPS);
|
|
vec4 p = vec4(texture(tex, uv).arr, 0.0);
|
|
if (p.x < 0.5) {
|
|
p.y = 1.0;
|
|
} else {
|
|
p.y = (p.y-0.5)/d;
|
|
p.y *= p.y;
|
|
}
|
|
int steps = min(STEPS, int(size)/2);
|
|
for (int i = 0; i < steps; ++i) {
|
|
float dy = float(i)*e;
|
|
p = best(p, uv, dy);
|
|
p = best(p, uv, -dy);
|
|
}
|
|
return vec4(p.z, 0.5+0.5*p.w*d, 0.0, p.x);
|
|
}
|
|
|
|
void fragment() {
|
|
COLOR = dilate_distance_v(UV);
|
|
}
|
|
"
|
|
|
|
[sub_resource type="ShaderMaterial" id=22]
|
|
shader = SubResource( 21 )
|
|
shader_param/size = 512.0
|
|
|
|
[sub_resource type="PrismMesh" id=23]
|
|
|
|
[node name="MapRenderer" type="Viewport"]
|
|
size = Vector2( 2048, 2048 )
|
|
own_world = true
|
|
world = SubResource( 1 )
|
|
transparent_bg = true
|
|
keep_3d_linear = true
|
|
render_target_update_mode = 0
|
|
script = ExtResource( 2 )
|
|
mesh_normal_material = SubResource( 3 )
|
|
inv_uv_material = SubResource( 5 )
|
|
white_material = SubResource( 7 )
|
|
curvature_material = SubResource( 9 )
|
|
ao_material = SubResource( 12 )
|
|
denoise_pass = SubResource( 14 )
|
|
dilate_pass1 = SubResource( 16 )
|
|
dilate_pass2 = SubResource( 18 )
|
|
seams_pass1 = SubResource( 20 )
|
|
seams_pass2 = SubResource( 22 )
|
|
|
|
[node name="MeshInstance" type="MeshInstance" parent="."]
|
|
mesh = SubResource( 23 )
|
|
material/0 = SubResource( 5 )
|
|
__meta__ = {
|
|
"_editor_description_": ""
|
|
}
|
|
|
|
[node name="Camera" type="Camera" parent="."]
|
|
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0.5, 6 )
|
|
projection = 1
|
|
|
|
[node name="CurvatureGenerator" type="Node" parent="."]
|
|
script = ExtResource( 1 )
|
|
|
|
[node name="BVHGenerator" type="Node" parent="."]
|
|
script = ExtResource( 3 )
|