feat: SongCreator 씬 완성 — Beat Sage URL 지원, info.dat 메타데이터 자동 추출

- BeatSageUploader: audio_url 지원(UploadFromUrl), PollAndDownload 공통화, ZIP 500 오류 3회 재시도
- BeatSageConverter: info.dat 파싱(SongMetadata), BPM 자동 감지 → 노트 타이밍 변환에 적용
- SongCreatorManager: title/BPM 필수 입력 제거, 난이도 4개 자동 선택, GenerateFlowFromUrl 버그 수정
- NasPublisher: audioPath null 허용(URL 흐름에서 로컬 파일 없는 경우 스킵)
- .gitignore/.gitattributes 초기 설정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 23:37:34 +09:00
commit 4dad9e5d5b
1068 changed files with 175146 additions and 0 deletions
@@ -0,0 +1,215 @@
// Author: Mathias Soeholm
// Date: 05/10/2016
// No license, do whatever you want with this script
using UnityEngine;
using UnityEngine.Serialization;
namespace VRSDK
{
[ExecuteInEditMode]
public class TubeRenderer : MonoBehaviour
{
[SerializeField] Vector3[] _positions;
[SerializeField] int _sides;
[SerializeField] float _radiusOne;
[SerializeField] float _radiusTwo;
[SerializeField] bool _useWorldSpace = true;
[SerializeField] bool _useTwoRadii = false;
private Vector3[] _vertices;
private Mesh _mesh;
private MeshFilter _meshFilter;
private MeshRenderer _meshRenderer;
public Material material
{
get { return _meshRenderer.material; }
set { _meshRenderer.material = value; }
}
void Awake()
{
_meshFilter = GetComponent<MeshFilter>();
if (_meshFilter == null)
{
_meshFilter = gameObject.AddComponent<MeshFilter>();
}
_meshRenderer = GetComponent<MeshRenderer>();
if (_meshRenderer == null)
{
_meshRenderer = gameObject.AddComponent<MeshRenderer>();
}
_mesh = new Mesh();
_meshFilter.mesh = _mesh;
}
private void OnEnable()
{
_meshRenderer.enabled = true;
}
private void OnDisable()
{
_meshRenderer.enabled = false;
}
void Update()
{
GenerateMesh();
}
private void OnValidate()
{
_sides = Mathf.Max( 3, _sides );
}
public void SetPositions(Vector3[] positions)
{
_positions = positions;
GenerateMesh();
}
private void GenerateMesh()
{
if (_mesh == null || _positions == null || _positions.Length <= 1)
{
_mesh = new Mesh();
return;
}
var verticesLength = _sides * _positions.Length;
if (_vertices == null || _vertices.Length != verticesLength)
{
_vertices = new Vector3[verticesLength];
var indices = GenerateIndices();
var uvs = GenerateUVs();
if (verticesLength > _mesh.vertexCount)
{
_mesh.vertices = _vertices;
_mesh.triangles = indices;
_mesh.uv = uvs;
}
else
{
_mesh.triangles = indices;
_mesh.vertices = _vertices;
_mesh.uv = uvs;
}
}
var currentVertIndex = 0;
for (int i = 0; i < _positions.Length; i++)
{
var circle = CalculateCircle( i );
foreach (var vertex in circle)
{
_vertices[currentVertIndex++] = _useWorldSpace ? transform.InverseTransformPoint( vertex ) : vertex;
}
}
_mesh.vertices = _vertices;
_mesh.RecalculateNormals();
_mesh.RecalculateBounds();
_meshFilter.mesh = _mesh;
}
private Vector2[] GenerateUVs()
{
var uvs = new Vector2[_positions.Length * _sides];
for (int segment = 0; segment < _positions.Length; segment++)
{
for (int side = 0; side < _sides; side++)
{
var vertIndex = ( segment * _sides + side );
var u = side / ( _sides - 1f );
var v = segment / ( _positions.Length - 1f );
uvs[vertIndex] = new Vector2( u, v );
}
}
return uvs;
}
private int[] GenerateIndices()
{
// Two triangles and 3 vertices
var indices = new int[_positions.Length * _sides * 2 * 3];
var currentIndicesIndex = 0;
for (int segment = 1; segment < _positions.Length; segment++)
{
for (int side = 0; side < _sides; side++)
{
var vertIndex = ( segment * _sides + side );
var prevVertIndex = vertIndex - _sides;
// Triangle one
indices[currentIndicesIndex++] = prevVertIndex;
indices[currentIndicesIndex++] = ( side == _sides - 1 ) ? ( vertIndex - ( _sides - 1 ) ) : ( vertIndex + 1 );
indices[currentIndicesIndex++] = vertIndex;
// Triangle two
indices[currentIndicesIndex++] = ( side == _sides - 1 ) ? ( prevVertIndex - ( _sides - 1 ) ) : ( prevVertIndex + 1 );
indices[currentIndicesIndex++] = ( side == _sides - 1 ) ? ( vertIndex - ( _sides - 1 ) ) : ( vertIndex + 1 );
indices[currentIndicesIndex++] = prevVertIndex;
}
}
return indices;
}
private Vector3[] CalculateCircle(int index)
{
var dirCount = 0;
var forward = Vector3.zero;
// If not first index
if (index > 0)
{
forward += ( _positions[index] - _positions[index - 1] ).normalized;
dirCount++;
}
// If not last index
if (index < _positions.Length - 1)
{
forward += ( _positions[index + 1] - _positions[index] ).normalized;
dirCount++;
}
// Forward is the average of the connecting edges directions
forward = ( forward / dirCount ).normalized;
var side = Vector3.Cross( forward, forward + new Vector3( .123564f, .34675f, .756892f ) ).normalized;
var up = Vector3.Cross( forward, side ).normalized;
var circle = new Vector3[_sides];
var angle = 0f;
var angleStep = ( 2 * Mathf.PI ) / _sides;
var t = index / ( _positions.Length - 1f );
var radius = _useTwoRadii ? Mathf.Lerp( _radiusOne, _radiusTwo, t ) : _radiusOne;
for (int i = 0; i < _sides; i++)
{
var x = Mathf.Cos( angle );
var y = Mathf.Sin( angle );
circle[i] = _positions[index] + side * x * radius + up * y * radius;
angle += angleStep;
}
return circle;
}
}
}