init commit after fatal local git file corruption
This commit is contained in:
parent
08406de6fd
commit
14d9add9e6
36 changed files with 1639 additions and 0 deletions
166
Arts.cs
Normal file
166
Arts.cs
Normal file
|
@ -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<string, Mesh> 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<XYZi, XYZi> 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
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
43
Assets/backbox.hlsl
Normal file
43
Assets/backbox.hlsl
Normal file
|
@ -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;
|
||||||
|
}
|
53
Assets/floor.hlsl
Normal file
53
Assets/floor.hlsl
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#include <stereokit.hlsli>
|
||||||
|
|
||||||
|
//--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);
|
||||||
|
}
|
BIN
Assets/meshes/assets.glb
(Stored with Git LFS)
Normal file
BIN
Assets/meshes/assets.glb
(Stored with Git LFS)
Normal file
Binary file not shown.
48
Assets/mono.hlsl
Normal file
48
Assets/mono.hlsl
Normal file
|
@ -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;
|
||||||
|
}
|
BIN
Assets/sfx/crisp_nom.mp3
(Stored with Git LFS)
Normal file
BIN
Assets/sfx/crisp_nom.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Assets/sfx/punch_through.mp3
(Stored with Git LFS)
Normal file
BIN
Assets/sfx/punch_through.mp3
(Stored with Git LFS)
Normal file
Binary file not shown.
46
Assets/unlit.hlsl
Normal file
46
Assets/unlit.hlsl
Normal file
|
@ -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;
|
||||||
|
}
|
193
Maths.cs
Normal file
193
Maths.cs
Normal file
|
@ -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<T>
|
||||||
|
|
||||||
|
public class SpatialArray<T>
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
280
Mono.cs
Normal file
280
Mono.cs
Normal file
|
@ -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<int>
|
||||||
|
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<XYZi, XYZi> 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<int> fill_array, XYZi sv)
|
||||||
|
{
|
||||||
|
Queue<XYZi> queue = new Queue<XYZi>();
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
315
PassthroughFBExt.cs
Normal file
315
PassthroughFBExt.cs
Normal file
|
@ -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:
|
||||||
|
// <uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true" />
|
||||||
|
// And adding this to the application section can also improve the passthrough
|
||||||
|
// experience:
|
||||||
|
// <meta-data android:name="com.oculus.ossplash.background" android:value="passthrough-contextual"/>
|
||||||
|
//
|
||||||
|
// 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<del_xrCreatePassthroughFB> ("xrCreatePassthroughFB");
|
||||||
|
xrDestroyPassthroughFB = Backend.OpenXR.GetFunction<del_xrDestroyPassthroughFB> ("xrDestroyPassthroughFB");
|
||||||
|
xrPassthroughStartFB = Backend.OpenXR.GetFunction<del_xrPassthroughStartFB> ("xrPassthroughStartFB");
|
||||||
|
xrPassthroughPauseFB = Backend.OpenXR.GetFunction<del_xrPassthroughPauseFB> ("xrPassthroughPauseFB");
|
||||||
|
xrCreatePassthroughLayerFB = Backend.OpenXR.GetFunction<del_xrCreatePassthroughLayerFB> ("xrCreatePassthroughLayerFB");
|
||||||
|
xrDestroyPassthroughLayerFB = Backend.OpenXR.GetFunction<del_xrDestroyPassthroughLayerFB> ("xrDestroyPassthroughLayerFB");
|
||||||
|
xrPassthroughLayerPauseFB = Backend.OpenXR.GetFunction<del_xrPassthroughLayerPauseFB> ("xrPassthroughLayerPauseFB");
|
||||||
|
xrPassthroughLayerResumeFB = Backend.OpenXR.GetFunction<del_xrPassthroughLayerResumeFB> ("xrPassthroughLayerResumeFB");
|
||||||
|
xrPassthroughLayerSetStyleFB = Backend.OpenXR.GetFunction<del_xrPassthroughLayerSetStyleFB>("xrPassthroughLayerSetStyleFB");
|
||||||
|
|
||||||
|
return
|
||||||
|
xrCreatePassthroughFB != null &&
|
||||||
|
xrDestroyPassthroughFB != null &&
|
||||||
|
xrPassthroughStartFB != null &&
|
||||||
|
xrPassthroughPauseFB != null &&
|
||||||
|
xrCreatePassthroughLayerFB != null &&
|
||||||
|
xrDestroyPassthroughLayerFB != null &&
|
||||||
|
xrPassthroughLayerPauseFB != null &&
|
||||||
|
xrPassthroughLayerResumeFB != null &&
|
||||||
|
xrPassthroughLayerSetStyleFB != null;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
55
Platforms/Android/AndroidManifest.xml
Normal file
55
Platforms/Android/AndroidManifest.xml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="32" />
|
||||||
|
|
||||||
|
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:label="@string/app_name" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true">
|
||||||
|
<!-- Oculus -->
|
||||||
|
<meta-data android:name="com.oculus.supportedDevices" android:value="quest|quest2|quest3|questpro"/>
|
||||||
|
<meta-data android:name="com.oculus.handtracking.version" android:value="V2.0"/>
|
||||||
|
<meta-data android:name="com.oculus.ossplash.background" android:value="passthrough-contextual"/>
|
||||||
|
<uses-native-library android:name="libopenxr_forwardloader.oculus.so" android:required="false"/>
|
||||||
|
<!-- Pico -->
|
||||||
|
<meta-data android:name="pvr.app.type" android:value="vr" />
|
||||||
|
<meta-data android:name="handtracking" android:value="1" />
|
||||||
|
<!-- Snapdragon -->
|
||||||
|
<meta-data android:name="spaces.version" android:value="0.15.0"/>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
|
||||||
|
<!-- Generic OpenXR items -->
|
||||||
|
<uses-feature android:name="android.hardware.vr.headtracking" android:required="false" android:version="1" />
|
||||||
|
<uses-permission android:name="org.khronos.openxr.permission.OPENXR_SYSTEM"/>
|
||||||
|
<uses-permission android:name="org.khronos.openxr.permission.OPENXR" />
|
||||||
|
<queries>
|
||||||
|
<provider android:authorities="org.khronos.openxr.runtime_broker;org.khronos.openxr.system_runtime_broker" />
|
||||||
|
<intent> <action android:name="org.khronos.openxr.OpenXRRuntimeService" /> </intent>
|
||||||
|
<intent> <action android:name="org.khronos.openxr.OpenXRApiLayerService" /> </intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<!-- Vive specific items -->
|
||||||
|
<uses-feature android:name="wave.feature.handtracking" android:required="false"/>
|
||||||
|
<uses-feature android:name="wave.feature.tracker" android:required="false"/>
|
||||||
|
|
||||||
|
<!-- Snapdragon specific items -->
|
||||||
|
<uses-permission android:name="com.qualcomm.qti.qxr.QXRServiceClientPermission" android:required="false"/>
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.qualcomm.qti.spaces.services" />
|
||||||
|
<package android:name="com.qualcomm.qti.openxrruntime" />
|
||||||
|
<intent> <action android:name="com.qualcomm.qti.openxr.spaces.intent.action.BIND" /> </intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<!-- Oculus specific items -->
|
||||||
|
<uses-permission android:name="com.oculus.permission.HAND_TRACKING"/>
|
||||||
|
<uses-permission android:name="com.oculus.permission.BODY_TRACKING"/>
|
||||||
|
<uses-permission android:name="com.oculus.permission.FACE_TRACKING"/>
|
||||||
|
<uses-permission android:name="com.oculus.permission.EYE_TRACKING"/>
|
||||||
|
<uses-feature android:name="com.oculus.feature.PASSTHROUGH" android:required="true"/>
|
||||||
|
<uses-feature android:name="oculus.software.handtracking" android:required="false"/>
|
||||||
|
<uses-feature android:name="com.oculus.software.body_tracking" android:required="false"/>
|
||||||
|
<uses-feature android:name="oculus.software.face_tracking" android:required="false"/>
|
||||||
|
<uses-feature android:name="oculus.software.eye_tracking" android:required="false"/>
|
||||||
|
</manifest>
|
87
Platforms/Android/MainActivity.cs
Normal file
87
Platforms/Android/MainActivity.cs
Normal file
|
@ -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<string[]> Program_Main = (Action<string[]>)Delegate.CreateDelegate(typeof(Action<string[]>), 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) { }
|
||||||
|
}
|
47
Platforms/Android/Resources/AboutResources.txt
Normal file
47
Platforms/Android/Resources/AboutResources.txt
Normal file
|
@ -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.
|
13
Platforms/Android/Resources/layout/activity_main.xml
Normal file
13
Platforms/Android/Resources/layout/activity_main.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:text="@string/app_text"
|
||||||
|
/>
|
||||||
|
</RelativeLayout>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/appicon_background" />
|
||||||
|
<foreground android:drawable="@mipmap/appicon_foreground" />
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@mipmap/appicon_background" />
|
||||||
|
<foreground android:drawable="@mipmap/appicon_foreground" />
|
||||||
|
</adaptive-icon>
|
BIN
Platforms/Android/Resources/mipmap-hdpi/appicon_background.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-hdpi/appicon_background.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-hdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-hdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-mdpi/appicon_background.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-mdpi/appicon_background.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-mdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-mdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-xhdpi/appicon_background.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-xhdpi/appicon_background.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-xhdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-xhdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-xxhdpi/appicon_background.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-xxhdpi/appicon_background.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-xxhdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-xxhdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-xxxhdpi/appicon_background.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-xxxhdpi/appicon_background.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Platforms/Android/Resources/mipmap-xxxhdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
BIN
Platforms/Android/Resources/mipmap-xxxhdpi/appicon_foreground.png
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#2C3E50</color>
|
||||||
|
</resources>
|
4
Platforms/Android/Resources/values/strings.xml
Normal file
4
Platforms/Android/Resources/values/strings.xml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">snake</string>
|
||||||
|
<string name="app_text">snake</string>
|
||||||
|
</resources>
|
BIN
Platforms/Net/App.ico
Normal file
BIN
Platforms/Net/App.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 110 KiB |
BIN
Platforms/Net/SKApp.ico
Normal file
BIN
Platforms/Net/SKApp.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
58
Program.cs
Normal file
58
Program.cs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
using StereoKit;
|
||||||
|
using StereoKit.Framework;
|
||||||
|
|
||||||
|
|
||||||
|
namespace snake;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
SK.AddStepper<PassthroughFBExt>();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
50
Projects/Android/snake.Android.csproj
Normal file
50
Projects/Android/snake.Android.csproj
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0-android</TargetFramework>
|
||||||
|
<RuntimeIdentifiers>android-arm64</RuntimeIdentifiers>
|
||||||
|
<SupportedOSPlatformVersion>29</SupportedOSPlatformVersion>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<ApplicationId>com.dofdev.snake</ApplicationId>
|
||||||
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||||
|
<RootNamespace>snake</RootNamespace>
|
||||||
|
<ReadOnlyProject>true</ReadOnlyProject>
|
||||||
|
|
||||||
|
<SKOpenXRLoader>Standard</SKOpenXRLoader>
|
||||||
|
<SKAssetFolder>..\..\Assets</SKAssetFolder>
|
||||||
|
<SKAssetDestination>Assets</SKAssetDestination>
|
||||||
|
|
||||||
|
<!--Suppress targetSdkVersion 29 warnings. XR devices use old versions
|
||||||
|
of Android.-->
|
||||||
|
<NoWarn>XA4211;XA1006;XA4301</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StereoKit" Version="0.3.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!--Point Android to the right folders, otherwise these default to the root
|
||||||
|
directory.-->
|
||||||
|
<PropertyGroup>
|
||||||
|
<AndroidManifest >..\..\Platforms\Android\AndroidManifest.xml</AndroidManifest>
|
||||||
|
<MonoAndroidResourcePrefix>..\..\Platforms\Android\Resources</MonoAndroidResourcePrefix>
|
||||||
|
<MonoAndroidAssetsPrefix >..\..\Platforms\Android\Assets</MonoAndroidAssetsPrefix>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\..\**\*.cs" Exclude="..\..\obj\**;..\..\bin\**;..\..\Projects\**" />
|
||||||
|
|
||||||
|
<!--Don't compile .NET Core code in this project.-->
|
||||||
|
<Compile Remove="..\..\**\*.Net.cs" />
|
||||||
|
<Compile Remove="..\..\Platforms\Net\**\*.cs" />
|
||||||
|
|
||||||
|
<!--Hide everything in this project, otherwise it's a lot of
|
||||||
|
duplication that creates noise.-->
|
||||||
|
<Compile Update="..\..\**" Visible="false" />
|
||||||
|
<None Update="..\..\**" Visible="false" />
|
||||||
|
<Content Update="..\..\**" Visible="false" />
|
||||||
|
|
||||||
|
<Compile Update="..\..\**\*.Android.cs" Visible="true" />
|
||||||
|
<Compile Update="..\..\Platforms\Android\**\*.cs" Visible="true" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
56
android_icons.sh
Executable file
56
android_icons.sh
Executable file
|
@ -0,0 +1,56 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if input file is provided
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: $0 <input.svg>"
|
||||||
|
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/"
|
49
readme.md
Normal file
49
readme.md
Normal file
|
@ -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
|
||||||
|
```
|
25
snake.csproj
Normal file
25
snake.csproj
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
|
||||||
|
<ApplicationIcon>Platforms\Net\App.ico</ApplicationIcon>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!--Don't compile Android code in this project, but do show it in the
|
||||||
|
explorer window.-->
|
||||||
|
<Compile Remove="**\*.Android.cs" />
|
||||||
|
<Compile Remove="Platforms\Android\**" />
|
||||||
|
<None Include="**\*.Android.cs" />
|
||||||
|
<None Include="Platforms\Android\**" />
|
||||||
|
|
||||||
|
<Compile Remove="Projects\**" />
|
||||||
|
<EmbeddedResource Remove="Projects\**" />
|
||||||
|
<None Remove="Projects\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StereoKit" Version="0.3.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
Loading…
Add table
Reference in a new issue