edx/assignment10final/Assets/ProCore/ProBuilder/API Examples/Icosphere FFT/Scripts/IcoBumpin.cs

273 lines
8.7 KiB
C#

#if UNITY_EDITOR || UNITY_STANDALONE
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ProBuilder2.Common;
using ProBuilder2.MeshOperations;
namespace ProBuilder2.Examples
{
[RequireComponent(typeof(AudioSource))]
public class IcoBumpin : MonoBehaviour
{
pb_Object ico; // A reference to the icosphere pb_Object component
Mesh icoMesh; // A reference to the icosphere mesh (cached because we access the vertex array every frame)
Transform icoTransform; // A reference to the icosphere transform component. Cached because I can't remember if GameObject.transform is still a performance drain :|
AudioSource audioSource;// Cached reference to the audiosource.
/**
* Holds a pb_Face, the normal of that face, and the index of every vertex that touches it (sharedIndices).
*/
struct FaceRef
{
public pb_Face face;
public Vector3 nrm; // face normal
public int[] indices; // all vertex indices (including shared connected vertices)
public FaceRef(pb_Face f, Vector3 n, int[] i)
{
face = f;
nrm = n;
indices = i;
}
}
// All faces that have been extruded
FaceRef[] outsides;
// Keep a copy of the original vertex array to calculate the distance from origin.
Vector3[] original_vertices, displaced_vertices;
// The radius of the mesh icosphere on instantiation.
[Range(1f, 10f)]
public float icoRadius = 2f;
// The number of subdivisions to give the icosphere.
[Range(0, 3)]
public int icoSubdivisions = 2;
// How far along the normal should each face be extruded when at idle (no audio input).
[Range(0f, 1f)]
public float startingExtrusion = .1f;
// The material to apply to the icosphere.
public Material material;
// The max distance a frequency range will extrude a face.
[Range(1f, 50f)]
public float extrusion = 30f;
// An FFT returns a spectrum including frequencies that are out of human hearing range -
// this restricts the number of bins used from the spectrum to the lower @fftBounds.
[Range(8, 128)]
public int fftBounds = 32;
// How high the icosphere transform will bounce (sample volume determines height).
[Range(0f, 10f)]
public float verticalBounce = 4f;
// Optionally weights the frequency amplitude when calculating extrude distance.
public AnimationCurve frequencyCurve;
// A reference to the line renderer that will be used to render the raw waveform.
public LineRenderer waveform;
// The y size of the waveform.
public float waveformHeight = 2f;
// How far from the icosphere should the waveform be.
public float waveformRadius = 20f;
// If @rotateWaveformRing is true, this is the speed it will travel.
public float waveformSpeed = .1f;
// If true, the waveform ring will randomly orbit the icosphere.
public bool rotateWaveformRing = false;
// If true, the waveform will bounce up and down with the icosphere.
public bool bounceWaveform = false;
public GameObject missingClipWarning;
// Icosphere's starting position.
Vector3 icoPosition = Vector3.zero;
float faces_length;
const float TWOPI = 6.283185f; // 2 * PI
const int WAVEFORM_SAMPLES = 1024; // How many samples make up the waveform ring.
const int FFT_SAMPLES = 4096; // How many samples are used in the FFT. More means higher resolution.
// Keep copy of the last frame's sample data to average with the current when calculating
// deformation amounts. Smoothes the visual effect.
float[] fft = new float[FFT_SAMPLES],
fft_history = new float[FFT_SAMPLES],
data = new float[WAVEFORM_SAMPLES],
data_history = new float[WAVEFORM_SAMPLES];
// Root mean square of raw data (volume, but not in dB).
float rms = 0f, rms_history = 0f;
/**
* Creates the icosphere, and loads all the cache information.
*/
void Start()
{
audioSource = GetComponent<AudioSource>();
if( audioSource.clip == null )
missingClipWarning.SetActive(true);
// Create a new icosphere.
ico = pb_ShapeGenerator.IcosahedronGenerator(icoRadius, icoSubdivisions);
// Shell is all the faces on the new icosphere.
pb_Face[] shell = ico.faces;
// Materials are set per-face on pb_Object meshes. pb_Objects will automatically
// condense the mesh to the smallest set of subMeshes possible based on materials.
#if !PROTOTYPE
foreach(pb_Face f in shell)
f.material = material;
#else
ico.gameObject.GetComponent<MeshRenderer>().sharedMaterial = material;
#endif
// Extrude all faces on the icosphere by a small amount. The third boolean parameter
// specifies that extrusion should treat each face as an individual, not try to group
// all faces together.
ico.Extrude(shell, ExtrudeMethod.IndividualFaces, startingExtrusion);
// ToMesh builds the mesh positions, submesh, and triangle arrays. Call after adding
// or deleting vertices, or changing face properties.
ico.ToMesh();
// Refresh builds the normals, tangents, and UVs.
ico.Refresh();
outsides = new FaceRef[shell.Length];
Dictionary<int, int> lookup = ico.sharedIndices.ToDictionary();
// Populate the outsides[] cache. This is a reference to the tops of each extruded column, including
// copies of the sharedIndices.
for(int i = 0; i < shell.Length; ++i)
outsides[i] = new FaceRef( shell[i],
pb_Math.Normal(ico, shell[i]),
ico.sharedIndices.AllIndicesWithValues(lookup, shell[i].distinctIndices).ToArray()
);
// Store copy of positions array un-modified
original_vertices = new Vector3[ico.vertices.Length];
System.Array.Copy(ico.vertices, original_vertices, ico.vertices.Length);
// displaced_vertices should mirror icosphere mesh vertices.
displaced_vertices = ico.vertices;
icoMesh = ico.msh;
icoTransform = ico.transform;
faces_length = (float)outsides.Length;
// Build the waveform ring.
icoPosition = icoTransform.position;
#if UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_5_3 || UNITY_5_4
waveform.SetVertexCount(WAVEFORM_SAMPLES);
#elif UNITY_5_5
waveform.numPositions = WAVEFORM_SAMPLES;
#else
waveform.positionCount = WAVEFORM_SAMPLES;
#endif
if( bounceWaveform )
waveform.transform.parent = icoTransform;
audioSource.Play();
}
void Update()
{
// fetch the fft spectrum
audioSource.GetSpectrumData(fft, 0, FFTWindow.BlackmanHarris);
// get raw data for waveform
audioSource.GetOutputData(data, 0);
// calculate root mean square (volume)
rms = RMS(data);
/**
* For each face, translate the vertices some distance depending on the frequency range assigned.
* Not using the TranslateVertices() pb_Object extension method because as a convenience, that method
* gathers the sharedIndices per-face on every call, which while not tremondously expensive in most
* contexts, is far too slow for use when dealing with audio, and especially so when the mesh is
* somewhat large.
*/
for(int i = 0; i < outsides.Length; i++)
{
float normalizedIndex = (i/faces_length);
int n = (int)(normalizedIndex*fftBounds);
Vector3 displacement = outsides[i].nrm * ( ((fft[n]+fft_history[n]) * .5f) * (frequencyCurve.Evaluate(normalizedIndex) * .5f + .5f)) * extrusion;
foreach(int t in outsides[i].indices)
{
displaced_vertices[t] = original_vertices[t] + displacement;
}
}
Vector3 vec = Vector3.zero;
// Waveform ring
for(int i = 0; i < WAVEFORM_SAMPLES; i++)
{
int n = i < WAVEFORM_SAMPLES-1 ? i : 0;
vec.x = Mathf.Cos((float)n/WAVEFORM_SAMPLES * TWOPI) * (waveformRadius + (((data[n] + data_history[n]) * .5f) * waveformHeight));
vec.z = Mathf.Sin((float)n/WAVEFORM_SAMPLES * TWOPI) * (waveformRadius + (((data[n] + data_history[n]) * .5f) * waveformHeight));
vec.y = 0f;
waveform.SetPosition(i, vec);
}
// Ring rotation
if( rotateWaveformRing )
{
Vector3 rot = waveform.transform.localRotation.eulerAngles;
rot.x = Mathf.PerlinNoise(Time.time * waveformSpeed, 0f) * 360f;
rot.y = Mathf.PerlinNoise(0f, Time.time * waveformSpeed) * 360f;
waveform.transform.localRotation = Quaternion.Euler(rot);
}
icoPosition.y = -verticalBounce + ((rms + rms_history) * verticalBounce);
icoTransform.position = icoPosition;
// Keep copy of last FFT samples so we can average with the current. Smoothes the movement.
System.Array.Copy(fft, fft_history, FFT_SAMPLES);
System.Array.Copy(data, data_history, WAVEFORM_SAMPLES);
rms_history = rms;
icoMesh.vertices = displaced_vertices;
}
/**
* Root mean square is a good approximation of perceived loudness.
*/
float RMS(float[] arr)
{
float v = 0f,
len = (float)arr.Length;
for(int i = 0; i < len; i++)
v += Mathf.Abs(arr[i]);
return Mathf.Sqrt(v / (float)len);
}
}
}
#endif