// Last edited on 2021-04-29 16:18:54 by jstolfi // Various kinds of arrows // BASIC ARROWS #macro axis_arrow(dir,len,rad) union { cylinder { <0,0,0>, len*dir, rad } cone { len*dir, 3*rad, (len+10*rad)*dir, 0 } } #end #macro coord_axes(len) #local rad = len/300; union { sphere { <0,0,0>, 2*rad pigment { color rgb <0,0,0> }} object { axis_arrow(x,len,rad) pigment { color rgb <1,0.2,0.2> }} object { axis_arrow(y,len,rad) pigment { color rgb <0,1,0> }} object { axis_arrow(z,len,rad) pigment { color rgb <0.3,0.3,1> }} } #end #macro gen_arrow(p,q, rad, fpos,flen, bpos,blen) // Arrow from {p} to {q} with optional arrowheads. // // The forward arrowhead is located {fpos} of the way from {p} to {q} // and has length {flen}. Thus {fpos=1} puts the arrowhead's apex // at {q}, {fpos=0} puts its base at {p}. The backwards arrowhead is // similarly located by {bpos} and {blen}. The shaft ends are // round-capped whenever they are exposed. #local len = vlength(q-p); #local dir = (q-p)/len; #local eps = 0.0001; // If the arrowheads are at the shaft ends, the shaft must be trimmed off: #local pskip = #if ((blen > 0) & (bpos < 0.01)) 0.999*blen; #else 0; #end #local qskip = #if ((flen > 0) & (fpos > 0.99)) 0.999*flen; #else 0; #end #local pm = p + pskip*dir; #local qm = q - qskip*dir; // Position of arrow tips: #local ftip = p + (flen + fpos*(len - flen))*dir; #local btip = p + (bpos*(len - blen))*dir; union { #if (len > 0) cylinder { pm, qm, rad } #if (flen > 0) cone { ftip - flen*dir, 3*rad, ftip, 0 } #end #if (blen > 0) cone { btip + blen*dir, 3*rad, btip, 0 } #end #if (pskip = 0) sphere { p, rad } #end #if (qskip = 0) sphere { q, rad } #end #else sphere { p, 20*rad } #end } #end // DIMENSIONAL ARROWS #macro ref_line(p,dp, rad, dgap,dext) // A cylindrical line from {p} to {p+dp}, with radius {rad}. The // line will actually start at distance {dgap} away from {p} and end // at distance {dext} beyond {p+dp}. #local len = vlength(dp); #if (dgap < len+dext) #local dir = dp/len; #local a = p + dgap*dir; #local b = p + dp + dext*dir; union { cylinder { a, b, rad } sphere { a, rad } sphere { b, rad } } #end #end #macro arrow_label( lab, lmag,lthk,talign,rot ) // Converts the label text {lab} into an object. // {lab} = string to convert. // {lmag} = font magnification factor. // {lthk} = thickness of text. // {talign} = relative position of label's ref point (a 2-vector). // {rot} = a rotation 3-vector (degrees). // The font will be scaled by {lmag}, and the characters will have thickness {lthk}. // The text will be translated so that the reference point is at the orgin, // then rotated by {rot}. // The reference point is defined by the text-relative displacement // {talign} (a 2-vector). See {align_label}. When {talign.x}. // The assumed character dimensions {chdim} are those of a // '0' digit. // Text object in its natural reference system: #local txt = text { ttf "arial.ttf" lab lthk/lmag, 0 scale lmag translate lthk/2*z } // A digit "0" for reference: #local zd = text { ttf "arial.ttf" "0" lthk/lmag, 0 scale lmag translate lthk/2*z} #local zdim = max_extent(zd) - min_extent(zd); #local tal = object{ align_label(txt,zdim, talign,rot) tal #end #macro align_label(lobj,chdim, talign,rot) // Position an object {lobj} so that its reference point is at the // orgin. // {lobj} = object to be aligned, e.g. a text object. // {chdim} = assumed character dimensions. // {talign} = relative position of label's ref point (a 2-vector). // {rot} = a rotation 3-vector (degrees). // The reference point of the object {lobj} is specified by // {talign.x,talign.y} {talign.x} is measured along the text's // horizontal axis and {talign.y} is measured along its vertical axis. // // If these numbers are between 0 and 1, they are assumed to be relative to the // dimensions of {lobj}. Thus {talign = <0,0>} is {lobj}'s lower left // corner, {talign = <1,1>} is the upper right corner, and // intermediate values are interpolated linearly. // // If {talign.x} and/or {talign.y} is less than 0 or greater than 1, // its units change to the {chdim.x} and {chdim.y}, respectively. // // The result of the macro is the object {lobj} // translated so that the reference point is at the origin, // and then rotated by {rot}. // Text dimensions and position: #local tmin = min_extent(lobj); #local tmax = max_extent(lobj); #local trad = (tmax - tmin)/2; // Half-diagonal #local tctr = (tmin + tmax)/2; // Center of text // Reference point of {lobj}: #local torgx = #if (talign.x < 0) tmin.x + talign.x*chdim.x; #end #if (talign.x > 1) tmax.x + (talign.x-1)*chdim.x; #end #if ((talign.x >= 0) & (talign.x <= 1)) (1-talign.x)*tmin.x + talign.x*tmax.x; #end #local torgy = #if (talign.y < 0) tmin.y + talign.y*chdim.y; #end #if (talign.y > 1) tmax.y + (talign.y-1)*chdim.y; #end #if ((talign.y >= 0) & (talign.y <= 1)) (1-talign.y)*tmin.y + talign.y*tmax.y; #end #local torg = < torgx, torgy, 0 >; // Rotated and scaled object, with reference point at origin #local res = object { lobj translate -torg rotate rot } res #end #macro labeled_arrow_noref(p,q, rad, lobj,aalign, ptrim,qtrim) // A cylindrical arrow from point {p} to point {q}, with radius {rad} // and conical arrowheads at both ends, labeled with the object {lobj} // rotated by {rot}. // // The label object is placed with its origin point at the point {m} // located {aalign} of the way // from {p} to {q}. // // If {ptrim} is nonzero, the start of the arrow is displaced by // that distance from {p} towards {q}; and conversely for {qtrim}. // // If the length of the arrow is less than 2.5 times the arrowhead // length, the arrow is replaced by two inward-pointing half-arrows. // Arrow reference point: #local actr = (1-aalign)*p + aalign*q; // Original length and direction: #local len = vlength(q-p); #local dir = (q-p)/len; // Arrowhead length: #local hdlen = 15*rad; union{ #if (len >= 2.5*hdlen) // Compute actual arrow tips {pm}, {qm}: #local pm = p + ptrim*dir; #local qm = q - qtrim*dir; object{ gen_arrow(pm,qm, rad, 1.0,hdlen, 0.0,hdlen) } #else // Must use an everted arrow. // Compute actual arrow tips {epm}, {ipm}, {iqm}, {eqm}: #local epm = p - 2.0*hdlen*dir; #local ipm = p - ptrim*dir; #local iqm = q + qtrim*dir; #local eqm = q + 2.0*hdlen*dir; object{ gen_arrow(epm,ipm, rad, 1.0,hdlen, 0.0,0.0) } object{ gen_arrow(iqm,eqm, rad, 1.0,0.0, 0.0,hdlen) } #end object{ lobj translate actr } } #end #macro labeled_arrow(p,q, ofs, arad,rrad, lobj,aalign, pgap,pext, qgap,qext) // A device that shows the distance from {p} to {q}. It consists of // a dimensional arrow parallel to the segment {p,q}, displaced from // it by {ofs}; and two reference lines drawn from {p} and {q}, // respectively, in the direction {ofs}. The arrow has radius // {arad}, and the reference lines have radius {rrad}. // // The arrow is labeled with the object {lobj}, translated // so that its origin is at the point {aalign} along the arrow. // // The reference line from {p} actually starts at distance {pgap} // away from {p} and ends at distance {pext} beyond the arrow tip. // If {pgap >= vlength(ofs)+pext}, the reference line is not drawn. // Ditto for {q,qgap,qext}. #local pa = p + ofs; // Start of arrow. #local qa = q + ofs; // End of arrow. #local arr = union { labeled_arrow_noref(pa, qa, arad, lobj, aalign, 2*rrad,2*rrad) ref_line(p, ofs, rrad, pgap,pext) ref_line(q, ofs, rrad, qgap,qext) } arr #end #macro dim_arrow(p,q, ofs, arad,rrad, un,prc, aalign,talign,rot, pgap,pext, qgap,qext) // Same as {labeled_arrow}, where the label is the distance from {p} to {q}, divided // by {un} and formatted with {prc} decimal digits. #local dpq = vlength(q-p); #local lab = str(dpq/un,0,prc) #local lmag = 25*arad; // Magnification factor for label font. #local lthk = 0.01*arad; // Thickness of text. #local lobj = object{ arrow_label( lab, lmag,lthk, talign,rot ) } object{ labeled_arrow(p,q, ofs, arad,rrad, lobj,aalign, pgap,pext, qgap,qext) } #end