From 14d9add9e6dcf6581ba1beb17cc1974a997d9f3a Mon Sep 17 00:00:00 2001 From: spatialfree Date: Sun, 3 Nov 2024 16:30:32 -0500 Subject: [PATCH] init commit after fatal local git file corruption --- Arts.cs | 166 +++++++++ Assets/backbox.hlsl | 43 +++ Assets/floor.hlsl | 53 +++ Assets/meshes/assets.glb | 3 + Assets/mono.hlsl | 48 +++ Assets/sfx/crisp_nom.mp3 | 3 + Assets/sfx/punch_through.mp3 | 3 + Assets/unlit.hlsl | 46 +++ Maths.cs | 193 +++++++++++ Mono.cs | 280 ++++++++++++++++ PassthroughFBExt.cs | 315 ++++++++++++++++++ Platforms/Android/AndroidManifest.xml | 55 +++ Platforms/Android/MainActivity.cs | 87 +++++ .../Android/Resources/AboutResources.txt | 47 +++ .../Resources/layout/activity_main.xml | 13 + .../Resources/mipmap-anydpi-v26/appicon.xml | 4 + .../mipmap-anydpi-v26/appicon_round.xml | 4 + .../mipmap-hdpi/appicon_background.png | 3 + .../mipmap-hdpi/appicon_foreground.png | 3 + .../mipmap-mdpi/appicon_background.png | 3 + .../mipmap-mdpi/appicon_foreground.png | 3 + .../mipmap-xhdpi/appicon_background.png | 3 + .../mipmap-xhdpi/appicon_foreground.png | 3 + .../mipmap-xxhdpi/appicon_background.png | 3 + .../mipmap-xxhdpi/appicon_foreground.png | 3 + .../mipmap-xxxhdpi/appicon_background.png | 3 + .../mipmap-xxxhdpi/appicon_foreground.png | 3 + .../values/ic_launcher_background.xml | 4 + .../Android/Resources/values/strings.xml | 4 + Platforms/Net/App.ico | Bin 0 -> 112163 bytes Platforms/Net/SKApp.ico | Bin 0 -> 38982 bytes Program.cs | 58 ++++ Projects/Android/snake.Android.csproj | 50 +++ android_icons.sh | 56 ++++ readme.md | 49 +++ snake.csproj | 25 ++ 36 files changed, 1639 insertions(+) create mode 100644 Arts.cs create mode 100644 Assets/backbox.hlsl create mode 100644 Assets/floor.hlsl create mode 100644 Assets/meshes/assets.glb create mode 100644 Assets/mono.hlsl create mode 100644 Assets/sfx/crisp_nom.mp3 create mode 100644 Assets/sfx/punch_through.mp3 create mode 100644 Assets/unlit.hlsl create mode 100644 Maths.cs create mode 100644 Mono.cs create mode 100644 PassthroughFBExt.cs create mode 100644 Platforms/Android/AndroidManifest.xml create mode 100644 Platforms/Android/MainActivity.cs create mode 100644 Platforms/Android/Resources/AboutResources.txt create mode 100644 Platforms/Android/Resources/layout/activity_main.xml create mode 100644 Platforms/Android/Resources/mipmap-anydpi-v26/appicon.xml create mode 100644 Platforms/Android/Resources/mipmap-anydpi-v26/appicon_round.xml create mode 100644 Platforms/Android/Resources/mipmap-hdpi/appicon_background.png create mode 100644 Platforms/Android/Resources/mipmap-hdpi/appicon_foreground.png create mode 100644 Platforms/Android/Resources/mipmap-mdpi/appicon_background.png create mode 100644 Platforms/Android/Resources/mipmap-mdpi/appicon_foreground.png create mode 100644 Platforms/Android/Resources/mipmap-xhdpi/appicon_background.png create mode 100644 Platforms/Android/Resources/mipmap-xhdpi/appicon_foreground.png create mode 100644 Platforms/Android/Resources/mipmap-xxhdpi/appicon_background.png create mode 100644 Platforms/Android/Resources/mipmap-xxhdpi/appicon_foreground.png create mode 100644 Platforms/Android/Resources/mipmap-xxxhdpi/appicon_background.png create mode 100644 Platforms/Android/Resources/mipmap-xxxhdpi/appicon_foreground.png create mode 100644 Platforms/Android/Resources/values/ic_launcher_background.xml create mode 100644 Platforms/Android/Resources/values/strings.xml create mode 100644 Platforms/Net/App.ico create mode 100644 Platforms/Net/SKApp.ico create mode 100644 Program.cs create mode 100644 Projects/Android/snake.Android.csproj create mode 100755 android_icons.sh create mode 100644 readme.md create mode 100644 snake.csproj diff --git a/Arts.cs b/Arts.cs new file mode 100644 index 0000000..68bc215 --- /dev/null +++ b/Arts.cs @@ -0,0 +1,166 @@ +using System.Collections.Generic; +using StereoKit; + +namespace snake; + +static class Arts +{ + static Model assets_model = Model.FromFile("meshes/assets.glb", Shader.Unlit); + static Dictionary meshes = new(); + static Material mat_mono = new Material("mono.hlsl"); + static Material mat_unlit = new Material("unlit.hlsl"); + static Material mat_backbox = new Material("backbox.hlsl"); + + static Quat food_ori = Quat.Identity; + + public static void Init() + { + foreach (ModelNode node in assets_model.Nodes) + { + if (node.Mesh != null) + { + meshes.Add(node.Name, node.Mesh); + } + } + + mat_backbox.Transparency = Transparency.Add; + mat_backbox.FaceCull = Cull.Front; + } + + public static void Frame() + { + // background + if (Device.DisplayBlend == DisplayBlend.Opaque) + { + + } + + // fullstick + Mesh.Sphere.Draw( + mat_unlit, + Matrix.TS( + Mono.r_con_stick.position, + 5 * U.mm + ), + Color.White + ); + Lines.Add( + Mono.r_con_stick.position + V.XYZ(0, 0, 0), + Mono.r_con_stick.position + Mono.fullstick * U.cm, + Color.White, + 2 * U.mm + ); + + // box + Hierarchy.Push(Mono.box_pose.ToMatrix(Mono.box_scale)); + meshes["InsideOut"].Draw(mat_unlit, Matrix.Identity); + meshes["Corrugation"].Draw(mat_unlit, Matrix.Identity); + meshes["Corrugation"].Draw(mat_backbox, Matrix.Identity); + + // snake + float fall_t = 0.0f; + if (Mono.snake_fall.state) + { + // if (Mono.snake_fall.delta == +1) // start falling + fall_t = 1.0f - (float)Mono.step_t; + } + Vec3 fall = V.XYZ(0, fall_t, 0); + bool food_next = (Mono.snake[0] + Mono.snake_dir) == Mono.food; + if (!food_next) + { + meshes["Tongue"].Draw( + mat_mono, + Matrix.TRS( + Mono.snake[0].ToVec3 + fall, + Quat.LookDir(Mono.fullstick), + V.XYZ(1, 1, 0.666f + Maths.smooth_stop((float)Mono.step_t) * 0.333f) + ) + ); + } + + string face = food_next ? "Face1Eat" : "Face0Default"; + meshes[face].Draw( + mat_mono, + Matrix.TRS( + Mono.snake[0].ToVec3 + fall, + Quat.LookDir(Mono.snake_dir.ToVec3), + V.XYZ(1, 1, 1)// * (float)(Mono.step_t)) + ) + ); + + for (int i = 1; i < Mono.snake_len; i++) + { + float scale = 1.0f; + if ((int)((Time.Total - Mono.eat_timestamp) * Mono.snake_len / Mono.step_step) == i) + { + scale = 1.1f; + } + meshes["Segment"].Draw( + mat_mono, + Matrix.TRS( + Mono.snake[i].ToVec3 + fall, + Quat.LookAt(Mono.snake[i].ToVec3, Mono.snake[i - 1].ToVec3), + scale + ) + ); + } + + // holes + foreach (KeyValuePair hole in Mono.holes) + { + meshes["Hole"].Draw( + mat_unlit, + Matrix.TRS( + hole.Key.ToVec3, + Quat.LookDir(hole.Value.ToVec3), + 1 + ) + ); + } + + + // food + float food_fall_t = 0.0f; + if (Mono.food_fall.state) + { + // if (Mono.food_fall.delta == +1) // start falling + food_fall_t = 1.0f - (float)Mono.step_t; + } + if (!food_next) + { + food_ori *= Quat.FromAngles( + 90 * Time.Stepf, + 30 * Time.Stepf, + 10 * Time.Stepf + ); + } + meshes["Food"].Draw( + mat_mono, + Matrix.TR( + Mono.food.ToVec3 + V.XYZ(0, food_fall_t, 0), + food_ori + ) + ); + + Hierarchy.Pop(); + + // for (int sx = -Mono.head_fill.Xslen; Mono.head_fill.InX(sx); sx++) + // { + // for (int sy = -Mono.head_fill.Yslen; Mono.head_fill.InY(sy); sy++) + // { + // for (int sz = -Mono.head_fill.Zslen; Mono.head_fill.InZ(sz); sz++) + // { + // Vec3 pos = Mono.box_pose.ToMatrix(U.cm) * V.XYZ(sx, sy, sz); + // Text.Add( + // (Mono.head_fill[new XYZi(sx, sy, sz)] + Mono.tail_fill[new XYZi(sx, sy, sz)]).ToString(), + // Matrix.TRS( + // pos, + // Quat.LookAt(pos, Input.Head.position), + // 0.1f + // ) + // ); + // } + // } + // } + } +} \ No newline at end of file diff --git a/Assets/backbox.hlsl b/Assets/backbox.hlsl new file mode 100644 index 0000000..cf59f51 --- /dev/null +++ b/Assets/backbox.hlsl @@ -0,0 +1,43 @@ +#include "stereokit.hlsli" + +//--name = dofdev/backbox +//--color:color = 1, 1, 1, 1 + +float4 color; + +struct vsIn { + float4 pos : SV_Position; + float3 norm : NORMAL0; + float4 col : COLOR0; +}; +struct psIn { + float4 pos : SV_POSITION; + float4 color : COLOR0; + uint view_id : SV_RenderTargetArrayIndex; +}; + +psIn vs(vsIn input, uint id : SV_InstanceID) { + psIn o; + o.view_id = id % sk_view_count; + id = id / sk_view_count; + + float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz; + o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]); + + float3 normal = normalize(mul(input.norm, (float3x3)sk_inst[id].world)); + + + float3 norm_shade = float3(0.5) + (normal * 0.5); + // float3 norm_shade = float3(0.5) + (norm_color * 0.5); + float3 view_dir = normalize(world - sk_camera_pos[o.view_id]); + // o.color = norm_shaed * clamp(dot(o.normal, -view_dir), 0, 0.2); + + // rim lighting + float rim = 1.0 - saturate(dot(normalize(view_dir), normal)); + o.color = float4(norm_shade * rim * rim * 0.2, 1); + + return o; +} +float4 ps(psIn input) : SV_TARGET { + return input.color; +} \ No newline at end of file diff --git a/Assets/floor.hlsl b/Assets/floor.hlsl new file mode 100644 index 0000000..8737363 --- /dev/null +++ b/Assets/floor.hlsl @@ -0,0 +1,53 @@ +#include + +//--name = app/floor + +//--color:color = 0,0,0,1 +float4 color; +//--radius = 5,10,0,0 +float4 radius; + +struct vsIn { + float4 pos : SV_POSITION; +}; +struct psIn { + float4 pos : SV_POSITION; + float4 world : TEXCOORD0; + uint view_id : SV_RenderTargetArrayIndex; +}; + +psIn vs(vsIn input, uint id : SV_InstanceID) { + psIn o; + o.view_id = id % sk_view_count; + id = id / sk_view_count; + + o.world = mul(input.pos, sk_inst[id].world); + o.pos = mul(o.world, sk_viewproj[o.view_id]); + + return o; +} +float4 ps(psIn input) : SV_TARGET{ + // This line algorithm is inspired by : + // http://madebyevan.com/shaders/grid/ + + // Minor grid lines + float2 step = 1 / fwidth(input.world.xz); + float2 grid = abs(frac(input.world.xz - 0.5) - 0.5) * step; // minor grid lines + float dist = sqrt(dot(input.world.xz, input.world.xz)); // transparency falloff + float size = min(grid.x, grid.y); + float val = 1.0 - min(size, 1.0); + val *= saturate(1 - ((dist - radius.x)/radius.y)); + + // Major axis lines + const float extraThickness = 0.5; + float2 axes = (abs(input.world.xz)) * step - extraThickness; + size = min(axes.x, axes.y); + float axisVal = 1.0 - min(size, 1.0); + axisVal *= saturate(1 - ((dist - radius.x*1.5)/(radius.y*1.5))); + + // combine, and drop transparent pixels + val = max(val*0.6, axisVal); + if (val <= 0) discard; + + return float4(color.rgb, val); +} \ No newline at end of file diff --git a/Assets/meshes/assets.glb b/Assets/meshes/assets.glb new file mode 100644 index 0000000..3ba69a5 --- /dev/null +++ b/Assets/meshes/assets.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03e6766296a916fbb3471e3601eb0ed0e2e82bda18b34bf5233116f2e2b80593 +size 405108 diff --git a/Assets/mono.hlsl b/Assets/mono.hlsl new file mode 100644 index 0000000..13eb797 --- /dev/null +++ b/Assets/mono.hlsl @@ -0,0 +1,48 @@ +#include "stereokit.hlsli" + +//--name = dofdev/mono +//--color:color = 1, 1, 1, 1 +//--tex_scale = 1 +//--diffuse = white + +float4 color; +float tex_scale; +Texture2D diffuse : register(t0); +SamplerState diffuse_s : register(s0); + +struct vsIn { + float4 pos : SV_Position; + float3 norm : NORMAL0; + float2 uv : TEXCOORD0; + float4 col : COLOR0; +}; +struct psIn { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float4 color : COLOR0; + uint view_id : SV_RenderTargetArrayIndex; +}; + +psIn vs(vsIn input, uint id : SV_InstanceID) { + psIn o; + o.view_id = id % sk_view_count; + id = id / sk_view_count; + + float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz; + o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]); + + float3 normal = normalize(mul(input.norm, (float3x3)sk_inst[id].world)); + + o.uv = input.uv * tex_scale; + float3 norm_color = float3(0.5) + (normal * 0.5); + float3 norm_shade = float3(0.5) + (norm_color * 0.5); + o.color = float4(norm_shade, 1) * input.col; // input.col * color * sk_inst[id].color; + return o; +} +float4 ps(psIn input) : SV_TARGET { + float4 col = diffuse.Sample(diffuse_s, input.uv); + + col = col * input.color; + + return col; +} \ No newline at end of file diff --git a/Assets/sfx/crisp_nom.mp3 b/Assets/sfx/crisp_nom.mp3 new file mode 100644 index 0000000..21544eb --- /dev/null +++ b/Assets/sfx/crisp_nom.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b13783430f48f61c656cdaf1616c58b372bf1c2a3299097dfef91c632bb6326 +size 6518 diff --git a/Assets/sfx/punch_through.mp3 b/Assets/sfx/punch_through.mp3 new file mode 100644 index 0000000..83886cb --- /dev/null +++ b/Assets/sfx/punch_through.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7997e0866066729c4784cc493ea7d3c01ddffee00b196b6607120cc0624bb84a +size 17294 diff --git a/Assets/unlit.hlsl b/Assets/unlit.hlsl new file mode 100644 index 0000000..9f698f2 --- /dev/null +++ b/Assets/unlit.hlsl @@ -0,0 +1,46 @@ +#include "stereokit.hlsli" + +//--name = dofdev/unlit +//--color:color = 1, 1, 1, 1 +//--tex_scale = 1 +//--diffuse = white + +float4 color; +float tex_scale; +Texture2D diffuse : register(t0); +SamplerState diffuse_s : register(s0); + +struct vsIn { + float4 pos : SV_Position; + float3 norm : NORMAL0; + float2 uv : TEXCOORD0; + float4 col : COLOR0; +}; +struct psIn { + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float4 color : COLOR0; + uint view_id : SV_RenderTargetArrayIndex; +}; + +psIn vs(vsIn input, uint id : SV_InstanceID) { + psIn o; + o.view_id = id % sk_view_count; + id = id / sk_view_count; + + float3 world = mul(float4(input.pos.xyz, 1), sk_inst[id].world).xyz; + o.pos = mul(float4(world, 1), sk_viewproj[o.view_id]); + + float3 normal = normalize(mul(input.norm, (float3x3)sk_inst[id].world)); + + o.uv = input.uv * tex_scale; + o.color = input.col * color * sk_inst[id].color; + return o; +} +float4 ps(psIn input) : SV_TARGET { + float4 col = diffuse.Sample(diffuse_s, input.uv); + + col = col * input.color; + + return col; +} \ No newline at end of file diff --git a/Maths.cs b/Maths.cs new file mode 100644 index 0000000..53e6dad --- /dev/null +++ b/Maths.cs @@ -0,0 +1,193 @@ +using System; + +namespace snake; + +public static class Maths +{ + public static int u_length(int s_len) => (1 + s_len) * 2; + public static int s_length(int u_len) => (u_len - 1) / 2; + + public static int u_index(int s_index, int s_len) => s_len + s_index; + public static int s_index(int u_index, int u_len) => u_index - u_len; + + public static float s_scalar(float u_scalar) => (u_scalar * 2.0f) - 1.0f; + public static float u_scalar(float s_scalar) => (1.0f + s_scalar) * 0.5f; + + public static int min(int a, int b) => Math.Min(a, b); + public static float min(float a, float b) => Math.Min(a, b); + public static double min(double a, double b) => Math.Min(a, b); + public static int max(int a, int b) => Math.Max(a, b); + public static float max(float a, float b) => Math.Max(a, b); + public static double max(double a, double b) => Math.Max(a, b); + + public static int abs(int x) => Math.Abs(x); + public static float abs(float x) => Math.Abs(x); + public static double abs(double x) => Math.Abs(x); + + public static int sign(float x) => x < 0 ? -1 : +1; + + public static float round(float x) => MathF.Round(x); + // public static int roundi(float value) => MathF.Round(value) + + public static float precision(float x, float p) + => round(x * p) / p; + + public static float smooth_start(float t) => (t * t * t); + public static float smooth_stop(float t) + { + float s = sign(t); + return (s - ((s - t) * (s - t) * (s - t))); + } + + public static float lerp(float a, float b, float t) => a + (b - a) * t; +} + +public class DeltaBool +{ + public int delta = 0; + public bool state = false; + + public DeltaBool(bool state) + { + this.state = state; + } + + public void Step(bool next_state) + { + delta = 0; + if (next_state != state) + { + delta = next_state ? +1 : -1; + state = next_state; + } + } +} + +public struct XYZi +{ + public int x { get; set; } + public int y { get; set; } + public int z { get; set; } + + public XYZi(int x, int y, int z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public static XYZi operator -(XYZi a, XYZi b) + => new(a.x - b.x, a.y - b.y, a.z - b.z); + public static XYZi operator +(XYZi a, XYZi b) + => new(a.x + b.x, a.y + b.y, a.z + b.z); + + public static bool operator ==(XYZi a, XYZi b) + => (a.x == b.x && a.y == b.y && a.z == b.z); + public static bool operator !=(XYZi a, XYZi b) + => (a.x != b.x || a.y != b.y || a.z != b.z); + public override bool Equals(object obj) + { + if (obj is XYZi other) + { + return this == other; + } + return false; + } + public override int GetHashCode() + => HashCode.Combine(x, y, z); + + public StereoKit.Vec3 ToVec3 + => StereoKit.V.XYZ(x, y, z); + + public override string ToString() + => string.Format("[{0:0.##}, {1:0.##}, {2:0.##}]", x, y, z); +} + +// public class SignedArray + +public class SpatialArray +{ + private T[,,] u_array; + private T default_value; + private int x_slen; // length past signed zero + private int y_slen; + private int z_slen; + public int Xslen => x_slen; + public int Yslen => y_slen; + public int Zslen => z_slen; + + public SpatialArray( + int x_slen, + int y_slen, + int z_slen, + T default_value = default(T) + ) + { + this.u_array = new T[ + Maths.u_length(x_slen), + Maths.u_length(y_slen), + Maths.u_length(z_slen) + ]; + this.x_slen = x_slen; + this.y_slen = y_slen; + this.z_slen = z_slen; + this.default_value = default_value; + + // initialize the arrays with the default value + Clear(default_value); + } + + public T this[XYZi sv] + { + get + { + if (InRange(sv)) + { + return u_array[ + Maths.u_index(sv.x, x_slen), + Maths.u_index(sv.y, y_slen), + Maths.u_index(sv.z, z_slen) + ]; + } + else + { + return default_value; + } + } + set + { + if (InRange(sv)) + { + u_array[ + Maths.u_index(sv.x, x_slen), + Maths.u_index(sv.y, y_slen), + Maths.u_index(sv.z, z_slen) + ] = value; + } + else + { + // throw new IndexOutOfRangeException("set index is out of range."); + } + } + } + + public bool InX(int sx) => !(Math.Abs(sx) > x_slen); + public bool InY(int sy) => !(Math.Abs(sy) > y_slen); + public bool InZ(int sz) => !(Math.Abs(sz) > z_slen); + public bool InRange(int sx, int sy, int sz) => InX(sx) && InY(sy) && InZ(sz); + public bool InRange(XYZi sv) => InRange(sv.x, sv.y, sv.z); + + public void Clear(T value) + { + for (int ux = 0; ux < u_array.GetLength(0); ux++) + { + for (int uy = 0; uy < u_array.GetLength(1); uy++) + { + for (int uz = 0; uz < u_array.GetLength(2); uz++) + { + u_array[ux, uy, uz] = value; + } + } + } + } +} \ No newline at end of file diff --git a/Mono.cs b/Mono.cs new file mode 100644 index 0000000..108fc19 --- /dev/null +++ b/Mono.cs @@ -0,0 +1,280 @@ +using System.Collections.Generic; +using StereoKit; + +namespace snake; + +static class Mono +{ + public static double step_step = 60.0 / 80; // 80|100|120 bpm + public static double step_time = 0.0; + public static double step_t = 0.0; + public static Pose box_pose = new(0, 0, -10 * U.cm); + public static float box_scale = 1.333f * U.cm; + + public const int SD_X = 2, SD_Y = 1, SD_Z = 2; + public static SpatialArray + s_array = new(SD_X, SD_Y, SD_Z, -1), + head_fill = new(SD_X, SD_Y, SD_Z, -1), + tail_fill = new(SD_X, SD_Y, SD_Z, -1); + + public static XYZi[] snake = new XYZi[ + Maths.u_length(SD_X) * Maths.u_length(SD_Y) * Maths.u_length(SD_Z) + ]; + public static int snake_len = 4; + public static int grow_buffer = 0; + public static Vec3 fullstick = Vec3.Up; + public static Pose r_con_stick = Pose.Identity; + public static XYZi snake_dir = new(0, 0, 1), last_snake_dir = new(0, 0, 1); + public static DeltaBool in_box = new(true); + public static DeltaBool snake_fall = new(false); + public static Dictionary holes = new(); + public static XYZi food = new(2, 0, 0); + public static DeltaBool food_fall = new(false); + public static double eat_timestamp = 0.0; + + static void PlayBox(this Sound sound, XYZi pos) + { + sound.Play(box_pose.ToMatrix(box_scale) * pos.ToVec3); + } + public static Sound sfx_crisp_nom = Sound.FromFile("sfx/crisp_nom.mp3"); + public static Sound sfx_punch_through = Sound.FromFile("sfx/punch_through.mp3"); + + public static void Init() + { + for (int i = 0; i < snake.Length; i++) + { + snake[i] = new XYZi(0, 1, 0); + } + } + + public static void Frame() + { + // flatscreen dev controls + if (Device.Name == "Simulator") + { + if (Input.Key(Key.MouseLeft).IsActive()) + { + float sx = Maths.s_scalar(Input.Mouse.pos.x / 640); + float ssx = Maths.smooth_start(sx); + box_pose.orientation *= Quat.FromAngles(0, sx * 180.0f * Time.Stepf, 0); + } + + if (Input.Key(Key.A).IsJustActive()) snake_dir = new(-1, 0, 0); + if (Input.Key(Key.S).IsJustActive()) snake_dir = new(+1, 0, 0); + if (Input.Key(Key.W).IsJustActive()) snake_dir = new(0, 0, -1); + if (Input.Key(Key.R).IsJustActive()) snake_dir = new(0, 0, +1); + if (Input.Key(Key.Shift).IsJustActive()) snake_dir = new(0, -1, 0); + if (Input.Key(Key.Space).IsJustActive()) snake_dir = new(0, +1, 0); + + fullstick = snake_dir.ToVec3; + } + else + { + Pose head = Input.Head; + box_pose.position = head.position + head.orientation * V.XYZ(0, -(SD_Y + 0.5f) * box_scale, -32 * U.cm); + + Hand r_hand = Input.Hand(Handed.Right); + Controller r_con = Input.Controller(Handed.Right); + bool con_tracked = r_con.trackedPos > TrackState.Lost; + Input.HandVisible(Handed.Max, !con_tracked); + if (con_tracked) + { + Vec2 stick = r_con.stick; + Quat stick_rot = Quat.FromAngles(stick.y * -90, 0, stick.x * +90); + float stick_sign = r_con.IsStickClicked ? -1 : +1; + r_con_stick = r_con.pose; + r_con_stick.position += r_con_stick.orientation * V.XYZ(0.0065f, -0.012f, -0.05f); + r_con_stick.orientation *= Quat.FromAngles(-50, 0, 0); + fullstick = r_con_stick.orientation * stick_rot * Vec3.Up * stick_sign; + + // Vec3 fullstick = r_hand.palm.orientation * Vec3.Up; + float ax = Maths.abs(fullstick.x); + float ay = Maths.abs(fullstick.y); + float az = Maths.abs(fullstick.z); + if (ax > ay && ax > az) snake_dir = new(Maths.sign(fullstick.x), 0, 0); + if (ay > ax && ay > az) snake_dir = new(0, Maths.sign(fullstick.y), 0); + if (az > ax && az > ay) snake_dir = new(0, 0, Maths.sign(fullstick.z)); + } + } + + // catch invalid direction and revert to last_dir + if (snake[0] + snake_dir == snake[1]) + { + snake_dir = last_snake_dir; + } + last_snake_dir = snake_dir; + } + + public static void Step() + { + if (s_array[snake[0] + snake_dir] > -1) + { + // lose condition + bool stuck = true; + for (int i = 0; i < directions.Length; i++) + { + if (s_array[snake[0] + directions[i]] == -1) + { + stuck = false; + } + } + if (stuck) + { + Log.Info("your stuck"); + } + return; + } + + if (snake_len == snake.Length) + { + // win condition + Log.Info("full snake"); + return; + } + else + { + if (grow_buffer > 0) + { + snake_len++; + grow_buffer--; + } + } + + // slither + for (int i = snake.Length - 1; i > 0; i--) + { + snake[i] = snake[i - 1]; + } + snake[0] += snake_dir; + + // gravity + bool grounded = false; + for (int i = 0; i < snake_len; i++) + { + if (snake[i].y == -SD_Y) + { + grounded = true; + break; + } + } + snake_fall.Step(!grounded); + if (snake_fall.state) + { + for (int i = 0; i < snake_len; i++) + { + snake[i] -= new XYZi(0, 1, 0); + } + } + + bool food_grounded = food.y == -SD_Y; + XYZi below_food = new XYZi(food.x, food.y - 1, food.z); + bool on_snake = s_array.InRange(below_food) && s_array[below_food] > -1; + food_fall.Step(!food_grounded && !on_snake); + if (food_fall.state) + { + food -= new XYZi(0, 1, 0); + } + + + in_box.Step(s_array.InRange(snake[0])); + if (in_box.delta != 0) // 1 just in -1 just out + { + holes.Add(snake[0], snake_dir); + sfx_punch_through.PlayBox(snake[0]); + } + if (holes.ContainsKey(snake[snake_len - 1])) + { + holes.Remove(snake[snake_len - 1]); + } + + s_array.Clear(-1); + for (int i = 0; i < snake_len; i++) + { + s_array[snake[i]] = i; + } + + // eat + if (food == snake[0]) + { + eat_timestamp = Time.Total; + grow_buffer += 3; + + Feed(); + sfx_crisp_nom.PlayBox(snake[0]); + } + } + + static void Feed() + { + head_fill.Clear(-1); + Gas(head_fill, snake[0]); + + // handle out of the box exception on tail + tail_fill.Clear(-1); + Gas(tail_fill, snake[snake_len - 1]); + + XYZi farthest_cell = new XYZi(0, 0, 0); + int farthest_dist = 0; + // [*] design configurable (we basically want it to be as far away as it can reasonably be) + const int max_dist = 6; + // [!] given how it loops over the space it directionally biases the results + for (int sx = -s_array.Xslen; s_array.InX(sx); sx++) + { + for (int sy = -s_array.Yslen; s_array.InY(sy); sy++) + { + for (int sz = -s_array.Zslen; s_array.InZ(sz); sz++) + { + XYZi sv = new XYZi(sx, sy, sz); + int dist = head_fill[sv]; + bool good_dist = dist > farthest_dist && dist < max_dist; + bool tail_access = tail_fill[sv] > 0; // help ensure completability + bool snake_free = s_array[sv] == -1; + if (good_dist && tail_access && snake_free) + { + farthest_dist = dist; + farthest_cell = sv; + } + } + } + } + + food = farthest_cell; + } + + // space fill algorithm + static void Gas(SpatialArray fill_array, XYZi sv) + { + Queue queue = new Queue(); + queue.Enqueue(sv); + fill_array[sv] = 0; + + while (queue.Count > 0) + { + XYZi _sv = queue.Dequeue(); + int currentDistance = fill_array[_sv]; + + // check all 4 directions + foreach (XYZi dir in directions) + { + XYZi newV = _sv + dir; + + if (fill_array.InRange(newV) && fill_array[newV] == -1 && s_array[newV] == -1) + { + fill_array[newV] = currentDistance + 1; + queue.Enqueue(newV); + } + } + } + } + + // directions for moving in the grid + static readonly XYZi[] directions = new XYZi[] + { + new XYZi(-1, 0, 0), // lft + new XYZi(+1, 0, 0), // rht + new XYZi(0, -1, 0), // dwn + new XYZi(0, +1, 0), // up + new XYZi(0, 0, -1), // fwd + new XYZi(0, 0, +1), // back + }; +} diff --git a/PassthroughFBExt.cs b/PassthroughFBExt.cs new file mode 100644 index 0000000..519b8c9 --- /dev/null +++ b/PassthroughFBExt.cs @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: MIT +// The authors below grant copyright rights under the MIT license: +// Copyright (c) 2024 Nick Klingensmith +// Copyright (c) 2024 Qualcomm Technologies, Inc. + +// This requires an addition to the Android Manifest to work on quest: +// +// And adding this to the application section can also improve the passthrough +// experience: +// +// +// To work on Quest+Link, you may need to enable beta features in the Oculus +// app's settings. + +using System; +using System.Runtime.InteropServices; + +namespace StereoKit.Framework +{ + class PassthroughFBExt : IStepper + { + bool extAvailable; + bool enabled; + bool enableOnInitialize; + XrPassthroughFB activePassthrough = new XrPassthroughFB(); + XrPassthroughLayerFB activeLayer = new XrPassthroughLayerFB(); + + Color oldColor; + bool oldSky; + + public bool Available => extAvailable; + public bool Enabled { get => enabled; set { + if (extAvailable == false || enabled == value) return; + if (value) + { + enabled = StartPassthrough(); + } + else + { + PausePassthrough(); + enabled = false; + } + } } + + public PassthroughFBExt() : this(true) { } + public PassthroughFBExt(bool enabled = true) + { + if (SK.IsInitialized) + Log.Err("PassthroughFBExt must be constructed before StereoKit is initialized!"); + Backend.OpenXR.RequestExt("XR_FB_passthrough"); + enableOnInitialize = enabled; + } + + public bool Initialize() + { + extAvailable = + Backend.XRType == BackendXRType.OpenXR && + Backend.OpenXR.ExtEnabled("XR_FB_passthrough") && + LoadBindings() && + InitPassthrough(); + + return true; + } + + public void Step() + { + if (Enabled == false) return; + + XrCompositionLayerPassthroughFB layer = new XrCompositionLayerPassthroughFB( + XrCompositionLayerFlags.BLEND_TEXTURE_SOURCE_ALPHA_BIT, activeLayer); + Backend.OpenXR.AddCompositionLayer(layer, -1); + } + + public void Shutdown() + { + if (!Enabled) return; + Enabled = false; + DestroyPassthrough(); + } + + bool InitPassthrough() + { + XrPassthroughFlagsFB flags = enableOnInitialize + ? XrPassthroughFlagsFB.IS_RUNNING_AT_CREATION_BIT_FB + : XrPassthroughFlagsFB.None; + + XrResult result = xrCreatePassthroughFB( + Backend.OpenXR.Session, + new XrPassthroughCreateInfoFB(flags), + out activePassthrough); + if (result != XrResult.Success) + { + Log.Err($"xrCreatePassthroughFB failed: {result}"); + return false; + } + + result = xrCreatePassthroughLayerFB( + Backend.OpenXR.Session, + new XrPassthroughLayerCreateInfoFB(activePassthrough, flags, XrPassthroughLayerPurposeFB.RECONSTRUCTION_FB), + out activeLayer); + if (result != XrResult.Success) + { + Log.Err($"xrCreatePassthroughLayerFB failed: {result}"); + return false; + } + + enabled = enableOnInitialize; + StartSky(); + return true; + } + + void DestroyPassthrough() + { + xrDestroyPassthroughLayerFB(activeLayer); + xrDestroyPassthroughFB(activePassthrough); + } + + bool StartPassthrough() + { + XrResult result = xrPassthroughStartFB(activePassthrough); + if (result != XrResult.Success) + { + Log.Err($"xrPassthroughStartFB failed: {result}"); + return false; + } + + result = xrPassthroughLayerResumeFB(activeLayer); + if (result != XrResult.Success) + { + Log.Err($"xrPassthroughLayerResumeFB failed: {result}"); + return false; + } + + StartSky(); + return true; + } + + void StartSky() + { + oldColor = Renderer.ClearColor; + oldSky = Renderer.EnableSky; + Renderer.ClearColor = Color.BlackTransparent; + Renderer.EnableSky = false; + } + + void PausePassthrough() + { + XrResult result = xrPassthroughLayerPauseFB(activeLayer); + if (result != XrResult.Success) + { + Log.Err($"xrPassthroughLayerPauseFB failed: {result}"); + return; + } + + result = xrPassthroughPauseFB(activePassthrough); + if (result != XrResult.Success) + { + Log.Err($"xrPassthroughPauseFB failed: {result}"); + return; + } + + Renderer.ClearColor = oldColor; + Renderer.EnableSky = oldSky; + } + + #region OpenXR native bindings and types + enum XrStructureType : UInt64 + { + XR_TYPE_PASSTHROUGH_CREATE_INFO_FB = 1000118001, + XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB = 1000118002, + XR_TYPE_PASSTHROUGH_STYLE_FB = 1000118020, + XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB = 1000118003, + } + enum XrPassthroughFlagsFB : UInt64 + { + None = 0, + IS_RUNNING_AT_CREATION_BIT_FB = 0x00000001, + LAYER_DEPTH_BIT_FB = 0x00000002 + } + enum XrCompositionLayerFlags : UInt64 + { + None = 0, + CORRECT_CHROMATIC_ABERRATION_BIT = 0x00000001, + BLEND_TEXTURE_SOURCE_ALPHA_BIT = 0x00000002, + UNPREMULTIPLIED_ALPHA_BIT = 0x00000004, + } + enum XrPassthroughLayerPurposeFB : UInt32 + { + RECONSTRUCTION_FB = 0, + PROJECTED_FB = 1, + TRACKED_KEYBOARD_HANDS_FB = 1000203001, + MAX_ENUM_FB = 0x7FFFFFFF, + } + enum XrResult : Int32 + { + Success = 0, + } + +#pragma warning disable 0169 // handle is not "used", but required for interop + [StructLayout(LayoutKind.Sequential)] struct XrPassthroughFB { ulong handle; } + [StructLayout(LayoutKind.Sequential)] struct XrPassthroughLayerFB { ulong handle; } +#pragma warning restore 0169 + + + [StructLayout(LayoutKind.Sequential)] + struct XrPassthroughCreateInfoFB + { + private XrStructureType type; + public IntPtr next; + public XrPassthroughFlagsFB flags; + + public XrPassthroughCreateInfoFB(XrPassthroughFlagsFB passthroughFlags) + { + type = XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_FB; + next = IntPtr.Zero; + flags = passthroughFlags; + } + } + [StructLayout(LayoutKind.Sequential)] + struct XrPassthroughLayerCreateInfoFB + { + private XrStructureType type; + public IntPtr next; + public XrPassthroughFB passthrough; + public XrPassthroughFlagsFB flags; + public XrPassthroughLayerPurposeFB purpose; + + public XrPassthroughLayerCreateInfoFB(XrPassthroughFB passthrough, XrPassthroughFlagsFB flags, XrPassthroughLayerPurposeFB purpose) + { + type = XrStructureType.XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB; + next = IntPtr.Zero; + this.passthrough = passthrough; + this.flags = flags; + this.purpose = purpose; + } + } + [StructLayout(LayoutKind.Sequential)] + struct XrPassthroughStyleFB + { + public XrStructureType type; + public IntPtr next; + public float textureOpacityFactor; + public Color edgeColor; + public XrPassthroughStyleFB(float textureOpacityFactor, Color edgeColor) + { + type = XrStructureType.XR_TYPE_PASSTHROUGH_STYLE_FB; + next = IntPtr.Zero; + this.textureOpacityFactor = textureOpacityFactor; + this.edgeColor = edgeColor; + } + } + [StructLayout(LayoutKind.Sequential)] + struct XrCompositionLayerPassthroughFB + { + public XrStructureType type; + public IntPtr next; + public XrCompositionLayerFlags flags; + public ulong space; + public XrPassthroughLayerFB layerHandle; + public XrCompositionLayerPassthroughFB(XrCompositionLayerFlags flags, XrPassthroughLayerFB layerHandle) + { + type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB; + next = IntPtr.Zero; + space = 0; + this.flags = flags; + this.layerHandle = layerHandle; + } + } + + delegate XrResult del_xrCreatePassthroughFB (ulong session, [In] XrPassthroughCreateInfoFB createInfo, out XrPassthroughFB outPassthrough); + delegate XrResult del_xrDestroyPassthroughFB (XrPassthroughFB passthrough); + delegate XrResult del_xrPassthroughStartFB (XrPassthroughFB passthrough); + delegate XrResult del_xrPassthroughPauseFB (XrPassthroughFB passthrough); + delegate XrResult del_xrCreatePassthroughLayerFB (ulong session, [In] XrPassthroughLayerCreateInfoFB createInfo, out XrPassthroughLayerFB outLayer); + delegate XrResult del_xrDestroyPassthroughLayerFB (XrPassthroughLayerFB layer); + delegate XrResult del_xrPassthroughLayerPauseFB (XrPassthroughLayerFB layer); + delegate XrResult del_xrPassthroughLayerResumeFB (XrPassthroughLayerFB layer); + delegate XrResult del_xrPassthroughLayerSetStyleFB(XrPassthroughLayerFB layer, [In] XrPassthroughStyleFB style); + + del_xrCreatePassthroughFB xrCreatePassthroughFB; + del_xrDestroyPassthroughFB xrDestroyPassthroughFB; + del_xrPassthroughStartFB xrPassthroughStartFB; + del_xrPassthroughPauseFB xrPassthroughPauseFB; + del_xrCreatePassthroughLayerFB xrCreatePassthroughLayerFB; + del_xrDestroyPassthroughLayerFB xrDestroyPassthroughLayerFB; + del_xrPassthroughLayerPauseFB xrPassthroughLayerPauseFB; + del_xrPassthroughLayerResumeFB xrPassthroughLayerResumeFB; + del_xrPassthroughLayerSetStyleFB xrPassthroughLayerSetStyleFB; + + bool LoadBindings() + { + xrCreatePassthroughFB = Backend.OpenXR.GetFunction ("xrCreatePassthroughFB"); + xrDestroyPassthroughFB = Backend.OpenXR.GetFunction ("xrDestroyPassthroughFB"); + xrPassthroughStartFB = Backend.OpenXR.GetFunction ("xrPassthroughStartFB"); + xrPassthroughPauseFB = Backend.OpenXR.GetFunction ("xrPassthroughPauseFB"); + xrCreatePassthroughLayerFB = Backend.OpenXR.GetFunction ("xrCreatePassthroughLayerFB"); + xrDestroyPassthroughLayerFB = Backend.OpenXR.GetFunction ("xrDestroyPassthroughLayerFB"); + xrPassthroughLayerPauseFB = Backend.OpenXR.GetFunction ("xrPassthroughLayerPauseFB"); + xrPassthroughLayerResumeFB = Backend.OpenXR.GetFunction ("xrPassthroughLayerResumeFB"); + xrPassthroughLayerSetStyleFB = Backend.OpenXR.GetFunction("xrPassthroughLayerSetStyleFB"); + + return + xrCreatePassthroughFB != null && + xrDestroyPassthroughFB != null && + xrPassthroughStartFB != null && + xrPassthroughPauseFB != null && + xrCreatePassthroughLayerFB != null && + xrDestroyPassthroughLayerFB != null && + xrPassthroughLayerPauseFB != null && + xrPassthroughLayerResumeFB != null && + xrPassthroughLayerSetStyleFB != null; + } + #endregion + } +} \ No newline at end of file diff --git a/Platforms/Android/AndroidManifest.xml b/Platforms/Android/AndroidManifest.xml new file mode 100644 index 0000000..feea22c --- /dev/null +++ b/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Platforms/Android/MainActivity.cs b/Platforms/Android/MainActivity.cs new file mode 100644 index 0000000..7da7335 --- /dev/null +++ b/Platforms/Android/MainActivity.cs @@ -0,0 +1,87 @@ +using Android.App; +using Android.Content; +using Android.Graphics; +using Android.OS; +using Android.Runtime; +using Android.Views; +using StereoKit; +using System; +using System.Reflection; +using System.Threading; + +namespace snake; + +[Activity(Label = "@string/app_name", MainLauncher = true, Exported = true)] +[IntentFilter(new[] { Intent.ActionMain }, Categories = new[] { "org.khronos.openxr.intent.category.IMMERSIVE_HMD", "com.oculus.intent.category.VR", Intent.CategoryLauncher })] +public class MainActivity : Activity, ISurfaceHolderCallback2 +{ + View surface; + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + Run(); + SetContentView(Resource.Layout.activity_main); + } + + protected override void OnDestroy() + { + SK.Quit(); + base.OnDestroy(); + } + + static bool running = false; + void Run() + { + if (running) return; + running = true; + + // Before anything else, give StereoKit the Activity. This should + // be set before any other SK calls, otherwise native library + // loading may fail. + SK.AndroidActivity = this; + + // Set up a surface for StereoKit to draw on, this is only really + // important for flatscreen experiences. + Window.TakeSurface(this); + Window.SetFormat(Format.Unknown); + surface = new View(this); + SetContentView(surface); + surface.RequestFocus(); + + // Task.Run will eat exceptions, but Thread.Start doesn't seem to. + new Thread(InvokeStereoKit).Start(); + } + + static void InvokeStereoKit() + { + Type entryClass = typeof(Program); + MethodInfo entryPoint = entryClass?.GetMethod("Main", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + // There are a number of potential method signatures for Main, so + // we need to check each one, and give it the correct values. + // + // Converting MethodInfo into an Action instead of calling Invoke on + // it allows for exceptions to properly bubble up to the IDE. + ParameterInfo[] entryParams = entryPoint?.GetParameters(); + if (entryParams == null || entryParams.Length == 0) + { + Action Program_Main = (Action)Delegate.CreateDelegate(typeof(Action), entryPoint); + Program_Main(); + } + else if (entryParams?.Length == 1 && entryParams[0].ParameterType == typeof(string[])) + { + Action Program_Main = (Action)Delegate.CreateDelegate(typeof(Action), entryPoint); + Program_Main(new string[] { }); + } + else throw new Exception("Couldn't invoke Program.Main!"); + + Process.KillProcess(Process.MyPid()); + } + + // Events related to surface state changes + public void SurfaceChanged (ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height) => SK.SetWindow(holder.Surface.Handle); + public void SurfaceCreated (ISurfaceHolder holder) => SK.SetWindow(holder.Surface.Handle); + public void SurfaceDestroyed (ISurfaceHolder holder) => SK.SetWindow(IntPtr.Zero); + public void SurfaceRedrawNeeded(ISurfaceHolder holder) { } +} \ No newline at end of file diff --git a/Platforms/Android/Resources/AboutResources.txt b/Platforms/Android/Resources/AboutResources.txt new file mode 100644 index 0000000..d2d3ddd --- /dev/null +++ b/Platforms/Android/Resources/AboutResources.txt @@ -0,0 +1,47 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.xml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.xml + + values/ + strings.xml + +For icons, a tool like https://icon.kitchen can be helpful to accelerate the +process of getting all the correct size variants! + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "Resource" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the Resource class would expose: + +public class Resource { + public class Drawable { + public const int icon = 0x123; + } + + public class Layout { + public const int main = 0x456; + } + + public class Strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use Resource.Drawable.icon to reference the drawable/icon.png file, or +Resource.Layout.main to reference the layout/main.xml file, or Resource.Strings.first_string +to reference the first string in the dictionary file values/strings.xml. \ No newline at end of file diff --git a/Platforms/Android/Resources/layout/activity_main.xml b/Platforms/Android/Resources/layout/activity_main.xml new file mode 100644 index 0000000..f949852 --- /dev/null +++ b/Platforms/Android/Resources/layout/activity_main.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/Platforms/Android/Resources/mipmap-anydpi-v26/appicon.xml b/Platforms/Android/Resources/mipmap-anydpi-v26/appicon.xml new file mode 100644 index 0000000..7751f69 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-anydpi-v26/appicon.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Platforms/Android/Resources/mipmap-anydpi-v26/appicon_round.xml b/Platforms/Android/Resources/mipmap-anydpi-v26/appicon_round.xml new file mode 100644 index 0000000..7751f69 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-anydpi-v26/appicon_round.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Platforms/Android/Resources/mipmap-hdpi/appicon_background.png b/Platforms/Android/Resources/mipmap-hdpi/appicon_background.png new file mode 100644 index 0000000..3b49ece --- /dev/null +++ b/Platforms/Android/Resources/mipmap-hdpi/appicon_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:001a5186cdbc0aa22d44f21c8f460c038cb3eecb001527edb6d2292ee793f98a +size 571 diff --git a/Platforms/Android/Resources/mipmap-hdpi/appicon_foreground.png b/Platforms/Android/Resources/mipmap-hdpi/appicon_foreground.png new file mode 100644 index 0000000..6e90911 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-hdpi/appicon_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8feb8d71289b44e6007064235c9c071b050361cd8f42fd669331b279746b220a +size 1526 diff --git a/Platforms/Android/Resources/mipmap-mdpi/appicon_background.png b/Platforms/Android/Resources/mipmap-mdpi/appicon_background.png new file mode 100644 index 0000000..f6efb10 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-mdpi/appicon_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a80d5c8958bc64ff34b20c02c739a1a6bc516c13e94d28e98181c2d45c7e7b7 +size 390 diff --git a/Platforms/Android/Resources/mipmap-mdpi/appicon_foreground.png b/Platforms/Android/Resources/mipmap-mdpi/appicon_foreground.png new file mode 100644 index 0000000..cfa7cf0 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-mdpi/appicon_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32eaa8cf1d7363736563c176efc84a99c6b2b75216e7f40b56f66d6e65386bcc +size 1040 diff --git a/Platforms/Android/Resources/mipmap-xhdpi/appicon_background.png b/Platforms/Android/Resources/mipmap-xhdpi/appicon_background.png new file mode 100644 index 0000000..46270d0 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-xhdpi/appicon_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dddcb9cbdd733c23a1e6d180dc253a73c077915961b704dff43d861060ce746a +size 741 diff --git a/Platforms/Android/Resources/mipmap-xhdpi/appicon_foreground.png b/Platforms/Android/Resources/mipmap-xhdpi/appicon_foreground.png new file mode 100644 index 0000000..e3af9b7 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-xhdpi/appicon_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cfcbd53ddc0d532a587ef5047701e815c1dd01e09334ea410075e98a6749182 +size 2013 diff --git a/Platforms/Android/Resources/mipmap-xxhdpi/appicon_background.png b/Platforms/Android/Resources/mipmap-xxhdpi/appicon_background.png new file mode 100644 index 0000000..9cf42b1 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-xxhdpi/appicon_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8db3b1755c49b6c38474fe970f50d125d95389007ebe3f93e3e63f4fa94d13b2 +size 1079 diff --git a/Platforms/Android/Resources/mipmap-xxhdpi/appicon_foreground.png b/Platforms/Android/Resources/mipmap-xxhdpi/appicon_foreground.png new file mode 100644 index 0000000..9b0abe6 --- /dev/null +++ b/Platforms/Android/Resources/mipmap-xxhdpi/appicon_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70b729b17de042fcaaa58dccc414b7e710ffab97ac926ab02660ecadd8dd682c +size 2937 diff --git a/Platforms/Android/Resources/mipmap-xxxhdpi/appicon_background.png b/Platforms/Android/Resources/mipmap-xxxhdpi/appicon_background.png new file mode 100644 index 0000000..c62e37d --- /dev/null +++ b/Platforms/Android/Resources/mipmap-xxxhdpi/appicon_background.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db83004ecbfef7d636546b76352d4e038fc96177ad063be371a81be12c5ef07b +size 1767 diff --git a/Platforms/Android/Resources/mipmap-xxxhdpi/appicon_foreground.png b/Platforms/Android/Resources/mipmap-xxxhdpi/appicon_foreground.png new file mode 100644 index 0000000..ff196bf --- /dev/null +++ b/Platforms/Android/Resources/mipmap-xxxhdpi/appicon_foreground.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1970b876622d57d14e58db2d3229ee3f46111b4458168b97ae24a0dd6a06d860 +size 3911 diff --git a/Platforms/Android/Resources/values/ic_launcher_background.xml b/Platforms/Android/Resources/values/ic_launcher_background.xml new file mode 100644 index 0000000..6ec24e6 --- /dev/null +++ b/Platforms/Android/Resources/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #2C3E50 + \ No newline at end of file diff --git a/Platforms/Android/Resources/values/strings.xml b/Platforms/Android/Resources/values/strings.xml new file mode 100644 index 0000000..43a0ae5 --- /dev/null +++ b/Platforms/Android/Resources/values/strings.xml @@ -0,0 +1,4 @@ + + snake + snake + diff --git a/Platforms/Net/App.ico b/Platforms/Net/App.ico new file mode 100644 index 0000000000000000000000000000000000000000..dbd447ea03b12300a593ae2d20b788b9e8b34199 GIT binary patch literal 112163 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 literal 0 HcmV?d00001 diff --git a/Platforms/Net/SKApp.ico b/Platforms/Net/SKApp.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c45461e29529a50d5027e54bfbe231b72678b31 GIT binary patch literal 38982 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 F{{iC9hwA_U literal 0 HcmV?d00001 diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..8413b20 --- /dev/null +++ b/Program.cs @@ -0,0 +1,58 @@ +using StereoKit; +using StereoKit.Framework; + + +namespace snake; + +class Program +{ + static void Main(string[] args) + { + SK.AddStepper(); + + // Initialize StereoKit + SKSettings settings = new SKSettings + { + appName = "snake", + assetsFolder = "Assets", + blendPreference = DisplayBlend.Blend, + // overlayApp = true, + // overlayPriority = 1, + depthMode = DepthMode.D32, + disableFlatscreenMRSim = true, + renderScaling = 2, + renderMultisample = 0, + }; + + if (!SK.Initialize(settings)) + return; + + Renderer.Scaling = 2; + // World.OcclusionEnabled = true; + // Device.DisplayBlend = DisplayBlend.Blend; + + Mono.Init(); + Arts.Init(); + + // Core application loop + SK.Run(() => + { + Mono.Frame(); + + // stepper + if (Time.Total > 3.0) + { + Mono.step_time += Time.Step; + Mono.step_t = Maths.min(Mono.step_time, Mono.step_step) / Mono.step_step; + } + if (Mono.step_time > Mono.step_step) + { + Mono.step_time -= Mono.step_step; + + Mono.Step(); + } + + Arts.Frame(); + }); + } +} \ No newline at end of file diff --git a/Projects/Android/snake.Android.csproj b/Projects/Android/snake.Android.csproj new file mode 100644 index 0000000..daf4b96 --- /dev/null +++ b/Projects/Android/snake.Android.csproj @@ -0,0 +1,50 @@ + + + net7.0-android + android-arm64 + 29 + Exe + com.dofdev.snake + 1 + 1.0 + snake + true + + Standard + ..\..\Assets + Assets + + + XA4211;XA1006;XA4301 + + + + + + + + + ..\..\Platforms\Android\AndroidManifest.xml + ..\..\Platforms\Android\Resources + ..\..\Platforms\Android\Assets + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android_icons.sh b/android_icons.sh new file mode 100755 index 0000000..8433855 --- /dev/null +++ b/android_icons.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Check if input file is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +input_file="$1" +# filename=$(basename "$input_file" .svg) +filename="appicon_foreground" +output_dir="Platforms/Android/Resources" + +# DPI configurations +# Format: [directory_name, scale_percentage] +declare -A dpi_configs=( + ["mipmap-mdpi"]="100" + ["mipmap-hdpi"]="150" + ["mipmap-xhdpi"]="200" + ["mipmap-xxhdpi"]="300" + ["mipmap-xxxhdpi"]="400" +) + +# Check if required tools are installed +if ! command -v inkscape &> /dev/null; then + echo "Error: Inkscape is required but not installed. Install using:" + echo "sudo pacman -S inkscape" + exit 1 +fi + +# Create output directory structure +mkdir -p "$output_dir" +for dpi in "${!dpi_configs[@]}"; do + mkdir -p "$output_dir/$dpi" +done + +# Get base size - let's use 48px as default mdpi size for app icons +# You can adjust this base size as needed +BASE_SIZE=48 + +# Export for each DPI +for dpi in "${!dpi_configs[@]}"; do + scale=${dpi_configs[$dpi]} + # Calculate new size using pure bash arithmetic + new_size=$(( BASE_SIZE * scale / 100 )) + + echo "Converting for $dpi (scale: ${scale}%)" + + inkscape --export-type="png" \ + --export-filename="$output_dir/$dpi/${filename}.png" \ + --export-width="$new_size" \ + --export-height="$new_size" \ + "$input_file" +done + +echo "Conversion complete! Files exported to $output_dir/" \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..58030bb --- /dev/null +++ b/readme.md @@ -0,0 +1,49 @@ +[lynx r1 resource](https://github.com/technobaboo/stereokit_lynx_dotnet_template) +[fb passthrough resource](https://www.aesiio.com/blog/passthrough-dot-net-core) +[fb passthrough tool source](https://github.com/StereoKit/StereoKit/blob/master/Examples/StereoKitTest/Tools/PassthroughFBExt.cs) + +```shell +# track lfs +git lfs track "*.mp3" +git lfs track "*.glb" +git lfs track "*.png" + +ffmpeg -i input.ogg output.wav + +# update icon from svg +magick -background none -density 256x256 Raw/icon.svg -define icon:auto-resize=256,128,64,48,32,16 Platforms/Net/App.ico +./android_icons.sh Raw/icon.svg + +adb devices +adb tcpip 5555 +adb shell ip -f inet addr show wlan0 +adb connect 192.168.1.219 + +# publish build +dotnet publish -c Release Projects/Android/snake.Android.csproj + +# stream install +adb install Projects/Android/bin/Release/net7.0-android/com.dofdev.snake-Signed.apk + +# debug shell +adb shell monkey -p com.dofdev.snake 1 + +# install a specific android platform +sdkmanager "platforms;android-33" "build-tools;33.0.0" +``` + +``` +todo + track steps out of box + if > x then snake get's pulled/slips out of the box + + check to see how far out of box (with some warning) + if (too far out || more segments out of box than in)than slip out of box from gravity + if going up slipping out due to gravity doesn't make sense so + just prevent the snake from going too far up instead (soft pause)? + + this means gravity could affect things even when in box + and breaking through the box and coiling up on yourself allows you to fill the space strategically + the food could even be affected by gravity so everything kind of starts of 2d + and 3d feels like an awesome treat/clever trick the user can play rather than an overwhelming paradigm shift +``` \ No newline at end of file diff --git a/snake.csproj b/snake.csproj new file mode 100644 index 0000000..13d68ea --- /dev/null +++ b/snake.csproj @@ -0,0 +1,25 @@ + + + net7.0 + Exe + + Platforms\Net\App.ico + + + + + + + + + + + + + + + + + + \ No newline at end of file