Да есть выбор, либо пользуешься классами и перегрузкой операторов и получаешь мало буков, либо пишешь в си-стайл и в варианте на с++ получаешь даже более длинные тексты чем в си.
Кстати в твоём примере показана интересная особенность языка С -- из за отсутствия inline такие короткие функции могут стать причиной частых кеш-промахов и большого оверхеда на сам вызов функций. У себя в рейкастинге я в критических местах не пользовался даже готовыми матфункциями, а писал сразу хард-кодом целый блок вычислений. В целом в услвиях достаточно стабильного графического пайплайна рейкастинга не требуется частых переписываний, поэтому выбранный мной подход пока себя оправдывает.
Причина почему в d3dx использовали такой подход я думаю кроется в том числе в намерении сделать библиотеки совместимые со множеством разных нативных языков. Не думаю что думать о таких вещах правильно когда цель gapi вычислять графику как можно быстрее. В dx11 они вроде сосредоточились исключительно на с++11 (из нативных языков, а так конечно вездесущий шарп там есть тоже).
Вообще если вернуться к вопросу оптимизации то твоя структура не очень правильно написана:
typedef struct entity_s {
vec3_t localPosition;
vec3_t localScale;
quat_t localRotation;
mat4_t globalTransform;
mat4_t localTransform;
mat4_t invBindTransform;
struct entity_s * parent;
list_t surfaces;
list_t childs;
char skinned;
char name[64];
char propBuffer[512];
list_t keyFrames;
int totalFrames;
anim_t * anim;
char visible;
char animated;
char globalTransformCalculated;
float depthHack;
/* components */
camera_t * camera;
light_t * light;
body_t * body;
struct entity_s *instanceOf;
} entity_t;
Например такие толстые вещи как name[ 64 ] и propBuffer[ 512 ] нужно убирать вниз, потому что следом за ними идут более тонкие и нужные поля, а префетчер начинает тратить время на чтение этих толстых массивов и не подготавливает вовремя нужные данные в кеш.
Ещё лучше делить такие большие структуры на "горячие" и "холодные" части. Предположим что первые три поля используются очень часто (каждый кадр или чаще), а другие реже, по мере трансформации объекта или запроса на изменение (ну тут ты сам лучше знаешь что и когда у тебя используется). Поэтому данные лучше распределить по двум структурам.
// Первая:
typedef struct {
vec3_t localPosition;
vec3_t localScale;
quat_t localRotation;
} entityHot_t;
// Вторая:
typedef struct {
// Всё остальное.
} entityCold_t;
Теперь множество этих структур будет расположено двумя массивами так:
entityHot_t hotEntities[ number ];
entityCold_t coldEntities[ number ];
Такое размещение называется Structure Of Arrays (SOA). В случае большой структуры как у тебя это Array Of Structs (AOS).
Обход первого массива (hotEntities) займет гораздо меньше времени, потому что потребуется прокешировать гораздо меньше данных чем во втором случае.
Возможно в языках с таким синтаксисом это не очень удобно, или же потребуется что-то придумать с макросами или обёртками. Но я видел более "заточенные" для игр языки где это решалось вот так:
struct entity_t {
vec3_t SOA pos; // hot part
char SOA name[ 512 ];
};
// Теперь когда делаем массив ентити:
entity_t entities[ 1024 ];
Компилятор видит метки SOA и размещает данные в памяти так что сначала идёт массив из 1024 векторов, а затем массив из массивов чаров. Таким образом горячая часть остается компактной на уровне железа, а программист видит абстракцию и продолжает работать со структурой как единым целым.