package localmods.mtkviewpoint; import java.nio.FloatBuffer; import org.joml.Matrix4f; import org.joml.Vector4f; /** Projected ribbons retain world depth; clipping prevents flashes behind the camera. */ public final class TracerMesh { public record Segment(float hx,float hy,float hz,float tx,float ty,float tz,float r,float g,float b,float alpha) {} public static final int STRIDE=10; private TracerMesh() {} public static int append(FloatBuffer out, Segment s, Matrix4f vp, int width, int height) { if (width<=0 || height<=0 || out.remaining()<6*STRIDE) return 0; var h=vp.transform(new Vector4f(s.hx,s.hy,s.hz,1)); var t=vp.transform(new Vector4f(s.tx,s.ty,s.tz,1)); if (!h.isFinite() || !t.isFinite()) return 0; float hn=h.z+h.w, tn=t.z+t.w; if (hn<=0 && tn<=0) return 0; if (hn<=0) h.lerp(t,(-hn+0.00001f)/(tn-hn)); else if (tn<=0) t.lerp(h,(-tn+0.00001f)/(hn-tn)); if (h.w<=0.000001f || t.w<=0.000001f) return 0; float dx=(h.x/h.w-t.x/t.w)*width, dy=(h.y/h.w-t.y/t.w)*height; float len=(float)Math.hypot(dx,dy); if (len<1) { // End-on rounds still have a small visible core. t.x-=4f/width*t.w; dx=4; dy=0; len=4; } float ox=-dy/len*2.5f/width, oy=dx/len*2.5f/height; vertex(out,t,ox,oy,-1,0,s); vertex(out,h,ox,oy,-1,1,s); vertex(out,h,ox,oy,1,1,s); vertex(out,t,ox,oy,-1,0,s); vertex(out,h,ox,oy,1,1,s); vertex(out,t,ox,oy,1,0,s); return 6; } private static void vertex(FloatBuffer b,Vector4f p,float ox,float oy,float side,float along,Segment s) { b.put(p.x+ox*side*p.w).put(p.y+oy*side*p.w).put(p.z).put(p.w); b.put(along).put(side).put(s.r).put(s.g).put(s.b).put(s.alpha); } }