C++ Backend Removal

This commit is contained in:
Josef 2025-01-03 13:09:23 +01:00
parent 73cdec243f
commit b852e63bf0
279 changed files with 12349 additions and 736 deletions

View File

@ -1,19 +1,20 @@
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() {
void vertex()
{
POSITION = vec4(VERTEX, 1.0);
CAMERA = INV_VIEW_MATRIX;
}
void fragment() {
void fragment()
{
float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
float depth = texture( DEPTH_TEXTURE, SCREEN_UV ).x;
//Vulkan.z size range 0-1 penGL.z size range -11
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
@ -22,10 +23,10 @@ void fragment() {
vec4 world = CAMERA * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;
float aabb_full = depth_scaler;
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;
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));
ALBEDO = vec3(clamp(world_position.z/aabb_full,0,1));
}

Binary file not shown.

View File

@ -10,7 +10,7 @@ vec4 dilate(sampler2D a_tex, sampler2D tex, vec2 coords, vec2 texel_size, int di
{
vec2 offsets[8] = {
vec2(-1,0),vec2(1,0), vec2(0,1), vec2(0,-1),
vec2(-1,0),vec2(1,0), vec2(0,1), vec2(0,-1),
vec2(-1,1), vec2(1,1), vec2(1,-1), vec2(-1,-1)
};
@ -34,7 +34,7 @@ vec4 dilate(sampler2D a_tex, sampler2D tex, vec2 coords, vec2 texel_size, int di
else
return vec4(off_sample.rgb, a_sample.a);
}
}
}
return sample;

Binary file not shown.

View File

@ -1,12 +1,12 @@
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 vec4 albedo_color : source_color;
uniform sampler2D albedo_texture : 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);
@ -14,17 +14,19 @@ uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
void vertex() {
UV=UV*uv1_scale.xy+uv1_offset.xy;
void vertex()
{
UV = UV * uv1_scale.xy + uv1_offset.xy;
}
void fragment() {
void fragment()
{
vec2 base_uv = UV;
vec4 albedo_tex = texture(texture_albedo,base_uv);
vec4 normal_tex = texture(normal_texture,base_uv);
vec4 albedo = texture( albedo_texture, base_uv ) * albedo_color;
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)
if ( use_normalmap )
{
vec3 normalmap;
normalmap.xy = normal_tex.xy * 2.0 - 1.0;
@ -32,12 +34,12 @@ void fragment() {
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)
ALBEDO = vec3( -NORMAL.x, NORMAL.y, -NORMAL.z) * 0.5 + 0.5;
if ( use_alpha_texture )
{
ALPHA = albedo_tex.a;
ALPHA = albedo.a;
ALPHA_SCISSOR_THRESHOLD = alpha_scissor_threshold;
}

Binary file not shown.

Binary file not shown.

View File

@ -31,7 +31,7 @@ void fragment() {
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)
{

Binary file not shown.

Binary file not shown.

View File

@ -32,6 +32,9 @@ uniform bool dither = false;
uniform float scale = 1.0f;
uniform float depth_scale : hint_range(0, 1) = 0.0f;
uniform float depthMapPivot = 0.5;
uniform float depthMapScale = 1;
uniform float depthMapOffset = 0;
uniform float normalmap_depth : hint_range(-5, 5) = 1.0f;

View File

@ -0,0 +1,373 @@
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;
uniform vec2 indexOffset = vec2(0,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 = recalculateUV(uv_frame2, frame2, xy_frame2, quad_size, depth_scale, imposterTextureDepth);
vec4 albedoTex = texture( imposterTextureAlbedo, uv );
ALBEDO = albedoTex.rgb;
NORMAL = texture( imposterTextureNormal, uv ).rgb;
ALPHA = albedoTex.a;
ALPHA_SCISSOR_THRESHOLD = 0.5;
vec4 ormTex = texture( imposterTextureOrm, uv );
METALLIC = ormTex.b * metallic;
SPECULAR = specular;
ROUGHNESS = ormTex.g * roughness;
}

3
External/Imposter/test.tres vendored Normal file
View File

@ -0,0 +1,3 @@
[gd_resource type="StandardMaterial3D" format=3 uid="uid://b8cbxcruu45c0"]
[resource]

21
Runtime/Actions/Action.cs Normal file
View File

@ -0,0 +1,21 @@
using Godot;
namespace Rokojori
{
[GlobalClass ]
public partial class Action : NetworkNode
{
public void Trigger()
{
_OnTrigger();
}
public virtual void _OnTrigger()
{
}
}
}

View File

@ -53,7 +53,7 @@ namespace Rokojori
public void ProcessNext()
{
RJLog.Log( "@" + sequence.Name, "Index:", sequencablesIndex );
// RJLog.Log( "@" + sequence.Name, "Index:", sequencablesIndex );
if ( sequencablesIndex == 0 )
{
@ -218,7 +218,7 @@ namespace Rokojori
// RJLog.Log( "Running", HierarchyName.Of( this ), run.sequencables.Count );
running.Add( run );
run.ProcessNext();
}
}
public override void CancelAction( int id )
{

View File

@ -0,0 +1,31 @@
using Godot;
namespace Rokojori
{
[GlobalClass ]
public partial class IterateActions : RJAction
{
[ExportGroup( "Read Only")]
[Export]
public int iterationIndex = 0;
public override void _OnTrigger()
{
var num = this.NumDirectChildrenOf<RJAction>();
if ( num == 0 )
{
iterationIndex = 0;
return;
}
iterationIndex = MathX.Repeat( iterationIndex, num );
Actions.Trigger( this.GetNthDirectChild<RJAction>( iterationIndex ) );
iterationIndex++;
}
}
}

View File

@ -0,0 +1,34 @@
using Godot;
namespace Rokojori
{
[GlobalClass, Tool ]
public partial class CopyPose : RJAction
{
[Export]
public Node3D source;
[Export]
public Node3D target;
public override void _Process( double delta )
{
_OnTrigger();
}
public override void _OnTrigger()
{
if ( source == null || target == null )
{
return;
}
target.GlobalPosition = source.GlobalPosition;
target.GlobalRotation = source.GlobalRotation;
}
}
}

View File

@ -0,0 +1,81 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class AnimationCurve:Resource
{
[Export]
public float duration = 1;
[Export]
public float durationRandomRange = 0;
[Export]
public float delay = 0;
[Export]
public float delayRandomRange = 0;
[Export]
public Curve curve = MathX.Curve( 0, 0, -1, 1 );
[Export]
public float scaleY = 1;
[Export]
public float scaleRandomRange = 0;
public float GetRandomizedDuration( Vector3 random )
{
return duration + random.X;
}
public float GetRandomizedDelay( Vector3 random )
{
return delay + random.Y;
}
public float GetRandomizedEndTime( Vector3 random )
{
return GetRandomizedDuration( random ) + GetRandomizedDelay( random );
}
public float GetRandomizedScale( Vector3 random )
{
return scaleY + random.Z;
}
public Vector3 Randomize( RandomEngine random = null )
{
random = random == null ? GodotRandom.Get() : random;
return new Vector3(
random.Polar() * durationRandomRange,
random.Polar() * delayRandomRange,
random.Polar() * scaleRandomRange
);
}
public float Sample( float time, float durationOffset = 0, float delayOffset = 0, float scaleOffset = 0 )
{
time -= Mathf.Max( 0f, delay + delayOffset );
var phase = MathX.Clamp01( time / ( Mathf.Max( 0.001f, duration + durationOffset ) ) );
return curve.Sample( phase ) * ( scaleY + scaleOffset );
}
public float Sample( float time, Vector3 randomization )
{
return Sample( time, randomization.X, randomization.Y, randomization.Z );
}
}
}

View File

@ -0,0 +1,93 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class AnimationCurve3D:Resource
{
[Export]
public float duration = 1;
[Export]
public float durationRandomRange = 0;
[Export]
public float delay = 0;
[Export]
public float delayRandomRange = 0;
[Export]
public Curve xCurve = MathX.Curve( 0, 0, -1, 1 );
[Export]
public Curve yCurve = MathX.Curve( 0, 0, -1, 1 );
[Export]
public Curve zCurve = MathX.Curve( 0, 0, -1, 1 );
[Export]
public float scaleAll = 1;
[Export]
public float scaleAllRandomRange = 0;
public float GetRandomizedDuration( Vector3 random )
{
return duration + random.X;
}
public float GetRandomizedDelay( Vector3 random )
{
return delay + random.Y;
}
public float GetRandomizedEndTime( Vector3 random )
{
return GetRandomizedDuration( random ) + GetRandomizedDelay( random );
}
public float GetRandomizedScale( Vector3 random )
{
return scaleAll + random.Z;
}
public Vector3 Randomize( RandomEngine random = null )
{
random = random == null ? GodotRandom.Get() : random;
return new Vector3(
random.Polar() * durationRandomRange,
random.Polar() * delayRandomRange,
random.Polar() * scaleAllRandomRange
);
}
public Vector3 Sample( float time, float durationOffset = 0, float delayOffset = 0, float scaleOffset = 0 )
{
time -= Mathf.Max( 0f, delay + delayOffset );
var phase = MathX.Clamp01( time / ( Mathf.Max( 0.001f, duration + durationOffset ) ) );
var x = xCurve.Sample( phase ) * ( scaleAll + scaleOffset );
var y = yCurve.Sample( phase ) * ( scaleAll + scaleOffset );
var z = zCurve.Sample( phase ) * ( scaleAll + scaleOffset );
return new Vector3( x, y, z );
}
public Vector3 Sample( float time, Vector3 randomization )
{
return Sample( time, randomization.X, randomization.Y, randomization.Z );
}
}
}

View File

@ -0,0 +1,74 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
public interface Animator
{
public void OnAnimatorStart();
public void OnAnimatorEnd();
public void OnAnimatorCancel();
}
/*
public void OnAnimatorStart(){}
public void OnAnimatorEnd(){}
public void OnAnimatorCancel(){}
*/
public class AnimationMember
{
string _name;
public string name => _name;
public AnimationMember( string name )
{
_name = name;
}
}
public class AnimationManager
{
static MultiMap<Node,string,Animator> _animating = new MultiMap<Node,string,Animator>();
public static AnimationMember position = new AnimationMember( "position" );
public static AnimationMember rotation = new AnimationMember( "rotation" );
public static AnimationMember scale = new AnimationMember( "scale" );
public static Animator GetAnimator( Node node, AnimationMember member )
{
return _animating[ node ][ member.name ];
}
public static bool IsAnimating( Animator animator, Node node, AnimationMember member )
{
return GetAnimator( node, member ) == animator;
}
public static void StartAnimation( Animator animator, Node node, AnimationMember member )
{
var activeAnimator = GetAnimator( node, member );
if ( activeAnimator != null )
{
activeAnimator.OnAnimatorCancel();
}
_animating.Set( node, member.name, animator );
}
public static void EndAnimation( Animator animator, Node node, AnimationMember member )
{
var activeAnimator = GetAnimator( node, member );
if ( activeAnimator != null )
{
activeAnimator.OnAnimatorCancel();
}
}
}
}

261
Runtime/Animation/Flash.cs Normal file
View File

@ -0,0 +1,261 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class Flash:RJSequenceAction
{
[Export]
public FlashEffect flashEffect;
[Export]
public Node3D[] targets;
int animationID = -1;
int actionID = -1;
Vector3 randomization;
List<Material> _materials;
OmniLight3D light = null;
Color ComputeLightColor( Color color, float phase )
{
if ( phase < 5/100f )
{
var n = MathX.NormalizeClamped( phase, 0, 5/100f );
color *= n;
}
else if ( phase > 95/100f )
{
var n = MathX.NormalizeClamped( phase, 1, 95/100f );
color *= n;
}
return color;
}
public override void _OnTrigger()
{
if ( actionID != -1 )
{
CancelAction( actionID );
}
actionID = DispatchStart();
// RJLog.Log( "Setting actionID id", actionID, GetLastSequenceActionID() );
var flashCurve = flashEffect.flashCurve;
var color = flashEffect.color.GetHDRColor();
var timeline = flashEffect.timeline;
randomization = flashCurve.Randomize();
var duration = flashCurve.GetRandomizedDuration( randomization );
var transparentColor = color;
var initialAlpha = transparentColor.A;
transparentColor.A = 0;
Material material = null;
if ( FlashEffect.FlashLightMode.No_Light != flashEffect.lightMode )
{
light = targets[ 0 ].CreateChild<OmniLight3D>();
light.LightColor = ComputeLightColor( color, 0 );
light.LightEnergy = 0;
light.OmniRange = flashEffect.lightRange;
light.ShadowEnabled = flashEffect.lightHasShadows;
// RJLog.Log( "Created Light", light.Name );
}
if ( FlashEffect.MaterialMode.Flat_Standard3D == flashEffect.materialMode )
{
var standardMaterial = new StandardMaterial3D();
standardMaterial.AlbedoColor = transparentColor;
standardMaterial.ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded;
standardMaterial.Transparency = BaseMaterial3D.TransparencyEnum.Alpha;
material = standardMaterial;
}
else if ( FlashEffect.MaterialMode.Fresnel_FresnelOverlay == flashEffect.materialMode )
{
var fresnelMaterial = new FresnelOverlayMaterial();
fresnelMaterial.albedo.Set( transparentColor );
material = fresnelMaterial;
}
else if ( FlashEffect.MaterialMode.Scan_ScanGradient == flashEffect.materialMode )
{
var scanMaterial = ScanGradientMaterial.White.Get();
scanMaterial.albedo.Set( transparentColor );
material = scanMaterial;
}
else if ( FlashEffect.MaterialMode.Shield_TriPlanarOverlay == flashEffect.materialMode )
{
var shieldMaterial = TriPlanarOverlayMaterial.WhiteShield.Get();
shieldMaterial.albedo.Set( transparentColor );
material = shieldMaterial;
}
else
{
material = flashEffect.customMaterial;
flashEffect.customFlashColorProperty.Set( material, transparentColor );
}
_materials = new List<Material>();
Arrays.ForEach( targets,
t =>
{
var m = (Material) material.Duplicate( true );
_materials.Add( m );
Materials.AddOverlay( t, m );
}
);
var start = TimeLineManager.GetPosition( timeline );
var end = start + duration;
animationID = TimeLineScheduler.ScheduleSpanIn( timeline, 0, duration,
( int id, int type )=>
{
if ( animationID != id )
{
return;
}
var phase = TimeLineManager.GetRangePhase( timeline, start, end );
var value = flashCurve.Sample( phase );
var phaseColor = color;
phaseColor.A = value * initialAlpha;
if ( light != null )
{
light.LightEnergy = value * flashEffect.lightFlashCurveScale;
light.LightColor = ComputeLightColor( color, phase );
}
_materials.ForEach(
( m )=>
{
if ( FlashEffect.MaterialMode.Flat_Standard3D == flashEffect.materialMode )
{
var sm = m as StandardMaterial3D;
sm.AlbedoColor = phaseColor;
}
else if ( FlashEffect.MaterialMode.Fresnel_FresnelOverlay == flashEffect.materialMode )
{
var sm = m as FresnelOverlayMaterial;
sm.albedo.Set( phaseColor );
}
else if ( FlashEffect.MaterialMode.Scan_ScanGradient == flashEffect.materialMode )
{
var sm = m as ScanGradientMaterial;
sm.albedo.Set( phaseColor );
sm.offset.Set( phase * 3 );
}
else if ( FlashEffect.MaterialMode.Shield_TriPlanarOverlay == flashEffect.materialMode )
{
var sm = m as TriPlanarOverlayMaterial;
sm.albedo.Set( phaseColor );
}
else if ( FlashEffect.MaterialMode.Custom_Material == flashEffect.materialMode )
{
var sm = m as ShaderMaterial;
flashEffect.customFlashColorProperty.Set( sm, phaseColor );
}
}
);
if ( type == TimeLineSpan.End )
{
if ( light != null )
{
light.LightEnergy = 0;
light.LightColor = new Color( 0, 0, 0, 0 );
}
CancelAction( actionID );
DispatchEnd( actionID );
}
}
);
// RJLog.Log( "Processing animationID", animationID );
}
public override void CancelAction( int id )
{
// RJLog.Log( "CancelAction", id );
if ( false && actionID != id )
{
// RJLog.Log( "actionID != id", "id", id, "actionID", actionID );
return;
}
if ( light != null )
{
// RJLog.Log( "Removing Light", light.Name );
Nodes.RemoveAndDelete( light );
light = null;
}
RemoveMaterials();
}
void RemoveMaterials()
{
if ( _materials.Count != targets.Length )
{
return;
}
for ( int i = 0; i < targets.Length; i++ )
{
Materials.RemoveOverlay( targets[ i ], _materials[ i ] );
}
_materials.Clear();
}
}
}

View File

@ -0,0 +1,38 @@
[gd_resource type="Resource" script_class="FlashEffect" load_steps=8 format=3 uid="uid://bfxwx0fiejnk3"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/HDRColor.cs" id="1_ejko8"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/AnimationCurve.cs" id="2_3i2og"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/FlashEffect.cs" id="3_65ipm"]
[ext_resource type="RJTimeLine" uid="uid://frruktikky46" path="res://addons/rokojori_action_library/Runtime/Time/TimeLines/GameTime.tres" id="4_urqjo"]
[sub_resource type="Resource" id="Resource_54hj8"]
script = ExtResource("1_ejko8")
color = Color(0.151445, 0.275065, 1, 1)
colorMultiply = 1.0
rgbMultiply = 3.0
alphaMultiply = 1.0
[sub_resource type="Curve" id="Curve_tp3r5"]
_data = [Vector2(0, 0), 0.0, -0.249925, 0, 0, Vector2(0.0955632, 0.982574), -2.27942, -2.27942, 0, 0, Vector2(0.238908, 0.442359), -1.89107, -1.89107, 0, 0, Vector2(0.982935, 0.00670242), -0.229111, 0.0, 0, 0]
point_count = 4
[sub_resource type="Resource" id="Resource_pwp07"]
script = ExtResource("2_3i2og")
duration = 1.0
durationRandomRange = 0.0
delay = 0.0
delayRandomRange = 0.0
curve = SubResource("Curve_tp3r5")
scaleY = 1.0
scaleRandomRange = 0.0
[resource]
script = ExtResource("3_65ipm")
flashCurve = SubResource("Resource_pwp07")
color = SubResource("Resource_54hj8")
lightMode = 1
lightRange = 5.0
lightFlashCurveScale = 5.0
lightHasShadows = true
materialMode = 3
timeline = ExtResource("4_urqjo")

View File

@ -0,0 +1,38 @@
[gd_resource type="Resource" script_class="FlashEffect" load_steps=8 format=3 uid="uid://drjdsp2qnceql"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/HDRColor.cs" id="1_yrhv1"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/AnimationCurve.cs" id="2_cdv3p"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/FlashEffect.cs" id="3_87ql1"]
[ext_resource type="RJTimeLine" uid="uid://frruktikky46" path="res://addons/rokojori_action_library/Runtime/Time/TimeLines/GameTime.tres" id="4_t20yf"]
[sub_resource type="Resource" id="Resource_54hj8"]
script = ExtResource("1_yrhv1")
color = Color(0, 1, 0.306108, 1)
colorMultiply = 1.0
rgbMultiply = 2.0
alphaMultiply = 1.0
[sub_resource type="Curve" id="Curve_tp3r5"]
_data = [Vector2(0, 0), 0.0, 0.441476, 0, 0, Vector2(0.0957096, 0.959975), -1.01521, -1.01521, 0, 0, Vector2(0.9967, 0.0283505), -0.240285, 0.0, 0, 0]
point_count = 3
[sub_resource type="Resource" id="Resource_pwp07"]
script = ExtResource("2_cdv3p")
duration = 0.6
durationRandomRange = 0.0
delay = 0.0
delayRandomRange = 0.0
curve = SubResource("Curve_tp3r5")
scaleY = 1.0
scaleRandomRange = 0.0
[resource]
script = ExtResource("3_87ql1")
flashCurve = SubResource("Resource_pwp07")
color = SubResource("Resource_54hj8")
lightMode = 1
lightRange = 5.0
lightFlashCurveScale = 2.0
lightHasShadows = true
materialMode = 2
timeline = ExtResource("4_t20yf")

View File

@ -0,0 +1,38 @@
[gd_resource type="Resource" script_class="FlashEffect" load_steps=8 format=3 uid="uid://d06w8jorebto2"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/HDRColor.cs" id="1_c8lnw"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/AnimationCurve.cs" id="2_w323b"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/FlashEffect.cs" id="3_eqd4c"]
[ext_resource type="RJTimeLine" uid="uid://frruktikky46" path="res://addons/rokojori_action_library/Runtime/Time/TimeLines/GameTime.tres" id="4_03b60"]
[sub_resource type="Resource" id="Resource_ny3sx"]
script = ExtResource("1_c8lnw")
color = Color(1, 0.472686, 0.335587, 1)
colorMultiply = 1.0
rgbMultiply = 4.0
alphaMultiply = 1.0
[sub_resource type="Curve" id="Curve_tp3r5"]
_data = [Vector2(0, 1), 0.0, -1.0, 0, 1, Vector2(1, 0), -1.0, 0.0, 1, 0]
point_count = 2
[sub_resource type="Resource" id="Resource_pwp07"]
script = ExtResource("2_w323b")
duration = 0.5
durationRandomRange = 0.0
delay = 0.0
delayRandomRange = 0.0
curve = SubResource("Curve_tp3r5")
scaleY = 1.0
scaleRandomRange = 0.0
[resource]
script = ExtResource("3_eqd4c")
flashCurve = SubResource("Resource_pwp07")
color = SubResource("Resource_ny3sx")
lightMode = 1
lightRange = 4.0
lightFlashCurveScale = 1.0
lightHasShadows = false
materialMode = 1
timeline = ExtResource("4_03b60")

View File

@ -0,0 +1,34 @@
[gd_resource type="Resource" script_class="FlashEffect" load_steps=8 format=3 uid="uid://cwdnm658q3jta"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/HDRColor.cs" id="1_nmdum"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/AnimationCurve.cs" id="2_0sgd7"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/FlashEffect.cs" id="3_7qcuh"]
[ext_resource type="RJTimeLine" uid="uid://frruktikky46" path="res://addons/rokojori_action_library/Runtime/Time/TimeLines/GameTime.tres" id="4_8oykd"]
[sub_resource type="Resource" id="Resource_54hj8"]
script = ExtResource("1_nmdum")
color = Color(1, 0, 0, 1)
colorMultiply = 1.0
rgbMultiply = 1.0
alphaMultiply = 1.0
[sub_resource type="Curve" id="Curve_tp3r5"]
_data = [Vector2(0, 1), 0.0, -1.0, 0, 1, Vector2(1, 0), -1.0, 0.0, 1, 0]
point_count = 2
[sub_resource type="Resource" id="Resource_pwp07"]
script = ExtResource("2_0sgd7")
duration = 0.5
durationRandomRange = 0.0
delay = 0.0
delayRandomRange = 0.0
curve = SubResource("Curve_tp3r5")
scaleY = 1.0
scaleRandomRange = 0.0
[resource]
script = ExtResource("3_7qcuh")
flashCurve = SubResource("Resource_pwp07")
color = SubResource("Resource_54hj8")
materialMode = 0
timeline = ExtResource("4_8oykd")

View File

@ -0,0 +1,38 @@
[gd_resource type="Resource" script_class="FlashEffect" load_steps=8 format=3 uid="uid://pok7bfoksfmr"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/HDRColor.cs" id="1_pp4qy"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/AnimationCurve.cs" id="2_f6f0o"]
[ext_resource type="Script" path="res://addons/rokojori_action_library/Runtime/Animation/FlashEffect.cs" id="3_dq1j1"]
[ext_resource type="RJTimeLine" uid="uid://frruktikky46" path="res://addons/rokojori_action_library/Runtime/Time/TimeLines/GameTime.tres" id="4_v17wj"]
[sub_resource type="Resource" id="Resource_ny3sx"]
script = ExtResource("1_pp4qy")
color = Color(1, 1, 1, 1)
colorMultiply = 1.0
rgbMultiply = 2.0
alphaMultiply = 1.0
[sub_resource type="Curve" id="Curve_nqaq6"]
_data = [Vector2(0, 1), 0.0, 0.0775112, 0, 0, Vector2(0.0779661, 0), 0.0, 0.0, 0, 0, Vector2(0.227119, 0), 0.113556, 0.113556, 0, 0, Vector2(0.244068, 0.913564), 0.0, 0.0, 0, 0, Vector2(0.308475, 0), 0.0, 0.0, 0, 0, Vector2(0.505085, 0), 0.0, 0.0, 0, 0, Vector2(0.518644, 0.697473), 0.0, 0.0, 0, 0, Vector2(0.60339, 0), 0.0, 0.0, 0, 0, Vector2(0.742794, 0), 0.0, 0.0, 0, 0, Vector2(0.749153, 0.438165), 0.0, 0.0, 0, 0, Vector2(0.80678, 0), 0.0, 0.0, 0, 0, Vector2(1, 0), 0.0442571, 0.0, 0, 0]
point_count = 12
[sub_resource type="Resource" id="Resource_pwp07"]
script = ExtResource("2_f6f0o")
duration = 0.7
durationRandomRange = 0.0
delay = 0.0
delayRandomRange = 0.0
curve = SubResource("Curve_nqaq6")
scaleY = 1.0
scaleRandomRange = 0.0
[resource]
script = ExtResource("3_dq1j1")
flashCurve = SubResource("Resource_pwp07")
color = SubResource("Resource_ny3sx")
lightMode = 0
lightRange = 2.0
lightFlashCurveScale = 2.0
lightHasShadows = false
materialMode = 0
timeline = ExtResource("4_v17wj")

View File

@ -0,0 +1,70 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class FlashEffect:Resource
{
[ExportGroup( "Flash")]
[Export]
public AnimationCurve flashCurve;
[Export]
public HDRColor color;
public enum FlashLightMode
{
No_Light,
Origin_Of_First,
Center_Of_All
}
[ExportGroup( "Flash/Light")]
[Export]
public FlashLightMode lightMode;
[Export]
public float lightRange = 2;
[Export]
public float lightFlashCurveScale = 2;
[Export]
public bool lightHasShadows = false;
public enum MaterialMode
{
Flat_Standard3D,
Fresnel_FresnelOverlay,
Scan_ScanGradient,
Shield_TriPlanarOverlay,
Custom_Material
}
[ExportGroup( "Material")]
[Export]
public MaterialMode materialMode;
[Export]
public Material customMaterial;
[Export]
public ColorPropertyName customFlashColorProperty;
[Export]
public RJTimeLine timeline;
}
}

View File

@ -0,0 +1,38 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class HDRColor:Resource
{
[Export]
public Color color;
[Export]
public float colorMultiply = 1;
[Export]
public float rgbMultiply = 1;
[Export]
public float alphaMultiply = 1;
public Color GetHDRColor()
{
var v4 = ColorX.ToVector4( color ) * colorMultiply;
v4.X *= rgbMultiply;
v4.Y *= rgbMultiply;
v4.Z *= rgbMultiply;
v4.W *= alphaMultiply;
return ColorX.FromVector4( v4 );
}
}
}

View File

@ -0,0 +1,60 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class Shake:RJSequenceAction
{
[Export]
public ShakeEffect shakeEffect;
[Export]
public Node3D[] targets;
List<Transform3D> _targetValues;
int actionID = -1;
int animationID = -1;
public override void _OnTrigger()
{
if ( actionID != -1 )
{
CancelAction( actionID );
}
actionID = DispatchStart();
var random = shakeEffect.shakeCurve.Randomize();
var timeline = shakeEffect.timeline;
var duration = shakeEffect.shakeCurve.GetRandomizedEndTime( random );
var start = TimeLineManager.GetPosition( shakeEffect.timeline );
var end = start + duration;
animationID = TimeLineScheduler.ScheduleSpanIn( timeline, 0, duration,
( int id, int type )=>
{
if ( animationID != id )
{
return;
}
}
);
}
public override void CancelAction( int id )
{
}
}
}

View File

@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class ShakeEffect:Resource
{
[Export]
public AnimationCurve shakeCurve;
[Export]
public Vector3 positionShake = Vector3.Zero;
[Export]
public Vector3 positionShakeRandom = Vector3.Zero;
[Export]
public Vector3 rotationShake = Vector3.Zero;
[Export]
public Vector3 rotationShakeRandom = Vector3.Zero;
[Export]
public Vector3 scaleShake = Vector3.Zero;
[Export]
public Vector3 scaleShakeRandom = Vector3.Zero;
[Export]
public bool scaleIsPercentage = true;
[Export]
public RJTimeLine timeline;
}
}

View File

@ -0,0 +1,89 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class AnimateTransform:RJSequenceAction, Animator
{
[Export]
public TransformAnimations animations;
[Export]
public Node3D target;
List<Vector3> _frameValues;
List<Vector3> _randomizations;
public void OnAnimatorStart(){}
public void OnAnimatorEnd(){}
public void OnAnimatorCancel(){}
public override void _OnTrigger()
{
_frameValues = new List<Vector3>();
_randomizations = new List<Vector3>();
foreach ( var curve in animations.curves )
{
_frameValues.Add( TransformTargets.Get( target, curve.transformTarget ) );
_randomizations.Add( curve.Randomize() );
}
var timeline = animations.timeline;
var duration = animations.GetMaxDuration( _randomizations );
var start = TimeLineManager.GetPosition( timeline );
var end = start + animations.GetMaxDuration( _randomizations );
var actionID = DispatchStart();
foreach ( var c in animations.curves )
{
AnimationManager.StartAnimation( c, target, c.animationMember );
}
TimeLineScheduler.ScheduleSpanIn( timeline, 0, duration,
( int id, int type )=>
{
var timeNow = TimeLineManager.GetPosition( timeline );
var elapsed = timeNow - start;
var index = 0;
foreach ( var c in animations.curves )
{
if ( AnimationManager.IsAnimating( c, target, c.animationMember ) )
{
c.Apply( elapsed, target, _randomizations[ index ], _frameValues[ index ] );
}
index ++;
}
if ( type == TimeLineSpan.End )
{
foreach ( var c in animations.curves )
{
AnimationManager.EndAnimation( c, target, c.animationMember );
}
DispatchEnd( actionID );
}
}
);
}
}
}

View File

@ -0,0 +1,32 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class TransformAnimations:Resource
{
[Export]
public TransformCurve[] curves;
[Export]
public RJTimeLine timeline;
public float GetMaxDuration( List<Vector3> randomizations )
{
var d = 0f;
for ( int i = 0; i < curves.Length; i++ )
{
d = Mathf.Max( d, curves[ i ].GetRandomizedEndTime( randomizations[ i ] ) );
}
return d;
}
}
}

View File

@ -0,0 +1,68 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class TransformCurve:AnimationCurve3D,Animator
{
public void OnAnimatorStart(){}
public void OnAnimatorEnd(){}
public void OnAnimatorCancel(){}
public enum OperatorMode
{
Replace,
Add,
Multiply
}
[Export]
public TransformTarget transformTarget;
[Export]
public OperatorMode operatorMode;
public AnimationMember animationMember => TransformTargets.ToAnimationMember( transformTarget );
public void Apply( float time, Node3D node, Vector3 randomization, Vector3 originalValue )
{
var animatedValue = Sample( time, randomization );
animatedValue = ApplyOperator( animatedValue, originalValue );
TransformTargets.Set( animatedValue, node, transformTarget );
}
Vector3 ApplyOperator( Vector3 animated, Vector3 original )
{
if ( OperatorMode.Replace == operatorMode )
{
return animated;
}
else if ( OperatorMode.Add == operatorMode )
{
return animated + original;
}
else if ( OperatorMode.Multiply == operatorMode )
{
return animated * original;
}
return original;
}
}
}

View File

@ -0,0 +1,92 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
namespace Rokojori
{
public enum TransformTarget
{
Global_Position,
Global_Rotation,
Local_Position,
Local_Rotation,
Local_Scale
}
public class TransformTargets
{
public static AnimationMember ToAnimationMember( TransformTarget target )
{
if ( TransformTarget.Local_Position == target || TransformTarget.Global_Position == target )
{
return AnimationManager.position;
}
if ( TransformTarget.Local_Rotation == target || TransformTarget.Global_Rotation == target )
{
return AnimationManager.rotation;
}
if ( TransformTarget.Local_Scale == target )
{
return AnimationManager.scale;
}
return null;
}
public static Vector3 Get( Node3D target, TransformTarget transformTarget )
{
if ( TransformTarget.Global_Position == transformTarget )
{
return target.GlobalPosition;
}
else if ( TransformTarget.Global_Rotation == transformTarget )
{
return target.GlobalRotation;
}
else if ( TransformTarget.Local_Position == transformTarget )
{
return target.Position;
}
else if ( TransformTarget.Local_Rotation == transformTarget )
{
return target.Rotation;
}
else if ( TransformTarget.Local_Scale == transformTarget )
{
return target.Scale;
}
return Vector3.Zero;
}
public static void Set( Vector3 value, Node3D target, TransformTarget transformTarget )
{
if ( TransformTarget.Global_Position == transformTarget )
{
target.GlobalPosition = value;
}
else if ( TransformTarget.Global_Rotation == transformTarget )
{
target.GlobalRotation = value;
}
else if ( TransformTarget.Local_Position == transformTarget )
{
target.Position = value;
}
else if ( TransformTarget.Local_Rotation == transformTarget )
{
target.Rotation = value;
}
else if ( TransformTarget.Local_Scale == transformTarget )
{
target.Scale = value;
}
}
}
}

View File

@ -0,0 +1,56 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioSample
{
float[,] _channels;
float _sampleRate;
public float sampleRate => _sampleRate;
public static AudioSample Create( float[,] channelData, float sampleRate = 44100 )
{
var sample = new AudioSample();
sample._channels = channelData;
sample._sampleRate = sampleRate;
return sample;
}
public static AudioSample Create( int channels, int numSamples, float sampleRate = 44100 )
{
var sample = new AudioSample();
sample._channels = new float[ channels, numSamples ];
sample._sampleRate = sampleRate;
return sample;
}
public float Get( int sample, int channel = 0 )
{
return _channels[ channel, sample ];
}
public float Left( int sample )
{
return _channels[ 0, sample ];
}
public float Right( int sample )
{
return _channels[ 1, sample ];
}
public float this[ int channel, int sample ]
{
get { return _channels[ channel, sample ]; }
set { _channels[ channel, sample ] = value; }
}
}
}

View File

@ -0,0 +1,37 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class Constant:AudioProcessor
{
public readonly AudioStreamOutput output;
protected float _constantValue;
public Constant( AudioGraph ag, float value ): base( ag )
{
_constantValue = value;
output = new AudioStreamOutput( this );
}
public void SetConstant( float value )
{
_constantValue = value;
}
protected override void _Process()
{
for ( int i = 0; i < bufferSize; i++ )
{
output[ i ] = _constantValue;
}
}
}
}

View File

@ -0,0 +1,21 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public abstract class AudioEffectMono:AudioProcessor
{
public readonly AudioStreamInput input;
public readonly AudioStreamOutput output;
public AudioEffectMono( AudioGraph ag ): base( ag )
{
input = new AudioStreamInput( this );
output = new AudioStreamOutput( this );
}
}
}

View File

@ -0,0 +1,27 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public abstract class AudioEffectStereo:AudioProcessor
{
public readonly AudioStreamInput inputL;
public readonly AudioStreamInput inputR;
public readonly AudioStreamOutput outputL;
public readonly AudioStreamOutput outputR;
public AudioEffectStereo( AudioGraph ag ): base( ag )
{
inputL = new AudioStreamInput( this );
inputR = new AudioStreamInput( this );
outputL = new AudioStreamOutput( this );
outputR = new AudioStreamOutput( this );
}
}
}

View File

@ -0,0 +1,29 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class Gain:AudioEffectMono
{
public readonly AudioStreamInput gain;
public Gain( AudioGraph ag ): base( ag )
{
gain = new AudioStreamInput( this );
}
protected override void _Process()
{
for ( int i = 0; i < bufferSize; i++ )
{
output[ i ] = gain[ i ] * input[ i ];
}
}
}
}

View File

@ -0,0 +1,19 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public abstract class AudioGeneratorMono:AudioProcessor
{
public readonly AudioStreamOutput output;
public AudioGeneratorMono( AudioGraph ag ): base( ag )
{
output = new AudioStreamOutput( this );
}
}
}

View File

@ -0,0 +1,21 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public abstract class AudioGeneratorStereo:AudioProcessor
{
public readonly AudioStreamOutput outputL;
public readonly AudioStreamOutput outputR;
public AudioGeneratorStereo( AudioGraph ag ): base( ag )
{
outputL = new AudioStreamOutput( this );
outputR = new AudioStreamOutput( this );
}
}
}

View File

@ -0,0 +1,198 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class DuoWaveTable
{
float[] _samples;
float _lowPitch;
float _highPitch;
int _maxSamples;
float _weight = 0.5f;
public DuoWaveTable( float[] samples, float lowPitch, float highPitch )
{
_samples = samples;
_lowPitch = lowPitch;
_highPitch = highPitch;
_maxSamples = samples.Length / 2;
}
public static DuoWaveTable FromWaveTables( WaveTable low, WaveTable high, float lowPitch, float highPitch )
{
var samples = new float[ low.samples.Length * 2 ];
for ( int i = 0; i < low.samples.Length; i++ )
{
samples[ i * 2 ] = low.samples[ i ];
}
for ( int i = 0; i < high.samples.Length; i++ )
{
samples[ i * 2 + 1 ] = high.samples[ i ] - low.samples[ i ];
}
return new DuoWaveTable( samples, lowPitch, highPitch );
}
public float ComputeWeightForPitch( float pitch )
{
return MathX.NormalizeClamped( pitch, _lowPitch, _highPitch );
}
public void SetWeight( float weight )
{
this._weight = weight;
}
public void SetWeightForPitch( float pitch )
{
SetWeight( ComputeWeightForPitch( pitch ) );
}
public float Get( float phase )
{
var max = _maxSamples;
var floatingSamplePosition = phase * max;
var p0 = (int) floatingSamplePosition;
var p1 = p0 == ( max - 1 ) ? 0 : ( p0 + 1 );
var mixAmount = floatingSamplePosition - p0;
var p0Index = p0 << 1;
var p1Index = p1 << 1;
var low0 = _samples[ p0Index ];
var diff0 = _samples[ p0Index + 1 ];
var low1 = _samples[ p1Index ];
var diff1 = _samples[ p1Index + 1 ];
var mixed0 = low0 + _weight * diff0;
var mixed1 = low1 + _weight * diff1;
return mixed0 + mixAmount * ( mixed1 - mixed0 );
}
}
public class BandLimitedWaveTable:iPhaseGenerator
{
List<DuoWaveTable> entries = new List<DuoWaveTable>();
int entriesPerOctave = 3;
float lowestPitch;
float highestPitch;
int _activeEntryIndex = 0;
DuoWaveTable _activeEntry;
public int GetEntryForPitch( float pitch )
{
var entry = Mathf.Min( MathX.RemapClamped( pitch, lowestPitch, highestPitch, 0, entries.Count ), entries.Count - 1 );
return (int) entry;
}
public void SetPitchRange( float minPitch, float maxPitch )
{
_activeEntryIndex = GetEntryForPitch( maxPitch );
_activeEntry = entries[ _activeEntryIndex ];
_activeEntry.SetWeightForPitch( maxPitch );
}
public float Get( float phase )
{
return _activeEntry.Get( phase );
}
public BandLimitedWaveTable FromSines( Vector2[] sines, int size = 4096, int entriesPerOctave = 3, float lowestPitch = 0 )
{
var blwt = new BandLimitedWaveTable();
blwt.entriesPerOctave = entriesPerOctave;
blwt.lowestPitch = lowestPitch;
float frequency = MathAudio.PitchToFrequency( blwt.lowestPitch );
float maxFrequency = 18000;
while ( frequency < maxFrequency )
{
for ( int i = 0; i < entriesPerOctave; i++ )
{
var lowPitchOffset = i / (float) entriesPerOctave;
var lowFrequency = MathAudio.OffsetFrequencyBySemitones( frequency, lowPitchOffset );
if ( lowFrequency > maxFrequency )
{
continue;
}
var highPitchOffset = ( i + 1 ) / (float) entriesPerOctave;
var highFrequency = MathAudio.OffsetFrequencyBySemitones( frequency, highPitchOffset );
var lowWaveTable = CreateWaveTable( lowFrequency, sines, size );
var highWaveTable = CreateWaveTable( highFrequency, sines, size );
var lowPitch = MathAudio.FrequencyToPitch( lowFrequency );
var highPitch = MathAudio.FrequencyToPitch( highFrequency );
var duoWaveTable = DuoWaveTable.FromWaveTables( lowWaveTable, highWaveTable, lowPitch, highPitch );
blwt.highestPitch = lowPitch;
entries.Add( duoWaveTable );
}
frequency *= 2;
}
return blwt;
}
WaveTable CreateWaveTable( float frequency, Vector2[] sines, int size )
{
var filterStart = FindFilterStart( frequency, sines.Length );
var filterEnd = FindFilterEnd( frequency, sines.Length, filterStart );
return WaveTable.FromSines( sines, filterStart, filterEnd, size );
}
int FindFilterStart( float baseFrequency, int numSines, float frequencyTreshold = 14000f )
{
for ( int i = 0; i < numSines; i++ )
{
var binFrequency = i * baseFrequency;
if ( binFrequency >= frequencyTreshold )
{
return i;
}
}
return numSines;
}
int FindFilterEnd( float baseFrequency, int numSines, int start, float frequencyTreshold = 18000f )
{
for ( int i = start; i < numSines; i++ )
{
var binFrequency = i * baseFrequency;
if ( binFrequency >= frequencyTreshold )
{
return i;
}
}
return numSines;
}
}
}

View File

@ -0,0 +1,44 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class PhaseGenerator:AudioGeneratorMono
{
public readonly AudioEventInput<float> pitch;
protected iPhaseGenerator _generator;
public PhaseGenerator( AudioGraph ag, iPhaseGenerator generator ): base( ag )
{
pitch = new AudioEventInput<float>( this );
this._generator = generator;
}
float _phase = 0;
float _increment = 0.01f;
protected override void _Process()
{
if ( pitch.numEvents > 0 )
{
var p = pitch.lastEvent;
var frequency = MathAudio.PitchToFrequency( p );
_increment = frequency / audioGraph.sampleRate;
}
for ( int i = 0; i < bufferSize; i++ )
{
_phase = MathAudio.IncrementPhase( _phase, _increment );
var value = _generator.Get( _phase );
output[ i ] = value;
}
}
}
}

View File

@ -0,0 +1,36 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class SineGenerator:AudioGeneratorMono
{
public readonly AudioStreamInput frequency;
public SineGenerator( AudioGraph ag ): base( ag )
{
frequency = new AudioStreamInput( this );
}
float _phase = 0;
protected override void _Process()
{
for ( int i = 0; i < bufferSize; i++ )
{
float increment = frequency[ i ] / audioGraph.sampleRate;
_phase += increment;
_phase = MathX.Repeat( _phase, 1 );
output[ i ] = Mathf.Sin( _phase * Mathf.Pi* 2.0f );
}
}
}
}

View File

@ -0,0 +1,79 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class WaveTable:iPhaseGenerator
{
float[] _samples;
public float[] samples => _samples;
public void SetPitchRange( float minPitch, float maxPitch )
{
}
public float Get( float phase )
{
var samplePosition = phase * _samples.Length;
samplePosition = MathX.Repeat( samplePosition, _samples.Length );
var l = (int) samplePosition;
var h = MathX.Repeat( l + 1, _samples.Length );
var lerp = samplePosition - l;
return Mathf.Lerp( _samples[ l ], _samples[ h ], lerp );
}
public static WaveTable FromSamples( float[] samples )
{
var wt = new WaveTable();
wt._samples = samples;
return wt;
}
public void Normalize()
{
var max = 0f;
for ( int i = 0; i < _samples.Length; i++ )
{
max = Mathf.Max( Mathf.Abs( _samples[ i ] ), max );
}
for ( int i = 0; i < _samples.Length; i++ )
{
_samples[ i ] = _samples[ i ] / max;
}
}
public static WaveTable FromSines( Vector2[] magnitudesAndPhases, float filterStart = -1, float filterEnd = -1, int size = 4096 )
{
var fftReal = new float[ size ];
var fftImaginary = new float[ size ];
var hasNoFilter = ( filterStart == -1 && filterEnd == -1 );
for ( int i = 0; i < magnitudesAndPhases.Length; i++ )
{
var filter = hasNoFilter ? 1 : ( 1f - MathX.NormalizeClamped( i, filterStart, filterEnd ) );
fftReal[ i ] = Mathf.Cos( magnitudesAndPhases[ i ].Y ) * magnitudesAndPhases[ i ].X * filter;
fftImaginary[ i ] = Mathf.Sin( magnitudesAndPhases[ i ].Y ) * magnitudesAndPhases[ i ].X * filter;
}
FFT.Inverse( size, fftReal, fftImaginary );
return FromSamples( fftReal );
}
}
}

View File

@ -0,0 +1,39 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class WaveTableGenerator:AudioGeneratorMono
{
public readonly AudioStreamInput frequency;
protected WaveTable waveTable;
public WaveTableGenerator( AudioGraph ag, WaveTable waveTable ): base( ag )
{
frequency = new AudioStreamInput( this );
this.waveTable = waveTable;
}
float _phase = 0;
protected override void _Process()
{
for ( int i = 0; i < bufferSize; i++ )
{
float increment = frequency[ i ] / audioGraph.sampleRate;
_phase += increment;
_phase = MathX.Repeat( _phase, 1 );
var value = waveTable.Get( _phase );
output[ i ] = waveTable.Get( _phase );
}
}
}
}

View File

@ -0,0 +1,13 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public interface iPhaseGenerator
{
float Get( float phase );
void SetPitchRange( float minPitch, float maxPitch );
}
}

View File

@ -0,0 +1,19 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class Instrument:AudioProcessor
{
public Instrument( AudioGraph ag ):base( ag ){}
List<Voice> _voices = new List<Voice>();
protected override void _Process()
{
}
}
}

View File

@ -0,0 +1,32 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class MusicTimeLine:AudioProcessor
{
public AudioEventInput<float> bpm;
public AudioEventInput<MusicTimeLineRange> loop;
public AudioEventOutput<MusicTimeLineRange> timeLineRanges;
public MusicTimeLine( AudioGraph ag ):base( ag )
{
bpm = new AudioEventInput<float>( this );
loop = new AudioEventInput<MusicTimeLineRange>( this );
timeLineRanges = new AudioEventOutput<MusicTimeLineRange>( this);
}
MusicTimeLineRange _loopRange;
float _bpm = 120;
protected override void _Process()
{
}
}
}

View File

@ -0,0 +1,14 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class MusicTimeLineRange
{
public double start;
public double end;
}
}

View File

@ -0,0 +1,16 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class NoteEvent
{
public int id;
public float pitch;
public float volume;
public float startPosition;
public float duration;
}
}

View File

@ -0,0 +1,23 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public abstract class Voice
{
NoteEvent _noteEvent;
public abstract void CancelNote();
public void Play( NoteEvent ne )
{
CancelNote();
_noteEvent = ne;
}
public abstract void Process();
}
}

View File

@ -0,0 +1,35 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioConnection : AudioNode
{
protected AudioProcessor _audioProcessor;
public AudioProcessor audioProcessor => _audioProcessor;
protected bool _isInput = true;
public bool isInput => _isInput;
public AudioConnection( AudioProcessor p, bool isInput ): base( p.audioGraph )
{
_audioProcessor = p;
_isInput = isInput;
p._Add( this );
}
public override void Destroy()
{
_audioGraph = null;
_audioProcessor = null;
}
public virtual void PollDependencies()
{
}
}
}

View File

@ -0,0 +1,22 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System;
namespace Rokojori
{
public abstract class AudioEvent<T> : AudioConnection
{
protected List<T> _events = new List<T>();
public AudioEvent( AudioProcessor p, bool isInput ):base( p, isInput )
{
}
public override void Clear()
{
_events.Clear();
}
}
}

View File

@ -0,0 +1,58 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System;
namespace Rokojori
{
public class AudioEventInput<T> : AudioEvent<T>
{
public AudioEventInput( AudioProcessor p ):base( p, true )
{}
protected List<AudioEventOutput<T>> _outputs = new List<AudioEventOutput<T>>();
public List<AudioEventOutput<T>> connected => _outputs;
public void _GetConnectedFrom( AudioEventOutput<T> output )
{
_outputs.Add( output );
}
public void _GetDisconnectedFrom( AudioEventOutput<T> output )
{
_outputs.Remove( output );
}
public int numEvents => _events.Count;
public T this[ int index ]
{
get { return _events[ index ]; }
}
public T lastEvent => _events[ _events.Count -1 ];
public override void PollDependencies()
{
_events.Clear();
_outputs.ForEach(
o =>
{
o.audioProcessor.Process();
}
);
_outputs.ForEach(
o =>
{
_events.AddRange( o.GetEventsList() );
}
);
}
}
}

View File

@ -0,0 +1,46 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioEventOutput<T> : AudioEvent<T>
{
public AudioEventOutput( AudioProcessor p, bool autoClear = true ):base( p, autoClear )
{}
List<AudioEventInput<T>> _inputs = new List<AudioEventInput<T>>();
public List<AudioEventInput<T>> connected => _inputs;
public bool IsConnectedTo( AudioEventInput<T> input )
{
return _inputs.Contains( input );
}
public void ConnectTo( AudioEventInput<T> input )
{
_inputs.Add( input );
input._GetConnectedFrom( this );
}
public void Disconnect( AudioEventInput<T> input )
{
_inputs.Remove( input );
input._GetDisconnectedFrom( this );
}
public void Push( T t )
{
_events.Add( t );
}
public List<T> GetEventsList()
{
return _events;
}
}
}

View File

@ -0,0 +1,96 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioGraph:AudioProcessor
{
public AudioGraph( float sampleRate = 44100, int bufferSize = 1024 ):base( null )
{
this.sampleRate = sampleRate;
this.SetBufferSize( bufferSize );
}
protected int _bufferSize = 1024;
public override int bufferSize => audioGraph == null ? _bufferSize : audioGraph.bufferSize;
protected int _frameIndex = -1;
public int frameIndex => _frameIndex;
public float sampleRate;
public float bpm;
public AudioProcessor output;
public override bool processed
{
get
{
if ( audioGraph == null )
{
return false;
}
return base.processed;
}
}
public override void SetProcessed()
{
if ( audioGraph == null )
{
_frameIndex ++;
return;
}
base.SetProcessed();
}
protected override void _Process()
{
_nodes.ForEach( n => n.Clear() );
output.Process();
}
List<AudioNode> _nodes = new List<AudioNode>();
public void _Add( AudioNode node )
{
_nodes.Add( node );
}
public void Destroy( AudioNode node )
{
_nodes.Remove( node );
node.Destroy();
}
public void SetBufferSize( int bufferSize )
{
_bufferSize = bufferSize;
_zeroBuffer = new float[ bufferSize ];
for ( int i = 0; i < bufferSize; i++ )
{
_zeroBuffer[ i ] = 0;
}
_nodes.ForEach( n => n.UpdateBuffserSize() );
}
float[] _zeroBuffer;
public float[] ClearBuffer( float[] buffer )
{
System.Array.Copy( _zeroBuffer, 0, buffer, 0, bufferSize );
return buffer;
}
}
}

View File

@ -0,0 +1,44 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioNode
{
protected AudioGraph _audioGraph;
public AudioGraph audioGraph => _audioGraph;
public virtual void Destroy()
{
_audioGraph = null;
}
public virtual void Clear()
{
}
public virtual void UpdateBuffserSize()
{
}
public virtual int bufferSize => audioGraph.bufferSize;
public AudioNode( AudioGraph ag)
{
_audioGraph = ag;
if ( ag != null )
{
ag._Add( this );
}
}
}
}

View File

@ -0,0 +1,69 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioProcessor:AudioNode
{
protected int _processedID = -1;
protected List<AudioConnection> _connections = new List<AudioConnection>();
protected List<AudioConnection> _inputConnections = new List<AudioConnection>();
protected List<AudioConnection> _outputConnections = new List<AudioConnection>();
public AudioProcessor( AudioGraph ag ):base( ag )
{}
public void _Add( AudioConnection ac )
{
_connections.Add( ac );
if ( ac.isInput )
{
_inputConnections.Add( ac );
}
else
{
_outputConnections.Add( ac );
}
}
public void Process()
{
if ( processed )
{
return;
}
SetProcessed();
for ( int i = 0; i < _inputConnections.Count; i++ )
{
_inputConnections[ i ].PollDependencies();
}
_Process();
}
protected virtual void _Process()
{
}
public virtual bool processed
{
get
{
return _processedID == _audioGraph.frameIndex;
}
}
public virtual void SetProcessed()
{
_processedID = _audioGraph.frameIndex;
}
}
}

View File

@ -0,0 +1,27 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioStream : AudioConnection
{
protected float[] _buffer = new float[ 0 ];
public AudioStream( AudioProcessor p, bool isInput ):base( p, isInput )
{
_buffer = new float[ audioGraph.bufferSize ];
}
public override void Clear()
{
_buffer = audioGraph.ClearBuffer( _buffer );
}
public override void UpdateBuffserSize()
{
_buffer = new float[ audioGraph.bufferSize ];
}
}
}

View File

@ -0,0 +1,59 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System;
namespace Rokojori
{
public class AudioStreamInput : AudioStream
{
public AudioStreamInput( AudioProcessor p ):base( p, true )
{}
protected List<AudioStreamOutput> _outputs = new List<AudioStreamOutput>();
public List<AudioStreamOutput> connected => _outputs;
public void _GetConnectedFrom( AudioStreamOutput output )
{
_outputs.Add( output );
}
public void _GetDisconnectedFrom( AudioStreamOutput output )
{
_outputs.Remove( output );
}
public override void PollDependencies()
{
_outputs.ForEach(
o =>
{
o.audioProcessor.Process();
}
);
_outputs.ForEach(
o =>
{
var buffer = o.GetOutputStreamBuffer();
for ( int i = 0; i < bufferSize; i++ )
{
_buffer[ i ] += buffer[ i ];
}
}
);
}
public float this[ int index ]
{
get { return _buffer[ index ]; }
}
}
}

View File

@ -0,0 +1,49 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public class AudioStreamOutput : AudioStream
{
public AudioStreamOutput( AudioProcessor p, bool autoClear = true ):base( p, autoClear )
{}
List<AudioStreamInput> _inputs = new List<AudioStreamInput>();
public List<AudioStreamInput> connected => _inputs;
public bool IsConnectedTo( AudioStreamInput input )
{
return _inputs.Contains( input );
}
public void ConnectTo( AudioStreamInput input )
{
_inputs.Add( input );
input._GetConnectedFrom( this );
}
public void Disconnect( AudioStreamInput input )
{
_inputs.Remove( input );
input._GetDisconnectedFrom( this );
}
public float this[ int index ]
{
set{ _buffer[ index ] = value;}
}
public float[] GetOutputStreamBuffer()
{
return _buffer;
}
}
}

View File

@ -0,0 +1,140 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
[GlobalClass]
public partial class SineWaveTest: Node
{
[Export]
public float frequency = 100;
[Export]
public AudioStreamPlayer player;
AudioStreamGeneratorPlayback _playback;
public override void _Ready()
{
CreateGraph();
if ( player.Stream is AudioStreamGenerator generator )
{
ag.sampleRate = generator.MixRate;
player.Play();
_playback = (AudioStreamGeneratorPlayback) ( player.GetStreamPlayback() );
Fill();
}
}
WaveTableGenerator generator;
AudioGraph ag;
Constant constant;
public override void _Process( double delta )
{
Fill();
}
void Fill()
{
if ( _playback == null )
{
return;
}
constant.SetConstant( frequency );
int framesAvailable = _playback.GetFramesAvailable();
if ( framesAvailable == 0 )
{
return;
}
int blocks = 0;
while ( framesAvailable > frameBuffer.Count )
{
ProcessAudio();
blocks ++;
}
var frameBufferData = frameBuffer.ToArray();
var bufferSlice = new Vector2[ framesAvailable ];
System.Array.Copy( frameBufferData, 0, bufferSlice, 0, framesAvailable );
_playback.PushBuffer( bufferSlice );
var rest = new Vector2[ frameBuffer.Count - framesAvailable ];
System.Array.Copy( frameBufferData, framesAvailable, rest, 0, rest.Length );
frameBuffer = new List<Vector2>( rest );
}
List<Vector2> frameBuffer = new List<Vector2>();
void ProcessAudio()
{
ag.Process();
var outputBuffer = generator.output.GetOutputStreamBuffer();
var data = new Vector2[ ag.bufferSize ];
for ( int i = 0; i < ag.bufferSize ; i++ )
{
data[ i ] = outputBuffer[ i ] * Vector2.One;
}
frameBuffer.AddRange( data );
}
public void CreateGraph()
{
ag = new AudioGraph();
var sine = new float[ 2048 ];
var square = new float[ 2048 ];
for ( int i = 0; i < sine.Length; i++ )
{
var p = i / 2048f;
var v = Mathf.Sin( p * Mathf.Pi * 2.0f );
sine[ i ] = v;
square[ i ] = i < sine.Length / 2 ? 1 : -1;
}
var sines = new Vector2[ 10 ];
sines[ 0 ] = Vector2.Zero;
for ( int i = 1; i < sines.Length; i++ )
{
sines[ i ] = new Vector2( Mathf.Pow( 0.5f, i - 1 ), 0 );
}
var waveTable = WaveTable.FromSines( sines );
waveTable.Normalize();
generator = new WaveTableGenerator( ag, waveTable );
constant = new Constant( ag, frequency );
constant.output.ConnectTo( generator.frequency );
ag.output = generator;
}
}
}

115
Runtime/Audio/MathAudio.cs Normal file
View File

@ -0,0 +1,115 @@
using System.Collections;
using System.Collections.Generic;
using Godot;
using System.Text;
using System;
namespace Rokojori
{
public class MathAudio
{
public static int ConcertA_Pitch = 69;
public static float ConcertA_Frequency = 440.0f;
public static float AmplitudeToDecibels( float amplitude )
{
return Mathf.Log(amplitude) * 20.0f / Mathf.Log(10.0f);
}
public static float DecibelsToAmplitude( float decibels )
{
return Mathf.Pow(10.0f, decibels / 20.0f);
}
public static float FrequencyToPitch( float frequency )
{
return ConcertA_Pitch + 12 * Mathf.Log(frequency / ConcertA_Frequency) / Mathf.Log(2.0f);
}
public static float PitchToFrequency( float pitch )
{
return ConcertA_Frequency * Mathf.Pow( 2.0f, ( ( pitch - ConcertA_Pitch ) * (1.0f / 12.0f)));
}
public static float SemitonesToFrequencyScaler( float semitones )
{
return Mathf.Pow( 2.0f, semitones / 12.0f);
}
public static float OffsetFrequencyBySemitones( float frequency, float semitones )
{
return SemitonesToFrequencyScaler( semitones ) * frequency;
}
public static float IncrementPhase( float phase, float increment )
{
return MathX.Repeat( phase + increment, 1 );
}
public static float OneBeatToSeconds( float bpm )
{
return 60f / bpm;
}
public static float OneSecondToBeats( float bpm )
{
return 1f / OneBeatToSeconds( bpm );
}
public static float OneSampleToSeconds( float sampleRate )
{
return 1f / sampleRate;
}
public static float OneSecondToSamples( float sampleRate )
{
return sampleRate;
}
public static float OneBeatToSamples( float bpm, float sampleRate )
{
var seconds = OneBeatToSeconds( bpm );
return seconds * OneSecondToSamples( sampleRate );
}
public static float OneSampleToBeats( float bpm, float sampleRate )
{
var seconds = OneBeatToSeconds( bpm );
return seconds * OneSecondToSamples( sampleRate );
}
public static float BeatsToSeconds( float beats, float bpm )
{
return beats * OneBeatToSeconds( bpm );
}
public static float SecondsToBeats( float seconds, float bpm )
{
return seconds * OneSecondToBeats( bpm );
}
public static float SamplesToSeconds( float samples, float sampleRate )
{
return samples * OneSampleToSeconds( sampleRate );
}
public static float SecondsToSamples( float seconds, float sampleRate )
{
return seconds * OneSecondToSamples( sampleRate );
}
public static float BeatsToSamples( float beats, float bpm, float sampleRate )
{
return beats * OneBeatToSamples( bpm, sampleRate );
}
public static float SamplesToBeats( float samples, float bpm, float sampleRate )
{
return samples * OneSampleToBeats( bpm, sampleRate );
}
}
}

View File

@ -24,6 +24,16 @@ namespace Rokojori
return new Color( rgb.X, rgb.Y, rgb.Z, a );
}
public static Vector4 ToVector4( Color c )
{
return new Vector4( c.R, c.G, c.B, c.A );
}
public static Color FromVector4( Vector4 c )
{
return new Color( c.X, c.Y, c.Z, c.W );
}
}
}

View File

@ -54,13 +54,18 @@ namespace Rokojori
}
}
public bool hasFileExtension( string extension )
public bool hasFileExtension( string extension, bool forceDot = true )
{
if ( fileExtension == null )
{
return false;
}
if ( forceDot && ! extension.StartsWith( "." ))
{
extension = "." + extension;
}
return fileExtension.ToLower() == extension.ToLower();
}
@ -93,6 +98,23 @@ namespace Rokojori
rp.parent = parent;
rp.path = path;
return rp;
}
public FilePath WithExtension( string extension, bool forceDot = true )
{
var rp = new FilePath();
rp.type = type;
rp.parent = parent;
if ( forceDot && ! extension.StartsWith( "." ) )
{
extension = "." + extension;
}
rp.path = Path.ChangeExtension( path, extension );
return rp;
}
@ -101,6 +123,14 @@ namespace Rokojori
return FilePath.Create( path, FilePathType.Absolute );
}
public FilePath CreateAbsoluteParent()
{
var path = this.fullPath;
return Absolute( RegexUtility.ParentPath( path ) );
}
public FilePath MakeRelative( string path )
{
return FilePath.Create( path, FilePathType.Relative, this );

View File

@ -0,0 +1,23 @@
using Godot;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace Rokojori
{
public static class GDScriptNames
{
public static string ToCS( string gdScriptName )
{
return gdScriptName.ToPascalCase();
}
public static string FromCS( string csName )
{
return csName.ToSnakeCase();
}
}
}

View File

@ -0,0 +1,38 @@
using Godot;
using System.Text;
using System.Collections.Generic;
using System;
namespace Rokojori
{
public class CachedResource<T> where T:Resource
{
protected T _resource;
protected string _path;
public string path => _path;
protected Func<Resource,T> _converter;
public CachedResource( string path, Func<Resource,T> converter = null )
{
_path = path;
_converter = converter;
}
public T Get()
{
if ( _resource == null )
{
_resource = _converter == null ?
ResourceLoader.Load<T>( _path ) :
_converter( ResourceLoader.Load( _path ) );
RJLog.Log( _path, _resource );
}
return _resource;
}
}
}

View File

@ -66,6 +66,11 @@ namespace Rokojori
public override bool Equals( object? obj )
{
if ( obj == null )
{
return false;
}
var other = obj as SerializedGodotObject;
if ( members.Count != other.members.Count )
@ -77,21 +82,27 @@ namespace Rokojori
for ( int i = 0; i < members.Count; i++ )
{
var member = members[ i ];
if ( member == null )
{
return false ;
}
// RJLog.Log( member );
var otherMember = other.members.Find( m => m.name == member.name );
if ( otherMember == null || ! member.Equals( otherMember ) )
{
// if ( otherMember == null )
// {
// RJLog.Log( member.name + " is not present" );
// }
// else
// {
// SerializedGodotObjectMember.debug = true;
// var eq = member.Equals( otherMember );
// SerializedGodotObjectMember.debug = false;
// RJLog.Log( eq, member.name + " is different, should be:", member.value, "but is: " + otherMember.value );
// }
if ( otherMember == null )
{
// RJLog.Log( member.name + " is not present" );
}
else
{
// SerializedGodotObjectMember.debug = true;
// var eq = member.Equals( otherMember );
// // SerializedGodotObjectMember.debug = false;
// // RJLog.Log( eq, member.name + " is different, should be:", member.value, "but is: " + otherMember.value );
}
return false;
}
@ -140,7 +151,7 @@ namespace Rokojori
var name = fields[ i ].Name;
if ( name.StartsWith( "X_" ) || name.StartsWith( "x_" ) )
if ( name.StartsWith( "X_" ) || name.StartsWith( "x_" ) || name.StartsWith( "_" ) )
{
continue;
}

View File

@ -0,0 +1,43 @@
using Godot;
using System.Text;
using System.Collections.Generic;
namespace Rokojori
{
public class TransformChange
{
Vector3 position = Vector3.Zero;
Quaternion rotation = Quaternion.Identity;
Vector3 scale = Vector3.One;
public bool Check( Node3D source )
{
var changed = false;
if ( position != source.GlobalPosition )
{
changed = true;
position = source.GlobalPosition;
}
var sourceRotation = source.GetGlobalQuaternion();
if ( rotation != sourceRotation )
{
changed = true;
rotation = sourceRotation;
}
if ( scale != source.Scale )
{
changed = true;
scale = source.Scale;
}
return changed;
}
}
}

View File

@ -6,7 +6,7 @@ namespace Rokojori
{
public class HierarchyName
{
public static string Of( Node node, string seperator = "/" )
public static string Of( Node node, string seperator = "" )
{
if ( node == null )
{
@ -19,8 +19,17 @@ namespace Rokojori
while ( it != null )
{
list.Add( it.Name );
it = it.GetParent();
var n = it.Name;
if ( RegexUtility.Matching( n, @"@\d+$" ) )
{
it = null;
}
else
{
list.Add( it.Name );
it = it.GetParent();
}
}
list.Reverse();

View File

@ -2,6 +2,8 @@ using Godot;
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
namespace Rokojori
{
@ -96,6 +98,7 @@ namespace Rokojori
public static List<T> AllIn<T>( Node root, Func<T,bool> filter = null, bool includeRoot = true) where T:class
{
var list = new List<T>();
ForEach<T>( root,
t =>
{
@ -166,6 +169,12 @@ namespace Rokojori
return t;
}
public static async Task RequestNextFrame( this Node node )
{
await node.ToSignal( RenderingServer.Singleton, RenderingServerInstance.SignalName.FramePostDraw );
return;
}
public static T CreateChild<T>( this Node parent, string name = null ) where T:Node,new()
{
return CreateChildIn<T>( parent, name );
@ -181,6 +190,16 @@ namespace Rokojori
return copy;
}
public static void LogInfo( this Node node, params object[] messages )
{
RJLog.Log( node, messages );
}
public static void LogError( this Node node, params object[] messages )
{
RJLog.Error( node, messages );
}
public static Node DeepCopyTo( this Node node, Node parent )
{
return CopyNodeHierarchy( node, parent );
@ -199,8 +218,33 @@ namespace Rokojori
return copy;
}
public static T EnsureValid<T>( T node ) where T:Node
{
if ( GodotObject.IsInstanceValid( node ) )
{
if ( node.IsQueuedForDeletion() )
{
return null;
}
return node;
}
return null;
}
public static void RemoveAndDelete( Node node )
{
if ( ! GodotObject.IsInstanceValid( node ) )
{
return;
}
if ( node.IsQueuedForDeletion() )
{
return;
}
var parent = node.GetParent();
if ( parent != null )
@ -208,6 +252,8 @@ namespace Rokojori
parent.RemoveChild( node );
}
node.QueueFree();
}
@ -242,6 +288,11 @@ namespace Rokojori
}
public static void DestroyChildren( this Node parent, bool includeInternal = false )
{
RemoveAndDeleteChildren( parent, includeInternal );
}
public static void RemoveAndDeleteChildren( Node parent, bool includeInternal = false )
{
if ( parent == null )
@ -250,7 +301,7 @@ namespace Rokojori
}
var numChildren = parent.GetChildCount( includeInternal );
for ( int i = numChildren - 1; i >= 0; i-- )
{
var node = parent.GetChild( i, includeInternal );
@ -312,6 +363,65 @@ namespace Rokojori
return list;
}
public static void OnAllDirectChildren<T>( this Node parent, System.Action<T> action ) where T:Node
{
ForEachDirectChild<T>( parent, action );
}
public static int NumDirectChildrenOf<T>( this Node parent ) where T:Node
{
if ( parent == null )
{
return 0;
}
var numChildren = parent.GetChildCount();
var typeIndex = 0;
for ( int i = 0; i < numChildren; i++ )
{
var node = parent.GetChild( i );
if ( node is T )
{
typeIndex ++;
}
}
return typeIndex;
}
public static T GetNthDirectChild<T>( this Node parent, int index ) where T:Node
{
if ( parent == null )
{
return null;
}
var numChildren = parent.GetChildCount();
var typeIndex = 0;
for ( int i = 0; i < numChildren; i++ )
{
var node = parent.GetChild( i );
if ( node is T t )
{
if ( typeIndex == index )
{
return t;
}
typeIndex ++;
}
}
return null;
}
public static void ForEachDirectChild<T>( Node parent, System.Action<T> action ) where T:Node
{
if ( parent == null || action == null )

View File

@ -50,11 +50,13 @@ namespace Rokojori
if ( CharacterUpdateMode.Process == characterUpdateMode )
{
this.delta = (float)delta;
Nodes.ForEach<CharacterControllerAction>( actionsContainer, a => Actions.Trigger( a ) );
Nodes.ForEachDirectChild<CharacterControllerAction>( actionsContainer, a => Actions.Trigger( a ) );
}
positionSmoother.CopyPosition( graphics, body, rotationSmoothingDuration, delta );
rotationSmoother.CopyRotation( graphics, body, positionSmoothingDuration, delta );
// positionSmoother.CopyPosition( graphics, body, rotationSmoothingDuration, delta );
// rotationSmoother.CopyRotation( graphics, body, positionSmoothingDuration, delta );
Pose.CopyTo( body, graphics );
}
public override void _PhysicsProcess( double delta )

View File

@ -13,15 +13,25 @@ namespace Rokojori
public CharacterBody3D body => controller.body;
public void SetVelocity( Vector3 velocity, bool replace )
public void AddVelocity( Vector3 velocity )
{
body.Velocity += velocity * controller.delta;
}
public void SetVelocity( Vector3 velocity )
{
body.Velocity = velocity * controller.delta;
}
public void Velocity( Vector3 velocity, bool replace )
{
if ( replace )
{
body.Velocity = velocity * controller.delta;
SetVelocity( velocity );
}
else
{
body.Velocity += velocity * controller.delta;
AddVelocity( velocity );
}
}

View File

@ -14,6 +14,36 @@ namespace Rokojori
[Export]
public bool overwriteVelocity = true;
[ExportGroup( "Actions" )]
[Export]
public RJAction onMoving;
[Export]
public RJAction onIdle;
[Export]
public RJAction onForward;
[Export]
public RJAction onBackwards;
[Export]
public RJAction onStrafeLeft;
[Export]
public RJAction onStrafeRight;
[ExportGroup( "Direction Source" )]
[Export]
public Node3D directionSource;
public enum DirectionProcessing
{
None,
Zero_Y_And_Normalize,
Project_On_TransformPlane
}
[Export]
public DirectionProcessing directionProcessing;
[ExportGroup( "Moving" )]
[Export]
@ -35,23 +65,83 @@ namespace Rokojori
[Export]
public RJSensor strafeRight;
[Export]
public bool useBodyDirection = true;
[ExportGroup( "Rotation" )]
[Export]
public bool adjustRotation = true;
[Export]
public Curve forwardToRotation = MathX.Curve( 0, 1 );
[Export( PropertyHint.Range, "0,1")]
public float rotationSmoothingCoefficient = 0.1f;
Smoother rotationSmoother = new Smoother();
public override void _OnTrigger()
{
if ( ! body.IsOnFloor() )
{
return;
}
var movement = Vector3.Zero;
var body = controller.body;
movement += body.GlobalForward() * Sensors.GetValue( forward );
movement -= body.GlobalForward() * Sensors.GetValue( backwards );
var fw = Sensors.GetValue( forward );
var bw = Sensors.GetValue( backwards );
movement += body.GlobalRight() * Sensors.GetValue( strafeRight );
movement -= body.GlobalRight() * Sensors.GetValue( strafeLeft );
var fbAxis = Sensors.PolarAxis( backwards, forward );
var dir = directionSource != null ? directionSource : body;
var forwardDirection = dir.GlobalForward();
var rightDirection = useBodyDirection ? body.GlobalRight() : dir.GlobalRight();
if ( DirectionProcessing.Zero_Y_And_Normalize == directionProcessing )
{
forwardDirection.Y = 0;
rightDirection.Y = 0;
}
else if ( DirectionProcessing.Project_On_TransformPlane == directionProcessing )
{
var bodyPlane = Plane3.CreateFromNode3D( body );
forwardDirection = bodyPlane.ConstrainToPlane( forwardDirection + body.GlobalPosition ) - body.GlobalPosition;
rightDirection = bodyPlane.ConstrainToPlane( rightDirection + body.GlobalPosition ) - body.GlobalPosition;
}
forwardDirection = forwardDirection.Normalized();
rightDirection = rightDirection.Normalized();
movement += forwardDirection * Sensors.PolarAxis( backwards, forward ) * moveSpeed;
movement += rightDirection * Sensors.PolarAxis( strafeLeft, strafeRight ) * strafeSpeed ;
if ( body.IsOnFloor() )
{
movement *= onFloorMultiply;
}
SetVelocity( movement, overwriteVelocity );
if ( adjustRotation )
{
var currentRotation = body.GetGlobalQuaternion();
var nextRotation = Math3D.LookRotation( forwardDirection );
var sm = 1f - rotationSmoothingCoefficient;
sm = sm * sm;
var speed = MathX.Clamp01( movement.Length() / moveSpeed ) * Mathf.Max( fw, bw );
sm *= forwardToRotation.Sample( speed );
var rotation = rotationSmoother.SmoothWithCoefficient( currentRotation, nextRotation, sm, controller.delta );
body.SetGlobalQuaternion( rotation );
}
Velocity( movement, overwriteVelocity );
}
}

View File

@ -0,0 +1,24 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[GlobalClass]
public partial class Gravity:CharacterControllerAction
{
[Export]
public float strength = 10;
public override void _OnTrigger()
{
if ( body.IsOnFloor() )
{
return;
}
// RJLog.Log( controller.body.Velocity );
AddVelocity( Vector3.Down * strength );
}
}
}

View File

@ -0,0 +1,21 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[GlobalClass]
public partial class GroundReset:CharacterControllerAction
{
public override void _OnTrigger()
{
if ( ! body.IsOnFloor() )
{
return;
}
SetVelocity( Vector3.Zero );
}
}
}

View File

@ -0,0 +1,72 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[GlobalClass]
public partial class Jump:CharacterControllerAction
{
[Export]
public RJSensor button;
[Export]
public float jumpStrength;
[Export]
public float maxJumpDuration;
[Export]
public Curve jumpCurveStrength = MathX.Curve( 1, 0 );
[Export]
public float jumpedDuration;
[Export]
public bool canJump = false;
[Export]
public bool jumping = false;
public override void _OnTrigger()
{
if ( body.IsOnFloor() )
{
jumping = false;
}
if ( ! ( jumping || body.IsOnFloor() ) )
{
return;
}
if ( ! button.IsActive() )
{
jumping = false;
return;
}
if ( ! jumping )
{
jumping = true;
jumpedDuration = 0;
}
jumpedDuration += controller.delta;
canJump = jumpedDuration < maxJumpDuration;
if ( jumpedDuration < maxJumpDuration )
{
var jumpStrengthMultiply = jumpCurveStrength.Sample( jumpedDuration / maxJumpDuration );
AddVelocity( Vector3.Up * jumpStrength );
}
}
}
}

View File

@ -10,6 +10,7 @@ namespace Rokojori
{
public override void _OnTrigger()
{
// RJLog.Log( controller.body.Velocity );
controller.body.MoveAndSlide();
}
}

View File

@ -0,0 +1,31 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LODArrangement:Resource
{
[Export]
public LODLevel[] levels;
public LODLevel GetLevel( LODNode node )
{
for ( int i = 0; i < levels.Length; i++ )
{
if ( ! levels[ i ].Evaluate( node ) )
{
continue;
}
return levels[ i ];
}
return null;
}
}
}

View File

@ -0,0 +1,22 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
public partial class LODCameraDistanceRule:LODLevelVisibilityRule
{
[Export]
public float minDistance;
[Export]
public float maxDistance;
public override bool Evaluate( LODNode lodNode )
{
return false;
}
}
}

View File

@ -0,0 +1,21 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
public partial class LODCameraPitchRule:LODLevelVisibilityRule
{
[Export]
public float pitch;
[Export]
public float pitchRange;
public override bool Evaluate( LODNode lodNode )
{
return false;
}
}
}

View File

@ -0,0 +1,21 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
public partial class LODCameraYawRule:LODLevelVisibilityRule
{
[Export]
public float yaw;
[Export]
public float yawRange;
public override bool Evaluate( LODNode lodNode )
{
return false;
}
}
}

34
Runtime/LOD/LODLevel.cs Normal file
View File

@ -0,0 +1,34 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LODLevel:Resource
{
[Export]
public LODLevelVisibilityRule[] visibilityRules;
[Export]
public Mesh mesh;
[Export]
public Material material;
public bool Evaluate( LODNode lodNode )
{
for ( int i = 0; i < visibilityRules.Length; i++ )
{
if ( ! visibilityRules[ i ].Evaluate( lodNode ) )
{
return false;
}
}
return true;
}
}
}

View File

@ -0,0 +1,17 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LODLevelVisibilityRule:Resource
{
public virtual bool Evaluate( LODNode lodNode )
{
return false;
}
}
}

131
Runtime/LOD/LODMultiMesh.cs Normal file
View File

@ -0,0 +1,131 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LODMultiMesh:MultiMeshInstance3D
{
[Export]
public float minDistance = 0;
[Export]
public float cullDistance = 100;
[Export]
public Curve fillCurve = MathX.Curve( 0, 1 );
float _distance = -10;
[Export]
public float distanceTreshold = 0.01f;
[Export( PropertyHint.Range, "0,1" )]
public float scaleRange = 0.5f;
[Export]
public float downMax = 0.1f;
[Export]
Vector3[] cachedOrigins = null;
[Export]
Vector3[] cachedBasis = null;
public void CacheTransforms()
{
var mm = Multimesh;
cachedOrigins = new Vector3[ mm.InstanceCount ];
cachedBasis = new Vector3[ mm.InstanceCount * 3 ];
for ( int i = 0; i < mm.InstanceCount; i++ )
{
var trsf = mm.GetInstanceTransform( i );
cachedBasis[ i * 3 + 0 ] = trsf.Basis.Column0;
cachedBasis[ i * 3 + 1 ] = trsf.Basis.Column1;
cachedBasis[ i * 3 + 2 ] = trsf.Basis.Column2;
cachedOrigins[ i ] =( trsf.Origin );
}
}
public override void _Process(double delta)
{
var camera = GetViewport().GetCamera3D();
#if TOOLS
if ( Engine.IsEditorHint() )
{
camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
}
#endif
var distance = ( camera.GlobalPosition - GlobalPosition ).Length();
if ( Mathf.Abs( _distance - distance ) < distanceTreshold )
{
return;
}
_distance = distance;
var mapped = MathX.RemapClamped( distance, minDistance, cullDistance, 1, 0 );
var mm = Multimesh;
// this.LogInfo( "MM", (int) distance, ( (int) ( 100 * mapped ) ) + "%", (int)( fillCurve.Sample( mapped ) * mm.InstanceCount) + " instances" );
if ( mm == null )
{
return;
}
if ( distance >= cullDistance )
{
mm.VisibleInstanceCount = 0;
return;
}
var count = Mathf.RoundToInt( fillCurve.Sample( mapped ) * mm.InstanceCount );
count = Mathf.Clamp( count, 0, mm.InstanceCount );
var fadeStart = count * ( 1 - scaleRange );
for ( int i = 0; i < count; i++ )
{
var cbasis = new Basis( cachedBasis[ i * 3 + 0 ], cachedBasis[ i * 3 + 1 ], cachedBasis[ i * 3 + 2 ] );
var trsf = new Transform3D( cbasis, cachedOrigins[ i ] );
if ( i >= fadeStart )
{
var amount = MathX.RemapClamped( i, fadeStart, count, 0, 1 );
var basis = trsf.Basis.Scaled( new Vector3( 1, Mathf.Max( ( 1f - amount ), 0.0001f ), 1 ));
var offsetTrsf = new Transform3D( basis, trsf.Origin + downMax * Vector3.Down);
mm.SetInstanceTransform( i, offsetTrsf );
}
else
{
mm.SetInstanceTransform( i, trsf );
}
}
mm.VisibleInstanceCount = count;
}
}
}

138
Runtime/LOD/LODNode.cs Normal file
View File

@ -0,0 +1,138 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class LODNode:MeshInstance3D
{
[Export]
public LODArrangement arrangement;
LODLevel _currentLevel;
public override void _Process( double delta )
{
if ( arrangement == null )
{
return;
}
_hasCameraDirection = false;
_hasDistance = false;
_hasPitch = false;
_hasYaw = false;
var level = arrangement.GetLevel( this );
if ( level == _currentLevel )
{
return;
}
Mesh = level.mesh;
MaterialOverride = level.material;
}
public Camera3D camera
{
get
{
var _camera = GetViewport().GetCamera3D();
#if TOOLS
if ( Engine.IsEditorHint() )
{
_camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
}
#endif
return _camera;
}
}
bool _hasCameraDirection = false;
Vector3 _cameraDirection;
public Vector3 cameraDirection
{
get
{
if ( _hasCameraDirection )
{
return _cameraDirection;
}
_cameraDirection = ( GlobalPosition - camera.GlobalPosition );
return _cameraDirection;
}
}
bool _hasDistance = false;
float _distance = 0;
public float distance
{
get
{
if ( _hasDistance )
{
return _distance;
}
_distance = cameraDirection.Length();
return _distance;
}
}
bool _hasPitch = false;
float _pitch = 0;
public float pitch
{
get
{
if ( _hasPitch )
{
return _pitch;
}
_pitch = Math3D.GlobalPitch( cameraDirection );
return _pitch;
}
}
bool _hasYaw = false;
float _yaw = 0;
public float yaw
{
get
{
if ( _hasYaw )
{
return _yaw;
}
_yaw = Math3D.GlobalYaw( cameraDirection );
return _yaw;
}
}
}
}

View File

@ -9,30 +9,71 @@ namespace Rokojori
[GlobalClass]
public partial class LODParent:Node3D
{
[Export]
public float cullDistance = 4000;
public enum DistanceMode
{
Via_Array,
Via_Curve
}
[Export]
public Curve distribution = MathX.Curve( 0, 1 );
public DistanceMode mode;
[ExportGroup("Array Mode")]
[Export]
public bool arrayCulling = true;
[Export]
public float[] arrayTresholds;
[ExportGroup("Curve Mode")]
[Export]
public bool curveCulling = true;
[Export]
public float curveCullDistance = 4000;
[Export]
public Curve curveDistribution = MathX.Curve( 0, 1 );
[ExportGroup("LODs")]
[Export]
public Node3D[] lods;
[Export]
public float updateDistance = 10;
[ExportGroup("Testing")]
[Export]
public float X_processingCameraDistance = 0;
[Export]
public float X_realCameraDistance = 0;
[Export]
public float X_selectedIndex = 0;
[Export]
public bool testMode = false;
[Export]
public float testDistance = 0;
[Export]
public bool processOnlyWhenVisible = true;
float lastSquaredDistance = -1;
int lastSelectedIndex = -1;
[Export]
public int changeBlock = 0;
[Export]
public int blocker = 0;
public override void _Process( double delta )
{
if ( processOnlyWhenVisible && ! Visible )
{
return;
}
if ( blocker > 0 )
{
blocker --;
@ -56,41 +97,78 @@ namespace Rokojori
#endif
var squaredDistance = GlobalPosition.DistanceSquaredTo( camera.GlobalPosition );
X_realCameraDistance = Mathf.Sqrt( squaredDistance );
if ( testMode )
{
squaredDistance = testDistance * testDistance;
}
if ( Mathf.Abs( lastSquaredDistance - squaredDistance ) < updateDistance * updateDistance )
{
{
return;
}
lastSquaredDistance = squaredDistance;
var realDistance = Mathf.Sqrt( lastSquaredDistance );
var normalizedDistance = MathX.NormalizeClamped( realDistance, 0, cullDistance );
X_processingCameraDistance = realDistance;
var active = distribution.Sample( normalizedDistance ) * lods.Length;
var selectedIndex = Mathf.Min( lods.Length - 1, Mathf.RoundToInt( active ) );
var selectedIndex = GetLODIndex( realDistance );
X_selectedIndex = selectedIndex;
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;
if ( selectedIndex >= lods.Length )
{
Arrays.ForEach( lods, l => NodeState.Set( l, false ) );
}
else
{
var selectedLOD = lods[ selectedIndex ];
Arrays.ForEach( lods, l => NodeState.Set( l, selectedLOD == l ) );
}
}
int GetLODIndex( float realDistance )
{
if ( DistanceMode.Via_Array == mode )
{
for ( int i = 0; i < arrayTresholds.Length; i++ )
{
if ( realDistance <= arrayTresholds[ i ] )
{
return i;
}
}
return arrayCulling ? lods.Length : ( lods.Length - 1 );
}
else if ( DistanceMode.Via_Curve == mode )
{
if ( curveCulling && realDistance >= curveCullDistance )
{
return lods.Length;
}
var normalizedDistance = MathX.NormalizeClamped( realDistance, 0, curveCullDistance );
var active = curveDistribution.Sample( normalizedDistance ) * lods.Length;
return Mathf.Min( lods.Length - 1, Mathf.RoundToInt( active ) );
}
return -1;
}
}
}

View File

@ -0,0 +1,121 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
using Godot.Collections;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class MultiMeshGenerator:Node
{
[Export]
public Node3D source;
[Export]
public Node3D output;
[Export]
public float blockSize = 50;
[Export]
public bool clearOutput = true;
[Export]
public bool update = false;
public override void _Process( double delta )
{
if ( ! update )
{
return;
}
update = false;
Generate();
}
public void Generate()
{
if ( clearOutput )
{
Nodes.RemoveAndDeleteChildren( output );
}
var grid = Grid2D<MeshInstance3D>.XZfromNode3D<MeshInstance3D>( blockSize, blockSize );
this.LogInfo( "Grid Created", grid );
var instances = Nodes.AllIn<MeshInstance3D>( source );
this.LogInfo( "Grabbed instances", instances.Count );
grid.AddAll( instances );
this.LogInfo( "Added instance to grid" );
grid.ForEachCell(
( cellID, list ) =>
{
var meshList = new MapList<Mesh,MeshInstance3D>();
list.ForEach(
( n )=>
{
var m = n.Mesh;
meshList.Add( m, n );
}
);
var cIDCount = 0;
meshList.ForEach(
( mesh, list ) =>
{
var cellIDInfo = "[" + cellID.X + "," + cellID.Y+"]";
var mmi = output.CreateChild<LODMultiMesh>( "Cell" + cellIDInfo + "(" + cIDCount + ")" );
var mm = new MultiMesh();
mm.Mesh = mesh;
mm.TransformFormat = MultiMesh.TransformFormatEnum.Transform3D;
mm.InstanceCount = list.Count;
mm.VisibleInstanceCount = list.Count;
var random = new LCG();
random.SetSeed( 12134 + (int)( Noise.Perlin( cellID ) * 10083 ) );
var randomList = RandomList<MeshInstance3D>.Randomize( list, random );
var center = Math3D.Center( randomList );
mmi.GlobalPosition = center;
for ( int i = 0; i < randomList.Count; i++ )
{
var trsf = randomList[ i ].GlobalTransform;
trsf.Origin -= center;
mm.SetInstanceTransform( i, trsf );
}
mmi.Multimesh = mm;
mmi.CacheTransforms();
Materials.Set( mmi, Materials.Get<Material>( list[ 0 ] ) );
cIDCount++;
}
);
}
);
}
}
}

View File

@ -72,9 +72,9 @@ namespace Rokojori
output.Append( obj.ToString() );
}
static void LogMessage( string message )
static void LogMessage( string message, int frameIndex = 3 )
{
var trace = GetTrace();
var trace = GetTrace( frameIndex );
GD.PrintRich("\n[b]" + message + "[/b]" );
GD.PrintRich( trace );
}
@ -86,9 +86,9 @@ namespace Rokojori
GD.PrintRich( trace );
}
static void LogErrorMessage( string message )
static void LogErrorMessage( string message, int frameIndex = 3 )
{
var trace = GetTrace();
var trace = GetTrace( frameIndex );
GD.PrintErr( "\n"+ message );
GD.PrintRich( trace );
}
@ -98,10 +98,9 @@ namespace Rokojori
return ( new System.Diagnostics.StackTrace( true ) ).ToString();
}
static string GetTrace()
static string GetTrace( int frameIndex = 3 )
{
var stackTrace = new System.Diagnostics.StackTrace( true );
var frameIndex = 3;
var frame = stackTrace.GetFrame( frameIndex );
@ -145,11 +144,22 @@ namespace Rokojori
LogMessage( GetLogString( objects ) );
}
public static void Log( Node node, params object[] objects)
{
LogMessage( "[color=#55aaff][ " + HierarchyName.Of( node ) + " ][/color] " + GetLogString( objects ), 4 );
}
public static void Error( params object[] objects)
{
LogErrorMessage( GetLogString( objects ) );
}
public static void Error( Node node, params object[] objects)
{
LogErrorMessage( "[ " + HierarchyName.Of( node ) + " ] " + GetLogString( objects ), 4 );
}
public static string GetLogString( object[] objects)
{
var sb = new StringBuilder();

189
Runtime/Math/FFT.cs Normal file
View File

@ -0,0 +1,189 @@
using Godot;
namespace Rokojori
{
public class FFT
{
class FloatSineTable
{
public float[] sineValues;
public FloatSineTable( int numBits )
{
int len = 1 << numBits;
sineValues = new float[1 << numBits];
for ( int i = 0; i < len; i++ )
{
sineValues[i] = ( float ) Mathf.Sin( ( i * Mathf.Pi * 2.0 ) / len );
}
}
}
class BitReverseTable
{
public int[] reversedBits;
public BitReverseTable( int numBits )
{
reversedBits = new int[1 << numBits];
for ( int i = 0; i < reversedBits.Length; i++ )
{
reversedBits[i] = ReverseBits( i, numBits );
}
}
static int ReverseBits( int index, int numBits )
{
int i = 0;
int rev = 0;
for ( i = rev = 0; i < numBits; i++ )
{
rev = ( rev << 1 ) | ( index & 1 );
index >>= 1;
}
return rev;
}
}
static readonly int MaxSizeLog2 = 16;
static BitReverseTable[] reverseTables = new BitReverseTable[ MaxSizeLog2 ];
static FloatSineTable[] floatSineTables = new FloatSineTable[ MaxSizeLog2 ];
static float[] GetFloatSineTable( int n )
{
FloatSineTable sineTable = floatSineTables[n];
if ( sineTable == null )
{
sineTable = new FloatSineTable( n );
floatSineTables[n] = sineTable;
}
return sineTable.sineValues;
}
static int[] GetReverseTable( int n )
{
BitReverseTable reverseTable = reverseTables[n];
if ( reverseTable == null )
{
reverseTable = new BitReverseTable( n );
reverseTables[n] = reverseTable;
}
return reverseTable.reversedBits;
}
static void Transform( int sign, int n, float[] real, float[] imaginary )
{
float scale = ( sign > 0 ) ? ( 2.0f / n ) : ( 0.5f );
int numBits = Rokojori.FFT.NumBits( n );
int[] reverseTable = GetReverseTable( numBits );
float[] sineTable = GetFloatSineTable( numBits );
int mask = n - 1;
int cosineOffset = n / 4; // phase offset between cos and sin
int i, j;
for ( i = 0; i < n; i++ )
{
j = reverseTable[i];
if ( j >= i )
{
float tempr = real[j] * scale;
float tempi = imaginary[j] * scale;
real[j] = real[i] * scale;
imaginary[j] = imaginary[i] * scale;
real[i] = tempr;
imaginary[i] = tempi;
}
}
int mmax, stride;
int numerator = sign * n;
for ( mmax = 1, stride = 2 * mmax; mmax < n; mmax = stride, stride = 2 * mmax )
{
int phase = 0;
int phaseIncrement = numerator / ( 2 * mmax );
for ( int m = 0; m < mmax; ++m )
{
float wr = sineTable[( phase + cosineOffset ) & mask]; // cosine
float wi = sineTable[phase];
for ( i = m; i < n; i += stride )
{
j = i + mmax;
float tr = ( wr * real[j] ) - ( wi * imaginary[j] );
float ti = ( wr * imaginary[j] ) + ( wi * real[j] );
real[j] = real[i] - tr;
imaginary[j] = imaginary[i] - ti;
real[i] += tr;
imaginary[i] += ti;
}
phase = ( phase + phaseIncrement ) & mask;
}
mmax = stride;
}
}
public static void CalculateMagnitudes( float[] ar, float[] ai, float[] magnitudes )
{
for ( int i = 0; i < magnitudes.Length; ++i )
{
magnitudes[i] = ( float ) Mathf.Sqrt( (ar[i] * ar[i] ) + ( ai[i] * ai[i] ));
}
}
static int NumBits( int powerOf2 )
{
int i;
for ( i = -1; powerOf2 > 0; powerOf2 = powerOf2 >> 1, i++ )
;
return i;
}
public static void Forward( int n, float[] real, float[] imaginary )
{
Transform( 1, n, real, imaginary );
}
public static void Forward( int n, float[] real )
{
float[] ai = new float[ real.Length ];
Forward( n, real, ai );
}
public static void Inverse( int n, float[] real, float[] imaginary )
{
Transform( -1, n, real, imaginary );
}
public static float FrequencyAtBin( int binIndex, int numBands, float sampleRate )
{
return binIndex * sampleRate / ( 2.0f * numBands );
}
}
}

View File

@ -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 )

View File

@ -25,8 +25,10 @@ namespace Rokojori
public float A{ get { return end.Y - start.Y; } }
public float B{ get { return start.X - end.X; } }
public float C{ get { return A * start.X + B * start.Y; } }
public Vector2 direction { get { return end - start; } }
public Vector2 reverseDirection { get { return start - end; } }
public float angle
{
get {
@ -47,6 +49,8 @@ namespace Rokojori
}
}
public Vector2 center => ( end - start ) / 2f;
public Vector2? InfiniteIntersectionOf( Line2 line2 )
{
@ -154,7 +158,24 @@ namespace Rokojori
return ( point - ClosestPointToPoint( point ) ).Length();
}
public Vector2 GetAt( float t )
{
return direction * t + start;
}
public void ScaleFromCenter( float scale )
{
var center = GetAt( 0.5f );
var dir = direction * 0.5f;
start = center - dir * scale;
end = center + dir * scale;
}
public void Translate( Vector2 translation )
{
start += translation;
end += translation;
}
}
}

View File

@ -10,6 +10,15 @@ namespace Rokojori
public Plane3(){}
public static Plane3 CreateFromNode3D( Node3D n )
{
var p = new Plane3();
p.SetFromNormalAndCoplanarPoint( n.GlobalUp(), n.GlobalPosition );
return p;
}
public void Set( Vector3 normal, float constant = 0 )
{
this.normal = normal;

View File

@ -196,6 +196,12 @@ namespace Rokojori
return cValue + sValue + position;
}
public static void CopyTo( Node3D source, Node3D target )
{
target.GlobalPosition = source.GlobalPosition;
target.SetGlobalQuaternion( source.GetGlobalQuaternion() );
}
public static Pose Merge( List<Pose> poses, List<float> weights = null )
{

View File

@ -62,6 +62,11 @@ namespace Rokojori
public static Sphere ContainingBox( Box3 box )
{
if ( box == null )
{
return null;
}
return new Sphere( box.center, box.maxDistance / 2f );
}

View File

@ -0,0 +1,153 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
namespace Rokojori
{
public class Triangle2
{
public Vector2 a;
public Vector2 b;
public Vector2 c;
bool _needsUpdate = true;
Vector2 _center;
public Triangle2( Vector2 a, Vector2 b, Vector2 c )
{
this.a = a;
this.b = b;
this.c = c;
}
public static Triangle2 AsXZ( Triangle3 t )
{
return new Triangle2( Math2D.XZ( t.a ), Math2D.XZ( t.b ), Math2D.XZ( t.c ) );
}
public void Update()
{
if ( ! _needsUpdate )
{
return;
}
_needsUpdate = false;
_center = ( a + b +c ) /3f;
}
public Vector2 center
{
get
{
Update();
return _center;
}
}
public Line2 GetEdge( int index )
{
return index == 0 ? new Line2( a, b ) : index == 1 ? new Line2( b, c ) : new Line2( c, a) ;
}
public Vector2 GetEdgeDirection( int index )
{
return GetEdge( index ).direction;
}
public Vector2 GetInnerNormal( int index )
{
var edge = GetEdge( index );
var normal = Math2D.Rotate90DegreesRight( edge.direction.Normalized() );
var flipNormal = - normal;
var cnt = center;
var edgeCenter = edge.center;
var dist = ( cnt - edgeCenter ).Length() * 0.1f;
if ( ContainsPoint( edgeCenter + normal ) )
{
return normal;
}
return -normal;
}
public bool ContainsPoint( Vector2 p )
{
if ( true)
{
return true;
}
var d1 = Sign( p, a, b );
var d2 = Sign( p, b, c );
var d3 = Sign( p, c, a );
var hasNegative = ( d1 < 0 ) || ( d2 < 0 ) || ( d3 < 0 );
var hasPositive = ( d1 > 0 ) || ( d2 > 0 ) || ( d3 > 0 );
return ! ( hasNegative && hasPositive );
}
public Vector2 GetOuterNormal( int index )
{
return - GetInnerNormal( index );
}
public Triangle2 Shrink( float distance )
{
var edges = new List<Line2>();
for ( int i = 0; i < 3; i++ )
{
edges.Add( GetEdge( i ) );
edges[ i ].ScaleFromCenter( 10 );
var t = GetInnerNormal( i ) * distance;
edges[ i ].Translate( t );
// RJLog.Log( i, ">", t );
}
var e0 = edges[ 0 ];
var e1 = edges[ 1 ];
var e2 = edges[ 2 ];
var i01 = e0.IntersectionOf( e1 );
if ( i01 == null || ! ContainsPoint( (Vector2) i01 ) )
{
RJLog.Log( "i01", i01, i01 != null ? ContainsPoint( (Vector2) i01 ) : false );
return null;
}
var i12 = e1.IntersectionOf( e2 );
if ( i12 == null || ! ContainsPoint( (Vector2) i12 ) )
{
RJLog.Log( "i12", i12, i12 != null ? ContainsPoint( (Vector2) i12 ) : false );
return null;
}
var i20 = e2.IntersectionOf( e0 );
if ( i20 == null || ! ContainsPoint( (Vector2) i20 ) )
{
RJLog.Log( "i20", i20, i20 != null ? ContainsPoint( (Vector2) i20 ) : false );
return null;
}
return new Triangle2( (Vector2) i20, (Vector2) i01, (Vector2) i12 );
}
static float Sign( Vector2 p1, Vector2 p2, Vector2 p3)
{
return ( p1.X - p3.X ) * ( p2.Y - p3.Y ) - ( p2.X - p3.X ) * (p1.Y - p3.Y );
}
}
}

View File

@ -21,6 +21,46 @@ namespace Rokojori
_needsUpdate = true;
}
public void SetFrom( MeshGeometry mg, int a, int b, int c )
{
this.a = mg.vertices[ a ];
this.b = mg.vertices[ b ];
this.c = mg.vertices[ c ];
_needsUpdate = true;
}
public static Triangle3 CreateFrom( MeshGeometry mg, int a, int b, int c )
{
var t = new Triangle3(
mg.vertices[ a ],
mg.vertices[ b ],
mg.vertices[ c ]
);
return t;
}
public static Triangle3 CreateFrom( MeshGeometry mg, int triangleIndex )
{
var triangleOffset = triangleIndex;
var t = new Triangle3(
mg.vertices[ triangleOffset ],
mg.vertices[ triangleOffset + 1],
mg.vertices[ triangleOffset + 2 ]
);
return t;
}
public void SetFrom( MeshGeometry mg, int triangleIndex )
{
var triangleOffset = triangleIndex;
SetFrom( mg, triangleOffset, triangleOffset + 1, triangleOffset + 2 );
}
public float perimeter
{
get
@ -38,6 +78,7 @@ namespace Rokojori
Vector3 _center;
Sphere _boundingSphere;
Plane3 _plane;
Vector3 _normal;
bool _needsUpdate = false;
@ -46,6 +87,16 @@ namespace Rokojori
_needsUpdate = true;
}
public Vector3 center
{
get
{
Update();
return _center;
}
}
public Sphere boundingSphere
{
get
@ -69,6 +120,10 @@ namespace Rokojori
_plane = new Plane3();
_plane.SetFromCoplanarPoints( a, b, c );
_normal = ( b - a ).Cross( c - a ).Normalized();
_center = ( a + b + c ) / 3f;
_needsUpdate = false;
}
@ -208,6 +263,11 @@ namespace Rokojori
return ( ( a - b ).Length() * h ) / 2f;
}
public LerpCurve3 GetOutline()
{
return LerpCurve3.FromPoints( GetPoint( 0 ), GetPoint( 1 ), GetPoint( 2 ) );
}
public Vector3 GetPoint( int i )
{
if ( i == 0 )
@ -326,6 +386,105 @@ namespace Rokojori
return false;
}
public void Rotate( Quaternion q, Vector3? pivot = null )
{
if ( pivot != null )
{
Translate( -( (Vector3)pivot ));
}
a = a * q;
b = b * q;
c = c * q;
if ( pivot != null )
{
Translate( ( (Vector3)pivot ));
}
}
public Triangle3 Clone()
{
return new Triangle3( a, b, c );
}
public Vector3 normal
{
get
{
Update();
return _normal;
}
}
public Quaternion GetXZAlignmentRotation()
{
return Math3D.AlignUp( normal );
}
public void ScaleFromCenter( float scale )
{
var cnt = center;
a = ( a - cnt ) * scale + cnt;
b = ( b - cnt ) * scale + cnt;
c = ( c - cnt ) * scale + cnt;
}
public Triangle3 Shrink( float distance )
{
var n = normal;
var t = Offset( distance );
if ( t == null || Math3D.LookingAtEachOther( t.normal, n ) )
{
return null;
}
return t;
}
public Triangle3 Offset( float distance )
{
var rotationXZ = GetXZAlignmentRotation();
var clone = Clone();
var originalCenter = center;
clone.Translate( - originalCenter );
clone.Rotate( rotationXZ );
var t2 = Triangle2.AsXZ( clone );
var shrinked = t2.Shrink( distance );
if ( shrinked == null )
{
return null;
}
clone = XYasXZ( shrinked );
clone.Rotate( rotationXZ.Inverse() );
clone.Translate( originalCenter );
return clone;
}
public void Translate( Vector3 translation )
{
a += translation;
b += translation;
c += translation;
}
public static Triangle3 XYasXZ( Triangle2 t )
{
return new Triangle3( Math3D.XYasXZ( t.a ), Math3D.XYasXZ( t.b ), Math3D.XYasXZ( t.c ) );
}
public static bool InsideTriangle( Vector3 point, Vector3 a, Vector3 b, Vector3 c )
{
@ -374,6 +533,7 @@ namespace Rokojori
return new Vector3( 1f - u - v, v, u );
}
}
}

View File

@ -0,0 +1,112 @@
using Godot;
using System.Collections;
using System.Collections.Generic;
namespace Rokojori
{
[Tool]
[GlobalClass]
public partial class TriangleTest:Node3D
{
[Export]
public bool alignUp = false;
[Export]
public bool shrink = false;
[Export]
public float shrinkDistance = 0;
[Export]
public bool scale = false;
[Export]
public float scaleAmount = 1;
[ExportGroup("Source")]
[Export]
public Node3D sA;
[Export]
public Node3D sB;
[Export]
public Node3D sC;
[ExportGroup("Target")]
[Export]
public Node3D tA;
[Export]
public Node3D tB;
[Export]
public Node3D tC;
[Export]
public Vector3 offset = Vector3.Zero;
public override void _Process( double delta )
{
if ( alignUp )
{
AlignUp();
}
if ( shrink )
{
Shrink();
}
if ( scale )
{
ScaleFromCenter();
}
}
void AlignUp()
{
var s = GetSourceTriangle();
var r = s.GetXZAlignmentRotation();
RJLog.Log( s.normal, r );
s.Rotate( r, s.center );
SetTargetTriangle( s );
}
void Shrink()
{
var s = GetSourceTriangle();
s = s.Offset( shrinkDistance );
if ( s == null )
{
RJLog.Log( "Shrinking failed" );
SetTargetTriangle( GetSourceTriangle() );
return;
}
SetTargetTriangle( s );
}
void ScaleFromCenter()
{
var s = GetSourceTriangle();
s.ScaleFromCenter( scaleAmount );
SetTargetTriangle( s );
}
Triangle3 GetSourceTriangle()
{
return new Triangle3( sA.GlobalPosition, sB.GlobalPosition, sC.GlobalPosition );
}
void SetTargetTriangle( Triangle3 t )
{
tA.GlobalPosition = t.a + offset;
tB.GlobalPosition = t.b + offset;
tC.GlobalPosition = t.c + offset;
}
}
}

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