diff --git a/External/Imposter/materials/albedo_material.material b/External/Imposter/materials/albedo_material.material new file mode 100644 index 0000000..f5f4dd8 Binary files /dev/null and b/External/Imposter/materials/albedo_material.material differ diff --git a/External/Imposter/materials/depth_baker.gdshader b/External/Imposter/materials/depth_baker.gdshader new file mode 100644 index 0000000..1ca602f --- /dev/null +++ b/External/Imposter/materials/depth_baker.gdshader @@ -0,0 +1,31 @@ +shader_type spatial; +render_mode unshaded, depth_test_disabled; +uniform float depth_scaler = 1.0f; +uniform vec4 col: source_color; + +uniform sampler2D DEPTH_TEXTURE:hint_depth_texture,filter_linear_mipmap; +varying mat4 CAMERA; +void vertex() { + + POSITION = vec4(VERTEX, 1.0); + CAMERA = INV_VIEW_MATRIX; +} + +void fragment() { + + float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x; + +//Vulkan.z size range 0-1 ,penGL.z size range -1,1 + vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth); +// vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0; + + vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0); + vec3 world_position = world.xyz / world.w; + +float aabb_full = depth_scaler; + +world_position *= 2.0; // going back to aabb but around 0 +world_position.z = aabb_full/2.0 - world_position.z; + +ALBEDO = vec3(clamp(world_position.z/aabb_full,0,1)); +} diff --git a/External/Imposter/materials/depth_baker.material b/External/Imposter/materials/depth_baker.material new file mode 100644 index 0000000..8d8425f Binary files /dev/null and b/External/Imposter/materials/depth_baker.material differ diff --git a/External/Imposter/materials/dilatate.gdshader b/External/Imposter/materials/dilatate.gdshader new file mode 100644 index 0000000..a4aa9b8 --- /dev/null +++ b/External/Imposter/materials/dilatate.gdshader @@ -0,0 +1,46 @@ +shader_type canvas_item; +render_mode blend_premul_alpha; + +uniform sampler2D u_alpha_tex; +uniform int u_distance = 16; +uniform bool u_alpha_overwrite = true; + + +vec4 dilate(sampler2D a_tex, sampler2D tex, vec2 coords, vec2 texel_size, int dist, float alpha_cutoff) +{ + + vec2 offsets[8] = { + vec2(-1,0),vec2(1,0), vec2(0,1), vec2(0,-1), + vec2(-1,1), vec2(1,1), vec2(1,-1), vec2(-1,-1) + }; + + vec4 sample = textureLod(tex, coords, 0); + vec4 a_sample = textureLod(a_tex, coords, 0); + if(a_sample.a > alpha_cutoff) + return sample; + + for(int curr_dist = 0; curr_dist < dist; curr_dist++) + { + for(int o = 0; o < 8; o++) + { + vec2 off_coords = offsets[o] * texel_size * float(curr_dist + 1); + vec4 off_sample = textureLod(tex, coords + off_coords, 0); + vec4 off_a_sample = textureLod(a_tex, coords + off_coords, 0); + + if (off_a_sample.a > alpha_cutoff) + { + if(u_alpha_overwrite) + return off_sample; + else + return vec4(off_sample.rgb, a_sample.a); + } + + } + } + return sample; +} + +void fragment() { + COLOR = dilate(u_alpha_tex, TEXTURE, UV, TEXTURE_PIXEL_SIZE, u_distance, 0.95); + //COLOR.a = 1.0; +} diff --git a/External/Imposter/materials/dilatate.material b/External/Imposter/materials/dilatate.material new file mode 100644 index 0000000..ef72d46 Binary files /dev/null and b/External/Imposter/materials/dilatate.material differ diff --git a/External/Imposter/materials/normal_baker.gdshader b/External/Imposter/materials/normal_baker.gdshader new file mode 100644 index 0000000..cf21be5 --- /dev/null +++ b/External/Imposter/materials/normal_baker.gdshader @@ -0,0 +1,44 @@ +shader_type spatial; +render_mode blend_mix,depth_draw_opaque,cull_disabled,diffuse_burley,specular_schlick_ggx; +uniform vec4 albedo : source_color; +uniform sampler2D texture_albedo : source_color; +uniform sampler2D normal_texture : source_color; + +uniform bool use_normalmap = false; +uniform bool use_alpha_texture = false; +uniform float roughness : hint_range(0,1); +uniform float alpha_scissor_threshold : hint_range(0,1); +uniform float normal_scale : hint_range(-5,5); + +uniform vec3 uv1_scale; +uniform vec3 uv1_offset; + + +void vertex() { + UV=UV*uv1_scale.xy+uv1_offset.xy; +} + +void fragment() { + vec2 base_uv = UV; + vec4 albedo_tex = texture(texture_albedo,base_uv); + vec4 normal_tex = texture(normal_texture,base_uv); + // 0.5 + -1.0 == -1.0 + 0.5 + //ALBEDO = vec3(1.0 - NORMAL.y, 1.0 - NORMAL.x, - NORMAL.z)* 0.5; + if(use_normalmap) + { + vec3 normalmap; + normalmap.xy = normal_tex.xy * 2.0 - 1.0; + normalmap.z = sqrt(max(0.0, 1.0 - dot(normalmap.xy, normalmap.xy))); + NORMAL = normalize(mix(NORMAL, TANGENT * normalmap.x + BINORMAL * normalmap.y + NORMAL * normalmap.z, normal_scale)); + } + + + + ALBEDO = vec3(-NORMAL.x, NORMAL.y, -NORMAL.z) * 0.5 + 0.5; + if(use_alpha_texture) + { + ALPHA = albedo_tex.a; + ALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold; + } + +} diff --git a/External/Imposter/materials/normal_baker.material b/External/Imposter/materials/normal_baker.material new file mode 100644 index 0000000..cc39eb8 Binary files /dev/null and b/External/Imposter/materials/normal_baker.material differ diff --git a/External/Imposter/materials/orm_baker.gdshader b/External/Imposter/materials/orm_baker.gdshader new file mode 100644 index 0000000..d24df02 --- /dev/null +++ b/External/Imposter/materials/orm_baker.gdshader @@ -0,0 +1,41 @@ +shader_type spatial; +render_mode blend_mix,depth_draw_opaque,cull_disabled,diffuse_burley,specular_schlick_ggx; +uniform vec4 albedo : source_color; +uniform sampler2D texture_albedo : source_color; +uniform sampler2D ao_texture : source_color; +uniform vec4 ao_texture_channel; +uniform sampler2D roughness_texture : source_color; +uniform vec4 roughness_texture_channel; +uniform sampler2D metallic_texture : source_color; +uniform vec4 metallic_texture_channel; +uniform bool use_alpha_texture = false; +uniform float roughness : hint_range(0,1) = 1.0; +uniform float metallic : hint_range(0,1) = 0.0; +uniform float alpha_scissor_threshold : hint_range(0,1); + +uniform bool use_ao_texture = false; +uniform bool use_roughness_texture = false; +uniform bool use_metallic_texture = false; + +uniform vec3 uv1_scale; +uniform vec3 uv1_offset; + + +void vertex() { + UV=UV*uv1_scale.xy+uv1_offset.xy; +} + +void fragment() { + vec2 base_uv = UV; + vec4 albedo_tex = texture(texture_albedo,base_uv); + float ao_tex = mix(1.0, dot(texture(ao_texture,base_uv), ao_texture_channel), float(use_ao_texture)); + float rougness_tex = mix(1.0,dot(texture(roughness_texture,base_uv), roughness_texture_channel), float(use_roughness_texture)); + float metallic_tex = mix(1.0,dot(texture(metallic_texture,base_uv), metallic_texture_channel), float(use_metallic_texture)); + + ALBEDO = vec3(ao_tex, rougness_tex * roughness, metallic_tex * metallic); + if(use_alpha_texture) + { + ALPHA = albedo_tex.a; + ALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold; + } +} diff --git a/External/Imposter/materials/orm_baker.material b/External/Imposter/materials/orm_baker.material new file mode 100644 index 0000000..f8ec72e Binary files /dev/null and b/External/Imposter/materials/orm_baker.material differ diff --git a/External/Imposter/materials/shaders/ImpostorShader.gdshader b/External/Imposter/materials/shaders/ImpostorShader.gdshader new file mode 100644 index 0000000..db26085 --- /dev/null +++ b/External/Imposter/materials/shaders/ImpostorShader.gdshader @@ -0,0 +1,409 @@ +shader_type spatial; + +render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx; + +uniform vec4 albedo : source_color = vec4(1, 1, 1, 1); + +uniform float specular = 0.5f; + +uniform float metallic = 1.0f; + +uniform float roughness : hint_range(0.0f, 1.0f) = 1.0f; + + +uniform sampler2D imposterTextureAlbedo : source_color; + +uniform sampler2D imposterTextureNormal : hint_normal; + +uniform sampler2D imposterTextureDepth : hint_default_white; + +uniform sampler2D imposterTextureOrm : hint_default_white; + +uniform vec2 imposterFrames = vec2(16.0f, 16.0f); + +uniform vec3 positionOffset = vec3(0.0f); + +uniform bool isFullSphere = true; + +uniform float alpha_clamp = 0.5f; + +uniform bool dither = false; + +uniform float scale = 1.0f; + +uniform float depth_scale : hint_range(0, 1) = 0.0f; + +uniform float normalmap_depth : hint_range(-5, 5) = 1.0f; + +uniform float aabb_max = 1.0; + +varying vec2 uv_frame1; +varying vec2 xy_frame1; +varying flat vec2 frame1; +varying flat vec3 frame1_normal; +varying vec2 uv_frame2; +varying vec2 xy_frame2; +varying flat vec2 frame2; +varying flat vec3 frame2_normal; +varying vec2 uv_frame3; +varying vec2 xy_frame3; +varying flat vec2 frame3; +varying flat vec3 frame3_normal; + +varying vec4 quad_blend_weights; + + +vec2 VecToSphereOct(vec3 pivotToCamera) +{ + vec3 octant = sign(pivotToCamera); + + // |x| + |y| + |z| = 1 + float sum = dot(pivotToCamera, octant); + vec3 octahedron = pivotToCamera / sum; + + if (octahedron.y < 0.0f) + { + vec3 absolute = abs(octahedron); + octahedron.xz = octant.xz * vec2(1.0f - absolute.z, 1.0f - absolute.x); + } + return octahedron.xz; +} + +vec2 VecToHemiSphereOct(vec3 pivotToCamera) +{ + pivotToCamera.y = max(pivotToCamera.y, 0.001); + pivotToCamera = normalize(pivotToCamera); + vec3 octant = sign(pivotToCamera); + + // |x| + |y| + |z| = 1 + float sum = dot(pivotToCamera, octant); + vec3 octahedron = pivotToCamera / sum; + + return vec2( + octahedron.x + octahedron.z, + octahedron.z - octahedron.x); +} + + +vec2 VectorToGrid(vec3 vec) +{ + if (isFullSphere) + { + return VecToSphereOct(vec); + } + else + { + return VecToHemiSphereOct(vec); + } +} + +//for sphere + +vec3 OctaSphereEnc(vec2 coord) +{ + coord = (coord - 0.5) * 2.0; + vec3 position = vec3(coord.x, 0.0f, coord.y); + vec2 absolute = abs(position.xz); + position.y = 1.0f - absolute.x - absolute.y; + + if (position.y < 0.0f) + { + position.xz = sign(position.xz) * vec2(1.0f - absolute.y, 1.0f - absolute.x); + } + + return position; +} + +//for hemisphere +vec3 OctaHemiSphereEnc(vec2 coord) +{ + vec3 position = vec3(coord.x - coord.y, 0.0f, -1.0 + coord.x + coord.y); + vec2 absolute = abs(position.xz); + position.y = 1.0f - absolute.x - absolute.y; + return position; +} + +vec3 GridToVector(vec2 coord) +{ + if (isFullSphere) + { + return OctaSphereEnc(coord); + } + else + { + return OctaHemiSphereEnc(coord); + } +} + +vec3 FrameXYToRay(vec2 frame, vec2 frameCountMinusOne) +{ + vec2 f = (frame.xy/ frameCountMinusOne); + + vec3 vec = GridToVector(f); + vec = normalize(vec); + return vec; +} + +vec3 SpriteProjection(vec3 pivotToCameraRayLocal, vec2 size, vec2 loc_uv) +{ + vec3 z = normalize(pivotToCameraRayLocal); + vec3 x, y; + vec3 up = vec3(0,1,0); + + if (abs(z.y) > 0.999f) + { + up = vec3(0,0,-1); + } + + x = normalize(cross(up, z)); + y = normalize(cross(x, z)); + + loc_uv -= vec2(0.5,0.5); + vec2 uv = (loc_uv) * 2.0; //-1 to 1 + + vec3 newX = x * uv.x; + vec3 newY = y * uv.y; + + vec2 vecSize = size * 0.5; + + newX *= vecSize.x; + newY *= vecSize.y; + + return newX + newY; +} + +vec4 quadBlendWieghts(vec2 coords) +{ + vec4 res; + /* 0 0 0 + 0 0 0 + 1 0 0 */ + res.x = min(1.0f - coords.x, 1.0f - coords.y); + /* 1 0 0 + 0 0 0 + 0 0 1 */ + res.y = abs(coords.x - coords.y); + /* 0 0 1 + 0 0 0 + 0 0 0 */ + res.z = min(coords.x, coords.y); + /* 0 0 0 + 0 0 1 + 0 1 1 */ + res.w = ceil(coords.x - coords.y); + //res.xyz /= (res.x + res.y + res.z); + return res; +} + +vec2 virtualPlaneUV(vec3 plane_normal,vec3 plane_x, vec3 plane_y, vec3 pivotToCameraRay, vec3 vertexToCameraRay, float size) +{ + plane_normal = normalize(plane_normal); + plane_x = normalize(plane_x); + plane_y = normalize(plane_y); + + float projectedNormalRayLength = dot(plane_normal, pivotToCameraRay); + float projectedVertexRayLength = dot(plane_normal, vertexToCameraRay); + float offsetLength = projectedNormalRayLength/projectedVertexRayLength; + vec3 offsetVector = vertexToCameraRay * offsetLength - pivotToCameraRay; + + vec2 duv = vec2( + dot(plane_x , offsetVector), + dot(plane_y, offsetVector) + ); + + //we are in space -1 to 1 + duv /= 2.0 * size; + duv += 0.5; + return duv; +} + +void calcuateXYbasis(vec3 plane_normal, out vec3 plane_x, out vec3 plane_y) +{ + vec3 up = vec3(0,1,0); + //cross product doesnt work if we look directly from bottom + if (abs(plane_normal.y) > 0.999f) + { + up = vec3(0,0,1); + } + plane_x = normalize(cross(plane_normal, up)); + plane_y = normalize(cross(plane_x, plane_normal)); +} + +vec3 projectOnPlaneBasis(vec3 ray, vec3 plane_normal, vec3 plane_x, vec3 plane_y) +{ + //reproject plane normal onto planeXY basos + return normalize(vec3( + dot(plane_x,ray), + dot(plane_y,ray), + dot(plane_normal,ray) + )); +} + +void vertex() +{ + vec2 framesMinusOne = imposterFrames - vec2(1); + vec3 cameraPos_WS = (INV_VIEW_MATRIX * vec4(vec3(0), 1.0)).xyz; + vec3 cameraPos_OS = (inverse(MODEL_MATRIX) * vec4(cameraPos_WS, 1.0)).xyz; + + //TODO: check if this is correct. We are using orho projected images, so + // camera far away + vec3 pivotToCameraRay = (cameraPos_OS) * 10.0; + vec3 pivotToCameraDir = normalize(cameraPos_OS); + + vec2 grid = VectorToGrid(pivotToCameraDir); + //bias and scale to 0 to 1 + grid = clamp((grid + 1.0) * 0.5, vec2(0, 0), vec2(1, 1)); + grid *= framesMinusOne; + grid = clamp(grid, vec2(0), vec2(framesMinusOne)); + vec2 gridFloor = min(floor(grid), framesMinusOne); + vec2 gridFract = fract(grid); + + //radius * 2 + vec2 size = vec2(2.0) * scale; + vec3 projected = SpriteProjection(pivotToCameraDir, size, UV); + vec3 vertexToCameraRay = (pivotToCameraRay - (projected)); + vec3 vertexToCameraDir = normalize(vertexToCameraRay); + + frame1 = gridFloor; + quad_blend_weights = quadBlendWieghts(gridFract); + //convert frame coordinate to octahedron direction + vec3 projectedQuadADir = FrameXYToRay(frame1, framesMinusOne); + + frame2 = clamp(frame1 + mix(vec2(0, 1), vec2(1, 0), quad_blend_weights.w), vec2(0,0), framesMinusOne); + vec3 projectedQuadBDir = FrameXYToRay(frame2, framesMinusOne); + + frame3 = clamp(frame1 + vec2(1), vec2(0,0), framesMinusOne); + vec3 projectedQuadCDir = FrameXYToRay(frame3, framesMinusOne); + + frame1_normal = (MODELVIEW_MATRIX *vec4(projectedQuadADir, 0)).xyz; + frame2_normal = (MODELVIEW_MATRIX *vec4(projectedQuadBDir, 0)).xyz; + frame3_normal = (MODELVIEW_MATRIX *vec4(projectedQuadCDir, 0)).xyz; + + //calcute virtual planes projections + vec3 plane_x1, plane_y1, plane_x2, plane_y2, plane_x3, plane_y3; + calcuateXYbasis(projectedQuadADir, plane_x1, plane_y1); + uv_frame1 = virtualPlaneUV(projectedQuadADir, plane_x1, plane_y1, pivotToCameraRay, vertexToCameraRay, scale); + xy_frame1 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadADir, plane_x1, plane_y1).xy; + + calcuateXYbasis(projectedQuadBDir, plane_x2, plane_y2); + uv_frame2 = virtualPlaneUV(projectedQuadBDir, plane_x2, plane_y2, pivotToCameraRay, vertexToCameraRay, scale); + xy_frame2 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadBDir, plane_x2, plane_y2).xy; + + calcuateXYbasis(projectedQuadCDir, plane_x3, plane_y3); + uv_frame3 = virtualPlaneUV(projectedQuadCDir, plane_x3, plane_y3, pivotToCameraRay, vertexToCameraRay, scale); + xy_frame3 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadCDir, plane_x3, plane_y3).xy; + + //to fragment shader + VERTEX.xyz = projected + positionOffset; + VERTEX.xyz +=pivotToCameraDir* aabb_max; + + NORMAL = normalize(pivotToCameraDir); + TANGENT= normalize(cross(NORMAL,vec3(0.0, 1.0, 0.0))); + BINORMAL = normalize(cross(TANGENT,NORMAL)); +} + +vec4 blenderColors(vec2 uv_1, vec2 uv_2, vec2 uv_3, vec4 grid_weights, sampler2D atlasTexture) +{ + vec4 quad_a, quad_b, quad_c; + + quad_a = textureLod(atlasTexture, uv_1, 0.0f); + quad_b = textureLod(atlasTexture, uv_2, 0.0f); + quad_c = textureLod(atlasTexture, uv_3, 0.0f); + return quad_a * grid_weights.x + quad_b * grid_weights.y + quad_c * grid_weights.z; +} + +vec3 normal_from_normalmap(vec4 normalTex, vec3 tangent, vec3 binormal, vec3 f_norm) +{ + vec3 normalmap; + normalmap.xy = normalTex.xy * 2.0 - 1.0; + normalmap.z = sqrt(max(0.0, 1.0 - dot(normalmap.xy, normalmap.xy))); + normalmap = normalize(normalmap); + return normalize(tangent * normalmap.x + binormal * normalmap.y + f_norm * normalmap.z); +} + +vec3 blendedNormals(vec2 uv_1, vec3 f1_n, + vec2 uv_2, vec3 f2_n, + vec2 uv_3, vec3 f3_n, + vec3 tangent, vec3 binormal, + vec4 grid_weights, sampler2D atlasTexture) +{ + vec4 quad_a, quad_b, quad_c; + quad_a = textureLod(atlasTexture, uv_1, 0.0f); + quad_b = textureLod(atlasTexture, uv_2, 0.0f); + quad_c = textureLod(atlasTexture, uv_3, 0.0f); + vec3 norm1 = normal_from_normalmap(quad_a, tangent, binormal, f1_n); + vec3 norm2 = normal_from_normalmap(quad_b, tangent, binormal, f2_n); + vec3 norm3 = normal_from_normalmap(quad_c, tangent, binormal, f3_n); + return normalize(norm1 * grid_weights.x + norm2 * grid_weights.y + norm3 * grid_weights.z); +} + +vec2 recalculateUV(vec2 uv_f, vec2 frame, vec2 xy_f, vec2 frame_size, float d_scale, sampler2D depthTexture) +{ + //clamp for parallax sampling + uv_f = clamp(uv_f, vec2(0), vec2(1)); + vec2 uv_quad = frame_size * (frame + uv_f); + //paralax + vec4 n_depth = (textureLod( depthTexture, uv_quad, 0 )); + uv_f = xy_f * (0.5-n_depth.r) * d_scale + uv_f; + //clamp parallax offset + uv_f = clamp(uv_f, vec2(0), vec2(1)); + uv_f = frame_size * (frame + uv_f); + //clamped full UV + return clamp(uv_f, vec2(0), vec2(1)); +} + +void fragment() +{ + vec2 quad_size = vec2(1.0f) / imposterFrames; + vec2 uv_f1 = recalculateUV(uv_frame1, frame1, xy_frame1, quad_size, depth_scale, imposterTextureDepth); + vec2 uv_f2 = recalculateUV(uv_frame2, frame2, xy_frame2, quad_size, depth_scale, imposterTextureDepth); + vec2 uv_f3 = recalculateUV(uv_frame3, frame3, xy_frame3, quad_size, depth_scale, imposterTextureDepth); + + vec4 baseTex = blenderColors(uv_f1, uv_f2, uv_f3, quad_blend_weights, imposterTextureAlbedo); + vec4 ormTex = blenderColors(uv_f1, uv_f2, uv_f3, quad_blend_weights, imposterTextureOrm); + + vec3 normalTex = blendedNormals(uv_f1, frame1_normal, + uv_f2, frame2_normal, + uv_f3, frame3_normal, + TANGENT, BINORMAL, + quad_blend_weights, imposterTextureNormal); + ALBEDO = baseTex.rgb * albedo.rgb; + NORMAL =normalTex.xyz; + + if(dither) + { + float opacity = baseTex.a; + int x = int(FRAGCOORD.x) % 4; + int y = int(FRAGCOORD.y) % 4; + int index = x + y * 4; + float limit = 0.0; + if (x < 8) { + if (index == 0) limit = 0.0625; + if (index == 1) limit = 0.5625; + if (index == 2) limit = 0.1875; + if (index == 3) limit = 0.6875; + if (index == 4) limit = 0.8125; + if (index == 5) limit = 0.3125; + if (index == 6) limit = 0.9375; + if (index == 7) limit = 0.4375; + if (index == 8) limit = 0.25; + if (index == 9) limit = 0.75; + if (index == 10) limit = 0.125; + if (index == 11) limit = 0.625; + if (index == 12) limit = 1.0; + if (index == 13) limit = 0.5; + if (index == 14) limit = 0.875; + if (index == 15) limit = 0.375; + } + // Is this pixel below the opacity limit? Skip drawing it + if (opacity < limit * alpha_clamp) + discard; + } + else { + ALPHA = float(baseTex.a>alpha_clamp); + ALPHA_SCISSOR_THRESHOLD = 0.5; + } + METALLIC = ormTex.b * metallic; + SPECULAR = specular; + ROUGHNESS = ormTex.g * roughness; +} diff --git a/External/Imposter/materials/shaders/ImpostorShaderShadows.gdshader b/External/Imposter/materials/shaders/ImpostorShaderShadows.gdshader new file mode 100644 index 0000000..9c61e30 --- /dev/null +++ b/External/Imposter/materials/shaders/ImpostorShaderShadows.gdshader @@ -0,0 +1,278 @@ +shader_type spatial; +render_mode blend_mix, depth_prepass_alpha, cull_back, unshaded; +uniform vec4 albedo : source_color = vec4(1, 1, 1, 1); +uniform float specular = 0.5f; +uniform float metallic = 0.0f; +uniform float roughness : hint_range(0, 1) = 1.0f; + +uniform sampler2D imposterTextureAlbedo : source_color; +uniform vec2 imposterFrames = vec2(16.0f, 16.0f); +uniform vec3 positionOffset = vec3(0.0f); +uniform bool isFullSphere = true; +uniform float alpha_clamp = 0.5f; +uniform float scale = 1.0f; +uniform float depth_scale : hint_range(0, 1) = 0.0f; +uniform float aabb_max = 1.0; + +varying vec2 uv_frame1; +varying vec2 xy_frame1; +varying flat vec2 frame1; + +varying vec4 quad_blend_weights; + +vec2 VecToSphereOct(vec3 pivotToCamera) +{ + vec3 octant = sign(pivotToCamera); + + // |x| + |y| + |z| = 1 + float sum = dot(pivotToCamera, octant); + vec3 octahedron = pivotToCamera / sum; + + if (octahedron.y < 0.0f) + { + vec3 absolute = abs(octahedron); + octahedron.xz = octant.xz * vec2(1.0f - absolute.z, 1.0f - absolute.x); + } + return octahedron.xz; +} + +vec2 VecToHemiSphereOct(vec3 pivotToCamera) +{ + pivotToCamera.y = max(pivotToCamera.y, 0.001); + pivotToCamera = normalize(pivotToCamera); + vec3 octant = sign(pivotToCamera); + + // |x| + |y| + |z| = 1 + float sum = dot(pivotToCamera, octant); + vec3 octahedron = pivotToCamera / sum; + + return vec2( + octahedron.x + octahedron.z, + octahedron.z - octahedron.x); +} + +vec2 VectorToGrid(vec3 vec) +{ + if (isFullSphere) + { + return VecToSphereOct(vec); + } + else + { + return VecToHemiSphereOct(vec); + } +} + +//for sphere +vec3 OctaSphereEnc(vec2 coord) +{ + coord = (coord - 0.5) * 2.0; + vec3 position = vec3(coord.x, 0.0f, coord.y); + vec2 absolute = abs(position.xz); + position.y = 1.0f - absolute.x - absolute.y; + + if (position.y < 0.0f) + { + position.xz = sign(position.xz) * vec2(1.0f - absolute.y, 1.0f - absolute.x); + } + + return position; +} + +//for hemisphere +vec3 OctaHemiSphereEnc(vec2 coord) +{ + + vec3 position = vec3(coord.x - coord.y, 0.0f, -1.0 + coord.x + coord.y); + vec2 absolute = abs(position.xz); + position.y = 1.0f - absolute.x - absolute.y; + return position; +} + +vec3 GridToVector(vec2 coord) +{ + if (isFullSphere) + { + return OctaSphereEnc(coord); + } + else + { + return OctaHemiSphereEnc(coord); + } +} + +vec3 FrameXYToRay(vec2 frame, vec2 frameCountMinusOne) +{ + //divide frame x y by framecount minus one to get 0-1 + vec2 f = (frame.xy/ frameCountMinusOne); + //bias and scale to -1 to 1 + + vec3 vec = GridToVector(f); + vec = normalize(vec); + return vec; +} + +vec3 SpriteProjection(vec3 pivotToCameraRayLocal, vec2 size, vec2 loc_uv) +{ + vec3 z = normalize(pivotToCameraRayLocal); + vec3 x, y; + vec3 up = vec3(0,1,0); + //cross product doesnt work if we look directly from bottom + if (abs(z.y) > 0.999f) + { + up = vec3(0,0,-1); + } + x = normalize(cross(up, z)); + y = normalize(cross(x, z)); + + loc_uv -= vec2(0.5,0.5); + vec2 uv = (loc_uv) * 2.0; //-1 to 1 + + vec3 newX = x * uv.x; + vec3 newY = y * uv.y; + + vec2 vecSize = size * 0.5; + + newX *= vecSize.x; + newY *= vecSize.y; + + return newX + newY; +} + +vec4 quadBlendWieghts(vec2 coords) +{ + vec4 res; + /* 0 0 0 + 0 0 0 + 1 0 0 */ + res.x = min(1.0f - coords.x, 1.0f - coords.y); + /* 1 0 0 + 0 0 0 + 0 0 1 */ + res.y = abs(coords.x - coords.y); + /* 0 0 1 + 0 0 0 + 0 0 0 */ + res.z = min(coords.x, coords.y); + /* 0 0 0 + 0 0 1 + 0 1 1 */ + res.w = ceil(coords.x - coords.y); + //res.xyz /= (res.x + res.y + res.z); + return res; +} + + +//this function works well in orthogonal projection. It works okeyish with further distances of perspective projection +vec2 virtualPlaneUV(vec3 plane_normal,vec3 plane_x, vec3 plane_y, vec3 pivotToCameraRay, vec3 vertexToCameraRay, float size) +{ + plane_normal = normalize(plane_normal); + plane_x = normalize(plane_x); + plane_y = normalize(plane_y); + + float projectedNormalRayLength = dot(plane_normal, pivotToCameraRay); + float projectedVertexRayLength = dot(plane_normal, vertexToCameraRay); + float offsetLength = projectedNormalRayLength/projectedVertexRayLength; + vec3 offsetVector = vertexToCameraRay * offsetLength - pivotToCameraRay; + + vec2 duv = vec2( + dot(plane_x , offsetVector), + dot(plane_y, offsetVector) + ); + + //we are in space -1 to 1 + duv /= 2.0 * size; + duv += 0.5; + return duv; +} + +void calcuateXYbasis(vec3 plane_normal, out vec3 plane_x, out vec3 plane_y) +{ + vec3 up = vec3(0,1,0); + //cross product doesnt work if we look directly from bottom + if (abs(plane_normal.y) > 0.999f) + { + up = vec3(0,0,1); + } + plane_x = normalize(cross(plane_normal, up)); + plane_y = normalize(cross(plane_x, plane_normal)); +} + +vec3 projectOnPlaneBasis(vec3 ray, vec3 plane_normal, vec3 plane_x, vec3 plane_y) +{ + //reproject plane normal onto planeXY basos + return normalize(vec3( + dot(plane_x,ray), + dot(plane_y,ray), + dot(plane_normal,ray) + )); +} + +void vertex() +{ + vec2 framesMinusOne = imposterFrames - vec2(1); + vec3 cameraPos_WS = (INV_VIEW_MATRIX * vec4(vec3(0), 1.0)).xyz; + vec3 cameraPos_OS = (inverse(MODEL_MATRIX) * vec4(cameraPos_WS, 1.0)).xyz; + + //TODO: check if this is correct. We are using orho projected images, so + // camera far away + vec3 pivotToCameraRay = (cameraPos_OS) * 10.0; + vec3 pivotToCameraDir = normalize(cameraPos_OS); + + vec2 grid = VectorToGrid(pivotToCameraDir); + //bias and scale to 0 to 1 + grid = clamp((grid + 1.0) * 0.5, vec2(0, 0), vec2(1, 1)); + grid *= framesMinusOne; + grid = clamp(grid, vec2(0), vec2(framesMinusOne)); + vec2 gridFloor = min(floor(grid), framesMinusOne); + vec2 gridFract = fract(grid); + + //radius * 2 + vec2 size = vec2(2.0) * scale; + vec3 projected = SpriteProjection(pivotToCameraDir, size, UV); + vec3 vertexToCameraRay = (pivotToCameraRay - (projected)); + vec3 vertexToCameraDir = normalize(vertexToCameraRay); + + frame1 = gridFloor; + quad_blend_weights = quadBlendWieghts(gridFract); + //convert frame coordinate to octahedron direction + vec3 projectedQuadADir = FrameXYToRay(frame1, framesMinusOne); + + //calcute virtual planes projections + vec3 plane_x1, plane_y1; + calcuateXYbasis(projectedQuadADir, plane_x1, plane_y1); + uv_frame1 = virtualPlaneUV(projectedQuadADir, plane_x1, plane_y1, pivotToCameraRay, vertexToCameraRay, scale); + xy_frame1 = projectOnPlaneBasis(-vertexToCameraDir, projectedQuadADir, plane_x1, plane_y1).xy; + + + + //to fragment shader + VERTEX.xyz = projected + positionOffset; + VERTEX.xyz +=pivotToCameraDir* aabb_max; + vec3 v0 = abs(NORMAL.y) < 0.999 ? vec3(0.0, 1.0, 0.0) : vec3(0.0, 0.0, 1.0); + + NORMAL = normalize(pivotToCameraDir); + TANGENT= normalize(cross(NORMAL,v0)); + BINORMAL = normalize(cross(TANGENT,NORMAL)); +} + + + +vec2 recalculateUV(vec2 uv_f, vec2 frame, vec2 xy_f, vec2 frame_size) +{ + //clamp for parallax sampling + uv_f = clamp(uv_f, vec2(0), vec2(1)); + return frame_size * (frame + uv_f); +} + +void fragment() +{ + vec2 quad_size = vec2(1.0f) / imposterFrames; + vec2 uv_f1 = recalculateUV(uv_frame1, frame1, xy_frame1, quad_size); + vec4 baseTex = textureLod(imposterTextureAlbedo, uv_f1, 0.0f); + + ALBEDO = baseTex.rgb; + ALPHA = float(baseTex.a>alpha_clamp); + ALPHA_SCISSOR_THRESHOLD = 0.5; + ROUGHNESS = 1.0; +} diff --git a/Runtime/Actions/TriggerActionInEditor.cs b/Runtime/Actions/TriggerActionInEditor.cs new file mode 100644 index 0000000..290c758 --- /dev/null +++ b/Runtime/Actions/TriggerActionInEditor.cs @@ -0,0 +1,29 @@ + +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class TriggerActionInEditor : Node + { + [Export] + public RJAction action; + + [Export] + public bool execute; + + public override void _Process( double delta ) + { + if ( ! execute ) + { + return; + } + + execute = false; + + action.Trigger(); + } + } +} \ No newline at end of file diff --git a/Runtime/Files/FilePath.cs b/Runtime/Files/FilePath.cs index 7d2b27c..c802115 100644 --- a/Runtime/Files/FilePath.cs +++ b/Runtime/Files/FilePath.cs @@ -54,6 +54,16 @@ namespace Rokojori } } + public bool hasFileExtension( string extension ) + { + if ( fileExtension == null ) + { + return false; + } + + return fileExtension.ToLower() == extension.ToLower(); + } + public string fullPath { get diff --git a/Runtime/Godot/Cameras.cs b/Runtime/Godot/Cameras.cs new file mode 100644 index 0000000..6db2411 --- /dev/null +++ b/Runtime/Godot/Cameras.cs @@ -0,0 +1,37 @@ +using Godot; +using System.Text; +using System.Collections.Generic; + +namespace Rokojori +{ + public class Cameras + { + public static float ComputeCameraFrameFittingDistance( Camera3D camera, float radius ) + { + var fovRadians = Mathf.DegToRad( camera.Fov ); + return ( radius * 2 ) / Mathf.Tan( fovRadians / 2.0f ); + } + + public static float ComputeCameraFrameFittingDistance( float fovDegrees, float radius ) + { + var fovRadians = Mathf.DegToRad( fovDegrees ); + return ( radius * 2 ) / Mathf.Tan( fovRadians / 2.0f ); + } + + public static float ComputeCameraFittingScale( float fovDegrees, float distance ) + { + var fovRadians = Mathf.DegToRad( fovDegrees ); + return distance / ( 0.5f / Mathf.Tan( fovRadians / 2.0f ) ); + } + + public static float ComputeFOVForBillboard( float fovDegrees, float radius, float placingDistance ) + { + var fovRadians = Mathf.DegToRad( fovDegrees ); + var d = ( radius * Mathf.Tan( fovRadians / 2f ) ) / placingDistance; + var rads = 2f * Mathf.Atan( d ); + + return Mathf.RadToDeg( rads ); + } + + } +} \ No newline at end of file diff --git a/Runtime/Godot/Nodes.cs b/Runtime/Godot/Nodes.cs index 9e1393c..e2bdfdf 100644 --- a/Runtime/Godot/Nodes.cs +++ b/Runtime/Godot/Nodes.cs @@ -5,7 +5,7 @@ using System; namespace Rokojori { - public class Nodes + public static class Nodes { public static T Find( Node root, NodePathLocatorType type = NodePathLocatorType.DirectChildren, int parentOffset = 0 ) where T:Node { @@ -93,12 +93,17 @@ namespace Rokojori return list; } - public static List AllIn( Node root, Func filter = null) where T:class + public static List AllIn( Node root, Func filter = null, bool includeRoot = true) where T:class { var list = new List(); ForEach( root, t => { + if ( ! includeRoot && t == root ) + { + return; + } + if ( filter == null || filter( t ) ) { list.Add( t ); @@ -144,6 +149,54 @@ namespace Rokojori } return GetDirectChild( parent ); + } + + public static T CreateChildIn( Node parent, string name = null ) where T:Node,new() + { + var t = new T(); + parent.AddChild( t ); + + t.Owner = parent.Owner; + + if ( name != null ) + { + t.Name = name; + } + + return t; + } + + public static T CreateChild( this Node parent, string name = null ) where T:Node,new() + { + return CreateChildIn( parent, name ); + } + + public static Node CopyNode( Node node, Node parent ) + { + var copy = node.Duplicate(); + + parent.AddChild( copy ); + copy.Owner = parent.Owner; + + return copy; + } + + public static Node DeepCopyTo( this Node node, Node parent ) + { + return CopyNodeHierarchy( node, parent ); + } + + public static Node CopyNodeHierarchy( Node from, Node parent ) + { + var copy = CopyNode( from, parent ); + + for ( int i = 0; i < from.GetChildCount(); i++ ) + { + var child = from.GetChild( i ); + CopyNodeHierarchy( child, copy ); + } + + return copy; } public static void RemoveAndDelete( Node node ) @@ -158,6 +211,37 @@ namespace Rokojori node.QueueFree(); } + public static void RemoveAndDeleteAll( List nodes ) where N:Node + { + nodes.ForEach( n => RemoveAndDelete( n ) ); + } + + public static void RemoveAndDeleteChildrenOfType( Node parent, bool includeInternal = false ) where T:Node + { + if ( parent == null ) + { + return; + } + + var numChildren = parent.GetChildCount( includeInternal ); + + for ( int i = numChildren - 1; i >= 0; i-- ) + { + var node = parent.GetChild( i, includeInternal ); + + var t = node as T; + + if ( t == null ) + { + continue; + } + + parent.RemoveChild( node ); + node.QueueFree(); + } + } + + public static void RemoveAndDeleteChildren( Node parent, bool includeInternal = false ) { if ( parent == null ) diff --git a/Runtime/Interactions/CharacterController/CharacterController.cs b/Runtime/Interactions/CharacterController/CharacterController.cs new file mode 100644 index 0000000..7a69e71 --- /dev/null +++ b/Runtime/Interactions/CharacterController/CharacterController.cs @@ -0,0 +1,69 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using Godot.Collections; + +namespace Rokojori +{ + [GlobalClass] + public partial class CharacterController:Node + { + [Export] + public CharacterBody3D body; + + public enum CharacterUpdateMode + { + Process, + Physics_Process + } + + [Export] + public CharacterUpdateMode characterUpdateMode = CharacterUpdateMode.Process; + + [Export] + public Node actionsContainer; + + [Export] + public Node3D graphics; + + + [Export] + public float rotationSmoothingDuration = 0; + + Smoother rotationSmoother = new Smoother(); + + [Export] + public float positionSmoothingDuration = 0; + + Smoother positionSmoother = new Smoother(); + + public float delta = 0; + + + public override void _Process( double delta ) + { + if ( graphics == null || body == null ) + { + return; + } + + if ( CharacterUpdateMode.Process == characterUpdateMode ) + { + this.delta = (float)delta; + Nodes.ForEach( actionsContainer, a => Actions.Trigger( a ) ); + } + + positionSmoother.CopyPosition( graphics, body, rotationSmoothingDuration, delta ); + rotationSmoother.CopyRotation( graphics, body, positionSmoothingDuration, delta ); + } + + public override void _PhysicsProcess( double delta ) + { + if ( CharacterUpdateMode.Physics_Process == characterUpdateMode ) + { + this.delta = (float)delta; + Nodes.ForEach( actionsContainer, a => Actions.Trigger( a ) ); + } + } + } +} \ No newline at end of file diff --git a/Runtime/Interactions/CharacterController/CharacterControllerAction.cs b/Runtime/Interactions/CharacterController/CharacterControllerAction.cs new file mode 100644 index 0000000..1be0101 --- /dev/null +++ b/Runtime/Interactions/CharacterController/CharacterControllerAction.cs @@ -0,0 +1,32 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using Godot.Collections; + +namespace Rokojori +{ + [GlobalClass] + public partial class CharacterControllerAction:RJAction + { + [Export] + public CharacterController controller; + + public CharacterBody3D body => controller.body; + + public void SetVelocity( Vector3 velocity, bool replace ) + { + if ( replace ) + { + body.Velocity = velocity * controller.delta; + } + else + { + body.Velocity += velocity * controller.delta; + } + } + + + + } + +} \ No newline at end of file diff --git a/Runtime/Interactions/CharacterController/CharacterMovement.cs b/Runtime/Interactions/CharacterController/CharacterMovement.cs new file mode 100644 index 0000000..43aee2b --- /dev/null +++ b/Runtime/Interactions/CharacterController/CharacterMovement.cs @@ -0,0 +1,59 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using Godot.Collections; + +namespace Rokojori +{ + [GlobalClass] + public partial class CharacterMovement:CharacterControllerAction + { + [Export] + public float onFloorMultiply = 0f; + + [Export] + public bool overwriteVelocity = true; + + [ExportGroup( "Moving" )] + + [Export] + public float moveSpeed; + + [Export] + public RJSensor forward; + + [Export] + public RJSensor backwards; + + [ExportGroup( "Strafing" )] + [Export] + public float strafeSpeed; + + [Export] + public RJSensor strafeLeft; + + [Export] + public RJSensor strafeRight; + + public override void _OnTrigger() + { + var movement = Vector3.Zero; + var body = controller.body; + + movement += body.GlobalForward() * Sensors.GetValue( forward ); + movement -= body.GlobalForward() * Sensors.GetValue( backwards ); + + movement += body.GlobalRight() * Sensors.GetValue( strafeRight ); + movement -= body.GlobalRight() * Sensors.GetValue( strafeLeft ); + + if ( body.IsOnFloor() ) + { + movement *= onFloorMultiply; + } + + SetVelocity( movement, overwriteVelocity ); + + } + } + +} \ No newline at end of file diff --git a/Runtime/Interactions/CharacterController/MoveAndSlide.cs b/Runtime/Interactions/CharacterController/MoveAndSlide.cs new file mode 100644 index 0000000..3d4f099 --- /dev/null +++ b/Runtime/Interactions/CharacterController/MoveAndSlide.cs @@ -0,0 +1,16 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using Godot.Collections; + +namespace Rokojori +{ + [GlobalClass] + public partial class MoveAndSlide:CharacterControllerAction + { + public override void _OnTrigger() + { + controller.body.MoveAndSlide(); + } + } +} \ No newline at end of file diff --git a/Runtime/LOD/LODParent.cs b/Runtime/LOD/LODParent.cs new file mode 100644 index 0000000..cf8c5f5 --- /dev/null +++ b/Runtime/LOD/LODParent.cs @@ -0,0 +1,96 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using Godot.Collections; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class LODParent:Node3D + { + [Export] + public float cullDistance = 4000; + + [Export] + public Curve distribution = MathX.Curve( 0, 1 ); + + [Export] + public Node3D[] lods; + + [Export] + public float updateDistance = 10; + + + float lastSquaredDistance = -1; + int lastSelectedIndex = -1; + + [Export] + public int changeBlock = 0; + + [Export] + public int blocker = 0; + + public override void _Process( double delta ) + { + if ( blocker > 0 ) + { + blocker --; + return; + } + + if ( lods == null || lods.Length == 0 ) + { + return; + } + + var camera = GetViewport().GetCamera3D(); + +#if TOOLS + + if ( Engine.IsEditorHint() ) + { + camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D(); + } + +#endif + + var squaredDistance = GlobalPosition.DistanceSquaredTo( camera.GlobalPosition ); + + if ( Mathf.Abs( lastSquaredDistance - squaredDistance ) < updateDistance * updateDistance ) + { + return; + } + + lastSquaredDistance = squaredDistance; + + var realDistance = Mathf.Sqrt( lastSquaredDistance ); + + var normalizedDistance = MathX.NormalizeClamped( realDistance, 0, cullDistance ); + + var active = distribution.Sample( normalizedDistance ) * lods.Length; + var selectedIndex = Mathf.Min( lods.Length - 1, Mathf.RoundToInt( active ) ); + + + if ( lastSelectedIndex == selectedIndex ) + { + return; + } + + // RJLog.Log( + // "realDistance:", realDistance, + // "normalizedDistance:", normalizedDistance, + // "active:", active, + // "selectedIndex:", selectedIndex + // ); + + var selectedLOD = lods[ selectedIndex ]; + + lastSelectedIndex = selectedIndex; + + Arrays.ForEach( lods, l => NodeState.Set( l, selectedLOD == l ) ); + + blocker = changeBlock; + } + } +} \ No newline at end of file diff --git a/Runtime/Math/Geometry/Box2.cs b/Runtime/Math/Geometry/Box2.cs index 6f459ee..ed277b2 100644 --- a/Runtime/Math/Geometry/Box2.cs +++ b/Runtime/Math/Geometry/Box2.cs @@ -28,6 +28,8 @@ namespace Rokojori return new Box2( min, max ); } + public Vector2 center => ( min + max ) / 2; + public Vector2 size => max - min; public void UnionWith( Box2 other ) { min = min.Min( other.min ); @@ -46,7 +48,6 @@ namespace Rokojori max = max.Max( p ); } - public Vector2 size => max - min; public void GrowRelativeToSize( float amount ) { @@ -68,6 +69,22 @@ namespace Rokojori return true; } + public float DistanceTo( Vector2 point ) + { + if ( ContainsPoint( point ) ) + { + return 0; + } + + var c = center; + var s = size; + + var dx = Mathf.Max( Mathf.Abs( point.X - c.X) - s.X / 2, 0); + var dy = Mathf.Max( Mathf.Abs( point.Y - c.Y) - s.Y / 2, 0); + + return Mathf.Sqrt( dx * dx + dy * dy ); + } + public void EnsureCorrectness() { diff --git a/Runtime/Math/Geometry/Box3.cs b/Runtime/Math/Geometry/Box3.cs index 7850900..8009cf4 100644 --- a/Runtime/Math/Geometry/Box3.cs +++ b/Runtime/Math/Geometry/Box3.cs @@ -9,6 +9,13 @@ namespace Rokojori public Vector3 min = Vector3.Zero; public Vector3 max = Vector3.Zero; + public Vector3 center => ( max + min ) / 2f; + + public static implicit operator Box3( Aabb aabb ) + { + return Box3.Create( aabb.Position, aabb.End ); + } + public static Box3 FromPositionAndScale( Vector3 position, Vector3 scale ) { var max = scale * 0.5f; @@ -44,6 +51,8 @@ namespace Rokojori return point; } + public float maxDistance => ( max - min ).Length(); + public static Vector3 Constrain( Vector3 point, Vector3 min, Vector3 max ) { point = min.Max( point ); diff --git a/Runtime/Math/Geometry/Pose.cs b/Runtime/Math/Geometry/Pose.cs index 67e1f8e..bba7b26 100644 --- a/Runtime/Math/Geometry/Pose.cs +++ b/Runtime/Math/Geometry/Pose.cs @@ -69,14 +69,26 @@ namespace Rokojori } } + + Vector3? explicitUp = null; public Vector3 up { get { + if ( explicitUp != null ) + { + return (Vector3) explicitUp; + } + Update(); return _basis.Y; } + + set + { + explicitUp = value; + } } public Pose() diff --git a/Runtime/Math/Geometry/Sphere.cs b/Runtime/Math/Geometry/Sphere.cs index a570fde..eada61d 100644 --- a/Runtime/Math/Geometry/Sphere.cs +++ b/Runtime/Math/Geometry/Sphere.cs @@ -58,6 +58,12 @@ namespace Rokojori var offset = radius * Vector3.One; return Box3.Create( center -offset, center + offset ); } + + + public static Sphere ContainingBox( Box3 box ) + { + return new Sphere( box.center, box.maxDistance / 2f ); + } } } diff --git a/Runtime/Math/Math3D.cs b/Runtime/Math/Math3D.cs index e5367da..f3b7bf8 100644 --- a/Runtime/Math/Math3D.cs +++ b/Runtime/Math/Math3D.cs @@ -242,6 +242,13 @@ namespace Rokojori return aligned.GetRotationQuaternion(); } + + public static Quaternion AlignUp( Vector3 upDirection, Quaternion? q = null ) + { + var quaternion = q == null ? Quaternion.Identity : (Quaternion) q; + return AlignUp( quaternion, upDirection ); + } + public static float AngleXY( Vector3 direction ) { return Mathf.Atan2( direction.Y, direction.X ); @@ -305,6 +312,11 @@ namespace Rokojori return Quaternion.FromEuler( new Vector3( radians, 0, 0 ) ); } + public static Quaternion RotateXDegrees( float degrees ) + { + return RotateX( Mathf.DegToRad( degrees ) ); + } + public static Quaternion RotateY( float radians ) { if ( radians == 0 ) { return Quaternion.Identity; } @@ -312,6 +324,11 @@ namespace Rokojori return Quaternion.FromEuler( new Vector3( 0, radians, 0 ) ); } + public static Quaternion RotateYDegrees( float degrees ) + { + return RotateY( Mathf.DegToRad( degrees ) ); + } + public static Quaternion RotateZ( float radians ) { if ( radians == 0 ) { return Quaternion.Identity; } @@ -319,18 +336,21 @@ namespace Rokojori return Quaternion.FromEuler( new Vector3( 0, 0, radians ) ); } + public static Quaternion RotateZDegrees( float degrees ) + { + return RotateZ( Mathf.DegToRad( degrees ) ); + } + + public static Quaternion YawPitchRotation( float yaw, float pitch ) + { + return RotateXDegrees( pitch ) * RotateYDegrees( yaw ); + } + public static void SetGlobalQuaternion( this Node3D node, Quaternion quaternion ) { - var offset = node.GlobalPosition; - var localScale = node.Scale; node.GlobalBasis = new Basis( quaternion ); - node.GlobalPosition = offset; node.Scale = localScale; - - - - //SetGlobalRotationTo( node, quaternion ); } public static void SetGlobalRotationTo( Node3D node, Quaternion quaternion ) @@ -382,6 +402,17 @@ namespace Rokojori return -node.GlobalBasis.Z; } + public static Vector3 GetGlobalScale( Node3D node ) + { + return node.GlobalTransform.Basis.Scale; + } + + public static float GetGlobalUniScale( Node3D node ) + { + var scale3 = GetGlobalScale( node ); + return MathX.Max( scale3.X, scale3.Y, scale3.Z ); + } + public static Vector3 GlobalUp( this Node3D node ) { return GetGlobalUp( node ); @@ -447,20 +478,66 @@ namespace Rokojori node.GlobalPosition = gp; } - public static Aabb? GetWorldBounds( this Node3D node ) + public static Vector3 Average( List vectors ) { - return GetWorldBoundsFrom( node ); + var average = Vector3.Zero; + + vectors.ForEach( v => average += v ); + + if ( average == Vector3.Zero ) + { + return vectors[ 0 ]; + } + + return ( average / vectors.Count ).Normalized(); } - public static Aabb? GetWorldBoundsFrom( Node3D node ) + public static Vector3 BlendNormals( Vector3 a, Vector3 b, float amount ) + { + if ( amount <= 0 ) + { + return a; + } + + if ( amount >= 1 ) + { + return b; + } + + var n = a.Lerp( b, amount ); + var length = n.Length(); + + if ( length > 0 ) + { + return n / length; + } + + return amount <= 0.5 ? a : b; + } + + public static Aabb? GetWorldBounds( this Node3D node, bool onlyVisible = true ) + { + return GetWorldBoundsFrom( node, onlyVisible ); + } + + public static Aabb? GetWorldBoundsFrom( Node3D node, bool onlyVisible = true ) { Aabb? worldBounds = null; Nodes.ForEach( node, ( vi )=> { + if ( onlyVisible && ! vi.IsVisibleInTree() ) + { + return; + } + var nBounds = vi.GetAabb(); + nBounds.Size *= GetGlobalUniScale( vi ); + nBounds.Position += vi.GlobalPosition; + nBounds.End += vi.GlobalPosition; + worldBounds = worldBounds == null ? nBounds : ( ((Aabb)worldBounds).Merge( nBounds ) ); } ); diff --git a/Runtime/Math/MathX.cs b/Runtime/Math/MathX.cs index 3ce4115..0c12d35 100644 --- a/Runtime/Math/MathX.cs +++ b/Runtime/Math/MathX.cs @@ -35,7 +35,7 @@ namespace Rokojori public static float Min( params float[] values ) { - var value = - float.MaxValue; + var value = float.MaxValue; for ( int i = 0; i < values.Length; i++ ) { diff --git a/Runtime/Math/Smoother.cs b/Runtime/Math/Smoother.cs index c3f03f9..a34018b 100644 --- a/Runtime/Math/Smoother.cs +++ b/Runtime/Math/Smoother.cs @@ -10,8 +10,8 @@ namespace Rokojori public float SmoothForDuration( float value, float nextValue, float duration, float delta, float processDelta = MathX.fps120Delta ) { - var coefficient = MathX.SmoothingCoefficient( duration * 1000f ); - return SmoothWithCoefficient( value, nextValue, coefficient, delta ); + var coefficient = MathX.SmoothingCoefficient( duration * 1000f, processDelta ); + return SmoothWithCoefficient( value, nextValue, coefficient, delta, processDelta ); } @@ -31,8 +31,8 @@ namespace Rokojori public Quaternion SmoothForDuration( Quaternion value, Quaternion nextValue, float duration, float delta, float processDelta = MathX.fps120Delta ) { - var coefficient = MathX.SmoothingCoefficient( duration * 1000f ); - return SmoothWithCoefficient( value, nextValue, coefficient, delta ); + var coefficient = MathX.SmoothingCoefficient( duration * 1000f, processDelta ); + return SmoothWithCoefficient( value, nextValue, coefficient, delta, processDelta ); } public Quaternion SmoothWithCoefficient( Quaternion value, Quaternion nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta ) @@ -46,6 +46,45 @@ namespace Rokojori } return value; + } + + public Vector3 SmoothForDuration( Vector3 value, Vector3 nextValue, float duration, float delta, float processDelta = MathX.fps120Delta ) + { + var coefficient = MathX.SmoothingCoefficient( duration * 1000f, processDelta ); + return SmoothWithCoefficient( value, nextValue, coefficient, delta, processDelta ); } + + public Vector3 SmoothWithCoefficient( Vector3 value, Vector3 nextValue, float coefficient, float delta, float processDelta = MathX.fps120Delta ) + { + overflowDelta += delta; + + while ( overflowDelta > processDelta ) + { + value = value.Lerp( nextValue, coefficient ); + overflowDelta -= processDelta; + } + + return value; + } + + public void CopyPosition( Node3D source, Node3D target, float duration, float delta ) + { + target.GlobalPosition = SmoothForDuration( target.GlobalPosition, source.GlobalPosition, duration, delta ); + } + + public void CopyPosition( Node3D source, Node3D target, float duration, double delta ) + { + CopyPosition( source, target, duration, (float) delta ); + } + + public void CopyRotation( Node3D source, Node3D target, float duration, float delta ) + { + target.SetGlobalQuaternion( SmoothForDuration( target.GetGlobalQuaternion(), source.GetGlobalQuaternion(), duration, delta ) ); + } + + public void CopyRotation( Node3D source, Node3D target, float duration, double delta ) + { + CopyRotation( source, target, duration, (float) delta ); + } } } \ No newline at end of file diff --git a/Runtime/Procedural/Assets/Grass/GrassPatch.cs b/Runtime/Procedural/Assets/Grass/GrassPatch.cs index b790139..ae3a4b0 100644 --- a/Runtime/Procedural/Assets/Grass/GrassPatch.cs +++ b/Runtime/Procedural/Assets/Grass/GrassPatch.cs @@ -76,6 +76,12 @@ namespace Rokojori [Export] public Curve bladeScale = MathX.Curve( 1f ); + [Export] + public Curve bladeHeight = MathX.Curve( 0.4f ); + + [Export] + public Curve bladeInGround = MathX.Curve( 0.05f ); + [Export] public Curve bladeWidth = MathX.Curve( 0.05f ); @@ -88,20 +94,20 @@ namespace Rokojori [Export] public Curve bladeBending2 = null; - [Export] - public Curve bladeHeight = MathX.Curve( 0.4f ); - - [Export] - public Curve bladeInGround = MathX.Curve( 0.05f ); - [Export] public Curve positionJitter = MathX.Curve( 0.05f ); [Export] - public Curve scaleByDistanceX = MathX.Curve( 1f, 1f ); + public Curve scaleByDistanceX = MathX.Curve( 1f ); [Export] - public Curve scaleByDistanceZ = MathX.Curve( 1f, 1f ); + public Curve scaleByDistanceZ = MathX.Curve( 1f ); + + [Export] + public Curve normalBlending = MathX.Curve( 0.5f ); + + [Export] + public Vector3 normalBlendingDirection = Vector3.Up; [Export] public Curve yawRotation = MathX.Curve( 0f, 1f ); @@ -130,6 +136,8 @@ namespace Rokojori return; } + update = false; + var current = SerializedGodotObject.Create( this ); @@ -140,7 +148,7 @@ namespace Rokojori return; } - update = false; + CreatePatch(); @@ -183,6 +191,7 @@ namespace Rokojori var yaw = random.Sample( yawRotation ); var rotationY = yaw * Mathf.Pi * 2f; var maxRotation = random.Sample( randomRotation ); + var normalBlendingAmount = random.Sample( normalBlending ); var rotationOther = random.Next() * Mathf.DegToRad( maxRotation ); var rotationX = random.Next() * rotationOther; var rotationZ = rotationOther - rotationX; @@ -204,14 +213,25 @@ namespace Rokojori } bladeMG.ApplyTransform( trsf ); + + MeshGeometry clonedBladeMG = null; + + if ( createBackFaces ) + { + clonedBladeMG = bladeMG.Clone(); + } + + bladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount ); mg.Add( bladeMG ); if ( createBackFaces ) { - var blade2MG = bladeMG.Clone(); - blade2MG.FlipNormalDirection(); - mg.Add( blade2MG ); + + clonedBladeMG.FlipNormalDirection(); + clonedBladeMG.BlendNormals( normalBlendingDirection, normalBlendingAmount ); + + mg.Add( clonedBladeMG ); } diff --git a/Runtime/Procedural/Baking/Baker.cs b/Runtime/Procedural/Baking/Baker.cs new file mode 100644 index 0000000..6a8b3f1 --- /dev/null +++ b/Runtime/Procedural/Baking/Baker.cs @@ -0,0 +1,169 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class Baker:Node + { + public enum CameraDistanceDetectionType + { + Automatic_Distance_Detection, + Custom_Distance + } + + public enum CameraFOVMode + { + Keep_Fov, + Compute_Fov_With_Distance, + Custom_Fov + } + + public enum MeshMode + { + World_Scale, + Custom_Scale + } + + [Export] + public bool update = false; + + [Export] + public bool updateAlways = false; + + [Export] + public Viewport viewport; + + [Export] + public Node3D target; + + [Export] + public Camera3D camera; + + + [Export] + public float originalFOV = 75; + + + [Export] + public bool assignFOV = false; + + [Export] + public float placingDistance = 500; + + [Export] + public float computedFOV = 75; + + [Export] + public bool useCustomFOV = false; + + [Export] + public float customFOV = 75; + + [Export] + public bool useCustomDistance = false; + + [Export] + public float customDistance = 50; + + [Export] + public float outputScale = 1; + + [Export] + public Node3D outputTexture; + + [Export] + public float outputTextureSize = 1; + + [Export( PropertyHint.Range, "-180,180")] + public float yaw = 0; + + [Export( PropertyHint.Range, "-180,180")] + public float pitch = 0; + + public Quaternion bakingRotation => Math3D.YawPitchRotation( yaw, pitch ); + + [Export] + public float zoom = 1; + + [Export] + public float distance = 1; + + public override void _Process( double delta ) + { + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + Bake(); + + } + + void Bake() + { + if ( viewport == null || target == null || camera == null ) + { + return; + } + + var box = target.GetWorldBounds(); + + if ( box == null ) + { + RJLog.Log( "No target" ); + return; + } + + var sphere = Sphere.ContainingBox( box ); + camera.Fov = originalFOV; + + var billboardFOV = camera.Fov; + + + if ( assignFOV ) + { + computedFOV = Cameras.ComputeFOVForBillboard( originalFOV, sphere.radius, placingDistance ); + billboardFOV = computedFOV; + camera.Fov = billboardFOV; + } + + if ( useCustomFOV ) + { + billboardFOV = customFOV; + camera.Fov = billboardFOV; + } + + var newDistance = useCustomDistance ? customDistance : Cameras.ComputeCameraFrameFittingDistance( billboardFOV, sphere.radius / zoom ); + + if ( newDistance != distance ) + { + distance = newDistance; + RJLog.Log( "New Distance:", box, sphere, distance ); + } + + var cameraRotation = Math3D.RotateXDegrees( pitch ) * Math3D.RotateYDegrees( yaw ); + var offset = ( Vector3.Back * distance ) * cameraRotation ; + camera.GlobalPosition = target.GlobalPosition + offset; + + camera.SetGlobalQuaternion( cameraRotation.Inverse() ); + + RJLog.Log( "Set Rotation", cameraRotation, ">>", camera.GetGlobalQuaternion() ); + + outputScale = Cameras.ComputeCameraFittingScale( camera.Fov, distance ); + + if ( outputTexture != null ) + { + outputTexture.Scale = Vector3.One * outputScale; + } + } + + } +} diff --git a/Runtime/Procedural/Baking/MultiBaker.cs b/Runtime/Procedural/Baking/MultiBaker.cs new file mode 100644 index 0000000..1dc3ef9 --- /dev/null +++ b/Runtime/Procedural/Baking/MultiBaker.cs @@ -0,0 +1,320 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class MultiBaker:Node + { + [Export] + public bool initialize; + + [Export] + public bool setupViews; + + + public enum BakeMode + { + Cylindric_Billboard + } + + [Export] + public BakeMode bakeMode; + + [Export] + public Node3D sourceTarget; + + [Export] + public Node outputTarget; + + + [ExportGroup( "Camera Settings")] + [Export] + public Baker.CameraDistanceDetectionType distanceDetectionType = Baker.CameraDistanceDetectionType.Automatic_Distance_Detection; + + [Export] + public float customDistance = 50; + + [Export] + public float cameraZoom = 1; + + [Export] + public Baker.CameraFOVMode fovMode = Baker.CameraFOVMode.Compute_Fov_With_Distance; + [Export] + public float originalFOV = 75; + [Export] + public float fovPlacingDistance = 200; + [Export] + public float customFOV = 75; + + [Export] + public Baker.MeshMode meshMode = Baker.MeshMode.World_Scale; + + + [ExportGroup("Cylindric Billboard")] + + [Export] + public int cylinderSides = 4; + [Export] + public bool cylinderTop = false; + [Export] + public bool cylinderBottom = false; + [Export] + public float cylinderSideOffset = 0; + [Export] + public float cylinderTopOffset = 0.5f; + [Export] + public float cylinderBottomOffset = 0.5f; + [Export] + public MeshInstance3D cylinderMesh; + + + [ExportGroup("Viewport")] + [Export] + public Vector2 textureSize = new Vector2( 2048, 2048 ); + + [ExportGroup("Debugging")] + + [Export] + public SubViewport X_bakingViewport; + + [Export] + public Node3D X_bakingTargetContainer; + + [Export] + public Node X_views; + + [Export] + public WorldEnvironment X_worldEnvironment; + + public override void _Process( double delta ) + { + if ( initialize ) + { + initialize = false; + Initialize(); + } + + if ( setupViews ) + { + setupViews = false; + CreateViews(); + SetupViews(); + } + } + + public void Initialize() + { + if ( outputTarget == null ) + { + outputTarget = this; + } + + Nodes.RemoveAndDeleteChildren( outputTarget ); + + X_bakingViewport = outputTarget.CreateChild( "Multi Baker Viewport" ); + + X_bakingViewport.Size = (Vector2I) textureSize; + X_bakingViewport.OwnWorld3D = true; + X_bakingViewport.TransparentBg = true; + + X_worldEnvironment = X_bakingViewport.CreateChild( "Multi Baker Environment" ); + X_worldEnvironment.Environment = new Godot.Environment(); + X_worldEnvironment.Environment.AmbientLightSource = Godot.Environment.AmbientSource.Color; + X_worldEnvironment.Environment.AmbientLightColor = HSLColor.white; + + X_bakingTargetContainer = X_bakingViewport.CreateChild( "Target Container" ); + + X_views = X_bakingViewport.CreateChild( "Views" ); + + sourceTarget.DeepCopyTo( X_bakingTargetContainer ); + + } + + public int GetNumViews() + { + if ( BakeMode.Cylindric_Billboard == bakeMode ) + { + var cylinderViews = cylinderSides; + + if ( cylinderBottom ) + { + cylinderViews ++; + } + + if ( cylinderTop ) + { + cylinderViews ++; + } + + return cylinderViews; + } + + return 0; + } + + List _bakers; + + public void CreateViews() + { + Nodes.RemoveAndDeleteChildren( X_views ); + + var numViews = GetNumViews(); + + _bakers = new List(); + + for ( int i = 0; i < numViews; i++ ) + { + var userIndex = ( i + 1 ); + var bakingView = X_views.CreateChild( "Baking View " + userIndex ); + bakingView.TransparentBg = true; + + var bakingCamera = bakingView.CreateChild( "Camera View " + userIndex ); + var baker = bakingView.CreateChild( "Baker " + userIndex ); + + baker.camera = bakingCamera; + baker.target = X_bakingTargetContainer; + baker.viewport = bakingView; + + baker.update = true; + + _bakers.Add( baker ); + } + } + + public void SetupViews() + { + if ( BakeMode.Cylindric_Billboard == bakeMode ) + { + CreateCylinderBillboardView(); + } + } + + Sphere _targetBoundingSphere; + + Sphere targetBoundingSphere + { + get + { + if ( _targetBoundingSphere == null ) + { + ComputeBoundingSphere(); + } + + return _targetBoundingSphere; + } + } + + void ComputeBoundingSphere() + { + var worldBounds = X_bakingTargetContainer.GetWorldBounds(); + _targetBoundingSphere = Sphere.ContainingBox( worldBounds ); + } + + float GetCameraFOV() + { + if ( Baker.CameraFOVMode.Custom_Fov == fovMode ) + { + return customFOV; + } + + if ( Baker.CameraFOVMode.Keep_Fov == fovMode ) + { + return originalFOV; + } + + return Cameras.ComputeFOVForBillboard( originalFOV, targetBoundingSphere.radius, fovPlacingDistance ); + } + + float GetCameraDistance() + { + if ( Baker.CameraDistanceDetectionType.Custom_Distance == distanceDetectionType ) + { + return customDistance; + } + + var fov = GetCameraFOV(); + + return Cameras.ComputeCameraFrameFittingDistance( fov, targetBoundingSphere.radius / cameraZoom ); + } + + float GetOutputScale() + { + var fov = GetCameraFOV(); + var distance = GetCameraDistance(); + + return Cameras.ComputeCameraFittingScale( fov, distance ); + } + + void CreateCylinderBillboardView() + { + _targetBoundingSphere = null; + var fov = GetCameraFOV(); + var distance = GetCameraDistance(); + var outputScale = GetOutputScale(); + + _bakers.ForEach( + b => + { + b.useCustomFOV = true; + b.customFOV = fov; + + b.useCustomDistance = true; + b.customDistance = distance; + } + ); + + var index = 0; + var mg = new MeshGeometry(); + + var numTextures = ( cylinderTop ? 1 : 0 ) + ( cylinderBottom ? 1 : 0 ) + cylinderSides; + var textureAlignment = TextureMerger.ComputeTextureAlignment( numTextures ); + + + if ( cylinderTop ) + { + _bakers[ index ].yaw = 0; + _bakers[ index ].pitch = 90f; + + var uvRectangle = TextureMerger.GetUVRectangle( textureAlignment, index ); + mg.AddQuad( _bakers[ index ].bakingRotation, outputScale, uvRectangle ); + + index ++; + } + + if ( cylinderBottom ) + { + _bakers[ index ].yaw = 0; + _bakers[ index ].pitch = -90f; + + var uvRectangle = TextureMerger.GetUVRectangle( textureAlignment, index ); + mg.AddQuad( _bakers[ index ].bakingRotation, outputScale, uvRectangle ); + + index ++; + } + + + for ( int i = 0; i < cylinderSides; i++ ) + { + var angle = ( 360f * i ) / (float) cylinderSides; + _bakers[ index + i ].yaw = angle; + _bakers[ index + i ].pitch = 0; + + var uv = TextureMerger.GetUVRectangle( textureAlignment, index + 1); + mg.AddQuad( _bakers[ index + i ].bakingRotation, outputScale, uv ); + } + + if ( cylinderMesh != null ) + { + cylinderMesh.Mesh = mg.GenerateMesh(); + } + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Baking/OctahedralMapping.cs b/Runtime/Procedural/Baking/OctahedralMapping.cs new file mode 100644 index 0000000..e1abae4 --- /dev/null +++ b/Runtime/Procedural/Baking/OctahedralMapping.cs @@ -0,0 +1,45 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public class OctahedralMapping + { + public static Vector3 Hemisphere( Vector2 uv ) + { + var position = new Vector3 (uv.X - uv.Y, 0, -1.0f + uv.X + uv.Y ); + var absolute = position.Abs(); + position.Y = 1.0f - absolute.X - absolute.Z; + + return position; + } + + public static Vector3 Sphere( Vector2 uv ) + { + uv = uv * 2.0f - Vector2.One; + var position = new Vector3( uv.X, 0, uv.Y ); + var absolute = position.Abs(); + position.Y = 1.0f - absolute.X - absolute.Z; + + if ( position.Y < 0 ) + { + var sign = position.Sign(); + position.X = sign.X * ( 1.0f - absolute.Z ); + position.Z = sign.Z * ( 1.0f - absolute.X ); + } + + return position; + } + + public static Vector3 Map( Vector2 uv, bool sphere ) + { + var position = sphere ? Sphere( uv ) : Hemisphere( uv ); + + return position.Normalized(); + } + } +} diff --git a/Runtime/Procedural/Baking/SaveViewportTexture.cs b/Runtime/Procedural/Baking/SaveViewportTexture.cs new file mode 100644 index 0000000..2ecdbb7 --- /dev/null +++ b/Runtime/Procedural/Baking/SaveViewportTexture.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class SaveViewportTexture:RJAction + { + [Export] + public SubViewport viewport; + + [Export] + public string path = ""; + + [Export] + public float quality = 1; + + public override void _OnTrigger() + { + Textures.Save( viewport.GetTexture(), path, quality ); + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Baking/SetBakingMaterials.cs b/Runtime/Procedural/Baking/SetBakingMaterials.cs new file mode 100644 index 0000000..54a20b4 --- /dev/null +++ b/Runtime/Procedural/Baking/SetBakingMaterials.cs @@ -0,0 +1,73 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class SetBakingMaterials:RJAction + { + [Export] + public Node targetContainer; + + public enum BakingMaterialMode + { + Albedo, + Normals, + ORM, + Depth + } + + [Export] + public BakingMaterialMode mode; + + public override void _OnTrigger() + { + var baseMaterial = GetBaseMaterial(); + + Nodes.ForEach( targetContainer, + ( v )=> + { + + } + ); + } + + + + public static readonly string materialsPath = "res://addons/rokojori_action_library/External/Imposter/materials/"; + static Dictionary _cachedMaterials = new Dictionary(); + + public static Material LoadMaterial( string materialName ) + { + if ( _cachedMaterials.ContainsKey( materialName ) ) + { + return _cachedMaterials[ materialName ]; + } + + var loadedMaterial = ResourceLoader.Load( materialsPath + materialName ) as Material; + _cachedMaterials[ materialName ] = loadedMaterial; + + return loadedMaterial; + } + + public Material GetBaseMaterial() + { + switch ( mode ) + { + case BakingMaterialMode.Albedo: return LoadMaterial( "albedo_material.material" ); + case BakingMaterialMode.Normals: return LoadMaterial( "normal_baker.material" ); + case BakingMaterialMode.ORM: return LoadMaterial( "orm_baker.material" ); + case BakingMaterialMode.Depth: return LoadMaterial( "depth_baker.material" ); + + } + + return null; + } + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Baking/TextureMerger.cs b/Runtime/Procedural/Baking/TextureMerger.cs new file mode 100644 index 0000000..e288a5c --- /dev/null +++ b/Runtime/Procedural/Baking/TextureMerger.cs @@ -0,0 +1,269 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class TextureMerger:Node + { + public enum SourceMode + { + Viewports, + Textures + } + + [Export] + public bool initialize; + + [Export] + public bool createLayout; + + + [ExportGroup("Source")] + [Export] + public SourceMode sourceMode; + + + [ExportGroup("Source/Viewports")] + [Export] + public Node sourceViewportsContainer = null; + + [Export] + public SubViewport[] sourceViewports; + + [ExportGroup("Source/Textures")] + [Export] + public Texture2D[] sourceTextures; + + [ExportGroup("Output")] + [Export] + public Node outputTarget; + + public enum LayoutMode + { + Grid, + Custom + } + + [Export] + public LayoutMode layoutMode = LayoutMode.Grid; + + [ExportGroup("Output/Custom")] + [Export] + public Vector2[] customPositions; + + [Export] + public Vector2[] customSizes; + + [ExportGroup("Viewport")] + [Export] + public Vector2 textureSize = new Vector2( 2048, 2048 ); + + [Export] + public BaseMaterial3D.TransparencyEnum transparencyMode = BaseMaterial3D.TransparencyEnum.AlphaScissor; + + [ExportGroup("Debugging")] + [Export] + public SubViewport X_textureMergerViewport; + + [Export] + public Camera3D X_mergerCamera; + + List _textures = new List(); + + public override void _Process( double delta ) + { + if ( initialize ) + { + initialize = false; + Initialize(); + } + + if ( createLayout ) + { + createLayout = false; + CreateLayout(); + } + } + + public void Initialize() + { + if ( outputTarget == null ) + { + outputTarget = this; + } + + Nodes.RemoveAndDeleteChildren( outputTarget ); + + X_textureMergerViewport = outputTarget.CreateChild( "Texture Merger Viewport" ); + + X_mergerCamera = X_textureMergerViewport.CreateChild( "Texture Merger Camera" ); + X_mergerCamera.Projection = Camera3D.ProjectionType.Orthogonal; + + X_textureMergerViewport.Size = (Vector2I) textureSize; + X_textureMergerViewport.OwnWorld3D = true; + X_textureMergerViewport.TransparentBg = true; + + X_mergerCamera.Size = 1; + + } + + void GrabTextures() + { + _textures = new List(); + + if ( SourceMode.Textures == sourceMode ) + { + _textures.AddRange( sourceTextures ); + return; + } + + if ( SourceMode.Viewports == sourceMode ) + { + if ( sourceViewportsContainer != null ) + { + sourceViewports = Nodes.AllIn( sourceViewportsContainer, null, false ).ToArray(); + } + + var vpTextures = Arrays.Map( sourceViewports, + s => + { + var vt = new ViewportTexture(); + vt.ViewportPath = s.GetPath(); + return vt; + } + ); + + _textures.AddRange( vpTextures ); + } + } + + void CreateLayout() + { + GrabTextures(); + + if ( LayoutMode.Grid == layoutMode ) + { + CreateGridLayout(); + } + else if ( LayoutMode.Custom == layoutMode ) + { + CreateCustomLayout(); + } + } + + Vector2 ConvertUVtoCameraSpace( Vector2 uv ) + { + var scale = Vector2.One; + var w = 1; + var h = 1; + + if ( X_textureMergerViewport.Size.X != X_textureMergerViewport.Size.Y ) + { + if ( X_textureMergerViewport.Size.X > X_textureMergerViewport.Size.Y ) + { + w = X_textureMergerViewport.Size.X / X_textureMergerViewport.Size.Y; + } + else + { + h = X_textureMergerViewport.Size.Y / X_textureMergerViewport.Size.X; + } + } + + var x = Mathf.Remap( uv.X, 0, 1, -w, w ); + var y = Mathf.Remap( uv.Y, 0, 1, -h, h ); + + return new Vector2( x, y ); + } + + void CreateGridLayout() + { + var alignment = ComputeTextureAlignment( _textures.Count ); + + Nodes.RemoveAndDeleteChildrenOfType( X_textureMergerViewport ); + + for ( int i = 0; i < _textures.Count; i++ ) + { + var mesh = X_textureMergerViewport.CreateChild( "Texture " + ( i + 1 ) ); + + var uvRectangle = GetUVRectangle( alignment, i ); + SetMeshCoordinates( mesh, uvRectangle ); + + var material = new StandardMaterial3D(); + material.Transparency = transparencyMode; + material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; + + material.AlbedoTexture = _textures[ i ]; + mesh.Material = material; + } + } + + void CreateCustomLayout() + { + for ( int i = 0; i < _textures.Count; i++ ) + { + var mesh = outputTarget.CreateChild( "Texture " + ( i + 1 ) ); + + SetMeshCoordinates( mesh, customPositions[ i ], customSizes[ i ] ); + + var material = new StandardMaterial3D(); + material.Transparency = transparencyMode; + material.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded; + + material.AlbedoTexture = _textures[ i ]; + mesh.Material = material; + } + } + + void SetMeshCoordinates( CsgMesh3D mesh, Rect2 rectangle ) + { + SetMeshCoordinates( mesh, rectangle.Position, rectangle.Size ); + } + + void SetMeshCoordinates( CsgMesh3D mesh, Vector2 start, Vector2 size ) + { + start = ConvertUVtoCameraSpace( start ); + var end = ConvertUVtoCameraSpace( start + size ); + + size = end - start; + + var quadMesh = new QuadMesh(); + quadMesh.Size = size; + mesh.Mesh = quadMesh; + + mesh.GlobalPosition = new Vector3( start.X - size.X , start.Y - size.Y, -1 ); + } + + public static Vector2I ComputeTextureAlignment( int numElements ) + { + var root = Mathf.Sqrt( numElements ); + + var ceiled = Mathf.CeilToInt( root ); + var floored = ceiled - 1; + + var weight = Mathf.RoundToInt( root % 1 ); + + var height = weight * ceiled + ( 1 - weight ) * floored; + + return new Vector2I( ceiled, height ); + } + + public static Rect2 GetUVRectangle( Vector2I textureAlignment, int index ) + { + var x = index % textureAlignment.X; + var y = index / textureAlignment.Y; + + var xs = 1f / textureAlignment.X; + var ys = 1f / textureAlignment.Y; + + return new Rect2( x * xs, y * ys, new Vector2( xs, ys ) ); + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/Baking/Textures.cs b/Runtime/Procedural/Baking/Textures.cs new file mode 100644 index 0000000..2bdddfc --- /dev/null +++ b/Runtime/Procedural/Baking/Textures.cs @@ -0,0 +1,64 @@ +using System.Collections; +using System.Collections.Generic; +using Godot; +using System; + + + +namespace Rokojori +{ + public static class Textures + { + public static void Save( Texture2D texture, string path, float quality = 0.75f ) + { + var image = texture.GetImage(); + + Save( image, path ); + } + + public static void Save( Image image, string path, float quality = 0.75f ) + { + var fp = FilePath.Absolute( path ); + + var output = ".png"; + + if ( + fp.hasFileExtension( ".jpg" ) || + fp.hasFileExtension( ".exr" ) || + fp.hasFileExtension( ".webp" ) + ) + { + output = fp.fileExtension; + } + + switch ( output ) + { + case ".jpg": + { + image.SaveJpg( path, quality ); + } + break; + + case ".png": + { + image.SavePng( path ); + } + break; + + case ".webp": + { + image.SaveWebp( path, quality < 1, quality ); + } + break; + + case ".exr": + { + image.SaveExr( path ); + } + break; + } + } + + + } +} \ No newline at end of file diff --git a/Runtime/Procedural/HeightMap/HeightMapData.cs b/Runtime/Procedural/HeightMap/HeightMapData.cs new file mode 100644 index 0000000..96be375 --- /dev/null +++ b/Runtime/Procedural/HeightMap/HeightMapData.cs @@ -0,0 +1,141 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public class HeightMapData + { + int width; + int height; + float[] data; + + public static HeightMapData Create( int w, int h, float[] data = null) + { + var hmd = new HeightMapData(); + hmd.width = w; + hmd.height = h; + + hmd.data = data == null ? new float[ w * h ] : data; + + return hmd; + } + + public float Get( int x, int y ) + { + x = Mathf.Clamp( x, 0, width - 1 ); + y = Mathf.Clamp( y, 0, height - 1 ); + return data[ x + y * width ]; + } + + public float GetLerped( float x, float y ) + { + var x0 = Mathf.FloorToInt( x ); + var x1 = x0 + 1; + + var y0 = Mathf.FloorToInt( y ); + var y1 = y0 + 1; + + var xL = x - x0; + var yL = y - y0; + + // xy xy + // 00 10 + // 10 11 + + var upValue = Mathf.Lerp( Get( x0, y0 ), Get( x1, y0 ), xL ); + var downValue = Mathf.Lerp( Get( x0, y1 ), Get( x1, y1 ), xL ); + + return Mathf.Lerp( upValue, downValue, yL ); + } + + public void Set( int x, int y, float value ) + { + if ( x < 0 || y < 0 || x >= width || y >= height ) + { + return; + } + + data[ x + y * width ] = value; + } + + public Vector3 GetNormal( int x, int y, float strength = 8f ) + { + var tl = Get( x-1, y-1 ); + var l = Get( x-1, y ); + var bl = Get( x-1, y+1) ; + + var t = Get( x, y-1 ); + var b = Get( x, y+1 ); + + var tr = Get( x+1, y-1 ); + var r = Get( x+1, y ); + var br = Get( x+1, y+1 ); + + + var nx = ( tr + 2.0f * r + br ) - ( tl + 2.0f * l + bl ); + var nz = ( bl + 2.0f * b + br ) - ( tl + 2.0f * t + tr ); + var ny = 1.0f / strength; + + return -new Vector3( nx, ny, nz ).Normalized(); + } + + public Vector3 GetNormal( float x, float y, float strength = 1f ) + { + return GetNormal( (int)x, (int)y, strength ); + } + + + public MeshGeometry GenerateMeshGeometry( Vector3 scale ) + { + var func = ( Vector2 uv )=> + { + var p = new Pose(); + + var x = uv.X * width; + var z = uv.Y * height; + var y = GetLerped( x, z ); + + p.position = new Vector3( x * scale.X, y * scale.Y, z * scale.Z ); + p.up = GetNormal( x, z ); + return p; + + }; + + return MeshGeometry.CreateFromUVFunction( func, width - 1, height - 1 ); + } + + public HeightMapData CreateLowerResolution( float averageVsMaxFilter = 0.5f ) + { + var lw = width/2; + var lh = height/2; + + var lower = Create( lw, lh ); + + for ( int i = 0; i < lw; i++ ) + { + for ( int j = 0; j < lh; j++ ) + { + var x0 = i * 2; + var x1 = x0 + 1; + var y0 = j * 2; + var y1 = y0 +1; + + var s0 = Get( x0, y0 ); + var s1 = Get( x1, y0 ); + var s2 = Get( x0, y1 ); + var s3 = Get( x1, y1 ); + + var maxValue = MathX.Max( s0, s1, s2, s3 ); + var avgValue = (s0 + s1 + s2 + s3 ) / 4f; + var value = Mathf.Lerp( avgValue, maxValue, averageVsMaxFilter ); + + lower.Set( i, j, value ); + } + } + + return lower; + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/HeightMap/LODHeightMapGeometry.cs b/Runtime/Procedural/HeightMap/LODHeightMapGeometry.cs new file mode 100644 index 0000000..440afa2 --- /dev/null +++ b/Runtime/Procedural/HeightMap/LODHeightMapGeometry.cs @@ -0,0 +1,109 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class LODHeightMapGeometry:LODParent + { + [Export] + public float size = 500; + + [Export] + public float height = 1000; + + [Export] + public int resolution = 128; + + [Export] + public float noiseScale = 0.0025f; + + [Export] + public float noiseScaleAmount = 10f; + + [Export] + public float noiseScale2 = 0.025f; + + [Export] + public float noiseScale2Amount = 1f; + + [Export] + public float noiseScale3 = 0.15f; + + [Export] + public float noiseScale3Amount = 0.1f; + + [Export] + public int levels = 6; + + [Export] + public Material material; + + public void Create() + { + var hmd = HeightMapData.Create( resolution, resolution ); + + var offsetX = GlobalPosition.X - size/2f; + var offsetY = GlobalPosition.Z - size/2f; + + var weights = noiseScaleAmount + noiseScale2Amount + noiseScale3Amount; + + for ( int i = 0; i < 128; i ++ ) + { + for ( int j = 0; j < 128; j++ ) + { + var x = i / (float)resolution * size + offsetX; + var y = j / (float)resolution * size + offsetY; + + var v = Noise.Perlin( new Vector2( x, y ) * noiseScale ) * noiseScaleAmount ; + var v2 = Noise.Perlin( new Vector2( x, y ) * noiseScale2 ) * noiseScale2Amount; + var v3 = Noise.Perlin( new Vector2( x, y ) * noiseScale3 ) * noiseScale3Amount; + + hmd.Set( i, j, ( v + v2 + v3 ) / weights ); + } + } + + var level = 1; + var ratio = size / resolution; + var scale = new Vector3( ratio * level, height, ratio * level ); + + var meshInstance3D = this.CreateChild(); + meshInstance3D.Mesh = hmd.GenerateMeshGeometry( scale ).GenerateMesh(); + Materials.Set( meshInstance3D, material ); + + var offset = size / -2; + meshInstance3D.Position = new Vector3( offset, 0, offset ); + + var levelScale = 2; + + var meshes = new List(); + + meshes.Add( meshInstance3D ); + + while ( level < levels ) + { + hmd = hmd.CreateLowerResolution(); + level ++; + + scale = new Vector3( ratio * levelScale , height, ratio * levelScale ); + + var lowMeshInstance3D = this.CreateChild(); + lowMeshInstance3D.Mesh = hmd.GenerateMeshGeometry( scale ).GenerateMesh(); + + Materials.Set( lowMeshInstance3D, material ); + + lowMeshInstance3D.Position = new Vector3( offset, 0, offset ); + + levelScale *= 2; + + meshes.Add( lowMeshInstance3D ); + } + + lods = meshes.ToArray(); + + } + } +} \ No newline at end of file diff --git a/Runtime/Procedural/MeshGeometry.cs b/Runtime/Procedural/MeshGeometry.cs index b64daee..972adc8 100644 --- a/Runtime/Procedural/MeshGeometry.cs +++ b/Runtime/Procedural/MeshGeometry.cs @@ -90,6 +90,29 @@ namespace Rokojori } } + public void BlendNormals( Vector3 direction, float amount ) + { + if ( amount <= 0 ) + { + return; + } + + if ( amount >= 1 ) + { + for ( int i = 0; i < normals.Count; i++ ) + { + normals[ i ] = direction; + } + + return; + } + + for ( int i = 0; i < normals.Count; i++ ) + { + normals[ i ] = Math3D.BlendNormals( normals[ i ], direction, amount ); + } + } + public void Offset( Vector3 offset ) { for ( int i = 0; i < vertices.Count; i++ ) @@ -119,6 +142,7 @@ namespace Rokojori return u * ( segments + 1 ) + v; } + public static MeshGeometry CreateFromUVFunction( Func uv, int uSegments, int vSegments ) { var mg = new MeshGeometry(); @@ -265,7 +289,7 @@ namespace Rokojori public MeshGeometry( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false ) { Initialize( normals, uvs, colors, uvs2 ); - } + } public void Initialize( bool normals = true, bool uvs = true, bool colors = false, bool uvs2 = false ) { @@ -275,6 +299,39 @@ namespace Rokojori this.colors = colors ? new List() : null; } + public Vector3 GetRawNormal( int triangleIndex ) + { + var va = vertices[ indices[ triangleIndex * 3 ] ]; + var vb = vertices[ indices[ triangleIndex * 3 + 1] ]; + var vc = vertices[ indices[ triangleIndex * 3 + 2 ] ]; + + return Math3D.ComputeNormal( va, vb, vc ); + } + + public void ComputeNormals() + { + var dl = new DictionaryList(); + + ForEachTriangle( + ( t, va, vb, vc)=> + { + var normal = GetRawNormal( t ); + + dl.Add( va, normal ); + dl.Add( vb, normal ); + dl.Add( vc, normal ); + } + ); + + foreach ( var e in dl ) + { + var normalIndex = e.Key; + var normal = Math3D.Average( e.Value ); + + normals[ normalIndex ] = normal; + } + } + public MeshGeometry Clone() { var mg = new MeshGeometry(); @@ -355,9 +412,17 @@ namespace Rokojori } - public void AddTriangle( int a, int b, int c ) + public void AddTriangle( int a, int b, int c, bool flip = false ) { - Lists.Add( indices, a, b, c ); + if ( flip ) + { + Lists.Add( indices, c, b, a ); + } + else + { + Lists.Add( indices, a, b, c ); + } + } @@ -408,9 +473,49 @@ namespace Rokojori AddTriangle( vc, vd, va, nc, nd, na, uvc, uvd, uva ); } - public void AddQuad( int lt, int rt, int lb, int rb ) + public void AddQuad( Quaternion rotation, float size, Rect2 rectangle ) { - Lists.Add( indices, lb, rt, lt, rt, lb, rb ); + AddQuad( rotation, size, rectangle.Position, rectangle.End ); + } + + public void AddQuad( Quaternion rotation, float size, Vector2 uv00, Vector2 uv11 ) + { + var l = size * 0.5f; + + var normal = Vector3.Forward * rotation; + var points = new List + { + new Vector3( -l, -l, 0 ), new Vector3( l, -l, 0 ), + new Vector3( -l, l, 0 ), new Vector3( l, l, 0 ) + }; + + for ( int i = 0; i < points.Count; i++ ) + { + points[ i ] = points[ i ] * rotation; + } + + var uv10 = new Vector2( uv11.X, uv00.Y ); + var uv01 = new Vector2( uv00.X, uv11.Y ); + + AddQuad( + points[ 0 ], points[ 1 ], points[ 2 ], points[ 3 ], + normal, normal, normal, normal, + uv00, uv10, uv11, uv10 + ); + + } + + public void AddQuad( int lt, int rt, int lb, int rb, bool flip = false ) + { + if ( flip ) + { + AddQuad( rt, lt, rb, lb, false ); + } + else + { + Lists.Add( indices, lb, rt, lt, rt, lb, rb ); + } + } diff --git a/Runtime/Procedural/Parametric/Cuboid/Cuboid.cs b/Runtime/Procedural/Parametric/Cuboid/Cuboid.cs new file mode 100644 index 0000000..ca19372 --- /dev/null +++ b/Runtime/Procedural/Parametric/Cuboid/Cuboid.cs @@ -0,0 +1,338 @@ + +using Godot; +using Rokojori; +using System.Collections.Generic; + +namespace Rokojori +{ + [Tool] + [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Spline.svg") ] + public partial class Cuboid : Node3D + { + [Export] + public float size = 1; + + [Export] + public float widthExtension = 0; + + [Export] + public float heightExtension = 0; + + [Export] + public float depthExtension = 0; + + [Export] + public float borderSize = 0.1f; + + [Export] + public MeshInstance3D output; + + [Export] + public bool update = false; + + [Export] + public bool updateAlways = false; + + public override void _Process( double delta ) + { + if ( ! ( update || updateAlways ) ) + { + return; + } + + update = false; + + Create(); + } + + + public float X() + { + return size + widthExtension; + } + + public float Y() + { + return size + heightExtension; + } + + public float Z() + { + return size + depthExtension; + } + + MeshGeometry mg = null; + + public void Create() + { + var maxBorderSize = ( MathX.Min( widthExtension, heightExtension, depthExtension ) + size ) / 2f; + borderSize = Mathf.Clamp( borderSize, 0, maxBorderSize ); + mg = new MeshGeometry(); + + for ( int i = 0; i < 24; i++ ) + { + var p = GetPointAt( i ); + mg.vertices.Add( p ); + + if ( i < 4 ) + { + mg.normals.Add( Vector3.Up ); + } + else if ( i < 12 ) + { + var cornerIndex = i - 4; + mg.normals.Add( GetEdgeNormal( cornerIndex ) ); + } + else if ( i < 20 ) + { + var cornerIndex = i - 12; + mg.normals.Add( GetEdgeNormal( cornerIndex ) ); + } + else + { + mg.normals.Add( Vector3.Down ); + } + + + mg.uvs.Add( Vector2.Zero ); + } + + mg.AddQuad( 0, 1, 2, 3 ); + + // back + AddSide( 2, 1 ); + // right + AddSide( 7, 3 ); + // left + AddSide( 0, 4 ); + //front + AddSide( 5, 6 ); + + + // side b,r + AddSide( 3, 2 ); + // side f, r + AddSide( 6, 7 ); + // side f, l + AddSide( 4, 5 ); + // side b, l + AddSide( 1, 0 ); + + + // left, back + AddCorner( 0, 1, 0 ); + + // back, right + AddCorner( 2, 3, 1 ); + + // left, front + AddCorner( 5, 4, 2 ); + + // front, right + AddCorner( 7, 6, 3 ); + + + // back + AddFaceSide( 0, 1, 2, 1 ); + + // right + AddFaceSide( 1, 3, 7, 3 ); + + // front + AddFaceSide( 3, 2, 5, 6 ); + + // left + AddFaceSide( 2, 0, 0, 4 ); + + + + mg.AddQuad( 20, 21, 22, 23, true ); + + output.Mesh = mg.GenerateMesh(); + + } + + void AddSide( int a, int b ) + { + mg.AddQuad( te( a ), te( b ), be( a ), be( b ) ); + } + + void AddFaceSide( int fA, int fB, int eA, int eB ) + { + mg.AddQuad( tf( fB ), tf( fA), te( eA ), te( eB ) ); + mg.AddQuad( bf( fB ), bf( fA), be( eA ), be( eB ), true ); + } + + void AddCorner( int eA, int eB, int f ) + { + mg.AddTriangle( te( eA ), te( eB ), tf( f ), true ); + mg.AddTriangle( be( eA ), be( eB ), bf( f ) ); + } + + int tf( int index ) + { + return index; + } + + int te( int index ) + { + return index + 4; + } + + int be( int index ) + { + return index + 12; + } + + int bf( int index ) + { + return index + 20; + } + + + public Vector3 GetEdgeNormal( int index ) + { + switch ( index ) + { + case 1: case 2: return Vector3.Back; + case 3: case 7: return Vector3.Right; + case 0: case 4: return Vector3.Left; + case 5: case 6: return Vector3.Forward; + } + + return Vector3.Zero; + } + + public Vector3 GetOffsetFor( int left, int front ) + { + var x = borderSize * Mathf.Sign( left ); + var z = borderSize * Mathf.Sign( front ); + + return new Vector3( x, 0, z ); + } + + public Vector3 GetBoundingCornerAt( int index, bool top ) + { + // -Z + // 0 1 + // *----* + //-X | | +X + // *----* + // 2 3 + // +Z + + var y = Y() / ( top ? 2 : -2 ); + + if ( 0 == index ) + { + return new Vector3( -X()/2, y, +Z()/2 ); + } + else if ( 1 == index ) + { + return new Vector3( +X()/2, y, +Z()/2 ); + } + else if ( 2 == index ) + { + return new Vector3( -X()/2, y, -Z()/2 ); + } + else if ( 3 == index ) + { + return new Vector3( +X()/2, y, -Z()/2 ); + } + + return Vector3.Zero; + + } + + public Vector3 GetPointAt( int index ) + { + // 00-03 4 Top Face + // 04-11 8 Top Edges + // 12-19 8 Bottom Edges + // 20-23 4 Bottom Face + + if ( index < 4 ) + { + return GetFacePoint( index, true ); + } + else if ( index < 12 ) + { + return GetEdgePoint( index - 4, true ); + } + else if ( index < 20 ) + { + return GetEdgePoint( index - 12, false ); + } + else if ( index < 24 ) + { + return GetFacePoint( index - 20, false ); + } + + return Vector3.Zero; + } + + Vector3 GetFacePoint( int index, bool top ) + { + var cornerPoint = GetBoundingCornerAt( index, top ); + + var offset = Vector3.Zero; + + if ( 0 == index ) + { + offset = GetOffsetFor( 1, -1 ); + } + else if ( 1 == index ) + { + offset = GetOffsetFor( -1, -1 ); + } + else if ( 2 == index ) + { + offset = GetOffsetFor( 1, 1 ); + } + else if ( 3 == index ) + { + offset = GetOffsetFor( -1, 1 ); + } + + return cornerPoint + offset; + } + + Vector3 GetEdgePoint( int index, bool top ) + { + var cornerIndex = index / 2; + var cornerPoint = GetBoundingCornerAt( cornerIndex, top ); + + if ( top ) + { + cornerPoint.Y -= borderSize; + } + else + { + cornerPoint.Y += borderSize; + } + + var offset = Vector3.Zero; + + if ( 0 == cornerIndex ) + { + offset = index == 0 ? GetOffsetFor( 0, -1 ) : GetOffsetFor( 1, 0 ); + } + else if ( 1 == cornerIndex ) + { + offset = index == 2 ? GetOffsetFor( -1, 0 ) : GetOffsetFor( 0, -1 ); + } + else if ( 2 == cornerIndex ) + { + offset = index == 4 ? GetOffsetFor( 0, 1 ) : GetOffsetFor( 1, 0 ); + } + else if ( 3 == cornerIndex ) + { + offset = index == 6 ? GetOffsetFor( -1, 0 ) : GetOffsetFor( 0, 1 ); + } + + return cornerPoint + offset; + } + + } + +} \ No newline at end of file diff --git a/Runtime/Sensors/KeySensor.cs b/Runtime/Sensors/KeySensor.cs new file mode 100644 index 0000000..61402c8 --- /dev/null +++ b/Runtime/Sensors/KeySensor.cs @@ -0,0 +1,110 @@ + +using Godot; + + +namespace Rokojori +{ + [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/RJSensor.svg")] + public partial class KeySensor : RJSensor + { + [Export] + public Key key; + + [ExportGroup( "Modifiers")] + [Export] + public Trillean ctrlHold = Trillean.Any; + + [Export] + public Trillean altHold = Trillean.Any; + + [Export] + public Trillean shiftHold = Trillean.Any; + + public bool modifiersEnabled => TrilleanLogic.AllAny( ctrlHold, altHold, shiftHold ); + + public enum ModifiersMode + { + Hold_Modifiers_Only_On_Down, + Hold_Modifiers_All_The_Time + } + + [Export] + public ModifiersMode modifiersMode; + + bool _isActive; + bool _wasActive; + float _value = 0; + float axisActivationTreshold = 0.5f; + float _lastInput = 0; + + public override void _Process( double delta ) + { + UpdateValue( _lastInput ); + } + + public override void _Input( InputEvent ev ) + { + var keyEvent = ev as InputEventKey; + + if ( keyEvent == null ) + { + return; + } + + if ( keyEvent.Keycode != key) + { + return; + } + + var checkModifiers = modifiersEnabled && + ( + ModifiersMode.Hold_Modifiers_All_The_Time == modifiersMode || + _lastInput == 0 && ModifiersMode.Hold_Modifiers_Only_On_Down == modifiersMode + ); + + if ( checkModifiers ) + { + if ( ! TrilleanLogic.Matches( ctrlHold, keyEvent.CtrlPressed ) ) + { + _lastInput = 0; + return; + } + + if ( ! TrilleanLogic.Matches( altHold, keyEvent.AltPressed ) ) + { + _lastInput = 0; + return; + } + + if ( ! TrilleanLogic.Matches( shiftHold, keyEvent.ShiftPressed ) ) + { + _lastInput = 0; + return; + } + } + + _lastInput = keyEvent.IsPressed() ? 1 : 0; + + + } + + public override bool IsActive() + { + return _isActive; + } + + public override bool WasActive() + { + return _wasActive; + } + + public override void UpdateValue( float value ) + { + _value = value; + + _wasActive = _isActive; + _isActive = _value > axisActivationTreshold; + } + + } +} \ No newline at end of file diff --git a/Runtime/Sensors/MouseMotionDelta.cs b/Runtime/Sensors/MouseMotionDelta.cs new file mode 100644 index 0000000..7610a39 --- /dev/null +++ b/Runtime/Sensors/MouseMotionDelta.cs @@ -0,0 +1,95 @@ + +using Godot; + + +namespace Rokojori +{ + [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/RJSensor.svg")] + public partial class MouseMotionDelta : RJSensor + { + public enum MouseMotionType + { + Right, + Left, + Up, + Down + } + + [Export] + public MouseMotionType motionType; + + [Export] + public float speedMultiply = 1; + + bool _isActive = false; + bool _wasActive = false; + float _value = 0; + float axisActivationTreshold = 0.5f; + float _lastInput = 0; + + public override void _Process( double delta ) + { + UpdateValue( _lastInput ); + _lastInput = 0; + } + + public override void _Input( InputEvent ev ) + { + var mouseEvent = ev as InputEventMouseMotion ; + + if ( mouseEvent == null ) + { + return; + } + + var delta = mouseEvent.ScreenRelative; + var motion = 0f; + + if ( MouseMotionType.Left == motionType) + { + motion = Mathf.Max( 0, -delta.X ); + } + else if ( MouseMotionType.Right == motionType) + { + motion = Mathf.Max( 0, delta.X ); + } + else if ( MouseMotionType.Up == motionType) + { + motion = Mathf.Max( 0, delta.Y ); + } + if ( MouseMotionType.Down == motionType) + { + motion = Mathf.Max( 0, -delta.Y ); + } + + // RJLog.Log( "Motion:", motionType, motion * speedMultiply ); + + _lastInput = motion * speedMultiply; + + } + + public override bool IsActive() + { + return _isActive; + } + + public override bool WasActive() + { + return _wasActive; + } + + public override float GetValue() + { + return _value; + } + + public override void UpdateValue( float value ) + { + _value = value; + + _wasActive = _isActive; + _isActive = _value > axisActivationTreshold; + } + + } +} \ No newline at end of file diff --git a/Runtime/Shading/Materials.cs b/Runtime/Shading/Materials.cs new file mode 100644 index 0000000..14b02c7 --- /dev/null +++ b/Runtime/Shading/Materials.cs @@ -0,0 +1,61 @@ +using Godot; +using System.Reflection; +using System.Collections.Generic; + +namespace Rokojori +{ + public class Materials + { + public static void Set( Node node, Material material, int index = 0 ) + { + if ( node is MeshInstance3D ) + { + var mi = (MeshInstance3D) node; + + mi.SetSurfaceOverrideMaterial( index, material ); + + if ( index == 0 && mi.MaterialOverride != null ) + { + mi.MaterialOverride = null; + } + + return; + } + + if ( node is CsgPrimitive3D ) + { + ReflectionHelper.SetMemberValue( node, "material", material ); + + var cp = (GpuParticles3D) node; + cp.ProcessMaterial = material; + + if ( cp.MaterialOverride != null ) + { + cp.MaterialOverride = null; + } + + return; + } + + if ( node is GpuParticles3D ) + { + var gp = (GpuParticles3D) node; + gp.ProcessMaterial = material; + + if ( gp.MaterialOverride != null ) + { + gp.MaterialOverride = null; + } + } + + if ( node is GeometryInstance3D ) + { + var gi = (GeometryInstance3D) node; + + gi.MaterialOverride = material; + return; + } + + } + } +} \ No newline at end of file diff --git a/Runtime/Tools/Arrays.cs b/Runtime/Tools/Arrays.cs index 3769779..d4764eb 100644 --- a/Runtime/Tools/Arrays.cs +++ b/Runtime/Tools/Arrays.cs @@ -12,6 +12,18 @@ namespace Rokojori return Array.IndexOf( values, other ); } + public static U[] Map( T[] values, Func mapper ) + { + var u = new U[ values.Length ]; + + for ( int i = 0; i < values.Length; i++ ) + { + u[ i ] = mapper( values[ i ] ); + } + + return u; + } + public static int FindIndex( T[] values, Func predicate ) { for ( int i = 0; i < values.Length; i++ ) diff --git a/Runtime/Tools/Trillean.cs b/Runtime/Tools/Trillean.cs new file mode 100644 index 0000000..3288905 --- /dev/null +++ b/Runtime/Tools/Trillean.cs @@ -0,0 +1,49 @@ +using System.Collections; +using System.Collections.Generic; +using System; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + public enum Trillean + { + False, + True, + Any + } + + public static class TrilleanLogic + { + public static bool Matches( Trillean value, bool state, bool anyValue = true ) + { + if ( Trillean.Any == value ) + { + return anyValue; + } + + var boolValue = Trillean.True == value; + + return boolValue == state; + + } + + public static bool AllAny( params Trillean[] values ) + { + if ( values == null || values.Length == 0 ) + { + return false; + } + + for ( int i = 0; i < values.Length; i++ ) + { + if ( values[ i ] != Trillean.Any ) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/MouseEditorCamera.cs b/Runtime/VirtualCameras/MouseEditorCamera.cs index 5a71312..ead12be 100644 --- a/Runtime/VirtualCameras/MouseEditorCamera.cs +++ b/Runtime/VirtualCameras/MouseEditorCamera.cs @@ -89,6 +89,7 @@ namespace Rokojori public override void _Process( double delta ) { + Orbit(); Pan(); Zoom(); @@ -109,18 +110,21 @@ namespace Rokojori public override void _Input( InputEvent inputEvent ) { - if ( inputEvent is InputEventMouseMotion ) + var mouseMotionEvent = inputEvent as InputEventMouseMotion; + + if ( mouseMotionEvent == null ) { - var eventMouseMotion = inputEvent as InputEventMouseMotion; - motionDelta = eventMouseMotion.ScreenRelative; - hasMotionDelta = true; + return; } + + motionDelta = mouseMotionEvent.ScreenRelative; + hasMotionDelta = true; } void Orbit() { - if ( ! orbitButton.IsActive() ) + if ( ! Sensors.IsActive( orbitButton ) ) { return; } @@ -134,7 +138,7 @@ namespace Rokojori void Pan() { - if ( ! panButton.IsActive() ) + if ( ! Sensors.IsActive( panButton ) ) { return; } @@ -148,12 +152,12 @@ namespace Rokojori void Zoom() { - if ( zoomInButton.IsActive() ) + if ( Sensors.IsActive( zoomInButton ) ) { distance *= Mathf.Pow( 1 + zoomStepInPercentage / 100f, 1 ); } - if ( zoomOutButton.IsActive() ) + if ( Sensors.IsActive( zoomOutButton ) ) { distance *= Mathf.Pow( 1 + zoomStepInPercentage / 100f, -1 ); } diff --git a/Runtime/VirtualCameras/ThirdPersonCamera.cs b/Runtime/VirtualCameras/ThirdPersonCamera.cs new file mode 100644 index 0000000..17a749d --- /dev/null +++ b/Runtime/VirtualCameras/ThirdPersonCamera.cs @@ -0,0 +1,123 @@ + +using System.Diagnostics; +using System.Collections; +using System.Collections.Generic; +using System; +using Godot; + + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class ThirdPersonCamera:VirtualCamera3D + { + [Export] + public Node3D target; + + [ExportGroup("Yaw")] + + [Export] + public float yawSpeed; + + [Export] + public RJSensor yawPositiveAxis; + + [Export] + public RJSensor yawNegativeAxis; + + + [Export] + public float yaw = 0; + + [Export] + public float yawSmoothingDuration = 0.1f; + + Smoother yawSmoother = new Smoother(); + + [ExportGroup("Pitch")] + + [Export] + public float pitchSpeed; + + [Export] + public RJSensor pitchPositiveAxis; + + [Export] + public RJSensor pitchNegativeAxis; + + [Export] + public float pitch = 0; + + [Export] + public float minPitch = -10; + + [Export] + public float maxPitch = 80; + + [Export] + public float pitchSmoothingDuration = 0.1f; + + Smoother pitchSmoother = new Smoother(); + + [Export] + public Curve distanceForPitch = MathX.Curve( 1, 1 ); + + [Export] + public float distanceScale = 1; + + Smoother distanceSmoother = new Smoother(); + + float smoothedYaw = 0; + float smoothedPitch = 0; + float smoothedDistance = 0; + + + public override void _Process( double delta ) + { + if ( target == null ) + { + return; + } + + var yawAxis = Sensors.PolarAxis( yawNegativeAxis, yawPositiveAxis ); + var pitchAxis = Sensors.PolarAxis( pitchNegativeAxis, pitchPositiveAxis ); + + + yaw += yawAxis * yawSpeed * (float)delta; + yaw = MathX.Repeat( yaw, 360f ); + + // pitch += pitchAxis * pitchSpeed * (float)delta; + // pitch = Mathf.Clamp( pitch, minPitch, maxPitch ); + + + if ( Mathf.Abs( yaw - smoothedYaw ) > 180 ) + { + if ( yaw > smoothedYaw ) + { + smoothedYaw += 360; + } + else if ( yaw < smoothedYaw ) + { + smoothedYaw -= 360; + } + } + + // smoothedYaw = yawSmoother.SmoothForDuration( smoothedYaw, yaw, yawSmoothingDuration, (float) delta ); + // smoothedPitch = pitchSmoother.SmoothForDuration( smoothedPitch, pitch, pitchSmoothingDuration, (float) delta ); + + smoothedYaw = yaw; + smoothedPitch = pitch; + var distance = distanceForPitch.Sample( MathX.NormalizeClamped( pitch, minPitch, maxPitch ) ) * distanceScale; + + GlobalPosition = target.GlobalPosition + Math3D.YawPitchRotation( smoothedYaw, smoothedPitch ) * Vector3.Forward * distance; + + LookAt( target.GlobalPosition, Vector3.Up, true ); + + + + } + + + } +} \ No newline at end of file diff --git a/Runtime/VirtualCameras/VirtualCamera3DManager.cs b/Runtime/VirtualCameras/VirtualCamera3DManager.cs index b1557ca..419b975 100644 --- a/Runtime/VirtualCameras/VirtualCamera3DManager.cs +++ b/Runtime/VirtualCameras/VirtualCamera3DManager.cs @@ -63,6 +63,7 @@ namespace Rokojori if ( sumPriority == 0 ) { + // RJLog.Log( "sumPriority == 0", _cameraSlots.Count, Lists.Map( _cameraSlots, c => c.priority ) ); return; } @@ -126,6 +127,7 @@ namespace Rokojori up = up.Normalized(); } + // RJLog.Log( "Set Cam", position ); camera.GlobalPosition = position; camera.LookAt( position - forward, up ); diff --git a/Runtime/WorldMap/WorldMap.cs b/Runtime/WorldMap/WorldMap.cs new file mode 100644 index 0000000..13ead5a --- /dev/null +++ b/Runtime/WorldMap/WorldMap.cs @@ -0,0 +1,254 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + + [Tool] + [GlobalClass] + public partial class WorldMap:Node + { + [Export] + public WorldMapDefinition worldMapDefinition; + + [Export] + public Node regionsContainer; + + [Export] + public Node3D[] streamTargets; + + [Export] + public float streamTargetRadius = 500; + + [Export] + public WorldMapLayerSetting[] layerSettings; + + [Export] + public float hueScale = 0.01f; + + public enum LoadingStatus + { + Not_Loaded, + Loading, + Loaded, + Unloading, + Error + } + + public class WorldMapRegionInfo + { + public float loadingTime; + public string id; + public LoadingStatus status; + public WorldRegion region; + public List layerInfos = new List(); + } + + public class WorldMapLayerInfo + { + public LoadingStatus status; + public WorldMapLayer layer; + } + + protected Dictionary _loadedRegions = new Dictionary(); + + public override void _Process( double delta ) + { + StreamRegions(); + } + + public Vector2 WorldToRegions( Vector3 worldPosition ) + { + return Math2D.XZ( worldPosition / worldMapDefinition.regionSize ); + } + + public Vector3 RegionsToWorld( Vector2 regionsPosition ) + { + return Math3D.XYasXZ( regionsPosition ) * worldMapDefinition.regionSize; + } + + public string CreateRegionID( Vector2 regionsPosition ) + { + var x = Mathf.RoundToInt( regionsPosition.X ); + var y = Mathf.RoundToInt( regionsPosition.Y ); + + return CreateRegionID( x, y ); + } + + public string CreateRegionID( int x, int y) + { + return x + "," + y; + } + + public Vector2I ParseID( string id ) + { + var numbers = id.Split( "," ); + + var x = RegexUtility.ParseInt( numbers[ 0 ] ); + var y = RegexUtility.ParseInt( numbers[ 1 ] ); + + return new Vector2I( x, y ); + } + + public Box2 GetXZWorldBoxOfRegion( int regionX, int regionY ) + { + var boxStart = RegionsToWorld( new Vector2( regionX, regionY ) ) ; + var boxEnd = boxStart + new Vector3( worldMapDefinition.regionSize, 0, worldMapDefinition.regionSize ); + + return new Box2( Math2D.XZ( boxStart ), Math2D.XZ( boxEnd ) ); + } + + protected void ClearRegions() + { + Nodes.RemoveAndDeleteChildren( regionsContainer ); + _loadedRegions.Clear(); + } + + protected void StreamRegions() + { + var regionsToStream = new HashSet(); + + for ( int i = 0; i < streamTargets.Length; i++ ) + { + GetStreamRegions( streamTargets[ i ], regionsToStream ); + } + + foreach ( var s in regionsToStream ) + { + if ( _loadedRegions.ContainsKey( s ) ) + { + continue; + } + + LoadRegion( s ); + } + + foreach ( var s in _loadedRegions ) + { + if ( regionsToStream.Contains( s.Key ) ) + { + continue; + } + + UnloadRegion( s.Key ); + } + } + + + + void GetStreamRegions( Node3D streamTarget, HashSet regions ) + { + var streamPosition = streamTarget.GlobalPosition; + + var streamMin = streamPosition - Vector3.One * streamTargetRadius; + var streamMax = streamPosition + Vector3.One * streamTargetRadius; + + var positionMin = WorldToRegions( streamPosition - Vector3.One * streamTargetRadius ); + var positionMax = WorldToRegions( streamPosition + Vector3.One * streamTargetRadius ); + + + positionMin = positionMin.Floor(); + positionMax = positionMax.Ceil(); + + // RJLog.Log( "CheckRegions", streamMin, streamMax, positionMin, positionMax ); + + var min = new Vector2I( (int)positionMin.X, (int)positionMin.Y ); + var max = new Vector2I( (int)positionMax.X, (int)positionMax.Y ); + + var index = 0; + + var streamPositionXZ = Math2D.XZ( streamPosition ); + + for ( int i = min.X; i <= max.X; i++ ) + { + for ( int j = min.Y; j <= max.Y; j++ ) + { + var regionID = CreateRegionID( i, j ); + + if ( regions.Contains( regionID ) ) + { + continue; + } + + var boxXZ = GetXZWorldBoxOfRegion( i, j ); + + var distance = boxXZ.DistanceTo( streamPositionXZ ); + + if ( index < 3 ) + { + // RJLog.Log( i, j, boxXZ.center, distance ); + index++; + } + + + if ( distance > streamTargetRadius ) + { + continue; + } + + regions.Add( regionID ); + } + } + + } + + void LoadRegion( string regionID ) + { + var regionIndex = ParseID( regionID ); + + // RJLog.Log( "Loading regionID:", regionID, regionIndex.X, regionIndex.Y ); + var regionInfo = new WorldMapRegionInfo(); + regionInfo.id = regionID; + regionInfo.loadingTime = Time.GetTicksMsec() / 1000f; + + _loadedRegions[ regionID ] = regionInfo; + + var worldRegion = regionsContainer.CreateChild( regionID ); + worldRegion.indexX = regionIndex.X; + worldRegion.indexZ = regionIndex.Y; + + var s = worldMapDefinition.regionSize; + var h = s / 2f; + var hue = Noise.PerlinXZ( RegionsToWorld( regionIndex ) * hueScale ); + + var material = new StandardMaterial3D(); + material.AlbedoColor = new HSLColor( hue * 360, 1, 0.5f ); + material.Metallic = 0.2f; + material.Roughness = 0.6f; + + worldRegion.GlobalPosition = RegionsToWorld( regionIndex ) + new Vector3( h, 0, h ); + + // var lodHM = worldRegion.CreateChild(); + // lodHM.material = material; + // lodHM.Create(); + + + var box = worldRegion.CreateChild( "Box" ); + box.Size = new Vector3( s, 1, s ); + box.Material = material; + + + + regionInfo.region = worldRegion; + + + + + } + + void UnloadRegion( string regionID ) + { + // RJLog.Log( "Unloading regionID:", regionID ); + + var info = _loadedRegions[ regionID ]; + + Nodes.RemoveAndDelete( info.region ); + + _loadedRegions.Remove( regionID ); + } + + + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldMapDefinition.cs b/Runtime/WorldMap/WorldMapDefinition.cs new file mode 100644 index 0000000..c917940 --- /dev/null +++ b/Runtime/WorldMap/WorldMapDefinition.cs @@ -0,0 +1,22 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldMapDefinition:Resource + { + [Export] + public string worldMapPath; + + [Export] + public float regionSize = 250; + + [Export] + public WorldMapLayerDefinition[] layers; + + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldMapEditor.cs b/Runtime/WorldMap/WorldMapEditor.cs new file mode 100644 index 0000000..aca19a6 --- /dev/null +++ b/Runtime/WorldMap/WorldMapEditor.cs @@ -0,0 +1,51 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldMapEditor:WorldMap + { + [Export] + public Node layerEditorsContainer; + + [Export] + public bool streaming = true; + + [Export] + public bool createEmptyRegions = false; + + [Export] + public PackedScene emptyRegion; + + [ExportGroup("Debugging")] + [Export] + public int numTiles = 0; + + bool wasStreaming = false; + + public override void _Process( double delta ) + { + if ( ! streaming ) + { + wasStreaming = false; + return; + } + + numTiles = _loadedRegions.Count; + + if ( ! wasStreaming ) + { + ClearRegions(); + } + + StreamRegions(); + + wasStreaming = true; + } + + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldMapLayer.cs b/Runtime/WorldMap/WorldMapLayer.cs new file mode 100644 index 0000000..0d207cd --- /dev/null +++ b/Runtime/WorldMap/WorldMapLayer.cs @@ -0,0 +1,16 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldMapLayer:Node + { + [Export] + public WorldMapLayerDefinition layerDefinition; + + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldMapLayerDefinition.cs b/Runtime/WorldMap/WorldMapLayerDefinition.cs new file mode 100644 index 0000000..7bdf020 --- /dev/null +++ b/Runtime/WorldMap/WorldMapLayerDefinition.cs @@ -0,0 +1,16 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldMapLayerDefinition:Resource + { + [Export] + public string layerName; + + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldMapLayerEditor.cs b/Runtime/WorldMap/WorldMapLayerEditor.cs new file mode 100644 index 0000000..477dec3 --- /dev/null +++ b/Runtime/WorldMap/WorldMapLayerEditor.cs @@ -0,0 +1,16 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldMapLayerEditor:Node + { + [Export] + public WorldMapLayerDefinition layerDefinition; + + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldMapLayerSetting.cs b/Runtime/WorldMap/WorldMapLayerSetting.cs new file mode 100644 index 0000000..a21edeb --- /dev/null +++ b/Runtime/WorldMap/WorldMapLayerSetting.cs @@ -0,0 +1,18 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldMapLayerSetting:Resource + { + [Export] + public WorldMapLayerDefinition layerDefinition; + + [Export] + public bool active = true; + } +} \ No newline at end of file diff --git a/Runtime/WorldMap/WorldRegion.cs b/Runtime/WorldMap/WorldRegion.cs new file mode 100644 index 0000000..93e5eb5 --- /dev/null +++ b/Runtime/WorldMap/WorldRegion.cs @@ -0,0 +1,18 @@ +using Godot; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace Rokojori +{ + [Tool] + [GlobalClass] + public partial class WorldRegion:Node3D + { + [Export] + public int indexX = 0; + + [Export] + public int indexZ = 0; + } +} \ No newline at end of file