using UnityEngine; using System.Collections; using System.Threading; using System.IO; using Winterdust; /// /// Showcase of BvhImporterExporter (Winterdust, Sweden). Loads up to 22 .bvh files found inside the program's working directory. /// This class should take care of everything. Just make a new blank project in Unity, add this component to any GameObject and press play. /// Important: Remember to put something with the Standard shader in the scene or else Unity will not include it in the stand-alone build. /// Alternatively just create a dummy material using the Standard shader in the "Resources" folder. Then you don't have to have anything in the scene except the main camera. /// public class BvhImporterExporterShowcase : MonoBehaviour { static readonly string[] hex = new string[] {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}; static readonly string CONTROLS1_TEXT = "WASD or Arrow Keys to move camera around. Space/C to go up/down (or page up/down). Shift to boost."; static readonly string CONTROLS2_TEXT = "R toggles rotation on x-axis (current skeleton only). Q/E to shrink/enlarge by 20% (all skeletons)."; static readonly string CONTROLS3_TEXT = "F = Focus on middle. X = Spread out skeletons. BvhImporterExporter (Winterdust, Sweden)"; static readonly Color GRAY_TRANS = new Color(0.5f, 0.5f, 0.5f, 0.5f); static readonly Vector3 LOADING_CUBE_PLACEMENT = new Vector3(0, 1, 0); string informationText = ""; string timerAText = ""; string timerBText = ""; string settingsText; Vector3 cameraPosition; GameObject loadingCube; BVH[] bvhArray; AnimationClip[] clipArray; BVH.PreparedAnimationClip[] preppedClipArray; GameObject[] skeletonArray; float loadingPhase = 0; int doShow = 0; Vector3 desiredCameraFocusPoint = LOADING_CUBE_PLACEMENT; Vector3 cameraFocusPoint = LOADING_CUBE_PLACEMENT; GameObject observedSkeletonGO = null; Transform transformInFocus = null; bool removeFirstFrameFromAnimation; bool insertLoopingKeyframe; bool limitFPS; string tochangeNfoStr = ""; Quaternion cameraRot = Quaternion.identity; BVH.ProgressTracker theTracker = new BVH.ProgressTracker(); float scale = 1f; System.Diagnostics.Stopwatch stopwatch3 = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch stopwatch4 = new System.Diagnostics.Stopwatch(); /// Entry point called by Unity. void Start () { removeFirstFrameFromAnimation = File.Exists("RemoveFirstFrameFromAnimation.txt"); insertLoopingKeyframe = File.Exists("InsertLoopingKeyframe.txt"); limitFPS = File.Exists("LimitImportFpsTo10.txt"); settingsText = "RemoveFirstFrameFromAnimation.txt="+ removeFirstFrameFromAnimation +" InsertLoopingKeyframe.txt="+ insertLoopingKeyframe +" LimitImportFpsTo10.txt="+ limitFPS; cameraPosition = Camera.main.transform.position; loadingCube = GameObject.CreatePrimitive(PrimitiveType.Cube); loadingCube.name = "Loading Cube"; loadingCube.transform.position = LOADING_CUBE_PLACEMENT; new Thread(new ThreadStart(this.ImportBvhFilesInDifferentThread)).Start(); //1: Start secondary thread makeLandscape(20, 10); } /// Method executed by secondary thread. It takes care of scanning for .bvh files, loading them all and preparing their AnimationClips. While this method runs the loading phase is in effect. public void ImportBvhFilesInDifferentThread() { string workDir = Directory.GetCurrentDirectory(); string[] filePaths = Directory.GetFiles(workDir, "*.bvh", SearchOption.TopDirectoryOnly); if (filePaths.Length>0) { timerAText = "Working on the first .bvh file..."; int cnt = filePaths.Length; if (cnt>22) { cnt = 22; //this program will not bother to import more than this many .bvh files (number keys plus F1-F12) } bvhArray = new BVH[cnt]; clipArray = new AnimationClip[cnt]; preppedClipArray = new BVH.PreparedAnimationClip[cnt]; skeletonArray = new GameObject[cnt]; System.Diagnostics.Stopwatch stopwatch1 = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch stopwatch2 = new System.Diagnostics.Stopwatch(); for (int i = 0; i < cnt; i++) { loadingPhase = (i+1f)/bvhArray.Length; informationText = "Importing "+(i+1)+"/"+ bvhArray.Length +": "+ filePaths[i].Substring(workDir.Length+1) +" [000.0000%]"; stopwatch1.Start(); bvhArray[i] = new BVH(filePaths[i], limitFPS?-10:1, progressTracker:theTracker); //2: Import .bvh files in different thread (if you have an extremely short animation this can probably be done on the main thread without any visible frame drops) stopwatch1.Stop(); if (removeFirstFrameFromAnimation) { bvhArray[i].removeFrame(0); } informationText = "Preparing "+(i+1)+"/"+ bvhArray.Length +": "+ filePaths[i].Substring(workDir.Length+1) +" [000.0000%]"; stopwatch2.Start(); preppedClipArray[i] = bvhArray[i].prepareAnimationClip(addExtraLoopKeyframe:insertLoopingKeyframe, progressTracker:theTracker); //3: Prepare AnimationClips in different thread (usually not needed for very short animations, then you can call makeAnimationClip() directly on the main thread without it causing any visible frame drops) stopwatch2.Stop(); timerAText = "Import: "+ stopwatch1.Elapsed.ToString() +" PrepAC: "+ stopwatch2.Elapsed.ToString(); } if (cnt>11) { tochangeNfoStr = "[0-9/F1-F"+(cnt-10)+" to change]"; } else if (cnt==11) { tochangeNfoStr = "[0-9/F1 to change]"; } else if (cnt==10) { tochangeNfoStr = "[0-9 to change]"; } else if (cnt>1) { tochangeNfoStr = "[1-"+cnt+" to change]"; } else { tochangeNfoStr = "[1 to toggle]"; } loadingPhase = 1000; //lets the main thread know that we're no longer in the loading phase } else { timerAText = "No .bvh files found in working directory!"; } } /// Checks every frame if the loading phase is done and responds to the user's controls. void Update () { userControls(); //0: Always react to user input if (loadingPhase<500) { loadingCube.transform.Rotate(new Vector3(0, Time.deltaTime*(1440*loadingPhase), 0)); //4: Wait until import thread finishes } else { if (loadingCube!=null) { //5: Remove loading cube GameObject.Destroy(loadingCube); loadingCube = null; } mainLogic(); //6: Start doing the main logic } } /// Just places some cubes below some stripes (planes) so that the skeletons have something to walk on. void makeLandscape(float size, int cubeAmount) { for (int i = 0; i < cubeAmount; i++) { GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = new Vector3(-size, -0.5f, -size) + new Vector3(Random.value*size, 0, Random.value*size); GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = new Vector3(-size, -0.5f, 0) + new Vector3(Random.value*size, 0, Random.value*size); GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = new Vector3(0, -0.5f, 0) + new Vector3(Random.value*size, 0, Random.value*size); GameObject.CreatePrimitive(PrimitiveType.Cube).transform.position = new Vector3(0, -0.5f, -size) + new Vector3(Random.value*size, 0, Random.value*size); } for (int i = 0; i < 8; i++) { GameObject p = GameObject.CreatePrimitive(PrimitiveType.Plane); p.transform.localScale = new Vector3(0.1f, 1, size/4); p.transform.eulerAngles = new Vector3(0, (180f/8f)*i, 0); } } /// The loading phase is done, time to display the skeletons... void mainLogic() { if (doShow!=-1) { //any display order queued? always 0 to begin with, otherwise set in userControls(). if (doShow(); for (int i = 0; i < ttt.Length; i++) { if (ttt[i]!=observedSkeletonGO.transform) { transformInFocus = ttt[i]; break; } } } else { transformInFocus = null; } } doShow = -1; } } /// Called whenever an AnimationClip is made or a new skeleton is added (even if the same one is added a second time). void updateTimerBText() { timerBText = " MakeAC: "+ stopwatch3.Elapsed.ToString() +" Show: "+ stopwatch4.Elapsed.ToString(); } /// Returns a color described in a typical HTML style (without the initial #). Example return: f3ab50 string getRandomHexColor() { System.Random rnd = new System.Random(); return hex[(int)(rnd.NextDouble()*16)] + hex[(int)(rnd.NextDouble()*16)] + hex[(int)(rnd.NextDouble()*16)] + hex[(int)(rnd.NextDouble()*16)] + hex[(int)(rnd.NextDouble()*16)] + hex[(int)(rnd.NextDouble()*16)]; } /// Respond to user inputs. An explanation of the controls can be found in the static fields CONTROLS1_TEXT, CONTROLS2_TEXT and CONTROLS3_TEXT. The mouse wheel is not mentioned though (it does the same thing as W and S). The working number keys and F1-F12 is added to tochangeNfoStr as needed (displayed in informationText). void userControls() { int multiplier = (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.LeftShift)) ? 5 : 1; if (Input.anyKeyDown || Input.GetAxisRaw("Mouse ScrollWheel")!=0) { cameraRot = Camera.main.transform.rotation; } if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.UpArrow) || Input.GetAxisRaw("Mouse ScrollWheel")>0) { cameraPosition += cameraRot * Vector3.forward * (multiplier+Mathf.Abs(Input.GetAxisRaw("Mouse ScrollWheel"))); } if (Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.DownArrow) || Input.GetAxisRaw("Mouse ScrollWheel")<0) { cameraPosition += cameraRot * Vector3.back * (multiplier+Mathf.Abs(Input.GetAxisRaw("Mouse ScrollWheel"))); } if (Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.LeftArrow)) { cameraPosition += Camera.main.transform.rotation * Vector3.left * multiplier; } if (Input.GetKey(KeyCode.D) || Input.GetKey(KeyCode.RightArrow)) { cameraPosition += Camera.main.transform.rotation * Vector3.right * multiplier; } if (Input.GetKey(KeyCode.Space) || Input.GetKey(KeyCode.PageUp)) { cameraPosition += Vector3.up * multiplier; } if (Input.GetKey(KeyCode.C) || Input.GetKey(KeyCode.PageDown)) { cameraPosition += Vector3.down * multiplier; } if (Input.GetKeyDown(KeyCode.Alpha0) || Input.GetKeyDown(KeyCode.Keypad0)) { doShow = 9; } else if (Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1)) { doShow = 0; } else if (Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2)) { doShow = 1; } else if (Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3)) { doShow = 2; } else if (Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4)) { doShow = 3; } else if (Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5)) { doShow = 4; } else if (Input.GetKeyDown(KeyCode.Alpha6) || Input.GetKeyDown(KeyCode.Keypad6)) { doShow = 5; } else if (Input.GetKeyDown(KeyCode.Alpha7) || Input.GetKeyDown(KeyCode.Keypad7)) { doShow = 6; } else if (Input.GetKeyDown(KeyCode.Alpha8) || Input.GetKeyDown(KeyCode.Keypad8)) { doShow = 7; } else if (Input.GetKeyDown(KeyCode.Alpha9) || Input.GetKeyDown(KeyCode.Keypad9)) { doShow = 8; } else if (Input.GetKeyDown(KeyCode.F1)) { doShow = 10; } else if (Input.GetKeyDown(KeyCode.F2)) { doShow = 11; } else if (Input.GetKeyDown(KeyCode.F3)) { doShow = 12; } else if (Input.GetKeyDown(KeyCode.F4)) { doShow = 13; } else if (Input.GetKeyDown(KeyCode.F5)) { doShow = 14; } else if (Input.GetKeyDown(KeyCode.F6)) { doShow = 15; } else if (Input.GetKeyDown(KeyCode.F7)) { doShow = 16; } else if (Input.GetKeyDown(KeyCode.F8)) { doShow = 17; } else if (Input.GetKeyDown(KeyCode.F9)) { doShow = 18; } else if (Input.GetKeyDown(KeyCode.F10)) { doShow = 19; } else if (Input.GetKeyDown(KeyCode.F11)) { doShow = 20; } else if (Input.GetKeyDown(KeyCode.F12)) { doShow = 21; } if (Input.GetKeyDown(KeyCode.R) && observedSkeletonGO!=null) { if (observedSkeletonGO.transform.eulerAngles.x==0) { observedSkeletonGO.transform.eulerAngles = new Vector3(-90, 0, 0); } else { observedSkeletonGO.transform.eulerAngles = Vector3.zero; } } if (Input.GetKeyDown(KeyCode.Q)) { scale *= 0.8f; syncScale(); } else if (Input.GetKeyDown(KeyCode.E)) { scale *= 1.2f; syncScale(); } if (Input.GetKeyDown(KeyCode.F)) { observedSkeletonGO = null; transformInFocus = null; informationText = tochangeNfoStr +" No tracking."; desiredCameraFocusPoint.x = 0; desiredCameraFocusPoint.z = 0; } if (Input.GetKeyDown(KeyCode.X)) { for (int i = 0; i < skeletonArray.Length; i++) { if (skeletonArray[i]!=null) { skeletonArray[i].transform.position = skeletonArray[i].transform.position + new Vector3(Random.value*20-10, 0, Random.value*20-10); } } } } /// Syncs all created skeletons with the current scale setting. Not using scale on BVH instances because that is lossy scale and would not work well with the already-prepared AnimationClips. void syncScale() { for (int i = 0; i < skeletonArray.Length; i++) { if (skeletonArray[i]!=null) { skeletonArray[i].transform.localScale = new Vector3(scale, scale, scale); } } } /// Position and point the camera. void LateUpdate() { if (transformInFocus!=null) { desiredCameraFocusPoint = transformInFocus.position; } cameraFocusPoint = Vector3.Lerp(cameraFocusPoint, desiredCameraFocusPoint, 0.25f); Camera.main.transform.position = Vector3.Lerp(Camera.main.transform.position, cameraPosition, 0.25f); Camera.main.transform.LookAt(cameraFocusPoint); } /// Print out the text in the top left corner. void OnGUI() { if (loadingPhase<500 && informationText.Contains("[")) { informationText = informationText.Substring(0, informationText.LastIndexOf("[")) +"["+theTracker.getPercentage(4, 3)+"%]"; } int spacing = 15; GUI.contentColor = GRAY_TRANS; GUI.Label(new Rect(9, 9, 1000, 25), CONTROLS1_TEXT); GUI.Label(new Rect(9, 9+spacing, 1000, 25), CONTROLS2_TEXT); GUI.Label(new Rect(9, 9+spacing*2, 1000, 25), informationText); GUI.Label(new Rect(9, 9+spacing*3, 1000, 25), CONTROLS3_TEXT); GUI.Label(new Rect(9, 9+spacing*4, 1000, 25), settingsText); GUI.Label(new Rect(9, 9+spacing*5, 1000, 25), timerAText + timerBText); GUI.contentColor = Color.black; GUI.Label(new Rect(11, 11, 1000, 25), CONTROLS1_TEXT); GUI.Label(new Rect(11, 11+spacing, 1000, 25), CONTROLS2_TEXT); GUI.Label(new Rect(11, 11+spacing*2, 1000, 25), informationText); GUI.Label(new Rect(11, 11+spacing*3, 1000, 25), CONTROLS3_TEXT); GUI.Label(new Rect(11, 11+spacing*4, 1000, 25), settingsText); GUI.Label(new Rect(11, 11+spacing*5, 1000, 25), timerAText + timerBText); GUI.contentColor = Color.white; GUI.Label(new Rect(10, 10, 1000, 25), CONTROLS1_TEXT); GUI.Label(new Rect(10, 10+spacing, 1000, 25), CONTROLS2_TEXT); GUI.Label(new Rect(10, 10+spacing*2, 1000, 25), informationText); GUI.Label(new Rect(10, 10+spacing*3, 1000, 25), CONTROLS3_TEXT); GUI.Label(new Rect(10, 10+spacing*4, 1000, 25), settingsText); GUI.Label(new Rect(10, 10+spacing*5, 1000, 25), timerAText + timerBText); } }