From 9a923eda489f9f2e391604e919ed46c0c363ba9b Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Mon, 30 Mar 2026 19:45:36 +0300 Subject: [PATCH] refactor(demos): adopt new sixth-3d API and add tutorial docs Update all demos to use the modernized sixth-3d engine API with fluent factory methods and in-place CSG operations. Add comprehensive tutorial documentation in the Essentials section with screenshots and code examples. - Use static imports: Point3D.point(), Color.hex() - Replace CSG class usage with in-place union/subtract/intersect methods - Update to renamed classes: SolidTriangle, TexturedTriangle - Use SolidPolygon.quad() for quad surfaces instead of two triangles - Add Minimal Example tutorial with step-by-step walkthrough - Add screenshots for CSG, Coordinate System, Shape Gallery, Winding Order - Rename AxisArrowsDemo to CoordinateSystemDemo with class references --- doc/Screenshots/Essentials/CSG demo.png | Bin 0 -> 41724 bytes .../Essentials/Coordinate system.png | Bin 0 -> 18771 bytes .../Essentials/Minimal example.png | Bin 0 -> 641 bytes doc/Screenshots/Essentials/Shape gallery.png | Bin 0 -> 24082 bytes doc/Screenshots/Essentials/Winding order.png | Bin 0 -> 1038 bytes doc/index.org | 345 ++++++++++++++++-- .../sixth/e3d/examples/ArrowDemo.java | 53 +-- .../sixth/e3d/examples/OctreeDemo.java | 35 +- .../e3d/examples/RainingNumbersDemo.java | 4 +- .../sixth/e3d/examples/SineHeightmap.java | 61 ++-- .../sixth/e3d/examples/TextEditorDemo.java | 21 +- .../sixth/e3d/examples/TextEditorDemo2.java | 119 +++--- .../e3d/examples/benchmark/TexturedCube.java | 6 +- .../e3d/examples/essentials/CSGDemo.java | 206 +++++------ ...owsDemo.java => CoordinateSystemDemo.java} | 40 +- .../examples/essentials/MinimalExample.java | 11 +- .../examples/essentials/ShapeGalleryDemo.java | 74 ++-- .../examples/essentials/WindingOrderDemo.java | 12 +- .../examples/graph_demo/MathGraphsDemo.java | 17 +- .../examples/graph_demo/SurfaceGraph3D.java | 10 +- .../launcher/ApplicationListPanel.java | 22 +- .../sixth/e3d/examples/life_demo/Cell.java | 22 +- .../sixth/e3d/examples/life_demo/Main.java | 12 +- .../examples/terrain_demo/TerrainDemo.java | 26 +- 24 files changed, 698 insertions(+), 398 deletions(-) create mode 100644 doc/Screenshots/Essentials/CSG demo.png create mode 100644 doc/Screenshots/Essentials/Coordinate system.png create mode 100644 doc/Screenshots/Essentials/Minimal example.png create mode 100644 doc/Screenshots/Essentials/Shape gallery.png create mode 100644 doc/Screenshots/Essentials/Winding order.png rename src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/{AxisArrowsDemo.java => CoordinateSystemDemo.java} (80%) diff --git a/doc/Screenshots/Essentials/CSG demo.png b/doc/Screenshots/Essentials/CSG demo.png new file mode 100644 index 0000000000000000000000000000000000000000..5602255134edee8fe56c675dbd7650e95db74d48 GIT binary patch literal 41724 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 literal 0 HcmV?d00001 diff --git a/doc/Screenshots/Essentials/Coordinate system.png b/doc/Screenshots/Essentials/Coordinate system.png new file mode 100644 index 0000000000000000000000000000000000000000..7896688a490fba99f02d707bb3551b1f661a7ba2 GIT binary patch literal 18771 zcmdpdWmg_fNdNR(r{;93q>3(3M>EsfTOG=rwssr(E$LE2o(HO17mHD zf2sgzs=btdIt2oOO#lEoItCPq)c^osW8*YF)&8$QAQ)I!Fj@d04FEtxL&wB~fx&3> z^z4|J*!utg1_mZGGcN`Pl$@NFpI-_F!=t8VL_8UP>w2n52#B>?OLsHhl- zh~Uqj$+NQy;^5#D6O%vT6cmU?fB*>zMKcIQMn;W?N5sY^z{&ZHj*g9%mW7Fln}b7` zgoKiSfP|f0NK*2}6CWulB^Q?nBO@0!HXfiBfP+H-XaXRSns7LcprEvvmjEqhP1S%n+4g}VAVPHu}sL9H{eEwWXTwIl#TND%X zqlk!NqaY3tn1evDfj})m0Dz*RApigue9{mgPzwNl0|K8MzyW|Rz>^0+Bi@t0PyP5L zxVR<4!m=D35sf%cww#<#=0BcF1b_>`&HbE+C?6l61PkkP6E_~D0)q~aO`SyZ?+K>` z03!gq006&-)Brw7ITQ;7+J5RDbo7-JRRAMnVj4h2Kp;rOQIVE56=|UfSO&<->iz)$ z{5hWift@tDfO>%78~_kY%PKCe*2GQ%=mh)(f*OfAyRldhQEWp1APCeS2?YJ;X%pZo zDAP#6^y$ejAiWru5FY?ogJRD<>C^v23{VGwTL`!TJx{^H+>F6hEKKSpCr$E>9J>$} zC&8(tYRncWNJGPt%r4Z7kBf$;?8U@m0{}JxaK6x!Qc-DOVI|1Wb7Enz*gsdSXTSx( z0PoPDq@)s|03CoGfEg18Fa`jgB6J!6fxs|>VU$p;Mi2%ot4;GI76$CDEDaz z8Z1N~8I?3zoo zI7pqVzMgPbBqIdGQg)1mhOU)!mEK3> zvFB4u!43EQm1J){9s(1iwkJw-@;f+@R9R+eti-mnb5eS#+J(O-x{Y&ZesZ1pxpBFU z1Ro!3WD<#nrXA_dtzMpeTD)UY6vKX5X(`rWm|UR~PTBS_JRUsj*k!V&Blfx*P;uzy zH-nn5)(}>i?5OroNh3$sePJHzCDiDy!>xuBj;qX z8!!=hKrL=^P&PJgLdd50C_`Ck2fYCm`NM4T+sO2e9~ z^hbYrL!7{Uip@t!VLrjw!3y$1);Et%?7|X4Se3c4lPZJl3qk1(lC*=8`@?~fs|f>} zYUT5GN{b|;K}zePUJGd(NnK7Gsp~C8^_ir5qPnNMe;iw-5&NWfF6$DazV z^W=W4oL*aYu%-P`J0QBF(9TF&&Ky3}w|8kmb*z?#rpq1cZ3m6Rd(A=F{kXESGOQuH zH#gkaX!L!3#ogKS^89>!Nv3eqSZB>?bM)9C;YI)0@$-ytl?)>>=2KX!5#CEngGDCo zU9UPDgWs|0J7yX`MEdJlFq)YDdu5;_TIx$`!@Ri?rd`prdM1=J`R(qv0b#tS2dBVg zEp>$cQY?-u_X`DS*B9uzU&n*L#Zc5Wm2y_lpXBBzGWQ*hcc-O2i(>geU3JIWI@5VF z{4(#G`9`Q*`Bn7-0aIu2m0a(On%nc+DkH@Y$F{VR=3iT!^0HPEv0571&A6J_`ZBI) zJ7$8#bUqj@{1d~gEG|3an#y-jGg&q07zQTV(Xe zpc)s}o6}ta3I6%&Ck^dS(H5sTcN||`$JtM}eN|gy`G{@WactQY&v2)f4#T!o3=40) z;92_k&BwiC^Um6-%%*BU8BVf3f&yH;b$xt6$SB|TvG=+)lSSi;kIVNfjdorA4((ko zcNsCJFRy%b|2XapQjhhK$^S9Tc7OYqp}po9(qo{f2PlRx9*cfeG5Zq;lcV_w!`RHU zsD=c8a0$BgzutT4yQi*VF&&!DMGIli!|Yd6S+Kj@j~77Uc-$zLB0iF)*ADa`3#!*1~xy8);v~(!W8g2?`^H%Hx5xb%*6K<2?jQayv)cGN`WPePx7n#9xNxRgC}~qq_MT}( z8S8N3+(>>FU*23@4fb<7^t<-=n{3=&olND4zbjqrux&Vcxu#A*zCk53g`nB=?V2|= z{3rdT^f#rCIILRv+0|}<+0N=_P%@?`2H!QF00QsIqZqjT`ygm7iI3)4Ce z=Fng>2^ZHS6^-t1^kV34+C4A@E%J4z)^a48EJ?<|97A!>lbMyy1wZ7 z_Mq66L!FR;2?gv8&A*R?@K%dn9t^fQiu7^24;&ev(#J{CP9HZo`f|ZNzIcNAU z6+De&tl6SKhi)gY66pv@i||)a*iDC68v>jk+I*n{6~{lx-VH}o)!I)ar}+P zNGiCb0LvG3HM@NK%0r!x+M==(;vX&_g7)KC?ms0p z|0ZsYji-VBelCU0!NE}~6ERD#!}AN>joI5n!Q#8VWcFv7rpm(Ar~#{FnZ80xlW%89rW-48!I8-)0>}?MCP( zElV9*dIX|VRG!`2LC*e2Jf`t)(FDI)og-+ED(R_UBq_Z4a1#{r_Rxs*+)Soc>3!G? zC`2%8o4ljdM1_ek#@I5*i+_cm+7^C@&ZB61uPYqymcFE_G?@V@-np&28FSra2>Lg~ z%?`X0-PmY4-fo=obgjr}4nHDb6-u4F^~Jb-T^g(8mU1kkO+Y#P23)AxA6%YR{({VK ze>6|qk!3h4aq>kE?RN?})rx^_Q2$7qt;`Y9i}dmC*s9UbkwrrDUrb6qjFdEs}p)p`fPKh1AH3k$^R^+(|ccy9U ze*O^M*T4LIW0L`_LmnI%V4R#Vz6)vCxxGKMn%fW+ze~)g6@7b|boXKI28Rh=jee9X zwNI1+GM7t=hW#eNos*f*fW7u(^^53ftUizIm1;@niJUW|ANxwcQh*7N0=iV=ixG#U zA8p%3B~Xe~)5NX?RrEYO{#L&nTFIHasWF$JOvld69?1jgRhpAf?2mtCqoKnt8*B)P zr7vR0CLKMtsZI*8Q8N5p8_y0^$9$T^D4=*jFCmfSOVB~rFM2I&9;s)hM4=uxk5@N| zc|X$GI=YI8MRdqB4=SCq;t@^12?Yj7%9fNqh=wAN;`rgH4rnxjw+Y5e=i9v53kccT zwQ2pVB^Qp$%RRWG4#dQV>TJ3Dk>&L{cZ#$e5dP>nCNapF0Ry)~kZ4VT$xu-L7;*#V z%~u!D(0Lp*mZ;T)-D?4*s#Q}7G>l+#&$iw^d9MCR-%D`MVvvIoS_}e>fP10%Ngxyc zX)Lr;yXd@vS2kh7bOq%s5G3H786S)4tNbVIy06=>dq~GPMtWT_fImXE(2)3WBZxnr zMNXmVoyKn8m+o%$51P-u`SLFn%n>1^&N#X2ABSk0DTVw_$^4fA0`xmYQ&*ilXb^Jr7Q!ts&x}UJ18R~$D+ip!Y`q5HKobeN3n9z^IQk#k0!7v{ zUfEC?)K*jhF-n3Uk_rj#a|K9~cZ_g&b%^JWHfuY&&h7xc&a;DY+%FJrcZtDKJL>;d zNMlclWY2sAc3Wr_{2B0(Y^VTw*mD3zI3)(1giX!CU^uA&r>c37L9}D6W)^c6y@3g7 zJ`&T>ZelCP!sqAsVDFDJZZZ0?7fG)Y*X%wf4)%2RcW0{KL#b)dq4`jG+%4dDJ85`j z%F7zwXF51WdhI4S6o+2vowJVJ9YX4Mb;~fVUKO%`x-DZ=MDWZ^b7y-+MSB(&mMfm! zoYmgZ(N1R49I}AMb&;<__du<5<2@`O8xnfpgRMd55PP82sWk$9Aw(Qo(tj9ThRM^Z z%;s;#4JfNCH^e5Bp{lvNB}xgoP-tCh-rBlfAL<<{^6_&#zck4Gbfo1#V$O4j30E}g zq0K?hEE;^kBod7wH~u#5X`w}THuiJj6#;~(vVs7_3OYgqFeqp#1;*Zqr#Pgu;4I2| z%-t?m_;&fcZ1gBFaQQ-r51I{B{SG(4Oy9jcM5Hr4xCb%d@kqcq^{)h7Qk%^3YndmL z2|>i|%S@@(hvEDl<51qgzi(ui(hLx>Cw|0SsZ+xucJ2P<@6o?ct{S>_Ju1JG`CAMd zF=2~xub&O|bWbN-OvTg>TS?AZUuJucan8HMtCB8cpDT`7g zjx*AF0qHm+?p)#8UY*f$rD~9hg@(vB3Ze~DjB>EXor!;?9G<3imdWCzdR4>3BJGcl zm1Q>p{l*Bbe$$J#MTaCG*=NY^8%LJoYeGKayPS0b{}#W;-_qRDjE5)25JKJ+2Y{{1!QM%xII=vQ{!%?|VW4F`zsDWQvG$YFUS;sbwvL znimnGK<+y(m)7^6;8v0&@A5X=$ZQ$JN#scLvm~!m`J$fh@#L{!Y;b=Mk}59D92=yK z?{F#23<9Y^+Nw3bXqr5eb(KLJL^@3E)%)YdfW$RyI_cxj26wi|LQo0-D`Ncgko>~7 zaaeCSDk3z-SOHiB-x`(91A7u9k+FTtP$=W}V@Sa`OgbFELvF2&VuLu)t>njCbE_Py zpwmTKnkK1qxB zWYLxnogpcNp!p#Rq5-p!$?o)LM6sQd_zuBM9;1Weo1u!pS{51D=mFLG?pw55N*R3e z1F~4LN&I;n%d-?=R^FM~Z*5j64vZ11b!L3`V$>4>8PxwhohGMG!CA`cr9A$=EGc** znKLA84H$}s7EX8}1oO(KYr}tn7fYE!+Zw|jj(BBX$AK?ffk0ZICCB_TVsk!OO}d^J zLnUfm28ezlx=#A_ggHuxGziqE_fdoOvsEO1I1+^R{<}deiwy24?ry?d4hR^aL2MUs z7rt#~2jxWLNA#2-L*WJEz#!zGL4?K67@3-Bp^$=pm?ac=M^gdAvWW)zD7_o#lZ z?_ybH2FpBgBifuI3!aTpNbAXjS?VC@5Ia#ehE(_2jS3@@P-gH0SUQs&w8`rHK0*BR z3bGuT$dbAh%jkh1KvRYS2rrsL3g%#iZFHCcC8?~*J2B_~HSDPa-)qWdc!`)|RxD?N zzRRsY!~ZQPt3j}yoNmJz1yZ&`$m24)j;SNusxLDg<3}_#EE>!X&y~IF2j}IJt3P-{*25<7Q}LC;kRbGi0i=SGkJ5?= z37BrV>e1ls^^IaxaC+E6u%tf?EUKUKQ=Gi%5)K1xLYcmv-hETUingO=@9|{vs$ou+ zp%DdT4t;YM6&@PM({t3OLDToW(g1ymQmOXn9;psnPECK!#4nRr31<{pEz8Yg;H}5o zzz_4j#_c(FAE}eP(dY{Bsm2dEF^QG1MqbM_bfJ2%`*g`iNXqUP7aJR-UUUd&7)|Uc zVW%l1G7;ioqtC8-dWImyKX<8#1-ZC5I@;N}Mx}0DadW1ttZ>SP7o;XCTt-T7v5}g) z;ivcMd0)+3I`%C#24mFJ!A(N`1yy)2gqSv8y1C(r9&bxkC_7{+J2RKgR7wd{VxHoRA%0rbSUS}#riIv8wDqX)H9HfV5I!<37R zYkT!kl4d-~u<+hjFStRUUj{OL41@oX^4lky)q6L5;j9L;wi>RSVI`-SH>&=Z;GHv6 ztLdFk^9Q3-l|T#2=X6W6&(52O+M}a^qm(N97d!dIaM&sa zS&J%KB1>(I{Z}0c)9#5@32AW_8cfP)&G>Q=D-kG@QpCC6uU&_$y|j()*Kg8Sb}ZuC zBnjGLFzbb7R^{eP3gZLWJrl}L#w>Ja3OVU|NZ#o;tzbA)#(D9f*4ZLG3)eop^)YTy zRd^+-LLSP91+5xhOS`u~8`O@0=ERGnw8MQGI%+pQDO0q znUhD!5%--E$0_tg6()^P0&SrO+7=~$%%x_RH-DB0CZN|B7S*&bWaSjQBte7a;Jb%# zPyD8?S^gYw<1~f?J(3=aEl*Ts2I?S-%mn@U@Jze&BK11)TH8oN{gr*Nr)RJ|cSOz* z?M0>|9l#YCOx%~pMdvn%NxP{s0!8vkuKp;w^<#g@hP0(G@|m8M{yADW0>llc5QP%I z6X94>xLO6Y5RV&sXj9Nzp>65UyeZ_#3IF zlxG6f*c2k^LLH&^*?F#tm$@8NLn5fvmF4LI*@l{}LKS*IpxQYz`CFRT6gh{X3SuM|0qQTGZ%Nnr zonr7~eOKk{AW0Jf-R;mp_6(q=lL=n55GDEA>%gq~6 zctosjh~#J)p8%b#(&PcY{V4;8m1T4#0WUe;#f_}roLcImIyeQ`h#9DiU`zqz3$9i~ z7k>_M=PgRsW*cg;>Qm^k6KAv`Ns1VBQ~FsZoeMOhhoXOGdUp9zE*MOAtff>zY;DBn z$6z5y6Uy%NirdOVN>m^vK=~^YhSvX}9$ZAt%R0K#!M-$$n;nozeh5M;v?Yr zFuDkE+;~tM-@nX$;0kNW%UF+^_X-qNn7|x@6!+xC&yM)#mI$P0@+R~IHAv(O5W&~z zMvz1htMZkR%zYf35XLglDR?{AbNWGG>&dR?L_}NZ#Duy=nAhQEbtEsTQu9B?SOQe? zw!7DBDOoH*ykPKW^2Q=G+}#~|y^83AQ7ogP>23Pwl$zXV%X(_PPyU%)*$jS#V&l#l(r*9t+rC zW1WztltEH3B+seJujmafJnxUhRctOU{w|x^%9U;vFNtq_ff0RtP(qi;;r4mH@6Qz_ ztQEV-mOeud1t+Ulm!I`>m9y@gj}8dHz!Ab^W`IuP>ccUm;;cIPY&j?ZJ{c;_3{e;LSnR{fTNiMurfKLcMvD$k5bU-%{^E zRq^S|Zf$n((9lYEB&v~Iw*Gob&mL!-jkOW9Tx*NYKK4N6STdE|1F%T+%ZVC|u($RR_5@|f=Ss}-A5 zIXVpH<{f%#XE(PhNm%u>C0UNp#Y~Z76G3{@ArjlXFdY>wR$%D7EjJ%Z0OXG!AD^I3 zp=LM4zkv6|2q(z22+X=O^J)<>tV#|m=q>RvDUl*E#15^Z8<8#%fwC?Oev*O8%rLQs z&3R5kIyy!a3xDG)!ICv(RlczI?=CMx4LK}SLYy3)ZcCuuT*uwMxeeaid-7tsq=WhR zh5TCQ-_MA;Fim$)DZm#2MLn5g=}qB?-a~>Nww#;L4f$S3!Q(j#3@reeL`o{$Lpw}P z0dzx`b?!Gr0knB@(Xo0SaPboC&wbT`fhR8xo6+LwVd7Q0nr~_ zrxRNP4n&1+9WC6}xc?E=6rl=gAD)K((XLA- zf%Z62QBI9Lghu6#G7Ra|W!IB4x?5CVsqx?)`18bXu#O-*fl47#a3jM_Qxz#%o#7Bp zth{2Ov4>yM{Al_iKlb;3x|;rz)BHmq#xTN;_T!@7#Xx!}3#t)9A&{IDhnvyr<$YKO z{_4sVU0qFj;ua>in`0U?VYrc{79+5adQBXwa~=T$5dh7MEeXPG1PbiYUJl7Rg(~~7 zj_?w(X%>vZp2i&qjC`Y0pSn|H9QmVXHLt4N%0!wfO|V{iY8uRM{d(bNI$xw&O$NZi zhmPLv{dP$VdF<02OP5Z&j7xy4q?K$Rl z+rNj4@;CP|))+uR6(7v(Y$FA2E*X;rgs%pIHdv#tlmM#FQzB9X&#e{Y_yjbA8!CE$ ztfQjZGcCcv7%#&t1RMA}e{Sks79Tzrxn0{dZwUxm@M^+zz#n5CMPC5UV|c&U$DYpM z&}U|5HVU~5VP|LOz<@xUoSnzV#=hn{Iyi`UAI-;fbXX53>grzoZfb2c@ASELvEHF< zGap(5$lUyi3^cWaAaKZGk?Hp7@eNqgK5V?@Q*u}#P!(I|R1eLPa>h<@=Zxk@UvD8a zqXyl*_Q69lM$f{fW5;V>@p0PV6L*oW)DulpWH&M{uDEysIX5?_qoYHH>gep`i35>+ z-$ZfrES#GsByw~2eF&AB+>Cb*;|W^syko7s^;>zCbml677xrm?>KP-BhKy;tAdQZM z(Z_CD0bKepDA3b3FI0_Rivr6oEPG10dGUaC^bF-Dj3#u9{ZXpd>=r#uh}6ule0<#5 zNmMK^AtAwj%B&ND`&~jpS9c3#rvC;+wcbnjI6>}ShY=O%zV)fP`Xfpb@Bml_@nReh zgc_d@0ja}VQ{UG&y}i*E`G)O(|NGE-rS&_n9KgZS5*#R91LT5I#5pUKS+*M!J`y0e;pUZ6L z@3$ItXs!pbA(8$@MJR^OO3uXeZu4RP^74bylf%(8mL%&x>wk9V!XfkBU5u+Z|PJ=jE;9U>uSE0DOE zeH!ERk4zkoEU_FvD*x@}g8rKiIHeA-nuzykoMJDYnYSQMbJ*CO}Fvc2qr(M6K)skmt@yy?#{S0@}C$dma)6*(?P# z+sIen(DBy?>cfk$dY2eyYo|Y71z&&VDwz~a*fES3nipR593tU7ijS;VSd_KeMzh{# zY@vy)&9jrKFkRVbLyL_wnCDNIUgmFF&g$UTFRiw8z$Rx8#q}xq2HP;zJW)SvAoJ8A zD|?n#6_S->`$Z=)XfyNX;e{W~MY{`|`tQGQ|NZ;t_f1xh'Kg4Hie<+>s~HD5#neO^0DI298HoZsZ`h2RA*(b|K`5Z9K-$b zJ4`u#(R%HeKk~eI@~0g83Ww>aEvBvd_l&;wg-o|d7kdq|oV1PG%87S@{hfjvTZ%_`j+HLWepR5r2GL_e50PC?-#Dezr|xc1OJ|~vOBv(* zJLGE>BLB7jorW;GM+ea=d+3PFo>&wf__h-L$7r&)!r#3Y33y>)F5s&|- zbgq9e-Fa@o>R>mBU_%oJJ=2!moqFXy9s_^jFn|<7^J7a4rB~1S)I{t$U`08bc#b#K z_EsoMJ9!LiDoAnbzlQJSeW2~W9Ur_vY94&`biG9)dVF?^wOYuhyth|q0>e+gMD>R} z`?P4aR^Qy*eCo=)w9v8P>Pk&bjXT`Ai|F2*pP$}b@IHHbf=sziTcjeX`dQ*s;qmcv zpvce?d`A2c3fiRCrR8unFbY|K6q02(xEB8xVU2pb@rfl~Gd0vL75{(@5}{h( zS|2D|K!EOKj`4{`T{VTVHBm`7i8U+9zJvUY87)=of_oWbBhv?mq4MUfq%(EXm|^{v zALHy}WoFd_~qZLO5JS`Z|v`o@zO_#iTgw?5td){DDk@C8;1F>rY>!u!mo0DOTIP5p#j_~+zq}g&NrK%xjRsec(>?!j{Hddo;i4+gw zlP~{9Z7LfC;PWFbP963?TFt1ww04<%=D)Q%t~flLU)*i{!{0v;FRZa2vn|Ph_*hwc zwea#zjbl?^~dG@?DcJb z`-1YTn}>^IkLJckHQado(B3V%y1O`48tf=(W{mC9FgPj%#V6-Rt4JS8wKIw-jJaeRt@ujdr6*@sjqzn|al z=b34xPJ6*Z%zW)Jb~Rkk*k8i@GNVMg`m?@2>ic>V8r4LO+v!ZJQDdnHRag&lmT1(F z{;1pTFHhe@W$4$%}#V}<!z5>0HkGL5GmecdMPjt2rnr;T=PS8< zPa#HcV$mFdfGU&Y8{WHJ?Q1UP)Ate)WbBWB34%gEuE#d6QHB_8#F$G*XKSYQP~m5P zPdW@{n08D;P|S?hoE{P5zvUXNEVNEy3S>1kMJ2UM_k)pxi}`CD{uLq1=#YCRh!F%7 zn;bs_TbPe@lHX0Zk|QmaNLiqPUZwZ?Nx#I^?YmX~r?(xXF*EMsaQ#RG2@N}rZP2c# z=c{vIf!InEqBi`MLVT6iud07r3GBhBIQ5&PNOFErBwOCn{h;RKH^Vwy;tZOqX;9^^ zkgbpAhz(yoS>8i(l5pURWJd!9p=v)8SJRS%tO?)bSK)&zHM@S^*0)B`kL)4rH#6xG z&h(Hi1aC%jb4JKrUQ7V}pcRrb7B82o?t*zq#_$)aIs)Dk2B1MF%vZ{Sv%=6Y6mgfq z+2K3mA#PmkY}`u7DiN&hBeG8?O34KWujLrR76@8FcZ_l^yL~UVyzqs zUcdm6p!dW|Xg2J8nv}8fCNl#&#tT0#9pDHukf=t<3m8%ckOuP5kfVc8pohX>*{g1% zZY|G9EG$usBpkowoCW~QXh=5v8@U0i0z-t-r^_El#z`#6;Wl)m(I*1%cq68QP_=ePJtw*9DDuV#4q;w7kY*rhInTg%j_D>NDBKTX_qt)bCXa`e| z3#Kr{wGw&)s0AgnNaQ{B=|>MT3W?%k^o-rpD>B%X1>0Hy>J`Vxx(W?tU{sF}$5Vzd z8z1+hNkC|FwL)z)xPhM!MS*33JNc(!QL*@H@d2*(Rhj0bQk8XxBy~Za>{nq3i0^4V zY~Fq1&^~GNqW81ZX?|9!yL?B4m3ZxeT+GEgu&Iv_b97HhK8{|Ql4JAy(c@5QbMeW83u#p@E8XPFTbF;6rTE1j|N zj2LZ&CiF!}I18bNd?Z@vgkml5Jyo3@E1~!A3iLEz2P6}O#iW;R(>>xa8FVV1vK2c! z_8tk)$0f9jw$pPdf%lxI&=(-bh&*apAG+wnK+^BP)+sH@QbXz)h4%?CekIPsASo0~DTs zfvDY)5A+P&)J0Xz$MLR#>po@U2rWkJk!>qfiNwGcOTKvQKQg?6_`FKuRTSeu76CQ( zv-xJBk`%awBc2BjS-`SXJZ{+U)fCLSOP9RQKS>db{f+#2$(EDC`&`} z(0#=Te#RE*7q5#hP?ML5n)5_i|A6^*>sFhf_JSDR5KA(5(6Yfg@#3c0aq#r37OY4I()9k7f%2_ zNKICtWOUr-^e3ioUT^(W#fROlGOGw6xnWW|nadNHn;+e~by;69M|p7As?Fb1wjC3Q z4YWlHf4KNN^! z)yDqI-Qr(Q@4Wn_FILWYn8e}JTBs=^wiIyA=7*{4kAxcYf%~IL%c*=%YvMB=vqb1A ze)Q;c+UlE8bmM zd#OVct+007`SL09WQi#QSDlBV`(61!Ye_ywQ_=&L2HiKVB-4WVA zzr#1%kmqvt_mA2F9Z8ez^cIQclo|uKg@gMvtE$P;n)uQxKruliE98U~rj7Bk5j7!O z=N@%UxGRhGlBBN?=<__8AdC?$JrhBzUC@A!i!-}tAuh5Qp^nQ4evK7s7jA{h0;S7> zrExnTfwHonfi7?*^j$$HCv>pD0)OW~gdmv@7l#lYk3LM$L6501aIN%HiU85W0$~ED z06x!nsKA~`8%ziqk~xoJmTB15XjJZT`1$Hq!31(^WR2K|(+E0@Qpr!xrsp&xlyL{d zwT$@7I}hS(YpwvSusNYu|NgCr|LZq3>0n!z|IR(N zvc8|SWY3(IRj+QB-)g?$m1QIOl#m#_X`gDxM`=i}jN2iRB1w$T3So@0o|PbQmw)p^ zSf$;>GWTP$3nz{m0-**|Do43!FzRBIMA(GE-y@OPaC{BmN`CZ2ay9nI>~n1fG@BUU zEpc?d!<*1`GNhT(BKq#6hWWRy+g}yV9&)MhKb0jq06gucu|q^J9G@(eK!fpAsrd>& z#cGR|r=_Y`P6yv4y5&VBS{yKh&JbthMD^u%Sx7!bbeDY1nZVZr%73U$qb-EXVvnNAJ<*vm`8%?Dmje$g6J+zyM@rgqk$x)S%k@l z=W?kW>@D2mBwcQz-|cq(>UZ*ZK4qLJ$VT|n$zh;3`O=@OfYvI0*&Y+xL-`G*4W>l? z@V@~K0JJrW!a<`%x(|X~#B7W^fy0Jm;Vwh_TYm#E@xh{%)(b>4niLLn?MT#LuXFIT zKXE{&h5FRj2dp@|Np|J2-u&-^D*7KWUP1A}MwQln_f2Rv1Yv*w$Zj2Ays9GuI~{>m zK@ESLps1c+y!9;}$nSY{C1j!+?1FqZfa=vy)6M%pzJdyF-T`6Buu$ZB3cbHGWwHku zIQs}hAwlrYt*(th{!9Qj2uSM2SC9lci&-Z#Hr5DlhifVUZ{)a{t;>PrPe`Sx0#i-^ zJsBOs6n0RVNW%A?YeBW~rEif1?;o-EY<{m0J1HCUHBq@3Hr0a`g0E)_%S9Ci5m|-3 zL|oi<{D;5013W#~)|{Qi#KctR=5B)Xa;a!&$jLkRZZy=;K$0KaUgi}Dg6W6tP4J%M zju^y+1b2B7l9$GZ79yD)A1w)XO=5l+T$PZ^K(@--B*fDYZmMdWf-yMNgx@Wgz!cDmK zs}{0r^s3ChI5cOgpMNppWGezMt z+S?X`X8d3?F^B`DBI|@i+I5rHdm97-UXMWW6n>9>CE_g(h+u8>$6s;*F#~sZPW)Mu zG>XOB-dtwiw^VpC@&zgQd@owpP+?Z}bB9#aGNNb%xkincqZQyFo7aq9iHV5k@=CZ! z7E+=awt@qPB2RtNJQ6K0T=wCp^7dbC&qZ%O2S5Bal(PRd;+`wvf2}%QapI2E#-eTi z`q$Fmxj9u;F)?T7wKY%An=`Hwfvmy7Kvv@H51Si#c?oirIvQhms5pS~JX9p+ZxJ^H@wYX6E}xV`~P6{(gQ@ zF?LaGNAx^Dg1qSHuf03-e|&ANWtoRxcwKvz>+4@JDBl^bA>3%V=I@z(n+jmInC&4!kZC73!t@3eV#d~0^5Fh+Xj5!DRuxou+5s^fO z4-?b48Uqfp+FE}5%1q7sY_sbBz+k%*uoIi*_6hR4K5)^IvftV)qe_)FdAz?`fcGWx zo$NN(6*syVtr|Al?aNt}&q=H2aQ|$IKLPlPHqTYft$%*ui?va!ZmLx*=EN!?GDnmw zDUv81{0^r(7Q|8-(jpMCrfr{y)9m8EaoC$EdFGH48a|Mi{U%*kTG|SuV9qh5hv7Fq zjqOxqo3NbU^`3l=jz4?%q4G_Ey@7_t%~ynjGTv{w%z$;GE60o6$z`cjhk(j6#VUbv zM@36}8JpocHqBqS;VhqOvFtL_o+8wNsT7@tgeFbGxb+vnms`m9H9pN~i*gpmuj1`6 zJ@K)kj+deL2iNn;OqT_P7!`^s(`i~Oz;o6UHP9Y8uy9z4psktpow)T_1qd}sEUD_0 zI&{Hi+NtBs{h_$dPBCOyY7Oum&PZdrMeqh))oLp%?7SqQtOmV33{LP4K4>1%B&y%dT45-BI_av6yDQdzHhl*)>AOs$bx9$ig zvxnjtMX;d)RuwuCdR*uWcCu~@Hsxjb#TXDeg&A@`F0Le(#n;(}o#_h-Bso5~i{eZf z~~xf2|Q__0_6d+c;*r#$x)$ zI)tGhNmk{k9WqJ{E)OyMc!m7&8*8G1hI|hZGNDE(pmEPn!DUKW?N)x*-wnK02Kv04AXG+0+M3$_O_cR3ul>Bjp2{JWi6J?Cn zE%yeZuGTh?67qqp42nu2qHe?Cj{*<}BMO1gShX3>$$|g?X0~@f1v0#hT^d|LhX?x0 zf>TEnhCrV(Hli2s=lCE&T-2HNrUbobB-#Ura04_gH2Nsv>}G|fzq;Whd)A@Eupo@e zgU90fFDtOU0*HHi2kQx_3yu*u$~!5sVby+*IVTE>Sy7Ns+p`q4`9eoi@~4}UT<{+T z&bYu;^eXj^2QY6N(P{5-Tb-Ui4)G^6RfNQ^?WYk5?Gy}_@5}cl5uf{%0`05-X2qIf zWv3N8l7WfBsn+!Y3Q4~7EDQ6im*}ejM;(;Sf8@p~)z0XaIA}15!YqEYF;53=O&o_2 zKdT*1dJz;EV}$pd8|R!bY$mXcrugx1RkVc3(e2Uvn+MG;MuaspGcxM94|90cuFpBrK`gn@o*6*AX?zNk^6>3#iqvH*a2C9~lBSC`sTV|Rh8uS4+M1h!n6*v``M*7>fJ<~4D0HyL>7=;^COJfx>q@Z1PyP=(#b z@=J-a2Gp=*)njIghy(KAOd#ME#nFv$-Zob1b>?so^zJi*H)*m2DOitf3xjPvvZqY? z`X%gr>61cBdoyE1ejJb!?H-Agix%GV?kro!tx|ZM!P8CH-1)#h%hvyr%Z~-d{imSW z1`+3t98Iir^5D_?CwbHOj$(L;!NRHn@-Y76sPcNO8@X4~77P%xxAJ6FrCIcljOD}c z{zJob?iiqP)X9dvSA8eFw&a6P)npgT>lq3w@?`RIi;<@w7JdZKvz=D*&4c>471yLcQal()A2e8nlC_KwJ^AL)8iLy!RYXApydmk68!c0G}&5Eufo z?1rEpnsHwYI0ejlBKV5ymkH0nM?I0|hC~S57K1=OS`(6-0`@%7)b!(Us}+7c?b!>~ zJdx&xX1ovs{yl}vdLmyYaas3N{8UdQL7*| zd7>@|%*5dA-wy^z0gnavSOmr(kV}L3mI(w#F&MBGa0-ws1K7h(f(1{62LzHB#NTy6 z&@Bc7t&cec2CK}5CmP*e@pL_;7lV%H!V!$cjo~Z8-6l%3XWetNbq5AFNXEQ$}i2>3ra|*a8>a*e#z50K9=g-qN5QcFo7Sthv0V!=NXbA#Km1q-Gib@qFf&igSku1f=Oc4?h9sLQq zAXjYel(mc~D{BU(eufNDl9`Q9P}^~Xli0z5p7Z+r+|s2&w7Fm2=g--vT)%Oh1;#MA zxz|KYDL`o!Jx$at3fA)^ATVZu2L;B2=+pwCzBJohq9%tPZ(ZB&~ zmK4k;56@J*x;#pnh!zB9RO;c54GL<(_ySbNu9~P^=2jDt$w<2pSZ65e9-1gP3Sf?h zzAWD!?x;J5Z@C->bTU#~4`pUNS~F09VZaUoG8yUAzjH)_yCyn!QXqf=807y3IHioV z_w2FYrisp-wHe!2x`Tp98Dvr!sj>8GHw3aY(a5@h>Gd9}NUa5EzacqJE9H2+Zdzp2!IBaxkv-alC&;Ps5qME}+8 z=SfXAhb`#6o$lIPP6z_H2^Aj||Y3Q!mL7zXM6TxEHEbvm>V5H$r48w0}`E4S=N0VwddjdHo|kZK~@ z5R5bi#vsW2l0^2S0Hqn{nus_Aql|$$2xM({SJX94)5V7Ir=DOl?opa?t%)oMa@%us z=a`oy>J0+1C1+|k)uz_GyHRvDf(WG<&zdN9F&8hFk10PeXGo&#jNmm0jK9#(+qX)+ zDTz|E{)>%AKxxLaCR$v1`Eyg<{P}W)?(e;;$x0^c1i_dB#_BnOg@)L^tr-+(ty;4I z3MQ_J=0m25rjzT(<#K%Pg7OU86(zZ<*aN{LAfr^PRk>FfFC-e0@<*0#6-9f-k^=aG z2ZzCY_=Z7jV{SWFq%Y(Q|0`Jo!6rbKbY1U@h8oRIN3M4mjRI8192;cn@zBsfFqcWd za5kD~cYZ6sJM9D`3Q!&MYLLSOL3beNx*Gx3((kgJ?43TviUO2od>Ul>8R z0+XMPx`0nj6qbzCw=Ixba$RmoD9xaW#&gCTL0}!TsApL{ZvzT=)I@y6LzXQtkCkey zj+BA|4mDBiAjMNW#D3ub#Uo5-F`lAqI%8|;SDWhT*3t?N1o&IeKYctYEf;Wr!tXGk zA3$~?*nE{%;riWe!Xjm4Fvty4fS&O%+lohGu2g46*Z zfH{t;CbEhGuHqq308KRRFNb&t6hvJUAszw+!D*r`nwluK_7(9ECzCkuigDpY^85t>TLEw9iT4{ic6ckKIgwrVvkdcCd$!Q`YrNKW4kdcCd z$Z8@K1)w1Eng~S!D43uoB65(z>as8Cb=hJ0Izwsp)XhiWAO+%~r+u5fPREO-AB7G4 z?}b>1ARc<+$GB1WQ7XpMsE$MYC5(9J>9zpXamWQZAb>fJye6_U((ix(6hu}NEx!2n t{!A^#Q#e8g3MQ(Fj!TujLOzIH_!m#HjO6dl<|+UH002ovPDHLkV1l{z6@35z literal 0 HcmV?d00001 diff --git a/doc/Screenshots/Essentials/Minimal example.png b/doc/Screenshots/Essentials/Minimal example.png new file mode 100644 index 0000000000000000000000000000000000000000..6bee1cb17e06c6b098f6ed14ad9c9894f41e8f36 GIT binary patch literal 641 zcmeAS@N?(olHy`uVBq!ia0y~yVEO~Z_AJamk)K=w%Yc+YfKP}kkp9oWpu@m$2`J9M z;K;zRmw|zmfkB0VL6?EyG6Ta_1_pKphARvVYZ(~W7#Naot>pk3#N+AW7*cWT?Ip#$ zLkSWM4|hqPneQ7q@m$WXJig|GhgoMFf3WfNw57Yg&u~=ySaGi5pm5%Wvii@tCqK(P zJR20eO?3E37L124#uduDyHk_IbBef;KwoE4zPstu}lXU%qPoynAou&G-Lx ze!ji_&7W`lbMOD#`1!VdN!{<`=kotw`1w}e8ZLSM#jWapzyFCp{Pg3EeZ5!r+=9w2 z%&(Sj64dK|uJ}`a`Jt2g_McAo|M{f6|IZ0z=KepQPV3_!(AC0Cz^w(F6%cK>Y=OGw zzrVFg{Y{SP$G@+9_kNGCUVpObfzyr_Bt^}SR_Wy%e`JJA+YU@A3=E#GelF{r5}E++ C()?Zk literal 0 HcmV?d00001 diff --git a/doc/Screenshots/Essentials/Shape gallery.png b/doc/Screenshots/Essentials/Shape gallery.png new file mode 100644 index 0000000000000000000000000000000000000000..76a4fe5fec7b4dd4afff45f0120685d1d6abdd6c GIT binary patch literal 24082 zcmd?Q^;aA1_XUc(6Wl|9;O-6yZlSolOQE6PJ!hYN)^pD6nYAa?$Uu{nh>-{b1A|ms3u26cfrG%nz{VlK{�* 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 literal 0 HcmV?d00001 diff --git a/doc/index.org b/doc/index.org index cb10d9b..734d9cb 100644 --- a/doc/index.org +++ b/doc/index.org @@ -53,34 +53,291 @@ It requires Java 21 or newer to run. To start the demo application, use command: : java -jar sixth-3d-demos.jar -* Navigating in space +* Essentials + +*Resources to help you understand the Sixth 3D library:* +- Read online [[https://www3.svjatoslav.eu/projects/sixth-3d/apidocs/][JavaDoc]]. +- See [[https://www3.svjatoslav.eu/projects/sixth-3d/graphs/][Sixth 3D class diagrams]]. (Diagrams were generated by using + [[https://www3.svjatoslav.eu/projects/javainspect/][JavaInspect]] utility) + +** Minimal example :PROPERTIES: -:CUSTOM_ID: navigating-in-space -:ID: 0aca7419-48df-48ac-b18b-a8bf7b9db0ac +:CUSTOM_ID: minimal-example +:ID: 1a2b3c4d-5e6f-7890-abcd-ef1234567890 :END: -| key | result | -|---------------------+--------------------------------------| -| Keyboard arrow keys | Move: left, right, forward, backward | -| Mouse scroll wheel | Move: up <-> down | -| Mouse drag | Turn head around in 3D space | +This is the "Hello World" of *Sixth 3D* - the minimal boilerplate +needed to render any 3D geometry. + +: eu.svjatoslav.sixth.e3d.examples.essentials.MinimalExample + +*Brief tutorial:* + +Here we guide you through creating your first 3D scene with Sixth 3D +engine. + +Prerequisites: +- Java 21 or later installed +- Maven 3.x +- Basic Java knowledge -* Example scenes +*** Add Dependency to Your Project :PROPERTIES: -:CUSTOM_ID: example-scenes -:ID: 5f88b493-6ab3-4659-8280-803f75dbd5e0 +:CUSTOM_ID: add-dependency-to-your-project +:ID: 3fffc32e-ae66-40b7-ad7d-fab6093c778b +:END: + +Add *Sixth 3D* to your pom.xml: + +#+BEGIN_SRC xml + + + eu.svjatoslav + sixth-3d + 1.4-SNAPSHOT + + + + + + svjatoslav.eu + Svjatoslav repository + https://www3.svjatoslav.eu/maven/ + + +#+END_SRC + + +*** Create Your First 3D Scene +:PROPERTIES: +:CUSTOM_ID: create-your-first-3d-scene +:ID: 564fa596-9b2b-418a-9df9-baa46f0d0a66 :END: + +#+BEGIN_SRC java + import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; + import eu.svjatoslav.sixth.e3d.gui.ViewFrame; + import eu.svjatoslav.sixth.e3d.math.Transform; + import eu.svjatoslav.sixth.e3d.renderer.raster.Color; + import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; + import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox; + + public class MyFirstScene { + public static void main(String[] args) { + // Create the application window + ViewFrame viewFrame = new ViewFrame("My First Scene"); + + // Get the collection where you add 3D shapes + ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection(); + + // Create a red box centered at origin + final Transform boxTransform = new Transform(); + SolidPolygonRectangularBox box = new SolidPolygonRectangularBox( + point(-50, -50, -50), + point(50, 50, 50), + Color.RED + ); + box.setTransform(boxTransform); + shapes.addShape(box); + + // Position your camera + viewFrame.getViewPanel().getCamera().getTransform() + .setTranslation(point(0, -100, -300)); + + // Trigger initial render + viewFrame.getViewPanel().repaintDuringNextViewUpdate(); + } + } +#+END_SRC + +Compile and run *MyFirstScene* class. A new window should open that will +display 3D scene with red box. + + +You should see this: +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[file:Screenshots/Essentials/Minimal example.png]] + + +*Navigating the scene:* + +| Input | Action | +|--------------------+-----------------------------| +| Arrow Up | Move forward | +| Arrow Down | Move backward | +| Arrow Left | Move left (strafe) | +| Arrow Right | Move right (strafe) | +| Mouse drag | Look around (rotate camera) | +| Mouse scroll wheel | Move up / down | + +Movement uses physics-based acceleration for smooth, natural +motion. The faster you're moving, the more acceleration builds up, +creating an intuitive flying experience. + Press *F12* in any demo to open the [[https://www3.svjatoslav.eu/projects/sixth-3d/#outline-container-developer-tools][Developer Tools panel]]. This debugging interface provides real-time insight into the rendering pipeline with diagnostic toggles. +** Coordinate system +:PROPERTIES: +:CUSTOM_ID: coordinate-system +:ID: 2b3c4d5e-6f7a-8901-bcde-f12345678901 +:END: + +: eu.svjatoslav.sixth.e3d.examples.essentials.CoordinateSystemDemo + +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[file:Screenshots/Essentials/Coordinate system.png]] + +Visual reference for the Sixth 3D coordinate system using three colored +arrows originating from (0,0,0): + +| Axis | Color | Direction | +|------+-------+------------------------------------| +| X | Red | Points RIGHT (positive X) | +| Y | Green | Points DOWN (positive Y) | +| Z | Blue | Points AWAY from viewer (positive Z) | + +A wireframe grid on the Y=0 plane provides spatial context and scale. Text +labels (X, Y, Z) appear at each arrow tip. + +This demo is essential for understanding Sixth 3D's coordinate system where: +- Positive Y goes down (screen coordinates convention) +- Positive Z goes into the screen (away from camera) +- Positive X goes to the right + +See the [[https://www3.svjatoslav.eu/projects/sixth-3d/#coordinate-system][Coordinate System]] section in the Sixth 3D documentation +for a detailed explanation of the axis conventions and coordinate math. + +** Winding order +:PROPERTIES: +:CUSTOM_ID: winding-order +:ID: 3c4d5e6f-7a8b-9012-cdef-123456789012 +:END: + +: eu.svjatoslav.sixth.e3d.examples.essentials.WindingOrderDemo + +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[file:Screenshots/Essentials/Winding order.png]] + +This demo demonstrates polygon winding order and backface culling - fundamental +concepts for efficient 3D rendering. + +The green triangle uses counter-clockwise (CCW) vertex ordering: +1. Upper-center vertex +2. Lower-left vertex +3. Lower-right vertex + +With backface culling enabled: +- *CCW winding* (negative signed area) = front-facing = visible +- *CW winding* (positive signed area) = back-facing = culled (not rendered) + +This optimization prevents rendering polygons facing away from the camera, +improving performance by roughly 50% for closed meshes. Navigate around the +scene and observe that the triangle becomes invisible when viewed from behind, +demonstrating the culling effect. + +See the [[https://www3.svjatoslav.eu/projects/sixth-3d/#winding-order-backface-culling][Winding Order & Backface Culling]] section +in the Sixth 3D documentation for a detailed explanation with diagrams. + +** Shape gallery +:PROPERTIES: +:CUSTOM_ID: shape-gallery +:ID: 4d5e6f7a-8b9c-0123-def0-234567890123 +:END: + +: eu.svjatoslav.sixth.e3d.examples.essentials.ShapeGalleryDemo + +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[file:Screenshots/Essentials/Shape gallery.png]] + +Comprehensive showcase of all primitive 3D shapes available in Sixth 3D, +organized in a 7×2 grid: + +| Column | Shape Type | Description | +|--------+------------+--------------------------------| +| 1 | Arrow | 3D arrow with conical tip | +| 2 | Cone | Circular base cone | +| 3 | Cube | Regular hexahedron | +| 4 | Cylinder | Tube with circular caps | +| 5 | Pyramid | Square base pyramid | +| 6 | Box | Rectangular parallelepiped | +| 7 | Sphere | Geodesic sphere approximation | + +*Top row:* Wireframe versions - edges only, no surfaces +*Bottom row:* Solid polygon versions - filled surfaces with lighting + +Additional features demonstrated: +- Dynamic lighting from 10 orbiting colored light sources (visible as small + glowing dots) +- Per-shape color assignment for visual distinction +- Grid floor showing spatial layout and alignment +- Text labels identifying each shape type +- Shading enabled on solid shapes to show lighting effects + +See the [[https://www3.svjatoslav.eu/projects/sixth-3d/#mesh][Mesh]] section in the Sixth 3D documentation +for an explanation of 3D mesh fundamentals and how shapes are constructed. + +** CSG demo +:PROPERTIES: +:CUSTOM_ID: csg-demo +:ID: 5e6f7a8b-9c0d-1234-ef01-345678901234 +:END: + +: eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo + +#+attr_html: :class responsive-img +#+attr_latex: :width 1000px +[[file:Screenshots/Essentials/CSG demo.png]] + +Constructive Solid Geometry (CSG) boolean operations on 3D meshes. Three +operations displayed left to right: + +1. *Subtract (Cube - Sphere)* - A red cube with a spherical cavity carved out + where a blue sphere intersects it. Shows the cube's interior surfaces. + +2. *Union (Cube + Sphere)* - A merged shape combining both volumes. Red + surfaces from the cube, blue surfaces from the sphere, seamlessly joined. + +3. *Intersect (Cube ∩ Sphere)* - Only the volume shared by both shapes + remains. Shows which parts of space were occupied by both the cube and + sphere simultaneously. + +Color scheme: +- *Red* = geometry from the first operand (cube) +- *Blue* = geometry from the second operand (sphere) + +Each operation includes a text description panel below the shape. The demo +uses: +- SolidPolygonCube (red) +- SolidPolygonSphere (blue) +- CSGSolid.subtract(), union(), intersect() methods +- toMesh() conversion for rendering +- Backface culling and shading enabled + +CSG is powerful for procedural modeling, allowing complex shapes to be built +from simple primitives through boolean combinations. + +* Advanced examples +:PROPERTIES: +:CUSTOM_ID: example-scenes +:ID: 5f88b493-6ab3-4659-8280-803f75dbd5e0 +:END: + + ** Conway's Game of Life :PROPERTIES: :CUSTOM_ID: conways-game-of-life :ID: 08914390-742b-4c78-88bf-602ab9640082 :END: +: eu.svjatoslav.sixth.e3d.examples.life_demo.Main + The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970. @@ -94,9 +351,9 @@ devised by the British mathematician John Horton Conway in 1970. [[file:Screenshots/Life.png]] -Current application projects 2D game grid/matrix onto three -dimensional space. Extra dimension (height) is used to visualize -history (previous iterations) using glowing dots suspended in space. +This demo projects the 2D game grid onto three-dimensional space. The extra +dimension (height) visualizes history (previous iterations) using glowing dots +suspended in space. Usage: | key | result | @@ -112,26 +369,26 @@ Usage: :ID: 39250157-db8e-4861-a21b-8568912bd160 :END: +: eu.svjatoslav.sixth.e3d.examples.TextEditorDemo + [[file:Screenshots/Text editors.png]] -Initial test for creating user interfaces in 3D and: +Initial test for creating user interfaces in 3D, demonstrating: + window focus handling -+ picking objects using mouse -+ redirecting keyboard input to focused window - ++ picking objects using the mouse ++ redirecting keyboard input to the focused window Window focus acts like a stack. -When window is clicked with the mouse, previously focused window (if -any) is pushed to the focus stack and new window receives focus. Red +When a window is clicked with the mouse, the previously focused window (if +any) is pushed to the focus stack and the new window receives focus. A red frame appears around the window to indicate this. -When ESC key is pressed, window focus is returned to previous window -(if any). +When the ESC key is pressed, focus is returned to the previous window (if any). -When any window is focused, all keyboard input is redirected to that -window, including cursor keys. To be able to navigate around the world -again, window must be unfocused first using ESC key. +When any window is focused, all keyboard input is redirected to that window, +including cursor keys. To navigate around the world again, the window must be +unfocused first by pressing the ESC key. + TODO: @@ -151,11 +408,13 @@ again, window must be unfocused first using ESC key. :ID: a7b8c9d0-e1f2-3456-0123-567890123456 :END: +: eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2 + *Quite a lot of text editors can be rendered:* [[file:Screenshots/Text editors 2.png]] -See also [[https://hackers-1995.vercel.app/][similar looking web based demo]] ! :) +See also this [[https://hackers-1995.vercel.app/][similar-looking web-based demo]]! ** Math graphs demo :PROPERTIES: @@ -163,6 +422,8 @@ See also [[https://hackers-1995.vercel.app/][similar looking web based demo]] ! :ID: b1c2d3e4-f5a6-7890-bcde-f12345678901 :END: +: eu.svjatoslav.sixth.e3d.examples.graph_demo.MathGraphsDemo + [[file:Screenshots/Mathematical formulas.png]] + TODO: instead of projecting 2D visualizations onto 3D space, @@ -174,6 +435,8 @@ See also [[https://hackers-1995.vercel.app/][similar looking web based demo]] ! :ID: b2c3d4e5-f6a7-8901-bcde-f12345678901 :END: +: eu.svjatoslav.sixth.e3d.examples.SineHeightmap + [[file:Screenshots/Sine heightmap and sphere.png]] Simple test scene. Easy to implement and looks nice. @@ -184,6 +447,8 @@ Simple test scene. Easy to implement and looks nice. :ID: c3d4e5f6-a7b8-9012-cdef-123456789012 :END: +: eu.svjatoslav.sixth.e3d.examples.OctreeDemo + [[file:Screenshots/Raytracing fractal in voxel polygon hybrid scene.png]] Test scene that is generated simultaneously using: @@ -192,31 +457,33 @@ Test scene that is generated simultaneously using: + voxels + for on-demand raytracing -Instead of storing voxels in dumb [X * Y * Z] array, dynamically -partitioned [[https://en.wikipedia.org/wiki/Octree][octree]] is used to compress data. Press "r" key anywhere in -the scene to raytrace current view through compressed voxel +Instead of storing voxels in a naive [X × Y × Z] array, a dynamically +partitioned [[https://en.wikipedia.org/wiki/Octree][octree]] compresses the data. Press the "r" key anywhere in +the scene to raytrace the current view through the compressed voxel data structure. -** Graphics Benchmark +* Graphics Benchmark :PROPERTIES: :CUSTOM_ID: graphics-benchmark :ID: e5f6a7b8-c9d0-1234-ef01-345678901234 :END: +: eu.svjatoslav.sixth.e3d.examples.benchmark.GraphicsBenchmark + An automated graphics benchmark that measures the engine's rendering performance across different rendering modes. [[file:Screenshots/Benchmark.png]] -The benchmark will cycle through different scenes that utilize different -rendering primitives (textured polygons, billboards, solid polygons, -etc.) to measure their relative performance. +The benchmark cycles through scenes that utilize different rendering +primitives (textured polygons, billboards, solid polygons, etc.) to measure +their relative performance. The camera follows a deterministic orbital path around the scene, ensuring reproducible results across runs. -At the end, the benchmark will output a report that is easy to preserve for -later comparisons. +Upon completion, the benchmark outputs a report suitable for preservation +and later comparisons. Example benchmark report: #+begin_example @@ -252,8 +519,8 @@ Star Grid 318.97 :ID: d4e5f6a7-b8c9-0123-def0-234567890123 :END: -*This program is free software: released under Creative Commons Zero -(CC0) license* +*This program is free software, released under the Creative Commons Zero +(CC0) license.* *Program author:* - Svjatoslav Agejenko @@ -268,5 +535,5 @@ Star Grid 318.97 : git clone https://www2.svjatoslav.eu/git/sixth-3d-demos.git *Understanding the Sixth 3D demos source code:* -- Read online [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/apidocs/][JavaDoc]]. -- Study underlying [[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D]] engine. +- Read the online [[https://www3.svjatoslav.eu/projects/sixth-3d-demos/apidocs/][JavaDoc]]. +- Study the underlying [[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D]] engine. diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java index a04c237..3b45854 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/ArrowDemo.java @@ -14,6 +14,9 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolyg import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCone; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; + /** * Demo showcasing the SolidPolygonArrow shape with various colors, sizes, * orientations, and transparency levels. @@ -34,13 +37,13 @@ public class ArrowDemo { final ShapeCollection shapes = viewPanel.getRootShapeCollection(); // Position camera to view the scene - viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, -200, -600)); + viewPanel.getCamera().getTransform().setTranslation(point(0, -200, -600)); // Add a 3D grid for spatial reference - final LineAppearance gridAppearance = new LineAppearance(1, new Color(100, 100, 100, 80)); + final LineAppearance gridAppearance = new LineAppearance(1, hex("64646450")); final Grid3D grid = new Grid3D( - new Point3D(-300, -200, -300), - new Point3D(300, 200, 300), + point(-300, -200, -300), + point(300, 200, 300), 100, gridAppearance ); @@ -50,49 +53,49 @@ public class ArrowDemo { // Red arrow pointing up (negative Y direction) final SolidPolygonArrow redArrow = new SolidPolygonArrow( - new Point3D(0, 150, 0), - new Point3D(0, -150, 0), + point(0, 150, 0), + point(0, -150, 0), 8, Color.RED ); shapes.addShape(redArrow); // Green arrow pointing along positive X final SolidPolygonArrow greenArrow = new SolidPolygonArrow( - new Point3D(-200, 0, 0), - new Point3D(0, 0, 0), + point(-200, 0, 0), + point(0, 0, 0), 6, Color.GREEN ); shapes.addShape(greenArrow); // Blue arrow pointing along positive Z final SolidPolygonArrow blueArrow = new SolidPolygonArrow( - new Point3D(0, 0, -200), - new Point3D(0, 0, 0), + point(0, 0, -200), + point(0, 0, 0), 6, Color.BLUE ); shapes.addShape(blueArrow); // Yellow arrow pointing diagonally final SolidPolygonArrow yellowArrow = new SolidPolygonArrow( - new Point3D(100, 100, 100), - new Point3D(300, -100, 300), + point(100, 100, 100), + point(300, -100, 300), 10, Color.YELLOW ); shapes.addShape(yellowArrow); // Semi-transparent cyan arrow (50% opacity) final SolidPolygonArrow transparentCyanArrow = new SolidPolygonArrow( - new Point3D(-150, 50, -100), - new Point3D(-50, -100, 100), - 8, new Color(0, 255, 255, 128) + point(-150, 50, -100), + point(-50, -100, 100), + 8, hex("00FFFF80") ); shapes.addShape(transparentCyanArrow); // Semi-transparent magenta arrow (25% opacity) final SolidPolygonArrow transparentMagentaArrow = new SolidPolygonArrow( - new Point3D(50, 200, 50), - new Point3D(50, 0, 50), - 12, new Color(255, 0, 255, 64) + point(50, 200, 50), + point(50, 0, 50), + 12, hex("FF00FF40") ); shapes.addShape(transparentMagentaArrow); @@ -105,21 +108,21 @@ public class ArrowDemo { final double endX = (radius + 80) * Math.cos(angle); final double endZ = (radius + 80) * Math.sin(angle); -final SolidPolygonArrow circleArrow = new SolidPolygonArrow( - new Point3D(startX, -80, startZ), - new Point3D(endX, -80, endZ), - 4, Color.WHITE - ); + final SolidPolygonArrow circleArrow = new SolidPolygonArrow( + point(startX, -80, startZ), + point(endX, -80, endZ), + 4, Color.WHITE + ); shapes.addShape(circleArrow); } // A standalone cone to demonstrate SolidPolygonCone final SolidPolygonCone standaloneCone = new SolidPolygonCone( - new Point3D(-300, 0, 0), + point(-300, 0, 0), 40, 80, 16, - new Color(255, 128, 0) + hex("FF8000FF") ); shapes.addShape(standaloneCone); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java index aedc08c..d4e1c9c 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/OctreeDemo.java @@ -28,6 +28,9 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D import java.awt.event.KeyEvent; import java.util.Vector; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; + /** * Demo showing volumetric octree rendering with raytracing capability. * Creates a 3D scene with various geometric shapes stored in an octree data structure. @@ -39,12 +42,12 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { * Scale factor for rendering octree voxels in the scene. */ private static final double magnification = 5; - private final LineAppearance gridAppearance = new LineAppearance(40, new Color(255, - 0, 0, 60)); + private final LineAppearance gridAppearance = new LineAppearance(40, hex("FF00003C")); private final Vector lights = new Vector<>(); private OctreeVolume octreeVolume; private ShapeCollection shapeCollection; private ViewPanel viewPanel; + /** * Creates a new OctreeDemo instance. */ @@ -70,13 +73,13 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { private void addLightToBothSystems(final Point3D location, final Color color, final float brightness) { shapeCollection.addShape(new LightSourceMarker(new Point3D(location) - .scaleUp(magnification), color)); + .multiply(magnification), color)); lights.add(new eu.svjatoslav.sixth.e3d.renderer.octree.raytracer.LightSource( location, color, brightness)); viewPanel.getLightingManager().addLight(new LightSource( - new Point3D(location).scaleUp(magnification), color, brightness * 50)); + new Point3D(location).multiply(magnification), color, brightness * 50)); } /** @@ -143,32 +146,32 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { shapeCollection = viewPanel.getRootShapeCollection(); shapeCollection.addShape(new Grid3D( - new Point3D(-10000, -10000, -10000), new Point3D(10000, 10000, + point(-10000, -10000, -10000), point(10000, 10000, 10000), 4000, gridAppearance)); - addLightToBothSystems(new Point3D(20, -450, 240), new Color(255, 255, 255), 100); - addLightToBothSystems(new Point3D(-150, -116, 141), new Color(255, 0, 0), 10); + addLightToBothSystems(point(20, -450, 240), new Color(255, 255, 255), 100); + addLightToBothSystems(point(-150, -116, 141), new Color(255, 0, 0), 10); dotSpiral(); // arbitrary rectangles putRect(new IntegerPoint(-10, -10, -10), new IntegerPoint(10, 10, -20), - new Color(200, 255, 200, 100)); + hex("C8FFC864")); putRect(new IntegerPoint(-3, 0, -30), new IntegerPoint(12, 3, 300), - new Color(255, 200, 200, 100)); + hex("FFC8C864")); putRect(new IntegerPoint(-20, 20, -20), new IntegerPoint(20, 80, 20), - new Color(255, 200, 255, 100)); + hex("FFC8FF64")); tiledFloor(); fractal(-50, 20, 100, 32, 1); - final TextCanvas message = new TextCanvas(new Transform(new Point3D( + final TextCanvas message = new TextCanvas(new Transform(point( -10, 20, -180)), "Press \"r\" to raytrace current view", Color.WHITE, Color.PURPLE); shapeCollection.addShape(message); @@ -204,8 +207,8 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { */ private void putPixel(final int x, final int y, final int z, final Color color) { - shapeCollection.addShape(new GlowingPoint(new Point3D(x, y, z) - .scaleUp(magnification), 3 * magnification, color)); + shapeCollection.addShape(new GlowingPoint(point(x, y, z) + .multiply(magnification), 3 * magnification, color)); octreeVolume.putCell(x, y, z, color); } @@ -219,8 +222,8 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { */ private void putRect(IntegerPoint p1, IntegerPoint p2, final Color color) { final SolidPolygonRectangularBox box = new SolidPolygonRectangularBox( - new Point3D(p1).scaleUp(magnification), - new Point3D(p2).scaleUp(magnification), color); + new Point3D(p1).multiply(magnification), + new Point3D(p2).multiply(magnification), color); box.setShadingEnabled(true); shapeCollection.addShape(box); octreeVolume.fillRectangle(p1, p2, color); @@ -247,7 +250,7 @@ public class OctreeDemo extends WorldNavigationUserInputTracker { private void tiledFloor() { final int step = 40; final int size = step - 15; - Color color = new Color(255, 255, 255, 100); + Color color = hex("FFFFFF64"); for (int x = -200; x < 200; x += step) for (int z = -200; z < 200; z += step) putRect( diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java index c2c82cd..bc6f6b8 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/RainingNumbersDemo.java @@ -12,6 +12,8 @@ import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.gui.FrameListener; import eu.svjatoslav.sixth.e3d.math.Transform; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; + +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractShape; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas; @@ -87,7 +89,7 @@ public class RainingNumbersDemo implements FrameListener { Random random = new Random(); for (int i = 0; i < NUMBERS_COUNT; i++) { - final Point3D location = new Point3D((Math.random() * AREA) + final Point3D location = point((Math.random() * AREA) - AREA_HALF, (Math.random() * AREA) - AREA_HALF, (Math.random() * AREA) - AREA_HALF); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java index 25642dd..64433da 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/SineHeightmap.java @@ -1,5 +1,5 @@ /* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. * This project is released under Creative Commons Zero (CC0) license. * */ @@ -14,49 +14,56 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.WireframeSphere; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; + /** * Demo showing a sine heightmap surface with a central wireframe sphere. * Two wobbly surfaces are positioned above and below the sphere. */ public class SineHeightmap { + /** + * Frequency of the wave pattern in the wobbly surfaces. + */ + private static final double WAVE_FREQUENCY = 50d; + /** + * Amplitude of the wave pattern in the wobbly surfaces. + */ + private static final double WAVE_AMPLITUDE = 50d; + /** + * Color for the square plates in the wobbly surfaces. + */ + private static final Color SQUARE_PLATE_COLOR = hex("88F7"); /** * Creates a new GraphDemo instance. */ public SineHeightmap() { } - /** Frequency of the wave pattern in the wobbly surfaces. */ - private static final double WAVE_FREQUENCY = 50d; - /** Amplitude of the wave pattern in the wobbly surfaces. */ - private static final double WAVE_AMPLITUDE = 50d; - /** Color for the square plates in the wobbly surfaces. */ - private static final Color SQUARE_PLATE_COLOR = new Color("88F7"); - /** Scale factor for the graph rendering. */ - private static final double GRAPH_SCALE = 50d; - /** * Creates a single square plate at the specified position. + * * @param shapeCollection the collection to add the plate to - * @param y the Y coordinate (elevation) - * @param x the X coordinate - * @param z the Z coordinate + * @param y the Y coordinate (elevation) + * @param x the X coordinate + * @param z the Z coordinate */ private static void makeSquarePlate(final ShapeCollection shapeCollection, final double y, final double x, final double z) { - final Point3D p1 = new Point3D(x, y, z); - final Point3D p2 = new Point3D(x + 20, y, z); - final Point3D p3 = new Point3D(x, y, z + 20); - final Point3D p4 = new Point3D(x + 20, y, z + 20); - final SolidPolygon polygon1 = new SolidPolygon(p1, p2, p3, SQUARE_PLATE_COLOR); - final SolidPolygon polygon2 = new SolidPolygon(p4, p2, p3, SQUARE_PLATE_COLOR); - shapeCollection.addShape(polygon1); - shapeCollection.addShape(polygon2); + final Point3D p1 = point(x, y, z); + final Point3D p2 = point(x + 20, y, z); + final Point3D p3 = point(x, y, z + 20); + final Point3D p4 = point(x + 20, y, z + 20); + final SolidPolygon quad = SolidPolygon.quad(p1, p2, p4, p3, SQUARE_PLATE_COLOR); + shapeCollection.addShape(quad); } /** * Creates a wobbly surface composed of square plates arranged in a wave pattern. - * @param shapeCollection the collection to add plates to + * + * @param shapeCollection the collection to add plates to * @param surfaceElevation the base Y elevation of the surface */ private static void addWobblySurface(final ShapeCollection shapeCollection, @@ -75,6 +82,7 @@ public class SineHeightmap { /** * Entry point for the graph demo. + * * @param args command line arguments (ignored) */ public static void main(final String[] args) { @@ -83,25 +91,26 @@ public class SineHeightmap { final ShapeCollection geometryCollection = viewFrame.getViewPanel() .getRootShapeCollection(); - viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, 0, -500)); + viewFrame.getViewPanel().getCamera().getTransform().setTranslation(point(0, 0, -500)); addSphere(geometryCollection); addWobblySurface(geometryCollection, 200); addWobblySurface(geometryCollection, -200); - + viewFrame.getViewPanel().repaintDuringNextViewUpdate(); } /** * Adds a wireframe sphere at the center of the scene. + * * @param geometryCollection the collection to add the sphere to */ private static void addSphere(ShapeCollection geometryCollection) { - geometryCollection.addShape(new WireframeSphere(new Point3D(0, 0, 0), + geometryCollection.addShape(new WireframeSphere(origin(), 100, new LineAppearance( 4, - new Color(255,0, 0, 30)) + hex("FF00001E")) )); } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java index 676c3fc..79fa5ad 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo.java @@ -1,24 +1,25 @@ /* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. * This project is released under Creative Commons Zero (CC0) license. * -*/ + */ package eu.svjatoslav.sixth.e3d.examples; import eu.svjatoslav.sixth.e3d.geometry.Point2D; -import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.geometry.Rectangle; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.LookAndFeel; import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextEditComponent; import eu.svjatoslav.sixth.e3d.math.Transform; -import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; + /** * Demo showing a grid of 3D text editor components. * Creates a 5x5 grid of text editors floating in 3D space with a decorative grid @@ -34,6 +35,7 @@ public class TextEditorDemo { /** * Entry point for the text editor demo. + * * @param args command line arguments (ignored) */ public static void main(final String[] args) { @@ -49,27 +51,28 @@ public class TextEditorDemo { addGrid(shapeCollection); addTextEditors(viewPanel, shapeCollection); - + viewFrame.getViewPanel().repaintDuringNextViewUpdate(); } /** * Adds a decorative grid below the text editors. + * * @param shapeCollection the collection to add the grid to */ private static void addGrid(ShapeCollection shapeCollection) { final Transform transform = Transform.fromAngles(0, 100, 0, 0, Math.PI / 2, 0); final Rectangle rectangle = new Rectangle(2000); - final LineAppearance appearance = new LineAppearance(10, new Color( - "00b3ad")); + final LineAppearance appearance = new LineAppearance(10, hex("00b3ad")); shapeCollection.addShape(new Grid2D(transform, rectangle, 10, 10, appearance)); } /** * Creates a grid of text editor components arranged in 3D space. - * @param viewPanel the view panel for the text editors + * + * @param viewPanel the view panel for the text editors * @param shapeCollection the collection to add editors to */ private static void addTextEditors(ViewPanel viewPanel, ShapeCollection shapeCollection) { @@ -78,7 +81,7 @@ public class TextEditorDemo { for (double x = -500 * m; x <= (500 * m); x += 250 * m) { final TextEditComponent textEditor = new TextEditComponent( - new Transform(new Point3D(x, 0, z)), viewPanel, + new Transform(point(x, 0, z)), viewPanel, new Point2D(200, 120), new LookAndFeel()); shapeCollection.addShape(textEditor); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java index 28b0246..ed38515 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/TextEditorDemo2.java @@ -1,20 +1,18 @@ /* - * Sixth 3D engine demos. Author: Svjatoslav Agejenko. + * Sixth 3D engine demos. Author: Svjatoslav Agejenko. * This project is released under Creative Commons Zero (CC0) license. * -*/ + */ package eu.svjatoslav.sixth.e3d.examples; import eu.svjatoslav.sixth.e3d.geometry.Point2D; -import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.geometry.Rectangle; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.LookAndFeel; import eu.svjatoslav.sixth.e3d.gui.textEditorComponent.TextEditComponent; import eu.svjatoslav.sixth.e3d.math.Transform; -import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid2D; @@ -26,7 +24,9 @@ import java.io.InputStreamReader; import java.net.URISyntaxException; import java.util.stream.Collectors; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; import static java.lang.Math.PI; /** @@ -45,6 +45,7 @@ public class TextEditorDemo2 { /** * Entry point for the text editor city demo. + * * @param args command line arguments (ignored) */ public static void main(final String[] args) { @@ -57,10 +58,43 @@ public class TextEditorDemo2 { } } + /** + * Adds a grid to the scene for visual reference. + * + * @param shapeCollection the collection to add the grid to + */ + private static void addGrid(ShapeCollection shapeCollection) { + final Transform transform = fromAngles(0, 100, 0, 0, PI / 2, 0); + + final Rectangle rectangle = new Rectangle(10000); + final LineAppearance appearance = new LineAppearance(10, hex("00b3ad")); + + shapeCollection.addShape(new Grid2D(transform, rectangle, 50, 50, appearance)); + } + + /** + * Reads a resource file as a string. + * + * @param fileName the name of the resource file + * @return the file contents as a string, or null if not found + * @throws IOException if an I/O error occurs + */ + static String getResourceFileAsString(String fileName) throws IOException { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + try (InputStream is = classLoader.getResourceAsStream(fileName)) { + if (is == null) return null; + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + } + /** * Builds and displays the 3D city scene with text editors. + * * @throws URISyntaxException if resource URI is malformed - * @throws IOException if demo text file cannot be read + * @throws IOException if demo text file cannot be read */ public void build() throws URISyntaxException, IOException { final ViewFrame viewFrame = new ViewFrame("Text Editors City"); @@ -74,66 +108,53 @@ public class TextEditorDemo2 { addGrid(shapeCollection); addCity(viewPanel, shapeCollection); - + viewFrame.getViewPanel().repaintDuringNextViewUpdate(); } /** * Creates a grid of buildings in the city. - * @param viewPanel the view panel for the text editors + * + * @param viewPanel the view panel for the text editors * @param shapeCollection the collection to add buildings to * @throws URISyntaxException if resource URI is malformed - * @throws IOException if demo text file cannot be read + * @throws IOException if demo text file cannot be read */ private void addCity(ViewPanel viewPanel, ShapeCollection shapeCollection) throws URISyntaxException, IOException { int citySize = 4000; - for (int z = -citySize; z< citySize; z+= 1000 ){ - for (int x = -citySize; x< citySize; x+= 1000 ){ + for (int z = -citySize; z < citySize; z += 1000) { + for (int x = -citySize; x < citySize; x += 1000) { addBuilding(viewPanel, shapeCollection, x, z); } } } - - /** - * Adds a grid to the scene for visual reference. - * @param shapeCollection the collection to add the grid to - */ - private static void addGrid(ShapeCollection shapeCollection) { - final Transform transform = fromAngles(0, 100, 0, 0, PI / 2, 0); - - final Rectangle rectangle = new Rectangle(10000); - final LineAppearance appearance = new LineAppearance(10, new Color( - "00b3ad")); - - shapeCollection.addShape(new Grid2D(transform, rectangle, 50, 50, appearance)); - } - - /** * Adds a single building with four text editor panels facing different directions. - * @param viewPanel the view panel for the text editors + * + * @param viewPanel the view panel for the text editors * @param shapeCollection the collection to add the building to - * @param x the X coordinate of the building center - * @param z the Z coordinate of the building center + * @param x the X coordinate of the building center + * @param z the Z coordinate of the building center * @throws URISyntaxException if resource URI is malformed - * @throws IOException if demo text file cannot be read + * @throws IOException if demo text file cannot be read */ private void addBuilding(ViewPanel viewPanel, ShapeCollection shapeCollection, double x, double z) throws URISyntaxException, IOException { - addTextEditor(viewPanel, shapeCollection, new Transform(new Point3D(x, -390, z-200))); + addTextEditor(viewPanel, shapeCollection, new Transform(point(x, -390, z - 200))); - addTextEditor(viewPanel, shapeCollection, fromAngles(x, -390, z+200, PI, 0, 0)); + addTextEditor(viewPanel, shapeCollection, fromAngles(x, -390, z + 200, PI, 0, 0)); - addTextEditor(viewPanel, shapeCollection, fromAngles(x-200, -390, z, PI/2, 0, 0)); + addTextEditor(viewPanel, shapeCollection, fromAngles(x - 200, -390, z, PI / 2, 0, 0)); - addTextEditor(viewPanel, shapeCollection, fromAngles(x+200, -390, z, PI/2*3f, 0, 0)); + addTextEditor(viewPanel, shapeCollection, fromAngles(x + 200, -390, z, PI / 2 * 3f, 0, 0)); } /** * Adds a single text editor component at the specified transform. - * @param viewPanel the view panel for the text editor + * + * @param viewPanel the view panel for the text editor * @param shapeCollection the collection to add the editor to - * @param transform the position and orientation of the editor + * @param transform the position and orientation of the editor * @throws IOException if demo text file cannot be read */ private void addTextEditor(ViewPanel viewPanel, ShapeCollection shapeCollection, Transform transform) throws IOException { @@ -144,42 +165,26 @@ public class TextEditorDemo2 { viewPanel, new Point2D(400, 1000), lookAndFeel - ); + ); String text = getResourceFileAsString("demo.txt"); textEditor.setText(text); - textEditor.goToLine((int)(Math.random()*200f)); + textEditor.goToLine((int) (Math.random() * 200f)); shapeCollection.addShape(textEditor); } /** * Creates the look and feel for text editors with a dark theme. + * * @return a LookAndFeel configured with dark blue background and light text */ private LookAndFeel getLookAndFeel() { LookAndFeel lookAndFeel = new LookAndFeel(); - lookAndFeel.background = new Color(20, 20, 50, 150); + lookAndFeel.background = hex("141E3296"); lookAndFeel.tabStopBackground = lookAndFeel.background; - lookAndFeel.foreground = new Color(150, 150, 255,250); + lookAndFeel.foreground = hex("9696FFFA"); return lookAndFeel; } - /** - * Reads a resource file as a string. - * @param fileName the name of the resource file - * @return the file contents as a string, or null if not found - * @throws IOException if an I/O error occurs - */ - static String getResourceFileAsString(String fileName) throws IOException { - ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - try (InputStream is = classLoader.getResourceAsStream(fileName)) { - if (is == null) return null; - try (InputStreamReader isr = new InputStreamReader(is); - BufferedReader reader = new BufferedReader(isr)) { - return reader.lines().collect(Collectors.joining(System.lineSeparator())); - } - } - } - } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java index ecb883e..f4ddb33 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/benchmark/TexturedCube.java @@ -8,7 +8,7 @@ package eu.svjatoslav.sixth.e3d.examples.benchmark; import eu.svjatoslav.sixth.e3d.geometry.Point2D; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.math.Vertex; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedTriangle; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape; import eu.svjatoslav.sixth.e3d.renderer.raster.texture.Texture; @@ -54,7 +54,7 @@ public class TexturedCube extends AbstractCompositeShape { private void addTexturedFace(Point3D p1, Point3D p2, Point3D p3, Point3D p4, Point2D t1, Point2D t2, Point2D t3, Point2D t4, Texture texture) { - TexturedPolygon tri1 = new TexturedPolygon( + TexturedTriangle tri1 = new TexturedTriangle( new Vertex(p1, t1), new Vertex(p2, t2), new Vertex(p3, t3), @@ -62,7 +62,7 @@ public class TexturedCube extends AbstractCompositeShape { ); tri1.setBackfaceCulling(true); - TexturedPolygon tri2 = new TexturedPolygon( + TexturedTriangle tri2 = new TexturedTriangle( new Vertex(p1, t1), new Vertex(p3, t3), new Vertex(p4, t4), diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java index b8d2cda..6169fc0 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CSGDemo.java @@ -5,17 +5,21 @@ */ package eu.svjatoslav.sixth.e3d.examples.essentials; -import eu.svjatoslav.sixth.e3d.csg.CSG; import eu.svjatoslav.sixth.e3d.geometry.Point3D; +import eu.svjatoslav.sixth.e3d.gui.TextPointer; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.gui.ViewPanel; +import eu.svjatoslav.sixth.e3d.math.Transform; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedTextBlock; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonCube; -import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonMesh; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonSphere; +import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas; + +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; /** * Demo showcasing Constructive Solid Geometry (CSG) boolean operations. @@ -27,40 +31,35 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolyg *

  • Intersect: The volume shared by a cube and sphere
  • * * - *

    Shapes are rendered with slight transparency (85% opacity) to allow - * seeing internal structure. The demo demonstrates how existing composite - * shapes like {@link SolidPolygonCube} and {@link SolidPolygonSphere} can - * be used with CSG operations.

    + *

    All operations use a consistent color scheme: red for the first argument + * (cube) and blue for the second argument (sphere). This makes it easy to see + * which parts of the result came from which input shape.

    * *

    Run this demo:

    *
    {@code
    - * java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.CSGDemo
    + * java -cp target/sixth-3d-demos.jar eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo
      * }
    * - * @see CSG the CSG solid class - * @see SolidPolygonMesh the renderable mesh + * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape#union + * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape#subtract + * @see eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape#intersect */ public class CSGDemo { /** - * Distance between shapes on the X axis. - */ - private static final double SPACING = 500; - - /** - * Size of the cube (half-edge length). + * Color for the first argument in boolean operations (cube). */ - private static final double CUBE_SIZE = 80; + private static final Color COLOR_FIRST = hex("FF6464D9"); /** - * Radius of the sphere (slightly larger for interesting intersection). + * Color for the second argument in boolean operations (sphere). */ - private static final double SPHERE_RADIUS = 96; + private static final Color COLOR_SECOND = hex("6496FFD9"); /** - * Number of segments for the sphere (smoothness). + * Distance between shapes on the X axis. */ - private static final int SPHERE_SEGMENTS = 12; + private static final double SPACING = 400; /** * Entry point for the CSG demo. @@ -68,26 +67,23 @@ public class CSGDemo { * @param args command line arguments (ignored) */ public static void main(final String[] args) { - final ViewFrame viewFrame = new ViewFrame("CSG Demo - Boolean Operations"); + final ViewFrame viewFrame = new ViewFrame("CSG demo - Boolean operations"); final ViewPanel viewPanel = viewFrame.getViewPanel(); final ShapeCollection shapes = viewPanel.getRootShapeCollection(); // Position camera to view all three shapes - viewPanel.getCamera().getTransform().set(0, -150, -600, 0, 0, 0); + viewPanel.getCamera().getTransform().set(-244.24, 254.40, -458.83, -0.26, 0.24, -0.00); // Set up lighting - viewPanel.getLightingManager().setAmbientLight(new Color(60, 60, 70)); + viewPanel.getLightingManager().setAmbientLight(hex("3C3C46")); // Create lights createLights(viewPanel, shapes); // Create the three CSG demonstrations - createSubtractDemo(shapes, -SPACING, 0, 0); - createUnionDemo(shapes, 0, 0, 0); - createIntersectDemo(shapes, SPACING, 0, 0); - - // Add labels - createLabels(shapes); + createSubtractDemo(shapes, point(-SPACING, 0, 0)); + createUnionDemo(shapes, origin()); + createIntersectDemo(shapes, point(SPACING, 0, 0)); viewPanel.repaintDuringNextViewUpdate(); } @@ -96,118 +92,100 @@ public class CSGDemo { * Creates the subtract operation demo: cube - sphere. * Results in a cube with a spherical cavity. * - * @param shapes the shape collection - * @param x the X position - * @param y the Y position - * @param z the Z position + * @param shapes the shape collection + * @param location the position for the result */ - private static void createSubtractDemo(final ShapeCollection shapes, - final double x, final double y, final double z) { - final SolidPolygonCube cube = new SolidPolygonCube( - new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE); - final SolidPolygonSphere sphere = new SolidPolygonSphere( - new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE); - - final CSG cubeCSG = CSG.fromCompositeShape(cube); - final CSG sphereCSG = CSG.fromCompositeShape(sphere); + private static void createSubtractDemo(final ShapeCollection shapes, final Point3D location) { - final CSG result = cubeCSG.subtract(sphereCSG); + final SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, COLOR_FIRST); + cube.subtract(new SolidPolygonSphere(origin(), 96, 12, COLOR_SECOND)); - final SolidPolygonMesh mesh = result.toMesh(new Color(255, 100, 100, 217), new Point3D(x, y, z)); - mesh.setShadingEnabled(true); - mesh.setBackfaceCulling(true); + shapes.addShape(cube + .setTransform(new Transform(location)) + .setShadingEnabled(true) + .setBackfaceCulling(true)); - shapes.addShape(mesh); + final String description = + "Subtract: Cube - Sphere\n" + + "\n" + + "Red = Cube (kept)\n" + + "Blue = Sphere (carved out)"; - System.out.println("Subtract (cube - sphere): " + mesh.getTriangleCount() + " triangles"); + shapes.addShape(createDescriptionPanel(location, description)); } /** * Creates the union operation demo: cube + sphere. * Results in a combined shape. * - * @param shapes the shape collection - * @param x the X position - * @param y the Y position - * @param z the Z position + * @param shapes the shape collection + * @param location the position for the result */ - private static void createUnionDemo(final ShapeCollection shapes, - final double x, final double y, final double z) { - final SolidPolygonCube cube = new SolidPolygonCube( - new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE); - final SolidPolygonSphere sphere = new SolidPolygonSphere( - new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE); + private static void createUnionDemo(final ShapeCollection shapes, final Point3D location) { - final CSG cubeCSG = CSG.fromCompositeShape(cube); - final CSG sphereCSG = CSG.fromCompositeShape(sphere); + final SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, COLOR_FIRST); + cube.union(new SolidPolygonSphere(origin(), 96, 12, COLOR_SECOND)); - final CSG result = cubeCSG.union(sphereCSG); + shapes.addShape(cube + .setTransform(new Transform(location)) + .setShadingEnabled(true) + .setBackfaceCulling(true)); - final SolidPolygonMesh mesh = result.toMesh(new Color(100, 255, 100, 217), new Point3D(x, y, z)); - mesh.setShadingEnabled(true); - mesh.setBackfaceCulling(true); + final String description = + "Union: Cube + Sphere\n" + + "\n" + + "Red = Cube\n" + + "Blue = Sphere"; - shapes.addShape(mesh); - - System.out.println("Union (cube + sphere): " + mesh.getTriangleCount() + " triangles"); + shapes.addShape(createDescriptionPanel(location, description)); } /** * Creates the intersect operation demo: cube ∩ sphere. * Results in the volume shared by both shapes. * - * @param shapes the shape collection - * @param x the X position - * @param y the Y position - * @param z the Z position + * @param shapes the shape collection + * @param location the position for the result */ - private static void createIntersectDemo(final ShapeCollection shapes, - final double x, final double y, final double z) { - final SolidPolygonCube cube = new SolidPolygonCube( - new Point3D(0, 0, 0), CUBE_SIZE, Color.WHITE); - final SolidPolygonSphere sphere = new SolidPolygonSphere( - new Point3D(0, 0, 0), SPHERE_RADIUS, SPHERE_SEGMENTS, Color.WHITE); - - final CSG cubeCSG = CSG.fromCompositeShape(cube); - final CSG sphereCSG = CSG.fromCompositeShape(sphere); + private static void createIntersectDemo(final ShapeCollection shapes, final Point3D location) { - final CSG result = cubeCSG.intersect(sphereCSG); + final SolidPolygonCube cube = new SolidPolygonCube(origin(), 80, COLOR_FIRST); + cube.intersect(new SolidPolygonSphere(origin(), 96, 12, COLOR_SECOND)); - final SolidPolygonMesh mesh = result.toMesh(new Color(100, 150, 255, 217), new Point3D(x, y, z)); - mesh.setShadingEnabled(true); - mesh.setBackfaceCulling(true); + shapes.addShape(cube + .setTransform(new Transform(location)) + .setShadingEnabled(true) + .setBackfaceCulling(true)); - shapes.addShape(mesh); + final String description = + "Intersect: Cube ∩ Sphere\n" + + "\n" + + "Red = from Cube\n" + + "Blue = from Sphere"; - System.out.println("Intersect (cube ∩ sphere): " + mesh.getTriangleCount() + " triangles"); + shapes.addShape(createDescriptionPanel(location, description)); } /** - * Creates labels for each operation. + * Creates a description panel positioned below a shape. * - * @param shapes the shape collection + * @param location the position of the associated shape + * @param text the description text to display + * @return a TextCanvas positioned below the shape */ - private static void createLabels(final ShapeCollection shapes) { - final double labelY = -150; - final double labelZ = 0; - - // Subtract label - shapes.addShape(new ForwardOrientedTextBlock( - new Point3D(-SPACING, labelY, labelZ), - 8.0, 2, "Subtract", Color.RED - )); - - // Union label - shapes.addShape(new ForwardOrientedTextBlock( - new Point3D(0, labelY, labelZ), - 8.0, 2, "Union", Color.GREEN - )); - - // Intersect label - shapes.addShape(new ForwardOrientedTextBlock( - new Point3D(SPACING, labelY, labelZ), - 8.0, 2, "Intersect", Color.BLUE - )); + private static TextCanvas createDescriptionPanel(final Point3D location, final String text) { + final Transform transform = Transform.fromAngles( + point(location.x, location.y + 220, location.z), + 0, 0); + + final TextCanvas panel = new TextCanvas( + transform, + new TextPointer(5, 35), + Color.WHITE, + new Color(0, 0, 40, 180)); + + panel.setText(text); + return panel; } /** @@ -219,16 +197,16 @@ public class CSGDemo { private static void createLights(final ViewPanel viewPanel, final ShapeCollection shapes) { // Main light from above-front final LightSource mainLight = new LightSource( - new Point3D(0, -300, -400), - new Color(255, 255, 255), + point(0, -300, -400), + hex("FFFFFF"), 1.5 ); viewPanel.getLightingManager().addLight(mainLight); // Fill light from the side final LightSource fillLight = new LightSource( - new Point3D(500, 100, -200), - new Color(150, 150, 200), + point(500, 100, -200), + hex("9696C8"), 0.8 ); viewPanel.getLightingManager().addLight(fillLight); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CoordinateSystemDemo.java similarity index 80% rename from src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java rename to src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CoordinateSystemDemo.java index 8bcd0a0..dafd300 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/AxisArrowsDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/CoordinateSystemDemo.java @@ -14,6 +14,10 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.ForwardOrientedT import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonArrow; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; + /** * Demo displaying coordinate system axes using 3D arrows. * @@ -33,7 +37,7 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.wireframe.Grid3D * @see SolidPolygonArrow * @see ForwardOrientedTextBlock */ -public class AxisArrowsDemo { +public class CoordinateSystemDemo { /** * Length of each axis arrow. @@ -56,12 +60,12 @@ public class AxisArrowsDemo { private static final double LABEL_SCALE = 20.0; /** - * Entry point for the axis arrows demo. + * Entry point for the coordinate system demo. * * @param args command line arguments (ignored) */ public static void main(final String[] args) { - final ViewFrame viewFrame = new ViewFrame("Axis Arrows Demo"); + final ViewFrame viewFrame = new ViewFrame("Coordinate System Demo"); final ViewPanel viewPanel = viewFrame.getViewPanel(); final ShapeCollection shapes = viewPanel.getRootShapeCollection(); @@ -84,10 +88,10 @@ public class AxisArrowsDemo { */ private static void createReferenceGrid(final ShapeCollection shapes) { final LineAppearance gridAppearance = new LineAppearance( - 1, new Color(80, 80, 100)); + 1, hex("505064FF")); final Grid3D grid = new Grid3D( - new Point3D(-300, 0, -300), - new Point3D(300, 0, 300), + point(-300, 0, -300), + point(300, 0, 300), 50, gridAppearance ); @@ -102,30 +106,30 @@ public class AxisArrowsDemo { private static void createAxisArrows(final ShapeCollection shapes) { // X-axis arrow (RED) - points in positive X direction (RIGHT) final SolidPolygonArrow xArrow = new SolidPolygonArrow( - new Point3D(0, 0, 0), - new Point3D(ARROW_LENGTH, 0, 0), - BODY_RADIUS, new Color(255, 0, 0, 180) + origin(), + point(ARROW_LENGTH, 0, 0), + BODY_RADIUS, hex("FF0000B4") ); shapes.addShape(xArrow); - createLabel(shapes, "X", new Point3D(ARROW_LENGTH + LABEL_OFFSET, 0, 0), Color.RED); + createLabel(shapes, "X", point(ARROW_LENGTH + LABEL_OFFSET, 0, 0), Color.RED); // Y-axis arrow (GREEN) - points in positive Y direction (DOWN) final SolidPolygonArrow yArrow = new SolidPolygonArrow( - new Point3D(0, 0, 0), - new Point3D(0, ARROW_LENGTH, 0), - BODY_RADIUS, new Color(0, 255, 0, 180) + origin(), + point(0, ARROW_LENGTH, 0), + BODY_RADIUS, hex("00FF00B4") ); shapes.addShape(yArrow); - createLabel(shapes, "Y", new Point3D(0, ARROW_LENGTH + LABEL_OFFSET, 0), Color.GREEN); + createLabel(shapes, "Y", point(0, ARROW_LENGTH + LABEL_OFFSET, 0), Color.GREEN); // Z-axis arrow (BLUE) - points in positive Z direction (AWAY) final SolidPolygonArrow zArrow = new SolidPolygonArrow( - new Point3D(0, 0, 0), - new Point3D(0, 0, ARROW_LENGTH), - BODY_RADIUS, new Color(0, 0, 255, 180) + origin(), + point(0, 0, ARROW_LENGTH), + BODY_RADIUS, hex("0000FFB4") ); shapes.addShape(zArrow); - createLabel(shapes, "Z", new Point3D(0, 0, ARROW_LENGTH + LABEL_OFFSET), Color.BLUE); + createLabel(shapes, "Z", point(0, 0, ARROW_LENGTH + LABEL_OFFSET), Color.BLUE); } /** diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java index b526eb3..aa4347c 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/MinimalExample.java @@ -5,13 +5,14 @@ */ package eu.svjatoslav.sixth.e3d.examples.essentials; -import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.math.Transform; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.solid.SolidPolygonRectangularBox; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; + /** * Minimal example demonstrating how to create a basic 3D scene. *

    @@ -29,12 +30,12 @@ public class MinimalExample { ViewFrame viewFrame = new ViewFrame("Minimal example"); ShapeCollection shapes = viewFrame.getViewPanel().getRootShapeCollection(); - viewFrame.getViewPanel().getCamera().getTransform().setTranslation(new Point3D(0, -100, -300)); + viewFrame.getViewPanel().getCamera().getTransform().setTranslation(point(0, -100, -300)); - Transform boxTransform = Transform.fromAngles(0, 0, 0, 0, 0, 0); + final Transform boxTransform = new Transform(); SolidPolygonRectangularBox box = new SolidPolygonRectangularBox( - new Point3D(-50, -50, -50), - new Point3D(50, 50, 50), + point(-50, -50, -50), + point(50, 50, 50), Color.RED ); box.setTransform(boxTransform); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java index 28e6337..9efa14b 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/ShapeGalleryDemo.java @@ -17,7 +17,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; import static eu.svjatoslav.sixth.e3d.math.Transform.fromAngles; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; /** * Gallery demo showcasing all available 3D shapes in the Sixth 3D engine. @@ -69,13 +71,13 @@ public class ShapeGalleryDemo { * Color palette - one color per column (shape type). */ private static final Color[] SHAPE_COLORS = { - new Color(255, 100, 100), // Arrow - Red - new Color(255, 180, 100), // Cone - Orange - new Color(255, 255, 100), // Cube - Yellow - new Color(100, 255, 100), // Cylinder - Green - new Color(100, 255, 255), // Pyramid - Cyan - new Color(100, 150, 255), // RectangularBox - Blue - new Color(200, 150, 255), // Sphere - Purple + hex("FF6464FF"), // Arrow - Red + hex("FFB464FF"), // Cone - Orange + hex("FFFF64FF"), // Cube - Yellow + hex("64FF64FF"), // Cylinder - Green + hex("64FFFFFF"), // Pyramid - Cyan + hex("6496FFFF"), // RectangularBox - Blue + hex("C896FFFF"), // Sphere - Purple }; /** @@ -105,7 +107,7 @@ public class ShapeGalleryDemo { viewPanel.getCamera().getTransform().set(-615.31, -299.50, -378.64, -0.62, -0.66, 0.00); // Set up lighting - viewPanel.getLightingManager().setAmbientLight(new Color(40, 40, 50)); + viewPanel.getLightingManager().setAmbientLight(hex("282832FF")); // Create floor grid createFloorGrid(shapes); @@ -125,14 +127,14 @@ public class ShapeGalleryDemo { * @param shapes the shape collection to add the grid to */ private static void createFloorGrid(final ShapeCollection shapes) { - final LineAppearance gridAppearance = new LineAppearance(1, new Color(80, 80, 100)); + final LineAppearance gridAppearance = new LineAppearance(1, hex("505064FF")); // Calculate grid bounds: 7 columns, 2 rows, centered around origin final double halfWidth = (COLUMN_COUNT * CELL_SIZE) / 2.0; final double gridDepth = 2 * CELL_SIZE; - final Point3D cornerA = new Point3D(-halfWidth, 0, -CELL_SIZE / 2); - final Point3D cornerB = new Point3D(halfWidth, 0, gridDepth - CELL_SIZE / 2); + final Point3D cornerA = point(-halfWidth, 0, -CELL_SIZE / 2); + final Point3D cornerB = point(halfWidth, 0, gridDepth - CELL_SIZE / 2); final Grid3D grid = new Grid3D(cornerA, cornerB, CELL_SIZE, gridAppearance); shapes.addShape(grid); @@ -150,12 +152,12 @@ public class ShapeGalleryDemo { final String name = SHAPE_NAMES[col]; // Row 0: Solid polygon shapes - final Point3D solidPos = new Point3D(x, 0, 0); + final Point3D solidPos = point(x, 0, 0); createSolidShape(shapes, col, solidPos, color); createLabel(shapes, solidPos, name, color); // Row 1: Wireframe shapes - final Point3D wireframePos = new Point3D(x, 0, CELL_SIZE); + final Point3D wireframePos = point(x, 0, CELL_SIZE); createWireframeShape(shapes, col, wireframePos, color); createLabel(shapes, wireframePos, "Wireframe " + name, color); } @@ -184,8 +186,8 @@ public class ShapeGalleryDemo { switch (col) { case 0: // Arrow final SolidPolygonArrow arrow = new SolidPolygonArrow( - new Point3D(pos.x, SHAPE_RADIUS, pos.z), - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + point(pos.x, SHAPE_RADIUS, pos.z), + point(pos.x, -SHAPE_RADIUS, pos.z), 8, color); arrow.setShadingEnabled(true); shapes.addShape(arrow); @@ -193,8 +195,8 @@ public class ShapeGalleryDemo { case 1: // Cone (pointing upward) final SolidPolygonCone cone = new SolidPolygonCone( - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), // apex above - new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below + point(pos.x, -SHAPE_RADIUS, pos.z), // apex above + point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below SHAPE_RADIUS * 0.8, SEGMENTS, color); cone.setShadingEnabled(true); shapes.addShape(cone); @@ -208,8 +210,8 @@ public class ShapeGalleryDemo { case 3: // Cylinder final SolidPolygonCylinder cylinder = new SolidPolygonCylinder( - new Point3D(pos.x, SHAPE_RADIUS, pos.z), - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + point(pos.x, SHAPE_RADIUS, pos.z), + point(pos.x, -SHAPE_RADIUS, pos.z), SHAPE_RADIUS * 0.7, SEGMENTS, color); cylinder.setShadingEnabled(true); shapes.addShape(cylinder); @@ -217,8 +219,8 @@ public class ShapeGalleryDemo { case 4: // Pyramid final SolidPolygonPyramid pyramid = new SolidPolygonPyramid( - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), // apex above - new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below + point(pos.x, -SHAPE_RADIUS, pos.z), // apex above + point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below SHAPE_RADIUS * 0.8, color); pyramid.setShadingEnabled(true); shapes.addShape(pyramid); @@ -226,8 +228,8 @@ public class ShapeGalleryDemo { case 5: // RectangularBox final SolidPolygonRectangularBox box = new SolidPolygonRectangularBox( - new Point3D(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35), - new Point3D(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35), + point(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35), + point(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35), color); box.setShadingEnabled(true); shapes.addShape(box); @@ -259,16 +261,16 @@ public class ShapeGalleryDemo { switch (col) { case 0: // Arrow final WireframeArrow arrow = new WireframeArrow( - new Point3D(pos.x, SHAPE_RADIUS, pos.z), - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + point(pos.x, SHAPE_RADIUS, pos.z), + point(pos.x, -SHAPE_RADIUS, pos.z), 8, appearance); shapes.addShape(arrow); break; case 1: // Cone final WireframeCone cone = new WireframeCone( - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), // apex above - new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below + point(pos.x, -SHAPE_RADIUS, pos.z), // apex above + point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below SHAPE_RADIUS * 0.8, SEGMENTS, appearance); shapes.addShape(cone); break; @@ -280,24 +282,24 @@ public class ShapeGalleryDemo { case 3: // Cylinder final WireframeCylinder cylinder = new WireframeCylinder( - new Point3D(pos.x, SHAPE_RADIUS, pos.z), - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), + point(pos.x, SHAPE_RADIUS, pos.z), + point(pos.x, -SHAPE_RADIUS, pos.z), SHAPE_RADIUS * 0.7, SEGMENTS, appearance); shapes.addShape(cylinder); break; case 4: // Pyramid final WireframePyramid pyramid = new WireframePyramid( - new Point3D(pos.x, -SHAPE_RADIUS, pos.z), // apex above - new Point3D(pos.x, SHAPE_RADIUS / 2, pos.z), // base below + point(pos.x, -SHAPE_RADIUS, pos.z), // apex above + point(pos.x, SHAPE_RADIUS / 2, pos.z), // base below SHAPE_RADIUS * 0.8, appearance); shapes.addShape(pyramid); break; case 5: // Box (WireframeBox) final WireframeBox box = new WireframeBox( - new Point3D(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35), - new Point3D(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35), + point(pos.x - SHAPE_RADIUS * 0.35, pos.y - SHAPE_RADIUS, pos.z - SHAPE_RADIUS * 0.35), + point(pos.x + SHAPE_RADIUS * 0.35, pos.y + SHAPE_RADIUS, pos.z + SHAPE_RADIUS * 0.35), appearance); shapes.addShape(box); break; @@ -322,7 +324,7 @@ public class ShapeGalleryDemo { */ private static void createLabel(final ShapeCollection shapes, final Point3D pos, final String text, final Color color) { - final Point3D labelPos = new Point3D(pos.x, pos.y + LABEL_Y_OFFSET, pos.z); + final Point3D labelPos = point(pos.x, pos.y + LABEL_Y_OFFSET, pos.z); final ForwardOrientedTextBlock label = new ForwardOrientedTextBlock( labelPos, 8.0, 2, text, color); shapes.addShape(label); @@ -353,7 +355,7 @@ public class ShapeGalleryDemo { final int axis = random.nextInt(3); final double ellipseFactor = 0.7 + random.nextDouble() * 0.3; - final LightSource light = new LightSource(new Point3D(0, 0, CELL_SIZE / 2), color, intensity); + final LightSource light = new LightSource(point(0, 0, CELL_SIZE / 2), color, intensity); final LightSourceMarker marker = new LightSourceMarker(light.getPosition(), color); viewPanel.getLightingManager().addLight(light); @@ -428,7 +430,7 @@ public class ShapeGalleryDemo { break; } - final Point3D newPosition = new Point3D(x, y + centerY, z); + final Point3D newPosition = point(x, y + centerY, z); orbitingLight.light.setPosition(newPosition); orbitingLight.marker.setTransform(fromAngles( newPosition.x, newPosition.y, newPosition.z, 0, 0, 0)); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java index 250fc62..fceba7b 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/essentials/WindingOrderDemo.java @@ -12,6 +12,8 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.Color; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; + /** * Demo to test winding order and backface culling documentation. *

    @@ -36,17 +38,17 @@ public class WindingOrderDemo { * @param args command line arguments (ignored) */ public static void main(String[] args) { - ViewFrame viewFrame = new ViewFrame("Winding Order Demo"); + ViewFrame viewFrame = new ViewFrame("Winding order demo"); ViewPanel viewPanel = viewFrame.getViewPanel(); ShapeCollection shapes = viewPanel.getRootShapeCollection(); - viewPanel.getCamera().getTransform().setTranslation(new Point3D(0, 0, -500)); + viewPanel.getCamera().getTransform().setTranslation(point(0, 0, -500)); double size = 150; - Point3D upperCenter = new Point3D(0, -size, 0); - Point3D lowerLeft = new Point3D(-size, +size, 0); - Point3D lowerRight = new Point3D(+size, +size, 0); + Point3D upperCenter = point(0, -size, 0); + Point3D lowerLeft = point(-size, +size, 0); + Point3D lowerRight = point(+size, +size, 0); SolidPolygon triangle = new SolidPolygon(upperCenter, lowerLeft, lowerRight, Color.GREEN); triangle.setBackfaceCulling(true); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java index 1f28002..508fb7c 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/MathGraphsDemo.java @@ -10,6 +10,9 @@ import eu.svjatoslav.sixth.e3d.geometry.Point2D; import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; + +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.Graph; @@ -163,22 +166,22 @@ public class MathGraphsDemo { */ private static void addMathFormulas(ShapeCollection geometryCollection) { int z = 1000; - Point3D location = new Point3D(-600, -300, z); + Point3D location = point(-600, -300, z); geometryCollection.addShape(getSineGraph(location)); - location = new Point3D(600, -300, z); + location = point(600, -300, z); geometryCollection.addShape(getFormula1Graph(location)); - location = new Point3D(-600, 0, z); + location = point(-600, 0, z); geometryCollection.addShape(getCosineGraph(location)); - location = new Point3D(600, 0, z); + location = point(600, 0, z); geometryCollection.addShape(getFormula2Graph(location)); - location = new Point3D(-600, 300, z); + location = point(-600, 300, z); geometryCollection.addShape(getTangentGraph(location)); - location = new Point3D(600, 300, z); + location = point(600, 300, z); geometryCollection.addShape(getFormula3Graph(location)); } @@ -196,7 +199,7 @@ public class MathGraphsDemo { new Color(150, 150, 150, 180), Color.WHITE, scale, - new Point3D(0, 0, 0) + origin() ); surface.addYIntersectionCurve(4, function, new Color(255, 255, 0, 220), 1.2); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java index 661a854..c4507d3 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/graph_demo/SurfaceGraph3D.java @@ -122,13 +122,9 @@ public class SurfaceGraph3D extends AbstractCompositeShape { } private void addQuad(final Point3D p1, final Point3D p2, final Point3D p3, final Point3D p4) { - final SolidPolygon poly1 = new SolidPolygon(p1, p2, p3, surfaceColor); - poly1.setBackfaceCulling(false); - addShape(poly1); - - final SolidPolygon poly2 = new SolidPolygon(p2, p4, p3, surfaceColor); - poly2.setBackfaceCulling(false); - addShape(poly2); + final SolidPolygon quad = SolidPolygon.quad(p1, p2, p4, p3, surfaceColor); + quad.setBackfaceCulling(false); + addShape(quad); } private void addGridLines(final Point3D p1, final Point3D p2, final Point3D p3, final Point3D p4) { diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java index 86cda8a..550e60d 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/launcher/ApplicationListPanel.java @@ -11,7 +11,7 @@ import eu.svjatoslav.sixth.e3d.examples.RainingNumbersDemo; import eu.svjatoslav.sixth.e3d.examples.SineHeightmap; import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo; import eu.svjatoslav.sixth.e3d.examples.TextEditorDemo2; -import eu.svjatoslav.sixth.e3d.examples.essentials.AxisArrowsDemo; +import eu.svjatoslav.sixth.e3d.examples.essentials.CoordinateSystemDemo; import eu.svjatoslav.sixth.e3d.examples.essentials.CSGDemo; import eu.svjatoslav.sixth.e3d.examples.essentials.MinimalExample; import eu.svjatoslav.sixth.e3d.examples.essentials.ShapeGalleryDemo; @@ -44,13 +44,13 @@ class ApplicationListPanel extends JPanel { new DemoEntry("Winding order", "Triangle demo for winding order & backface culling", new ShowWindingOrder()), - new DemoEntry("Axis arrows", + new DemoEntry("Coordinate system", "Coordinate system axes: X (red), Y (green), Z (blue)", - new ShowAxisArrows()), + new ShowCoordinateSystem()), new DemoEntry("Shape gallery", "All 3D shapes with orbiting colored light sources", new ShowShadedShapes()), - new DemoEntry("CSG Demo", + new DemoEntry("CSG demo", "Boolean operations: union, subtract, intersect on 3D shapes", new ShowCSG()), }; @@ -174,7 +174,7 @@ class ApplicationListPanel extends JPanel { private static class ShowMinimalExample extends AbstractAction { ShowMinimalExample() { - putValue(NAME, "Minimal Example"); + putValue(NAME, "Minimal example"); } @Override @@ -185,7 +185,7 @@ class ApplicationListPanel extends JPanel { private static class ShowWindingOrder extends AbstractAction { ShowWindingOrder() { - putValue(NAME, "Winding Order"); + putValue(NAME, "Winding order"); } @Override @@ -293,14 +293,14 @@ class ApplicationListPanel extends JPanel { } } - private static class ShowAxisArrows extends AbstractAction { - ShowAxisArrows() { - putValue(NAME, "Axis arrows"); + private static class ShowCoordinateSystem extends AbstractAction { + ShowCoordinateSystem() { + putValue(NAME, "Coordinate system"); } @Override public void actionPerformed(final ActionEvent e) { - AxisArrowsDemo.main(null); + CoordinateSystemDemo.main(null); } } @@ -328,7 +328,7 @@ class ApplicationListPanel extends JPanel { private static class ShowCSG extends AbstractAction { ShowCSG() { - putValue(NAME, "CSG Demo"); + putValue(NAME, "CSG demo"); } @Override diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java index 80622ff..c9be293 100755 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Cell.java @@ -15,14 +15,16 @@ import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base.AbstractCom class Cell extends AbstractCompositeShape implements MouseInteractionController { - /** Visual size of each cell in world units. */ + /** + * Visual size of each cell in world units. + */ static final int SIZE = 20; /** * Color of the active cell (R, G, B, A) */ private static final Color ACTIVE_COLOR = new Color("A8FF"); /** - * Color of the active cell (R, G, B, A) while mouse is over it. + * Color of the active cell (R, G, B, A) while the mouse is over it. */ private static final Color ACTIVE_COLOR_MOUSE_OVER = new Color("F9FF"); /** @@ -49,6 +51,7 @@ class Cell extends AbstractCompositeShape implements /** * Constructs a cell at the specified center position. + * * @param center the 3D position of the cell center */ public Cell(final Point3D center) { @@ -60,7 +63,9 @@ class Cell extends AbstractCompositeShape implements setMouseInteractionController(this); } - /** Creates the visual representation of the cell as two triangles. */ + /** + * Creates the visual representation of the cell as two triangles. + */ private void createCellShape() { final double halfSize = SIZE / 2f; @@ -80,6 +85,7 @@ class Cell extends AbstractCompositeShape implements /** * Computes the cell color based on active state and mouse hover. + * * @return the appropriate color for the current state */ private Color computeCellColor() { @@ -96,6 +102,7 @@ class Cell extends AbstractCompositeShape implements /** * Returns whether this cell is currently active (alive). + * * @return true if the cell is active */ public boolean isActive() { @@ -104,6 +111,7 @@ class Cell extends AbstractCompositeShape implements /** * Sets the active state of this cell and updates its color. + * * @param active true to make the cell active, false for inactive */ public void setActive(final boolean active) { @@ -113,6 +121,7 @@ class Cell extends AbstractCompositeShape implements /** * Handles mouse click by toggling the cell state. + * * @param button the mouse button that was clicked * @return true to indicate the event was consumed */ @@ -124,6 +133,7 @@ class Cell extends AbstractCompositeShape implements /** * Handles mouse entering the cell area by highlighting it. + * * @return true to indicate the event was consumed */ @Override @@ -134,6 +144,7 @@ class Cell extends AbstractCompositeShape implements /** * Handles mouse exiting the cell area by removing highlight. + * * @return true to indicate the event was consumed */ @Override @@ -144,6 +155,7 @@ class Cell extends AbstractCompositeShape implements /** * Sets the mouse-over state and updates the cell color. + * * @param isMouseOver true if mouse is over the cell */ private void setMouseOver(final boolean isMouseOver) { @@ -151,7 +163,9 @@ class Cell extends AbstractCompositeShape implements updateColor(); } - /** Updates the cell's visual color to match its current state. */ + /** + * Updates the cell's visual color to match its current state. + */ private void updateColor() { setColor(computeCellColor()); } diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java index 0e77299..d60dad7 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/life_demo/Main.java @@ -8,6 +8,10 @@ import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.gui.humaninput.WorldNavigationUserInputTracker; import eu.svjatoslav.sixth.e3d.math.Transform; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; + +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.LineAppearance; import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.textcanvas.TextCanvas; @@ -34,7 +38,7 @@ public class Main extends WorldNavigationUserInputTracker { * The game of life matrix, centered at the origin. */ private static final Matrix MATRIX = new Matrix( - new Point3D() // position matrix in the center of the scene + origin() // position matrix in the center of the scene ); /** @@ -116,7 +120,7 @@ public class Main extends WorldNavigationUserInputTracker { 5, 5, new LineAppearance(3, - new Color("FF000050") + hex("FF000050") ) ); } @@ -147,7 +151,7 @@ public class Main extends WorldNavigationUserInputTracker { "Hover - Highlight cell"; final Transform location = Transform.fromAngles( - new Point3D(500, -80, 0), + point(500, -80, 0), -Math.PI / 2, 0 ); @@ -158,7 +162,7 @@ public class Main extends WorldNavigationUserInputTracker { location, dimensions, Color.WHITE, - new Color(0, 0, 80, 200) + hex("000050C8") ); panel.setText(helpText); diff --git a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java index 1ad9d43..1bf03ec 100644 --- a/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java +++ b/src/main/java/eu/svjatoslav/sixth/e3d/examples/terrain_demo/TerrainDemo.java @@ -9,6 +9,10 @@ import eu.svjatoslav.sixth.e3d.geometry.Point3D; import eu.svjatoslav.sixth.e3d.gui.ViewFrame; import eu.svjatoslav.sixth.e3d.gui.ViewPanel; import eu.svjatoslav.sixth.e3d.renderer.raster.Color; + +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.origin; +import static eu.svjatoslav.sixth.e3d.geometry.Point3D.point; +import static eu.svjatoslav.sixth.e3d.renderer.raster.Color.hex; import eu.svjatoslav.sixth.e3d.renderer.raster.ShapeCollection; import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightSource; import eu.svjatoslav.sixth.e3d.renderer.raster.lighting.LightingManager; @@ -41,7 +45,7 @@ public class TerrainDemo { // Get terrain height at center and place tree trunk on it final double terrainHeight = terrain.getHeightAt(0, 0); - shapes.addShape(new FractalTree(new Point3D(0, terrainHeight, 0), 30, 6)); + shapes.addShape(new FractalTree(point(0, terrainHeight, 0), 30, 6)); viewPanel.repaintDuringNextViewUpdate(); } @@ -54,21 +58,21 @@ public class TerrainDemo { */ private static void setLights(ViewFrame viewFrame, ShapeCollection shapes) { LightingManager lightingManager = viewFrame.getViewPanel().getLightingManager(); - lightingManager.setAmbientLight(new Color(250, 150, 100)); + lightingManager.setAmbientLight(hex("FA9664FF")); final LightSource warmLight = new LightSource( - new Point3D(-400, -500, 0), - new Color(255, 180, 100), + point(-400, -500, 0), + hex("FFB464FF"), 190.0 ); final LightSource coolLight = new LightSource( - new Point3D(400, -500, 0), - new Color(100, 200, 255), + point(400, -500, 0), + hex("64C8FFFF"), 220.0 ); final LightSource neutralLight = new LightSource( - new Point3D(0, -600, 300), - new Color(255, 255, 255), + point(0, -600, 300), + hex("FFFFFFFF"), 250.0 ); @@ -76,8 +80,8 @@ public class TerrainDemo { lightingManager.addLight(coolLight); lightingManager.addLight(neutralLight); - shapes.addShape(new LightSourceMarker(warmLight.getPosition(), new Color(255, 180, 100))); - shapes.addShape(new LightSourceMarker(coolLight.getPosition(), new Color(100, 200, 255))); - shapes.addShape(new LightSourceMarker(neutralLight.getPosition(), new Color(255, 255, 255))); + shapes.addShape(new LightSourceMarker(warmLight.getPosition(), hex("FFB464FF"))); + shapes.addShape(new LightSourceMarker(coolLight.getPosition(), hex("64C8FFFF"))); + shapes.addShape(new LightSourceMarker(neutralLight.getPosition(), hex("FFFFFFFF"))); } } \ No newline at end of file -- 2.20.1