123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- #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
|