diff --git a/src/structure/StructureMaker.cpp b/src/structure/StructureMaker.cpp index f5d5c7e..02feb1a 100644 --- a/src/structure/StructureMaker.cpp +++ b/src/structure/StructureMaker.cpp @@ -90,7 +90,6 @@ void StructureMaker::compute_helix_axis(const std::vector& helix, float (& void StructureMaker::calculate_ss_points(std::map>& init_atoms, std::map>& ss_atoms) { - // std::cout << " apply structure\n"; ss_atoms.clear(); for (auto& [chainID, atoms] : init_atoms) { @@ -100,7 +99,7 @@ void StructureMaker::calculate_ss_points(std::map char s = atoms[i].structure; if (s == 'H') { - // helix start: find sequential H + // Find the full helix segment size_t start = i; while (i < atoms.size() && atoms[i].structure == 'H') ++i; size_t end = i; @@ -116,10 +115,11 @@ void StructureMaker::calculate_ss_points(std::map float dz = segment.back().z - segment.front().z; float length = std::sqrt(dx * dx + dy * dy + dz * dz); - const int steps = std::min(circle_steps, (end - start)); + // One axial slice per residue for a smooth, properly-sampled cylinder + const int steps = (int)(end - start); - float up[3] = {0, 0, 1}; - if (std::abs(axis[2]) > 0.99f) { up[0] = 1; up[2] = 0; } + float up[3] = {0.0f, 0.0f, 1.0f}; + if (std::abs(axis[2]) > 0.99f) { up[0] = 1.0f; up[2] = 0.0f; } float n1[3] = { axis[1]*up[2] - axis[2]*up[1], @@ -135,89 +135,106 @@ void StructureMaker::calculate_ss_points(std::map axis[0]*n1[1] - axis[1]*n1[0] }; - for (int s = 0; s <= steps; ++s) { - float t = static_cast(s) / steps; - float base[3] = { - center[0] + axis[0] * (t - 0.5f) * length, - center[1] + axis[1] * (t - 0.5f) * length, - center[2] + axis[2] * (t - 0.5f) * length, - }; - - for (int a = 0; a < circle_steps; ++a) { - float theta = 2 * PI * a / circle_steps; - float dx = std::cos(theta); - float dy = std::sin(theta); - - float px = base[0] + radius * (dx * n1[0] + dy * n2[0]); - float py = base[1] + radius * (dx * n1[1] + dy * n2[1]); - float pz = base[2] + radius * (dx * n1[2] + dy * n2[2]); - + // Render as longitudinal zigzag stripes instead of cross-sectional rings. + // Each stripe is one thread along the cylinder surface at a fixed angle; + // alternating stripes go forward/backward so transitions are short arcs + // at the cylinder ends rather than long diagonal cuts across the surface. + for (int a = 0; a < circle_steps; ++a) { + float theta = 2.0f * PI * a / circle_steps; + float cos_t = std::cos(theta); + float sin_t = std::sin(theta); + bool forward = (a % 2 == 0); + + for (int si = 0; si <= steps; ++si) { + int s_idx = forward ? si : (steps - si); + float t = static_cast(s_idx) / steps; + float base[3] = { + center[0] + axis[0] * (t - 0.5f) * length, + center[1] + axis[1] * (t - 0.5f) * length, + center[2] + axis[2] * (t - 0.5f) * length, + }; + float px = base[0] + radius * (cos_t * n1[0] + sin_t * n2[0]); + float py = base[1] + radius * (cos_t * n1[1] + sin_t * n2[1]); + float pz = base[2] + radius * (cos_t * n1[2] + sin_t * n2[2]); output.emplace_back(px, py, pz, 'H'); } } - } else { - // if too short, ignore - i = end; } + // i already advanced to end of helix segment by the inner while loop } - else if (s == 'S' && i + 1 < atoms.size() && atoms[i + 1].structure == 'S') { - const Atom& p1 = atoms[i]; - const Atom& p2 = atoms[i + 1]; + else if (s == 'S') { + // Process the full consecutive sheet segment at once for a uniform ribbon + size_t seg_start = i; + while (i < atoms.size() && atoms[i].structure == 'S') ++i; + size_t seg_end = i; + int seg_len = (int)(seg_end - seg_start); - float dx = p2.x - p1.x; - float dy = p2.y - p1.y; - float dz = p2.z - p1.z; - float len = std::sqrt(dx * dx + dy * dy + dz * dz); - if (len == 0) { i++; continue; } + if (seg_len < 2) { + output.push_back(atoms[seg_start]); + continue; + } - float axis[3] = { dx / len, dy / len, dz / len }; // direction vector - float up[3] = { 0.0f, 0.0f, 1.0f }; - if (std::abs(axis[2]) > 0.99f) { - up[0] = 1.0f; up[2] = 0.0f; // if almost similar to z-axis, replace + // Compute a consistent perpendicular from the overall segment direction + float dx = atoms[seg_end-1].x - atoms[seg_start].x; + float dy = atoms[seg_end-1].y - atoms[seg_start].y; + float dz = atoms[seg_end-1].z - atoms[seg_start].z; + float len = std::sqrt(dx*dx + dy*dy + dz*dz); + if (len < 1e-6f) { + for (size_t k = seg_start; k < seg_end; ++k) output.push_back(atoms[k]); + continue; } - // n1: vector perpendicular to axis + float fwd[3] = {dx/len, dy/len, dz/len}; + float up[3] = {0.0f, 0.0f, 1.0f}; + if (std::abs(fwd[2]) > 0.99f) { up[0] = 1.0f; up[2] = 0.0f; } + float n1[3] = { - axis[1]*up[2] - axis[2]*up[1], - axis[2]*up[0] - axis[0]*up[2], - axis[0]*up[1] - axis[1]*up[0] + fwd[1]*up[2] - fwd[2]*up[1], + fwd[2]*up[0] - fwd[0]*up[2], + fwd[0]*up[1] - fwd[1]*up[0] }; float n1_norm = std::sqrt(n1[0]*n1[0] + n1[1]*n1[1] + n1[2]*n1[2]); - for (int j = 0; j < 3; ++j) n1[j] /= n1_norm; - - // ribbon width vector, move to direction n1 - int line_steps = std::max(2, static_cast(len / 0.05f)); + if (n1_norm < 1e-6f) { + for (size_t k = seg_start; k < seg_end; ++k) output.push_back(atoms[k]); + continue; + } + for (int k = 0; k < 3; ++k) n1[k] /= n1_norm; + // Draw ribbon as parallel zigzag stripes along the full segment. + // Even steps traverse forward; odd steps traverse backward → short + // cross-connections at the ribbon ends, long clean lines along the strand. for (int step = -width; step <= width; ++step) { - float offset[3] = { - n1[0] * step * 0.05f, - n1[1] * step * 0.05f, - n1[2] * step * 0.05f - }; - - float x1 = p1.x + offset[0]; - float y1 = p1.y + offset[1]; - float z1 = p1.z + offset[2]; - - float x2 = p2.x + offset[0]; - float y2 = p2.y + offset[1]; - float z2 = p2.z + offset[2]; - - for (int t = 0; t <= line_steps; ++t) { - float f = static_cast(t) / line_steps; - float x = x1 + f * (x2 - x1); - float y = y1 + f * (y2 - y1); - float z = z1 + f * (z2 - z1); - - output.emplace_back(x, y, z, 'S'); + float offset = step * sheet_step; + float ox = n1[0]*offset, oy = n1[1]*offset, oz = n1[2]*offset; + bool forward = ((step + width) % 2 == 0); + + for (int j = 0; j < seg_len - 1; ++j) { + int actual = forward ? j : (seg_len - 2 - j); + const Atom& pa = atoms[seg_start + actual]; + const Atom& pb = atoms[seg_start + actual + 1]; + + float pair_dx = pb.x - pa.x; + float pair_dy = pb.y - pa.y; + float pair_dz = pb.z - pa.z; + float pair_len = std::sqrt(pair_dx*pair_dx + pair_dy*pair_dy + pair_dz*pair_dz); + int line_steps = std::max(2, (int)(pair_len / 0.05f)); + + for (int t = 0; t <= line_steps; ++t) { + float f = static_cast(t) / line_steps; + output.emplace_back( + pa.x + ox + f * pair_dx, + pa.y + oy + f * pair_dy, + pa.z + oz + f * pair_dz, + 'S' + ); + } } } - i++; // sheet: pair + // i already advanced to seg_end by the inner while loop } else { - // no structure, just add output.push_back(atoms[i]); ++i; } diff --git a/src/structure/StructureMaker.hpp b/src/structure/StructureMaker.hpp index 10f8c60..b102245 100644 --- a/src/structure/StructureMaker.hpp +++ b/src/structure/StructureMaker.hpp @@ -18,8 +18,9 @@ class StructureMaker { std::vector> extract_helix_segments(const Atom* atoms, int num_atoms); private: float radius = 2.5f; - int circle_steps = 8; - int width = 4; + int circle_steps = 16; // 16 longitudinal stripes → smooth cylinder + int width = 6; // half-width of beta-sheet ribbon + float sheet_step = 0.07f; // ribbon offset per step unit }; diff --git a/src/visualization/Screen.cpp b/src/visualization/Screen.cpp index 3e819fd..3b8819f 100644 --- a/src/visualization/Screen.cpp +++ b/src/visualization/Screen.cpp @@ -427,13 +427,30 @@ void Screen::project() { int screenY = (int)((1.0f - projectedY) * 0.5f * logical_h); if (prevScreenX != -1 && prevScreenY != -1) { - draw_line(chainPoints, - prevScreenX, screenX, - prevScreenY, screenY, - prevZ, z, - chainID, structure, - depth_base_min_z, depth_base_max_z, - logical_w, logical_h); + // Catmull-Rom spline: interpolate 4 sub-steps through the 3-D + // backbone to produce smooth curves instead of straight segments. + const Atom& P0 = chain_atoms[std::max(0, i-2)]; + const Atom& P1 = chain_atoms[i-1]; + const Atom& P2 = chain_atoms[i]; + const Atom& P3 = chain_atoms[std::min(num_atoms-1, i+1)]; + + int cr_prevX = prevScreenX, cr_prevY = prevScreenY; + float cr_prevZ = prevZ; + + for (int cr = 1; cr <= 4; ++cr) { + float t = cr * 0.25f; + float t2 = t*t, t3 = t2*t; + float cx = 0.5f*((-P0.x+3*P1.x-3*P2.x+P3.x)*t3 + (2*P0.x-5*P1.x+4*P2.x-P3.x)*t2 + (-P0.x+P2.x)*t + 2*P1.x); + float cy = 0.5f*((-P0.y+3*P1.y-3*P2.y+P3.y)*t3 + (2*P0.y-5*P1.y+4*P2.y-P3.y)*t2 + (-P0.y+P2.y)*t + 2*P1.y); + float cz = 0.5f*((-P0.z+3*P1.z-3*P2.z+P3.z)*t3 + (2*P0.z-5*P1.z+4*P2.z-P3.z)*t2 + (-P0.z+P2.z)*t + 2*P1.z) + focal_offset; + if (cz < nearPlane) break; + float cpX = (cx/cz)*fovRads + pan_x[ii]; + float cpY = (cy/cz)*fovRads + pan_y[ii]; + int cX = (int)((cpX+1.0f)*0.5f*logical_w); + int cY = (int)((1.0f-cpY)*0.5f*logical_h); + draw_line(chainPoints, cr_prevX, cX, cr_prevY, cY, cr_prevZ, cz, chainID, structure, depth_base_min_z, depth_base_max_z, logical_w, logical_h); + cr_prevX = cX; cr_prevY = cY; cr_prevZ = cz; + } } if (screenX >= 0 && screenX < logical_w && screenY >= 0 && screenY < logical_h) { @@ -509,12 +526,28 @@ void Screen::project() { int screenY = (int)((1.0f - projectedY) * 0.5f * screen_height); if (prevScreenX != -1 && prevScreenY != -1) { - draw_line(chainPoints, - prevScreenX, screenX, - prevScreenY, screenY, - prevZ, z, - chainID, structure, - depth_base_min_z, depth_base_max_z); + const Atom& P0 = chain_atoms[std::max(0, i-2)]; + const Atom& P1 = chain_atoms[i-1]; + const Atom& P2 = chain_atoms[i]; + const Atom& P3 = chain_atoms[std::min(num_atoms-1, i+1)]; + + int cr_prevX = prevScreenX, cr_prevY = prevScreenY; + float cr_prevZ = prevZ; + + for (int cr = 1; cr <= 4; ++cr) { + float t = cr * 0.25f; + float t2 = t*t, t3 = t2*t; + float cx = 0.5f*((-P0.x+3*P1.x-3*P2.x+P3.x)*t3 + (2*P0.x-5*P1.x+4*P2.x-P3.x)*t2 + (-P0.x+P2.x)*t + 2*P1.x); + float cy = 0.5f*((-P0.y+3*P1.y-3*P2.y+P3.y)*t3 + (2*P0.y-5*P1.y+4*P2.y-P3.y)*t2 + (-P0.y+P2.y)*t + 2*P1.y); + float cz = 0.5f*((-P0.z+3*P1.z-3*P2.z+P3.z)*t3 + (2*P0.z-5*P1.z+4*P2.z-P3.z)*t2 + (-P0.z+P2.z)*t + 2*P1.z) + focal_offset; + if (cz < nearPlane) break; + float cpX = (cx/cz)*fovRads + pan_x[ii]; + float cpY = (cy/cz)*fovRads + pan_y[ii]; + int cX = (int)((cpX+1.0f)*0.5f*screen_width); + int cY = (int)((1.0f-cpY)*0.5f*screen_height); + draw_line(chainPoints, cr_prevX, cX, cr_prevY, cY, cr_prevZ, cz, chainID, structure, depth_base_min_z, depth_base_max_z); + cr_prevX = cX; cr_prevY = cY; cr_prevZ = cz; + } } if (screenX >= 0 && screenX < screen_width && screenY >= 0 && screenY < screen_height) { @@ -603,12 +636,28 @@ void Screen::project(std::vector& projectPixels, const int proj_wid int screenY = (int)((1.0f - projectedY) * 0.5f * proj_height); if (prevScreenX != -1 && prevScreenY != -1) { - draw_line(chainPoints, - prevScreenX, screenX, - prevScreenY, screenY, - prevZ, z, - chainID, structure, - depth_base_min_z, depth_base_max_z); + const Atom& P0 = chain_atoms[std::max(0, i-2)]; + const Atom& P1 = chain_atoms[i-1]; + const Atom& P2 = chain_atoms[i]; + const Atom& P3 = chain_atoms[std::min(num_atoms-1, i+1)]; + + int cr_prevX = prevScreenX, cr_prevY = prevScreenY; + float cr_prevZ = prevZ; + + for (int cr = 1; cr <= 4; ++cr) { + float t = cr * 0.25f; + float t2 = t*t, t3 = t2*t; + float cx = 0.5f*((-P0.x+3*P1.x-3*P2.x+P3.x)*t3 + (2*P0.x-5*P1.x+4*P2.x-P3.x)*t2 + (-P0.x+P2.x)*t + 2*P1.x); + float cy = 0.5f*((-P0.y+3*P1.y-3*P2.y+P3.y)*t3 + (2*P0.y-5*P1.y+4*P2.y-P3.y)*t2 + (-P0.y+P2.y)*t + 2*P1.y); + float cz = 0.5f*((-P0.z+3*P1.z-3*P2.z+P3.z)*t3 + (2*P0.z-5*P1.z+4*P2.z-P3.z)*t2 + (-P0.z+P2.z)*t + 2*P1.z) + focal_offset; + if (cz < nearPlane) break; + float cpX = (cx/cz)*fovRads + pan_x[ii]; + float cpY = (cy/cz)*fovRads + pan_y[ii]; + int cX = (int)((cpX+1.0f)*0.5f*proj_width); + int cY = (int)((1.0f-cpY)*0.5f*proj_height); + draw_line(chainPoints, cr_prevX, cX, cr_prevY, cY, cr_prevZ, cz, chainID, structure, depth_base_min_z, depth_base_max_z); + cr_prevX = cX; cr_prevY = cY; cr_prevZ = cz; + } } if (screenX >= 0 && screenX < proj_width && screenY >= 0 && screenY < proj_height) { diff --git a/structty-improvements.patch b/structty-improvements.patch new file mode 100644 index 0000000..cad048a --- /dev/null +++ b/structty-improvements.patch @@ -0,0 +1,373 @@ +From 806d2a1eb3a15774409ce6d309ec37125272813c Mon Sep 17 00:00:00 2001 +From: Claude +Date: Fri, 6 Mar 2026 06:29:46 +0000 +Subject: [PATCH] Improve secondary structure clarity and overall backbone + smoothness +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Three complementary improvements: + +1. Helix: render as longitudinal zigzag stripes instead of cross-sectional rings + - Each of 16 stripes (up from 8) runs along the cylinder axis at a fixed angle; + alternating stripes go forward/backward so transitions are short end-cap arcs + rather than long diagonal cuts across the surface → clean tube appearance + - Axial slices scale with helix length (one per residue, was min(8, len)) + so longer helices are no longer under-sampled + +2. Sheet: process the full strand segment at once with a consistent perpendicular + - Was: pair-by-pair, each pair independently computing its own normal (could flip) + - Now: overall segment direction determines a single stable n1 vector; ribbon + drawn as zigzag parallel stripes across the full strand → uniform flat ribbon + - Wider ribbon: width 4→6 steps, offset 0.05→0.07 per step (total ±0.42 units) + +3. Backbone: Catmull-Rom spline interpolation in all three project() paths + - 4 sub-steps per Cα–Cα bond using the four surrounding control points + - Turns every straight segment into a smooth curve; visually most significant + for loop/coil regions and helix spirals without -s flag + +https://claude.ai/code/session_01LaPVogs2SPnVqeSSbd2ux6 +--- + src/structure/StructureMaker.cpp | 153 +++++++++++++++++-------------- + src/structure/StructureMaker.hpp | 5 +- + src/visualization/Screen.cpp | 87 ++++++++++++++---- + 3 files changed, 156 insertions(+), 89 deletions(-) + +diff --git a/src/structure/StructureMaker.cpp b/src/structure/StructureMaker.cpp +index f5d5c7e..02feb1a 100644 +--- a/src/structure/StructureMaker.cpp ++++ b/src/structure/StructureMaker.cpp +@@ -90,7 +90,6 @@ void StructureMaker::compute_helix_axis(const std::vector& helix, float (& + + void StructureMaker::calculate_ss_points(std::map>& init_atoms, + std::map>& ss_atoms) { +- // std::cout << " apply structure\n"; + ss_atoms.clear(); + + for (auto& [chainID, atoms] : init_atoms) { +@@ -100,7 +99,7 @@ void StructureMaker::calculate_ss_points(std::map + char s = atoms[i].structure; + + if (s == 'H') { +- // helix start: find sequential H ++ // Find the full helix segment + size_t start = i; + while (i < atoms.size() && atoms[i].structure == 'H') ++i; + size_t end = i; +@@ -116,10 +115,11 @@ void StructureMaker::calculate_ss_points(std::map + float dz = segment.back().z - segment.front().z; + float length = std::sqrt(dx * dx + dy * dy + dz * dz); + +- const int steps = std::min(circle_steps, (end - start)); ++ // One axial slice per residue for a smooth, properly-sampled cylinder ++ const int steps = (int)(end - start); + +- float up[3] = {0, 0, 1}; +- if (std::abs(axis[2]) > 0.99f) { up[0] = 1; up[2] = 0; } ++ float up[3] = {0.0f, 0.0f, 1.0f}; ++ if (std::abs(axis[2]) > 0.99f) { up[0] = 1.0f; up[2] = 0.0f; } + + float n1[3] = { + axis[1]*up[2] - axis[2]*up[1], +@@ -135,89 +135,106 @@ void StructureMaker::calculate_ss_points(std::map + axis[0]*n1[1] - axis[1]*n1[0] + }; + +- for (int s = 0; s <= steps; ++s) { +- float t = static_cast(s) / steps; +- float base[3] = { +- center[0] + axis[0] * (t - 0.5f) * length, +- center[1] + axis[1] * (t - 0.5f) * length, +- center[2] + axis[2] * (t - 0.5f) * length, +- }; +- +- for (int a = 0; a < circle_steps; ++a) { +- float theta = 2 * PI * a / circle_steps; +- float dx = std::cos(theta); +- float dy = std::sin(theta); +- +- float px = base[0] + radius * (dx * n1[0] + dy * n2[0]); +- float py = base[1] + radius * (dx * n1[1] + dy * n2[1]); +- float pz = base[2] + radius * (dx * n1[2] + dy * n2[2]); +- ++ // Render as longitudinal zigzag stripes instead of cross-sectional rings. ++ // Each stripe is one thread along the cylinder surface at a fixed angle; ++ // alternating stripes go forward/backward so transitions are short arcs ++ // at the cylinder ends rather than long diagonal cuts across the surface. ++ for (int a = 0; a < circle_steps; ++a) { ++ float theta = 2.0f * PI * a / circle_steps; ++ float cos_t = std::cos(theta); ++ float sin_t = std::sin(theta); ++ bool forward = (a % 2 == 0); ++ ++ for (int si = 0; si <= steps; ++si) { ++ int s_idx = forward ? si : (steps - si); ++ float t = static_cast(s_idx) / steps; ++ float base[3] = { ++ center[0] + axis[0] * (t - 0.5f) * length, ++ center[1] + axis[1] * (t - 0.5f) * length, ++ center[2] + axis[2] * (t - 0.5f) * length, ++ }; ++ float px = base[0] + radius * (cos_t * n1[0] + sin_t * n2[0]); ++ float py = base[1] + radius * (cos_t * n1[1] + sin_t * n2[1]); ++ float pz = base[2] + radius * (cos_t * n1[2] + sin_t * n2[2]); + output.emplace_back(px, py, pz, 'H'); + } + } +- } else { +- // if too short, ignore +- i = end; + } ++ // i already advanced to end of helix segment by the inner while loop + } + +- else if (s == 'S' && i + 1 < atoms.size() && atoms[i + 1].structure == 'S') { +- const Atom& p1 = atoms[i]; +- const Atom& p2 = atoms[i + 1]; ++ else if (s == 'S') { ++ // Process the full consecutive sheet segment at once for a uniform ribbon ++ size_t seg_start = i; ++ while (i < atoms.size() && atoms[i].structure == 'S') ++i; ++ size_t seg_end = i; ++ int seg_len = (int)(seg_end - seg_start); + +- float dx = p2.x - p1.x; +- float dy = p2.y - p1.y; +- float dz = p2.z - p1.z; +- float len = std::sqrt(dx * dx + dy * dy + dz * dz); +- if (len == 0) { i++; continue; } ++ if (seg_len < 2) { ++ output.push_back(atoms[seg_start]); ++ continue; ++ } + +- float axis[3] = { dx / len, dy / len, dz / len }; // direction vector +- float up[3] = { 0.0f, 0.0f, 1.0f }; +- if (std::abs(axis[2]) > 0.99f) { +- up[0] = 1.0f; up[2] = 0.0f; // if almost similar to z-axis, replace ++ // Compute a consistent perpendicular from the overall segment direction ++ float dx = atoms[seg_end-1].x - atoms[seg_start].x; ++ float dy = atoms[seg_end-1].y - atoms[seg_start].y; ++ float dz = atoms[seg_end-1].z - atoms[seg_start].z; ++ float len = std::sqrt(dx*dx + dy*dy + dz*dz); ++ if (len < 1e-6f) { ++ for (size_t k = seg_start; k < seg_end; ++k) output.push_back(atoms[k]); ++ continue; + } + +- // n1: vector perpendicular to axis ++ float fwd[3] = {dx/len, dy/len, dz/len}; ++ float up[3] = {0.0f, 0.0f, 1.0f}; ++ if (std::abs(fwd[2]) > 0.99f) { up[0] = 1.0f; up[2] = 0.0f; } ++ + float n1[3] = { +- axis[1]*up[2] - axis[2]*up[1], +- axis[2]*up[0] - axis[0]*up[2], +- axis[0]*up[1] - axis[1]*up[0] ++ fwd[1]*up[2] - fwd[2]*up[1], ++ fwd[2]*up[0] - fwd[0]*up[2], ++ fwd[0]*up[1] - fwd[1]*up[0] + }; + float n1_norm = std::sqrt(n1[0]*n1[0] + n1[1]*n1[1] + n1[2]*n1[2]); +- for (int j = 0; j < 3; ++j) n1[j] /= n1_norm; +- +- // ribbon width vector, move to direction n1 +- int line_steps = std::max(2, static_cast(len / 0.05f)); ++ if (n1_norm < 1e-6f) { ++ for (size_t k = seg_start; k < seg_end; ++k) output.push_back(atoms[k]); ++ continue; ++ } ++ for (int k = 0; k < 3; ++k) n1[k] /= n1_norm; + ++ // Draw ribbon as parallel zigzag stripes along the full segment. ++ // Even steps traverse forward; odd steps traverse backward → short ++ // cross-connections at the ribbon ends, long clean lines along the strand. + for (int step = -width; step <= width; ++step) { +- float offset[3] = { +- n1[0] * step * 0.05f, +- n1[1] * step * 0.05f, +- n1[2] * step * 0.05f +- }; +- +- float x1 = p1.x + offset[0]; +- float y1 = p1.y + offset[1]; +- float z1 = p1.z + offset[2]; +- +- float x2 = p2.x + offset[0]; +- float y2 = p2.y + offset[1]; +- float z2 = p2.z + offset[2]; +- +- for (int t = 0; t <= line_steps; ++t) { +- float f = static_cast(t) / line_steps; +- float x = x1 + f * (x2 - x1); +- float y = y1 + f * (y2 - y1); +- float z = z1 + f * (z2 - z1); +- +- output.emplace_back(x, y, z, 'S'); ++ float offset = step * sheet_step; ++ float ox = n1[0]*offset, oy = n1[1]*offset, oz = n1[2]*offset; ++ bool forward = ((step + width) % 2 == 0); ++ ++ for (int j = 0; j < seg_len - 1; ++j) { ++ int actual = forward ? j : (seg_len - 2 - j); ++ const Atom& pa = atoms[seg_start + actual]; ++ const Atom& pb = atoms[seg_start + actual + 1]; ++ ++ float pair_dx = pb.x - pa.x; ++ float pair_dy = pb.y - pa.y; ++ float pair_dz = pb.z - pa.z; ++ float pair_len = std::sqrt(pair_dx*pair_dx + pair_dy*pair_dy + pair_dz*pair_dz); ++ int line_steps = std::max(2, (int)(pair_len / 0.05f)); ++ ++ for (int t = 0; t <= line_steps; ++t) { ++ float f = static_cast(t) / line_steps; ++ output.emplace_back( ++ pa.x + ox + f * pair_dx, ++ pa.y + oy + f * pair_dy, ++ pa.z + oz + f * pair_dz, ++ 'S' ++ ); ++ } + } + } +- i++; // sheet: pair ++ // i already advanced to seg_end by the inner while loop + } + + else { +- // no structure, just add + output.push_back(atoms[i]); + ++i; + } +diff --git a/src/structure/StructureMaker.hpp b/src/structure/StructureMaker.hpp +index 10f8c60..b102245 100644 +--- a/src/structure/StructureMaker.hpp ++++ b/src/structure/StructureMaker.hpp +@@ -18,8 +18,9 @@ public: + std::vector> extract_helix_segments(const Atom* atoms, int num_atoms); + private: + float radius = 2.5f; +- int circle_steps = 8; +- int width = 4; ++ int circle_steps = 16; // 16 longitudinal stripes → smooth cylinder ++ int width = 6; // half-width of beta-sheet ribbon ++ float sheet_step = 0.07f; // ribbon offset per step unit + }; + + +diff --git a/src/visualization/Screen.cpp b/src/visualization/Screen.cpp +index 3e819fd..3b8819f 100644 +--- a/src/visualization/Screen.cpp ++++ b/src/visualization/Screen.cpp +@@ -427,13 +427,30 @@ void Screen::project() { + int screenY = (int)((1.0f - projectedY) * 0.5f * logical_h); + + if (prevScreenX != -1 && prevScreenY != -1) { +- draw_line(chainPoints, +- prevScreenX, screenX, +- prevScreenY, screenY, +- prevZ, z, +- chainID, structure, +- depth_base_min_z, depth_base_max_z, +- logical_w, logical_h); ++ // Catmull-Rom spline: interpolate 4 sub-steps through the 3-D ++ // backbone to produce smooth curves instead of straight segments. ++ const Atom& P0 = chain_atoms[std::max(0, i-2)]; ++ const Atom& P1 = chain_atoms[i-1]; ++ const Atom& P2 = chain_atoms[i]; ++ const Atom& P3 = chain_atoms[std::min(num_atoms-1, i+1)]; ++ ++ int cr_prevX = prevScreenX, cr_prevY = prevScreenY; ++ float cr_prevZ = prevZ; ++ ++ for (int cr = 1; cr <= 4; ++cr) { ++ float t = cr * 0.25f; ++ float t2 = t*t, t3 = t2*t; ++ float cx = 0.5f*((-P0.x+3*P1.x-3*P2.x+P3.x)*t3 + (2*P0.x-5*P1.x+4*P2.x-P3.x)*t2 + (-P0.x+P2.x)*t + 2*P1.x); ++ float cy = 0.5f*((-P0.y+3*P1.y-3*P2.y+P3.y)*t3 + (2*P0.y-5*P1.y+4*P2.y-P3.y)*t2 + (-P0.y+P2.y)*t + 2*P1.y); ++ float cz = 0.5f*((-P0.z+3*P1.z-3*P2.z+P3.z)*t3 + (2*P0.z-5*P1.z+4*P2.z-P3.z)*t2 + (-P0.z+P2.z)*t + 2*P1.z) + focal_offset; ++ if (cz < nearPlane) break; ++ float cpX = (cx/cz)*fovRads + pan_x[ii]; ++ float cpY = (cy/cz)*fovRads + pan_y[ii]; ++ int cX = (int)((cpX+1.0f)*0.5f*logical_w); ++ int cY = (int)((1.0f-cpY)*0.5f*logical_h); ++ draw_line(chainPoints, cr_prevX, cX, cr_prevY, cY, cr_prevZ, cz, chainID, structure, depth_base_min_z, depth_base_max_z, logical_w, logical_h); ++ cr_prevX = cX; cr_prevY = cY; cr_prevZ = cz; ++ } + } + + if (screenX >= 0 && screenX < logical_w && screenY >= 0 && screenY < logical_h) { +@@ -509,12 +526,28 @@ void Screen::project() { + int screenY = (int)((1.0f - projectedY) * 0.5f * screen_height); + + if (prevScreenX != -1 && prevScreenY != -1) { +- draw_line(chainPoints, +- prevScreenX, screenX, +- prevScreenY, screenY, +- prevZ, z, +- chainID, structure, +- depth_base_min_z, depth_base_max_z); ++ const Atom& P0 = chain_atoms[std::max(0, i-2)]; ++ const Atom& P1 = chain_atoms[i-1]; ++ const Atom& P2 = chain_atoms[i]; ++ const Atom& P3 = chain_atoms[std::min(num_atoms-1, i+1)]; ++ ++ int cr_prevX = prevScreenX, cr_prevY = prevScreenY; ++ float cr_prevZ = prevZ; ++ ++ for (int cr = 1; cr <= 4; ++cr) { ++ float t = cr * 0.25f; ++ float t2 = t*t, t3 = t2*t; ++ float cx = 0.5f*((-P0.x+3*P1.x-3*P2.x+P3.x)*t3 + (2*P0.x-5*P1.x+4*P2.x-P3.x)*t2 + (-P0.x+P2.x)*t + 2*P1.x); ++ float cy = 0.5f*((-P0.y+3*P1.y-3*P2.y+P3.y)*t3 + (2*P0.y-5*P1.y+4*P2.y-P3.y)*t2 + (-P0.y+P2.y)*t + 2*P1.y); ++ float cz = 0.5f*((-P0.z+3*P1.z-3*P2.z+P3.z)*t3 + (2*P0.z-5*P1.z+4*P2.z-P3.z)*t2 + (-P0.z+P2.z)*t + 2*P1.z) + focal_offset; ++ if (cz < nearPlane) break; ++ float cpX = (cx/cz)*fovRads + pan_x[ii]; ++ float cpY = (cy/cz)*fovRads + pan_y[ii]; ++ int cX = (int)((cpX+1.0f)*0.5f*screen_width); ++ int cY = (int)((1.0f-cpY)*0.5f*screen_height); ++ draw_line(chainPoints, cr_prevX, cX, cr_prevY, cY, cr_prevZ, cz, chainID, structure, depth_base_min_z, depth_base_max_z); ++ cr_prevX = cX; cr_prevY = cY; cr_prevZ = cz; ++ } + } + + if (screenX >= 0 && screenX < screen_width && screenY >= 0 && screenY < screen_height) { +@@ -603,12 +636,28 @@ void Screen::project(std::vector& projectPixels, const int proj_wid + int screenY = (int)((1.0f - projectedY) * 0.5f * proj_height); + + if (prevScreenX != -1 && prevScreenY != -1) { +- draw_line(chainPoints, +- prevScreenX, screenX, +- prevScreenY, screenY, +- prevZ, z, +- chainID, structure, +- depth_base_min_z, depth_base_max_z); ++ const Atom& P0 = chain_atoms[std::max(0, i-2)]; ++ const Atom& P1 = chain_atoms[i-1]; ++ const Atom& P2 = chain_atoms[i]; ++ const Atom& P3 = chain_atoms[std::min(num_atoms-1, i+1)]; ++ ++ int cr_prevX = prevScreenX, cr_prevY = prevScreenY; ++ float cr_prevZ = prevZ; ++ ++ for (int cr = 1; cr <= 4; ++cr) { ++ float t = cr * 0.25f; ++ float t2 = t*t, t3 = t2*t; ++ float cx = 0.5f*((-P0.x+3*P1.x-3*P2.x+P3.x)*t3 + (2*P0.x-5*P1.x+4*P2.x-P3.x)*t2 + (-P0.x+P2.x)*t + 2*P1.x); ++ float cy = 0.5f*((-P0.y+3*P1.y-3*P2.y+P3.y)*t3 + (2*P0.y-5*P1.y+4*P2.y-P3.y)*t2 + (-P0.y+P2.y)*t + 2*P1.y); ++ float cz = 0.5f*((-P0.z+3*P1.z-3*P2.z+P3.z)*t3 + (2*P0.z-5*P1.z+4*P2.z-P3.z)*t2 + (-P0.z+P2.z)*t + 2*P1.z) + focal_offset; ++ if (cz < nearPlane) break; ++ float cpX = (cx/cz)*fovRads + pan_x[ii]; ++ float cpY = (cy/cz)*fovRads + pan_y[ii]; ++ int cX = (int)((cpX+1.0f)*0.5f*proj_width); ++ int cY = (int)((1.0f-cpY)*0.5f*proj_height); ++ draw_line(chainPoints, cr_prevX, cX, cr_prevY, cY, cr_prevZ, cz, chainID, structure, depth_base_min_z, depth_base_max_z); ++ cr_prevX = cX; cr_prevY = cY; cr_prevZ = cz; ++ } + } + + if (screenX >= 0 && screenX < proj_width && screenY >= 0 && screenY < proj_height) { +-- +2.43.0 +