Jump to content

Fake morse effect on the line


Theophylact

Recommended Posts

Please help me, how should I approach this geo effect?

Let’s take a single line as a base geometry. From this line, generate a new geometry composed entirely of dots.
The dot pattern is inspired by fake Morse code: it follows the visual logic of Morse, but only consists of dots with different density.

Only dots are used -  no dashes.
What would normally be a dash is represented as a region of higher dot density (clusters of tightly packed points).
Pauses or separations are represented by lower-density or empty gaps - something like that. Any ideas?
 

192fdf48-020f-425e-8e32-68d48d2f5037.png

Link to comment
Share on other sites

// DETAIL WRANGLE (run once)
// Input 0: a single curve/polyline primitive (uses "prim" parm)

// -------------------- Parameters --------------------
int prim = chi("prim");
int seed = chi("seed");
string msg = chs("message");

float unit_len      = chf("unit_len");       // unit length in curve-u (0..1)
float spacing_units = chf("spacing_units");  // base dot spacing in "units"
float dot_mult      = chf("dot_mult");       // density multiplier for dot
float dash_mult     = chf("dash_mult");      // density multiplier for dash (dense dots)
float jitter_radius = chf("jitter_radius");  // world-space jitter radius around curve
float jitter_along  = chf("jitter_along");   // along-interval jitter (0..1)
int   max_pts       = chi("max_pts");

// Morse timing (in units)
float dot_units        = chf("dot_units");         // usually 1
float dash_units       = chf("dash_units");        // usually 3
float gap_symbol_units = chf("gap_symbol_units");  // usually 1 (between . and - in same letter)
float gap_letter_units = chf("gap_letter_units");  // usually 3 (between letters)
float gap_word_units   = chf("gap_word_units");    // usually 7 (between words)

// -------------------- Helpers --------------------
function void frame_on_curve(int geoh; int pr; float u; vector up;
                             export vector P; export vector T; export vector N; export vector B)
{
    P = primuv(geoh, "P", pr, set(u, 0, 0));
    float du = 1e-4;
    vector P2 = primuv(geoh, "P", pr, set(clamp(u + du, 0.0, 1.0), 0, 0));
    T = normalize(P2 - P);

    vector a = normalize(cross(up, T));
    if (length(a) < 1e-6)
        a = normalize(cross({1,0,0}, T));
    B = normalize(a);
    N = normalize(cross(T, B));
}

function int emit_interval(int geoh; int pr; float u0; float u1; float density_mult;
                           int seedbase; float unit_len; float spacing_units;
                           float jitter_radius; float jitter_along; int max_pts)
{
    float du = max(0.0, u1 - u0);
    float interval_units = du / max(1e-8, unit_len);

    float base_count = interval_units / max(1e-8, spacing_units);
    int npts = int(ceil(base_count * max(0.0, density_mult)));
    if (npts <= 0) return 0;

    vector up = {0,1,0};
    int created = 0;

    for (int i = 0; i < npts; i++)
    {
        if (created >= max_pts) break;

        float t = (npts <= 1) ? 0.5 : (float(i) / float(npts - 1));
        float r = rand(set(seedbase, i, 19.123));

        float u = lerp(u0, u1, t);
        // jitter a bit along the interval
        u += (r - 0.5) * jitter_along * (spacing_units * unit_len);
        u = clamp(u, u0, u1);

        vector P, T, N, B;
        frame_on_curve(geoh, pr, u, up, P, T, N, B);

        // jitter in disk around curve
        float ang = rand(set(seedbase, i, 3.7)) * 2.0 * M_PI;
        float rr  = sqrt(rand(set(seedbase, i, 8.1))) * jitter_radius;
        vector jitter = cos(ang) * N * rr + sin(ang) * B * rr;

        int pt = addpoint(0, P + jitter);

        setpointattrib(0, "curveu", pt, u, "set");
        setpointattrib(0, "pscale", pt, fit01(rand(set(seedbase, i, 77.7)), 0.6, 1.2), "set");

        created++;
    }
    return created;
}

// Morse lookup: chars and their morse strings
function string morse_of(string ch)
{
    string chars[] = {
        "A","B","C","D","E","F","G","H","I","J",
        "K","L","M","N","O","P","Q","R","S","T",
        "U","V","W","X","Y","Z",
        "0","1","2","3","4","5","6","7","8","9"
    };

    string codes[] = {
        ".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
        "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-",
        "..-","...-",".--","-..-","-.--","--..",
        "-----",".----","..---","...--","....-",".....","-....","--...","---..","----."
    };

    // already uppercase before calling, but safe:
    string up = toupper(ch);

    int idx = -1;
    for (int i = 0; i < len(chars); i++)
    {
        if (up == chars[i]) { idx = i; break; }
    }
    if (idx < 0) return ""; // unsupported char -> treat as gap
    return codes[idx];
}

// -------------------- Main: walk along curve-u --------------------
float u = 0.0;
int total = 0;

msg = toupper(msg);

// iterate message characters
for (int c = 0; c < len(msg); c++)
{
    if (u >= 1.0 || total >= max_pts) break;

    string ch = msg[c];

    // Word gap
    if (ch == " ")
    {
        u = min(1.0, u + gap_word_units * unit_len);
        continue;
    }

    string code = morse_of(ch);

    // Unsupported char: treat like a word-ish gap
    if (code == "")
    {
        u = min(1.0, u + gap_letter_units * unit_len);
        continue;
    }

    // For each symbol in morse code
    for (int s = 0; s < len(code); s++)
    {
        if (u >= 1.0 || total >= max_pts) break;

        string sym = code[s];

        float dur_units = (sym == ".") ? dot_units : dash_units;
        float dens      = (sym == ".") ? dot_mult : dash_mult;

        float u1 = min(1.0, u + dur_units * unit_len);
        total += emit_interval(0, prim, u, u1, dens, seed + total * 31, unit_len,
                               spacing_units, jitter_radius, jitter_along, max_pts);
        u = u1;

        // symbol gap (but not after last symbol)
        if (s < len(code) - 1)
            u = min(1.0, u + gap_symbol_units * unit_len);
    }

    // letter gap after each character (but not if next is space)
    if (c < len(msg) - 1 && msg[c+1] != " ")
        u = min(1.0, u + gap_letter_units * unit_len);
}

multiple Lines
 

// DETAIL WRANGLE (run once)
// Input 0: multiple polyline primitives

int seed = chi("seed");
string msg = chs("message");

float unit_len      = chf("unit_len");
float spacing_units = chf("spacing_units");
float dot_mult      = chf("dot_mult");
float dash_mult     = chf("dash_mult");
float jitter_radius = chf("jitter_radius");
float jitter_along  = chf("jitter_along");
int   max_pts_total = chi("max_pts_total");

// Morse timing (units)
float dot_units        = chf("dot_units");
float dash_units       = chf("dash_units");
float gap_symbol_units = chf("gap_symbol_units");
float gap_letter_units = chf("gap_letter_units");
float gap_word_units   = chf("gap_word_units");

// ---------- Helpers ----------
function void frame_on_curve(int geoh; int pr; float u; vector up;
                             export vector P; export vector T; export vector N; export vector B)
{
    P = primuv(geoh, "P", pr, set(u, 0, 0));
    float du = 1e-4;
    vector P2 = primuv(geoh, "P", pr, set(clamp(u + du, 0.0, 1.0), 0, 0));
    T = normalize(P2 - P);

    vector a = normalize(cross(up, T));
    if (length(a) < 1e-6)
        a = normalize(cross({1,0,0}, T));
    B = normalize(a);
    N = normalize(cross(T, B));
}

function int emit_interval(int geoh; int pr; float u0; float u1; float density_mult;
                           int seedbase; float unit_len; float spacing_units;
                           float jitter_radius; float jitter_along; int max_pts_cap;
                           int prim_id; int symbol_id)
{
    float du = max(0.0, u1 - u0);
    float interval_units = du / max(1e-8, unit_len);

    float base_count = interval_units / max(1e-8, spacing_units);
    int npts = int(ceil(base_count * max(0.0, density_mult)));
    if (npts <= 0) return 0;

    vector up = {0,1,0};
    int created = 0;

    for (int i = 0; i < npts; i++)
    {
        if (created >= max_pts_cap) break;

        float t = (npts <= 1) ? 0.5 : (float(i) / float(npts - 1));
        float r = rand(set(seedbase, i, 19.123));

        float u = lerp(u0, u1, t);
        u += (r - 0.5) * jitter_along * (spacing_units * unit_len);
        u = clamp(u, u0, u1);

        vector P, T, N, B;
        frame_on_curve(geoh, pr, u, up, P, T, N, B);

        float ang = rand(set(seedbase, i, 3.7)) * 2.0 * M_PI;
        float rr  = sqrt(rand(set(seedbase, i, 8.1))) * jitter_radius;
        vector jitter = cos(ang) * N * rr + sin(ang) * B * rr;

        int pt = addpoint(0, P + jitter);

        setpointattrib(0, "curveu", pt, u, "set");
        setpointattrib(0, "pscale", pt, fit01(rand(set(seedbase, i, 77.7)), 0.6, 1.2), "set");

        // super useful for shading / grouping
        setpointattrib(0, "sourceprim", pt, prim_id, "set"); // which input curve
        setpointattrib(0, "symbol",     pt, symbol_id, "set"); // 0=dot, 1=dash

        created++;
    }
    return created;
}

function string morse_of(string ch)
{
    string chars[] = {
        "A","B","C","D","E","F","G","H","I","J",
        "K","L","M","N","O","P","Q","R","S","T",
        "U","V","W","X","Y","Z",
        "0","1","2","3","4","5","6","7","8","9"
    };

    string codes[] = {
        ".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
        "-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-",
        "..-","...-",".--","-..-","-.--","--..",
        "-----",".----","..---","...--","....-",".....","-....","--...","---..","----."
    };

    string upc = toupper(ch);

    for (int i = 0; i < len(chars); i++)
        if (upc == chars[i]) return codes[i];

    return "";
}

// ---------- Main ----------
msg = toupper(msg);

int npr = nprimitives(0);
int total_pts = 0;

// You can choose: same message for every curve, or offset/seed per curve.
for (int pr = 0; pr < npr; pr++)
{
    if (total_pts >= max_pts_total) break;

    float u = 0.0;

    // per-primitive seed so each curve has a unique pattern jitter
    int sseed = seed + pr * 10007;

    for (int c = 0; c < len(msg); c++)
    {
        if (u >= 1.0 || total_pts >= max_pts_total) break;

        string ch = msg[c];

        if (ch == " ")
        {
            u = min(1.0, u + gap_word_units * unit_len);
            continue;
        }

        string code = morse_of(ch);
        if (code == "")
        {
            u = min(1.0, u + gap_letter_units * unit_len);
            continue;
        }

        for (int k = 0; k < len(code); k++)
        {
            if (u >= 1.0 || total_pts >= max_pts_total) break;

            string sym = code[k];

            float dur = (sym == ".") ? dot_units : dash_units;
            float den = (sym == ".") ? dot_mult : dash_mult;
            int   sid = (sym == ".") ? 0 : 1;

            float u1 = min(1.0, u + dur * unit_len);

            // cap per-interval emission so one curve can't explode points
            int cap_here = max(1, (max_pts_total - total_pts));

            int made = emit_interval(0, pr, u, u1, den,
                                     sseed + total_pts * 31, unit_len, spacing_units,
                                     jitter_radius, jitter_along, cap_here,
                                     pr, sid);

            total_pts += made;
            u = u1;

            if (k < len(code) - 1)
                u = min(1.0, u + gap_symbol_units * unit_len);
        }

        if (c < len(msg) - 1 && msg[c+1] != " ")
            u = min(1.0, u + gap_letter_units * unit_len);
    }
}

image.png

Edited by Librarian
  • Like 1
Link to comment
Share on other sites

You can also use the clip node (clip attribute). The attribute can be generated by some random function (combined sin, cos, ...). You can clip your curve into two complement sets. One of these parts can be resampled for the dots and the other for the lines (ribbons). Here is a file with some ideas. 

clip_carve_lines_dots.hipnc

  • Like 3
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...