/* by korenkonder GitHub/GitLab: korenkonder */ #include "glitter.hpp" #include "../gl_state.hpp" namespace Glitter { RenderGroup::RenderGroup() : flags(), type(), draw_type(), pivot(), z_offset(), count(), ctrl(), disp(), texture(), mask_texture(), frame(), elements(), max_count(), random_ptr(), disp_type(), fog_type(), vao(), vbo(), ebo() { split_u = 1; split_v = 1; split_uv = 1.0f; mat = mat4_identity; mat_rot = mat4_identity; mat_draw = mat4_identity; disp_type = DISP_NORMAL; emission = 1.0f; blend_mode = PARTICLE_BLEND_TYPICAL; mask_blend_mode = PARTICLE_BLEND_TYPICAL; } RenderGroup::~RenderGroup() { } RenderElement* RenderGroup::AddElement(RenderElement* rend_elem) { if (!rend_elem) rend_elem = elements; size_t left_count = count - (rend_elem - elements); size_t index = 0; if (left_count < 1) return 0; while (rend_elem->alive) { index++; rend_elem++; if (index >= left_count) return 0; } rend_elem->alive = true; ctrl++; return rend_elem; } F2RenderGroup::F2RenderGroup(F2ParticleInst* a1) : particle() { switch (a1->data.data.type) { case PARTICLE_QUAD: case PARTICLE_LINE: case PARTICLE_LOCUS: break; default: return; } if (a1->data.data.count > 0) count = a1->data.data.count; else count = a1->data.data.type == PARTICLE_LOCUS ? 30 : 250; max_count = count * 4; random_ptr = a1->data.random_ptr; particle = a1; elements = new RenderElement[count]; if (!elements) { count = 0; max_count = 0; return; } memset(elements, 0, sizeof(RenderElement) * count); if (!max_count) return; bool is_quad = a1->data.data.type == PARTICLE_QUAD; glGenVertexArrays(1, &vao); gl_state_bind_vertex_array(vao, true); glGenBuffers(1, &vbo); gl_state_bind_array_buffer(vbo, true); static const GLsizei buffer_size = sizeof(Buffer); if (GLAD_GL_VERSION_4_4) glBufferStorage(GL_ARRAY_BUFFER, (GLsizeiptr)(buffer_size * max_count), 0, GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT); else glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(buffer_size * max_count), 0, GL_DYNAMIC_DRAW); if (is_quad) { size_t count = max_count / 4 * 5; uint32_t* ebo_data = force_malloc_s(uint32_t, count - 1); for (size_t i = 0, j = 0, k = count / 5 - 1; i < count; i += 5, j += 4, k--) { ebo_data[i + 0] = (uint32_t)(j + 0); ebo_data[i + 1] = (uint32_t)(j + 1); ebo_data[i + 2] = (uint32_t)(j + 3); ebo_data[i + 3] = (uint32_t)(j + 2); if (k) ebo_data[i + 4] = 0xFFFFFFFF; } glGenBuffers(1, &ebo); gl_state_bind_element_array_buffer(ebo, true); if (GLAD_GL_VERSION_4_4) glBufferStorage(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(sizeof(uint32_t) * (count - 1)), ebo_data, 0); else glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(sizeof(uint32_t) * (count - 1)), ebo_data, GL_STATIC_DRAW); free_def(ebo_data); } glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, position)); // Pos glEnableVertexAttribArray(3); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, color)); // Color0 glEnableVertexAttribArray(8); glVertexAttribPointer(8, 2, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, uv)); // TexCoord0 glEnableVertexAttribArray(9); glVertexAttribPointer(9, 2, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, uv)); // TexCoord1 gl_state_bind_array_buffer(0); gl_state_bind_vertex_array(0); if (is_quad) gl_state_bind_element_array_buffer(0); if (!is_quad) draw_list.reserve(count); } F2RenderGroup::~F2RenderGroup() { DeleteBuffers(false); } bool F2RenderGroup::CannotDisp() { if (!particle) return true; else if (particle->data.effect) return (particle->data.effect->flags & EFFECT_INST_HAS_EXT_ANIM_NON_INIT) != 0; else if ((particle = particle->data.parent) && particle->data.effect) return (particle->data.effect->flags & EFFECT_INST_HAS_EXT_ANIM_NON_INIT) != 0; return true; } void F2RenderGroup::Copy(F2RenderGroup* dst) { dst->flags = flags; dst->type = type; dst->draw_type = draw_type; dst->blend_mode = blend_mode; dst->mask_blend_mode = mask_blend_mode; dst->pivot = pivot; dst->split_u = split_u; dst->split_v = split_v; dst->split_uv = split_uv; dst->z_offset = z_offset; if (dst->count > count) dst->count = count; dst->ctrl = ctrl; dst->texture = texture; dst->mask_texture = mask_texture; dst->frame = frame; dst->mat = mat; dst->mat_rot = mat_rot; dst->mat_draw = mat_draw; if (dst->count == count) { memmove(dst->elements, elements, sizeof(RenderElement) * dst->count); dst->ctrl = ctrl; } } void F2RenderGroup::Ctrl(GLT, float_t delta_frame, bool copy_mats) { if (!particle) return; F2ParticleInst::Data* data = &particle->data; blend_mode = data->data.blend_mode; mask_blend_mode = data->data.mask_blend_mode; texture = data->data.texture; mask_texture = data->data.mask_texture; split_u = data->data.split_u; split_v = data->data.split_v; split_uv = data->data.split_uv; type = data->data.type; draw_type = data->data.draw_type; z_offset = data->data.z_offset; pivot = data->data.pivot; flags = data->data.flags; if (copy_mats && particle->data.emitter) { F2EmitterInst* emitter = particle->data.emitter; mat = emitter->mat; mat_rot = emitter->mat_rot; } for (size_t ctrl = this->ctrl, i = 0; ctrl; i++) { if (!elements[i].alive) continue; CtrlParticle(GLT_VAL, &elements[i], delta_frame); ctrl--; } frame += delta_frame; } void F2RenderGroup::CtrlParticle(GLT, Glitter::RenderElement* rend_elem, float_t delta_frame) { if (!particle || (particle->data.data.flags & PARTICLE_LOOP && particle->HasEnded(false)) || rend_elem->frame >= rend_elem->life_time) { rend_elem->alive = false; delete rend_elem->locus_history; rend_elem->locus_history = 0; ctrl--; return; } particle->AccelerateParticle(GLT_VAL, rend_elem, rend_elem->frame * (float_t)(1.0 / 60.0), delta_frame, random_ptr); if (particle->data.data.draw_type == DIRECTION_PARTICLE_ROTATION) rend_elem->rotation += rend_elem->rotation_add * delta_frame; else rend_elem->rotation.z += rend_elem->rotation_add.z * delta_frame; rend_elem->uv_scroll += particle->data.data.uv_scroll_add * (particle->data.data.uv_scroll_add_scale * delta_frame); particle->StepUVParticle(GLT_VAL, rend_elem, delta_frame, random_ptr); rend_elem->disp = true; rend_elem->color = { -1.0f, -1.0f, -1.0f, -1.0f }; bool disp = true; if (particle->data.data.sub_flags & PARTICLE_SUB_USE_CURVE) disp = particle->GetValue(GLT_VAL, rend_elem, rend_elem->frame, random_ptr); if (particle->data.data.draw_type == DIRECTION_PARTICLE_ROTATION || fabsf(rend_elem->rotation.z) <= 0.000001f) { rend_elem->rot_z_cos = 1.0f; rend_elem->rot_z_sin = 0.0f; } else { rend_elem->rot_z_cos = cosf(rend_elem->rotation.z); rend_elem->rot_z_sin = sinf(rend_elem->rotation.z); } if (disp) particle->GetColor(rend_elem); if (particle->data.data.type == PARTICLE_LOCUS) rend_elem->locus_history->Append(rend_elem, particle); rend_elem->frame += delta_frame; if (particle->data.data.flags & PARTICLE_LOOP && rend_elem->frame >= rend_elem->life_time) rend_elem->frame -= rend_elem->life_time; } void F2RenderGroup::DeleteBuffers(bool free) { if (particle) { if (!free) particle->data.render_group = 0; particle = 0; } if (ebo) { glDeleteBuffers(1, &ebo); ebo = 0; } if (vbo) { glDeleteBuffers(1, &vbo); vbo = 0; } if (vao) { glDeleteVertexArrays(1, &vao); vao = 0; } if (!free && elements) { Free(); delete[] elements; elements = 0; } } void F2RenderGroup::Emit(GPM, GLT, F2ParticleInst::Data* ptcl_inst_data, F2EmitterInst* emit_inst, int32_t dup_count, int32_t count) { RenderElement* element = 0; for (int32_t i = 0; i < dup_count; i++) for (int32_t index = 0; index < count; index++, element++) { element = AddElement(element); if (!element) break; particle->EmitParticle(GPM_VAL, GLT_VAL, element, emit_inst, ptcl_inst_data, index, random_ptr); } } void F2RenderGroup::Free() { if (count <= 0) { ctrl = 0; return; } RenderElement* elem = elements; for (size_t i = count; i; i--, elem++) { elem->alive = false; if (elem->locus_history) { delete elem->locus_history; elem->locus_history = 0; } } ctrl = 0; } void F2RenderGroup::FreeData() { DeleteBuffers(true); } bool F2RenderGroup::GetExtAnimScale(vec3* ext_anim_scale, float_t* some_scale) { if (particle) particle->GetExtAnimScale(ext_anim_scale, some_scale); return false; } mat4 F2RenderGroup::RotateToEmitPosition(F2RenderGroup* rend_group, RenderElement* rend_elem, vec3* vec) { vec3 vec2; if (rend_group->flags & PARTICLE_EMITTER_LOCAL) vec2 = rend_elem->translation; else { mat4_get_translation(&rend_group->mat, &vec2); vec2 -= rend_elem->translation; } vec2 = vec3::normalize(vec2); mat4 mat; if (fabsf(vec2.y) >= 0.000001f) { mat = mat4_identity; vec3 vec1 = *vec; vec3 axis; float_t angle; axis_angle_from_vectors(&axis, &angle, &vec1, &vec2); mat4_mult_axis_angle(&mat, &axis, angle, &mat); } else mat4_rotate_z((float_t)M_PI, &mat); return mat; } mat4 F2RenderGroup::RotateToPrevPosition(F2RenderGroup* rend_group, RenderElement* rend_elem, vec3* vec) { vec3 vec2 = rend_elem->translation - rend_elem->translation_prev; if (vec3::length_squared(vec2) < 0.000001f) vec2 = vec3::normalize(rend_elem->translation); else vec2 = vec3::normalize(vec2); mat4 mat; mat4_rotate_z((float_t)M_PI, &mat); if (fabsf(vec2.y) >= 0.000001f) { vec3 vec1 = *vec; vec3 axis; float_t angle; axis_angle_from_vectors(&axis, &angle, &vec1, &vec2); mat4_mult_axis_angle(&mat, &axis, angle, &mat); } return mat; } XRenderGroup::XRenderGroup(XParticleInst* a1) : particle(), use_culling() { object_name_hash = hash_murmurhash_empty; switch (a1->data.data.type) { case PARTICLE_QUAD: if (a1->data.data.count > 0) count = a1->data.data.count; else count = 2500; max_count = 4 * count; break; case PARTICLE_LINE: count = (size_t)a1->data.data.locus_history_size + a1->data.data.locus_history_size_random; max_count = count; break; case PARTICLE_LOCUS: count = (size_t)a1->data.data.locus_history_size + a1->data.data.locus_history_size_random; max_count = 2 * count; break; case PARTICLE_MESH: count = 1280; max_count = 0; break; default: return; } random_ptr = a1->data.random_ptr; particle = a1; elements = new RenderElement[count]; if (!elements) { count = 0; max_count = 0; return; } memset(elements, 0, sizeof(RenderElement) * count); if (!max_count || a1->data.data.type == PARTICLE_MESH) return; bool is_quad = a1->data.data.type == PARTICLE_QUAD; glGenVertexArrays(1, &vao); gl_state_bind_vertex_array(vao, true); glGenBuffers(1, &vbo); gl_state_bind_array_buffer(vbo, true); static const GLsizei buffer_size = sizeof(Buffer); if (GLAD_GL_VERSION_4_4) glBufferStorage(GL_ARRAY_BUFFER, (GLsizeiptr)(buffer_size * max_count), 0, GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT); else glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(buffer_size * max_count), 0, GL_DYNAMIC_DRAW); if (is_quad) { size_t count = max_count / 4 * 5; uint32_t* ebo_data = force_malloc_s(uint32_t, count - 1); for (size_t i = 0, j = 0, k = count / 5 - 1; i < count; i += 5, j += 4, k--) { ebo_data[i + 0] = (uint32_t)(j + 0); ebo_data[i + 1] = (uint32_t)(j + 1); ebo_data[i + 2] = (uint32_t)(j + 3); ebo_data[i + 3] = (uint32_t)(j + 2); if (k) ebo_data[i + 4] = 0xFFFFFFFF; } glGenBuffers(1, &ebo); gl_state_bind_element_array_buffer(ebo, true); if (GLAD_GL_VERSION_4_4) glBufferStorage(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(sizeof(uint32_t) * (count - 1)), ebo_data, 0); else glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)(sizeof(uint32_t) * (count - 1)), ebo_data, GL_STATIC_DRAW); free_def(ebo_data); } glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, position)); // Pos glEnableVertexAttribArray(3); glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, color)); // Color0 glEnableVertexAttribArray(8); glVertexAttribPointer(8, 2, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, uv)); // TexCoord0 glEnableVertexAttribArray(9); glVertexAttribPointer(9, 2, GL_FLOAT, GL_FALSE, buffer_size, (void*)offsetof(Buffer, uv)); // TexCoord1 gl_state_bind_array_buffer(0); gl_state_bind_vertex_array(0); if (is_quad) gl_state_bind_element_array_buffer(0); if (!is_quad) draw_list.reserve(count); } XRenderGroup::~XRenderGroup() { DeleteBuffers(false); } bool XRenderGroup::CannotDisp() { XEffectInst* effect; if (!particle) return true; else if (particle->data.effect) return (particle->data.effect->flags & EFFECT_INST_HAS_EXT_ANIM_NON_INIT) != 0; else if ((particle = particle->data.parent) && (effect = particle->data.effect)) return (effect->flags & EFFECT_INST_HAS_EXT_ANIM_NON_INIT) != 0; else return true; } void XRenderGroup::Copy(XRenderGroup* dst) { dst->flags = flags; dst->type = type; dst->draw_type = draw_type; dst->blend_mode = blend_mode; dst->mask_blend_mode = mask_blend_mode; dst->pivot = pivot; dst->split_u = split_u; dst->split_v = split_v; dst->split_uv = split_uv; dst->z_offset = z_offset; if (dst->count > count) dst->count = count; dst->ctrl = ctrl; dst->texture = texture; dst->mask_texture = mask_texture; dst->frame = frame; dst->mat = mat; dst->mat_rot = mat_rot; dst->mat_draw = mat_draw; if (dst->count == count) { memmove(dst->elements, elements, sizeof(RenderElement) * dst->count); dst->ctrl = ctrl; } } void XRenderGroup::Ctrl(float_t delta_frame, bool copy_mats) { if (!particle) return; XParticleInst::Data* data = &particle->data; blend_mode = data->data.blend_mode; mask_blend_mode = data->data.mask_blend_mode; texture = data->data.texture; mask_texture = data->data.mask_texture; object_name_hash = data->data.mesh.object_name_hash; split_u = data->data.split_u; split_v = data->data.split_v; split_uv = data->data.split_uv; type = data->data.type; draw_type = data->data.draw_type; z_offset = data->data.z_offset; pivot = data->data.pivot; flags = data->data.flags; if (copy_mats && particle->data.emitter) { XEmitterInst* emitter = particle->data.emitter; mat = emitter->mat; mat_rot = emitter->mat_rot; } for (size_t ctrl = this->ctrl, i = 0; ctrl; i++) { if (!elements[i].alive) continue; CtrlParticle(&elements[i], delta_frame); ctrl--; } frame += delta_frame; } void XRenderGroup::CtrlParticle(RenderElement* rend_elem, float_t delta_frame) { random_ptr->XSetStep(rend_elem->step); if (!particle || (particle->data.data.flags & PARTICLE_LOOP && particle->HasEnded(false)) || rend_elem->frame >= rend_elem->life_time) { rend_elem->alive = false; delete rend_elem->locus_history; rend_elem->locus_history = 0; ctrl--; return; } particle->AccelerateParticle(rend_elem, delta_frame, random_ptr); if (particle->data.data.draw_type == DIRECTION_PARTICLE_ROTATION || particle->data.data.type == PARTICLE_MESH) rend_elem->rotation += rend_elem->rotation_add * delta_frame; else rend_elem->rotation.z += rend_elem->rotation_add.z * delta_frame; vec2 uv_scroll; vec2_mult_scalar(particle->data.data.uv_scroll_add, particle->data.data.uv_scroll_add_scale * delta_frame, uv_scroll); if (uv_scroll.x != 0.0f) rend_elem->uv_scroll.x = fmodf(rend_elem->uv_scroll.x + uv_scroll.x, 1.0f); if (uv_scroll.y != 0.0f) rend_elem->uv_scroll.y = fmodf(rend_elem->uv_scroll.y + uv_scroll.y, 1.0f); if (particle->data.data.sub_flags & PARTICLE_SUB_UV_2ND_ADD) { vec2 uv_scroll_2nd; vec2_mult_scalar(particle->data.data.uv_scroll_2nd_add, particle->data.data.uv_scroll_2nd_add_scale * delta_frame, uv_scroll_2nd); if (uv_scroll_2nd.x != 0.0f) rend_elem->uv_scroll_2nd.x = fmodf(rend_elem->uv_scroll_2nd.x + uv_scroll_2nd.x, 1.0f); if (uv_scroll_2nd.y != 0.0f) rend_elem->uv_scroll_2nd.y = fmodf(rend_elem->uv_scroll_2nd.y + uv_scroll_2nd.y, 1.0f); } particle->StepUVParticle(rend_elem, delta_frame, random_ptr); rend_elem->disp = true; rend_elem->color = { -1.0f, -1.0f, -1.0f, -1.0f }; bool disp = true; float_t color_scale = -1.0f; if (particle->data.data.sub_flags & PARTICLE_SUB_USE_CURVE) disp = particle->GetValue(rend_elem, rend_elem->frame, random_ptr, &color_scale); if (particle->data.data.draw_type == DIRECTION_PARTICLE_ROTATION || fabsf(rend_elem->rotation.z) <= 0.000001f) { rend_elem->rot_z_cos = 1.0f; rend_elem->rot_z_sin = 0.0f; } else { rend_elem->rot_z_cos = cosf(rend_elem->rotation.z); rend_elem->rot_z_sin = sinf(rend_elem->rotation.z); } if (disp) particle->GetColor(rend_elem, color_scale); if (particle->data.data.type == PARTICLE_LOCUS) rend_elem->locus_history->Append(rend_elem, particle); rend_elem->frame += delta_frame; if (particle->data.data.flags & PARTICLE_LOOP && rend_elem->frame >= rend_elem->life_time) rend_elem->frame -= rend_elem->life_time; } void XRenderGroup::DeleteBuffers(bool a2) { if (particle) { if (!a2) particle->data.render_group = 0; particle = 0; } if (ebo) { glDeleteBuffers(1, &ebo); ebo = 0; } if (vbo) { glDeleteBuffers(1, &vbo); vbo = 0; } if (vao) { glDeleteVertexArrays(1, &vao); vao = 0; } if (!a2 && elements) { Free(); free_def(elements); } } void XRenderGroup::Emit(XParticleInst::Data* ptcl_inst_data, XEmitterInst* emit_inst, int32_t dup_count, int32_t count) { RenderElement* element; int64_t i; int32_t index; uint8_t step; step = emit_inst->RandomGetStep(); random_ptr->XSetStep(1); for (element = 0, i = 0; i < dup_count; i++) for (index = 0; index < count; index++, element++) { element = AddElement(element); if (!element) break; emit_inst->RandomStepValue(); particle->EmitParticle(element, emit_inst, ptcl_inst_data, index, step, random_ptr); } } void XRenderGroup::Free() { if (count <= 0) { ctrl = 0; return; } RenderElement* elem = elements; for (size_t i = count; i; i--, elem++) { elem->alive = false; if (elem->locus_history) { delete elem->locus_history; elem->locus_history = 0; } } ctrl = 0; } void XRenderGroup::FreeData() { DeleteBuffers(true); } bool XRenderGroup::GetEmitterScale(vec3& emitter_scale) { if (particle) { Glitter::EmitterInst* emit_inst = particle->data.emitter; if (emit_inst) { emitter_scale = emit_inst->scale * emit_inst->scale_all; return emitter_scale.x > 0.000001f || emitter_scale.y > 0.000001f || emitter_scale.z > 0.000001f; } } emitter_scale = 1.0f; return false; } bool XRenderGroup::GetExtAnimScale(vec3* ext_anim_scale, float_t* some_scale) { if (particle) particle->GetExtAnimScale(ext_anim_scale, some_scale); return false; } bool XRenderGroup::HasEnded() { if (particle) return particle->HasEnded(true); return true; } mat4 XRenderGroup::RotateMeshToEmitPosition(XRenderGroup* rend_group, RenderElement* rend_elem, vec3* vec, vec3* trans) { vec3 vec2; mat4_get_translation(&rend_group->mat, &vec2); vec2 -= *trans; if (vec3::length_squared(vec2) < 0.000001f) vec2 = { 0.0f, 1.0f, 0.0f }; else vec2 = vec3::normalize(vec2); vec3 vec1 = *vec; vec3 axis; float_t angle; axis_angle_from_vectors(&axis, &angle, &vec1, &vec2); mat4 mat; mat4_mult_axis_angle(&mat4_identity, &axis, angle, &mat); return mat; } mat4 XRenderGroup::RotateMeshToPrevPosition(XRenderGroup* rend_group, RenderElement* rend_elem, vec3* vec, vec3* trans) { vec3 vec2 = rend_elem->translation - rend_elem->translation_prev; if (vec3::length_squared(vec2) < 0.000001f) vec2 = vec3::normalize(rend_elem->base_direction); else vec2 = vec3::normalize(vec2); vec3 vec1 = *vec; vec3 axis; float_t angle; axis_angle_from_vectors(&axis, &angle, &vec1, &vec2); mat4 mat; mat4_mult_axis_angle(&mat4_identity, &axis, angle, &mat); return mat; } mat4 XRenderGroup::RotateToEmitPosition(XRenderGroup* rend_group, RenderElement* rend_elem, vec3* vec) { vec3 vec2; mat4_get_translation(&rend_elem->mat, &vec2); vec2 -= rend_elem->translation; if (vec3::length_squared(vec2) < 0.000001f) vec2 = { 0.0f, 1.0f, 0.0f }; else vec3::normalize(vec2); vec3 vec1 = *vec; vec3 axis; float_t angle; axis_angle_from_vectors(&axis, &angle, &vec1, &vec2); mat4 mat; mat4_mult_axis_angle(&mat4_identity, &axis, angle, &mat); return mat; } mat4 XRenderGroup::RotateToPrevPosition(XRenderGroup* rend_group, RenderElement* rend_elem, vec3* vec) { vec3 vec2 = rend_elem->translation - rend_elem->translation_prev; if (vec3::length_squared(vec2) < 0.000001f) vec2 = vec3::normalize(rend_elem->base_direction); else vec2 = vec3::normalize(vec2); vec3 vec1 = *vec; vec3 axis; float_t angle; axis_angle_from_vectors(&axis, &angle, &vec1, &vec2); mat4 mat; mat4_mult_axis_angle(&mat4_identity, &axis, angle, &mat); return mat; } }