# Project export: VR-TINES <3

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2024
- Tagline: Bridging hearts across miles with immersive VR—experience shared moments, tastes, and realistic presence! Your solution to overcoming distance—because love knows no bounds, and now, neither do you!
- Devpost: https://devpost.com/software/vr-tines
- GitHub: https://github.com/kmay23/VR-TINES
- Video: https://www.youtube.com/embed/zOW5E1oHc_g?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Otsuka/Valuenex: Best Hack to Connect with Others through Food (4x $500 DoorDash + 4x $250 Spa Giftcards))
- Team: 2 GitHub contributor(s) — jennyh23 (2 commits), kmay23 (1 commits)

## Devpost submission (written by the team)

### Overview

Long-distance is challenging for any relationship, whether romantic, platonic, or familial. 32.5% of college relationships are long-distance relationships (LDRs) Nearly Three-Quarters (72%) of Americans Feel Lonely. The lack of physical presence often leads to feelings of disconnect and loneliness. Current solutions, such as video calls and messaging apps, lack the depth and immersion needed to truly feel connected. The most crucial aspect of a relationship, shared activities and involvement of senses in addition to just sight and sound, are often the hardest to achieve from a distance. This Valentine’s weekend, we present to you… VR-Tines! 💗VR-Tines is an innovative VR experience designed to enhance long-distance relationships through immersive, interactive, and emotionally fulfilling activities. ⭐Key Experience Points Collaborative Scrapbooking: Couples can work together on a scrapbook, flipping through pages and dragging in photos and various decorative elements. This scrapbook feels like you’re actually working on it in the real world due to its alignment with 3d surfaces and interactive elements of flipping pages and dragging in elements. By having your partner right next to you, it’s like you’re working on it together in this shared space. Love to reflect on the mems :) Shared Taste: Get a themed-meal at the same time! Here, we use the two users’ favorite boba orders. Using the Doordash API, we synchronize the delivery of identical beverages or snacks to both partners during their VR date, which we track in the Doordash delivery simulator. Enhanced Realism with Live Webcam Feed: People typically use passthrough with VR headsets to feed what’s “real” into their experiences. We take advantage of this idea to stream a live webcam feed into the VR-tines experience, so it feels like your partner is actually sitting right next to you (and you see them in passthrough) as you do activities together! Tab Bar Navigation: We support toggling through the three main options: scrapbooking, Doordash, and home. Social Impact: VR-tines is about bringing people together. Our project has the potential to significantly reduce the emotional distance in long-distance relationships, fostering stronger bonds and happier couples. This problem is also important to us because all of us are in long-distance relationships! However, we notice many similar issues that arise in all kinds of relationships in life, such as family, friends, work, etc due to the difficulty of being far apart. Therefore, we sought to solve this real user problem faced every day and create a better solution than current methods of call communication to create more seamless, immersive experiences to feel closer to our loved ones. As first-time hackers and Stanford freshmen, the concept of home stretches across oceans to Myanmar and Vietnam, where our families reside. The yearning for a deeper connection with our loved ones, despite the geographical miles that separate us, sparked not just a need but a personal quest. Facing this hackathon, our greatest challenge was not the complexity of the technology or the novelty of the concept, but the mental hurdle of believing we could make a significant impact. This project became more than a hack; it evolved into a journey of discovery, learning, and overcoming, driven by our shared experiences of longing and the universal desire to feel closer to those we hold dear. It’s a testament to our belief that distance shouldn't dim the bonds of love and friendship but rather, with the right innovation, can be bridged beautifully.

## README (from the GitHub repository)

![banner valentines](https://github.com/kmay23/VR-TINES/assets/160367605/daac9b51-dff0-4f35-a2cb-2f17db029480)
# 💗 **VR-TINES**: Your VR Valentine’s Date

Watch our video: https://youtu.be/zOW5E1oHc_g

Long-distance is challenging for any relationship, whether romantic, platonic, or familial. 
- 32.5% of college relationships are long-distance relationships (LDRs) 
- Nearly Three-Quarters (72%) of Americans Feel Lonely. 
The lack of physical presence often leads to feelings of disconnect and loneliness. Current solutions, such as video calls and messaging apps, lack the depth and immersion needed to truly feel connected. The most crucial aspect of a relationship, shared activities and involvement of senses in addition to just sight and sound, are often the hardest to achieve from a distance. 

This Valentine’s weekend, we present to you… VR-TINES! 💗VR-TINES is an innovative VR experience designed to enhance long-distance relationships through immersive, interactive, and emotionally fulfilling activities.

## ⭐ Key Experience Points
- **Collaborative Scrapbooking**: Couples can work together on a scrapbook, flipping through pages and dragging in photos and various decorative elements. This scrapbook feels like you’re actually working on it in the real world due to its alignment with 3d surfaces and interactive elements of flipping pages and dragging in elements. By having your partner right next to you, it’s like you’re working on it together in this shared space. Love to reflect on the mems :)
- **Shared Taste**: Get a themed-meal at the same time! Here, we use the two users’ favorite boba orders. Using the Doordash API, we synchronize the delivery of identical beverages or snacks to both partners during their VR date, which we track in the Doordash delivery simulator.
- **Enhanced Realism with Live Webcam Feed**: People typically use passthrough with VR headsets to feed what’s “real” into their experiences. We take advantage of this idea to stream a live webcam feed into the VR-tines experience, so it feels like your partner is actually sitting right next to you (and you see them in passthrough) as you do activities together! 
- **Tab Bar Navigation**: We support toggling through the three main options: scrapbooking, Doordash, and home.
- **Social Impact**: VR-tines is about bringing people together. Our project has the potential to significantly reduce the emotional distance in long-distance relationships, fostering stronger bonds and happier couples.

## Getting Started
- Download Unity: https://unity.com/download
- Download Oculus Integration Kit: https://assetstore.unity.com/packages/tools/integration/oculus-integration-deprecated-82022
- Install all necessary Android Module installations in Unity
- Create a Meta Developer Account
- Enable Meta Developer Mode
- Build settings Switch Platform to Android, download necessary modules
- Build and Run to your Oculus Headset
- When you are in the virtual environment, the world is you and your VR-tines’! Interact with the environment using the Oculus Quest Touch Controllers. Pick up items, look around the immersive view with your call partner within the space, and interact with the scrapbook. 
- For fun: cast your Oculus viewing to any computer on oculus.com/casting

This problem is also important to us because all of us are in long-distance relationships! However, we notice many similar issues that arise in all kinds of relationships in life, such as family, friends, work, etc due to the difficulty of being far apart. Therefore, we sought to solve this real user problem faced every day and create a better solution than current methods of call communication to create more seamless, immersive experiences to feel close :) 

It’s honestly a real-life user problem :’)


## Detected evidence (automated analysis)

Indexed codebase: 1956 recognized source files, 9777 KB.
- C# (language) — detected in the code
- Java (language) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (120 of 8555)

```
.gitignore
.vscode/extensions.json
.vscode/launch.json
.vscode/settings.json
Assembly-CSharp-Editor.csproj
Assembly-CSharp.csproj
Assets/Book-Page Curl.meta
Assets/Book-Page Curl/Example.meta
Assets/Book-Page Curl/Example/podium material.mat
Assets/Book-Page Curl/Example/podium material.mat.meta
Assets/Book-Page Curl/Example/prefabs.meta
Assets/Book-Page Curl/Example/prefabs/Book.prefab
Assets/Book-Page Curl/Example/prefabs/Book.prefab.meta
Assets/Book-Page Curl/Example/scenes.meta
Assets/Book-Page Curl/Example/scenes/3d.unity
Assets/Book-Page Curl/Example/scenes/3d.unity.meta
Assets/Book-Page Curl/Example/scenes/Canvas.prefab
Assets/Book-Page Curl/Example/scenes/Canvas.prefab.meta
Assets/Book-Page Curl/Example/scenes/EventSystem.prefab
Assets/Book-Page Curl/Example/scenes/EventSystem.prefab.meta
Assets/Book-Page Curl/Example/scenes/Example_1_Basic Book.unity
Assets/Book-Page Curl/Example/scenes/Example_1_Basic Book.unity.meta
Assets/Book-Page Curl/Example/scenes/Example_2_Controled Flipping.unity
Assets/Book-Page Curl/Example/scenes/Example_2_Controled Flipping.unity.meta
Assets/Book-Page Curl/Example/scenes/Example_2_Controled FlippingSettings.lighting
Assets/Book-Page Curl/Example/scenes/Example_2_Controled FlippingSettings.lighting.meta
Assets/Book-Page Curl/Example/scenes/Example_3_WorldSpace.unity
Assets/Book-Page Curl/Example/scenes/Example_3_WorldSpace.unity.meta
Assets/Book-Page Curl/Example/scenes/Stand.prefab
Assets/Book-Page Curl/Example/scenes/Stand.prefab.meta
Assets/Book-Page Curl/Example/sprites.meta
Assets/Book-Page Curl/Example/sprites/1.png.meta
Assets/Book-Page Curl/Example/sprites/2.png.meta
Assets/Book-Page Curl/Example/sprites/3.png.meta
Assets/Book-Page Curl/Example/sprites/4.png.meta
Assets/Book-Page Curl/Example/sprites/5.png.meta
Assets/Book-Page Curl/Example/sprites/6.png.meta
Assets/Book-Page Curl/Example/sprites/background.png.meta
Assets/Book-Page Curl/Example/sprites/Kimberly.png.meta
Assets/Book-Page Curl/Example/sprites/P0_Back.png.meta
Assets/Book-Page Curl/Example/sprites/P1_Front.png.meta
Assets/Book-Page Curl/Example/sprites/P2_Back^P3_Front.png.meta
Assets/Book-Page Curl/Example/sprites/P3_Back.png.meta
Assets/Book-Page Curl/Example/sprites/P4_Front.png.meta
Assets/Book-Page Curl/Example/sprites/page0.png.meta
Assets/Book-Page Curl/Example/sprites/page1.png.meta
Assets/Book-Page Curl/Example/sprites/page2.png.meta
Assets/Book-Page Curl/Example/sprites/page3.png.meta
Assets/Book-Page Curl/Example/sprites/page4.png.meta
Assets/Book-Page Curl/Example/sprites/page5.png.meta
Assets/Book-Page Curl/Example/sprites/page6.png.meta
Assets/Book-Page Curl/Example/sprites/page7.png.meta
Assets/Book-Page Curl/Example/sprites/TransparentGraybackgtound.png.meta
Assets/Book-Page Curl/README.pdf.meta
Assets/Book-Page Curl/scripts.meta
Assets/Book-Page Curl/scripts/AutoFlip.cs
Assets/Book-Page Curl/scripts/AutoFlip.cs.meta
Assets/Book-Page Curl/scripts/Book.cs
Assets/Book-Page Curl/scripts/Book.cs.meta
Assets/Book-Page Curl/sprites.meta
Assets/Book-Page Curl/sprites/shadow.png.meta
Assets/Book-Page Curl/sprites/shadowLTR.png.meta
Assets/DoorDash.py
Assets/DoorDash.py.meta
Assets/FeedCameraScript.cs
Assets/FeedCameraScript.cs.meta
Assets/HandGrab.cs
Assets/HandGrab.cs.meta
Assets/Icons.meta
Assets/Icons/1_b4ZHbSsBVwTAos2uc1sTeA.png.meta
Assets/Icons/canvaicon.png.meta
Assets/Icons/DoorDash+Delivery+Button (1).png.meta
Assets/Icons/homebtn.png.meta
Assets/Icons/homebutton.png.meta
Assets/Materials.meta
Assets/Materials/1_b4ZHbSsBVwTAos2uc1sTeA.mat
Assets/Materials/1_b4ZHbSsBVwTAos2uc1sTeA.mat.meta
Assets/Materials/4.mat
Assets/Materials/4.mat.meta
Assets/Materials/canvaicon.mat
Assets/Materials/canvaicon.mat.meta
Assets/Materials/DoorDash+Delivery+Button (1).mat
Assets/Materials/DoorDash+Delivery+Button (1).mat.meta
Assets/Materials/green-screen-with-green-background_873925-13107.jpg.meta
Assets/Materials/homebtn.mat
Assets/Materials/homebtn.mat.meta
Assets/Materials/homebutton.mat
Assets/Materials/homebutton.mat.meta
Assets/Materials/New Material.mat
Assets/Materials/New Material.mat.meta
Assets/MeshMerger.meta
Assets/MeshMerger/Editor.meta
Assets/MeshMerger/Editor/MeshMergerEditor.cs
Assets/MeshMerger/Editor/MeshMergerEditor.cs.meta
Assets/MeshMerger/Materials.meta
Assets/MeshMerger/Materials/Blue.mat
Assets/MeshMerger/Materials/Blue.mat.meta
Assets/MeshMerger/Materials/Green.mat
Assets/MeshMerger/Materials/Green.mat.meta
Assets/MeshMerger/Materials/Red.mat
Assets/MeshMerger/Materials/Red.mat.meta
Assets/MeshMerger/Materials/Yellow.mat
Assets/MeshMerger/Materials/Yellow.mat.meta
Assets/MeshMerger/Prefabs.meta
Assets/MeshMerger/Prefabs/BlueSphere.prefab
Assets/MeshMerger/Prefabs/BlueSphere.prefab.meta
Assets/MeshMerger/Prefabs/GreenSphere.prefab
Assets/MeshMerger/Prefabs/GreenSphere.prefab.meta
Assets/MeshMerger/Prefabs/MeshMerger.prefab
Assets/MeshMerger/Prefabs/MeshMerger.prefab.meta
Assets/MeshMerger/Prefabs/RedSphere.prefab
Assets/MeshMerger/Prefabs/RedSphere.prefab.meta
Assets/MeshMerger/Prefabs/YellowSphere.prefab
Assets/MeshMerger/Prefabs/YellowSphere.prefab.meta
Assets/MeshMerger/README.pdf.meta
Assets/MeshMerger/SavedMeshes.meta
Assets/MeshMerger/SavedMeshes/Spheres.prefab
Assets/MeshMerger/SavedMeshes/Spheres.prefab.meta
Assets/MeshMerger/SavedMeshes/SpheresMesh519367.asset
Assets/MeshMerger/SavedMeshes/SpheresMesh519367.asset.meta
[8435 more files omitted for size]
```

### Dependencies

- Assets/Plugins/GLTFUtility/package.json: com.unity.nuget.newtonsoft-json@2.0.0

### Recent commits (newest first)

- Update README.md
- Create README.md
- Submission code

## Key source files (fetched from GitHub, selected and truncated for size)

### Assets/Plugins/Newtonsoft Json/LICENSE.md

```markdown
Nuget.Newtonsoft.Json copyright © 2019 Unity Technologies SF​

Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).

Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.

```

### Assets/Oculus/Interaction/LICENSE.md

```markdown
Copyright (c) Meta Platforms, Inc. and affiliates.
All rights reserved.

Licensed under the Oculus SDK License Agreement (the "License");
you may not use the Oculus SDK except in compliance with the License,
which is provided at the time of installation or download, or which
otherwise accompanies this software in either electronic or hard copy form.

You may obtain a copy of the License at

https://developer.oculus.com/licenses/oculussdk/

Unless required by applicable law or agreed to in writing, the Oculus SDK
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

```

### Assets/Oculus/Interaction/package.json

```
{
  "name": "com.oculus.integration.interaction",
  "displayName": "[SDK] Oculus Interaction",
  "version": "0.57.0",
  "unity": "2020.3",
  "keywords": [
    "Oculus",
    "Interaction",
    "SDK"
  ],
  "description": "Oculus Interaction SDK"
}

```

### Assets/Plugins/GLTFUtility/package.json

```
{
    "name": "com.siccity.gltfutility",
    "displayName": "GLTFUtility",
    "version": "0.6.0",
    "unity": "2018.2",
    "keywords": ["glTF", "import"],
	"description": "Lightweight glTF loader.",
	"dependencies": {
		"com.unity.nuget.newtonsoft-json": "2.0.0"
	}
}

```

### Assets/PageFlipper.cs

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PageFlipper : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        // Check if the A button on the right-hand controller is pressed
        if (OVRInput.GetDown(OVRInput.Button.One, OVRInput.Controller.RTouch))
        {
            pageFlip();
        }
    }

    // Define the pageFlip function
    void pageFlip()
    {
        // Page flip logic goes here
        Debug.Log("Page flipped!");
    }
}
```

### Assets/FeedCameraScript.cs

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class FeedCameraScript : MonoBehaviour
{
    private bool camAvailable;
    private WebCamTexture backCam;
    private Texture defaultBackground;

    public RawImage background;
    public AspectRatioFitter fit;
    // Start is called before the first frame update
    private void Start()
    {
        // Set fallback background texture if camera feed is not available
        defaultBackground = background.texture;

        WebCamDevice[] devices = WebCamTexture.devices;

        if(devices.Length == 0)
        {
            Debug.Log("No camera detected");
            camAvailable = false;
            return;
        }
        for(int i = 0; i < devices.Length; i++)
        {

            backCam = new WebCamTexture(devices[i].name, Screen.width, Screen.height);

        }
        // if(backCam == null)
        // {
        //     Debug.Log("Unable to find back camera");
        //     return;
        // }
        // backCam.Play();
        // background.texture = backCam;

        // camAvailable = true;

        // Select the first available webcam (usually the built-in webcam)
        // webcamTexture = new WebCamTexture(devices[0].name, Screen.width, Screen.height);

        backCam.Play();
        background.texture = backCam;

        camAvailable = true;
    }

    // Update is called once per frame
    private void Update()
    {
        if(!camAvailable)
        {
            return;
        }
        float ratio = (float)backCam.width / (float)backCam.height;
        fit.aspectRatio = ratio;

        float scaleY = backCam.videoVerticallyMirrored ? -1f : 1f;
        background.rectTransform.localScale = new Vector3(1f, scaleY, 1f);

        int orient = -backCam.videoRotationAngle;
        background.rectTransform.localEulerAngles = new Vector3(0, 0, orient);
    }
}

```

### Assets/DoorDash.py

```python
import requests
from os import access
import jwt.utils
import time
import math
import random

developer_id = "169ba5ed-accb-46d3-803e-b6dc771b12ed"
key_id = "95e01146-a74f-4804-91b6-cc480ffca83c"
signing_secret = "2aUz2Ou0Cp14bFShubCFMCTmrlcqMInBXH12In7PAzY"

token = jwt.encode(
    {
        "aud": "doordash",
        "iss": developer_id,
        "kid": key_id,
        "exp": str(math.floor(time.time() + 300)),
        "iat": str(math.floor(time.time())),
    },
    jwt.utils.base64url_decode(signing_secret),
    algorithm="HS256",
    headers={"dd-ver": "DD-JWT-V1"},
)

# print(token)

endpoint = "https://openapi.doordash.com/drive/v2/deliveries/"

headers = {
    "Accept-Encoding": "application/json",
    "Authorization": "Bearer " + token,
    "Content-Type": "application/json",
}

# CREATE A DELIVERY FOR USER A - west coast
random_id = random.randint(100000, 999999)
request_body = {
    "external_delivery_id": f"D-{random_id}",
    "pickup_address": "2675 Middlefield Rd C, Palo Alto, CA 94306",
    "pickup_business_name": "Teaspoon Tea",
    "pickup_phone_number": "+16505555555",
    "pickup_instructions": "Pick up from the front shelf.",
    "dropoff_address": "475 Via Ortega, Stanford, CA 94305",
    "dropoff_business_name": "Stanford dormitories",
    "dropoff_phone_number": "+16505555555",
    "dropoff_instructions": "Enter gate code 1094 on the callbox.",
    "order_value": 1999,
}
create_delivery = requests.post(endpoint, headers=headers, json=request_body)
print(create_delivery.status_code)
print(create_delivery.text)
print(create_delivery.reason)

# CREATE A DELIVERY FOR USER B - east coast
random_id = random.randint(100000, 999999)
request_body = {
    "external_delivery_id": f"D-{random_id}",
    "pickup_address": "27 Witherspoon St, Princeton, NJ 08542",
    "pickup_business_name": "Junbi Tea",
    "pickup_phone_number": "+19038923060",
    "pickup_instructions": "Give name to waiter.",
    "dropoff_address": "Rockefeller College, Princeton, NJ 08540",
    "dropoff_business_name": "Princeton dormitories",
    "dropoff_phone_number": "+19082948204",
    "dropoff_instructions": "Leave outside door.",
    "order_value": 1999,
}
create_delivery = requests.post(endpoint, headers=headers, json=request_body)
print(create_delivery.status_code)
print(create_delivery.text)
print(create_delivery.reason)

```

### Assets/HandGrab.cs

```c#
// using System.Collections;
// using System.Collections.Generic;
// using UnityEngine;
// using Oculus;

// public class HandGrab : MonoBehaviour
// {
// public Transform grabPoint; // The place where the object will be held
// private GameObject currentGrabbableObject;
// private bool isGrabbing;

// void Update()
// {
// // Check if the hand trigger is pressed
// if (OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, OVRInput.Controller.RTouch) > 0.5f && !isGrabbing)
// {
// TryGrabObject();
// }
// else if (OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, OVRInput.Controller.RTouch) < 0.5f && isGrabbing)
// {
// DropObject();
// }
// }

// void TryGrabObject()
// {
// RaycastHit hit;
// if (Physics.Raycast(grabPoint.position, grabPoint.forward, out hit, 0.1f))
// {
// if (hit.collider.gameObject.CompareTag("Grabbable"))
// {
// currentGrabbableObject = hit.collider.gameObject;
// GrabObject();
// }
// }
// }

// void GrabObject()
// {
// isGrabbing = true;

// // Make the object a child of the hand
// currentGrabbableObject.transform.SetParent(grabPoint);

// // Disable the object's physics while it's being held
// Rigidbody rb = currentGrabbableObject.GetComponent<Rigidbody>();
// if (rb)
// {
// rb.isKinematic = true;
// }
// }

// void DropObject()
// {
// isGrabbing = false;

// // Remove the object from being a child of the hand
// currentGrabbableObject.transform.SetParent(null);

// // Re-enable the object's physics
// Rigidbody rb = currentGrabbableObject.GetComponent<Rigidbody>();
// if (rb)
// {
// rb.isKinematic = false;
// }

// currentGrabbableObject = null;
// }
// }
using UnityEngine;

public class HandGrabbing : MonoBehaviour
{
 public OVRInput.Controller controllerType; // Define which hand this script is for
 private bool isGrabbing;
 private Rigidbody grabbedObjectRb;

 void Update()
 {
 // Check if the grab button is pressed
 if (OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, controllerType) > 0.5f && !isGrabbing)
 {
 RaycastHit hitInfo;
 if (Physics.Raycast(transform.position, transform.forward, out hitInfo, 0.1f))
 {
 if (hitInfo.collider.gameObject.CompareTag("Graph"))
 {
 grabbedObjectRb = hitInfo.collider.gameObject.GetComponent<Rigidbody>();
 grabbedObjectRb.isKinematic = true;
 grabbedObjectRb.transform.SetParent(transform);
 isGrabbing = true;
 }
 }
 }
 // Release the object when the button is released
 else if (OVRInput.Get(OVRInput.Axis1D.PrimaryHandTrigger, controllerType) < 0.5f && isGrabbing)
 {
 grabbedObjectRb.transform.SetParent(null);
 grabbedObjectRb.isKinematic = false;
 grabbedObjectRb = null;
 isGrabbing = false;
 }
 }
}
```

### Assets/MeshMerger/Editor/MeshMergerEditor.cs

```c#
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(MeshMerger))]
public class MeshMergerEditor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        MeshMerger meshMerger = (MeshMerger)target;
        if (GUILayout.Button("Merge")) meshMerger.Merge();
    }
}

```

### Assets/MeshMerger/Scripts/MeshSaver.cs

```c#
#if(UNITY_EDITOR)
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class MeshSaver
{
    public void SaveMesh(Mesh mesh, string path)
    {
        if (!AssetDatabase.IsValidFolder(path)) CreateFolder(path);
        AssetDatabase.CreateAsset(mesh, $"{path}/{mesh.name}{Random.Range(100000, 1000000)}.asset");
    }

    private void CreateFolder(string path)
    {
        string[] folders = path.Split('/');
        for (int folderIndex = 0; !AssetDatabase.IsValidFolder(path); folderIndex++)
            AssetDatabase.CreateFolder(GetPath(folders, folderIndex + 1), folders[folderIndex + 1]);
    }

    private string GetPath(string[] folders, int foldersCount)
    {
        string result = "";
        for (int i = 0; i < foldersCount; i++)
        {
            result += folders[i];
            if (i < foldersCount - 1) result += "/";
        }

        return result;
    }
}
#endif
```

[1926 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]