Всем привет. В 3д помимо моделек - статических, существуют анимации - анимированные модели, которые имеют набор своих данных, эти данные нужны для отображения модельки и её анимирования.
Если выбрать такой язык как С, по каким-либо причинам, рано или поздно можно столкнуться с отсутствием некоторых сущностей. Самой часто используемой сущностью является vector. Если оттолкнуться от того, что простейшая абстракция вектор, и еще чуть упростить можно придти логически к *односвязному списку. Но вот незадача, если пребывать на этом этапе по наименьшему сопротивлению, то придётся на каждую структуру писать свою реализацию это в худшем случае. В этой статье хочу показать как решил вопрос с односвязным списком.
Постановка задачи
**Скелетная анимация - есть моделька,в которой есть сетка - mesh(состоит из полигонов) и скелетная составляющая.
выглядит она например так
Скрытый текст

При работе по сбору данных из анимированной модельки в момент её инициализации, при длительном подходе, я выявил следующую задачу: нужна такая сущность, в которую можно добавлять в конец данные. Количество данных в момент первых обходов могут быть не известны. Тоесть, например, чтобы проинициализировать массивы вершин, их можно просто узнать сколько их, когда. В тоже время, иерархия костной составляющей не так тривиальна. Поэтому, на первом масштабном приближении, я пришел к односвязному списку на костную составляющую.
Так же, как опция я принял решение, что тип будет кастомный - не известен как будто, так же понадобится возможность поиска по индексу и вставка в следующий за найденым индексом операции.
И окончательное требование - соглашение на 1 сущность 1 тип данных.
Пользуюсь компилятором gcc 14.2 в среде Linux, assimp из репозитория
Задача поставлена, решил её так:
Скрытый текст
//язык С
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(1)
//кастомная тестовая структура
struct EpicLEgendaryNodes_t
{
char *name;
int Num;
float weights;
};
typedef struct EpicLEgendaryNodes_t EpicLEgendaryNodes;
//определение типов
enum typeElements
{
INT,
CHAR,
DOUBLE,
FLOAT,
CHARLINE,
EPICLEGENDARYNODES
};
//структура списка
typedef struct NodeL_t NodeL;
struct NodeL_t
{
enum typeElements T;
void* data;
NodeL* Next;
};
//структура хендлера
typedef struct VNodeL_t VNodeL;
struct VNodeL_t
{
int NodeSz;
NodeL *head;
};
//определение необходимого минимума функций, которые покроют поставленную задачу
NodeL* getNode(VNodeL *vNodeL,enum typeElements T,void* d);
NodeL* addNodeL(void *d,enum typeElements T,VNodeL *vNodeL);
NodeL* FREENodeL(VNodeL *vNodeL);
NodeL* getByIndex(VNodeL *vNodeL,int Index);
NodeL* setByAfterIndex(VNodeL *vNodeL,int Index,void *d,enum typeElements T);
void printList(VNodeL *vNodeL);
//метод получения ноды - создание
NodeL* getNode(VNodeL *vNodeL,enum typeElements T,void* d)
{
char* dd=NULL;
NodeL* tmp=NULL;
tmp=malloc(sizeof(NodeL));
switch(T)
{
case INT:
tmp->data=malloc(sizeof(int));
memcpy(tmp->data, d, sizeof(int)); //! использую memcpy
vNodeL->NodeSz++;
break;
case CHAR:
tmp->data=malloc(sizeof(char));
memcpy(tmp->data, d, sizeof(char));
vNodeL->NodeSz++;
break;
case DOUBLE:
tmp->data=malloc(sizeof(double));
memcpy(tmp->data, d, sizeof(double));
vNodeL->NodeSz++;
break;
case FLOAT:
tmp->data=malloc(sizeof(float));
memcpy(tmp->data, d, sizeof(float));
vNodeL->NodeSz++;
break;
case CHARLINE:
dd=d;
size_t szS=strlen(dd)+1;
tmp->data=malloc(sizeof(char)*szS);
///dd[szS]='\0';
memcpy(tmp->data, d, sizeof(char)*szS);
vNodeL->NodeSz++;
break;
case EPICLEGENDARYNODES:
tmp->data=malloc(sizeof(EpicLEgendaryNodes));
memcpy(tmp->data, d, sizeof(EpicLEgendaryNodes));
vNodeL->NodeSz++;
break;
}
tmp->Next=NULL;
return tmp;
}
//добавление в конец ноды
NodeL* addNodeL(void *d,enum typeElements T,VNodeL *vNodeL)
{
NodeL* tmp=getNode(vNodeL,T,d);
if( vNodeL->head == NULL )
{
tmp->T=T;
return tmp;
}
NodeL* cur = vNodeL->head;
while( cur->Next != NULL )cur = cur->Next;
cur->Next = tmp;
return vNodeL->head;
}
//очищение структуры списка
NodeL* FREENodeL(VNodeL *vNodeL)
{
NodeL* cur=vNodeL->head;
while( cur != NULL )
{
NodeL* temp=cur;
cur = temp->Next;
free(temp);
}
return vNodeL->head;
}
//получение значения по индексу
NodeL* getByIndex(VNodeL *vNodeL,int Index)
{
if(Index>vNodeL->NodeSz-1)
{
printf("\n\ngetByIndex\nSearchIndex > SizeNodeLists\n");
return 0;
}
int tInd=0;
NodeL *cur=vNodeL->head;
while(cur!=NULL)
{
if(tInd==Index)break;
tInd++;
cur=cur->Next;
}
return cur;
}
//вставка в место следующее за заданным индексом
NodeL* setByAfterIndex(VNodeL *vNodeL,int Index,void *d,enum typeElements T)
{
NodeL *setting=getNode(vNodeL,T,d);
if(Index>vNodeL->NodeSz-1)
{
printf("SearchIndex > SizeNodeLists\n");
return 0;
}
int tInd=0;
NodeL *cur=vNodeL->head;
while(cur!=NULL)
{
if(tInd==Index)break;
tInd++;
cur=cur->Next;
}
NodeL *tmp1=cur->Next;
cur->Next=setting;
setting->Next=tmp1;
return vNodeL->head;
}
//вывод списка
void printList(VNodeL *vNodeL)
{
if(vNodeL->head==NULL){printf("LIST IS EMPTY\n");return ;}
if(vNodeL==NULL){printf("LIST IS EMPTY\n");return ;}
NodeL* cur=vNodeL->head;
enum typeElements Type=vNodeL->head->T;
while(cur!=NULL)
{
switch(Type)
{
case INT:
printf("%d\n",*(int*)cur->data);
break;
case CHAR:
printf("%c\n",*(char*)cur->data);
break;
case DOUBLE:
printf("%f\n",*(double*)cur->data);
break;
case FLOAT:
printf("%f\n",*(float*)cur->data);
break;
case CHARLINE:
printf("%s\n",(char*)cur->data);
break;
case EPICLEGENDARYNODES:
{
EpicLEgendaryNodes *temp=(EpicLEgendaryNodes*)cur->data;
printf("%s\n",temp->name);
printf("%d\n",temp->Num);
printf("%f\n",temp->weights);
break;
}
}
cur=cur->Next;
}
}
//типовая прогонка списка 1 тест базовых типов 2 тест кастомной структурки
int main()
{
//1
VNodeL *tempBuf=malloc(sizeof(VNodeL));
tempBuf->head=NULL;
tempBuf->NodeSz=0;
int t=4;
float qq=0.1f;
double qw=0.1f;
char* strline=(char*)"CHARLINE";
tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
strline="CHARLINE1";
tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
strline="CHARLINE2";
tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
strline="CHARLINE3";
tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
printList(tempBuf);
NodeL* search=getByIndex(tempBuf,4);
printf("%d\n",tempBuf->NodeSz);
tempBuf->head=setByAfterIndex(tempBuf,2,"CHARLINE10",CHARLINE);
search=getByIndex(tempBuf,4);
printf("\nSearched element\n%s\n\n",(char*)search->data);//см в printList
tempBuf->head=FREENodeL(tempBuf);
free(tempBuf);
//tempBuf->head=NULL;
//printList(tempBuf);
//2
VNodeL *tempBuf1=malloc(sizeof(VNodeL));;
tempBuf1->head=NULL;
tempBuf1->NodeSz=0;
EpicLEgendaryNodes temp;
temp.name="CHARLINE111111";
temp.Num=t;
temp.weights=qq;
tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);t++;qq++;qw++;
temp.name="CHARLINE22221";
temp.Num=t;
temp.weights=qq;
tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);t++;qq++;qw++;
temp.name="CHARLINE23333";
temp.Num=t;
temp.weights=qq;
tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);t++;qq++;qw++;
temp.name="CHARLINE34444";
temp.Num=t;
temp.weights=qq;
tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);
printList(tempBuf1);
tempBuf1->head=FREENodeL(tempBuf1);
free(tempBuf1);
//tempBuf1->head=NULL;
//printList(tempBuf1);
return 0;
}
Вывод:
CHARLINE
CHARLINE1
CHARLINE2
CHARLINE3
getByIndex
SearchIndex > SizeNodeLists
4
Searched element
CHARLINE3
CHARLINE111111
8
4.100000
CHARLINE22221
9
5.100000
CHARLINE23333
10
6.100000
CHARLINE34444
11
7.100000
gcc -Ofast -ffast-math -flto -msse4.1 main.c -lm
clang -Ofast -ffast-math -flto -msse4.1 main.c -lm
/nologo /MD /O2 /Ob2 /DNDEBUG - x64! msvc v19.40 VS17.10
Выше представлено то, что получилось. Это, возможно, не лучшая реализация односвязного списка. Но на текущем подходе к скелетной анимации достаточно, теперь можно создавать структуры и заполнять.
При такой организации, конечно придётся хранить структуры рядом с перечислением.
Дополнительная информация:
Комментарии (48)
DmitryKoterov
15.02.2025 23:12Чо-то нет консистентности: ни PascalCase, ни camelCase, ни snake_case… что-то все время промежуточное и смешанное.
Serpentine
15.02.2025 23:12typedef struct NodeL_t NodeL; struct NodeL_t //структура списка { enum typeElements T; void* data; NodeL* Next; }; typedef struct VNodeL_t VNodeL; struct VNodeL_t//структура хендлера { long NodeSz; NodeL *head; };
Не знаю, на какой архитектуре сидите, но если на x64 у вас вероятно будет паддинг в 4 байта на экземпляр каждой из этих структур. Кроме того, в Win64
long
обычно 32-битный.Если не знали про выравнивание структур, то можете почитать, например, перевод статьи Эрика Реймонда "The Lost Art of C Structure Packing".
Jijiki Автор
15.02.2025 23:12спасибо, код несет исключительно применительный характер, все изменения будут производиться на проигранной анимации, когда будет видна нагрузка ключевая
проверил да паддинг есть (NodeL 4 байта), инта хватит конечно )
structure-member-alignment-padding-and-data-packing/
Скрытый текст
struct Meshes_t { vec3 *vertices; vec3 *colors; vec3 *normals; vec4 *boneIDs; vec4 *weights; unsigned int *indices; }; typedef struct Meshes_t Meshes; struct Model_t { Meshes *meshes; VNodeL *vNodel; }; typedef struct Model_t Model;
чтото около рабочего всё равно будет что-то наподобии такого
mat4 весит 64 - да кстати а их например с варешкой и скрепленным мешем - цельным 50 в сумме , спасибо что про паддинги напомнили
Jijiki Автор
15.02.2025 23:12тестовый код без анимаций, всё равно мягче работает по моим наблюдениям, чем на С++ или 1 в 1, плюс на С у меня почти готова процедурная генерация там масштабы уже побольше чем на С++, хочу посмотреть как будет вести себя анимация, будет ли мягче чем на С++, сейчас анимация на С++ чуть грузит проц - толи от самой анимации, толи там расчеты, ну а математика у меня реализована, надеюсь будет мягче, ну в худшем случае будет так же как в С++ сейчас по анимации
расти есть куда по анимациям можно будет еще изучить двойные кватернионы если похуже будет, хотя по процессору должно быть чуть мягче
VoodooCat
15.02.2025 23:12Вы случайно не аудиофил? Код работает "мягче" - это характеристика - "мне нравится". И если аудиофилы страдают невозможностью объективных тестов за счет психоакустики - то в данной области - инструменты есть.
Serpentine
15.02.2025 23:12case CHARLINE: char* dd=d; tmp->data=malloc(sizeof(char)*strlen(dd)); memcpy(tmp->data, dd, sizeof(char)*strlen(dd)); vNodeL->NodeSz++; break; /*...*/ case EPICLEGENDARYNODES: EpicLEgendaryNodes *temp=(EpicLEgendaryNodes*)cur->data; printf("%s\n",temp->name); printf("%d\n",temp->Num); printf("%f\n",temp->weights); break;
Это C23 что ли? В C за метками должны следовать операторы, тогда как объявления переменных не являются операторами, и в православных стандартах это вообще компилироваться недолжно. В С++ тоже - там в пределах одной области видимости запрещен переход минуя инициализацию, т.е. блок {} так и так нужен.
Jijiki Автор
15.02.2025 23:12вы про фигурные скобочки?
Скрытый текст
.L3: mov edi, 16 call malloc movdqu xmm0, XMMWORD PTR [r13+0] add QWORD PTR [r12], 1 mov QWORD PTR [rbp+4], rax movups XMMWORD PTR [rax], xmm0 jmp .L2
там 2 свитча, в свитчах кейсы, первая часть что вы показали из стадии получения ноды, вторая часть после троеточия в стадии вывода ноды в консоль
у меня не компилируется на кланге 19 версии, выше O1 - проблема в установке ноды, что-то урезается при этом компилируется ассемблерная часть, по тому что я заметил
я клангом не пользуюсь, кланг стоит только для clangd, а создавал на gcc 14.2
а извиняюсь. обнуление индекса при инициализации хендлера фиксит-тут пофиксил, просто в тесте не было. На обоих компилируется (clang 19/gcc 14.2)
Jijiki Автор
15.02.2025 23:12Скрытый текст
case EPICLEGENDARYNODES: { EpicLEgendaryNodes *temp=(EpicLEgendaryNodes*)cur->data; printf("%s\n",temp->name); printf("%d\n",temp->Num); printf("%f\n",temp->weights); break; }
вас понял да скобочки, спасибо пофикшу
Serpentine
15.02.2025 23:12Лучше перед публикацией скомпилировать код, который приводите в статье, с флагами
-Wall -pedantic
и исправить его, чтобы не было таких эксцессов.У вас еще, например, из кучи этих переменных:
int t=4,i=1,q=2,w=3,e=5,r=6,y=7,u=8;
часть вообще не используется.
В setByAfterIndex()
NodeL *tmp
тоже не используется, строкой ниже вы пишетеNodeL *tmp1=cur->Next;
.Про неправильные спецификаторы типа в
print()
тоже покажет.Инициализацию
NodeSz
нулем вы исправили, тоже ее заметил. При компиляции MSVC программа просто упадет.Кстати, вот что он выводит, когда я ошибки с размером long`а, метками и
NodeSz
немного поправил:Скрытый текст
CHARLINE¤¤¤¤ CHARLINE1¤¤¤¤ CHARLINE2¤¤¤¤ CHARLINE3¤¤¤¤▌▌▌EУ░ж getByIndex SearchIndex > SizeNodeLists 4 Searched element CHARLINE3¤¤¤¤▌▌▌OТ╣↓Еж CHARLINE111111 3 1.100000 CHARLINE22221 4 2.100000 CHARLINE23333 5 3.100000 CHARLINE34444 6 4.100000
Думаю, перед использованием вам его еще много придется дебажить.
Jijiki Автор
15.02.2025 23:12тоесть вы скопировали то что в статье и у вас вывелось так да?, платформа Linux извините.
Serpentine
15.02.2025 23:12Да. Вот что выдает при версии, что вы исправили сейчас:
Скрытый текст
CHARLINE¤¤¤¤
CHARLINE1¤¤¤¤
CHARLINE2¤¤¤¤
CHARLINE3¤¤¤¤getByIndex
SearchIndex > SizeNodeLists
4Searched element
CHARLINE3¤¤¤¤CHARLINE111111
3
1.100000
CHARLINE22221
4
2.100000
CHARLINE23333
5
3.100000
CHARLINE34444
6
4.100000Jijiki Автор
15.02.2025 23:12а где вы запускаете?
Скрытый текст
а что в принт не так простите?
Serpentine
15.02.2025 23:12Windows 10 x64. Visual Studio 2019. Стандарт С17.
Сборка с Release тоже выдает всякий мусор
Скрытый текст
CHARLINEP☺Л М☺ CHARLINE1 CHARLINE2 CHARLINE3 getByIndex SearchIndex > SizeNodeLists 4 Searched element CHARLINE3 CHARLINE111111 3 1.100000 CHARLINE22221 4 2.100000 CHARLINE23333 5 3.100000 CHARLINE34444 6 4.100000
На первую строчку взгляните. Читаем что-то из неинициализированной памяти.
Jijiki Автор
15.02.2025 23:12понял, спасибо вижу. Простите
Serpentine
15.02.2025 23:12Небольшая подсказка. Взгляните на
getNode()
case CHARLINE: //char* dd=d; //tmp->data=malloc(sizeof(char)*strlen(dd)); //memcpy(tmp->data, dd, sizeof(char)*strlen(dd)); //vNodeL->NodeSz++; tmp->data=malloc(sizeof(char)*strlen((char*)d)); memcpy(tmp->data, d, sizeof(char)*strlen((char*)d)); vNodeL->NodeSz++; break;
Вы аллоцируете память для строки размером в ее длину, а под завершающий ноль места уже нет.
Serpentine
15.02.2025 23:12case CHARLINE: char* dd=d; size_t szS=(size_t)strlen(dd)+1; tmp->data=malloc(sizeof(char)*szS)); memcpy(tmp->data, d, sizeof(char)*szS); dd[szS]='\0'; /* WTF??? */ vNodeL->NodeSz++; break;
Вы куда терминальный ноль записали, а главное для чего?
Jijiki Автор
15.02.2025 23:12в Windows https://learn.microsoft.com/ru-ru/cpp/c-runtime-library/reference/strdup-wcsdup?view=msvc-170 и она кидает предупреждение, которое можно отключить (уровень 3 :C4996), поэтому даже не знаю как лучше
SpiderEkb
15.02.2025 23:12Какой у вас размер списка (сколько элементов в типовых сценариях)?
Я про поиск по индексу перебором - не будет ли это слишком долго?
Может стоит присмотреться к алгоритму SkipList
https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf
и его расширениям
https://webdiis.unizar.es/asignaturas/APD/skip_list_cookbook.pdf
Jijiki Автор
15.02.2025 23:12спасибо большое посмотрю, первичная проборка 50(на меш 25 костей - упрощенный скелет без пальцев ) - этот этап пока не проверял, далее надо будет загнать веса и посмотреть, толи 4*вершины, толи 1 в 1 если учесть что вектор-веса xyzw, в гифке чтото около грубого расчета по вершинам, очень грубого в большую сторону 2к
Скрытый текст
1310 Vertices 1310 Colors 1310 Normals 1314 Indices 1310 BoneIDS 1310 Weights Num Bones 25#list 2128 Vertices 2128 Colors 2128 Normals 2216 Indices 2128 BoneIDS 2128 Weights Num Bones 25#list LIST NUMBER ELEMENTS: 50 mixamorig:Hips 100.000000 0.000001 -0.000000 0.000000 -0.000000 -0.000004 -100.000000 0.000000 0.000001 100.000015 -0.000004 0.000000 0.000002 -413.847046 13.272202 1.000000 mixamorig:Spine 100.000000 0.000001 0.000000 0.000000 -0.000000 3.754293 -99.929504 0.000000 0.000001 99.929504 3.754293 0.000000 0.000002 -458.326569 -2.274207 1.000000 mixamorig:Spine1 ...
SpiderEkb
15.02.2025 23:12Ну это немного... Я привык работать со списками в сотни тысяч элементов.
В вашем случае, возможно, перебором будет достаточно.
Хотя можно ускорить если перейти на двусвязный список и хранить указатели на начало, конец и середину (этот указатель будет двигаться вперед или назад при добавлении или удалении элемента). Тогда при поиске по индексу можно будет искать не по всему списку, а по четверти его - если индекс в первой четвери - ищем с начала вперед, если по второй - с середины назад, в третьей - с середины вперед, в четвертой - с конца назад.
В реализации это не добавит много сложности (в отличии от SkipList - это достаточно сложная в реализации структура данных), но ускорит поиск ощутимо. И, думаю, на нескольких сотнях элементов баланс между стоимостью реализации и производительностью будет неплохой.
А возможно тут вообще не список нужен а динамический массив. Но тут, опять, типовые сценарии использования. Если элементы постоянно добавляются-удаляются - это список. А если вы один раз заполняете его, а потом многократно используете - это однозначно массив.
Jijiki Автор
15.02.2025 23:12ну как некое сегметирование, как разбиение пространства получается - общая механика,просто у геома(увидел в фрибсд в своё время, теперь представляю линейные последовательности, которые поделены на чанки, как геомы - типо как чанки в майнкрафте, но линейные) пространство линейно. понял, звучит интересно, возможно гляну
alef13
15.02.2025 23:12а ещё лучше дерево :)
SpiderEkb
15.02.2025 23:12Не всегда. Дерево хорошо для поиска. И то при условии что оно сбалансировано (что при постоянном добавлении-удалении элементов будет требовать дополнительных затрат).
Но вот при обходе "от первого до последнего элемента" дерево будет хуже списка.
unreal_undead2
15.02.2025 23:12Кстати попробовал - обход обычного std::map тормознее std::list раз в пять (в одних конкретно взятых условиях). Не думал, что разница настолько большая.
SpiderEkb
15.02.2025 23:12Ну я за свою специфику могу говорить - большие объемы данных + HiLoad (все что делаем проходит через нагрузочное тестирование).
В моей специфике деревья как-то не прижились. Два основных сценария
Потоковая обработка данных - сортировка (часто с исключением дубликатов) или агрегация данных с одинак4овым ключом (нет элемента - добавляем, есть - что-то там агрегируем). С последующей обработкой полученных данных. В этом сценарии лучше всего работает алгоритм на базе SkipList - сначала составляем список элементов на базе входящего потока, потом уже работаем со списком - идем по нему от первого элемента до последнего.
Справочники в памяти. Тут справочник один раз заполняется при старте, потом многократно используется для поиска нужных данных. Здесь лучше всего работает отсортированный массив с двоичным поиском по нему (самый быстрый поиск). Заполнили, один раз отсортировали и потом пользуемся. Тут, в принципе, деревья тоже неплохи, но они сложнее в реализации и накладных расходов там немного больше.
Jijiki Автор
15.02.2025 23:12теперь сравните vector и list)
1000 елементов 100 000 и 1 000 000 елементов
unreal_undead2
15.02.2025 23:12Тут даже смысла особого нет, и так всё понятно ) Но если смотреть list vs map - и там и там pointer chasing, в мапе всё посложнее, но казалось бы можно сделать похоже.
SpiderEkb
15.02.2025 23:12Дерево в принципе не подразумевает обхода в порядке следования элементов. Т.е. оно реализуемо, но это отдельный алгоритм, который сложнее чем просто перейти по указателю на следующий элемент.
А список - это именно упорядоченная последовательность элементов. Там каждый элемент содержит указатель на следующий. Но там сложнее реализовать поиск элемента по значению (кроме перебора). Потому и придумали SkipList список с возможностью быстрого не такого быстрого как по дереву, но быстрее чем перебором) поиска. Именно для тех ситуаций когда нужно потом с ним работать как со списком и "гулять" по нему вперед-назад.
Затем и нужно понимание "что под капотом" таких сущностей как list, vector, map... чтобы под конкретный сценарий использования выбирать наиболее эффективный подход.
Jijiki Автор
15.02.2025 23:12на фрибсд классно работает я тест С++ глянул(но нагрузку не посмотрел на CPU linux от фряхи отличаются некоторыми функциями- на линуксе были всплески по частоте на анимации, которая на гифке тут. а на бсд вроде не было всплесков, потом посмотрю внимательнее ), и фрибсд стал дружелюбнее прям не верится, возможно не в тему, ставится быстрее чем раньше, все доки поправили до текущих, он настраивается прям из хендбука минут за 5, подвезли блендер ассимп вулкан, на гноме красота
Jijiki Автор
15.02.2025 23:12по итогу 3 списка, но будет около 10, вообщем до 100 чтоли как я понял будет. я эти моменты еще посмотрю, ну грубо говоря 10 списков по 100 елементов это по максимуму - фантазирую, но на самом деле грубо прикинул, потомучто 2 комплекта бонов, и трансформации - это только начало, там еще сами анимации - так сказать в анимациях, то на чем посмотрел быстро вроде работает, ключевая нагрузка всё равно будет в цикле при обновлении, там будет видно
slonopotamus
15.02.2025 23:12Вся статья сводится к
struct LinkedList { void* data; struct LinkedList* next; };
Всё, я сделал односвязный список на C.
Sazonov
Чем это лучше существующих решений? В чём смысл статьи? Где код работы со скелетной анимацией, почему не используется GPU, и тп.
Ваш текст выглядит как отчёт по лабораторной работе на 1 курсе колледжа.
Jijiki Автор
спасибо, возможно будет в будущем по анимациям. Анимация на С покачто в процессе. Здесь упомянул вскользь, потомучто вектор общего повествования связан с анимацией и в дальнейшем если будет возможность, будут продолжения.
Отладка базовых действий у меня происходит через консоль поэтапно, плюс тут еще математика - функция сглаживания. Необходимо поэтапно всё это проверять перед граффической составляющей.
Sazonov
Вы забыли ответить на главный вопрос: в чём смысл вашей статьи на Хабре?
Зачем вы упомянули скелетную анимацию, если ни строчки про неё не написали?
Зачем отладка через вывод в консоль, когда уже более четверти века есть нормальные отладчики?
Jijiki Автор
а как вы сделаете скелетную анимацию представленную через код с математикой, скажите пожалуйста ход действий? ну тоесть не пропуская все этапы, как вы поймёте что использовать из математики, и как вы поймёте что то что берем из файла валидно? по гайдам, но ведь это надо проверить, в гайдах математика на глм, сделайте шаг от глм и вот уже всё надо проверять
Sazonov
Это вы должны были сделать, раз уж с этого началась статья. И, пожалуйста, не игнорируйте остальные вопросы. Я понимаю, что на них вам неудобно отвечать, но всё же постарайтесь.
Jijiki Автор
спасибо, конкретно анимации будет в следующей статье если будет такая возможность.
Jijiki Автор
в статье написано
отладка, это такой процесс, который удобен разработчику, в данном случае выбран путь отладки всех ключевых составляющих из-за реализации через консоль.
сомневаюсь что через дебагер сразу скопом с участием ГПУ вы разберетесь и с математикой и с листами и со всеми нюансами
тоесть тема статьи про лист, в контексте разработки своего не движка, соотв из-за того что сразу всё отладить сложно, надо создавать условия для последовательной отладки разных составляющих и граффическая составляющая включается в конце, когда +- происходит то что должно происходить, на отладочном окружении, и это может быть не только дебагер, в качестве пути был пройден путь всех тестов до анимаций на С++ не задумываясь. Но изза того что было принято решение уходить от некоторых библиотек пришлось переходить на С, и из-за ввода математики разбираться во всём, и последовательно создавать условия для всех ключевых нюансов.
в статье указан пример анимации, указано что это такое, и то почему не массивы
Sazonov
Ну то есть написать printf, подсунуть туда нужные переменные - это по вашему проще чем 1 раз кликнуть мышкой (или хоткеем) для установки breakpoint?
И всё таки - у вас ни слова про анимации. Какие будете использовать? Скелетные? Велосипед изобретёте? Функции работы с матрицами свои напишете? GPU будете использовать для матриц? Обосновать написание велосипедов, вместо использования готового сможете? Если нет, то мы возвращаемся к тому, что ваша статья - это лаба по спискам на си, которую вы почему-то решили оформить на Хабре.
Это вот всё - очень большие темы, по ним люди книжки пишут. А у вас даже банальных примеров нет.
Jijiki Автор
вопрос не связан с темой статьи, анимации указаны вскользь потомучто я пишу не движок, поставлена задача такая потомучто в С ничего нету, а ваш вопрос уникален для разработчиков (тоесть если указаны условия выбора сущности в условиях анимаций, вы конечно можете настаивать на раскрытии темы около которой статья, но это не решение локального вопроса), которые пишут реализации. Остальное индивидуально касается реализации.
В статьях всё охватить невозможно, или в гайдах всегда, идёт описание не глубокое, чтобы не решить даже возможно, а чтобы читатель сам понял что и как он будет использовать, рано или поздно придётся же разбираться что и почему вы делаете. А статья и гайд где охвачено всё это нечитаемо и не интересно, так как всё это надо же изучать правильно?
Как пример: 10 часов видео гайда конвертируется в реальное обучение к примеру и это реальность осваивания/обучения и придётся еще обучаться смежным вопросам
тоесть 10 часов гайда видео придётся еще понять, и попробовать разные моменты, чтобы придти к реализации комплексной, то что вы спрашиваете надо иметь реализацию, тоесть, как сделать модельку вас не волнует, вас волнует только анимация. то что на контент потратиться время вас тоже не волнует, ну а анимация, на С++ попроще может быть все сущности известны, пара гайдов есть, в том числе начиная с консоли тоже, тут как кому удобно изучать. А и потом разве есть единственное решение анимаций? есть тема в 3д что можно анимировать, как не крути это придётся изучать, и даже те люди кто пишет книги или гайды учаться.
Sazonov
Интересное у вас «вскользь», аж 2/3 текста «статьи».
Кем и какая поставлена задача?
Почему вы решили что ваш «гайд» по написанию односвязных списков имеет какую-то ценность по сравнению с существующими? Проведите сравнение эталонных реализаций и вашей, расскажите о преимуществах- это вот и будет статья.
Повторюсь - пока что весь ваш код и статья выглядят как лаба 1 курса, да и то средненькая. Впрочем, оценки за статью как бы не это намекают.
Jijiki Автор
воспользуйтесь успешной реализацией которую вы считаете более успешной и подходлящей вам, моя реализация успешно справляется с моей задачью
Jijiki Автор
прошу прощения если не угодил в статье.