123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135(* Claude Code
*
* Copyright (C) 2026 Yoann Padioleau
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public License
* (LGPL) as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*)(* See Render.mli *)typepaint=Colorofint|TextureofTexture.imagetypeface={paint:paint;points:(Vec3.t*(float*float)*Vec3.t)list;material:Material.t}typevisibility=Z_buffer|Painters_algorithmtypeoptions={shading:Shading.mode;interpolation:Interpolate.mode;visibility:visibility;wireframe:bool;backface_culling:bool;bilinear:bool;clipping:bool;fill_rule:Triangle.fill_rule;}letdefault_options={shading=Shading.Phong;interpolation=Interpolate.Perspective_correct;visibility=Z_buffer;wireframe=false;backface_culling=true;bilinear=true;clipping=true;fill_rule=Triangle.Epsilon;}(*****************************************************************************)(* Colors *)(*****************************************************************************)letscale_channel(c:int)(brightness:float):int=int_of_float(float_of_intc*.brightness)(* the [color] closure for a face, resolving a pixel's final 0xRRGGBB
* color from its interpolated (u, v) and its brightness: a constant
* closure for a flat-colored face, a texture sample for a textured one.
* Computed once per face, not once per pixel, except for the actual
* texture sampling (genuinely per-pixel, since the color varies across
* the face) and the [brightness] scaling (genuinely per-pixel too, for
* Gouraud/Phong -- see Shading, which is what actually computes it;
* this function doesn't know or care which shading mode produced it). *)letcolor_of_paint(options:options)(paint:paint):u:float->v:float->brightness:float->int=letshade(r,g,b)~brightness:int=(scale_channelrbrightnesslsl16)lor(scale_channelgbrightnesslsl8)lorscale_channelbbrightnessinmatchpaintwith|Colorrgb->let(r,g,b)=((rgblsr16)land0xFF,(rgblsr8)land0xFF,rgbland0xFF)infun~u:_~v:_~brightness->shade(r,g,b)~brightness|Textureimg->letsample=ifoptions.bilinearthenTexture.sample_bilinearelseTexture.sample_nearestinfun~u~v~brightness->shade(sampleimg~u~v)~brightness(*****************************************************************************)(* The pipeline *)(*****************************************************************************)(* fan-triangulate a (convex, e.g. a cube face or a plane) polygon:
* (p0,p1,p2), (p0,p2,p3), (p0,p3,p4), ... *)letrecfan_triangles=function|p0::p1::p2::rest->(p0,p1,p2)::fan_triangles(p0::p2::rest)|_->[]letpoints_of(face:face):Vec3.tlist=List.map(fun(p,_uv,_n)->p)face.pointsletrender?(options=default_options)(fb:Framebuffer.t)(zbuffer:Zbuffer.t)(camera:Camera.t)(faces:facelist):unit=(* claude: only in Painters_algorithm mode -- Triangle.fill without a
* z-buffer has no per-pixel depth test at all, so *draw order* is the
* only thing that determines what ends up on top; sorting faces
* farthest-from-the-camera-first here, so nearer faces are drawn
* later and end up covering farther ones, is what makes that mode
* look right at all (still not correct for intersecting/cyclically-
* overlapping geometry -- see Painter.mli). The z-buffer mode needs
* no such sort: its per-pixel depth test makes the result correct
* regardless of draw order. *)letfaces,zbuffer=matchoptions.visibilitywith|Z_buffer->Zbuffer.clearzbuffer;(faces,Somezbuffer)|Painters_algorithm->(Painter.sort_far_to_near~eye:camera.eyepoints_offaces,None)infaces|>List.iter(fun(face:face)->if(notoptions.backface_culling)||Cull.faces_camera~eye:camera.eye(points_offace)thenbeginletcolor=color_of_paintoptionsface.paintinletproject=Project.vertexcamera~width:fb.width~height:fb.heightinletprojected=fan_trianglesface.points|>List.concat_map(fun(pa,pb,pc)->ifnotoptions.clippingthen[(projectpa,projectpb,projectpc)]else(* claude: in view coordinates, where the near plane is
* simply z = near *)letto_view(p,uv,n)=(Camera.viewcamerap,uv,n)inletproject=Project.vertex_of_viewcamera~width:fb.width~height:fb.heightinClip.near_plane~near:camera.near[to_viewpa;to_viewpb;to_viewpc]|>fan_triangles|>List.map(fun(a,b,c)->(projecta,projectb,projectc)))inifoptions.wireframethenbegin(* one representative, unlit color for the whole face
* (sampled at the texture's center for a textured one) --
* wireframe mode draws bare edges, not shaded pixels, so
* brightness is always 1. here regardless of the shading
* mode *)letrgb=color~u:0.5~v:0.5~brightness:1.inprojected|>List.iter(functionSomev0,Somev1,Somev2->Triangle.outlinefb~rgbv0v1v2|_->())endelseprojected|>List.iter(function|Somev0,Somev1,Somev2->Triangle.fillfb~fill_rule:options.fill_rule~zbuffer~interpolation:options.interpolation~shading:options.shading~colorv0v1v2|_->(* a vertex is behind the camera (or too near; only
* without clipping) or too far: drop the whole
* triangle *)())end)