aboutsummaryrefslogtreecommitdiff
path: root/libgraphics
diff options
context:
space:
mode:
authorrgl <devnull@localhost>2020-02-03 22:42:28 +0100
committerrgl <devnull@localhost>2020-02-03 22:42:28 +0100
commit0373255087377122eeb10e006ffb8aa1b57e611c (patch)
tree33a4fafa4996fc2efa205b2973622c3fbd27f368 /libgraphics
download3dee-0373255087377122eeb10e006ffb8aa1b57e611c.tar.gz
3dee-0373255087377122eeb10e006ffb8aa1b57e611c.tar.bz2
3dee-0373255087377122eeb10e006ffb8aa1b57e611c.zip
after a year or so of work, i dare create a proper repo.
Diffstat (limited to 'libgraphics')
-rw-r--r--libgraphics/camera.c103
-rw-r--r--libgraphics/doc/libgraphics.ms34
-rw-r--r--libgraphics/doc/libgraphics.pdfbin0 -> 15087 bytes
-rw-r--r--libgraphics/doc/libgraphics.ps583
-rw-r--r--libgraphics/doc/mkfile15
-rw-r--r--libgraphics/mkfile11
-rw-r--r--libgraphics/render.c84
-rw-r--r--libgraphics/triangle.c43
8 files changed, 873 insertions, 0 deletions
diff --git a/libgraphics/camera.c b/libgraphics/camera.c
new file mode 100644
index 0000000..c850477
--- /dev/null
+++ b/libgraphics/camera.c
@@ -0,0 +1,103 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "../geometry.h"
+#include "../graphics.h"
+
+static int
+max(int a, int b)
+{
+ return a > b ? a : b;
+}
+
+static void
+verifycfg(Camera *c)
+{
+ assert(c->viewport != nil);
+ if(c->ptype == Ppersp)
+ assert(c->fov >= 1 && c->fov < 360);
+ assert(c->clip.n > 0 && c->clip.n < c->clip.f);
+}
+
+void
+perspective(Matrix3 m, double fov, double a, double n, double f)
+{
+ double cotan;
+
+ cotan = 1/tan(fov/2*DEG);
+ identity3(m);
+ m[0][0] = cotan/a;
+ m[1][1] = cotan;
+ m[2][2] = -(f+n)/(f-n);
+ m[2][3] = -2*f*n/(f-n);
+ m[3][2] = -1;
+}
+
+void
+orthographic(Matrix3 m, double l, double r, double b, double t, double n, double f)
+{
+ identity3(m);
+ m[0][0] = 2/(r - l);
+ m[1][1] = 2/(t - b);
+ m[2][2] = -2/(f - n);
+ m[0][3] = -(r + l)/(r - l);
+ m[1][3] = -(t + b)/(t - b);
+ m[2][3] = -(f + n)/(f - n);
+}
+
+void
+configcamera(Camera *c, Image *v, double fov, double n, double f, Projection p)
+{
+ c->viewport = v;
+ c->fov = fov;
+ c->clip.n = n;
+ c->clip.f = f;
+ c->ptype = p;
+ reloadcamera(c);
+}
+
+void
+placecamera(Camera *c, Point3 p, Point3 focus, Point3 up)
+{
+ c->p = p;
+ if(focus.w == 0)
+ c->bz = focus;
+ else
+ c->bz = normvec3(subpt3(c->p, focus));
+ c->bx = normvec3(crossvec3(up, c->bz));
+ c->by = crossvec3(c->bz, c->bx);
+}
+
+void
+aimcamera(Camera *c, Point3 focus)
+{
+ placecamera(c, c->p, focus, c->by);
+}
+
+void
+reloadcamera(Camera *c)
+{
+ double a;
+ double l, r, b, t;
+
+ verifycfg(c);
+ switch(c->ptype){
+ case Portho:
+ /*
+ r = Dx(c->viewport->r)/2;
+ t = Dy(c->viewport->r)/2;
+ l = -r;
+ b = -t;
+ */
+ l = t = 0;
+ r = Dx(c->viewport->r);
+ b = Dy(c->viewport->r);
+ orthographic(c->proj, l, r, b, t, c->clip.n, c->clip.f);
+ break;
+ case Ppersp:
+ a = (double)Dx(c->viewport->r)/Dy(c->viewport->r);
+ perspective(c->proj, c->fov, a, c->clip.n, c->clip.f);
+ break;
+ default: sysfatal("unknown projection type");
+ }
+}
diff --git a/libgraphics/doc/libgraphics.ms b/libgraphics/doc/libgraphics.ms
new file mode 100644
index 0000000..87a23a7
--- /dev/null
+++ b/libgraphics/doc/libgraphics.ms
@@ -0,0 +1,34 @@
+.TL
+libgraphics
+.AU
+Rodrigo G. López
+.sp
+rgl@antares-labs.eu
+.AI
+Antares Telecom Laboratories
+Albatera, Alicante
+.FS
+ACHTUNG! this is a
+.B "WORK IN PROGRESS"
+.FE
+.NH 1
+Data Structures
+.NH 2
+Camera
+.P1
+struct Camera {
+ RFrame3; /* VCS */
+ Image *viewport;
+ double fov; /* vertical FOV */
+ struct {
+ double n, f; /* near and far clipping planes */
+ } clip;
+ Projection ptype;
+ Matrix3 proj; /* VCS to viewport xform */
+};
+.P2
+.PP
+A camera is an image capturing entity, analog to the real world device
+we all know, that allows us to see the virtual 3-D world by projecting
+it into a viewport we can attach to a screen or window for real-time
+visualization or write out into a file.
diff --git a/libgraphics/doc/libgraphics.pdf b/libgraphics/doc/libgraphics.pdf
new file mode 100644
index 0000000..0291710
--- /dev/null
+++ b/libgraphics/doc/libgraphics.pdf
Binary files differ
diff --git a/libgraphics/doc/libgraphics.ps b/libgraphics/doc/libgraphics.ps
new file mode 100644
index 0000000..1a566f7
--- /dev/null
+++ b/libgraphics/doc/libgraphics.ps
@@ -0,0 +1,583 @@
+%!PS-Adobe-2.0
+%%Version: 0.1
+%%Creator: troff, Plan 9 edition
+%%DocumentFonts: (atend)
+%%Pages: (atend)
+%%EndComments
+%
+% Version 3.3.2 prologue for troff files.
+%
+
+/#copies 1 store
+/aspectratio 1 def
+/formsperpage 1 def
+/landscape false def
+/linewidth .3 def
+/magnification 1 def
+/margin 0 def
+/orientation 0 def
+/resolution 720 def
+/rotation 1 def
+/xoffset 0 def
+/yoffset 0 def
+
+/roundpage true def
+/useclippath true def
+/pagebbox [0 0 612 792] def
+
+/R /Times-Roman def
+/I /Times-Italic def
+/B /Times-Bold def
+/BI /Times-BoldItalic def
+/H /Helvetica def
+/HI /Helvetica-Oblique def
+/HB /Helvetica-Bold def
+/HX /Helvetica-BoldOblique def
+/CW /Courier def
+/CO /Courier def
+/CI /Courier-Oblique def
+/CB /Courier-Bold def
+/CX /Courier-BoldOblique def
+/PA /Palatino-Roman def
+/PI /Palatino-Italic def
+/PB /Palatino-Bold def
+/PX /Palatino-BoldItalic def
+/Hr /Helvetica-Narrow def
+/Hi /Helvetica-Narrow-Oblique def
+/Hb /Helvetica-Narrow-Bold def
+/Hx /Helvetica-Narrow-BoldOblique def
+/KR /Bookman-Light def
+/KI /Bookman-LightItalic def
+/KB /Bookman-Demi def
+/KX /Bookman-DemiItalic def
+/AR /AvantGarde-Book def
+/AI /AvantGarde-BookOblique def
+/AB /AvantGarde-Demi def
+/AX /AvantGarde-DemiOblique def
+/NR /NewCenturySchlbk-Roman def
+/NI /NewCenturySchlbk-Italic def
+/NB /NewCenturySchlbk-Bold def
+/NX /NewCenturySchlbk-BoldItalic def
+/ZD /ZapfDingbats def
+/ZI /ZapfChancery-MediumItalic def
+/S /S def
+/S1 /S1 def
+/GR /Symbol def
+
+/inch {72 mul} bind def
+/min {2 copy gt {exch} if pop} bind def
+
+/setup {
+ counttomark 2 idiv {def} repeat pop
+
+ landscape {/orientation 90 orientation add def} if
+ /scaling 72 resolution div def
+ linewidth setlinewidth
+ 1 setlinecap
+
+ pagedimensions
+ xcenter ycenter translate
+ orientation rotation mul rotate
+ width 2 div neg height 2 div translate
+ xoffset inch yoffset inch neg translate
+ margin 2 div dup neg translate
+ magnification dup aspectratio mul scale
+ scaling scaling scale
+
+ addmetrics
+ 0 0 moveto
+} def
+
+/pagedimensions {
+ useclippath userdict /gotpagebbox known not and {
+ /pagebbox [clippath pathbbox newpath] def
+ roundpage currentdict /roundpagebbox known and {roundpagebbox} if
+ } if
+ pagebbox aload pop
+ 4 -1 roll exch 4 1 roll 4 copy
+ landscape {4 2 roll} if
+ sub /width exch def
+ sub /height exch def
+ add 2 div /xcenter exch def
+ add 2 div /ycenter exch def
+ userdict /gotpagebbox true put
+} def
+
+/addmetrics {
+ /Symbol /S null Sdefs cf
+ /Times-Roman /S1 StandardEncoding dup length array copy S1defs cf
+} def
+
+/pagesetup {
+ /page exch def
+ currentdict /pagedict known currentdict page known and {
+ page load pagedict exch get cvx exec
+ } if
+} def
+
+/decodingdefs [
+ {counttomark 2 idiv {y moveto show} repeat}
+ {neg /y exch def counttomark 2 idiv {y moveto show} repeat}
+ {neg moveto {2 index stringwidth pop sub exch div 0 32 4 -1 roll widthshow} repeat}
+ {neg moveto {spacewidth sub 0.0 32 4 -1 roll widthshow} repeat}
+ {counttomark 2 idiv {y moveto show} repeat}
+ {neg setfunnytext}
+] def
+
+/setdecoding {/t decodingdefs 3 -1 roll get bind def} bind def
+
+/w {neg moveto show} bind def
+/m {neg dup /y exch def moveto} bind def
+/done {/lastpage where {pop lastpage} if} def
+
+/f {
+ dup /font exch def findfont exch
+ dup /ptsize exch def scaling div dup /size exch def scalefont setfont
+ linewidth ptsize mul scaling 10 mul div setlinewidth
+ /spacewidth ( ) stringwidth pop def
+} bind def
+
+/changefont {
+ /fontheight exch def
+ /fontslant exch def
+ currentfont [
+ 1 0
+ fontheight ptsize div fontslant sin mul fontslant cos div
+ fontheight ptsize div
+ 0 0
+ ] makefont setfont
+} bind def
+
+/sf {f} bind def
+
+/cf {
+ dup length 2 idiv
+ /entries exch def
+ /chtab exch def
+ /newencoding exch def
+ /newfont exch def
+
+ findfont dup length 1 add dict
+ /newdict exch def
+ {1 index /FID ne {newdict 3 1 roll put}{pop pop} ifelse} forall
+
+ newencoding type /arraytype eq {newdict /Encoding newencoding put} if
+
+ newdict /Metrics entries dict put
+ newdict /Metrics get
+ begin
+ chtab aload pop
+ 1 1 entries {pop def} for
+ newfont newdict definefont pop
+ end
+} bind def
+
+%
+% A few arrays used to adjust reference points and character widths in some
+% of the printer resident fonts. If square roots are too high try changing
+% the lines describing /radical and /radicalex to,
+%
+% /radical [0 -75 550 0]
+% /radicalex [-50 -75 500 0]
+%
+% Move braceleftbt a bit - default PostScript character is off a bit.
+%
+
+/Sdefs [
+ /bracketlefttp [201 500]
+ /bracketleftbt [201 500]
+ /bracketrighttp [-81 380]
+ /bracketrightbt [-83 380]
+ /braceleftbt [203 490]
+ /bracketrightex [220 -125 500 0]
+ /radical [0 0 550 0]
+ /radicalex [-50 0 500 0]
+ /parenleftex [-20 -170 0 0]
+ /integral [100 -50 500 0]
+ /infinity [10 -75 730 0]
+] def
+
+/S1defs [
+ /underscore [0 80 500 0]
+ /endash [7 90 650 0]
+] def
+%
+% Tries to round clipping path dimensions, as stored in array pagebbox, so they
+% match one of the known sizes in the papersizes array. Lower left coordinates
+% are always set to 0.
+%
+
+/roundpagebbox {
+ 7 dict begin
+ /papersizes [8.5 inch 11 inch 14 inch 17 inch] def
+
+ /mappapersize {
+ /val exch def
+ /slop .5 inch def
+ /diff slop def
+ /j 0 def
+ 0 1 papersizes length 1 sub {
+ /i exch def
+ papersizes i get val sub abs
+ dup diff le {/diff exch def /j i def} {pop} ifelse
+ } for
+ diff slop lt {papersizes j get} {val} ifelse
+ } def
+
+ pagebbox 0 0 put
+ pagebbox 1 0 put
+ pagebbox dup 2 get mappapersize 2 exch put
+ pagebbox dup 3 get mappapersize 3 exch put
+ end
+} bind def
+
+%%EndProlog
+%%BeginSetup
+mark
+%
+% Encoding vector and redefinition of findfont for the ISO Latin1 standard.
+% The 18 characters missing from ROM based fonts on older printers are noted
+% below.
+%
+
+/ISOLatin1Encoding [
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /space
+ /exclam
+ /quotedbl
+ /numbersign
+ /dollar
+ /percent
+ /ampersand
+ /quoteright
+ /parenleft
+ /parenright
+ /asterisk
+ /plus
+ /comma
+ /minus
+ /period
+ /slash
+ /zero
+ /one
+ /two
+ /three
+ /four
+ /five
+ /six
+ /seven
+ /eight
+ /nine
+ /colon
+ /semicolon
+ /less
+ /equal
+ /greater
+ /question
+ /at
+ /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
+ /bracketleft
+ /backslash
+ /bracketright
+ /asciicircum
+ /underscore
+ /quoteleft
+ /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
+ /braceleft
+ /bar
+ /braceright
+ /asciitilde
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /.notdef
+ /dotlessi
+ /grave
+ /acute
+ /circumflex
+ /tilde
+ /macron
+ /breve
+ /dotaccent
+ /dieresis
+ /.notdef
+ /ring
+ /cedilla
+ /.notdef
+ /hungarumlaut
+ /ogonek
+ /caron
+ /space
+ /exclamdown
+ /cent
+ /sterling
+ /currency
+ /yen
+ /brokenbar % missing
+ /section
+ /dieresis
+ /copyright
+ /ordfeminine
+ /guillemotleft
+ /logicalnot
+ /hyphen
+ /registered
+ /macron
+ /degree % missing
+ /plusminus % missing
+ /twosuperior % missing
+ /threesuperior % missing
+ /acute
+ /mu % missing
+ /paragraph
+ /periodcentered
+ /cedilla
+ /onesuperior % missing
+ /ordmasculine
+ /guillemotright
+ /onequarter % missing
+ /onehalf % missing
+ /threequarters % missing
+ /questiondown
+ /Agrave
+ /Aacute
+ /Acircumflex
+ /Atilde
+ /Adieresis
+ /Aring
+ /AE
+ /Ccedilla
+ /Egrave
+ /Eacute
+ /Ecircumflex
+ /Edieresis
+ /Igrave
+ /Iacute
+ /Icircumflex
+ /Idieresis
+ /Eth % missing
+ /Ntilde
+ /Ograve
+ /Oacute
+ /Ocircumflex
+ /Otilde
+ /Odieresis
+ /multiply % missing
+ /Oslash
+ /Ugrave
+ /Uacute
+ /Ucircumflex
+ /Udieresis
+ /Yacute % missing
+ /Thorn % missing
+ /germandbls
+ /agrave
+ /aacute
+ /acircumflex
+ /atilde
+ /adieresis
+ /aring
+ /ae
+ /ccedilla
+ /egrave
+ /eacute
+ /ecircumflex
+ /edieresis
+ /igrave
+ /iacute
+ /icircumflex
+ /idieresis
+ /eth % missing
+ /ntilde
+ /ograve
+ /oacute
+ /ocircumflex
+ /otilde
+ /odieresis
+ /divide % missing
+ /oslash
+ /ugrave
+ /uacute
+ /ucircumflex
+ /udieresis
+ /yacute % missing
+ /thorn % missing
+ /ydieresis
+] def
+
+/NewFontDirectory FontDirectory maxlength dict def
+
+%
+% Apparently no guarantee findfont is defined in systemdict so the obvious
+%
+% systemdict /findfont get exec
+%
+% can generate an error. So far the only exception is a VT600 (version 48.0).
+%
+
+userdict /@RealFindfont known not {
+ userdict begin
+ /@RealFindfont systemdict begin /findfont load end def
+ end
+} if
+
+/findfont {
+ dup NewFontDirectory exch known not {
+ dup
+ %dup systemdict /findfont get exec % not always in systemdict
+ dup userdict /@RealFindfont get exec
+ dup /Encoding get StandardEncoding eq {
+ dup length dict begin
+ {1 index /FID ne {def}{pop pop} ifelse} forall
+ /Encoding ISOLatin1Encoding def
+ currentdict
+ end
+ /DummyFontName exch definefont
+ } if
+ NewFontDirectory 3 1 roll put
+ } if
+ NewFontDirectory exch get
+} bind def
+
+%%Patch from lp
+%%EndPatch from lp
+
+setup
+%%EndSetup
+%%Page: 1 1
+/saveobj save def
+mark
+1 pagesetup
+12 /LucidaSans-Demi f
+(libgraphics) 2533 1220 w
+10 /LucidaSans-Italic f
+(Rodrigo G. L\363pez) 2469 1480 w
+(rgl@antares-labs.eu) 2377 1760 w
+10 /LucidaSansUnicode00 f
+(Antares Telecom Laboratories) 2156 1960 w
+(Albatera, Alicante) 2451 2100 w
+10 /LucidaSans-Demi f
+(1.) 720 2700 w
+(Data Structures) 873 2700 w
+(1.1.) 720 2940 w
+(Camera) 962 2940 w
+9 /LucidaTypewriter f
+(struct) 920 3110 w
+(Camera) 1375 3110 w
+({) 1830 3110 w
+(};) 920 3330 w
+10 /LucidaSansUnicode00 f
+(A camera) 970 3546 w
+8 /S1 f
+(__________________) 720 6980 w
+8 /LucidaSansUnicode00 f
+(ACHTUNG!) 720 7080 w
+(this) 1163 7080 w
+(is) 1333 7080 w
+(a) 1423 7080 w
+8 /LucidaSans-Demi f
+(WORK) 1493 7080 w
+(IN) 1769 7080 w
+(PROGRESS) 1883 7080 w
+cleartomark
+showpage
+saveobj restore
+%%EndPage: 1 1
+%%Trailer
+done
+%%DocumentFonts: S1 LucidaSansUnicode00 LucidaSans-Demi LucidaSans-Italic LucidaTypewriter
+%%Pages: 1
diff --git a/libgraphics/doc/mkfile b/libgraphics/doc/mkfile
new file mode 100644
index 0000000..3bac031
--- /dev/null
+++ b/libgraphics/doc/mkfile
@@ -0,0 +1,15 @@
+FONTS='.FP lucidasans'
+DOCNAME=libgraphics
+
+all:VQ: $DOCNAME.ps $DOCNAME.pdf
+
+clean:VQ:
+ rm -f $DOCNAME.ps $DOCNAME.pdf
+
+$DOCNAME.ps:V: $DOCNAME.ms
+ {echo $FONTS; cat $prereq}> _$prereq
+ eval `{doctype _$prereq} | lp -dstdout > $target && rm -f _$prereq
+
+$DOCNAME.pdf:V: $DOCNAME.ps
+ cat /sys/doc/docfonts $prereq > _$prereq
+ ps2pdf _$prereq $target && rm -f _$prereq
diff --git a/libgraphics/mkfile b/libgraphics/mkfile
new file mode 100644
index 0000000..b7669a3
--- /dev/null
+++ b/libgraphics/mkfile
@@ -0,0 +1,11 @@
+</$objtype/mkfile
+
+LIB=../libgraphics.a$O
+OFILES=\
+ triangle.$O\
+ camera.$O\
+ render.$O\
+
+HFILES=../graphics.h
+
+</sys/src/cmd/mklib
diff --git a/libgraphics/render.c b/libgraphics/render.c
new file mode 100644
index 0000000..f4e4c0f
--- /dev/null
+++ b/libgraphics/render.c
@@ -0,0 +1,84 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "../geometry.h"
+#include "../graphics.h"
+
+/*
+ * careful with concurrent rendering.
+ * use a lock or embed on each routine.
+ */
+static RFrame imgrframe = {
+ 0, 0, 1, /* p */
+ 1, 0, 0, /* bx */
+ 0, -1, 0 /* by */
+};
+
+/* requires p to be in NDC */
+int
+isclipping(Point3 p)
+{
+ if(p.x > p.w || p.x < -p.w ||
+ p.y > p.w || p.y < -p.w ||
+ p.z > p.w || p.z < 0)
+ return 1;
+ return 0;
+}
+
+static Point2
+flatten(Camera *c, Point3 p)
+{
+ Point2 p2;
+ Matrix S = {
+ Dx(c->viewport->r)/2, 0, 0,
+ 0, Dy(c->viewport->r)/2, 0,
+ 0, 0, 1,
+ }, T = {
+ 1, 0, 1,
+ 0, 1, 1,
+ 0, 0, 1,
+ };
+
+ p2 = (Point2){p.x, p.y, p.w};
+ if(p2.w != 0)
+ p2 = divpt2(p2, p2.w);
+ mulm(S, T);
+ p2 = xform(p2, S);
+ return p2;
+}
+
+Point
+toviewport(Camera *c, Point3 p)
+{
+ Point2 p2;
+
+ imgrframe.p = Pt2(c->viewport->r.min.x, c->viewport->r.max.y, 1);
+ p2 = invrframexform(flatten(c, p), imgrframe);
+ return (Point){p2.x, p2.y};
+}
+
+Point2
+fromviewport(Camera *c, Point p)
+{
+ imgrframe.p = Pt2(c->viewport->r.min.x, c->viewport->r.max.y, 1);
+ return rframexform(Pt2(p.x, p.y, 1), imgrframe);
+}
+
+void
+line3(Camera *c, Point3 p0, Point3 p1, int end0, int end1, Image *src)
+{
+ p0 = WORLD2NDC(c, p0);
+ p1 = WORLD2NDC(c, p1);
+ if(isclipping(p0) || isclipping(p1))
+ return;
+ line(c->viewport, toviewport(c, p0), toviewport(c, p1), end0, end1, 0, src, ZP);
+}
+
+Point
+string3(Camera *c, Point3 p, Image *src, Font *f, char *s)
+{
+ p = WORLD2NDC(c, p);
+ if(isclipping(p))
+ return (Point){~0, ~0};
+ return string(c->viewport, toviewport(c, p), src, ZP, f, s);
+}
diff --git a/libgraphics/triangle.c b/libgraphics/triangle.c
new file mode 100644
index 0000000..b598855
--- /dev/null
+++ b/libgraphics/triangle.c
@@ -0,0 +1,43 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include "../geometry.h"
+#include "../graphics.h"
+
+Triangle
+Trian(int x0, int y0, int x1, int y1, int x2, int y2)
+{
+ return (Triangle){Pt(x0, y0), Pt(x1, y1), Pt(x2, y2)};
+}
+
+Triangle
+Trianpt(Point p0, Point p1, Point p2)
+{
+ return (Triangle){p0, p1, p2};
+}
+
+void
+triangle(Image *dst, Triangle t, int thick, Image *src, Point sp)
+{
+ Point pl[4];
+
+ pl[0] = t.p0;
+ pl[1] = t.p1;
+ pl[2] = t.p2;
+ pl[3] = pl[0];
+
+ poly(dst, pl, nelem(pl), 0, 0, thick, src, sp);
+}
+
+void
+filltriangle(Image *dst, Triangle t, Image *src, Point sp)
+{
+ Point pl[4];
+
+ pl[0] = t.p0;
+ pl[1] = t.p1;
+ pl[2] = t.p2;
+ pl[3] = pl[0];
+
+ fillpoly(dst, pl, nelem(pl), 0, src, sp);
+}