diff --git a/README.md b/README.md
index f45f9cfb..b0ec5893 100644
--- a/README.md
+++ b/README.md
@@ -451,6 +451,31 @@ Extended Permissions mode on device must be enabled in some of the projects to a
+## Community Samples
+
+
+
## Support
If you have any questions or need assistance, please don't hesitate to reach out. Our community is here to help, and you can connect with us and ask for support [here](https://www.reddit.com/r/Spectacles/). We look forward to hearing from you and are excited to assist you on your journey!
diff --git a/Spectacles-2-Unitree/.devcontainer/Dockerfile b/Spectacles-2-Unitree/.devcontainer/Dockerfile
new file mode 100644
index 00000000..e877d6a0
--- /dev/null
+++ b/Spectacles-2-Unitree/.devcontainer/Dockerfile
@@ -0,0 +1,96 @@
+FROM ubuntu:noble AS dev
+
+LABEL org.opencontainers.image.description="Spectacles-2-Unitree dev container"
+
+ARG UV_VERSION=0.6.2
+
+ENV CYCLONEDDS_HOME=/usr/local \
+ DEBIAN_FRONTEND=noninteractive \
+ PATH="/opt/conda/bin:${PATH}"
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ ca-certificates \
+ apt-transport-https \
+ curl \
+ gnupg \
+ # Needed for some Python dependencies that want the full non-headless version of OpenCV.
+ libgl1 \
+ libglib2.0-0 \
+ lsb-release \
+ python3.12 \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+RUN . /etc/lsb-release \
+ # Git LFS
+ && curl -fsSL https://packagecloud.io/github/git-lfs/gpgkey \
+ | gpg --dearmor -o /etc/apt/keyrings/github_git-lfs-archive-keyring.gpg \
+ && curl -fsSL "https://packagecloud.io/install/repositories/github/git-lfs/config_file.list?os=${DISTRIB_ID}&dist=${DISTRIB_CODENAME}" > /etc/apt/sources.list.d/github_git-lfs.list \
+ # GitHub CLI
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg -o /usr/share/keyrings/githubcli.gpg \
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli.gpg] https://cli.github.com/packages stable main" > /etc/apt/sources.list.d/github-cli.list \
+ # Docker
+ && curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list
+
+# Additional dev packages
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ build-essential \
+ cmake \
+ docker-buildx-plugin \
+ docker-ce-cli \
+ gcc-aarch64-linux-gnu \
+ gh \
+ git \
+ git-lfs \
+ htop \
+ iputils-ping \
+ jq \
+ less \
+ linux-tools-common \
+ linux-tools-generic \
+ micro \
+ net-tools \
+ openssl \
+ pipx \
+ python3-pip \
+ ssh \
+ unzip \
+ && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# Install act
+RUN --mount=type=cache,target=/root/.cache/act \
+ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/nektos/act/master/install.sh | bash
+
+# Install uv
+RUN pipx ensurepath && pipx install "uv==$UV_VERSION"
+
+# Install conda
+RUN curl -fsSL https://github.com/conda-forge/miniforge/releases/download/25.3.0-1/Miniforge3-25.3.0-1-Linux-aarch64.sh -o /tmp/miniforge.sh \
+ && chmod +x /tmp/miniforge.sh \
+ && /tmp/miniforge.sh -b -p /opt/conda \
+ && rm /tmp/miniforge.sh
+
+# Build CycloneDDS
+RUN git clone https://github.com/eclipse-cyclonedds/cyclonedds /tmp/cyclonedds \
+ && cd /tmp/cyclonedds \
+ # The unitree-sdk2py depends on CycloneDDS 0.10.2
+ && git checkout 0.10.2 \
+ && mkdir build && cd build \
+ && cmake -DCMAKE_INSTALL_PREFIX=/usr/local .. \
+ && cmake --build . --target install \
+ && cd / && rm -rf /tmp/cyclonedds
+
+SHELL ["/bin/bash", "-c"]
+
+COPY unitree-client/environment.yml environment.yml
+
+RUN source /opt/conda/etc/profile.d/conda.sh \
+ && conda env create -f environment.yml \
+ && conda clean -afy
+
+RUN source /opt/conda/etc/profile.d/conda.sh \
+ && conda activate unitree-client \
+ && pip install --no-cache-dir \
+ meshcat@git+https://github.com/TastyDucks/meshcat-python.git \
+ unitree_sdk2py@git+https://github.com/TastyDucks/unitree_sdk2_python.git \
+ cyclonedds==0.10.2
\ No newline at end of file
diff --git a/Spectacles-2-Unitree/.devcontainer/devcontainer.json b/Spectacles-2-Unitree/.devcontainer/devcontainer.json
new file mode 100644
index 00000000..49953d73
--- /dev/null
+++ b/Spectacles-2-Unitree/.devcontainer/devcontainer.json
@@ -0,0 +1,24 @@
+// For format details, see https://aka.ms/devcontainer.json.
+{
+ "name": "Spectacles-2-Unitree dev",
+ "build": {
+ "dockerfile": "Dockerfile",
+ "context": "..",
+ "options": [
+ "--platform=linux/arm64"
+ ]
+ },
+ "mounts": [
+ {
+ "source": "/var/run/docker.sock",
+ "target": "/var/run/docker.sock",
+ "type": "bind"
+ }
+ ],
+ "runArgs": [
+ "--platform=linux/arm64"
+ ],
+ "forwardPorts": [
+ 8080
+ ]
+}
\ No newline at end of file
diff --git a/Spectacles-2-Unitree/.gitattributes b/Spectacles-2-Unitree/.gitattributes
new file mode 100644
index 00000000..b5606a96
--- /dev/null
+++ b/Spectacles-2-Unitree/.gitattributes
@@ -0,0 +1,105 @@
+# Shipped by Lens Studio
+*.esproj text linguist-language=yaml
+
+Assets/**/* filter=lfs diff=lfs merge=lfs -text
+
+Assets/**/*.css !filter !diff !merge text
+Assets/**/*.glsl !filter !diff !merge text
+Assets/**/*.js !filter !diff !merge text
+Assets/**/*.json !filter !diff !merge text
+Assets/**/*.lensanim !filter !diff !merge text
+Assets/**/*.md !filter !diff !merge text
+Assets/**/*.rtf !filter !diff !merge text
+Assets/**/*.svg !filter !diff !merge text
+Assets/**/*.t3d !filter !diff !merge text
+Assets/**/*.ts !filter !diff !merge text
+Assets/**/*.txt !filter !diff !merge text
+Assets/**/*.ui !filter !diff !merge text
+Assets/**/*.yaml !filter !diff !merge text
+
+Assets/**/*.meta !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+
+Assets/**/*.animationAsset !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.animationCurveTrack !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.animationLayer !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.animationMixerLayer !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.audioOutput !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.bitmojiModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.bodyDepthTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.bodyInstanceSegmentationTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.bodyMesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.bodyNormalsTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.bodyTracking3D !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.cameraModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.cameraRawModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.cloudStorageModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.colocatedLandmarks2DMesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.colocatedLandmarks3DMesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.connectedLensModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.coreMLModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.depthStencilRenderTarget !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.depthTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.deviceCameraTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.deviceTrackingModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.dialogModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.faceCropTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.faceImagePickerTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.faceTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.faceTrackingModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.facelessTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.facemesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.filter !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.handTracking3D !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.imagePickerTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.imgmarker !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.internetModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.leaderBoardModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.licensedAudio !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.location !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.locationCloudStorageModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.locationTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.location_mesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.mapModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.mat !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.matter !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.mediaPickerTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.micaudio !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.microphoneRawModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.motionControllerModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.native !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.objectTrackingTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.prefab !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.processedLocationModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.proxyTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.rawLocationModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.rayTracingSettings !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.remoteMLAsset !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.remoteMediaModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.remoteReferenceAsset !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.remoteServiceModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.renderTarget !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.reverseCameraTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.scanModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.scene !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.scmarker !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.screenCropTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.screenTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.segmentationTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.shoppingModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.textInputModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.textTexture !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.textToSpeechModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.upperBodyMesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.upperBodyTracking3D !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.voiceMLModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.worldQueryModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.worldSettings !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.worldUnderstandingModule !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+Assets/**/*.worldmesh !filter !diff !merge text linguist-language=yaml merge=lens-studio-yaml-merge
+# Shipped by Lens Studio
+.png filter=lfs diff=lfs merge=lfs -text
+.jpg filter=lfs diff=lfs merge=lfs -text
+.gif filter=lfs diff=lfs merge=lfs -text
+
+# Unitree Client Meshes
+*.STL filter=lfs diff=lfs merge=lfs -text
diff --git a/Spectacles-2-Unitree/.github/workflows/build-and-push.yml b/Spectacles-2-Unitree/.github/workflows/build-and-push.yml
new file mode 100644
index 00000000..1714e4b0
--- /dev/null
+++ b/Spectacles-2-Unitree/.github/workflows/build-and-push.yml
@@ -0,0 +1,80 @@
+name: Build and Push Docker Images
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+permissions:
+ id-token: write
+ contents: read
+ packages: write
+env:
+ URL: ghcr.io/${{ github.repository }}
+jobs:
+ build-and-push:
+ permissions:
+ id-token: write
+ contents: read
+ packages: write
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ container:
+ - context: .
+ file: .devcontainer/Dockerfile
+ name: dev
+ platforms: linux/arm64
+ - context: unitree-client
+ file: unitree-client/Dockerfile
+ name: client
+ target: runtime
+ platforms: linux/arm64
+ - context: coordination-server
+ file: coordination-server/Dockerfile
+ name: server
+ platforms: linux/amd64
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ with:
+ lfs: true
+ - run: git lfs pull
+ - name: Log in to GitHub Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.URL }}.${{ matrix.container.name}}
+ flavor: |
+ latest=true
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=ref,event=tag
+ type=sha
+ - name: QEMU
+ uses: docker/setup-qemu-action@v3
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+ with:
+ version: latest
+ driver-opts: image=moby/buildkit:latest
+ - name: Build and Push
+ uses: docker/build-push-action@v6
+ with:
+ context: ${{ matrix.container.context }}
+ file: ${{ matrix.container.file }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ platforms: ${{ matrix.container.platforms }}
+ target: ${{ matrix.container.target }}
+ push: true
+ pull: true
+ cache-from: |
+ type=gha,scope=${{ matrix.container.name }}
+ type=registry,ref=${{ env.URL }}.${{ matrix.container.name }}:cache
+ cache-to: type=gha,mode=max,scope=${{ matrix.container.name }}
diff --git a/Spectacles-2-Unitree/.gitignore b/Spectacles-2-Unitree/.gitignore
new file mode 100644
index 00000000..2761cf3f
--- /dev/null
+++ b/Spectacles-2-Unitree/.gitignore
@@ -0,0 +1,16 @@
+# Shipped by Lens Studio
+Cache/**
+PluginsUserPreferences/**
+Support/**
+BackUp/**
+Workspaces/**
+.vscode/**
+jsconfig.json
+tsconfig.json
+.DS_Store
+*.lock
+# Shipped by Lens Studio
+# Python
+.venv
+*.egg-info
+__pycache__
diff --git a/Spectacles-2-Unitree/ATTRIBUTION.md b/Spectacles-2-Unitree/ATTRIBUTION.md
new file mode 100644
index 00000000..5132b697
--- /dev/null
+++ b/Spectacles-2-Unitree/ATTRIBUTION.md
@@ -0,0 +1,23 @@
+# Third Party License Information
+
+- This project uses Python source, STL, and URDF files from [Unitree](https://github.com/unitreerobotics/avp_teleoperate/blob/main/LICENSE), originally licensed under the Apache 2.0 License:
+ - [unitree-client/src/ik/urdf/](./unitree-client/src/ik/urdf/)
+ - [unitree-client/src/ik/utils/](./unitree-client/src/ik/utils/)
+ - [unitree-client/src/ik/g1_controller.py](./unitree-client/src/ik/g1_controller.py)
+ - [unitree-client/src/ik/g1_solver.py](./unitree-client/src/ik/g1_solver.py), modifications licensed under the [MIT License](./LICENSE).
+- Python dependencies are not included in the source code, and are gathered as part of the build process for the Docker images via PyPi and conda-forge:
+ - [aiohttp](https://github.com/aio-libs/aiohttp/blob/master/LICENSE.txt) (Apache 2.0 License)
+ - [aiohttp-jinja2](https://github.com/aio-libs/aiohttp-jinja2/blob/master/LICENSE) (Apache 2.0 License)
+ - [asyncio](https://github.com/python/cpython/blob/main/LICENSE) (Python Software Foundation License) - Part of Python Standard Library
+ - [casadi](https://github.com/casadi/casadi/blob/develop/LICENSE.txt) (LGPL v3)
+ - [ipython](https://github.com/ipython/ipython/blob/main/LICENSE) (BSD 3-Clause License)
+ - [jinja2](https://github.com/pallets/jinja/blob/main/LICENSE.txt) (BSD 3-Clause License)
+ - [mujoco](https://github.com/google-deepmind/mujoco/blob/main/LICENSE) (Apache 2.0 License)
+ - [numpy](https://github.com/numpy/numpy/blob/main/LICENSE.txt) (BSD 3-Clause License)
+ - [opencv-contrib-python-headless](https://github.com/opencv/opencv-python/blob/master/LICENSE.txt) (MIT)
+ - [pillow](https://github.com/python-pillow/Pillow/blob/main/LICENSE) (MIT-CMU License)
+ - [pinocchio](https://github.com/stack-of-tasks/pinocchio/blob/master/LICENSE) (BSD 2-Clause License)
+ - [pip](https://github.com/pypa/pip/blob/main/LICENSE.txt) (MIT License)
+ - [pyzmq](https://github.com/zeromq/pyzmq/blob/main/LICENSE.md) (BSD 3-Clause License)
+ - [scipy](https://github.com/scipy/scipy/blob/main/LICENSE.txt) (BSD 3-Clause License)
+ - [websockets](https://github.com/aaugustin/websockets/blob/main/LICENSE) (BSD 3-Clause License)
\ No newline at end of file
diff --git a/Spectacles-2-Unitree/Assets/Base.png b/Spectacles-2-Unitree/Assets/Base.png
new file mode 100644
index 00000000..1634b508
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Base.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cfd8423521180c6c746030558b626228586a382de0f6e6623ee8c5183804a3d
+size 1370
diff --git a/Spectacles-2-Unitree/Assets/Base.png.meta b/Spectacles-2-Unitree/Assets/Base.png.meta
new file mode 100644
index 00000000..16baa3d8
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Base.png.meta
@@ -0,0 +1,19 @@
+- !
+ ImportedAssetIds:
+ Texture: ! 093e6eaf-54cf-48bd-8d5a-346e6dea3dd7
+ ImporterName: FileTextureImporter
+ PrimaryAsset: ! 093e6eaf-54cf-48bd-8d5a-346e6dea3dd7
+ PackageType: NotAPackage
+ PackagePolicy: CannotBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ {}
+ DependentFiles:
+ []
+ ImporterSettings: !
+ {}
+ CompressionSettings: !
+ CompressorName: Performance
+ CompressionSettings: !
+ Level: Medium
diff --git a/Spectacles-2-Unitree/Assets/Device Camera Texture.deviceCameraTexture b/Spectacles-2-Unitree/Assets/Device Camera Texture.deviceCameraTexture
new file mode 100644
index 00000000..0d50bea0
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Device Camera Texture.deviceCameraTexture
@@ -0,0 +1,2 @@
+- !
+ PackagePath: ""
diff --git a/Spectacles-2-Unitree/Assets/Device Camera Texture.deviceCameraTexture.meta b/Spectacles-2-Unitree/Assets/Device Camera Texture.deviceCameraTexture.meta
new file mode 100644
index 00000000..67026e1c
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Device Camera Texture.deviceCameraTexture.meta
@@ -0,0 +1,17 @@
+- !
+ ImportedAssetIds:
+ Texture: ! 996c6f4e-b2ad-4117-9e3e-3db82e6e97f5
+ ImporterName: DeviceCameraTextureImporter
+ PrimaryAsset: ! 996c6f4e-b2ad-4117-9e3e-3db82e6e97f5
+ PackageType: NotAPackage
+ AssetDataMap:
+ {}
+ ImporterSettings: !
+ {}
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
+ PackagePolicy: CannotBeUnpacked
+ DependentFiles:
+ []
+ NativePackageRoot: ! 00000000-0000-0000-0000-000000000000
+ ExtraData:
+ {}
diff --git a/Spectacles-2-Unitree/Assets/Echopark.hdr b/Spectacles-2-Unitree/Assets/Echopark.hdr
new file mode 100644
index 00000000..3f52fab0
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Echopark.hdr
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7f718a37e096b94af7cd15a6c6142e7ec0386887b756b9cdced63f1fc71b38d4
+size 1495252
diff --git a/Spectacles-2-Unitree/Assets/Echopark.hdr.meta b/Spectacles-2-Unitree/Assets/Echopark.hdr.meta
new file mode 100644
index 00000000..72e7722c
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Echopark.hdr.meta
@@ -0,0 +1,18 @@
+- !
+ ImportedAssetIds:
+ Diffuse: ! afde1835-875a-4672-9a39-ba23cfd936bf
+ Specular: ! 2fda0d5e-3f7d-4d0d-a4b5-a911aa775787
+ ImporterName: EnvmapTextureImporter
+ PrimaryAsset: ! 00000000-0000-0000-0000-000000000000
+ PackageType: LegacyPackage
+ PackagePolicy: CanBeUnpacked
+ AssetDataMap:
+ {}
+ DependentFiles:
+ []
+ ImporterSettings: !
+ ImageQuality: Normal
+ SpecularImageResolution: Medium
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
+ ExtraData:
+ {}
diff --git a/Spectacles-2-Unitree/Assets/Image.mat b/Spectacles-2-Unitree/Assets/Image.mat
new file mode 100644
index 00000000..9753c5d8
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Image.mat
@@ -0,0 +1,73 @@
+- !
+ PackagePath: ""
+ PassesInfo:
+ - ! 8e5e83aa-575b-4d41-b93b-dc62968eb2f6
+- !
+ CachedProperties:
+ - Port_Default_N369:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ DepthWrite: false
+ DepthTest: false
+ DepthFunction: LessEqual
+ TwoSided: false
+ ColorMask: {x: true, y: true, z: true, w: true}
+ CullMode: Back
+ PolygonOffset: {x: 0.000000, y: 0.000000}
+ FrustumCulling: Auto
+ FrustumCullPad: 0.000000
+ InstanceCount: 1
+ Defines:
+ - NODE_13_DROPLIST_ITEM 0
+ - NODE_27_DROPLIST_ITEM 0
+ - NODE_49_DROPLIST_ITEM 0
+ - NODE_69_DROPLIST_ITEM 0
+ - ENABLE_BASE_TEX
+ BlendMode: PremultipliedAlphaAuto
+ Properties:
+ Port_Default_N204:
+ typeIdx: 1
+ value: 0.000000
+ PreviewEnabled:
+ typeIdx: 0
+ value: 0
+ baseColor:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ baseTex:
+ typeIdx: 9
+ value:
+ id: f7c0ecfc-f7ba-44b8-bd24-62337451798a
+ sampler:
+ filteringMode: Trilinear
+ wrapModeU: ClampToEdge
+ wrapModeV: ClampToEdge
+ wrapModeW: ClampToEdge
+ borderColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ mipmaps: false
+ type: Texture2D
+ baseTexBorderColor:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ baseTexDims:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ baseTexTransform:
+ typeIdx: 7
+ value:
+ 0: {x: 1.000000, y: 0.000000, z: 0.000000}
+ 1: {x: 0.000000, y: 1.000000, z: 0.000000}
+ 2: {x: 0.000000, y: 0.000000, z: 1.000000}
+ baseTexUvMinMax:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ overrideTimeDelta:
+ typeIdx: 1
+ value: 0.000000
+ overrideTimeElapsed:
+ typeIdx: 1
+ value: 0.000000
+ overrideTimeEnabled:
+ typeIdx: 0
+ value: 0
+ Pass: ! 11d16b8f-d694-4051-b9fb-fd4d097b0cb8
diff --git a/Spectacles-2-Unitree/Assets/Image.mat.meta b/Spectacles-2-Unitree/Assets/Image.mat.meta
new file mode 100644
index 00000000..3ac370ea
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Image.mat.meta
@@ -0,0 +1,16 @@
+- !
+ ImportedAssetIds:
+ Material: ! c412b735-9316-498c-9999-21f28326f2a7
+ ImporterName: MaterialImporter
+ PrimaryAsset: ! c412b735-9316-498c-9999-21f28326f2a7
+ PackageType: NotAPackage
+ PackagePolicy: CannotBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ {}
+ DependentFiles:
+ []
+ ImporterSettings: !
+ {}
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Base.png b/Spectacles-2-Unitree/Assets/Joystick/Base.png
new file mode 100644
index 00000000..1634b508
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Base.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5cfd8423521180c6c746030558b626228586a382de0f6e6623ee8c5183804a3d
+size 1370
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Base.png.meta b/Spectacles-2-Unitree/Assets/Joystick/Base.png.meta
new file mode 100644
index 00000000..3fc20ce2
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Base.png.meta
@@ -0,0 +1,19 @@
+- !
+ ImportedAssetIds:
+ Texture: ! 2b90fb2b-deb9-498e-8ff6-ce0dbf4ac26c
+ ImporterName: FileTextureImporter
+ PrimaryAsset: ! 2b90fb2b-deb9-498e-8ff6-ce0dbf4ac26c
+ PackageType: NotAPackage
+ PackagePolicy: CannotBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ {}
+ DependentFiles:
+ []
+ ImporterSettings: !
+ {}
+ CompressionSettings: !
+ CompressorName: Performance
+ CompressionSettings: !
+ Level: Medium
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Cylinder.fbx b/Spectacles-2-Unitree/Assets/Joystick/Cylinder.fbx
new file mode 100644
index 00000000..3d954f2f
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Cylinder.fbx
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:369e11b255c139f4129125b800b5133380e906707414f6184bdc6a7eddbebb24
+size 12396
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Cylinder.fbx.meta b/Spectacles-2-Unitree/Assets/Joystick/Cylinder.fbx.meta
new file mode 100644
index 00000000..d596dd72
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Cylinder.fbx.meta
@@ -0,0 +1,105 @@
+- !
+ ImportedAssetIds:
+ 0edc0b811f1d76e2: ! 651eea3e-e7e3-4658-8b56-3c94c1e58dff
+ 2fe651d50e3f5cdf: ! 3fbc4387-0b6b-4817-aba6-e17b55bc3221
+ 66780cf3a4dcb2b8: ! 81ae0752-a4e8-4a4c-80c4-a382860e8702
+ Meshes/Cylinder: ! c3a5c848-4468-48d2-9df0-0c9778bb7552
+ Scene: ! e3215c38-5e1f-4f04-a28b-fe123299612d
+ ImporterName: FbxImporter
+ PrimaryAsset: ! e3215c38-5e1f-4f04-a28b-fe123299612d
+ PackageType: LegacyPackage
+ PackagePolicy: CanBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ Scene: ! 0ec58c07-d94b-4133-ad47-ed91a948da9f
+ DependentFiles:
+ []
+ ImporterSettings: !
+ UseLegacyImporter: false
+ WithMaterials: true
+ NamingConvention: ByCategorySanitize
+ PersistentIdGenerationAlgorithm: DescriptorBased
+ AnimationCurveOptimization: true
+ CustomAnimationSamplingRate: 30.000000
+ ImportVertexColor: true
+ ImportTangentsAndBinormals: true
+ RegenerateTangentsAndBinormals: false
+ RegenerateNormalsForSkinnedMeshes: true
+ ImportBlendShapeAnimation: true
+ ImportVertexAnimation: true
+ GeneratePivots: true
+ TriangulateNURBS: true
+ UseLegacyFbxLoader: false
+ CompressionSettings: !
+ Presets:
+ None Compression: !
+ CompressorName: None
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
+ ExtensionMappings:
+ mesh: None Compression
+ Overrides:
+ {}
+- !
+ SourceRemaps:
+ - 0354ea87-c025-440c-9419-6dfc59b9ade2: 1699b635-28a8-420d-94d1-d7cadfa6adec
+ - 7aa1d913-8f98-4438-9126-310e5510a4f1: 775ed927-8a13-4568-a077-74787a09714c
+ - a40b3800-3ad6-4bd6-a686-13843fd6bef8: ef917935-7bb5-418a-95d7-16392ddbc33f
+ - e3215c38-5e1f-4f04-a28b-fe123299612d: 981f13d8-2cc6-4496-99b4-5e8f8d535c64
+ SourceState: ! 981f13d8-2cc6-4496-99b4-5e8f8d535c64
+- !
+ PrefabRemaps:
+ []
+ Name: component_0
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ []
+ Mesh: ! c3a5c848-4468-48d2-9df0-0c9778bb7552
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Cylinder
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: 0.000000, z: 0.000000}
+ rotation: {x: -90.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 100.000000, y: 100.000000, z: 100.000000}
+ hasSegmentScaleCompensation: false
+ Children:
+ []
+ Components:
+ - ! 1699b635-28a8-420d-94d1-d7cadfa6adec
+- !
+ PackagePath: ""
+ objects:
+ - ! ef917935-7bb5-418a-95d7-16392ddbc33f
+ LazyLoading: false
+ RetainAssets: true
+- !
+ PrefabRemaps:
+ []
+ Name: Cylinder
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: 0.000000, z: 0.000000}
+ rotation: {x: 0.000000, y: 0.000000, z: 0.000000}
+ scale: {x: 1.000000, y: 1.000000, z: 1.000000}
+ hasSegmentScaleCompensation: false
+ Children:
+ - ! 775ed927-8a13-4568-a077-74787a09714c
+ Components:
+ []
diff --git a/Spectacles-2-Unitree/Assets/Joystick/ImageMaterial.mat b/Spectacles-2-Unitree/Assets/Joystick/ImageMaterial.mat
new file mode 100644
index 00000000..4e3daa51
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/ImageMaterial.mat
@@ -0,0 +1,73 @@
+- !
+ PackagePath: ""
+ PassesInfo:
+ - ! 5542ac3f-c80d-43e5-a892-e6f199cfbe85
+- !
+ CachedProperties:
+ - Port_Default_N369:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ DepthWrite: true
+ DepthTest: true
+ DepthFunction: LessEqual
+ TwoSided: false
+ ColorMask: {x: true, y: true, z: true, w: true}
+ CullMode: Back
+ PolygonOffset: {x: 0.000000, y: 0.000000}
+ FrustumCulling: Auto
+ FrustumCullPad: 0.000000
+ InstanceCount: 1
+ Defines:
+ - ENABLE_BASE_TEX
+ - NODE_13_DROPLIST_ITEM 0
+ - NODE_27_DROPLIST_ITEM 0
+ - NODE_49_DROPLIST_ITEM 0
+ - NODE_69_DROPLIST_ITEM 0
+ BlendMode: PremultipliedAlphaAuto
+ Properties:
+ Port_Default_N204:
+ typeIdx: 1
+ value: 0.000000
+ PreviewEnabled:
+ typeIdx: 0
+ value: 0
+ baseColor:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 0.830000}
+ baseTex:
+ typeIdx: 9
+ value:
+ id: 4862c6b3-6c6b-4b6d-a323-1fbe4c6ce2a1
+ sampler:
+ filteringMode: Trilinear
+ wrapModeU: Repeat
+ wrapModeV: Repeat
+ wrapModeW: Repeat
+ borderColor: {x: 0.000000, y: 0.000000, z: 0.000000, w: 0.000000}
+ mipmaps: false
+ type: Texture2D
+ baseTexBorderColor:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ baseTexDims:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ baseTexTransform:
+ typeIdx: 7
+ value:
+ 0: {x: 1.000000, y: 0.000000, z: 0.000000}
+ 1: {x: 0.000000, y: 1.000000, z: 0.000000}
+ 2: {x: 0.000000, y: 0.000000, z: 1.000000}
+ baseTexUvMinMax:
+ typeIdx: 5
+ value: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ overrideTimeDelta:
+ typeIdx: 1
+ value: 0.000000
+ overrideTimeElapsed:
+ typeIdx: 1
+ value: 0.000000
+ overrideTimeEnabled:
+ typeIdx: 0
+ value: 0
+ Pass: ! ed11a32f-2415-41b3-8eac-cfb2cd729437
diff --git a/Spectacles-2-Unitree/Assets/Joystick/ImageMaterial.mat.meta b/Spectacles-2-Unitree/Assets/Joystick/ImageMaterial.mat.meta
new file mode 100644
index 00000000..f34286b3
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/ImageMaterial.mat.meta
@@ -0,0 +1,16 @@
+- !
+ ImportedAssetIds:
+ Material: ! dcf513cc-3b47-48eb-a7c8-1825fba52216
+ ImporterName: MaterialImporter
+ PrimaryAsset: ! dcf513cc-3b47-48eb-a7c8-1825fba52216
+ PackageType: NotAPackage
+ PackagePolicy: CannotBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ {}
+ DependentFiles:
+ []
+ ImporterSettings: !
+ {}
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Joystick.prefab b/Spectacles-2-Unitree/Assets/Joystick/Joystick.prefab
new file mode 100644
index 00000000..a9b40cde
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Joystick.prefab
@@ -0,0 +1,298 @@
+- !
+ PackagePath: ""
+ objects:
+ - ! a0d1c19f-3a8c-4c92-a02b-f115e89fb3ce
+ LazyLoading: false
+ RetainAssets: true
+- !
+ PrefabRemaps:
+ []
+ Name: Physics Body
+ Enabled: true
+ Shape: !
+ FitVisual: true
+ Radius: 6.000000
+ Matter: ! 00000000-0000-0000-0000-000000000000
+ Filter: ! 00000000-0000-0000-0000-000000000000
+ OverlapFilter: ! 00000000-0000-0000-0000-000000000000
+ WorldSettings: ! 00000000-0000-0000-0000-000000000000
+ EnableSmoothing: false
+ PositionTension: 30.000000
+ RotationTension: 30.000000
+ ForceCompound: false
+ Intangible: false
+ ShowCollider: false
+ Dynamic: false
+ BodySetting: Density
+ BodySettingValue: 1.000000
+ Damping: 0.000000
+ AngularDamping: 0.000000
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: 0.000000, z: 0.000000}
+ rotation: {x: 0.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 3.645104, y: 3.645104, z: 3.645104}
+ hasSegmentScaleCompensation: false
+ Children:
+ - ! c584c132-37b0-4945-831d-fe91dd59ef35
+ Components:
+ - ! fe131a8f-0219-45ea-ade8-fc0f1d7c48d2
+ - ! 22ce32c6-57f8-4526-9f66-6d7d2805d5ae
+ - ! 9f8f7a3f-199b-40b1-8c46-0869a8c8e457
+ - ! 4504672c-47d6-4351-b0f7-bdbcddeefd77
+ - ! e2719e11-8f89-4bdb-b462-7fc9315f7ec2
+ - ! ea29a1e8-1895-4e61-abfe-6ee850841dd1
+ - ! 4eba8cc3-7469-47be-9833-a8e502fe50e8
+- !
+ PrefabRemaps:
+ []
+ Name: Render Mesh Visual
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ - ! 67275e46-74cf-4d9e-a2fe-63594c108a55
+ Mesh: ! be44ec86-6509-4953-8e7d-046468e6b199
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ ScriptAsset: ! 9b59ee1c-abca-4e6b-9e2e-a9a5884bc389
+ ScriptInputsDefault:
+ moveSpeed: true
+ deadzone: true
+ cardinalWindowDegrees: true
+ client: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ joystickObject: ! 25a7a1ab-5e91-4626-8d76-670866dfa5a7
+ robotObject: ! 84bafc52-ea01-4f87-a412-867fae0effac
+ rightIndexFinger: ! d180e208-2df8-4507-8437-213a1fbd5da6
+ leftIndexFinger: ! 1ab3fbdf-e28e-45a8-943d-76938ac530b4
+ rightThumb: ! 1b6d2315-e870-4a05-9817-029318f16b5a
+ leftThumb: ! 60037818-559f-4345-ac44-f7c5dfc86f3c
+ moveSpeed: ! 0.000000
+ deadzone: 0.100000
+ cardinalWindowDegrees: ! 15.000000
+ client: ! 00000000-0000-0000-0000-000000000000
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Render Mesh Visual
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ - ! dcf513cc-3b47-48eb-a7c8-1825fba52216
+ Mesh: ! 198d4c8c-c021-43ce-a954-a22f1f606ca2
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Render Mesh Visual
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ - ! 67275e46-74cf-4d9e-a2fe-63594c108a55
+ Mesh: ! c3a5c848-4468-48d2-9df0-0c9778bb7552
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: InteractableManipulation
+ Enabled: true
+ ScriptAsset: ! 701e5d18-6a44-49ba-9cf8-a20c366c1439
+ ScriptInputsDefault:
+ minimumScaleFactor: true
+ maximumScaleFactor: true
+ enableTranslation: true
+ enableRotation: true
+ enableScale: true
+ enableStretchZ: true
+ showStretchZProperties: true
+ zStretchFactorMin: true
+ zStretchFactorMax: true
+ useFilter: true
+ showFilterProperties: true
+ minCutoff: true
+ beta: true
+ dcutoff: true
+ showTranslationProperties: true
+ _enableXTranslation: true
+ _enableYTranslation: true
+ _enableZTranslation: true
+ showRotationProperties: true
+ _rotationAxis: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ manipulateRootSceneObject: ! 25a7a1ab-5e91-4626-8d76-670866dfa5a7
+ minimumScaleFactor: 0.250000
+ maximumScaleFactor: ! 20.000000
+ enableTranslation: true
+ enableRotation: true
+ enableScale: true
+ enableStretchZ: true
+ showStretchZProperties: false
+ zStretchFactorMin: ! 1.000000
+ zStretchFactorMax: ! 12.000000
+ useFilter: true
+ showFilterProperties: false
+ minCutoff: ! 2.000000
+ beta: 0.015000
+ dcutoff: ! 1.000000
+ showTranslationProperties: false
+ _enableXTranslation: true
+ _enableYTranslation: true
+ _enableZTranslation: true
+ showRotationProperties: false
+ _rotationAxis: All
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 14.315620, y: -24.865599, z: -37.412357}
+ rotation: {x: 0.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 1.000000, y: 1.000000, z: 1.000000}
+ hasSegmentScaleCompensation: false
+ Children:
+ - ! 25a7a1ab-5e91-4626-8d76-670866dfa5a7
+ - ! e7a4755d-7eb1-47d7-b744-c8e4232212fa
+ Components:
+ []
+- !
+ PrefabRemaps:
+ []
+ Name: Cylinder
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: -0.760220, z: 0.000000}
+ rotation: {x: 90.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 0.100000, y: 0.100000, z: 0.250000}
+ hasSegmentScaleCompensation: false
+ Children:
+ []
+ Components:
+ - ! 94fb5544-22f6-43b0-a482-ee45d1056712
+- !
+ PrefabRemaps:
+ []
+ Name: InteractableOutlineFeedback
+ Enabled: true
+ ScriptAsset: ! c2e90e99-2305-4b10-a778-d72bf9b9adbd
+ ScriptInputsDefault:
+ hoveringColor: true
+ activatingColor: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ targetOutlineMaterial: ! d7ccef9e-ae51-4e26-9634-922fadb15639
+ hoveringColor: ! {x: 1.000000, y: 1.000000, z: 0.040000, w: 1.000000}
+ activatingColor: ! {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ outlineWeight: 0.025000
+ meshVisuals: !
+ - ! 4504672c-47d6-4351-b0f7-bdbcddeefd77
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: JoystickPlane
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: -3.230324, z: 0.000000}
+ rotation: {x: -90.000008, y: 0.000000, z: 0.000000}
+ scale: {x: 1.000000, y: 1.000000, z: 1.000000}
+ hasSegmentScaleCompensation: false
+ Children:
+ []
+ Components:
+ - ! 7bc670b3-df3f-44c6-ae49-73b4ea111638
+- !
+ PrefabRemaps:
+ []
+ Name: InteractableAudioFeedback
+ Enabled: true
+ ScriptAsset: ! c57fd4b4-b5d5-4eae-b45c-2f62c57e2bc3
+ ScriptInputsDefault:
+ {}
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ hoverAudioTrack: ! 0b4c9b86-170b-4692-ac74-668e4aa1af4e
+ triggerStartAudioTrack: ! d531ff43-b8df-4401-839c-645878c5bd2e
+ triggerEndAudioTrack: ! 7b58983a-a4ca-417f-8171-2a074e30c6d1
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Interactable
+ Enabled: true
+ ScriptAsset: ! c973d211-281b-4016-ae39-e12f3317bcae
+ ScriptInputsDefault:
+ targetingMode: true
+ allowMultipleInteractors: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ targetingMode: ! 3.000000
+ enableInstantDrag: true
+ allowMultipleInteractors: true
+ ScriptTypes:
+ "":
+ {}
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Joystick.prefab.meta b/Spectacles-2-Unitree/Assets/Joystick/Joystick.prefab.meta
new file mode 100644
index 00000000..2b15636c
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Joystick.prefab.meta
@@ -0,0 +1,331 @@
+- !
+ ImportedAssetIds:
+ ObjectPrefab: ! 8f266568-bb9b-448b-a04a-bf2fdc7f4444
+ ImporterName: ObjectPrefabImporter
+ PrimaryAsset: ! 8f266568-bb9b-448b-a04a-bf2fdc7f4444
+ PackageType: NotAPackage
+ PackagePolicy: CannotBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ ObjectPrefab: ! 34dbacad-f069-49f3-b92f-49aa929aba7f
+ DependentFiles:
+ []
+ ImporterSettings: !
+ {}
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
+- !
+ PrefabRemaps:
+ []
+ Name: JoystickPlane
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: -3.230324, z: 0.000000}
+ rotation: {x: -90.000008, y: 0.000000, z: 0.000000}
+ scale: {x: 1.000000, y: 1.000000, z: 1.000000}
+ hasSegmentScaleCompensation: false
+ Children:
+ []
+ Components:
+ - ! 9b6ea3e9-228c-4cd1-8ab6-9b61ad25092c
+- !
+ PrefabRemaps:
+ []
+ Name: InteractableManipulation
+ Enabled: true
+ ScriptAsset: ! 701e5d18-6a44-49ba-9cf8-a20c366c1439
+ ScriptInputsDefault:
+ minimumScaleFactor: true
+ maximumScaleFactor: true
+ enableTranslation: true
+ enableRotation: true
+ enableScale: true
+ enableStretchZ: true
+ showStretchZProperties: true
+ zStretchFactorMin: true
+ zStretchFactorMax: true
+ useFilter: true
+ showFilterProperties: true
+ minCutoff: true
+ beta: true
+ dcutoff: true
+ showTranslationProperties: true
+ _enableXTranslation: true
+ _enableYTranslation: true
+ _enableZTranslation: true
+ showRotationProperties: true
+ _rotationAxis: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ manipulateRootSceneObject: ! 9134f32e-3f21-4bf9-a3e8-86c7137c9119
+ minimumScaleFactor: 0.250000
+ maximumScaleFactor: ! 20.000000
+ enableTranslation: true
+ enableRotation: true
+ enableScale: true
+ enableStretchZ: true
+ showStretchZProperties: false
+ zStretchFactorMin: ! 1.000000
+ zStretchFactorMax: ! 12.000000
+ useFilter: true
+ showFilterProperties: false
+ minCutoff: ! 2.000000
+ beta: 0.015000
+ dcutoff: ! 1.000000
+ showTranslationProperties: false
+ _enableXTranslation: true
+ _enableYTranslation: true
+ _enableZTranslation: true
+ showRotationProperties: false
+ _rotationAxis: All
+ ScriptTypes:
+ "":
+ {}
+- !
+ SourceRemaps:
+ - 22ce32c6-57f8-4526-9f66-6d7d2805d5ae: b1f81d55-7d96-43c4-bd47-cea32d40b358
+ - 25a7a1ab-5e91-4626-8d76-670866dfa5a7: 9134f32e-3f21-4bf9-a3e8-86c7137c9119
+ - 4504672c-47d6-4351-b0f7-bdbcddeefd77: ded34553-30b8-4c11-95a2-8195103f8858
+ - 4eba8cc3-7469-47be-9833-a8e502fe50e8: 69b05356-b455-44c1-b3b5-9d84463d8230
+ - 7bc670b3-df3f-44c6-ae49-73b4ea111638: 9b6ea3e9-228c-4cd1-8ab6-9b61ad25092c
+ - 8f266568-bb9b-448b-a04a-bf2fdc7f4444: 37f349f8-5806-4c41-ada5-5a69dd788a76
+ - 94fb5544-22f6-43b0-a482-ee45d1056712: fb29ff3d-dcdd-48ca-b6bc-034179856966
+ - 9f8f7a3f-199b-40b1-8c46-0869a8c8e457: 1a18b875-55b3-4d25-ae27-71a9585a2776
+ - a0d1c19f-3a8c-4c92-a02b-f115e89fb3ce: 688b33b7-f62e-4041-a69b-597cc4a28ea1
+ - c584c132-37b0-4945-831d-fe91dd59ef35: 81c5793c-c420-4e4b-bc63-b5fc522ed368
+ - e2719e11-8f89-4bdb-b462-7fc9315f7ec2: 5bc7100c-5eca-4002-aac1-c3c6830ded4f
+ - e7a4755d-7eb1-47d7-b744-c8e4232212fa: 03fabfa6-e9bb-4b0f-ad43-2e93c6e76952
+ - ea29a1e8-1895-4e61-abfe-6ee850841dd1: d41cd63a-1d4e-4688-8b47-7688a65e004d
+ - fe131a8f-0219-45ea-ade8-fc0f1d7c48d2: fcb84540-6e86-461e-be43-ff2a1447061b
+ SourceState: ! 37f349f8-5806-4c41-ada5-5a69dd788a76
+- !
+ PackagePath: ""
+ objects:
+ - ! 688b33b7-f62e-4041-a69b-597cc4a28ea1
+ LazyLoading: false
+ RetainAssets: true
+- !
+ PrefabRemaps:
+ []
+ Name: InteractableOutlineFeedback
+ Enabled: true
+ ScriptAsset: ! c2e90e99-2305-4b10-a778-d72bf9b9adbd
+ ScriptInputsDefault:
+ hoveringColor: true
+ activatingColor: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ targetOutlineMaterial: ! d7ccef9e-ae51-4e26-9634-922fadb15639
+ hoveringColor: ! {x: 1.000000, y: 1.000000, z: 0.040000, w: 1.000000}
+ activatingColor: ! {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ outlineWeight: 0.025000
+ meshVisuals: !
+ - ! ded34553-30b8-4c11-95a2-8195103f8858
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 14.315620, y: -24.865599, z: -37.412357}
+ rotation: {x: 0.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 1.000000, y: 1.000000, z: 1.000000}
+ hasSegmentScaleCompensation: false
+ Children:
+ - ! 9134f32e-3f21-4bf9-a3e8-86c7137c9119
+ - ! 03fabfa6-e9bb-4b0f-ad43-2e93c6e76952
+ Components:
+ []
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ ScriptAsset: ! 9b59ee1c-abca-4e6b-9e2e-a9a5884bc389
+ ScriptInputsDefault:
+ moveSpeed: true
+ deadzone: true
+ cardinalWindowDegrees: true
+ client: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ joystickObject: ! 9134f32e-3f21-4bf9-a3e8-86c7137c9119
+ robotObject: ! 84bafc52-ea01-4f87-a412-867fae0effac
+ rightIndexFinger: ! d180e208-2df8-4507-8437-213a1fbd5da6
+ leftIndexFinger: ! 1ab3fbdf-e28e-45a8-943d-76938ac530b4
+ rightThumb: ! 1b6d2315-e870-4a05-9817-029318f16b5a
+ leftThumb: ! 60037818-559f-4345-ac44-f7c5dfc86f3c
+ moveSpeed: ! 0.000000
+ deadzone: 0.100000
+ cardinalWindowDegrees: ! 15.000000
+ client: ! 00000000-0000-0000-0000-000000000000
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Cylinder
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: -0.760220, z: 0.000000}
+ rotation: {x: 90.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 0.100000, y: 0.100000, z: 0.250000}
+ hasSegmentScaleCompensation: false
+ Children:
+ []
+ Components:
+ - ! fb29ff3d-dcdd-48ca-b6bc-034179856966
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 0.000000, y: 0.000000, z: 0.000000}
+ rotation: {x: 0.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 3.645104, y: 3.645104, z: 3.645104}
+ hasSegmentScaleCompensation: false
+ Children:
+ - ! 81c5793c-c420-4e4b-bc63-b5fc522ed368
+ Components:
+ - ! fcb84540-6e86-461e-be43-ff2a1447061b
+ - ! b1f81d55-7d96-43c4-bd47-cea32d40b358
+ - ! 1a18b875-55b3-4d25-ae27-71a9585a2776
+ - ! ded34553-30b8-4c11-95a2-8195103f8858
+ - ! 5bc7100c-5eca-4002-aac1-c3c6830ded4f
+ - ! d41cd63a-1d4e-4688-8b47-7688a65e004d
+ - ! 69b05356-b455-44c1-b3b5-9d84463d8230
+- !
+ PrefabRemaps:
+ []
+ Name: Render Mesh Visual
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ - ! dcf513cc-3b47-48eb-a7c8-1825fba52216
+ Mesh: ! 198d4c8c-c021-43ce-a954-a22f1f606ca2
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Physics Body
+ Enabled: true
+ Shape: !
+ FitVisual: true
+ Radius: 6.000000
+ Matter: ! 00000000-0000-0000-0000-000000000000
+ Filter: ! 00000000-0000-0000-0000-000000000000
+ OverlapFilter: ! 00000000-0000-0000-0000-000000000000
+ WorldSettings: ! 00000000-0000-0000-0000-000000000000
+ EnableSmoothing: false
+ PositionTension: 30.000000
+ RotationTension: 30.000000
+ ForceCompound: false
+ Intangible: false
+ ShowCollider: false
+ Dynamic: false
+ BodySetting: Density
+ BodySettingValue: 1.000000
+ Damping: 0.000000
+ AngularDamping: 0.000000
+- !
+ PrefabRemaps:
+ []
+ Name: InteractableAudioFeedback
+ Enabled: true
+ ScriptAsset: ! c57fd4b4-b5d5-4eae-b45c-2f62c57e2bc3
+ ScriptInputsDefault:
+ {}
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ hoverAudioTrack: ! 0b4c9b86-170b-4692-ac74-668e4aa1af4e
+ triggerStartAudioTrack: ! d531ff43-b8df-4401-839c-645878c5bd2e
+ triggerEndAudioTrack: ! 7b58983a-a4ca-417f-8171-2a074e30c6d1
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Render Mesh Visual
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ - ! 67275e46-74cf-4d9e-a2fe-63594c108a55
+ Mesh: ! be44ec86-6509-4953-8e7d-046468e6b199
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Render Mesh Visual
+ Enabled: true
+ RenderOrder: 0
+ StretchMode: Stretch
+ VerticalAlignment: Center
+ HorizontalAlignment: Center
+ MeshShadowMode: None
+ ShadowColor: {x: 1.000000, y: 1.000000, z: 1.000000, w: 1.000000}
+ ShadowDensity: 1.000000
+ Materials:
+ - ! 67275e46-74cf-4d9e-a2fe-63594c108a55
+ Mesh: ! c3a5c848-4468-48d2-9df0-0c9778bb7552
+ Skin: ! 00000000-0000-0000-0000-000000000000
+ BlendShapesEnabled: true
+ BlendNormals: false
+ BlendShapes:
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Interactable
+ Enabled: true
+ ScriptAsset: ! c973d211-281b-4016-ae39-e12f3317bcae
+ ScriptInputsDefault:
+ targetingMode: true
+ allowMultipleInteractors: true
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ targetingMode: ! 3.000000
+ enableInstantDrag: true
+ allowMultipleInteractors: true
+ ScriptTypes:
+ "":
+ {}
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Joystick.ts b/Spectacles-2-Unitree/Assets/Joystick/Joystick.ts
new file mode 100644
index 00000000..fb1e7db4
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Joystick.ts
@@ -0,0 +1,270 @@
+import { CoordinationClient, FORWARD_SPEED_MAX, LATERAL_SPEED_MAX, ROTATION_SPEED_MAX } from "../Unitree2Spectacles/Scripts/TS/CoordinationClient";
+
+@component
+export class Joystick extends BaseScriptComponent
+{
+ @input
+ joystickObject: SceneObject;
+ @input
+ robotObject: SceneObject;
+
+ @input
+ rightIndexFinger: SceneObject;
+ @input
+ leftIndexFinger: SceneObject;
+
+ @input
+ rightThumb: SceneObject;
+ @input
+ leftThumb: SceneObject;
+
+ @input
+ moveSpeed: number;
+
+ @input
+ deadzone: number = 0.1; // Small deadzone.
+
+ @input
+ cardinalWindowDegrees: number = 15; // 15° window for cardinal directions where movement is locked to one axis.
+
+ @input('Component.ScriptComponent')
+ client: CoordinationClient;
+
+ joystickTransform: Transform;
+ robotTransform: Transform;
+
+ rightIndexFingerTransform: Transform; //called "index-3"
+ leftIndexFingerTransform: Transform; //called "index-3"
+ rightThumbTransform: Transform; //called "thumb-3"
+ leftThumbTransform: Transform; //called "thumb-3"
+
+ rightHandPinching: Boolean;
+ leftHandPinching: Boolean;
+ rightHandPinchingPrev: Boolean;
+ leftHandPinchingPrev: Boolean;
+
+ startPosition: vec3;
+ rightPinchStartPosition: vec3;
+ leftPinchStartPosition: vec3;
+ rightPinchPosition: vec3;
+ leftPinchPosition: vec3;
+
+ pinchStartedOnJoystick: Boolean;
+
+ readonly maxSpeed: number = 5.0; // Maximum speed units per second
+
+ kicking: Boolean = false;
+
+ isEditor: Boolean;
+
+ jxSmoothed: number = 0;
+ jySmoothed: number = 0;
+
+ scale: number = 4; // Scale factor for clamping
+
+ //JOYSTICK POSITION VARIABLES
+ jx: number = 0;
+ jy: number = 0;
+
+ onAwake()
+ {
+ this.isEditor = typeof require === 'function';
+
+ print("is editor: " + this.isEditor);
+
+ this.joystickTransform = this.joystickObject.getTransform();
+ this.robotTransform = this.robotObject.getTransform();
+ this.rightIndexFingerTransform = this.rightIndexFinger.getTransform();
+ this.leftIndexFingerTransform = this.leftIndexFinger.getTransform();
+ this.rightThumbTransform = this.rightThumb.getTransform();
+ this.leftThumbTransform = this.leftThumb.getTransform();
+ this.startPosition = this.joystickTransform.getLocalPosition();
+ this.createEvent("UpdateEvent").bind(this.onUpdate.bind(this));
+ this.createEvent("LateUpdateEvent").bind(this.onLateUpdate.bind(this));
+ }
+
+ onUpdate()
+ {
+ // Check distance between right index finger and thumb
+ const rightFingerPos = this.rightIndexFingerTransform.getWorldPosition();
+ const rightThumbPos = this.rightThumbTransform.getWorldPosition();
+ const rightHandDistance = rightFingerPos.distance(rightThumbPos);
+
+ const leftFingerPos = this.leftIndexFingerTransform.getWorldPosition();
+ const leftThumbPos = this.leftThumbTransform.getWorldPosition();
+ const leftHandDistance = leftFingerPos.distance(leftThumbPos);
+
+ this.rightHandPinchingPrev = this.rightHandPinching;
+ this.leftHandPinchingPrev = this.leftHandPinching;
+ this.rightHandPinching = (rightHandDistance < 2);
+ this.leftHandPinching = (leftHandDistance < 2);
+
+ var justPinchedRightHand = false;
+ var justPinchedLeftHand = false;
+
+ if (this.rightHandPinching)
+ {
+ if (!this.rightHandPinchingPrev)
+ {
+ justPinchedRightHand = true;
+ // Calculate average point between right thumb and index finger
+ this.rightPinchStartPosition = new vec3(
+ (rightFingerPos.x + rightThumbPos.x) / 2,
+ (rightFingerPos.y + rightThumbPos.y) / 2,
+ (rightFingerPos.z + rightThumbPos.z) / 2
+ );
+ // Only set pinchStartedOnJoystick if pinch occurred near joystick
+ const joystickPos = this.joystickTransform.getWorldPosition();
+ const distanceToJoystick = this.rightPinchStartPosition.distance(joystickPos);
+ const grabRadius = 0.1; // Adjust this value as needed
+ this.pinchStartedOnJoystick = distanceToJoystick < grabRadius;
+ }
+
+ this.rightPinchPosition = new vec3(
+ (rightFingerPos.x + rightThumbPos.x) / 2,
+ (rightFingerPos.y + rightThumbPos.y) / 2,
+ (rightFingerPos.z + rightThumbPos.z) / 2
+ );
+ }
+
+ if (this.leftHandPinching)
+ {
+ if (!this.leftHandPinchingPrev)
+ {
+ justPinchedLeftHand = true;
+ // Calculate average point between left thumb and index finger
+ this.leftPinchStartPosition = new vec3(
+ (leftFingerPos.x + leftThumbPos.x) / 2,
+ (leftFingerPos.y + leftThumbPos.y) / 2,
+ (leftFingerPos.z + leftThumbPos.z) / 2
+ );
+ }
+
+ this.leftPinchPosition = new vec3(
+ (leftFingerPos.x + leftThumbPos.x) / 2,
+ (leftFingerPos.y + leftThumbPos.y) / 2,
+ (leftFingerPos.z + leftThumbPos.z) / 2
+ );
+ }
+
+ if (this.isEditor)
+ {
+ //editor joystick controls
+ this.jx = this.joystickTransform.getLocalPosition().x - this.startPosition.x;
+ this.jy = this.joystickTransform.getLocalPosition().z - this.startPosition.z;
+ this.jy -= this.joystickTransform.getLocalPosition().y - this.startPosition.y;
+ this.joystickTransform.setLocalPosition(this.startPosition);
+ }
+
+ if (this.rightHandPinching && this.pinchStartedOnJoystick)
+ {
+ this.jx = this.rightPinchPosition.x - this.rightPinchStartPosition.x;
+ this.jy = this.rightPinchPosition.z - this.rightPinchStartPosition.z;
+ this.jy -= this.rightPinchPosition.y - this.rightPinchStartPosition.y;
+
+ //this.joystickTransform.setWorldPosition(this.rightPinchStartPosition);
+
+ this.joystickTransform.setWorldPosition(this.rightPinchPosition);
+ }
+ else
+ {
+ //this.joystickTransform.setWorldScale(new vec3(3.6, 3.6, 3.6));
+ }
+
+ this.jx /= this.scale;
+ this.jy /= this.scale;
+
+ this.jx = Math.max(-1, Math.min(1, this.jx));
+ this.jy = Math.max(-1, Math.min(1, this.jy));
+
+ print("joystick position = [" + this.jx.toFixed(2) + ", " + this.jy.toFixed(2) + "]");
+
+ this.calculateAndSendMovement();
+ }
+
+ onLateUpdate()
+ {
+ const currentPosition = this.joystickTransform.getLocalPosition();
+
+ // Calculate offset from start position
+ const offsetX = currentPosition.x - this.startPosition.x;
+ const offsetY = currentPosition.y - this.startPosition.y;
+ const offsetZ = currentPosition.z - this.startPosition.z;
+
+ // Clamp the offsets
+ const clampedPosition = new vec3(
+ this.startPosition.x + Math.max(-this.scale, Math.min(this.scale, offsetX)),
+ this.startPosition.y,
+ this.startPosition.z + Math.max(-this.scale, Math.min(this.scale, offsetZ))
+ );
+
+ this.joystickTransform.setLocalPosition(clampedPosition);
+
+ // Zero out the rotation of the joystick
+ this.joystickTransform.setLocalRotation(quat.fromEulerAngles(0, 0, 0));
+ }
+
+
+ private calculateAndSendMovement() {
+ // Deadzone
+ let jxNorm = Math.abs(this.jx) < this.deadzone ? 0 : this.jx;
+ let jyNorm = Math.abs(this.jy) < this.deadzone ? 0 : this.jy;
+
+ const magnitude = Math.sqrt(jxNorm * jxNorm + jyNorm * jyNorm);
+
+ if (magnitude < this.deadzone) {
+ // If within deadzone, don't move
+ this.client.sendMovement(0, 0, 0);
+ return;
+ }
+
+ // Normalize to ensure max magnitude is 1.0
+ if (magnitude > 1.0) {
+ jxNorm /= magnitude;
+ jyNorm /= magnitude;
+ }
+
+ // Calculate angle in degrees (0° is up/forward, 90° is right)
+ let angleDegrees = Math.atan2(jxNorm, jyNorm) * (180 / Math.PI);
+ if (angleDegrees < 0) angleDegrees += 360;
+
+ // Initialize movement variables
+ let forward = 0;
+ let lateral = 0;
+ let yaw = 0;
+
+ // Check for cardinal directions (using 15° windows)
+ const isForward = this.isInCardinalWindow(angleDegrees, 0);
+ const isRight = this.isInCardinalWindow(angleDegrees, 90);
+ const isBackward = this.isInCardinalWindow(angleDegrees, 180);
+ const isLeft = this.isInCardinalWindow(angleDegrees, 270);
+
+ // Apply movement based on cardinal direction
+ if (isForward) {
+ forward = magnitude * FORWARD_SPEED_MAX;
+ } else if (isBackward) {
+ forward = -magnitude * FORWARD_SPEED_MAX;
+ } else if (isRight) {
+ lateral = magnitude * LATERAL_SPEED_MAX;
+ } else if (isLeft) {
+ lateral = -magnitude * LATERAL_SPEED_MAX;
+ } else {
+ // For diagonal movements, calculate components and yaw
+ forward = jyNorm * FORWARD_SPEED_MAX;
+ lateral = jxNorm * LATERAL_SPEED_MAX;
+
+ // Calculate yaw based on how far from forward/backward axis
+ // More yaw when moving sideways, less when moving forward/backward
+ const yawFactor = Math.abs(jxNorm) / (Math.abs(jxNorm) + Math.abs(jyNorm));
+ yaw = jxNorm * ROTATION_SPEED_MAX * yawFactor;
+ }
+
+ // Send movement command to the robot
+ this.client.sendMovement(forward, lateral, yaw);
+ }
+
+ private isInCardinalWindow(angle: number, direction: number): boolean {
+ const diff = Math.abs((angle - direction + 360) % 360);
+ return diff < this.cardinalWindowDegrees || diff >= (360 - this.cardinalWindowDegrees);
+ }
+}
diff --git a/Spectacles-2-Unitree/Assets/Joystick/Joystick.ts.meta b/Spectacles-2-Unitree/Assets/Joystick/Joystick.ts.meta
new file mode 100644
index 00000000..14bcf25c
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/Joystick.ts.meta
@@ -0,0 +1,65 @@
+- !
+ ImportedAssetIds:
+ TypeScriptAsset: ! 9b59ee1c-abca-4e6b-9e2e-a9a5884bc389
+ ImporterName: TypeScriptAssetImporter
+ PrimaryAsset: ! 9b59ee1c-abca-4e6b-9e2e-a9a5884bc389
+ PackageType: NotAPackage
+ PackagePolicy: CannotBeUnpacked
+ ExtraData:
+ {}
+ AssetDataMap:
+ TypeScriptAsset: ! e7e7f978-527b-4ab9-9621-aed369716a15
+ DependentFiles:
+ []
+ ImporterSettings: !
+ {}
+ CompressionSettings: ! 00000000-0000-0000-0000-000000000000
+- !
+ SvgIcon: ""
+ Description: ""
+ InternalVersion: 0
+ VersionMajor: 0
+ VersionMinor: 0
+ VersionPatch: 0
+ ComponentUid: 00000000-0000-0000-0000-000000000000
+ ExportUid: 00000000-0000-0000-0000-000000000000
+ CurrentVisibility: Editable
+ MaxVisibility: Editable
+ ScriptInputsHidden:
+ cardinalWindowDegrees: false
+ client: false
+ deadzone: false
+ joystickObject: false
+ leftIndexFinger: false
+ leftThumb: false
+ moveSpeed: false
+ rightIndexFinger: false
+ rightThumb: false
+ robotObject: false
+ ScriptTypesHidden:
+ {}
+ DefaultScriptInputs:
+ - joystickObject: ! 00000000-0000-0000-0000-000000000000
+ rightIndexFinger: ! 00000000-0000-0000-0000-000000000000
+ leftIndexFinger: ! 00000000-0000-0000-0000-000000000000
+ rightThumb: ! 00000000-0000-0000-0000-000000000000
+ leftThumb: ! 00000000-0000-0000-0000-000000000000
+ moveSpeed: ! 0.000000
+ robotObject: ! 00000000-0000-0000-0000-000000000000
+ deadzone: 0.100000
+ cardinalWindowDegrees: ! 15.000000
+ client: ! 00000000-0000-0000-0000-000000000000
+ ScriptTypes:
+ -
+ {}
+ InputLines:
+ - // @input SceneObject joystickObject
+ - // @input SceneObject robotObject
+ - // @input SceneObject rightIndexFinger
+ - // @input SceneObject leftIndexFinger
+ - // @input SceneObject rightThumb
+ - // @input SceneObject leftThumb
+ - // @input float moveSpeed
+ - // @input float deadzone = 0.1
+ - // @input float cardinalWindowDegrees = 15
+ - // @input Component.ScriptComponent client
diff --git a/Spectacles-2-Unitree/Assets/Joystick/JoystickInner.prefab b/Spectacles-2-Unitree/Assets/Joystick/JoystickInner.prefab
new file mode 100644
index 00000000..592abbae
--- /dev/null
+++ b/Spectacles-2-Unitree/Assets/Joystick/JoystickInner.prefab
@@ -0,0 +1,207 @@
+- !
+ PackagePath: ""
+ objects:
+ - ! 55847278-3293-4af2-9f42-44ea3ffb53ea
+ LazyLoading: false
+ RetainAssets: true
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ Layers: 1
+ LocalTransform:
+ position: {x: 6.309025, y: -34.000000, z: -31.000000}
+ rotation: {x: 0.000000, y: -0.000000, z: 0.000000}
+ scale: {x: 3.645104, y: 3.645104, z: 3.645104}
+ hasSegmentScaleCompensation: false
+ Children:
+ []
+ Components:
+ - ! 66df1500-d300-4aad-983d-97255191bf90
+ - ! 7f0c59ca-5620-4202-9afb-d0eeb66b95fb
+ - ! 75cf1d14-7574-4e73-bdbe-6a782f03ce86
+ - ! 9ad35435-d450-4cf0-81a4-96a283b4814b
+ - ! 8120ce6a-1029-4b67-b649-b99d199dc703
+ - ! 9c50ae2a-f7d3-40b4-8710-cb0f5b1be02c
+ - ! c362c261-eae1-441e-b583-166a38382ca2
+- !
+ PrefabRemaps:
+ []
+ Name: Joystick
+ Enabled: true
+ ScriptAsset: ! 2de985bb-051f-4e22-b6eb-4177ba1b20aa
+ ScriptInputsDefault:
+ {}
+ ScriptTypesDefault:
+ {}
+ ScriptInputs:
+ "":
+ joystickObject: ! 55847278-3293-4af2-9f42-44ea3ffb53ea
+ playerObject: ! 6f721893-d700-4dfd-94b0-99df12fcf240
+ rightIndexFinger: ! d180e208-2df8-4507-8437-213a1fbd5da6
+ leftIndexFinger: ! 1ab3fbdf-e28e-45a8-943d-76938ac530b4
+ rightThumb: ! 1b6d2315-e870-4a05-9817-029318f16b5a
+ leftThumb: ! 60037818-559f-4345-ac44-f7c5dfc86f3c
+ ball: ! e24cb0c9-bf40-4808-ba9a-570c077dbd14
+ playerControls: ! 499f70a7-a91d-4d95-bbd4-209c9506d3f7
+ ScriptTypes:
+ "":
+ {}
+- !
+ PrefabRemaps:
+ []
+ Name: Physics Body
+ Enabled: true
+ Shape: !
+ FitVisual: true
+ Radius: 6.000000
+ Matter: ! 00000000-0000-0000-0000-000000000000
+ Filter: !