├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── array.h ├── character.h ├── common.h ├── database.h ├── looping.cpp ├── quat.h ├── resources ├── bvh.py ├── character.bin ├── character.fs ├── character.vs ├── checkerboard.fs ├── checkerboard.vs ├── database.bin ├── generate_database.py └── quat.py ├── shell.html ├── spring.h ├── vec.h └── wasm-server.py /.gitignore: -------------------------------------------------------------------------------- 1 | jointlimits.exe 2 | jointlimits.data 3 | jointlimits.js 4 | jointlimits.html 5 | jointlimits.wasm 6 | *.log 7 | *__pycache__* 8 | *.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Holden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PLATFORM ?= PLATFORM_DESKTOP 2 | 3 | RAYLIB_DIR = C:/raylib 4 | INCLUDE_DIR = -I ./ -I $(RAYLIB_DIR)/raylib/src -I $(RAYLIB_DIR)/raygui/src 5 | LIBRARY_DIR = -L $(RAYLIB_DIR)/raylib/src 6 | DEFINES = -D _DEFAULT_SOURCE -D RAYLIB_BUILD_MODE=RELEASE -D $(PLATFORM) 7 | 8 | ifeq ($(PLATFORM),PLATFORM_DESKTOP) 9 | CC = g++ 10 | EXT = .exe 11 | CFLAGS ?= $(DEFINES) -O3 $(RAYLIB_DIR)/raylib/src/raylib.rc.data $(INCLUDE_DIR) $(LIBRARY_DIR) 12 | LIBS = -lraylib -lopengl32 -lgdi32 -lwinmm 13 | endif 14 | 15 | ifeq ($(PLATFORM),PLATFORM_WEB) 16 | CC = emcc 17 | EXT = .html 18 | CFLAGS ?= $(DEFINES) $(RAYLIB_DIR)/raylib/src/libraylib.bc -Os -s USE_GLFW=3 -s FORCE_FILESYSTEM=1 -s MAX_WEBGL_VERSION=2 -s ALLOW_MEMORY_GROWTH=1 --preload-file $(dir $<)resources@resources --shell-file ./shell.html $(INCLUDE_DIR) $(LIBRARY_DIR) 19 | endif 20 | 21 | SOURCE = $(wildcard *.cpp) 22 | HEADER = $(wildcard *.h) 23 | 24 | .PHONY: all 25 | 26 | all: looping 27 | 28 | looping: $(SOURCE) $(HEADER) 29 | $(CC) -o $@$(EXT) $(SOURCE) $(CFLAGS) $(LIBS) 30 | 31 | clean: 32 | rm looping$(EXT) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Animation Looping 2 | 3 | This repo contains the source code for all the demos from [this article](https://theorangeduck.com/page/creating-looping-animations-motion-capture). 4 | 5 | It uses [raylib](https://www.raylib.com/) or more specifically [raygui](https://github.com/raysan5/raygui) so if you have that installed it should be easy to play around and out. 6 | -------------------------------------------------------------------------------- /array.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | //-------------------------------------- 8 | 9 | // Basic type representing a pointer to some 10 | // data and the size of the data. `__restrict__` 11 | // here is used to indicate the data should not 12 | // alias against any other input parameters and 13 | // can sometimes produce important performance 14 | // gains. 15 | template 16 | struct slice1d 17 | { 18 | int size; 19 | T* __restrict__ data; 20 | 21 | slice1d(int _size, T* _data) : size(_size), data(_data) {} 22 | 23 | void zero() { memset((char*)data, 0, sizeof(T) * size); } 24 | void set(const T& x) { for (int i = 0; i < size; i++) { data[i] = x; } } 25 | 26 | inline T& operator()(int i) const { assert(i >= 0 && i < size); return data[i]; } 27 | 28 | slice1d& operator=(const slice1d& rhs) { assert(size == rhs.size); memcpy(data, rhs.data, rhs.size * sizeof(T)); return *this; }; 29 | }; 30 | 31 | // Same but for a 2d array of data. 32 | template 33 | struct slice2d 34 | { 35 | int rows, cols; 36 | T* __restrict__ data; 37 | 38 | slice2d(int _rows, int _cols, T* _data) : rows(_rows), cols(_cols), data(_data) {} 39 | 40 | void zero() { memset((char*)data, 0, sizeof(T) * rows * cols); } 41 | void set(const T& x) { for (int i = 0; i < rows * cols; i++) { data[i] = x; } } 42 | 43 | inline slice1d operator()(int i) const { assert(i >= 0 && i < rows); return slice1d(cols, &data[i * cols]); } 44 | inline T& operator()(int i, int j) const { assert(i >= 0 && i < rows && j >= 0 && j < cols); return data[i * cols + j]; } 45 | 46 | slice2d& operator=(const slice2d& rhs) { assert(rows == rhs.rows && cols == rhs.cols); memcpy(data, rhs.data, rhs.rows * rhs.cols * sizeof(T)); return *this; }; 47 | }; 48 | 49 | //-------------------------------------- 50 | 51 | // These types are used for the storage of arrays of data. 52 | // They implicitly cast to slices so can be given directly 53 | // as inputs to functions requiring them. 54 | template 55 | struct array1d 56 | { 57 | int size; 58 | T* data; 59 | 60 | array1d() : size(0), data(NULL) {} 61 | array1d(int _size) : array1d() { resize(_size); } 62 | array1d(const slice1d& rhs) : array1d() { resize(rhs.size); memcpy(data, rhs.data, rhs.size * sizeof(T)); } 63 | array1d(const array1d& rhs) : array1d() { resize(rhs.size); memcpy(data, rhs.data, rhs.size * sizeof(T)); } 64 | ~array1d() { resize(0); } 65 | 66 | array1d& operator=(const slice1d& rhs) { resize(rhs.size); memcpy(data, rhs.data, rhs.size * sizeof(T)); return *this; }; 67 | array1d& operator=(const array1d& rhs) { resize(rhs.size); memcpy(data, rhs.data, rhs.size * sizeof(T)); return *this; }; 68 | 69 | inline T& operator()(int i) const { assert(i >= 0 && i < size); return data[i]; } 70 | operator slice1d() const { return slice1d(size, data); } 71 | 72 | void zero() { memset(data, 0, sizeof(T) * size); } 73 | void set(const T& x) { for (int i = 0; i < size; i++) { data[i] = x; } } 74 | 75 | void resize(int _size) 76 | { 77 | if (_size == 0 && size != 0) 78 | { 79 | free(data); 80 | data = NULL; 81 | size = 0; 82 | } 83 | else if (_size > 0 && size == 0) 84 | { 85 | data = (T*)malloc(_size * sizeof(T)); 86 | size = _size; 87 | assert(data != NULL); 88 | } 89 | else if (_size > 0 && size > 0 && _size != size) 90 | { 91 | data = (T*)realloc(data, _size * sizeof(T)); 92 | size = _size; 93 | assert(data != NULL); 94 | } 95 | } 96 | }; 97 | 98 | template 99 | void array1d_write(const array1d& arr, FILE* f) 100 | { 101 | fwrite(&arr.size, sizeof(int), 1, f); 102 | size_t num = fwrite(arr.data, sizeof(T), arr.size, f); 103 | assert((int)num == arr.size); 104 | } 105 | 106 | template 107 | void array1d_read(array1d& arr, FILE* f) 108 | { 109 | int size; 110 | fread(&size, sizeof(int), 1, f); 111 | arr.resize(size); 112 | size_t num = fread(arr.data, sizeof(T), size, f); 113 | assert((int)num == size); 114 | } 115 | 116 | // Similar type but for 2d data 117 | template 118 | struct array2d 119 | { 120 | int rows, cols; 121 | T* data; 122 | 123 | array2d() : rows(0), cols(0), data(NULL) {} 124 | array2d(int _rows, int _cols) : array2d() { resize(_rows, _cols); } 125 | ~array2d() { resize(0, 0); } 126 | 127 | array2d& operator=(const array2d& rhs) { resize(rhs.rows, rhs.cols); memcpy(data, rhs.data, rhs.rows * rhs.cols * sizeof(T)); return *this; }; 128 | array2d& operator=(const slice2d& rhs) { resize(rhs.rows, rhs.cols); memcpy(data, rhs.data, rhs.rows * rhs.cols * sizeof(T)); return *this; }; 129 | 130 | inline slice1d operator()(int i) const { assert(i >= 0 && i < rows); return slice1d(cols, &data[i * cols]); } 131 | inline T& operator()(int i, int j) const { assert(i >= 0 && i < rows && j >= 0 && j < cols); return data[i * cols + j]; } 132 | operator slice2d() const { return slice2d(rows, cols, data); } 133 | 134 | slice2d slice(int start, int stop) const { return slice2d(stop - start, cols, data + start * cols); } 135 | 136 | void zero() { memset(data, 0, sizeof(T) * rows * cols); } 137 | void set(const T& x) { for (int i = 0; i < rows * cols; i++) { data[i] = x; } } 138 | 139 | void resize(int _rows, int _cols) 140 | { 141 | int _size = _rows * _cols; 142 | int size = rows * cols; 143 | 144 | if (_size == 0 && size != 0) 145 | { 146 | free(data); 147 | data = NULL; 148 | rows = 0; 149 | cols = 0; 150 | } 151 | else if (_size > 0 && size == 0) 152 | { 153 | data = (T*)malloc(_size * sizeof(T)); 154 | rows = _rows; 155 | cols = _cols; 156 | assert(data != NULL); 157 | } 158 | else if (_size > 0 && size > 0 && _size != size) 159 | { 160 | data = (T*)realloc(data, _size * sizeof(T)); 161 | rows = _rows; 162 | cols = _cols; 163 | assert(data != NULL); 164 | } 165 | } 166 | }; 167 | 168 | template 169 | void array2d_write(const array2d& arr, FILE* f) 170 | { 171 | fwrite(&arr.rows, sizeof(int), 1, f); 172 | fwrite(&arr.cols, sizeof(int), 1, f); 173 | size_t num = fwrite(arr.data, sizeof(T), arr.rows * arr.cols, f); 174 | assert((int)num == arr.rows * arr.cols); 175 | } 176 | 177 | template 178 | void array2d_read(array2d& arr, FILE* f) 179 | { 180 | int rows, cols; 181 | fread(&rows, sizeof(int), 1, f); 182 | fread(&cols, sizeof(int), 1, f); 183 | arr.resize(rows, cols); 184 | size_t num = fread(arr.data, sizeof(T), rows * cols, f); 185 | assert((int)num == rows * cols); 186 | } 187 | 188 | template 189 | void array2d_transpose(array2d& a0, const array2d& a1) 190 | { 191 | assert(a0.rows == a1.cols); 192 | assert(a0.cols == a1.rows); 193 | for (int i = 0; i < a1.rows; i++) 194 | { 195 | for (int j = 0; j < a1.cols; j++) 196 | { 197 | a0(j, i) = a1(i, j); 198 | } 199 | } 200 | } 201 | 202 | 203 | -------------------------------------------------------------------------------- /character.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vec.h" 4 | #include "quat.h" 5 | #include "array.h" 6 | 7 | #include 8 | #include 9 | 10 | //-------------------------------------- 11 | 12 | enum Bones 13 | { 14 | Bone_Entity = 0, 15 | Bone_Hips = 1, 16 | Bone_LeftUpLeg = 2, 17 | Bone_LeftLeg = 3, 18 | Bone_LeftFoot = 4, 19 | Bone_LeftToe = 5, 20 | Bone_RightUpLeg = 6, 21 | Bone_RightLeg = 7, 22 | Bone_RightFoot = 8, 23 | Bone_RightToe = 9, 24 | Bone_Spine = 10, 25 | Bone_Spine1 = 11, 26 | Bone_Spine2 = 12, 27 | Bone_Neck = 13, 28 | Bone_Head = 14, 29 | Bone_LeftShoulder = 15, 30 | Bone_LeftArm = 16, 31 | Bone_LeftForeArm = 17, 32 | Bone_LeftHand = 18, 33 | Bone_RightShoulder = 19, 34 | Bone_RightArm = 20, 35 | Bone_RightForeArm = 21, 36 | Bone_RightHand = 22, 37 | }; 38 | 39 | const char* BoneNames[] = 40 | { 41 | "Entity", 42 | "Hips", 43 | "LeftUpLeg", 44 | "LeftLeg", 45 | "LeftFoot", 46 | "LeftToe", 47 | "RightUpLeg", 48 | "RightLeg", 49 | "RightFoot", 50 | "RightToe", 51 | "Spine", 52 | "Spine1", 53 | "Spine2", 54 | "Neck", 55 | "Head", 56 | "LeftShoulder", 57 | "LeftArm", 58 | "LeftForeArm", 59 | "LeftHand", 60 | "RightShoulder", 61 | "RightArm", 62 | "RightForeArm", 63 | "RightHand" 64 | }; 65 | 66 | //-------------------------------------- 67 | 68 | struct character 69 | { 70 | array1d positions; 71 | array1d normals; 72 | array1d texcoords; 73 | array1d triangles; 74 | 75 | array2d bone_weights; 76 | array2d bone_indices; 77 | 78 | array1d bone_rest_positions; 79 | array1d bone_rest_rotations; 80 | }; 81 | 82 | void character_load(character& c, const char* filename) 83 | { 84 | FILE* f = fopen(filename, "rb"); 85 | assert(f != NULL); 86 | 87 | array1d_read(c.positions, f); 88 | array1d_read(c.normals, f); 89 | array1d_read(c.texcoords, f); 90 | array1d_read(c.triangles, f); 91 | 92 | array2d_read(c.bone_weights, f); 93 | array2d_read(c.bone_indices, f); 94 | 95 | array1d_read(c.bone_rest_positions, f); 96 | array1d_read(c.bone_rest_rotations, f); 97 | 98 | fclose(f); 99 | } 100 | 101 | //-------------------------------------- 102 | 103 | void linear_blend_skinning_positions( 104 | slice1d anim_positions, 105 | const slice1d rest_positions, 106 | const slice2d bone_weights, 107 | const slice2d bone_indices, 108 | const slice1d bone_rest_positions, 109 | const slice1d bone_rest_rotations, 110 | const slice1d bone_anim_positions, 111 | const slice1d bone_anim_rotations) 112 | { 113 | anim_positions.zero(); 114 | 115 | for (int i = 0; i < anim_positions.size; i++) 116 | { 117 | for (int j = 0; j < bone_indices.cols; j++) 118 | { 119 | if (bone_weights(i, j) > 0.0f) 120 | { 121 | int b = bone_indices(i, j); 122 | 123 | vec3 position = rest_positions(i); 124 | position = quat_inv_mul_vec3(bone_rest_rotations(b), position - bone_rest_positions(b)); 125 | position = quat_mul_vec3(bone_anim_rotations(b), position) + bone_anim_positions(b); 126 | 127 | anim_positions(i) = anim_positions(i) + bone_weights(i, j) * position; 128 | } 129 | } 130 | } 131 | } 132 | 133 | void linear_blend_skinning_normals( 134 | slice1d anim_normals, 135 | const slice1d rest_normals, 136 | const slice2d bone_weights, 137 | const slice2d bone_indices, 138 | const slice1d bone_rest_rotations, 139 | const slice1d bone_anim_rotations) 140 | { 141 | anim_normals.zero(); 142 | 143 | for (int i = 0; i < anim_normals.size; i++) 144 | { 145 | for (int j = 0; j < bone_indices.cols; j++) 146 | { 147 | if (bone_weights(i, j) > 0.0f) 148 | { 149 | int b = bone_indices(i, j); 150 | 151 | vec3 normal = rest_normals(i); 152 | normal = quat_inv_mul_vec3(bone_rest_rotations(b), normal); 153 | normal = quat_mul_vec3(bone_anim_rotations(b), normal); 154 | 155 | anim_normals(i) = anim_normals(i) + bone_weights(i, j) * normal; 156 | } 157 | } 158 | } 159 | 160 | for (int i = 0; i < anim_normals.size; i++) 161 | { 162 | anim_normals(i) = normalize(anim_normals(i)); 163 | } 164 | } 165 | 166 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define PIf 3.14159265358979323846f 6 | #define LN2f 0.69314718056f 7 | 8 | static inline float clampf(float x, float min, float max) 9 | { 10 | return x > max ? max : x < min ? min : x; 11 | } 12 | 13 | static inline float minf(float x, float y) 14 | { 15 | return x < y ? x : y; 16 | } 17 | 18 | static inline float maxf(float x, float y) 19 | { 20 | return x > y ? x : y; 21 | } 22 | 23 | static inline int min(int x, int y) 24 | { 25 | return x < y ? x : y; 26 | } 27 | 28 | static inline int max(int x, int y) 29 | { 30 | return x > y ? x : y; 31 | } 32 | 33 | 34 | static inline float squaref(float x) 35 | { 36 | return x*x; 37 | } 38 | 39 | static inline float lerpf(float x, float y, float a) 40 | { 41 | return (1.0f - a) * x + a * y; 42 | } 43 | 44 | static inline float signf(float x) 45 | { 46 | return x > 0.0f ? 1.0f : x < 0.0f ? -1.0f : 0.0f; 47 | } 48 | 49 | static inline float fast_negexpf(float x) 50 | { 51 | return 1.0f / (1.0f + x + 0.48f*x*x + 0.235f*x*x*x); 52 | } 53 | 54 | static inline float fast_atanf(float x) 55 | { 56 | float z = fabs(x); 57 | float w = z > 1.0f ? 1.0f / z : z; 58 | float y = (PIf / 4.0f)*w - w*(w - 1.0f)*(0.2447f + 0.0663f*w); 59 | return copysign(z > 1.0f ? PIf / 2.0f - y : y, x); 60 | } 61 | 62 | static inline int clamp(int x, int min, int max) 63 | { 64 | return x < min ? min : x > max ? max : x; 65 | } 66 | -------------------------------------------------------------------------------- /database.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | #include "vec.h" 5 | #include "quat.h" 6 | #include "array.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //-------------------------------------- 14 | 15 | struct database 16 | { 17 | array2d bone_positions; 18 | array2d bone_rotations; 19 | array1d bone_parents; 20 | 21 | array1d range_starts; 22 | array1d range_stops; 23 | 24 | int nframes() const { return bone_positions.rows; } 25 | int nbones() const { return bone_positions.cols; } 26 | int nranges() const { return range_starts.size; } 27 | }; 28 | 29 | void database_load(database& db, const char* filename) 30 | { 31 | FILE* f = fopen(filename, "rb"); 32 | assert(f != NULL); 33 | 34 | array2d_read(db.bone_positions, f); 35 | array2d_read(db.bone_rotations, f); 36 | array1d_read(db.bone_parents, f); 37 | 38 | array1d_read(db.range_starts, f); 39 | array1d_read(db.range_stops, f); 40 | 41 | fclose(f); 42 | } 43 | 44 | //-------------------------------------- 45 | 46 | // Here I am using a simple recursive version of forward kinematics 47 | void forward_kinematics( 48 | vec3& bone_position, 49 | quat& bone_rotation, 50 | const slice1d bone_positions, 51 | const slice1d bone_rotations, 52 | const slice1d bone_parents, 53 | const int bone) 54 | { 55 | if (bone_parents(bone) != -1) 56 | { 57 | vec3 parent_position; 58 | quat parent_rotation; 59 | 60 | forward_kinematics( 61 | parent_position, 62 | parent_rotation, 63 | bone_positions, 64 | bone_rotations, 65 | bone_parents, 66 | bone_parents(bone)); 67 | 68 | bone_position = quat_mul_vec3(parent_rotation, bone_positions(bone)) + parent_position; 69 | bone_rotation = quat_mul(parent_rotation, bone_rotations(bone)); 70 | } 71 | else 72 | { 73 | bone_position = bone_positions(bone); 74 | bone_rotation = bone_rotations(bone); 75 | } 76 | } 77 | 78 | // Compute forward kinematics of just some joints using a 79 | // mask to indicate which joints are already computed 80 | void forward_kinematics_partial( 81 | slice1d global_bone_positions, 82 | slice1d global_bone_rotations, 83 | slice1d global_bone_computed, 84 | const slice1d local_bone_positions, 85 | const slice1d local_bone_rotations, 86 | const slice1d bone_parents, 87 | int bone) 88 | { 89 | if (global_bone_computed(bone)) 90 | { 91 | return; 92 | } 93 | 94 | if (bone_parents(bone) == -1) 95 | { 96 | global_bone_positions(bone) = local_bone_positions(bone); 97 | global_bone_rotations(bone) = local_bone_rotations(bone); 98 | global_bone_computed(bone) = true; 99 | return; 100 | } 101 | 102 | if (!global_bone_computed(bone_parents(bone))) 103 | { 104 | forward_kinematics_partial( 105 | global_bone_positions, 106 | global_bone_rotations, 107 | global_bone_computed, 108 | local_bone_positions, 109 | local_bone_rotations, 110 | bone_parents, 111 | bone_parents(bone)); 112 | } 113 | 114 | vec3 parent_position = global_bone_positions(bone_parents(bone)); 115 | quat parent_rotation = global_bone_rotations(bone_parents(bone)); 116 | global_bone_positions(bone) = quat_mul_vec3(parent_rotation, local_bone_positions(bone)) + parent_position; 117 | global_bone_rotations(bone) = quat_mul(parent_rotation, local_bone_rotations(bone)); 118 | global_bone_computed(bone) = true; 119 | } 120 | 121 | // Compute forward kinematics for all joints 122 | void forward_kinematics_full( 123 | slice1d global_bone_positions, 124 | slice1d global_bone_rotations, 125 | const slice1d local_bone_positions, 126 | const slice1d local_bone_rotations, 127 | const slice1d bone_parents) 128 | { 129 | for (int i = 0; i < bone_parents.size; i++) 130 | { 131 | // Assumes bones are always sorted from root onwards 132 | assert(bone_parents(i) < i); 133 | 134 | if (bone_parents(i) == -1) 135 | { 136 | global_bone_positions(i) = local_bone_positions(i); 137 | global_bone_rotations(i) = local_bone_rotations(i); 138 | } 139 | else 140 | { 141 | vec3 parent_position = global_bone_positions(bone_parents(i)); 142 | quat parent_rotation = global_bone_rotations(bone_parents(i)); 143 | global_bone_positions(i) = quat_mul_vec3(parent_rotation, local_bone_positions(i)) + parent_position; 144 | global_bone_rotations(i) = quat_mul(parent_rotation, local_bone_rotations(i)); 145 | } 146 | } 147 | } 148 | 149 | // Compute backward kinematics for all joints 150 | void backward_kinematics_full( 151 | slice1d local_bone_positions, 152 | slice1d local_bone_rotations, 153 | const slice1d global_bone_positions, 154 | const slice1d global_bone_rotations, 155 | const slice1d bone_parents) 156 | { 157 | for (int i = 0; i < bone_parents.size; i++) 158 | { 159 | if (bone_parents(i) == -1) 160 | { 161 | local_bone_positions(i) = global_bone_positions(i); 162 | local_bone_rotations(i) = global_bone_rotations(i); 163 | } 164 | else 165 | { 166 | vec3 parent_position = global_bone_positions(bone_parents(i)); 167 | quat parent_rotation = global_bone_rotations(bone_parents(i)); 168 | 169 | local_bone_positions(i) = quat_inv_mul_vec3(parent_rotation, 170 | global_bone_positions(i) - parent_position); 171 | local_bone_rotations(i) = quat_inv_mul(parent_rotation, global_bone_rotations(i)); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /looping.cpp: -------------------------------------------------------------------------------- 1 | extern "C" 2 | { 3 | #include "raylib.h" 4 | #include "raymath.h" 5 | #define RAYGUI_IMPLEMENTATION 6 | #include "raygui.h" 7 | } 8 | #if defined(PLATFORM_WEB) 9 | #include 10 | #endif 11 | 12 | #include "common.h" 13 | #include "vec.h" 14 | #include "quat.h" 15 | #include "spring.h" 16 | #include "array.h" 17 | #include "character.h" 18 | #include "database.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | //-------------------------------------- 26 | 27 | static inline Vector3 to_Vector3(vec3 v) 28 | { 29 | return (Vector3){ v.x, v.y, v.z }; 30 | } 31 | 32 | //-------------------------------------- 33 | 34 | // Perform linear blend skinning and copy 35 | // result into mesh data. Update and upload 36 | // deformed vertex positions and normals to GPU 37 | void deform_character_mesh( 38 | Mesh& mesh, 39 | const character& c, 40 | const slice1d bone_anim_positions, 41 | const slice1d bone_anim_rotations, 42 | const slice1d bone_parents) 43 | { 44 | linear_blend_skinning_positions( 45 | slice1d(mesh.vertexCount, (vec3*)mesh.vertices), 46 | c.positions, 47 | c.bone_weights, 48 | c.bone_indices, 49 | c.bone_rest_positions, 50 | c.bone_rest_rotations, 51 | bone_anim_positions, 52 | bone_anim_rotations); 53 | 54 | linear_blend_skinning_normals( 55 | slice1d(mesh.vertexCount, (vec3*)mesh.normals), 56 | c.normals, 57 | c.bone_weights, 58 | c.bone_indices, 59 | c.bone_rest_rotations, 60 | bone_anim_rotations); 61 | 62 | UpdateMeshBuffer(mesh, 0, mesh.vertices, mesh.vertexCount * 3 * sizeof(float), 0); 63 | UpdateMeshBuffer(mesh, 2, mesh.normals, mesh.vertexCount * 3 * sizeof(float), 0); 64 | } 65 | 66 | Mesh make_character_mesh(character& c) 67 | { 68 | Mesh mesh = { 0 }; 69 | 70 | mesh.vertexCount = c.positions.size; 71 | mesh.triangleCount = c.triangles.size / 3; 72 | mesh.vertices = (float*)MemAlloc(c.positions.size * 3 * sizeof(float)); 73 | mesh.texcoords = (float*)MemAlloc(c.texcoords.size * 2 * sizeof(float)); 74 | mesh.normals = (float*)MemAlloc(c.normals.size * 3 * sizeof(float)); 75 | mesh.indices = (unsigned short*)MemAlloc(c.triangles.size * sizeof(unsigned short)); 76 | 77 | memcpy(mesh.vertices, c.positions.data, c.positions.size * 3 * sizeof(float)); 78 | memcpy(mesh.texcoords, c.texcoords.data, c.texcoords.size * 2 * sizeof(float)); 79 | memcpy(mesh.normals, c.normals.data, c.normals.size * 3 * sizeof(float)); 80 | memcpy(mesh.indices, c.triangles.data, c.triangles.size * sizeof(unsigned short)); 81 | 82 | UploadMesh(&mesh, true); 83 | 84 | return mesh; 85 | } 86 | 87 | //-------------------------------------- 88 | 89 | float orbit_camera_update_azimuth( 90 | const float azimuth, 91 | const float mouse_dx, 92 | const float dt) 93 | { 94 | return azimuth + 1.0f * dt * -mouse_dx; 95 | } 96 | 97 | float orbit_camera_update_altitude( 98 | const float altitude, 99 | const float mouse_dy, 100 | const float dt) 101 | { 102 | return clampf(altitude + 1.0f * dt * mouse_dy, 0.0, 0.4f * PIf); 103 | } 104 | 105 | float orbit_camera_update_distance( 106 | const float distance, 107 | const float dt) 108 | { 109 | return clampf(distance + 20.0f * dt * -GetMouseWheelMove(), 0.1f, 100.0f); 110 | } 111 | 112 | void orbit_camera_update( 113 | Camera3D& cam, 114 | float& camera_azimuth, 115 | float& camera_altitude, 116 | float& camera_distance, 117 | const vec3 target, 118 | const float mouse_dx, 119 | const float mouse_dy, 120 | const float dt) 121 | { 122 | camera_azimuth = orbit_camera_update_azimuth(camera_azimuth, mouse_dx, dt); 123 | camera_altitude = orbit_camera_update_altitude(camera_altitude, mouse_dy, dt); 124 | camera_distance = orbit_camera_update_distance(camera_distance, dt); 125 | 126 | quat rotation_azimuth = quat_from_angle_axis(camera_azimuth, vec3(0, 1, 0)); 127 | vec3 position = quat_mul_vec3(rotation_azimuth, vec3(0, 0, camera_distance)); 128 | vec3 axis = normalize(cross(position, vec3(0, 1, 0))); 129 | 130 | quat rotation_altitude = quat_from_angle_axis(camera_altitude, axis); 131 | 132 | vec3 eye = target + quat_mul_vec3(rotation_altitude, position); 133 | 134 | cam.target = (Vector3){ target.x, target.y, target.z }; 135 | cam.position = (Vector3){ eye.x, eye.y, eye.z }; 136 | } 137 | 138 | //-------------------------------------- 139 | 140 | void draw_axis(const vec3 pos, const quat rot, const float scale = 1.0f) 141 | { 142 | vec3 axis0 = pos + quat_mul_vec3(rot, scale * vec3(1.0f, 0.0f, 0.0f)); 143 | vec3 axis1 = pos + quat_mul_vec3(rot, scale * vec3(0.0f, 1.0f, 0.0f)); 144 | vec3 axis2 = pos + quat_mul_vec3(rot, scale * vec3(0.0f, 0.0f, 1.0f)); 145 | 146 | DrawLine3D(to_Vector3(pos), to_Vector3(axis0), RED); 147 | DrawLine3D(to_Vector3(pos), to_Vector3(axis1), GREEN); 148 | DrawLine3D(to_Vector3(pos), to_Vector3(axis2), BLUE); 149 | } 150 | 151 | //-------------------------------------- 152 | 153 | void compute_start_end_positional_difference( 154 | slice1d diff_pos, 155 | slice1d diff_vel, 156 | const slice2d pos, 157 | const float dt) 158 | { 159 | // Check we have at least 2 frames of animation 160 | assert(pos.rows >= 2); 161 | 162 | // Loop over every joint 163 | for (int j = 0; j < pos.cols; j++) 164 | { 165 | // Positional difference between first and last frame 166 | diff_pos(j) = pos(pos.rows-1, j) - pos(0, j); 167 | 168 | // Velocity difference between first and last frame 169 | diff_vel(j) = 170 | ((pos(pos.rows-1, j) - pos(pos.rows-2, j)) / dt) - 171 | ((pos( 1, j) - pos( 0, j)) / dt); 172 | } 173 | } 174 | 175 | void compute_start_end_rotational_difference( 176 | slice1d diff_rot, 177 | slice1d diff_vel, 178 | const slice2d rot, 179 | const float dt) 180 | { 181 | // Check we have at least 2 frames of animation 182 | assert(rot.rows >= 2); 183 | 184 | // Loop over every joint 185 | for (int j = 0; j < rot.cols; j++) 186 | { 187 | // Rotational difference between first and last frame 188 | // represented in scaled-angle-axis space 189 | diff_rot(j) = quat_to_scaled_angle_axis( 190 | quat_abs(quat_mul_inv(rot(rot.rows-1, j), rot(0, j)))); 191 | 192 | // Angular velocity difference between first and last frame 193 | diff_vel(j) = 194 | quat_differentiate_angular_velocity( 195 | rot(rot.rows-1, j), rot(rot.rows-2, j), dt) - 196 | quat_differentiate_angular_velocity( 197 | rot( 1, j), rot( 0, j), dt); 198 | } 199 | } 200 | 201 | //-------------------------------------- 202 | 203 | void apply_positional_offsets( 204 | slice2d out, 205 | const slice2d pos, 206 | const slice2d offsets) 207 | { 208 | // Loop over every frame 209 | for (int i = 0; i < pos.rows; i++) 210 | { 211 | // Loop over every joint 212 | for (int j = 0; j < pos.cols; j++) 213 | { 214 | // Simply add on offset 215 | out(i, j) = pos(i, j) + offsets(i, j); 216 | } 217 | } 218 | } 219 | 220 | void apply_rotational_offsets( 221 | slice2d out, 222 | const slice2d rot, 223 | const slice2d offsets) 224 | { 225 | // Loop over every frame 226 | for (int i = 0; i < rot.rows; i++) 227 | { 228 | // Loop over every joint 229 | for (int j = 0; j < rot.cols; j++) 230 | { 231 | // Convert back from scaled-angle-axis space and 232 | // multiply on the left. This rotates the first 233 | // frame toward the last frame. 234 | out(i, j) = quat_mul( 235 | quat_from_scaled_angle_axis(offsets(i, j)), 236 | rot(i, j)); 237 | } 238 | } 239 | } 240 | 241 | //-------------------------------------- 242 | 243 | vec3 decayed_offset( 244 | const vec3 x, // Initial Position 245 | const vec3 v, // Initial Velocity 246 | const float halflife, 247 | const float dt) 248 | { 249 | float y = halflife_to_damping(halflife) / 2.0f; 250 | vec3 j1 = v + x*y; 251 | float eydt = fast_negexpf(y*dt); 252 | 253 | return eydt*(x + j1*dt); 254 | } 255 | 256 | void compute_inertialize_start_offsets( 257 | slice2d offsets, 258 | const slice1d diff_pos, 259 | const slice1d diff_vel, 260 | const float halflife, 261 | const float dt) 262 | { 263 | for (int i = 0; i < offsets.rows; i++) 264 | { 265 | for (int j = 0; j < offsets.cols; j++) 266 | { 267 | offsets(i, j) = decayed_offset( 268 | diff_pos(j), 269 | diff_vel(j), 270 | halflife, 271 | i * dt); 272 | } 273 | } 274 | } 275 | 276 | void compute_inertialize_end_offsets( 277 | slice2d offsets, 278 | const slice1d diff_pos, 279 | const slice1d diff_vel, 280 | const float halflife, 281 | const float dt) 282 | { 283 | for (int i = 0; i < offsets.rows; i++) 284 | { 285 | for (int j = 0; j < offsets.cols; j++) 286 | { 287 | offsets(i, j) = decayed_offset( 288 | -diff_pos(j), 289 | diff_vel(j), 290 | halflife, 291 | ((offsets.rows-1) - i) * dt); 292 | } 293 | } 294 | } 295 | 296 | void compute_inertialize_both_offsets( 297 | slice2d offsets, 298 | const slice1d diff_pos, 299 | const slice1d diff_vel, 300 | const float halflife_start, 301 | const float halflife_end, 302 | const float ratio, 303 | const float dt) 304 | { 305 | // Check ratio of correction for start 306 | // and end is between 0 and 1 307 | assert(ratio >= 0.0f && ratio <= 1.0f); 308 | 309 | // Loop over every frame 310 | for (int i = 0; i < offsets.rows; i++) 311 | { 312 | // Loop over every joint 313 | for (int j = 0; j < offsets.cols; j++) 314 | { 315 | offsets(i, j) = 316 | // Decayed offset from start 317 | decayed_offset( 318 | ratio * diff_pos(j), 319 | ratio * diff_vel(j), 320 | halflife_start, 321 | i * dt) + 322 | // Decayed offset from end 323 | decayed_offset( 324 | (1.0f-ratio) * -diff_pos(j), 325 | (1.0f-ratio) * diff_vel(j), 326 | halflife_end, 327 | ((offsets.rows-1) - i) * dt); 328 | } 329 | } 330 | } 331 | 332 | //-------------------------------------- 333 | 334 | vec3 decayed_offset_cubic( 335 | const vec3 x, // Initial Position 336 | const vec3 v, // Initial Velocity 337 | const float blendtime, 338 | const float dt, 339 | const float eps=1e-8) 340 | { 341 | float t = clampf(dt / (blendtime + eps), 0, 1); 342 | 343 | vec3 d = x; 344 | vec3 c = v * blendtime; 345 | vec3 b = -3*d - 2*c; 346 | vec3 a = 2*d + c; 347 | 348 | return a*t*t*t + b*t*t + c*t + d; 349 | } 350 | 351 | void compute_inertialize_cubic_offsets( 352 | slice2d offsets, 353 | const slice1d diff_pos, 354 | const slice1d diff_vel, 355 | const float blendtime_start, 356 | const float blendtime_end, 357 | const float ratio, 358 | const float dt) 359 | { 360 | assert(ratio >= 0.0f && ratio <= 1.0f); 361 | 362 | for (int i = 0; i < offsets.rows; i++) 363 | { 364 | for (int j = 0; j < offsets.cols; j++) 365 | { 366 | offsets(i, j) = 367 | decayed_offset_cubic( 368 | ratio * diff_pos(j), 369 | ratio * diff_vel(j), 370 | blendtime_start, 371 | i * dt) + 372 | decayed_offset_cubic( 373 | (1.0f-ratio) * -diff_pos(j), 374 | (1.0f-ratio) * diff_vel(j), 375 | blendtime_end, 376 | ((offsets.rows-1) - i) * dt); 377 | } 378 | } 379 | } 380 | 381 | //-------------------------------------- 382 | 383 | vec3 decayed_velocity_offset( 384 | const vec3 v, // Initial Velocity 385 | const float halflife, 386 | const float dt) 387 | { 388 | float y = halflife_to_damping(halflife) / 2.0f; 389 | return fast_negexpf(y*dt)*v*dt; 390 | } 391 | 392 | vec3 decayed_velocity_offset_cubic( 393 | const vec3 v, // Initial Velocity 394 | const float blendtime, 395 | const float dt, 396 | const float eps=1e-8f) 397 | { 398 | float t = clampf(dt / (blendtime + eps), 0, 1); 399 | 400 | vec3 c = v * blendtime; 401 | vec3 b = -2*c; 402 | vec3 a = c; 403 | 404 | return a*t*t*t + b*t*t + c*t; 405 | } 406 | 407 | void compute_linear_offsets( 408 | slice2d offsets, 409 | const slice1d diff_pos, 410 | const float ratio) 411 | { 412 | for (int i = 0; i < offsets.rows; i++) 413 | { 414 | for (int j = 0; j < offsets.cols; j++) 415 | { 416 | offsets(i, j) = lerpf( 417 | ratio, 418 | (ratio-1.0f), 419 | ((float)i / (offsets.rows-1))) * diff_pos(j); 420 | } 421 | } 422 | } 423 | 424 | void compute_linear_inertialize_offsets( 425 | slice2d offsets, 426 | const slice1d diff_pos, 427 | const slice1d diff_vel, 428 | const float blendtime_start, 429 | const float blendtime_end, 430 | const float ratio, 431 | const float dt) 432 | { 433 | // Input sanity checks 434 | assert(ratio >= 0.0f && ratio <= 1.0f); 435 | assert(blendtime_start >= 0.0f); 436 | assert(blendtime_end >= 0.0f); 437 | 438 | // Loop over every frame 439 | for (int i = 0; i < offsets.rows; i++) 440 | { 441 | // Loop over every joint 442 | for (int j = 0; j < offsets.cols; j++) 443 | { 444 | offsets(i, j) = 445 | // Initial linear offset 446 | lerpf( 447 | ratio, 448 | (ratio-1.0f), 449 | ((float)i / (offsets.rows-1))) * diff_pos(j) + 450 | // Velocity offset at start 451 | decayed_velocity_offset_cubic( 452 | ratio * diff_vel(j), 453 | blendtime_start, 454 | i * dt) + 455 | // Velocity offset at end 456 | decayed_velocity_offset_cubic( 457 | (1.0f-ratio) * diff_vel(j), 458 | blendtime_end, 459 | ((offsets.rows-1) - i) * dt); 460 | } 461 | } 462 | } 463 | 464 | //-------------------------------------- 465 | 466 | float softfade(const float x, const float alpha) 467 | { 468 | return logf(1.0f + expf(alpha - 2.0f*alpha*x)) / alpha; 469 | } 470 | 471 | // Function using `softfade` to decay some offset 472 | vec3 decayed_offset_softfade( 473 | const vec3 x, // Initial Position 474 | const float duration, 475 | const float hardness, 476 | const float dt) 477 | { 478 | return x * softfade(dt / duration, hardness); 479 | } 480 | 481 | // Gradient of the `softfade` function at zero 482 | float softfade_grad_zero(const float alpha) 483 | { 484 | return (-2.0f * expf(alpha)) / (1.0f + expf(alpha)); 485 | } 486 | 487 | // Gradient of the `decayed_offset_softfade` 488 | // function with a `dt` of zero 489 | vec3 decayed_offset_softfade_grad_zero( 490 | const vec3 x, 491 | const float duration, 492 | const float hardness) 493 | { 494 | return x * (softfade_grad_zero(hardness) / duration); 495 | } 496 | 497 | void compute_softfade_start_end_difference( 498 | slice1d diff_pos, 499 | slice1d diff_vel, 500 | const slice2d pos, 501 | const float duration_start, 502 | const float duration_end, 503 | const float hardness_start, 504 | const float hardness_end, 505 | const float ratio, 506 | const float dt) 507 | { 508 | assert(pos.rows >= 2); 509 | 510 | // Loop over every joint 511 | for (int j = 0; j < pos.cols; j++) 512 | { 513 | // Positional difference between first and last frame 514 | diff_pos(j) = pos(pos.rows-1, j) - pos(0, j); 515 | 516 | // End frame velocity (including softfade) 517 | vec3 velocity_end = 518 | (pos(pos.rows-1, j) - pos(pos.rows-2, j)) / dt + 519 | decayed_offset_softfade_grad_zero( 520 | ratio * diff_pos(j), 521 | duration_start, 522 | hardness_start) * dt; 523 | 524 | // Start frame velocity (including softfade) 525 | vec3 velocity_start = 526 | (pos( 1, j) - pos( 0, j)) / dt + 527 | decayed_offset_softfade_grad_zero( 528 | (1.0f-ratio) * diff_pos(j), 529 | duration_end, 530 | hardness_end) * dt; 531 | 532 | // Velocity difference between first and last frame 533 | diff_vel(j) = velocity_end - velocity_start; 534 | } 535 | } 536 | 537 | void compute_softfade_start_end_difference( 538 | slice1d diff_rot, 539 | slice1d diff_vel, 540 | const slice2d rot, 541 | const float duration_start, 542 | const float duration_end, 543 | const float hardness_start, 544 | const float hardness_end, 545 | const float ratio, 546 | const float dt) 547 | { 548 | assert(rot.rows >= 2); 549 | 550 | // Loop over every joint 551 | for (int j = 0; j < rot.cols; j++) 552 | { 553 | // Rotational difference between first and last frame 554 | // represented in scaled-angle-axis space 555 | diff_rot(j) = quat_to_scaled_angle_axis( 556 | quat_abs(quat_mul_inv(rot(rot.rows-1, j), rot(0, j)))); 557 | 558 | // End frame velocity (including softfade) 559 | vec3 velocity_end = 560 | quat_differentiate_angular_velocity( 561 | rot(rot.rows-1, j), rot(rot.rows-2, j), dt) + 562 | decayed_offset_softfade_grad_zero( 563 | ratio * diff_rot(j), 564 | duration_start, 565 | hardness_start) * dt; 566 | 567 | // Start frame velocity (including softfade) 568 | vec3 velocity_start = 569 | quat_differentiate_angular_velocity( 570 | rot( 1, j), rot( 0, j), dt) + 571 | decayed_offset_softfade_grad_zero( 572 | (1.0f-ratio) * diff_rot(j), 573 | duration_end, 574 | hardness_end) * dt; 575 | 576 | // Velocity difference between first and last frame 577 | diff_vel(j) = velocity_end - velocity_start; 578 | } 579 | } 580 | 581 | void compute_softfade_inertialize_offsets( 582 | slice2d offsets, 583 | const slice1d diff_pos, 584 | const slice1d diff_vel, 585 | const float blendtime_start, 586 | const float blendtime_end, 587 | const float duration_start, 588 | const float duration_end, 589 | const float hardness_start, 590 | const float hardness_end, 591 | const float ratio, 592 | const float dt) 593 | { 594 | // Loop over every frame 595 | for (int i = 0; i < offsets.rows; i++) 596 | { 597 | // Loop over every joint 598 | for (int j = 0; j < offsets.cols; j++) 599 | { 600 | offsets(i, j) = 601 | // Softfade at start 602 | decayed_offset_softfade( 603 | ratio * diff_pos(j), 604 | duration_start, 605 | hardness_start, 606 | i * dt) + 607 | // Softfade at end 608 | decayed_offset_softfade( 609 | (1.0f-ratio) * -diff_pos(j), 610 | duration_end, 611 | hardness_end, 612 | ((offsets.rows-1) - i) * dt) + 613 | // Velocity offset at start 614 | decayed_velocity_offset_cubic( 615 | ratio * diff_vel(j), 616 | blendtime_start, 617 | i * dt) + 618 | // Velocity offset at end 619 | decayed_velocity_offset_cubic( 620 | (1.0f-ratio) * diff_vel(j), 621 | blendtime_end, 622 | ((offsets.rows-1) - i) * dt); 623 | } 624 | } 625 | } 626 | 627 | //-------------------------------------- 628 | 629 | void compute_root_inertialize_offsets( 630 | slice2d offsets_pos, 631 | slice2d offsets_rot, 632 | const slice2d pos, 633 | const slice2d rot, 634 | const float blendtime_start, 635 | const float blendtime_end, 636 | const float ratio, 637 | const float dt) 638 | { 639 | // Check animation is at least 2 frames 640 | assert(rot.rows >= 2 && pos.rows >= 2); 641 | 642 | // Get root start and end rotations 643 | quat root_start = rot( 0, 0); 644 | quat root_end = rot(rot.rows-1, 0); 645 | 646 | // Compute character space difference in positional velocity 647 | vec3 pos_vel_end = quat_inv_mul_vec3(root_end, 648 | (pos(pos.rows-1, 0) - pos(pos.rows-2, 0)) / dt); 649 | vec3 pos_vel_start = quat_inv_mul_vec3(root_start, 650 | (pos( 1, 0) - pos( 0, 0)) / dt); 651 | vec3 diff_pos_vel = pos_vel_end - pos_vel_start; 652 | 653 | // Compute character space difference in rotational velocity 654 | vec3 rot_vel_end = quat_inv_mul_vec3(root_end, 655 | quat_differentiate_angular_velocity( 656 | rot(rot.rows-1, 0), rot(rot.rows-2, 0), dt)); 657 | vec3 rot_vel_start = quat_inv_mul_vec3(root_start, 658 | quat_differentiate_angular_velocity( 659 | rot( 1, 0), rot( 0, 0), dt)); 660 | vec3 diff_rot_vel = rot_vel_end - rot_vel_start; 661 | 662 | // Loop over frames 663 | for (int i = 0; i < rot.rows; i++) 664 | { 665 | // Root positional offset 666 | offsets_pos(i, 0) = 667 | // Velocity offset at start 668 | decayed_velocity_offset_cubic( 669 | ratio * quat_mul_vec3(root_start, diff_pos_vel), 670 | blendtime_start, 671 | i * dt) + 672 | // velocity offset at end 673 | decayed_velocity_offset_cubic( 674 | (1.0f-ratio) * quat_mul_vec3(root_end, diff_pos_vel), 675 | blendtime_end, 676 | ((rot.rows-1) - i) * dt); 677 | 678 | // Root rotational offset 679 | offsets_rot(i, 0) = 680 | // Velocity offset at start 681 | decayed_velocity_offset_cubic( 682 | ratio * quat_mul_vec3(root_start, diff_rot_vel), 683 | blendtime_start, 684 | i * dt) + 685 | // velocity offset at end 686 | decayed_velocity_offset_cubic( 687 | (1.0f-ratio) * quat_mul_vec3(root_end, diff_rot_vel), 688 | blendtime_end, 689 | ((rot.rows-1) - i) * dt); 690 | } 691 | } 692 | 693 | //-------------------------------------- 694 | 695 | static inline void animation_sample( 696 | slice1d sampled, 697 | const slice2d pos, 698 | const float time, 699 | const float dt) 700 | { 701 | int st = (int)(time / dt); 702 | int s0 = clamp(st + 0, 0, pos.rows - 1); 703 | int s1 = clamp(st + 1, 0, pos.rows - 1); 704 | float alpha = fmod(time / dt, 1.0f); 705 | 706 | for (int j = 0; j < pos.cols; j++) 707 | { 708 | sampled(j) = lerp(pos(s0, j), pos(s1, j), alpha); 709 | } 710 | } 711 | 712 | static inline void animation_sample( 713 | slice1d sampled, 714 | const slice2d rot, 715 | const float time, 716 | const float dt) 717 | { 718 | int st = (int)(time / dt); 719 | int s0 = clamp(st + 0, 0, rot.rows - 1); 720 | int s1 = clamp(st + 1, 0, rot.rows - 1); 721 | float alpha = fmod(time / dt, 1.0f); 722 | 723 | for (int j = 0; j < rot.cols; j++) 724 | { 725 | sampled(j) = quat_nlerp(rot(s0, j), rot(s1, j), alpha); 726 | } 727 | } 728 | 729 | static inline void animation_sample_root_local_velocity( 730 | vec3& sampled_root_velocity, 731 | vec3& sampled_root_angular_velocity, 732 | const slice2d pos, 733 | const slice2d rot, 734 | const float time, 735 | const float dt) 736 | { 737 | int s0 = clamp((int)(time / dt), 0, pos.rows - 1); 738 | float alpha = fmod(time / dt, 1.0f); 739 | 740 | if (s0 == 0) 741 | { 742 | sampled_root_velocity = quat_inv_mul_vec3( 743 | rot(s0, 0), 744 | (pos(s0 + 1, 0) - pos(s0 + 0, 0)) / dt); 745 | 746 | sampled_root_angular_velocity = quat_inv_mul_vec3( 747 | rot(s0, 0), 748 | quat_differentiate_angular_velocity(rot(s0 + 1, 0), rot(s0 + 0, 0), dt)); 749 | } 750 | else 751 | { 752 | sampled_root_velocity = quat_inv_mul_vec3( 753 | rot(s0, 0), 754 | (pos(s0 - 0, 0) - pos(s0 - 1, 0)) / dt); 755 | 756 | sampled_root_angular_velocity = quat_inv_mul_vec3( 757 | rot(s0, 0), 758 | quat_differentiate_angular_velocity(rot(s0 - 0, 0), rot(s0 - 1, 0), dt)); 759 | } 760 | } 761 | 762 | //-------------------------------------- 763 | 764 | void update_callback(void* args) 765 | { 766 | ((std::function*)args)->operator()(); 767 | } 768 | 769 | enum 770 | { 771 | LOOP_UNLOOPED, 772 | LOOP_INERTIALIZE_START, 773 | LOOP_INERTIALIZE_END, 774 | LOOP_INERTIALIZE_BOTH, 775 | LOOP_INERTIALIZE_CUBIC, 776 | LOOP_LINEAR, 777 | LOOP_LINEAR_INERTIALIZE, 778 | LOOP_SOFTFADE_INERTIALIZE, 779 | }; 780 | 781 | static inline void loop_animation( 782 | slice2d looped_bone_positions, 783 | slice2d looped_bone_rotations, 784 | slice2d offset_bone_positions, 785 | slice2d offset_bone_rotations, 786 | const slice2d raw_bone_positions, 787 | const slice2d raw_bone_rotations, 788 | const int loop_mode, 789 | const float halflife_start, 790 | const float halflife_end, 791 | const float blendtime_start, 792 | const float blendtime_end, 793 | const float ratio, 794 | const float softfade_duration_start, 795 | const float softfade_duration_end, 796 | const float softfade_hardness_start, 797 | const float softfade_hardness_end, 798 | const float root_blendtime_start, 799 | const float root_blendtime_end, 800 | const bool inertialize_root, 801 | const float dt) 802 | { 803 | array1d pos_diff(raw_bone_positions.cols); 804 | array1d vel_diff(raw_bone_positions.cols); 805 | array1d rot_diff(raw_bone_positions.cols); 806 | array1d ang_diff(raw_bone_positions.cols); 807 | 808 | switch (loop_mode) 809 | { 810 | case LOOP_UNLOOPED: break; 811 | 812 | case LOOP_INERTIALIZE_START: 813 | case LOOP_INERTIALIZE_END: 814 | case LOOP_INERTIALIZE_BOTH: 815 | case LOOP_INERTIALIZE_CUBIC: 816 | case LOOP_LINEAR: 817 | case LOOP_LINEAR_INERTIALIZE: 818 | compute_start_end_positional_difference(pos_diff, vel_diff, raw_bone_positions, dt); 819 | compute_start_end_rotational_difference(rot_diff, ang_diff, raw_bone_rotations, dt); 820 | break; 821 | 822 | case LOOP_SOFTFADE_INERTIALIZE: 823 | compute_softfade_start_end_difference( 824 | pos_diff, 825 | vel_diff, 826 | raw_bone_positions, 827 | softfade_duration_start, 828 | softfade_duration_end, 829 | softfade_hardness_start, 830 | softfade_hardness_end, 831 | ratio, 832 | dt); 833 | 834 | compute_softfade_start_end_difference( 835 | rot_diff, 836 | ang_diff, 837 | raw_bone_rotations, 838 | softfade_duration_start, 839 | softfade_duration_end, 840 | softfade_hardness_start, 841 | softfade_hardness_end, 842 | ratio, 843 | dt); 844 | break; 845 | 846 | default: assert(false); 847 | } 848 | 849 | switch (loop_mode) 850 | { 851 | case LOOP_UNLOOPED: 852 | offset_bone_positions.zero(); 853 | offset_bone_rotations.zero(); 854 | break; 855 | 856 | case LOOP_INERTIALIZE_START: 857 | compute_inertialize_start_offsets(offset_bone_positions, pos_diff, vel_diff, halflife_start, dt); 858 | compute_inertialize_start_offsets(offset_bone_rotations, rot_diff, ang_diff, halflife_start, dt); 859 | break; 860 | 861 | case LOOP_INERTIALIZE_END: 862 | compute_inertialize_end_offsets(offset_bone_positions, pos_diff, vel_diff, halflife_end, dt); 863 | compute_inertialize_end_offsets(offset_bone_rotations, rot_diff, ang_diff, halflife_end, dt); 864 | break; 865 | 866 | case LOOP_INERTIALIZE_BOTH: 867 | compute_inertialize_both_offsets(offset_bone_positions, pos_diff, vel_diff, halflife_start, halflife_end, ratio, dt); 868 | compute_inertialize_both_offsets(offset_bone_rotations, rot_diff, ang_diff, halflife_start, halflife_end, ratio, dt); 869 | break; 870 | 871 | case LOOP_INERTIALIZE_CUBIC: 872 | compute_inertialize_cubic_offsets(offset_bone_positions, pos_diff, vel_diff, blendtime_start, blendtime_end, ratio, dt); 873 | compute_inertialize_cubic_offsets(offset_bone_rotations, rot_diff, ang_diff, blendtime_start, blendtime_end, ratio, dt); 874 | break; 875 | 876 | case LOOP_LINEAR: 877 | compute_linear_offsets(offset_bone_positions, pos_diff, ratio); 878 | compute_linear_offsets(offset_bone_rotations, rot_diff, ratio); 879 | break; 880 | 881 | case LOOP_LINEAR_INERTIALIZE: 882 | compute_linear_inertialize_offsets(offset_bone_positions, pos_diff, vel_diff, blendtime_start, blendtime_end, ratio, dt); 883 | compute_linear_inertialize_offsets(offset_bone_rotations, rot_diff, ang_diff, blendtime_start, blendtime_end, ratio, dt); 884 | break; 885 | 886 | case LOOP_SOFTFADE_INERTIALIZE: 887 | compute_softfade_inertialize_offsets( 888 | offset_bone_positions, 889 | pos_diff, 890 | vel_diff, 891 | blendtime_start, 892 | blendtime_end, 893 | softfade_duration_start, 894 | softfade_duration_end, 895 | softfade_hardness_start, 896 | softfade_hardness_end, 897 | ratio, 898 | dt); 899 | compute_softfade_inertialize_offsets( 900 | offset_bone_rotations, 901 | rot_diff, 902 | ang_diff, 903 | blendtime_start, 904 | blendtime_end, 905 | softfade_duration_start, 906 | softfade_duration_end, 907 | softfade_hardness_start, 908 | softfade_hardness_end, 909 | ratio, 910 | dt); 911 | break; 912 | 913 | default: assert(false); 914 | } 915 | 916 | if (inertialize_root) 917 | { 918 | compute_root_inertialize_offsets( 919 | offset_bone_positions, 920 | offset_bone_rotations, 921 | raw_bone_positions, 922 | raw_bone_rotations, 923 | root_blendtime_start, 924 | root_blendtime_end, 925 | ratio, 926 | dt); 927 | } 928 | else 929 | { 930 | for (int i = 0; i < offset_bone_positions.rows; i++) 931 | { 932 | offset_bone_positions(i, 0) = vec3(); 933 | offset_bone_rotations(i, 0) = vec3(); 934 | } 935 | } 936 | 937 | apply_positional_offsets(looped_bone_positions, raw_bone_positions, offset_bone_positions); 938 | apply_rotational_offsets(looped_bone_rotations, raw_bone_rotations, offset_bone_rotations); 939 | } 940 | 941 | enum 942 | { 943 | ROOT_FIX = 0, 944 | ROOT_ACCUMULATE = 1, 945 | ROOT_LOOP = 2 946 | }; 947 | 948 | struct anim_clip 949 | { 950 | const char* name; 951 | int start, stop; 952 | }; 953 | 954 | int main(void) 955 | { 956 | // Init Window 957 | 958 | const int screen_width = 1280; 959 | const int screen_height = 720; 960 | 961 | SetConfigFlags(FLAG_VSYNC_HINT); 962 | SetConfigFlags(FLAG_MSAA_4X_HINT); 963 | InitWindow(screen_width, screen_height, "raylib [animation looping]"); 964 | SetTargetFPS(60); 965 | 966 | GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE)); 967 | 968 | // Camera 969 | 970 | Camera3D camera = { 0 }; 971 | camera.position = (Vector3){ 2.0f, 3.0f, 5.0f }; 972 | camera.target = (Vector3){ -0.5f, 1.0f, 0.0f }; 973 | camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; 974 | camera.fovy = 45.0f; 975 | camera.projection = CAMERA_PERSPECTIVE; 976 | 977 | float camera_azimuth = 0.0f; 978 | float camera_altitude = 0.4f; 979 | float camera_distance = 4.0f; 980 | 981 | // Ground Plane 982 | 983 | Shader ground_plane_shader = LoadShader("./resources/checkerboard.vs", "./resources/checkerboard.fs"); 984 | Mesh ground_plane_mesh = GenMeshPlane(20.0f, 20.0f, 10, 10); 985 | Model ground_plane_model = LoadModelFromMesh(ground_plane_mesh); 986 | ground_plane_model.materials[0].shader = ground_plane_shader; 987 | 988 | // Character 989 | 990 | character character_data; 991 | character_load(character_data, "./resources/character.bin"); 992 | 993 | Shader character_shader = LoadShader("./resources/character.vs", "./resources/character.fs"); 994 | Mesh character_mesh = make_character_mesh(character_data); 995 | Model character_model = LoadModelFromMesh(character_mesh); 996 | character_model.materials[0].shader = character_shader; 997 | 998 | // Load Animation Data 999 | 1000 | database db; 1001 | database_load(db, "./resources/database.bin"); 1002 | 1003 | // Clips 1004 | 1005 | std::vector clips = 1006 | { 1007 | { "Short Run Cycle", 358, 394 }, 1008 | { "Long Run Cycle", 624, 698 }, 1009 | { "Plant and Turn Run", 697, 768 }, 1010 | { "Arc Turn Run", 3253, 3337 }, 1011 | { "S Turn Run", 3419, 3543 }, 1012 | { "Run to Strafe", 4765, 4850 }, 1013 | { "Sidestepping Run", 5608, 5671 }, 1014 | { "Short Walk Cycle", 13158, 13210 }, 1015 | { "90 Degree Turn Walk", 13362, 13456 }, 1016 | { "Plant and Turn Walk", 14464, 14565 }, 1017 | { "Arc Turn Walk", 15284, 15336 }, 1018 | { "Dance 1", 30198, 30245 }, 1019 | { "Dance 2", 30301, 30403 }, 1020 | { "Dance 3", 30957, 31157 }, 1021 | { "Dance 4", 32995, 33052 }, 1022 | { "Punch", 40183, 40292 }, 1023 | { "Kick", 40454, 40525 }, 1024 | { "Uppercut", 41401, 41510 }, 1025 | }; 1026 | 1027 | std::string clip_combo = ""; 1028 | for (int i = 0; i < clips.size(); i++) 1029 | { 1030 | clip_combo += clips[i].name; 1031 | if (i != (int)clips.size() - 1) 1032 | { 1033 | clip_combo += ";"; 1034 | } 1035 | } 1036 | 1037 | bool clip_edit = false; 1038 | int clip_index = 0; 1039 | 1040 | int start_frame = clips[0].start; 1041 | int stop_frame = clips[0].stop; 1042 | 1043 | // Root Motion 1044 | 1045 | int root_motion = ROOT_FIX; 1046 | bool root_motion_edit = false; 1047 | vec3 root_position = vec3(); 1048 | quat root_rotation = quat(); 1049 | 1050 | // Looping 1051 | 1052 | int loop_mode = LOOP_UNLOOPED; 1053 | bool loop_mode_edit = false; 1054 | float halflife_start = 0.1f; 1055 | float halflife_end = 0.1f; 1056 | float blendtime_start = 0.2f; 1057 | float blendtime_end = 0.2f; 1058 | float ratio = 0.5f; 1059 | float softfade_duration_start = 0.4f; 1060 | float softfade_duration_end = 0.4f; 1061 | float softfade_hardness_start = 16.0f; 1062 | float softfade_hardness_end = 16.0f; 1063 | float root_blendtime_start = 0.5f; 1064 | float root_blendtime_end = 0.5f; 1065 | bool inertialize_root = true; 1066 | 1067 | // Playback 1068 | 1069 | const float dt = 1.0f / 60.0f; 1070 | float time = 0.0f; 1071 | float playrate = 1.0f; 1072 | bool paused = false; 1073 | 1074 | // Pose Data 1075 | 1076 | array2d raw_bone_positions; 1077 | array2d raw_bone_rotations; 1078 | array2d looped_bone_positions; 1079 | array2d looped_bone_rotations; 1080 | array2d offset_bone_positions; 1081 | array2d offset_bone_rotations; 1082 | 1083 | raw_bone_positions = db.bone_positions.slice(start_frame, stop_frame); 1084 | raw_bone_rotations = db.bone_rotations.slice(start_frame, stop_frame); 1085 | 1086 | looped_bone_positions = db.bone_positions.slice(start_frame, stop_frame); 1087 | looped_bone_rotations = db.bone_rotations.slice(start_frame, stop_frame); 1088 | 1089 | offset_bone_positions.resize(looped_bone_positions.rows, looped_bone_positions.cols); 1090 | offset_bone_rotations.resize(looped_bone_rotations.rows, looped_bone_rotations.cols); 1091 | 1092 | array1d sampled_bone_positions(raw_bone_positions.cols); 1093 | array1d sampled_bone_rotations(raw_bone_rotations.cols); 1094 | 1095 | animation_sample(sampled_bone_positions, looped_bone_positions, time, dt); 1096 | animation_sample(sampled_bone_rotations, looped_bone_rotations, time, dt); 1097 | 1098 | array1d global_bone_positions(db.nbones()); 1099 | array1d global_bone_rotations(db.nbones()); 1100 | array1d global_bone_computed(db.nbones()); 1101 | 1102 | // Go 1103 | 1104 | auto update_func = [&]() 1105 | { 1106 | // Update Camera 1107 | 1108 | orbit_camera_update( 1109 | camera, 1110 | camera_azimuth, 1111 | camera_altitude, 1112 | camera_distance, 1113 | vec3(0, 0.5f, 0), 1114 | (IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(0)) ? GetMouseDelta().x : 0.0f, 1115 | (IsKeyDown(KEY_LEFT_CONTROL) && IsMouseButtonDown(0)) ? GetMouseDelta().y : 0.0f, 1116 | dt); 1117 | 1118 | // Tick 1119 | 1120 | if (!paused) 1121 | { 1122 | time = fmod(time + playrate * dt, (raw_bone_positions.rows - 1) * dt); 1123 | } 1124 | 1125 | // Loop Clip 1126 | 1127 | raw_bone_positions = db.bone_positions.slice(start_frame, stop_frame); 1128 | raw_bone_rotations = db.bone_rotations.slice(start_frame, stop_frame); 1129 | 1130 | looped_bone_positions.resize(raw_bone_positions.rows, raw_bone_positions.cols); 1131 | looped_bone_rotations.resize(raw_bone_rotations.rows, raw_bone_rotations.cols); 1132 | 1133 | offset_bone_positions.resize(raw_bone_positions.rows, raw_bone_positions.cols); 1134 | offset_bone_rotations.resize(raw_bone_rotations.rows, raw_bone_rotations.cols); 1135 | 1136 | loop_animation( 1137 | looped_bone_positions, 1138 | looped_bone_rotations, 1139 | offset_bone_positions, 1140 | offset_bone_rotations, 1141 | raw_bone_positions, 1142 | raw_bone_rotations, 1143 | loop_mode, 1144 | halflife_start, 1145 | halflife_end, 1146 | blendtime_start, 1147 | blendtime_end, 1148 | ratio, 1149 | softfade_duration_start, 1150 | softfade_duration_end, 1151 | softfade_hardness_start, 1152 | softfade_hardness_end, 1153 | root_blendtime_start, 1154 | root_blendtime_end, 1155 | inertialize_root, 1156 | dt); 1157 | 1158 | // Sample Animation 1159 | 1160 | animation_sample(sampled_bone_positions, looped_bone_positions, time, dt); 1161 | animation_sample(sampled_bone_rotations, looped_bone_rotations, time, dt); 1162 | 1163 | // Root Motion 1164 | 1165 | if (root_motion == ROOT_FIX) 1166 | { 1167 | sampled_bone_positions(0) = vec3(); 1168 | sampled_bone_rotations(0) = quat(); 1169 | root_position = vec3(); 1170 | root_rotation = quat(); 1171 | } 1172 | else if (root_motion == ROOT_LOOP) 1173 | { 1174 | sampled_bone_positions(0) = quat_inv_mul_vec3(looped_bone_rotations(0,0), sampled_bone_positions(0) - looped_bone_positions(0,0)); 1175 | sampled_bone_rotations(0) = quat_inv_mul(looped_bone_rotations(0,0), sampled_bone_rotations(0)); 1176 | root_position = vec3(); 1177 | root_rotation = quat(); 1178 | } 1179 | else if (root_motion == ROOT_ACCUMULATE) 1180 | { 1181 | vec3 sampled_root_velocity, sampled_root_angular_velocity; 1182 | animation_sample_root_local_velocity( 1183 | sampled_root_velocity, 1184 | sampled_root_angular_velocity, 1185 | looped_bone_positions, 1186 | looped_bone_rotations, 1187 | time, 1188 | dt); 1189 | 1190 | root_position = quat_mul_vec3(root_rotation, playrate * dt * sampled_root_velocity) + root_position; 1191 | root_rotation = quat_mul(quat_from_scaled_angle_axis(quat_mul_vec3(root_rotation, sampled_root_angular_velocity) * playrate * dt), root_rotation); 1192 | 1193 | sampled_bone_positions(0) = root_position; 1194 | sampled_bone_rotations(0) = root_rotation; 1195 | } 1196 | 1197 | // Done! 1198 | 1199 | forward_kinematics_full( 1200 | global_bone_positions, 1201 | global_bone_rotations, 1202 | sampled_bone_positions, 1203 | sampled_bone_rotations, 1204 | db.bone_parents); 1205 | 1206 | // Render 1207 | 1208 | BeginDrawing(); 1209 | ClearBackground(RAYWHITE); 1210 | 1211 | BeginMode3D(camera); 1212 | 1213 | deform_character_mesh( 1214 | character_mesh, 1215 | character_data, 1216 | global_bone_positions, 1217 | global_bone_rotations, 1218 | db.bone_parents); 1219 | 1220 | DrawModel(character_model, (Vector3){0.0f, 0.0f, 0.0f}, 1.0f, RAYWHITE); 1221 | 1222 | draw_axis(global_bone_positions(0), global_bone_rotations(0), 0.25f); 1223 | 1224 | DrawModel(ground_plane_model, (Vector3){0.0f, -0.01f, 0.0f}, 1.0f, WHITE); 1225 | DrawGrid(20, 1.0f); 1226 | draw_axis(vec3(), quat()); 1227 | 1228 | EndMode3D(); 1229 | 1230 | // UI 1231 | 1232 | //--------- 1233 | 1234 | float ui_hei_plot = 470; 1235 | 1236 | DrawRectangle( 20, ui_hei_plot, 1240, 120, Fade(RAYWHITE, 0.5f)); 1237 | DrawRectangleLines( 20, ui_hei_plot, 1240, 120, GRAY); 1238 | DrawLine(20 + 1240/2, ui_hei_plot, 20 + 1240/2, ui_hei_plot + 120, GRAY); 1239 | 1240 | DrawRectangle( 20, ui_hei_plot + 130, 1240, 60, Fade(RAYWHITE, 0.5f)); 1241 | DrawRectangleLines( 20, ui_hei_plot + 130, 1240, 60, GRAY); 1242 | DrawLine(20 + 1240/2, ui_hei_plot + 130, 20 + 1240/2, ui_hei_plot + 130 + 60, GRAY); 1243 | DrawLine(20, ui_hei_plot + 130 + 30, 20 + 1240, ui_hei_plot + 130 + 30, Fade(GRAY, 0.5f)); 1244 | 1245 | float plot_min = FLT_MAX; 1246 | float plot_max = FLT_MIN; 1247 | float offset_plot_min = FLT_MAX; 1248 | float offset_plot_max = FLT_MIN; 1249 | 1250 | for (int i = 0; i < looped_bone_positions.rows; i++) 1251 | { 1252 | plot_min = minf(plot_min, raw_bone_positions(i, 1).x); 1253 | plot_max = maxf(plot_max, raw_bone_positions(i, 1).x); 1254 | plot_min = minf(plot_min, looped_bone_positions(i, 1).x); 1255 | plot_max = maxf(plot_max, looped_bone_positions(i, 1).x); 1256 | offset_plot_min = minf(offset_plot_min, offset_bone_positions(i, 1).x); 1257 | offset_plot_max = maxf(offset_plot_max, offset_bone_positions(i, 1).x); 1258 | } 1259 | 1260 | float plot_scale = (plot_max - plot_min) + 0.01f; 1261 | float plot_center = plot_min + (plot_max - plot_min) / 2.0f; 1262 | float offset_scale = 2.0f * plot_scale; 1263 | 1264 | for (int r = 0; r < 2; r++) 1265 | { 1266 | float time_ratio = time / ((looped_bone_positions.rows - 1) * dt); 1267 | 1268 | DrawLine(20 + time_ratio * 1240/2, ui_hei_plot, 20 + time_ratio * 1240/2, ui_hei_plot + 120, PURPLE); 1269 | DrawLine(20 + 1240/2 + time_ratio/2 * 1240, ui_hei_plot, 20 + 1240/2 + time_ratio * 1240/2, ui_hei_plot + 120, PURPLE); 1270 | 1271 | for (int i = 0; i < looped_bone_positions.rows - 1; i++) 1272 | { 1273 | float offset0 = (((float)i + 0) / (looped_bone_positions.rows - 1)) * 1240/2 + 20 + r * 1240/2; 1274 | float offset1 = (((float)i + 1) / (looped_bone_positions.rows - 1)) * 1240/2 + 20 + r * 1240/2; 1275 | float raw_value0 = ((raw_bone_positions(i+0, 1).x - plot_center) / plot_scale) * 120 + ui_hei_plot + 60; 1276 | float raw_value1 = ((raw_bone_positions(i+1, 1).x - plot_center) / plot_scale) * 120 + ui_hei_plot + 60; 1277 | 1278 | float loop_value0 = ((looped_bone_positions(i+0, 1).x - plot_center) / plot_scale) * 120 + ui_hei_plot + 60; 1279 | float loop_value1 = ((looped_bone_positions(i+1, 1).x - plot_center) / plot_scale) * 120 + ui_hei_plot + 60; 1280 | 1281 | DrawLine(offset0, loop_value0, offset1, loop_value1, RED); 1282 | DrawLine(offset0, raw_value0, offset1, raw_value1, Fade(RED, 0.5f)); 1283 | } 1284 | 1285 | for (int i = 0; i < offset_bone_positions.rows - 1; i++) 1286 | { 1287 | float offset0 = (((float)i + 0) / (offset_bone_positions.rows - 1)) * 1240/2 + 20 + r * 1240/2; 1288 | float offset1 = (((float)i + 1) / (offset_bone_positions.rows - 1)) * 1240/2 + 20 + r * 1240/2; 1289 | float value0 = (offset_bone_positions(i+0, 1).x / offset_scale) * 120 + ui_hei_plot + 130 + 30; 1290 | float value1 = (offset_bone_positions(i+1, 1).x / offset_scale) * 120 + ui_hei_plot + 130 + 30; 1291 | 1292 | DrawLine(offset0, value0, offset1, value1, GREEN); 1293 | } 1294 | } 1295 | 1296 | //--------- 1297 | 1298 | float ui_ctrl_hei = 20; 1299 | 1300 | GuiGroupBox((Rectangle){ 1010, ui_ctrl_hei, 250, 60 }, "controls"); 1301 | 1302 | GuiLabel((Rectangle){ 1030, ui_ctrl_hei + 10, 200, 20 }, "Ctrl + Left Click - Move Camera"); 1303 | GuiLabel((Rectangle){ 1030, ui_ctrl_hei + 30, 200, 20 }, "Mouse Wheel - Zoom"); 1304 | 1305 | //--------- 1306 | 1307 | float ui_playback_hei = 90; 1308 | 1309 | GuiGroupBox((Rectangle){ 960, ui_playback_hei, 300, 120 }, "playback"); 1310 | 1311 | if (GuiButton((Rectangle){ 1020, ui_playback_hei + 10, 140, 20 }, paused ? "play" : "pause")) 1312 | { 1313 | paused = !paused; 1314 | } 1315 | 1316 | GuiSliderBar( 1317 | (Rectangle){ 1020, ui_playback_hei + 40, 140, 20 }, 1318 | "Playrate", 1319 | TextFormat("%7.2f", playrate), 1320 | &playrate, 1321 | 0.0f, 1322 | 2.0f); 1323 | 1324 | if (GuiButton((Rectangle){ 1200, ui_playback_hei + 40, 40, 20 }, "reset")) 1325 | { 1326 | playrate = 1.0f; 1327 | } 1328 | 1329 | GuiLabel((Rectangle){ 1020, ui_playback_hei + 70, 200, 20 }, "Root Motion"); 1330 | 1331 | if (GuiDropdownBox( 1332 | (Rectangle){ 1020, ui_playback_hei + 90, 140, 20 }, 1333 | "Fix;Accumulate;Loop", 1334 | &root_motion, 1335 | root_motion_edit)) 1336 | { 1337 | root_motion_edit = !root_motion_edit; 1338 | } 1339 | 1340 | if (GuiButton((Rectangle){ 1200, ui_playback_hei + 90, 40, 20 }, "reset")) 1341 | { 1342 | root_position = vec3(); 1343 | root_rotation = quat(); 1344 | } 1345 | 1346 | //--------- 1347 | 1348 | GuiGroupBox((Rectangle){ 350, 20, 160, 40 }, "Clip"); 1349 | 1350 | if (GuiDropdownBox( 1351 | (Rectangle){ 360, 30, 140, 20 }, 1352 | clip_combo.c_str(), 1353 | &clip_index, 1354 | clip_edit)) 1355 | { 1356 | clip_edit = !clip_edit; 1357 | start_frame = clips[clip_index].start; 1358 | stop_frame = clips[clip_index].stop; 1359 | time = 0.0f; 1360 | } 1361 | 1362 | //--------- 1363 | 1364 | GuiGroupBox((Rectangle){ 20, 20, 310, 400 }, "Looping Methods"); 1365 | 1366 | GuiSliderBar( 1367 | (Rectangle){ 170, 30, 120, 20 }, 1368 | "halflife start", 1369 | TextFormat("%5.3f", halflife_start), 1370 | &halflife_start, 0.0f, 0.2f); 1371 | 1372 | GuiSliderBar( 1373 | (Rectangle){ 170, 60, 120, 20 }, 1374 | "halflife end", 1375 | TextFormat("%5.3f", halflife_end), 1376 | &halflife_end, 0.0f, 0.2f); 1377 | 1378 | GuiSliderBar( 1379 | (Rectangle){ 170, 90, 120, 20 }, 1380 | "blend time start", 1381 | TextFormat("%5.3f", blendtime_start), 1382 | &blendtime_start, 0.01f, 1.0f); 1383 | 1384 | GuiSliderBar( 1385 | (Rectangle){ 170, 120, 120, 20 }, 1386 | "blend time end", 1387 | TextFormat("%5.3f", blendtime_end), 1388 | &blendtime_end, 0.01f, 1.0f); 1389 | 1390 | GuiSliderBar( 1391 | (Rectangle){ 170, 150, 120, 20 }, 1392 | "ratio", 1393 | TextFormat("%5.3f", ratio), 1394 | &ratio, 0.0f, 1.0f); 1395 | 1396 | GuiSliderBar( 1397 | (Rectangle){ 170, 180, 120, 20 }, 1398 | "softfade duration start", 1399 | TextFormat("%5.3f", softfade_duration_start), 1400 | &softfade_duration_start, 0.01f, 1.0f); 1401 | 1402 | GuiSliderBar( 1403 | (Rectangle){ 170, 210, 120, 20 }, 1404 | "softfade duration end", 1405 | TextFormat("%5.3f", softfade_duration_end), 1406 | &softfade_duration_end, 0.01f, 1.0f); 1407 | 1408 | GuiSliderBar( 1409 | (Rectangle){ 170, 240, 120, 20 }, 1410 | "softfade hardness start", 1411 | TextFormat("%5.3f", softfade_hardness_start), 1412 | &softfade_hardness_start, 1.0f, 50.0f); 1413 | 1414 | GuiSliderBar( 1415 | (Rectangle){ 170, 270, 120, 20 }, 1416 | "softfade hardness end", 1417 | TextFormat("%5.3f", softfade_hardness_end), 1418 | &softfade_hardness_end, 1.0f, 50.0f); 1419 | 1420 | GuiCheckBox( 1421 | (Rectangle){ 170, 300, 20, 20 }, 1422 | "inertialize root", 1423 | &inertialize_root); 1424 | 1425 | GuiSliderBar( 1426 | (Rectangle){ 170, 330, 120, 20 }, 1427 | "root blend time start", 1428 | TextFormat("%5.3f", root_blendtime_start), 1429 | &root_blendtime_start, 0.01f, 1.0f); 1430 | 1431 | GuiSliderBar( 1432 | (Rectangle){ 170, 360, 120, 20 }, 1433 | "root blend time end", 1434 | TextFormat("%5.3f", root_blendtime_end), 1435 | &root_blendtime_end, 0.01f, 1.0f); 1436 | 1437 | if (GuiDropdownBox( 1438 | (Rectangle){ 170, 390, 120, 20 }, 1439 | "Unlooped;Inertialize Start;Inertialize End;" 1440 | "Inertialize Both;Inertialize Cubic;Linear;Linear Inertialize;" 1441 | "Softfade Inertialize", 1442 | &loop_mode, 1443 | loop_mode_edit)) 1444 | { 1445 | loop_mode_edit = !loop_mode_edit; 1446 | } 1447 | 1448 | //--------- 1449 | 1450 | float ui_hei_anim = 670; 1451 | 1452 | GuiGroupBox((Rectangle){ 20, ui_hei_anim, 1240, 40 }, "animation"); 1453 | 1454 | GuiSliderBar( 1455 | (Rectangle){ 80, ui_hei_anim + 10, 1100, 20 }, 1456 | "time", 1457 | TextFormat("%5.3f (%i)", time, start_frame + (int)(time / dt)), 1458 | &time, 1459 | 0.0f, (looped_bone_positions.rows - 1) * dt); 1460 | 1461 | //--------- 1462 | 1463 | EndDrawing(); 1464 | 1465 | }; 1466 | 1467 | #if defined(PLATFORM_WEB) 1468 | std::function u{update_func}; 1469 | emscripten_set_main_loop_arg(update_callback, &u, 0, 1); 1470 | #else 1471 | while (!WindowShouldClose()) 1472 | { 1473 | update_func(); 1474 | } 1475 | #endif 1476 | 1477 | // Unload stuff and finish 1478 | UnloadModel(character_model); 1479 | UnloadModel(ground_plane_model); 1480 | UnloadShader(character_shader); 1481 | UnloadShader(ground_plane_shader); 1482 | 1483 | CloseWindow(); 1484 | 1485 | return 0; 1486 | } -------------------------------------------------------------------------------- /quat.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vec.h" 4 | 5 | struct quat 6 | { 7 | quat() : w(1.0f), x(0.0f), y(0.0f), z(0.0f) {} 8 | quat(float _w, float _x, float _y, float _z) : w(_w), x(_x), y(_y), z(_z) {} 9 | 10 | float w, x, y, z; 11 | }; 12 | 13 | static inline quat operator*(quat q, float s) 14 | { 15 | return quat(q.w * s, q.x * s, q.y * s, q.z * s); 16 | } 17 | 18 | static inline quat operator*(float s, quat q) 19 | { 20 | return quat(q.w * s, q.x * s, q.y * s, q.z * s); 21 | } 22 | 23 | static inline quat operator+(quat q, quat p) 24 | { 25 | return quat(q.w + p.w, q.x + p.x, q.y + p.y, q.z + p.z); 26 | } 27 | 28 | static inline quat operator-(quat q, quat p) 29 | { 30 | return quat(q.w - p.w, q.x - p.x, q.y - p.y, q.z - p.z); 31 | } 32 | 33 | static inline quat operator/(quat q, float s) 34 | { 35 | return quat(q.w / s, q.x / s, q.y / s, q.z / s); 36 | } 37 | 38 | static inline quat operator-(quat q) 39 | { 40 | return quat(-q.w, -q.x, -q.y, -q.z); 41 | } 42 | 43 | static inline quat quat_normalize(quat q, const float eps=1e-8f) 44 | { 45 | return q / (sqrtf(q.w*q.w + q.x*q.x + q.y*q.y + q.z*q.z) + eps); 46 | } 47 | 48 | static inline quat quat_inv(quat q) 49 | { 50 | return quat(q.w, -q.x, -q.y, -q.z); 51 | } 52 | 53 | static inline quat quat_mul(quat q, quat p) 54 | { 55 | return quat( 56 | p.w*q.w - p.x*q.x - p.y*q.y - p.z*q.z, 57 | p.w*q.x + p.x*q.w - p.y*q.z + p.z*q.y, 58 | p.w*q.y + p.x*q.z + p.y*q.w - p.z*q.x, 59 | p.w*q.z - p.x*q.y + p.y*q.x + p.z*q.w); 60 | } 61 | 62 | static inline quat quat_inv_mul(quat q, quat p) 63 | { 64 | return quat_mul(quat_inv(q), p); 65 | } 66 | 67 | static inline quat quat_mul_inv(quat q, quat p) 68 | { 69 | return quat_mul(q, quat_inv(p)); 70 | } 71 | 72 | static inline vec3 quat_mul_vec3(quat q, vec3 v) 73 | { 74 | vec3 t = 2.0f * cross(vec3(q.x, q.y, q.z), v); 75 | return v + q.w * t + cross(vec3(q.x, q.y, q.z), t); 76 | } 77 | 78 | static inline vec3 quat_inv_mul_vec3(quat q, vec3 v) 79 | { 80 | return quat_mul_vec3(quat_inv(q), v); 81 | } 82 | 83 | static inline quat quat_abs(quat x) 84 | { 85 | return x.w < 0.0 ? -x : x; 86 | } 87 | 88 | static inline quat quat_exp(vec3 v, float eps=1e-8f) 89 | { 90 | float halfangle = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); 91 | 92 | if (halfangle < eps) 93 | { 94 | return quat_normalize(quat(1.0f, v.x, v.y, v.z)); 95 | } 96 | else 97 | { 98 | float c = cosf(halfangle); 99 | float s = sinf(halfangle) / halfangle; 100 | return quat(c, s * v.x, s * v.y, s * v.z); 101 | } 102 | } 103 | 104 | static inline vec3 quat_log(quat q, float eps=1e-8f) 105 | { 106 | float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z); 107 | 108 | if (length < eps) 109 | { 110 | return vec3(q.x, q.y, q.z); 111 | } 112 | else 113 | { 114 | float halfangle = acosf(clampf(q.w, -1.0f, 1.0f)); 115 | return halfangle * (vec3(q.x, q.y, q.z) / length); 116 | } 117 | } 118 | 119 | static inline quat quat_from_scaled_angle_axis(vec3 v, float eps=1e-8f) 120 | { 121 | return quat_exp(v / 2.0f, eps); 122 | } 123 | 124 | static inline vec3 quat_to_scaled_angle_axis(quat q, float eps=1e-8f) 125 | { 126 | return 2.0f * quat_log(q, eps); 127 | } 128 | 129 | static inline vec3 quat_differentiate_angular_velocity( 130 | quat next, quat curr, float dt, float eps=1e-8f) 131 | { 132 | return quat_to_scaled_angle_axis( 133 | quat_abs(quat_mul_inv(next, curr)), eps) / dt; 134 | } 135 | 136 | static inline quat quat_integrate_angular_velocity( 137 | vec3 vel, quat curr, float dt, float eps=1e-8f) 138 | { 139 | return quat_mul(quat_from_scaled_angle_axis(vel * dt, eps), curr); 140 | } 141 | 142 | static inline quat quat_from_angle_axis(float angle, vec3 axis) 143 | { 144 | float c = cosf(angle / 2.0f); 145 | float s = sinf(angle / 2.0f); 146 | return quat(c, s * axis.x, s * axis.y, s * axis.z); 147 | } 148 | 149 | static inline void quat_to_angle_axis(quat q, float& angle, vec3& axis, float eps=1e-8f) 150 | { 151 | float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z); 152 | 153 | if (length < eps) 154 | { 155 | angle = 0.0f; 156 | axis = vec3(1.0f, 0.0f, 0.0f); 157 | } 158 | else 159 | { 160 | angle = 2.0f * acosf(clampf(q.w, -1.0f, 1.0f)); 161 | axis = vec3(q.x, q.y, q.z) / length; 162 | } 163 | } 164 | 165 | static inline float quat_dot(quat q, quat p) 166 | { 167 | return q.w*p.w + q.x*p.x + q.y*p.y + q.z*p.z; 168 | } 169 | 170 | static inline quat quat_nlerp(quat q, quat p, float alpha) 171 | { 172 | return quat_normalize(quat( 173 | lerpf(q.w, p.w, alpha), 174 | lerpf(q.x, p.x, alpha), 175 | lerpf(q.y, p.y, alpha), 176 | lerpf(q.z, p.z, alpha))); 177 | } 178 | 179 | static inline quat quat_nlerp_shortest(quat q, quat p, float alpha) 180 | { 181 | if (quat_dot(q, p) < 0.0f) 182 | { 183 | p = -p; 184 | } 185 | 186 | return quat_nlerp(q, p, alpha); 187 | } 188 | 189 | static inline quat quat_slerp_shortest(quat q, quat p, float alpha, float eps=1e-5f) 190 | { 191 | if (quat_dot(q, p) < 0.0f) 192 | { 193 | p = -p; 194 | } 195 | 196 | float dot = quat_dot(q, p); 197 | float theta = acosf(clampf(dot, -1.0f, 1.0f)); 198 | 199 | if (theta < eps) 200 | { 201 | return quat_nlerp(q, p, alpha); 202 | } 203 | 204 | quat r = quat_normalize(p - q*dot); 205 | 206 | return q * cosf(theta * alpha) + r * sinf(theta * alpha); 207 | } 208 | 209 | // Taken from https://zeux.io/2015/07/23/approximating-slerp/ 210 | static inline quat quat_slerp_shortest_approx(quat q, quat p, float alpha) 211 | { 212 | float ca = quat_dot(q, p); 213 | 214 | if (ca < 0.0f) 215 | { 216 | p = -p; 217 | } 218 | 219 | float d = fabsf(ca); 220 | float a = 1.0904f + d * (-3.2452f + d * (3.55645f - d * 1.43519f)); 221 | float b = 0.848013f + d * (-1.06021f + d * 0.215638f); 222 | float k = a * (alpha - 0.5f) * (alpha - 0.5f) + b; 223 | float oalpha = alpha + alpha * (alpha - 0.5f) * (alpha - 1) * k; 224 | 225 | return quat_nlerp(q, p, oalpha); 226 | } 227 | 228 | static inline float quat_angle_between(quat q, quat p) 229 | { 230 | quat diff = quat_abs(quat_mul_inv(q, p)); 231 | return 2.0f * acosf(clampf(diff.w, -1.0f, 1.0f)); 232 | } 233 | 234 | static inline quat quat_between(vec3 p, vec3 q) 235 | { 236 | vec3 c = cross(p, q); 237 | 238 | return quat_normalize(quat( 239 | sqrtf(dot(p, p) * dot(q, q)) + dot(p, q), 240 | c.x, 241 | c.y, 242 | c.z)); 243 | } 244 | 245 | static inline quat quat_from_euler_xyz(float x, float y, float z) 246 | { 247 | return quat_mul( 248 | quat_from_angle_axis(z, vec3(0,0,1)), 249 | quat_mul( 250 | quat_from_angle_axis(y, vec3(0,1,0)), 251 | quat_from_angle_axis(x, vec3(1,0,0)))); 252 | } 253 | 254 | static inline void quat_swing_twist( 255 | quat& swing, 256 | quat& twist, 257 | const quat q, 258 | const vec3 axis) 259 | { 260 | vec3 p = dot(vec3(q.x, q.y, q.z), axis) * axis; 261 | twist = quat_normalize(quat(q.w, p.x, p.y, p.z)); 262 | swing = -quat_mul_inv(q, twist); 263 | } 264 | 265 | -------------------------------------------------------------------------------- /resources/bvh.py: -------------------------------------------------------------------------------- 1 | import re, os, ntpath 2 | import numpy as np 3 | 4 | channelmap = { 5 | 'Xrotation': 'x', 6 | 'Yrotation': 'y', 7 | 'Zrotation': 'z' 8 | } 9 | 10 | channelmap_inv = { 11 | 'x': 'Xrotation', 12 | 'y': 'Yrotation', 13 | 'z': 'Zrotation', 14 | } 15 | 16 | ordermap = { 17 | 'x': 0, 18 | 'y': 1, 19 | 'z': 2, 20 | } 21 | 22 | def load(filename, order=None): 23 | 24 | f = open(filename, "r") 25 | 26 | i = 0 27 | active = -1 28 | end_site = False 29 | 30 | names = [] 31 | orients = np.array([]).reshape((0, 4)) 32 | offsets = np.array([]).reshape((0, 3)) 33 | parents = np.array([], dtype=int) 34 | 35 | # Parse the file, line by line 36 | for line in f: 37 | 38 | if "HIERARCHY" in line: continue 39 | if "MOTION" in line: continue 40 | 41 | rmatch = re.match(r"ROOT (\w+)", line) 42 | if rmatch: 43 | names.append(rmatch.group(1)) 44 | offsets = np.append(offsets, np.array([[0, 0, 0]]), axis=0) 45 | orients = np.append(orients, np.array([[1, 0, 0, 0]]), axis=0) 46 | parents = np.append(parents, active) 47 | active = (len(parents) - 1) 48 | continue 49 | 50 | if "{" in line: continue 51 | 52 | if "}" in line: 53 | if end_site: 54 | end_site = False 55 | else: 56 | active = parents[active] 57 | continue 58 | 59 | offmatch = re.match(r"\s*OFFSET\s+([\-\d\.e]+)\s+([\-\d\.e]+)\s+([\-\d\.e]+)", line) 60 | if offmatch: 61 | if not end_site: 62 | offsets[active] = np.array([list(map(float, offmatch.groups()))]) 63 | continue 64 | 65 | chanmatch = re.match(r"\s*CHANNELS\s+(\d+)", line) 66 | if chanmatch: 67 | channels = int(chanmatch.group(1)) 68 | if order is None: 69 | channelis = 0 if channels == 3 else 3 70 | channelie = 3 if channels == 3 else 6 71 | parts = line.split()[2 + channelis:2 + channelie] 72 | if any([p not in channelmap for p in parts]): 73 | continue 74 | order = "".join([channelmap[p] for p in parts]) 75 | continue 76 | 77 | jmatch = re.match("\s*JOINT\s+(\w+)", line) 78 | if jmatch: 79 | names.append(jmatch.group(1)) 80 | offsets = np.append(offsets, np.array([[0, 0, 0]]), axis=0) 81 | orients = np.append(orients, np.array([[1, 0, 0, 0]]), axis=0) 82 | parents = np.append(parents, active) 83 | active = (len(parents) - 1) 84 | continue 85 | 86 | if "End Site" in line: 87 | end_site = True 88 | continue 89 | 90 | fmatch = re.match("\s*Frames:\s+(\d+)", line) 91 | if fmatch: 92 | fnum = int(fmatch.group(1)) 93 | positions = offsets[np.newaxis].repeat(fnum, axis=0) 94 | rotations = np.zeros((fnum, len(orients), 3)) 95 | continue 96 | 97 | fmatch = re.match("\s*Frame Time:\s+([\d\.]+)", line) 98 | if fmatch: 99 | frametime = float(fmatch.group(1)) 100 | continue 101 | 102 | dmatch = line.strip().split(' ') 103 | if dmatch: 104 | data_block = np.array(list(map(float, dmatch))) 105 | N = len(parents) 106 | fi = i 107 | if channels == 3: 108 | positions[fi, 0:1] = data_block[0:3] 109 | rotations[fi, :] = data_block[3:].reshape(N, 3) 110 | elif channels == 6: 111 | data_block = data_block.reshape(N, 6) 112 | positions[fi, :] = data_block[:, 0:3] 113 | rotations[fi, :] = data_block[:, 3:6] 114 | elif channels == 9: 115 | positions[fi, 0] = data_block[0:3] 116 | data_block = data_block[3:].reshape(N - 1, 9) 117 | rotations[fi, 1:] = data_block[:, 3:6] 118 | positions[fi, 1:] += data_block[:, 0:3] * data_block[:, 6:9] 119 | else: 120 | raise Exception("Too many channels! %i" % channels) 121 | 122 | i += 1 123 | 124 | f.close() 125 | 126 | return { 127 | 'rotations': rotations, 128 | 'positions': positions, 129 | 'offsets': offsets, 130 | 'parents': parents, 131 | 'names': names, 132 | 'order': order 133 | } 134 | 135 | 136 | def save_joint(f, data, t, i, save_order, order='zyx', save_positions=False): 137 | 138 | save_order.append(i) 139 | 140 | f.write("%sJOINT %s\n" % (t, data['names'][i])) 141 | f.write("%s{\n" % t) 142 | t += '\t' 143 | 144 | f.write("%sOFFSET %f %f %f\n" % (t, data['offsets'][i,0], data['offsets'][i,1], data['offsets'][i,2])) 145 | 146 | if save_positions: 147 | f.write("%sCHANNELS 6 Xposition Yposition Zposition %s %s %s \n" % (t, 148 | channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) 149 | else: 150 | f.write("%sCHANNELS 3 %s %s %s\n" % (t, 151 | channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) 152 | 153 | end_site = True 154 | 155 | for j in range(len(data['parents'])): 156 | if data['parents'][j] == i: 157 | t = save_joint(f, data, t, j, save_order, order=order, save_positions=save_positions) 158 | end_site = False 159 | 160 | if end_site: 161 | f.write("%sEnd Site\n" % t) 162 | f.write("%s{\n" % t) 163 | t += '\t' 164 | f.write("%sOFFSET %f %f %f\n" % (t, 0.0, 0.0, 0.0)) 165 | t = t[:-1] 166 | f.write("%s}\n" % t) 167 | 168 | t = t[:-1] 169 | f.write("%s}\n" % t) 170 | 171 | return t 172 | 173 | 174 | def save(filename, data, frametime=1.0/60.0, save_positions=False): 175 | 176 | order = data['order'] 177 | 178 | with open(filename, 'w') as f: 179 | 180 | t = "" 181 | f.write("%sHIERARCHY\n" % t) 182 | f.write("%sROOT %s\n" % (t, data['names'][0])) 183 | f.write("%s{\n" % t) 184 | t += '\t' 185 | 186 | f.write("%sOFFSET %f %f %f\n" % (t, data['offsets'][0,0], data['offsets'][0,1], data['offsets'][0,2]) ) 187 | f.write("%sCHANNELS 6 Xposition Yposition Zposition %s %s %s \n" % 188 | (t, channelmap_inv[order[0]], channelmap_inv[order[1]], channelmap_inv[order[2]])) 189 | 190 | save_order = [0] 191 | 192 | for i in range(len(data['parents'])): 193 | if data['parents'][i] == 0: 194 | t = save_joint(f, data, t, i, save_order, order=order, save_positions=save_positions) 195 | 196 | t = t[:-1] 197 | f.write("%s}\n" % t) 198 | 199 | rots, poss = data['rotations'], data['positions'] 200 | 201 | f.write("MOTION\n") 202 | f.write("Frames: %i\n" % len(rots)); 203 | f.write("Frame Time: %f\n" % frametime); 204 | 205 | for i in range(rots.shape[0]): 206 | for j in save_order: 207 | 208 | if save_positions or j == 0: 209 | 210 | f.write("%f %f %f %f %f %f " % ( 211 | poss[i,j,0], poss[i,j,1], poss[i,j,2], 212 | rots[i,j,ordermap[order[0]]], rots[i,j,ordermap[order[1]]], rots[i,j,ordermap[order[2]]])) 213 | 214 | else: 215 | 216 | f.write("%f %f %f " % ( 217 | rots[i,j,ordermap[order[0]]], rots[i,j,ordermap[order[1]]], rots[i,j,ordermap[order[2]]])) 218 | 219 | f.write("\n") 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /resources/character.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangeduck/Animation-Looping/e6d7f06ad25252f0e4c82079350e53f3423ecc5d/resources/character.bin -------------------------------------------------------------------------------- /resources/character.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | in vec3 fragPosition; 5 | in vec2 fragTexCoord; 6 | in vec4 fragColor; 7 | in vec3 fragNormal; 8 | 9 | uniform vec4 colDiffuse; 10 | 11 | out vec4 finalColor; 12 | 13 | 14 | void main() 15 | { 16 | vec3 light_dir = normalize(vec3(0.25, -0.8, 0.1)); 17 | 18 | float half_lambert = (dot(-light_dir, fragNormal) + 1.0) / 2.0; 19 | 20 | finalColor = vec4(half_lambert * colDiffuse.xyz + 0.1, 1.0); 21 | } 22 | -------------------------------------------------------------------------------- /resources/character.vs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 vertexPosition; 4 | in vec2 vertexTexCoord; 5 | in vec3 vertexNormal; 6 | in vec4 vertexColor; 7 | 8 | uniform mat4 mvp; 9 | uniform mat4 matModel; 10 | uniform mat4 matNormal; 11 | 12 | out vec3 fragPosition; 13 | out vec2 fragTexCoord; 14 | out vec4 fragColor; 15 | out vec3 fragNormal; 16 | 17 | void main() 18 | { 19 | fragPosition = vec3(matModel * vec4(vertexPosition, 1.0)); 20 | fragTexCoord = vertexTexCoord; 21 | fragColor = vertexColor; 22 | fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 1.0))); 23 | 24 | gl_Position = mvp * vec4(vertexPosition, 1.0); 25 | } 26 | -------------------------------------------------------------------------------- /resources/checkerboard.fs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | precision mediump float; 3 | 4 | in vec3 fragPosition; 5 | in vec2 fragTexCoord; 6 | in vec4 fragColor; 7 | in vec3 fragNormal; 8 | 9 | uniform vec4 colDiffuse; 10 | 11 | out vec4 finalColor; 12 | 13 | void main() 14 | { 15 | float total = floor(fragPosition.x * 2.0f) + 16 | floor(fragPosition.z * 2.0f); 17 | 18 | finalColor = mod(total, 2.0f) == 0.0f ? 19 | vec4(0.8f, 0.8f, 0.8f, 1.0f) : 20 | vec4(0.85f, 0.85f, 0.85f, 1.0f); 21 | } 22 | -------------------------------------------------------------------------------- /resources/checkerboard.vs: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | in vec3 vertexPosition; 4 | in vec2 vertexTexCoord; 5 | in vec3 vertexNormal; 6 | in vec4 vertexColor; 7 | 8 | uniform mat4 mvp; 9 | uniform mat4 matModel; 10 | uniform mat4 matNormal; 11 | 12 | out vec3 fragPosition; 13 | out vec2 fragTexCoord; 14 | out vec4 fragColor; 15 | out vec3 fragNormal; 16 | 17 | void main() 18 | { 19 | fragPosition = vec3(matModel * vec4(vertexPosition, 1.0f)); 20 | fragTexCoord = vertexTexCoord; 21 | fragColor = vertexColor; 22 | fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 1.0f))); 23 | 24 | gl_Position = mvp * vec4(vertexPosition, 1.0f); 25 | } 26 | -------------------------------------------------------------------------------- /resources/database.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orangeduck/Animation-Looping/e6d7f06ad25252f0e4c82079350e53f3423ecc5d/resources/database.bin -------------------------------------------------------------------------------- /resources/generate_database.py: -------------------------------------------------------------------------------- 1 | import quat 2 | import bvh 3 | from scipy.interpolate import griddata 4 | import scipy.signal as signal 5 | import scipy.ndimage as ndimage 6 | import struct 7 | import numpy as np 8 | 9 | import matplotlib.pyplot as plt 10 | plt.style.use('ggplot') 11 | 12 | """ Files to Process """ 13 | 14 | files = [ 15 | 'run1_subject5.bvh', 16 | 'walk1_subject5.bvh', 17 | 'dance2_subject5.bvh', 18 | 'fight1_subject5.bvh', 19 | ] 20 | 21 | """ We will accumulate data in these lists """ 22 | 23 | bone_positions = [] 24 | bone_rotations = [] 25 | bone_parents = [] 26 | bone_names = [] 27 | 28 | range_starts = [] 29 | range_stops = [] 30 | 31 | """ Loop Over Files """ 32 | 33 | for filename in files: 34 | 35 | """ Load Data """ 36 | 37 | print('Loading "%s"...' % filename) 38 | 39 | bvh_data = bvh.load(filename) 40 | bvh_data['positions'] = bvh_data['positions'] 41 | bvh_data['rotations'] = bvh_data['rotations'] 42 | 43 | positions = bvh_data['positions'] 44 | rotations = quat.unroll(quat.from_euler(np.radians(bvh_data['rotations']), order=bvh_data['order'])) 45 | 46 | # Convert from cm to m 47 | positions *= 0.01 48 | 49 | """ Supersample """ 50 | 51 | nframes = positions.shape[0] 52 | nbones = positions.shape[1] 53 | 54 | # Supersample data to 60 fps 55 | original_times = np.linspace(0, nframes - 1, nframes) 56 | sample_times = np.linspace(0, nframes - 1, int(0.9 * (nframes * 2 - 1))) # Speed up data by 10% 57 | 58 | # This does a cubic interpolation of the data for supersampling and also speeding up by 10% 59 | positions = griddata(original_times, positions.reshape([nframes, -1]), sample_times, method='cubic').reshape([len(sample_times), nbones, 3]) 60 | rotations = griddata(original_times, rotations.reshape([nframes, -1]), sample_times, method='cubic').reshape([len(sample_times), nbones, 4]) 61 | 62 | # Need to re-normalize after super-sampling 63 | rotations = quat.normalize(rotations) 64 | 65 | """ Extract Simulation Bone """ 66 | 67 | # First compute world space positions/rotations 68 | global_rotations, global_positions = quat.fk(rotations, positions, bvh_data['parents']) 69 | 70 | # Specify joints to use for simulation bone 71 | sim_position_joint = bvh_data['names'].index("Spine2") 72 | sim_rotation_joint = bvh_data['names'].index("Hips") 73 | 74 | # Position comes from spine joint 75 | sim_position = np.array([1.0, 0.0, 1.0]) * global_positions[:,sim_position_joint:sim_position_joint+1] 76 | sim_position = signal.savgol_filter(sim_position, 31, 3, axis=0, mode='interp') 77 | 78 | # Direction comes from projected hip forward direction 79 | sim_direction = np.array([1.0, 0.0, 1.0]) * quat.mul_vec(global_rotations[:,sim_rotation_joint:sim_rotation_joint+1], np.array([0.0, 1.0, 0.0])) 80 | 81 | # We need to re-normalize the direction after both projection and smoothing 82 | sim_direction = sim_direction / np.sqrt(np.sum(np.square(sim_direction), axis=-1))[...,np.newaxis] 83 | sim_direction = signal.savgol_filter(sim_direction, 61, 3, axis=0, mode='interp') 84 | sim_direction = sim_direction / np.sqrt(np.sum(np.square(sim_direction), axis=-1)[...,np.newaxis]) 85 | 86 | # Extract rotation from direction 87 | sim_rotation = quat.normalize(quat.between(np.array([0, 0, 1]), sim_direction)) 88 | 89 | # Transform first joints to be local to sim and append sim as root bone 90 | positions[:,0:1] = quat.mul_vec(quat.inv(sim_rotation), positions[:,0:1] - sim_position) 91 | rotations[:,0:1] = quat.mul(quat.inv(sim_rotation), rotations[:,0:1]) 92 | 93 | positions = np.concatenate([sim_position, positions], axis=1) 94 | rotations = np.concatenate([sim_rotation, rotations], axis=1) 95 | rotations = quat.unroll(rotations) 96 | 97 | bone_parents = np.concatenate([[-1], bvh_data['parents'] + 1]) 98 | 99 | bone_names = ['Simulation'] + bvh_data['names'] 100 | 101 | """ Compute Velocities """ 102 | 103 | # Compute velocities via central difference 104 | velocities = np.empty_like(positions) 105 | velocities[1:-1] = ( 106 | 0.5 * (positions[2: ] - positions[1:-1]) * 60.0 + 107 | 0.5 * (positions[1:-1] - positions[ :-2]) * 60.0) 108 | velocities[ 0] = velocities[ 1] - (velocities[ 3] - velocities[ 2]) 109 | velocities[-1] = velocities[-2] + (velocities[-2] - velocities[-3]) 110 | 111 | # Same for angular velocities 112 | angular_velocities = np.zeros_like(positions) 113 | angular_velocities[1:-1] = ( 114 | 0.5 * quat.to_scaled_angle_axis(quat.abs(quat.mul_inv(rotations[2: ], rotations[1:-1]))) * 60.0 + 115 | 0.5 * quat.to_scaled_angle_axis(quat.abs(quat.mul_inv(rotations[1:-1], rotations[ :-2]))) * 60.0) 116 | angular_velocities[ 0] = angular_velocities[ 1] - (angular_velocities[ 3] - angular_velocities[ 2]) 117 | angular_velocities[-1] = angular_velocities[-2] + (angular_velocities[-2] - angular_velocities[-3]) 118 | 119 | """ Compute Contact Data """ 120 | 121 | global_rotations, global_positions, global_velocities, global_angular_velocities = quat.fk_vel( 122 | rotations, 123 | positions, 124 | velocities, 125 | angular_velocities, 126 | bone_parents) 127 | 128 | contact_velocity_threshold = 0.15 129 | 130 | contact_velocity = np.sqrt(np.sum(global_velocities[:,np.array([ 131 | bone_names.index("LeftToe"), 132 | bone_names.index("RightToe")])]**2, axis=-1)) 133 | 134 | # Contacts are given for when contact bones are below velocity threshold 135 | contacts = contact_velocity < contact_velocity_threshold 136 | 137 | # Median filter here acts as a kind of "majority vote", and removes 138 | # small regions where contact is either active or inactive 139 | for ci in range(contacts.shape[1]): 140 | 141 | contacts[:,ci] = ndimage.median_filter( 142 | contacts[:,ci], 143 | size=6, 144 | mode='nearest') 145 | 146 | """ Append to Database """ 147 | 148 | bone_positions.append(positions) 149 | bone_rotations.append(rotations) 150 | 151 | offset = 0 if len(range_starts) == 0 else range_stops[-1] 152 | 153 | range_starts.append(offset) 154 | range_stops.append(offset + len(positions)) 155 | 156 | 157 | """ Concatenate Data """ 158 | 159 | bone_positions = np.concatenate(bone_positions, axis=0).astype(np.float32) 160 | bone_rotations = np.concatenate(bone_rotations, axis=0).astype(np.float32) 161 | bone_parents = bone_parents.astype(np.int32) 162 | 163 | range_starts = np.array(range_starts).astype(np.int32) 164 | range_stops = np.array(range_stops).astype(np.int32) 165 | 166 | """ Visualize """ 167 | 168 | def halflife_to_damping(halflife, eps=1e-5): 169 | return (4.0 * 0.69314718056) / (halflife + eps) 170 | 171 | 172 | def decay( 173 | x, # Initial Position 174 | v, # Initial Velocity 175 | halflife, # Halflife 176 | dt # Time Delta 177 | ): 178 | 179 | y = halflife_to_damping(halflife) / 2.0 180 | 181 | return np.exp(-y*dt)*(x + (v + x*y)*dt) 182 | 183 | 184 | def decay_velocity( 185 | v, # Initial Velocity 186 | halflife, # Halflife 187 | dt # Time Delta 188 | ): 189 | 190 | y = halflife_to_damping(halflife) / 2.0 191 | 192 | return np.exp(-y*dt)*v*dt 193 | 194 | def decay_cubic( 195 | x, 196 | v, 197 | blend_time, 198 | dt): 199 | 200 | t = np.clip(dt / blend_time, 0, 1) 201 | 202 | d = x 203 | c = v * blend_time 204 | b = -3*d - 2*c 205 | a = 2*d + c 206 | 207 | return a*t*t*t + b*t*t + c*t + d 208 | 209 | 210 | def decay_velocity_cubic( 211 | v, 212 | blend_time, 213 | dt): 214 | 215 | t = np.clip(dt / blend_time, 0, 1) 216 | 217 | c = v * blend_time 218 | b = -2*c 219 | a = c 220 | 221 | return a*t*t*t + b*t*t + c*t 222 | 223 | def softfade(x, hardness=1.0): 224 | return np.log(1 + np.exp(hardness - 2*hardness*x)) / hardness 225 | 226 | def softfade_grad(x, hardness=1.0): 227 | return (-2*np.exp(hardness)) / (np.exp(2*hardness*x) + np.exp(hardness)) 228 | 229 | def softfade_initial_grad(hardness=1.0): 230 | return (-2*np.exp(hardness)) / (1 + np.exp(hardness)) 231 | 232 | def decay_softfade( 233 | y_scale, 234 | x_scale, 235 | hardness, 236 | time): 237 | 238 | return y_scale * softfade(time/x_scale, hardness) 239 | 240 | def decay_softfade_grad( 241 | y_scale, 242 | x_scale, 243 | hardness, 244 | time): 245 | 246 | return y_scale * (softfade_grad(time/x_scale, hardness) / x_scale) 247 | 248 | def decay_softfade_initial_grad( 249 | y_scale, 250 | x_scale, 251 | hardness): 252 | 253 | return y_scale * (softfade_initial_grad(hardness) / x_scale) 254 | 255 | 256 | 257 | 258 | visualize_looper = True 259 | 260 | if visualize_looper: 261 | 262 | start, stop = 500+162+66, 500+162+367 263 | bone, component = 0, 0 264 | 265 | part = bone_positions[start:stop,bone,component].copy() 266 | part_velocity = abs(np.gradient(part)) 267 | part_adjust = np.cumsum(part_velocity) / np.sum(part_velocity) 268 | 269 | offset = part[-1] - part[0] 270 | 271 | linear_offset = np.linspace(offset/2, -offset/2, len(part)) 272 | adjust_offset = -offset * part_adjust + offset/2 273 | 274 | velocity_start = part[1] - part[0] 275 | velocity_end = part[-1] - part[-2] 276 | velocity_offset = velocity_end - velocity_start 277 | 278 | fig, ax = plt.subplots(1, 1, figsize=(6.4, 2.4)) 279 | fig.suptitle('Unlooped') 280 | ax.plot(np.arange(len(part)), part, color='red') 281 | ax.plot(len(part) - 1 + np.arange(len(part)), part, color='red') 282 | ax.set_xlabel('Time') 283 | ax.set_ylabel('Displacement') 284 | ax.axes.xaxis.set_ticklabels([]) 285 | ax.set_ylim([-3, 3]) 286 | plt.tight_layout() 287 | plt.show() 288 | 289 | start_spring_offset = decay(offset, velocity_offset, 20.0, np.arange(len(part))) 290 | end_spring_offset = decay(-offset, velocity_offset, 20.0, np.arange(len(part))[::-1]) 291 | 292 | start_spring_half_offset = decay(offset/2, velocity_offset/2, 20.0, np.arange(len(part))) 293 | end_spring_half_offset = decay(-offset/2, velocity_offset/2, 40.0, np.arange(len(part))[::-1]) 294 | final_spring_offset = start_spring_half_offset + end_spring_half_offset 295 | 296 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 297 | fig.suptitle('Inertialize Start') 298 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 299 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 300 | ax[0].plot(np.arange(len(part)), part + start_spring_offset, color='red') 301 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + start_spring_offset, color='red') 302 | ax[0].set_xlabel('') 303 | ax[0].set_ylabel('Displacement') 304 | ax[0].axes.xaxis.set_ticklabels([]) 305 | ax[0].set_ylim([-3, 3]) 306 | ax[1].plot(np.arange(len(part)), start_spring_offset, color='green', linestyle='dashed') 307 | ax[1].plot(len(part) - 1 + np.arange(len(part)), start_spring_offset, color='green', linestyle='dashed') 308 | ax[1].set_xlabel('Time') 309 | ax[1].set_ylabel('Offset') 310 | ax[1].axes.xaxis.set_ticklabels([]) 311 | ax[1].set_ylim([-3, 3]) 312 | plt.tight_layout() 313 | plt.show() 314 | 315 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 316 | fig.suptitle('Inertialize End') 317 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 318 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 319 | ax[0].plot(np.arange(len(part)), part + end_spring_offset, color='red') 320 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + end_spring_offset, color='red') 321 | ax[0].set_xlabel('') 322 | ax[0].set_ylabel('Displacement') 323 | ax[0].axes.xaxis.set_ticklabels([]) 324 | ax[0].set_ylim([-3, 3]) 325 | ax[1].plot(np.arange(len(part)), end_spring_offset, color='green', linestyle='dashed') 326 | ax[1].plot(len(part) - 1 + np.arange(len(part)), end_spring_offset, color='green', linestyle='dashed') 327 | ax[1].set_xlabel('Time') 328 | ax[1].set_ylabel('Offset') 329 | ax[1].axes.xaxis.set_ticklabels([]) 330 | ax[1].set_ylim([-3, 3]) 331 | plt.tight_layout() 332 | plt.show() 333 | 334 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 335 | fig.suptitle('Inertialize Both') 336 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 337 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 338 | ax[0].plot(np.arange(len(part)), part + final_spring_offset, color='red') 339 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + final_spring_offset, color='red') 340 | ax[0].set_xlabel('') 341 | ax[0].set_ylabel('Displacement') 342 | ax[0].axes.xaxis.set_ticklabels([]) 343 | ax[0].set_ylim([-3, 3]) 344 | ax[1].plot(np.arange(len(part)), final_spring_offset, color='green', linestyle='dashed') 345 | ax[1].plot(len(part) - 1 + np.arange(len(part)), final_spring_offset, color='green', linestyle='dashed') 346 | ax[1].set_xlabel('Time') 347 | ax[1].set_ylabel('Offset') 348 | ax[1].axes.xaxis.set_ticklabels([]) 349 | ax[1].set_ylim([-3, 3]) 350 | plt.tight_layout() 351 | plt.show() 352 | 353 | start_cubic_half_offset = decay_cubic(offset/2, velocity_offset/2, 100.0, np.arange(len(part))) 354 | end_cubic_half_offset = decay_cubic(-offset/2, velocity_offset/2, 100.0, np.arange(len(part))[::-1]) 355 | final_cubic_offset = start_cubic_half_offset + end_cubic_half_offset 356 | 357 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 358 | fig.suptitle('Inertialize Cubic') 359 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 360 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 361 | ax[0].plot(np.arange(len(part)), part + final_cubic_offset, color='red') 362 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + final_cubic_offset, color='red') 363 | ax[0].set_xlabel('') 364 | ax[0].set_ylabel('Displacement') 365 | ax[0].axes.xaxis.set_ticklabels([]) 366 | ax[0].set_ylim([-3, 3]) 367 | ax[1].plot(np.arange(len(part)), final_cubic_offset, color='green', linestyle='dashed') 368 | ax[1].plot(len(part) - 1 + np.arange(len(part)), final_cubic_offset, color='green', linestyle='dashed') 369 | ax[1].set_xlabel('Time') 370 | ax[1].set_ylabel('Offset') 371 | ax[1].axes.xaxis.set_ticklabels([]) 372 | ax[1].set_ylim([-3, 3]) 373 | plt.tight_layout() 374 | plt.show() 375 | 376 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 377 | fig.suptitle('Linear Offset') 378 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 379 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 380 | ax[0].plot(np.arange(len(part)), part + linear_offset, color='red') 381 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + linear_offset, color='red') 382 | ax[0].set_xlabel('') 383 | ax[0].set_ylabel('Displacement') 384 | ax[0].axes.xaxis.set_ticklabels([]) 385 | ax[0].set_ylim([-3, 3]) 386 | ax[1].plot(np.arange(len(part)), linear_offset, color='green', linestyle='dashed') 387 | ax[1].plot(len(part) - 1 + np.arange(len(part)), linear_offset, color='green', linestyle='dashed') 388 | ax[1].set_xlabel('Time') 389 | ax[1].set_ylabel('Offset') 390 | ax[1].axes.xaxis.set_ticklabels([]) 391 | ax[1].set_ylim([-3, 3]) 392 | plt.tight_layout() 393 | plt.show() 394 | 395 | start_offset = decay_velocity_cubic(velocity_offset / 2, 50.0, np.arange(len(part))) 396 | end_offset = decay_velocity_cubic(velocity_offset / 2, 50.0, np.arange(len(part))[::-1]) 397 | final_offset = linear_offset + start_offset + end_offset 398 | 399 | fig, ax = plt.subplots(1, 1, figsize=(6.4, 2.4)) 400 | fig.suptitle('Velocity Offset') 401 | ax.plot(start_offset + end_offset, color='green', linestyle='dashed') 402 | ax.set_xlabel('') 403 | ax.set_ylabel('Offset') 404 | ax.axes.xaxis.set_ticklabels([]) 405 | ax.set_ylim([-3, 3]) 406 | plt.tight_layout() 407 | plt.show() 408 | 409 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 410 | fig.suptitle('Linear Offset + Velocity Offset') 411 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 412 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 413 | ax[0].plot(np.arange(len(part)), part + final_offset, color='red') 414 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + final_offset, color='red') 415 | ax[0].set_xlabel('') 416 | ax[0].set_ylabel('Displacement') 417 | ax[0].axes.xaxis.set_ticklabels([]) 418 | ax[0].set_ylim([-3, 3]) 419 | ax[1].plot(np.arange(len(part)), final_offset, color='green', linestyle='dashed') 420 | ax[1].plot(len(part) - 1 + np.arange(len(part)), final_offset, color='green', linestyle='dashed') 421 | ax[1].set_xlabel('Time') 422 | ax[1].set_ylabel('Offset') 423 | ax[1].axes.xaxis.set_ticklabels([]) 424 | ax[1].set_ylim([-3, 3]) 425 | plt.tight_layout() 426 | plt.show() 427 | 428 | # TODO: Different widths on each side 429 | 430 | start_linear_offset = decay_softfade(offset/2, 75.0, 10.0, np.arange(len(part))) 431 | end_linear_offset = decay_softfade(-offset/2, 150.0, 10.0, np.arange(len(part))[::-1]) 432 | 433 | velocity_fade_start = decay_softfade_initial_grad(offset/2, 75.0, 10.0) 434 | velocity_fade_end = decay_softfade_initial_grad(offset/2, 150.0, 10.0) 435 | velocity_fade_offset = (velocity_end + velocity_fade_end) - (velocity_start + velocity_fade_start) 436 | 437 | fade_start_offset = decay_velocity_cubic(velocity_fade_offset / 2, 50.0, np.arange(len(part))) 438 | fade_end_offset = decay_velocity_cubic(velocity_fade_offset / 2, 50.0, np.arange(len(part))[::-1]) 439 | 440 | fade_offset = start_linear_offset + end_linear_offset + fade_start_offset + fade_end_offset 441 | 442 | fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8)) 443 | fig.suptitle('$\\frac{\\log(1 + \\exp(\\alpha - 2\\ \\alpha\\ x))}{\\alpha}$') 444 | ax.plot(np.linspace(0, 1, 100), softfade(np.linspace(0, 1, 100), 5.0), label='$\\alpha=5$') 445 | ax.plot(np.linspace(0, 1, 100), softfade(np.linspace(0, 1, 100), 10.0), label='$\\alpha=10$') 446 | ax.plot(np.linspace(0, 1, 100), softfade(np.linspace(0, 1, 100), 25.0), label='$\\alpha=25$') 447 | ax.set_xlabel('') 448 | ax.set_ylabel('') 449 | ax.legend() 450 | plt.tight_layout() 451 | plt.show() 452 | 453 | fig, ax = plt.subplots(1, 1, figsize=(6.4, 2.4)) 454 | fig.suptitle('Softfade Offset') 455 | ax.plot(start_linear_offset + end_linear_offset, color='green', linestyle='dashed') 456 | ax.set_xlabel('') 457 | ax.set_ylabel('Offset') 458 | ax.axes.xaxis.set_ticklabels([]) 459 | ax.set_ylim([-3, 3]) 460 | plt.tight_layout() 461 | plt.show() 462 | 463 | fig, ax = plt.subplots(2, 1, figsize=(6.4, 4.8)) 464 | fig.suptitle('Softfade Offset + Velocity Offset') 465 | ax[0].plot(np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 466 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part, color='red', alpha=0.25, linestyle='dotted') 467 | ax[0].plot(np.arange(len(part)), part + fade_offset, color='red') 468 | ax[0].plot(len(part) - 1 + np.arange(len(part)), part + fade_offset, color='red') 469 | ax[0].set_xlabel('') 470 | ax[0].set_ylabel('Displacement') 471 | ax[0].axes.xaxis.set_ticklabels([]) 472 | ax[0].set_ylim([-3, 3]) 473 | ax[1].plot(np.arange(len(part)), fade_offset, color='green', linestyle='dashed') 474 | ax[1].plot(len(part) - 1 + np.arange(len(part)), fade_offset, color='green', linestyle='dashed') 475 | ax[1].set_xlabel('Time') 476 | ax[1].set_ylabel('Offset') 477 | ax[1].axes.xaxis.set_ticklabels([]) 478 | ax[1].set_ylim([-3, 3]) 479 | plt.tight_layout() 480 | plt.show() 481 | 482 | root_pos_part0 = bone_positions[1560:1583,0].copy() 483 | root_rot_part0 = bone_rotations[1560:1583,0].copy() 484 | 485 | root_pos_part1 = quat.mul_vec(root_rot_part0[-1:], quat.inv_mul_vec(root_rot_part0[:1], root_pos_part0 - root_pos_part0[:1])) + root_pos_part0[-1:] 486 | 487 | root_vel_part0 = root_pos_part0[-1] - root_pos_part0[-2] 488 | root_vel_part1 = quat.mul_vec(root_rot_part0[-1:], quat.inv_mul_vec(root_rot_part0[0], root_pos_part0[1] - root_pos_part0[0])) 489 | root_vel_offset = root_vel_part0 - root_vel_part1 490 | 491 | root_offset0 = decay_velocity_cubic(root_vel_offset / 2, 15.0, np.arange(len(root_pos_part0))[::-1][...,None]) 492 | root_offset1 = decay_velocity_cubic(root_vel_offset / 2, 15.0, np.arange(len(root_pos_part0))[...,None]) 493 | 494 | fig, ax = plt.subplots(1, 1, figsize=(6.4, 4.8)) 495 | fig.suptitle('Root Motion') 496 | ax.plot(root_pos_part0[...,0], root_pos_part0[...,2], color='red', alpha=0.25, linestyle='dotted') 497 | ax.plot(root_pos_part1[...,0], root_pos_part1[...,2], color='red', alpha=0.25, linestyle='dotted') 498 | ax.plot(root_offset0[...,0] + root_pos_part0[...,0], root_offset0[...,2] + root_pos_part0[...,2], color='red') 499 | ax.plot(root_offset1[...,0] + root_pos_part1[...,0], root_offset1[...,2] + root_pos_part1[...,2], color='red') 500 | ax.set_xlabel('') 501 | ax.set_ylabel('') 502 | ax.axes.xaxis.set_ticklabels([]) 503 | ax.axes.yaxis.set_ticklabels([]) 504 | ax.set_xlim([-3.75, -2]) 505 | ax.set_ylim([0.5, 1.75]) 506 | ax.set_aspect('equal') 507 | plt.tight_layout() 508 | plt.show() 509 | 510 | 511 | """ Write Database """ 512 | 513 | print("Writing Database...") 514 | 515 | with open('database.bin', 'wb') as f: 516 | 517 | nframes = bone_positions.shape[0] 518 | nbones = bone_positions.shape[1] 519 | nranges = range_starts.shape[0] 520 | 521 | f.write(struct.pack('II', nframes, nbones) + bone_positions.ravel().tobytes()) 522 | f.write(struct.pack('II', nframes, nbones) + bone_rotations.ravel().tobytes()) 523 | f.write(struct.pack('I', nbones) + bone_parents.ravel().tobytes()) 524 | 525 | f.write(struct.pack('I', nranges) + range_starts.ravel().tobytes()) 526 | f.write(struct.pack('I', nranges) + range_stops.ravel().tobytes()) 527 | 528 | 529 | -------------------------------------------------------------------------------- /resources/quat.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def _fast_cross(a, b): 4 | return np.concatenate([ 5 | a[...,1:2]*b[...,2:3] - a[...,2:3]*b[...,1:2], 6 | a[...,2:3]*b[...,0:1] - a[...,0:1]*b[...,2:3], 7 | a[...,0:1]*b[...,1:2] - a[...,1:2]*b[...,0:1]], axis=-1) 8 | 9 | def eye(shape, dtype=np.float32): 10 | return np.ones(list(shape) + [4], dtype=dtype) * np.asarray([1, 0, 0, 0], dtype=dtype) 11 | 12 | def length(x): 13 | return np.sqrt(np.sum(x * x, axis=-1)) 14 | 15 | def normalize(x, eps=1e-8): 16 | return x / (length(x)[...,np.newaxis] + eps) 17 | 18 | def abs(x): 19 | return np.where(x[...,0:1] > 0.0, x, -x) 20 | 21 | def from_angle_axis(angle, axis): 22 | c = np.cos(angle / 2.0)[..., np.newaxis] 23 | s = np.sin(angle / 2.0)[..., np.newaxis] 24 | q = np.concatenate([c, s * axis], axis=-1) 25 | return q 26 | 27 | def to_xform(x): 28 | 29 | qw, qx, qy, qz = x[...,0:1], x[...,1:2], x[...,2:3], x[...,3:4] 30 | 31 | x2, y2, z2 = qx + qx, qy + qy, qz + qz 32 | xx, yy, wx = qx * x2, qy * y2, qw * x2 33 | xy, yz, wy = qx * y2, qy * z2, qw * y2 34 | xz, zz, wz = qx * z2, qz * z2, qw * z2 35 | 36 | return np.concatenate([ 37 | np.concatenate([1.0 - (yy + zz), xy - wz, xz + wy], axis=-1)[...,np.newaxis,:], 38 | np.concatenate([xy + wz, 1.0 - (xx + zz), yz - wx], axis=-1)[...,np.newaxis,:], 39 | np.concatenate([xz - wy, yz + wx, 1.0 - (xx + yy)], axis=-1)[...,np.newaxis,:], 40 | ], axis=-2) 41 | 42 | def to_xform_xy(x): 43 | 44 | qw, qx, qy, qz = x[...,0:1], x[...,1:2], x[...,2:3], x[...,3:4] 45 | 46 | x2, y2, z2 = qx + qx, qy + qy, qz + qz 47 | xx, yy, wx = qx * x2, qy * y2, qw * x2 48 | xy, yz, wy = qx * y2, qy * z2, qw * y2 49 | xz, zz, wz = qx * z2, qz * z2, qw * z2 50 | 51 | return np.concatenate([ 52 | np.concatenate([1.0 - (yy + zz), xy - wz], axis=-1)[...,np.newaxis,:], 53 | np.concatenate([xy + wz, 1.0 - (xx + zz)], axis=-1)[...,np.newaxis,:], 54 | np.concatenate([xz - wy, yz + wx], axis=-1)[...,np.newaxis,:], 55 | ], axis=-2) 56 | 57 | 58 | 59 | def from_euler(e, order='zyx'): 60 | axis = { 61 | 'x': np.asarray([1, 0, 0], dtype=np.float32), 62 | 'y': np.asarray([0, 1, 0], dtype=np.float32), 63 | 'z': np.asarray([0, 0, 1], dtype=np.float32)} 64 | 65 | q0 = from_angle_axis(e[..., 0], axis[order[0]]) 66 | q1 = from_angle_axis(e[..., 1], axis[order[1]]) 67 | q2 = from_angle_axis(e[..., 2], axis[order[2]]) 68 | 69 | return mul(q0, mul(q1, q2)) 70 | 71 | def from_xform(ts): 72 | 73 | return normalize( 74 | np.where((ts[...,2,2] < 0.0)[...,np.newaxis], 75 | np.where((ts[...,0,0] > ts[...,1,1])[...,np.newaxis], 76 | np.concatenate([ 77 | (ts[...,2,1]-ts[...,1,2])[...,np.newaxis], 78 | (1.0 + ts[...,0,0] - ts[...,1,1] - ts[...,2,2])[...,np.newaxis], 79 | (ts[...,1,0]+ts[...,0,1])[...,np.newaxis], 80 | (ts[...,0,2]+ts[...,2,0])[...,np.newaxis]], axis=-1), 81 | np.concatenate([ 82 | (ts[...,0,2]-ts[...,2,0])[...,np.newaxis], 83 | (ts[...,1,0]+ts[...,0,1])[...,np.newaxis], 84 | (1.0 - ts[...,0,0] + ts[...,1,1] - ts[...,2,2])[...,np.newaxis], 85 | (ts[...,2,1]+ts[...,1,2])[...,np.newaxis]], axis=-1)), 86 | np.where((ts[...,0,0] < -ts[...,1,1])[...,np.newaxis], 87 | np.concatenate([ 88 | (ts[...,1,0]-ts[...,0,1])[...,np.newaxis], 89 | (ts[...,0,2]+ts[...,2,0])[...,np.newaxis], 90 | (ts[...,2,1]+ts[...,1,2])[...,np.newaxis], 91 | (1.0 - ts[...,0,0] - ts[...,1,1] + ts[...,2,2])[...,np.newaxis]], axis=-1), 92 | np.concatenate([ 93 | (1.0 + ts[...,0,0] + ts[...,1,1] + ts[...,2,2])[...,np.newaxis], 94 | (ts[...,2,1]-ts[...,1,2])[...,np.newaxis], 95 | (ts[...,0,2]-ts[...,2,0])[...,np.newaxis], 96 | (ts[...,1,0]-ts[...,0,1])[...,np.newaxis]], axis=-1)))) 97 | 98 | 99 | def from_xform_xy(x): 100 | 101 | c2 = _fast_cross(x[...,0], x[...,1]) 102 | c2 = c2 / np.sqrt(np.sum(np.square(c2), axis=-1))[...,np.newaxis] 103 | c1 = _fast_cross(c2, x[...,0]) 104 | c1 = c1 / np.sqrt(np.sum(np.square(c1), axis=-1))[...,np.newaxis] 105 | c0 = x[...,0] 106 | 107 | return from_xform(np.concatenate([ 108 | c0[...,np.newaxis], 109 | c1[...,np.newaxis], 110 | c2[...,np.newaxis]], axis=-1)) 111 | 112 | def inv(q): 113 | return np.asarray([1, -1, -1, -1], dtype=np.float32) * q 114 | 115 | def mul(x, y): 116 | x0, x1, x2, x3 = x[..., 0:1], x[..., 1:2], x[..., 2:3], x[..., 3:4] 117 | y0, y1, y2, y3 = y[..., 0:1], y[..., 1:2], y[..., 2:3], y[..., 3:4] 118 | 119 | return np.concatenate([ 120 | y0 * x0 - y1 * x1 - y2 * x2 - y3 * x3, 121 | y0 * x1 + y1 * x0 - y2 * x3 + y3 * x2, 122 | y0 * x2 + y1 * x3 + y2 * x0 - y3 * x1, 123 | y0 * x3 - y1 * x2 + y2 * x1 + y3 * x0], axis=-1) 124 | 125 | def inv_mul(x, y): 126 | return mul(inv(x), y) 127 | 128 | def mul_inv(x, y): 129 | return mul(x, inv(y)) 130 | 131 | def mul_vec(q, x): 132 | t = 2.0 * _fast_cross(q[..., 1:], x) 133 | return x + q[..., 0][..., np.newaxis] * t + _fast_cross(q[..., 1:], t) 134 | 135 | def inv_mul_vec(q, x): 136 | return mul_vec(inv(q), x) 137 | 138 | def unroll(x): 139 | y = x.copy() 140 | for i in range(1, len(x)): 141 | d0 = np.sum( y[i] * y[i-1], axis=-1) 142 | d1 = np.sum(-y[i] * y[i-1], axis=-1) 143 | y[i][d0 < d1] = -y[i][d0 < d1] 144 | return y 145 | 146 | def between(x, y): 147 | return np.concatenate([ 148 | np.sqrt(np.sum(x*x, axis=-1) * np.sum(y*y, axis=-1))[...,np.newaxis] + 149 | np.sum(x * y, axis=-1)[...,np.newaxis], 150 | _fast_cross(x, y)], axis=-1) 151 | 152 | def log(x, eps=1e-5): 153 | length = np.sqrt(np.sum(np.square(x[...,1:]), axis=-1))[...,np.newaxis] 154 | halfangle = np.where(length < eps, np.ones_like(length), np.arctan2(length, x[...,0:1]) / length) 155 | return halfangle * x[...,1:] 156 | 157 | def exp(x, eps=1e-5): 158 | halfangle = np.sqrt(np.sum(np.square(x), axis=-1))[...,np.newaxis] 159 | c = np.where(halfangle < eps, np.ones_like(halfangle), np.cos(halfangle)) 160 | s = np.where(halfangle < eps, np.ones_like(halfangle), np.sinc(halfangle / np.pi)) 161 | return np.concatenate([c, s * x], axis=-1) 162 | 163 | def to_scaled_angle_axis(x, eps=1e-5): 164 | return 2.0 * log(x, eps) 165 | 166 | def from_scaled_angle_axis(x, eps=1e-5): 167 | return exp(x / 2.0, eps) 168 | 169 | def fk(lrot, lpos, parents): 170 | 171 | gp, gr = [lpos[...,:1,:]], [lrot[...,:1,:]] 172 | for i in range(1, len(parents)): 173 | gp.append(mul_vec(gr[parents[i]], lpos[...,i:i+1,:]) + gp[parents[i]]) 174 | gr.append(mul (gr[parents[i]], lrot[...,i:i+1,:])) 175 | 176 | return np.concatenate(gr, axis=-2), np.concatenate(gp, axis=-2) 177 | 178 | def ik(grot, gpos, parents): 179 | 180 | return ( 181 | np.concatenate([ 182 | grot[...,:1,:], 183 | mul(inv(grot[...,parents[1:],:]), grot[...,1:,:]), 184 | ], axis=-2), 185 | np.concatenate([ 186 | gpos[...,:1,:], 187 | mul_vec( 188 | inv(grot[...,parents[1:],:]), 189 | gpos[...,1:,:] - gpos[...,parents[1:],:]), 190 | ], axis=-2)) 191 | 192 | def fk_vel(lrot, lpos, lvel, lang, parents): 193 | 194 | gp, gr, gv, ga = [lpos[...,:1,:]], [lrot[...,:1,:]], [lvel[...,:1,:]], [lang[...,:1,:]] 195 | for i in range(1, len(parents)): 196 | gp.append(mul_vec(gr[parents[i]], lpos[...,i:i+1,:]) + gp[parents[i]]) 197 | gr.append(mul (gr[parents[i]], lrot[...,i:i+1,:])) 198 | gv.append(mul_vec(gr[parents[i]], lvel[...,i:i+1,:]) + 199 | _fast_cross(ga[parents[i]], mul_vec(gr[parents[i]], lpos[...,i:i+1,:])) + 200 | gv[parents[i]]) 201 | ga.append(mul_vec(gr[parents[i]], lang[...,i:i+1,:]) + ga[parents[i]]) 202 | 203 | return ( 204 | np.concatenate(gr, axis=-2), 205 | np.concatenate(gp, axis=-2), 206 | np.concatenate(gv, axis=-2), 207 | np.concatenate(ga, axis=-2)) 208 | 209 | 210 | def to_euler(x, order='xyz'): 211 | 212 | q0 = x[...,0:1] 213 | q1 = x[...,1:2] 214 | q2 = x[...,2:3] 215 | q3 = x[...,3:4] 216 | 217 | if order == 'xyz': 218 | 219 | return np.concatenate([ 220 | np.arctan2(2 * (q0 * q1 + q2 * q3), 1 - 2 * (q1 * q1 + q2 * q2)), 221 | np.arcsin((2 * (q0 * q2 - q3 * q1)).clip(-1,1)), 222 | np.arctan2(2 * (q0 * q3 + q1 * q2), 1 - 2 * (q2 * q2 + q3 * q3))], axis=-1) 223 | 224 | elif order == 'yzx': 225 | 226 | return np.concatenate([ 227 | np.arctan2(2 * (q1 * q0 - q2 * q3), -q1 * q1 + q2 * q2 - q3 * q3 + q0 * q0), 228 | np.arctan2(2 * (q2 * q0 - q1 * q3), q1 * q1 - q2 * q2 - q3 * q3 + q0 * q0), 229 | np.arcsin((2 * (q1 * q2 + q3 * q0)).clip(-1,1))], axis=-1) 230 | 231 | else: 232 | raise NotImplementedError('Cannot convert from ordering %s' % order) 233 | -------------------------------------------------------------------------------- /shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Animation Looping Demo 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 155 | 156 | 157 | 158 |
159 | 160 | 170 | 171 |
172 | 173 |
174 | 175 |
176 | 177 | 178 | 179 | 180 | 198 | 283 | {{{ SCRIPT }}} 284 | 285 | 286 | -------------------------------------------------------------------------------- /spring.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | #include "vec.h" 5 | #include "quat.h" 6 | 7 | //-------------------------------------- 8 | 9 | static inline float damper_exact(float x, float g, float halflife, float dt, float eps=1e-5f) 10 | { 11 | return lerpf(x, g, 1.0f - fast_negexpf((LN2f * dt) / (halflife + eps))); 12 | } 13 | 14 | static inline vec3 damper_exact(vec3 x, vec3 g, float halflife, float dt, float eps=1e-5f) 15 | { 16 | return lerp(x, g, 1.0f - fast_negexpf((LN2f * dt) / (halflife + eps))); 17 | } 18 | 19 | static inline quat damper_exact(quat x, quat g, float halflife, float dt, float eps=1e-5f) 20 | { 21 | return quat_slerp_shortest_approx(x, g, 1.0f - fast_negexpf((LN2f * dt) / (halflife + eps))); 22 | } 23 | 24 | static inline float damp_adjustment_exact(float g, float halflife, float dt, float eps=1e-5f) 25 | { 26 | return g * (1.0f - fast_negexpf((LN2f * dt) / (halflife + eps))); 27 | } 28 | 29 | static inline vec3 damp_adjustment_exact(vec3 g, float halflife, float dt, float eps=1e-5f) 30 | { 31 | return g * (1.0f - fast_negexpf((LN2f * dt) / (halflife + eps))); 32 | } 33 | 34 | static inline quat damp_adjustment_exact(quat g, float halflife, float dt, float eps=1e-5f) 35 | { 36 | return quat_slerp_shortest_approx(quat(), g, 1.0f - fast_negexpf((LN2f * dt) / (halflife + eps))); 37 | } 38 | 39 | //-------------------------------------- 40 | 41 | static inline float halflife_to_damping(float halflife, float eps = 1e-5f) 42 | { 43 | return (4.0f * LN2f) / (halflife + eps); 44 | } 45 | 46 | static inline float damping_to_halflife(float damping, float eps = 1e-5f) 47 | { 48 | return (4.0f * LN2f) / (damping + eps); 49 | } 50 | 51 | static inline float frequency_to_stiffness(float frequency) 52 | { 53 | return squaref(2.0f * PIf * frequency); 54 | } 55 | 56 | static inline float stiffness_to_frequency(float stiffness) 57 | { 58 | return sqrtf(stiffness) / (2.0f * PIf); 59 | } 60 | 61 | //-------------------------------------- 62 | 63 | static inline void simple_spring_damper_exact( 64 | float& x, 65 | float& v, 66 | const float x_goal, 67 | const float halflife, 68 | const float dt) 69 | { 70 | float y = halflife_to_damping(halflife) / 2.0f; 71 | float j0 = x - x_goal; 72 | float j1 = v + j0*y; 73 | float eydt = fast_negexpf(y*dt); 74 | 75 | x = eydt*(j0 + j1*dt) + x_goal; 76 | v = eydt*(v - j1*y*dt); 77 | } 78 | 79 | static inline void simple_spring_damper_exact( 80 | vec3& x, 81 | vec3& v, 82 | const vec3 x_goal, 83 | const float halflife, 84 | const float dt) 85 | { 86 | float y = halflife_to_damping(halflife) / 2.0f; 87 | vec3 j0 = x - x_goal; 88 | vec3 j1 = v + j0*y; 89 | float eydt = fast_negexpf(y*dt); 90 | 91 | x = eydt*(j0 + j1*dt) + x_goal; 92 | v = eydt*(v - j1*y*dt); 93 | } 94 | 95 | static inline void simple_spring_damper_exact( 96 | quat& x, 97 | vec3& v, 98 | const quat x_goal, 99 | const float halflife, 100 | const float dt) 101 | { 102 | float y = halflife_to_damping(halflife) / 2.0f; 103 | 104 | vec3 j0 = quat_to_scaled_angle_axis(quat_abs(quat_mul(x, quat_inv(x_goal)))); 105 | vec3 j1 = v + j0*y; 106 | 107 | float eydt = fast_negexpf(y*dt); 108 | 109 | x = quat_mul(quat_from_scaled_angle_axis(eydt*(j0 + j1*dt)), x_goal); 110 | v = eydt*(v - j1*y*dt); 111 | } 112 | 113 | //-------------------------------------- 114 | 115 | -------------------------------------------------------------------------------- /vec.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common.h" 4 | 5 | struct vec2 6 | { 7 | vec2() : x(0.0f), y(0.0f) {} 8 | vec2(float _x, float _y) : x(_x), y(_y) {} 9 | 10 | float x, y; 11 | }; 12 | 13 | static inline vec2 operator+(float s, vec2 v) 14 | { 15 | return vec2(v.x + s, v.y + s); 16 | } 17 | 18 | static inline vec2 operator+(vec2 v, float s) 19 | { 20 | return vec2(v.x + s, v.y + s); 21 | } 22 | 23 | static inline vec2 operator+(vec2 v, vec2 w) 24 | { 25 | return vec2(v.x + w.x, v.y + w.y); 26 | } 27 | 28 | static inline vec2 operator-(float s, vec2 v) 29 | { 30 | return vec2(s - v.x, s - v.y); 31 | } 32 | 33 | static inline vec2 operator-(vec2 v, float s) 34 | { 35 | return vec2(v.x - s, v.y - s); 36 | } 37 | 38 | static inline vec2 operator-(vec2 v, vec2 w) 39 | { 40 | return vec2(v.x - w.x, v.y - w.y); 41 | } 42 | 43 | static inline vec2 operator*(float s, vec2 v) 44 | { 45 | return vec2(v.x * s, v.y * s); 46 | } 47 | 48 | static inline vec2 operator*(vec2 v, float s) 49 | { 50 | return vec2(v.x * s, v.y * s); 51 | } 52 | 53 | static inline vec2 operator*(vec2 v, vec2 w) 54 | { 55 | return vec2(v.x * w.x, v.y * w.y); 56 | } 57 | 58 | static inline vec2 operator/(vec2 v, float s) 59 | { 60 | return vec2(v.x / s, v.y / s); 61 | } 62 | 63 | static inline vec2 operator/(float s, vec2 v) 64 | { 65 | return vec2(s / v.x, s / v.y); 66 | } 67 | 68 | static inline vec2 operator/(vec2 v, vec2 w) 69 | { 70 | return vec2(v.x / w.x, v.y / w.y); 71 | } 72 | 73 | static inline vec2 operator-(vec2 v) 74 | { 75 | return vec2(-v.x, -v.y); 76 | } 77 | 78 | static inline float dot(vec2 v, vec2 w) 79 | { 80 | return v.x*w.x + v.y*w.y; 81 | } 82 | 83 | static inline float length(vec2 v) 84 | { 85 | return sqrtf(v.x*v.x + v.y*v.y); 86 | } 87 | 88 | static inline vec2 normalize(vec2 v, float eps=1e-8f) 89 | { 90 | return v / (length(v) + eps); 91 | } 92 | 93 | static inline vec2 lerp(vec2 v, vec2 w, float alpha) 94 | { 95 | return v * (1.0f - alpha) + w * alpha; 96 | } 97 | 98 | //-------------------------------------- 99 | 100 | struct vec3 101 | { 102 | vec3() : x(0.0f), y(0.0f), z(0.0f) {} 103 | vec3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} 104 | 105 | float x, y, z; 106 | 107 | inline vec3 operator+=(vec3 v) 108 | { 109 | this->x += v.x; 110 | this->y += v.y; 111 | this->z += v.z; 112 | return *this; 113 | } 114 | 115 | inline vec3 operator-=(vec3 v) 116 | { 117 | this->x -= v.x; 118 | this->y -= v.y; 119 | this->z -= v.z; 120 | return *this; 121 | } 122 | }; 123 | 124 | static inline vec3 operator+(float s, vec3 v) 125 | { 126 | return vec3(v.x + s, v.y + s, v.z + s); 127 | } 128 | 129 | static inline vec3 operator+(vec3 v, float s) 130 | { 131 | return vec3(v.x + s, v.y + s, v.z + s); 132 | } 133 | 134 | static inline vec3 operator+(vec3 v, vec3 w) 135 | { 136 | return vec3(v.x + w.x, v.y + w.y, v.z + w.z); 137 | } 138 | 139 | static inline vec3 operator-(float s, vec3 v) 140 | { 141 | return vec3(s - v.x, s - v.y, s - v.z); 142 | } 143 | 144 | static inline vec3 operator-(vec3 v, float s) 145 | { 146 | return vec3(v.x - s, v.y - s, v.z - s); 147 | } 148 | 149 | static inline vec3 operator-(vec3 v, vec3 w) 150 | { 151 | return vec3(v.x - w.x, v.y - w.y, v.z - w.z); 152 | } 153 | 154 | static inline vec3 operator*(float s, vec3 v) 155 | { 156 | return vec3(v.x * s, v.y * s, v.z * s); 157 | } 158 | 159 | static inline vec3 operator*(vec3 v, float s) 160 | { 161 | return vec3(v.x * s, v.y * s, v.z * s); 162 | } 163 | 164 | static inline vec3 operator*(vec3 v, vec3 w) 165 | { 166 | return vec3(v.x * w.x, v.y * w.y, v.z * w.z); 167 | } 168 | 169 | static inline vec3 operator/(vec3 v, float s) 170 | { 171 | return vec3(v.x / s, v.y / s, v.z / s); 172 | } 173 | 174 | static inline vec3 operator/(float s, vec3 v) 175 | { 176 | return vec3(s / v.x, s / v.y, s / v.z); 177 | } 178 | 179 | static inline vec3 operator/(vec3 v, vec3 w) 180 | { 181 | return vec3(v.x / w.x, v.y / w.y, v.z / w.z); 182 | } 183 | 184 | static inline vec3 operator-(vec3 v) 185 | { 186 | return vec3(-v.x, -v.y, -v.z); 187 | } 188 | 189 | static inline float dot(vec3 v, vec3 w) 190 | { 191 | return v.x*w.x + v.y*w.y + v.z*w.z; 192 | } 193 | 194 | static inline float sum(vec3 v) 195 | { 196 | return v.x + v.y + v.z; 197 | } 198 | 199 | static inline vec3 cross(vec3 v, vec3 w) 200 | { 201 | return vec3( 202 | v.y*w.z - v.z*w.y, 203 | v.z*w.x - v.x*w.z, 204 | v.x*w.y - v.y*w.x); 205 | } 206 | 207 | static inline float length(vec3 v) 208 | { 209 | return sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); 210 | } 211 | 212 | static inline vec3 normalize(vec3 v, float eps=1e-8f) 213 | { 214 | return v / (length(v) + eps); 215 | } 216 | 217 | static inline vec3 lerp(vec3 v, vec3 w, float alpha) 218 | { 219 | return v * (1.0f - alpha) + w * alpha; 220 | } 221 | 222 | static inline vec3 min(vec3 v, vec3 w) 223 | { 224 | return vec3( 225 | minf(v.x, w.x), 226 | minf(v.y, w.y), 227 | minf(v.z, w.z)); 228 | } 229 | 230 | static inline vec3 max(vec3 v, vec3 w) 231 | { 232 | return vec3( 233 | maxf(v.x, w.x), 234 | maxf(v.y, w.y), 235 | maxf(v.z, w.z)); 236 | } 237 | 238 | static inline vec3 clamp(vec3 v, vec3 min, vec3 max) 239 | { 240 | return vec3( 241 | clampf(v.x, min.x, max.x), 242 | clampf(v.y, min.y, max.y), 243 | clampf(v.z, min.z, max.z)); 244 | } 245 | 246 | static inline vec3 abs(vec3 v) 247 | { 248 | return vec3( 249 | fabs(v.x), 250 | fabs(v.y), 251 | fabs(v.z)); 252 | } 253 | -------------------------------------------------------------------------------- /wasm-server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socketserver 3 | from http.server import SimpleHTTPRequestHandler 4 | 5 | class WasmHandler(SimpleHTTPRequestHandler): 6 | def end_headers(self): 7 | # Include additional response headers here. CORS for example: 8 | #self.send_header('Access-Control-Allow-Origin', '*') 9 | SimpleHTTPRequestHandler.end_headers(self) 10 | 11 | 12 | # Python 3.7.5 adds in the WebAssembly Media Type. If this is an older 13 | # version, add in the Media Type. 14 | if sys.version_info < (3, 7, 5): 15 | WasmHandler.extensions_map['.wasm'] = 'application/wasm' 16 | 17 | 18 | if __name__ == '__main__': 19 | PORT = 8080 20 | with socketserver.TCPServer(("", PORT), WasmHandler) as httpd: 21 | print("Listening on port {}. Press Ctrl+C to stop.".format(PORT)) 22 | httpd.serve_forever() --------------------------------------------------------------------------------