forked from cgvr/DeltaVR
207 lines
6.8 KiB
C#
207 lines
6.8 KiB
C#
using GLTFast;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
|
|
public class ModelGenerationUtils : MonoBehaviour
|
|
{
|
|
public static ModelGenerationUtils Instance { get; private set; }
|
|
|
|
private void Awake()
|
|
{
|
|
Instance = this;
|
|
}
|
|
|
|
// Start is called before the first frame update
|
|
void Start()
|
|
{
|
|
|
|
}
|
|
|
|
// Update is called once per frame
|
|
void Update()
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
* Generate model by starting a new Python subprocess
|
|
* NOT USED IN LATEST VERSION
|
|
**/
|
|
public async Task<string> GenerateModelAsync(string inputPrompt)
|
|
{
|
|
return await Task.Run(() =>
|
|
{
|
|
// Path to your virtual environment's python.exe
|
|
string pythonExe = @"D:\users\henrisel\DeltaVR3DModelGeneration\3d-generation-pipeline\.venv\Scripts\python.exe";
|
|
|
|
// Path to your Python script
|
|
string scriptPath = @"D:\users\henrisel\DeltaVR3DModelGeneration\3d-generation-pipeline\start_pipeline.py";
|
|
|
|
// Arguments to pass to the script
|
|
string arguments = $"{scriptPath} --prompt \"{inputPrompt}\"";
|
|
|
|
ProcessStartInfo psi = new ProcessStartInfo
|
|
{
|
|
FileName = pythonExe,
|
|
Arguments = arguments,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true
|
|
};
|
|
|
|
using (Process process = new Process())
|
|
{
|
|
process.StartInfo = psi;
|
|
process.OutputDataReceived += (sender, e) => UnityEngine.Debug.Log(e.Data);
|
|
process.ErrorDataReceived += (sender, e) => UnityEngine.Debug.LogError(e.Data);
|
|
|
|
process.Start();
|
|
|
|
string output = process.StandardOutput.ReadToEnd();
|
|
string error = process.StandardError.ReadToEnd();
|
|
|
|
process.WaitForExit();
|
|
|
|
|
|
// Extract model path from output
|
|
foreach (string line in output.Split('\n'))
|
|
{
|
|
if (line.StartsWith("Generated 3D model file: "))
|
|
{
|
|
return line.Replace("Generated 3D model file: ", "").Trim();
|
|
}
|
|
}
|
|
|
|
throw new Exception("Failed to generate 3D model!");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Spawn model stored on disk
|
|
* NOT USED IN LATEST VERSION
|
|
*
|
|
**/
|
|
public async Task<GameObject> SpawnModel(string modelPath)
|
|
{
|
|
var gltf = new GltfImport();
|
|
bool loadSuccess = await gltf.Load(modelPath);
|
|
if (loadSuccess)
|
|
{
|
|
string objectName = Path.GetFileName(modelPath);
|
|
return await SpawnModel(gltf, objectName);
|
|
}
|
|
|
|
throw new Exception("Failed to load GameObject from model" + modelPath);
|
|
}
|
|
|
|
public async Task<GameObject> SpawnModel(byte[] modelBinary, float boundingSphereDiameter = 0)
|
|
{
|
|
var gltf = new GltfImport();
|
|
bool loadSuccess = await gltf.Load(modelBinary);
|
|
if (loadSuccess)
|
|
{
|
|
return await SpawnModel(gltf, "GeneratedModel", boundingSphereDiameter);
|
|
}
|
|
|
|
throw new Exception("Failed to load GameObject from binary!");
|
|
}
|
|
|
|
public async Task<GameObject> SpawnModel(GltfImport gltfImport, string objectName, float boundingSphereDiameter = 0)
|
|
{
|
|
GameObject spawningParent = new GameObject("Parent-" + objectName);
|
|
|
|
bool spawnSuccess = await gltfImport.InstantiateMainSceneAsync(spawningParent.transform);
|
|
if (spawnSuccess)
|
|
{
|
|
Transform spawnedObjectWorldTransform = spawningParent.transform.GetChild(0).transform;
|
|
GameObject spawnedObjectBody = spawnedObjectWorldTransform.GetChild(0).transform.gameObject;
|
|
MeshCollider collider = spawnedObjectBody.AddComponent<MeshCollider>();
|
|
collider.convex = true;
|
|
MeshRenderer renderer = spawnedObjectBody.GetComponent<MeshRenderer>();
|
|
renderer.material.SetFloat("metallicFactor", 0);
|
|
|
|
AdjustScale(spawnedObjectBody, collider, boundingSphereDiameter);
|
|
|
|
spawnedObjectBody.name = objectName;
|
|
return spawnedObjectBody;
|
|
}
|
|
|
|
throw new Exception("Failed to spawn GameObject " + objectName);
|
|
}
|
|
|
|
private async void AdjustScale(GameObject obj, MeshCollider collider, float boundingSphereDiameter)
|
|
{
|
|
|
|
// Wait one frame to ensure collider cooking & bounds are valid
|
|
await Task.Yield();
|
|
|
|
// --- Compute current bounding sphere diameter (from world AABB) ---
|
|
Bounds b = collider.bounds; // world-space AABB
|
|
float currentDiameter = 2f * b.extents.magnitude;
|
|
|
|
if (boundingSphereDiameter <= 0f)
|
|
{
|
|
UnityEngine.Debug.Log("Not adjusting scale");
|
|
return;
|
|
}
|
|
|
|
if (currentDiameter <= Mathf.Epsilon)
|
|
throw new Exception("Spawned mesh has zero-sized bounds; cannot scale to target diameter.");
|
|
|
|
// --- Compute and apply uniform scale ---
|
|
float scaleFactor = boundingSphereDiameter / currentDiameter;
|
|
obj.transform.localScale *= scaleFactor;
|
|
|
|
// Give Unity a moment to recompute bounds after scaling, then do a corrective pass
|
|
await Task.Yield();
|
|
|
|
// Corrective pass (optional): re-measure and nudge scale to reduce residual error
|
|
Bounds b2 = collider.bounds;
|
|
float newDiameter = 2f * b2.extents.magnitude;
|
|
|
|
// Only correct if off by more than a tiny epsilon (1%)
|
|
const float tolerance = 0.01f; // 1%
|
|
float err = Mathf.Abs(newDiameter - boundingSphereDiameter) / boundingSphereDiameter;
|
|
if (err > tolerance && newDiameter > Mathf.Epsilon)
|
|
{
|
|
float correction = boundingSphereDiameter / newDiameter;
|
|
obj.transform.localScale *= correction;
|
|
// No need to wait again unless you need to read back the final diameter immediately
|
|
}
|
|
}
|
|
|
|
|
|
public static Texture2D CreateTexture(byte[] imageBytes)
|
|
{
|
|
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
|
// ImageConversion.LoadImage returns bool (true = success)
|
|
if (!ImageConversion.LoadImage(tex, imageBytes, markNonReadable: false))
|
|
{
|
|
Destroy(tex);
|
|
throw new InvalidOperationException("Failed to decode image bytes into Texture2D.");
|
|
}
|
|
|
|
tex.filterMode = FilterMode.Bilinear;
|
|
tex.wrapMode = TextureWrapMode.Clamp;
|
|
|
|
return tex;
|
|
}
|
|
|
|
public static Sprite CreateSprite(Texture2D tex)
|
|
{
|
|
var sprite = Sprite.Create(
|
|
tex,
|
|
new Rect(0, 0, tex.width, tex.height),
|
|
new Vector2(0.5f, 0.5f),
|
|
pixelsPerUnit: 100f
|
|
);
|
|
|
|
return sprite;
|
|
}
|
|
}
|