From 47487afc04ea8ebc7335c99d0cf1869e881b34a0 Mon Sep 17 00:00:00 2001 From: aap Date: Wed, 3 Mar 2021 16:43:20 +0100 Subject: [PATCH] subrast example --- premake5.lua | 8 + skeleton/skeleton.cpp | 4 + src/charset.cpp | 8 + src/d3d/d3d.cpp | 2 +- src/d3d/d3ddevice.cpp | 36 +- src/gl/gl3device.cpp | 102 ++-- src/raster.cpp | 4 +- tools/subrast/files/clump.dff | Bin 0 -> 76384 bytes tools/subrast/files/textures/whiteash.png | Bin 0 -> 4313 bytes tools/subrast/main.cpp | 352 +++++++++++++ tools/subrast/subrast.cpp | 592 ++++++++++++++++++++++ tools/subrast/subrast.h | 10 + 12 files changed, 1065 insertions(+), 53 deletions(-) create mode 100644 tools/subrast/files/clump.dff create mode 100644 tools/subrast/files/textures/whiteash.png create mode 100644 tools/subrast/main.cpp create mode 100644 tools/subrast/subrast.cpp create mode 100644 tools/subrast/subrast.h diff --git a/premake5.lua b/premake5.lua index 484e407..141432e 100755 --- a/premake5.lua +++ b/premake5.lua @@ -209,6 +209,14 @@ project "lights" removeplatforms { "*null" } removeplatforms { "ps2" } +project "subrast" + kind "WindowedApp" + characterset ("MBCS") + skeltool("subrast") + flags { "WinMain" } + removeplatforms { "*null" } + removeplatforms { "ps2" } + project "ska2anm" kind "ConsoleApp" characterset ("MBCS") diff --git a/skeleton/skeleton.cpp b/skeleton/skeleton.cpp index 740a3ab..3defebe 100644 --- a/skeleton/skeleton.cpp +++ b/skeleton/skeleton.cpp @@ -47,6 +47,8 @@ InitRW(void) if(!rw::Engine::start()) return false; + rw::Charset::open(); + rw::Image::setSearchPath("./"); return true; } @@ -54,6 +56,8 @@ InitRW(void) void TerminateRW(void) { + rw::Charset::close(); + // TODO: delete all tex dicts rw::Engine::stop(); rw::Engine::close(); diff --git a/src/charset.cpp b/src/charset.cpp index 0d94be8..5188b80 100644 --- a/src/charset.cpp +++ b/src/charset.cpp @@ -128,8 +128,16 @@ Charset::flushBuffer(void) rw::SetRenderState(rw::TEXTUREADDRESS, rw::Texture::WRAP); rw::SetRenderState(rw::TEXTUREFILTER, rw::Texture::NEAREST); + uint32 cull = rw::GetRenderState(rw::CULLMODE); + uint32 ztest = rw::GetRenderState(rw::ZTESTENABLE); + rw::SetRenderState(rw::CULLMODE, rw::CULLNONE); + rw::SetRenderState(rw::ZTESTENABLE, 0); + im2d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, vertices, numChars*4, indices, numChars*6); + + rw::SetRenderState(rw::CULLMODE, cull); + rw::SetRenderState(rw::ZTESTENABLE, ztest); } numChars = 0; diff --git a/src/d3d/d3d.cpp b/src/d3d/d3d.cpp index 55608a5..f1f5f49 100644 --- a/src/d3d/d3d.cpp +++ b/src/d3d/d3d.cpp @@ -18,7 +18,7 @@ namespace rw { namespace d3d { -bool32 isP8supported = 1; +bool32 isP8supported = 1; // set to 0 when actual d3d device is used // stolen from d3d8to9 static uint32 diff --git a/src/d3d/d3ddevice.cpp b/src/d3d/d3ddevice.cpp index 922530c..1f39f18 100644 --- a/src/d3d/d3ddevice.cpp +++ b/src/d3d/d3ddevice.cpp @@ -944,6 +944,9 @@ setRenderSurfaces(Camera *cam) Raster *fbuf = cam->frameBuffer; assert(fbuf); { + if(fbuf->parent) + fbuf = fbuf->parent; + D3dRaster *natras = GETD3DRASTEREXT(fbuf); assert(fbuf->type == Raster::CAMERA || fbuf->type == Raster::CAMERATEXTURE); if(natras->texture == nil) @@ -959,6 +962,9 @@ setRenderSurfaces(Camera *cam) Raster *zbuf = cam->zBuffer; if(zbuf){ + if(zbuf->parent) + zbuf = zbuf->parent; + D3dRaster *natras = GETD3DRASTEREXT(zbuf); assert(zbuf->type == Raster::ZBUFFER); setDepthSurface(natras->texture); @@ -967,6 +973,19 @@ setRenderSurfaces(Camera *cam) } +static void +setViewport(Raster *fb) +{ + D3DVIEWPORT9 vp; + vp.MinZ = 0.0f; + vp.MaxZ = 1.0f; + vp.X = fb->offsetX; + vp.Y = fb->offsetY; + vp.Width = fb->width; + vp.Height = fb->height; + d3ddevice->SetViewport(&vp); +} + static void beginUpdate(Camera *cam) { @@ -1046,23 +1065,14 @@ beginUpdate(Camera *cam) setRenderSurfaces(cam); - D3DVIEWPORT9 vp; - vp.MinZ = 0.0f; - vp.MaxZ = 1.0f; - vp.X = cam->frameBuffer->offsetX; - vp.Y = cam->frameBuffer->offsetY; - vp.Width = cam->frameBuffer->width; - vp.Height = cam->frameBuffer->height; - d3ddevice->SetViewport(&vp); + setViewport(cam->frameBuffer); - // TODO: figure out when to call this d3ddevice->BeginScene(); } static void endUpdate(Camera *cam) { - // TODO: figure out when to call this d3ddevice->EndScene(); } @@ -1314,7 +1324,6 @@ clearCamera(Camera *cam, RGBA *col, uint32 mode) RECT r; GetClientRect(d3d9Globals.window, &r); BOOL icon = IsIconic(d3d9Globals.window); - Raster *ras = cam->frameBuffer; if(!icon && (r.right != d3d9Globals.present.BackBufferWidth || r.bottom != d3d9Globals.present.BackBufferHeight)){ @@ -1328,7 +1337,8 @@ clearCamera(Camera *cam, RGBA *col, uint32 mode) setRenderSurfaces(cam); - d3ddevice->Clear(0, 0, mode, c, 1.0f, 0); + setViewport(cam->frameBuffer); // need to set this for the clear to work correctly + d3ddevice->Clear(0, nil, mode, c, 1.0f, 0); } static void @@ -1604,6 +1614,8 @@ startD3D(void) // d3d9Globals.present.PresentationInterval = D3DPRESENT_INTERVAL_ONE; d3d9Globals.present.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + rw::d3d::isP8supported = 0; + assert(d3d::d3ddevice == nil); BOOL icon = IsIconic(d3d9Globals.window); diff --git a/src/gl/gl3device.cpp b/src/gl/gl3device.cpp index 90ac299..e9ea6f3 100644 --- a/src/gl/gl3device.cpp +++ b/src/gl/gl3device.cpp @@ -1225,6 +1225,50 @@ setFrameBuffer(Camera *cam) } } +static Rect +getFramebufferRect(Raster *frameBuffer) +{ + Rect r; + Raster *fb = frameBuffer->parent; + if(fb->type == Raster::CAMERA){ +#ifdef LIBRW_SDL2 + SDL_GetWindowSize(glGlobals.window, &r.w, &r.h); +#else + glfwGetFramebufferSize(glGlobals.window, &r.w, &r.h); +#endif + }else{ + r.w = fb->width; + r.h = fb->height; + } + r.x = 0; + r.y = 0; + + // Got a subraster + if(frameBuffer != fb){ + r.x = frameBuffer->offsetX; + // GL y offset is from bottom + r.y = r.h - frameBuffer->height - frameBuffer->offsetY; + r.w = frameBuffer->width; + r.h = frameBuffer->height; + } + + return r; +} + +static void +setViewport(Raster *frameBuffer) +{ + Rect r = getFramebufferRect(frameBuffer); + if(r.w != glGlobals.presentWidth || r.h != glGlobals.presentHeight || + r.x != glGlobals.presentOffX || r.y != glGlobals.presentOffY){ + glViewport(r.x, r.y, r.w, r.h); + glGlobals.presentWidth = r.w; + glGlobals.presentHeight = r.h; + glGlobals.presentOffX = r.x; + glGlobals.presentOffY = r.y; + } +} + static void beginUpdate(Camera *cam) { @@ -1279,10 +1323,10 @@ beginUpdate(Camera *cam) proj[14] = -2.0f*cam->nearPlane*cam->farPlane*invz; proj[15] = 0.0f; }else{ - proj[10] = -(cam->farPlane+cam->nearPlane)*invz; + proj[10] = 2.0f*invz; proj[11] = 0.0f; - proj[14] = 2.0f*invz; + proj[14] = -(cam->farPlane+cam->nearPlane)*invz; proj[15] = 1.0f; } memcpy(&cam->devProj, &proj, sizeof(RawMatrix)); @@ -1301,39 +1345,7 @@ beginUpdate(Camera *cam) setFrameBuffer(cam); - int w, h; - int x, y; - Raster *fb = cam->frameBuffer->parent; - if(fb->type == Raster::CAMERA){ -#ifdef LIBRW_SDL2 - SDL_GetWindowSize(glGlobals.window, &w, &h); -#else - glfwGetFramebufferSize(glGlobals.window, &w, &h); -#endif - }else{ - w = fb->width; - h = fb->height; - } - x = 0; - y = 0; - - // Got a subraster - if(cam->frameBuffer != fb){ - x = cam->frameBuffer->offsetX; - // GL y offset is from bottom - y = h - cam->frameBuffer->height - cam->frameBuffer->offsetY; - w = cam->frameBuffer->width; - h = cam->frameBuffer->height; - } - - if(w != glGlobals.presentWidth || h != glGlobals.presentHeight || - x != glGlobals.presentOffX || y != glGlobals.presentOffY){ - glViewport(x, y, w, h); - glGlobals.presentWidth = w; - glGlobals.presentHeight = h; - glGlobals.presentOffX = x; - glGlobals.presentOffY = y; - } + setViewport(cam->frameBuffer); } static void @@ -1349,6 +1361,15 @@ clearCamera(Camera *cam, RGBA *col, uint32 mode) setFrameBuffer(cam); + // make sure we're only clearing the part of the framebuffer + // that is subrastered + bool setScissor = cam->frameBuffer != cam->frameBuffer->parent; + if(setScissor){ + Rect r = getFramebufferRect(cam->frameBuffer); + glScissor(r.x, r.y, r.w, r.h); + glEnable(GL_SCISSOR_TEST); + } + convColor(&colf, col); glClearColor(colf.red, colf.green, colf.blue, colf.alpha); mask = 0; @@ -1361,12 +1382,17 @@ clearCamera(Camera *cam, RGBA *col, uint32 mode) glDepthMask(GL_TRUE); glClear(mask); glDepthMask(rwStateCache.zwrite); + + if(setScissor) + glDisable(GL_SCISSOR_TEST); } static void showRaster(Raster *raster, uint32 flags) { - // TODO: do this properly! +// glViewport(raster->offsetX, raster->offsetY, +// raster->width, raster->height); + #ifdef LIBRW_SDL2 if(flags & Raster::FLIPWAITVSYNCH) SDL_GL_SetSwapInterval(1); @@ -1746,8 +1772,8 @@ initOpenGL(void) // printf("%d %s\n", i, ext); } */ - gl3Caps.dxtSupported = GLAD_GL_EXT_texture_compression_s3tc; - gl3Caps.astcSupported = GLAD_GL_KHR_texture_compression_astc_ldr; + gl3Caps.dxtSupported = !!GLAD_GL_EXT_texture_compression_s3tc; + gl3Caps.astcSupported = !!GLAD_GL_KHR_texture_compression_astc_ldr; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gl3Caps.maxAnisotropy); diff --git a/src/raster.cpp b/src/raster.cpp index fb654ef..c7ebffe 100644 --- a/src/raster.cpp +++ b/src/raster.cpp @@ -87,8 +87,8 @@ Raster::subRaster(Raster *parent, Rect *r) return; this->width = r->w; this->height = r->h; - this->offsetX += r->x; - this->offsetY += r->y; + this->offsetX = parent->offsetX + r->x; + this->offsetY = parent->offsetY + r->y; this->parent = parent->parent; } diff --git a/tools/subrast/files/clump.dff b/tools/subrast/files/clump.dff new file mode 100644 index 0000000000000000000000000000000000000000..b65e41d9e09839f7bfc5a0f503d01cc79c87b7e2 GIT binary patch literal 76384 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ϖU;e2Fpl6B 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 U$=d1h5%bB}Qy(Ay|E+<40qFcqmjD0& literal 0 HcmV?d00001 diff --git a/tools/subrast/files/textures/whiteash.png b/tools/subrast/files/textures/whiteash.png new file mode 100644 index 0000000000000000000000000000000000000000..aa60569fc77cc36567c7259094ab2fcc07b6517d GIT binary patch literal 4313 zcmds4i91y97gv;Bsg#hSge1m3c2mTd&@{HOBx8x2C0EFVWRe&WW2pwAAu{-4>_bsg zmIh%g*$pCFS<3#q{TsjgocFw+d*Ab(^ZA_Tz0Y&+Q!9(Bf_!3p92^{iW~Nur&}{f` z@NhytqbhP9nz#eat{HQ8aq=GKQ*Z|+TA@e0{-*XI2M0gq-`GdpixA@A;N-AEVT@@6 z9GwUz@wQ2EQ5$+5YRL{l7UAu31}7( zO9K&fJd%b-(}@5b!UQyf1TqL%Isr$;V;Lko9YnH7I65B9fP(Q@DgdVf2qpnR1JG0u z@{l1JP$3dPe;z_3U?~8W0wSp(ngT6>ND7ExKzRsA7F3RarQ(rP9GpP}nGkaVmPP>R zcpMdq2at3kuEq~e$3qF>EFwS!u~Zzy2gv}@EHXgGAt?Z4a3&egB!hHlBLSp>I3}b8 zgybNY|ENOp0V<@F2+{x;1*b;=u`Ciu#Y3tADh@`0QV}7xI5G~wBBHaL;0!#D1`zpmg1QiRX14tUA1%$c{V?tH%aH_N72pJrqfJ`!OgoI`N z^Uy7RganL`0TvkoxEV5paV!d+4$&hZMu=Dz1!UooGh}cE0wnwhg)l?G&p^#5AXp^y z2(*g=FhM91Jwie=$Yl!RGPyQSLe0u5s2eB-z%b97smbn z4i5x3>()HpCm78;PUL-KEA^wwG6(H-T)Oeflkd7yLIXl`BB8ouSV|W--Dhb+N#R#PSkr`%PpZ>>8zAa>|2^(?JE=QcfW5UE}5KqBAB9mNT@^T ziD%g(3DxPC=Jg$g*~h}t7}MI86FWiEBZBf}-wIuNAKDYNeu(I|&(^QhK99(EV`po@ z^>wz3v-s=&Zba#pe8O7kZ}y`u&V=~unQ5rzxQ`E`DylBW#mTvgXniPDe<2tE!}P!| zvt@Bl_NXJJV;`zEQ1Jiy!)~3=!>@kY3%R2n^>eJ@r$m+&jXfJvZ67IGXcG2(=Aep* z^>BC)KAMi}E&Sa4phQ&g^mua9!-Q28_NXt77OR$KgD-hx$Yfss=S zj_t?V(<@T!i`>r`@U1^PVYt<{H5V_u)z;k6mXjpxQM1{%@Ivkq>~P}!$TJac7f-Q& z|0$jI4~te1yHPdB6-1LDUANU0RXVrDOA109LzJ?79kPZy{ z`szH1WNpyyWRB&_gwC;`!pRYu0;_SF>injMEN13Bx)aj!JeE zwfF*;0xXI;-Dx)B|81;Y&PfQF>TYHSbG@R+ywx#YT9vxnmVO-AyBm7qkl@#~vJcIE z=N^pb)ge5}TIVji#}^*E$U2YNvkt!6XnF99KrBgtWF zDe3S62gek`GubUp;{s~D=|dvTFX6bVRA6Yi#x-GwlLLfipTKUu)i?P|>mMJw{dE#$ zZ-QKZBzC{+n{^wmc98C-o$wGqz);HnQD5;Vs3JSP9`pa_wDnPlz^fUNJRQa6G5Of% zd6RE!@ciZxlWEQ#jp)21ESNO_d`>N?`d)@*6ki^ImmbZIkeEOwRmU}YUL8Fb<&yo^ zKZKMxx8J}bX075Cdnd@jx`{`0TJz`*#xx_cO|H~o*f3nA_f25`m#(_`i;azIODF+u z_>nQyQ!E9^6gN|z_!Oc~_hQNTsJ=#^@1=v7v0QOIb1Xt;q4$XLp`uA{*`2<&5PuCV z$z%3ub3bOEukDVz{FQKDe5Rt~U7MQ8b2<2$_-O3`<|GWRuOZQ)e$=i$TkzoP0L_if z54U!8N*8Uu@8Cy@$|k}^M{)5!BI|49z5J?yQ856{CBOUJ%cE;Yyma0KmZ z&~G<(RZ;8|zQmXc*B5*$J1Zi?QX?l+|TAlIO=!2bHO`c%pc+Sl?`Q6#7`W$zb zyQS;y=#R_0!j=TJCOd(~R*HYH{K*UPLN+Du`t_CX3FM2XhF`qGv6A&6ZThEV|J8-l zCamGy$=vnVV#K02LN&j$&fW!Cf`yfvKuY2_n%p4Qv(I$jW;h}dRz86=4eGC#5npCa z-gq7n=_`{L_0VIOPegX&O0Vnrp5z9VSMDE@i%%4)e-C2uzI@v1-gnMUP+xC(Gb(3+ zs{H(@)CH?sCyUGY`Y7o+^OCADMi!({4d;8Bm&M%dOZI7J>wG%JUCmr7^VK=2($UW5 zASf$#NGViLfzSVJ+2Js3)2!<$ze2O^y*&&<;XLcrZWX{nUf_YDRbKAOOb2`N(p1)r z;rq({sGdPjzi&Ye2T&_dF9oK{%YFT}P_sN0I_$Z0fNNEuz`AVTmpq4Y4pB>`7d(fP zSRXVzb!6MaUaTetzNiKJu>ijH382OE{?C2S*mRc!b3hGoGin5x5_ z_MdkqMKn!0%$lw_GYq%P_8SaXHzB5y8xst)<@j8J+VMoIm%Ys{!<;9Vw@u;bk+#hCt4d5;09b{g-6_E(qlyMEGtyL~FJ(ubi&v+t>8 zmzbh#{pXe7F`}6G=}g`fkC|=mnLpd8S_5RRfnYVhcd@ZPH35%64_P%Z^Ie;PW1{%*5h;7wrB&D{zEdQz% zn>l)u+KWMEq~~7ROQQ7dMxkfC`aM?c54 zWk>)cVO>;Z$iwx^x%A?s8(bm_tv__g-p;RbKcC!#hDn|4$FAoq&(86{uB}jx$uy+| zmT}3&+}OiOXKlODI8NpqEUo?R4XEVu?JbvGmJev!pQbu_+PzL`Fvh*1r-Cf~)@Ye< zENVzH!g29uG&ASB*SVF$*NfhKg~z2_YIvTPM^e3qzBzrL{!&{&Wc+*tAzwFF^!BVY zqVB*YrzVb%zLx7Tv2VKM-j(F(7Z((-8yzrGsrR|LFJVMC14VAJUPdawuCKM~CbK zdn(CoC{-d~?J9^V&mST7f>*lc-en|3B>zVDY%4SpUw4dZP@Wz*?zdJC_}a}B*S2Ho zl?QoP%H}C9M><3g){g~h7YwG>j5&tpc9(hOTP0{0JS#q1hvB<}-1u|9p2sAQ!jy9_ zwZ4yxed@7QCu1g511kszJ2Xhs)$MtnOw0ZvOh_H{#q*A*EBK9&{766)@6VU)o1#~9 zy#-ZvO;A9qNlDk)H=|D!BOXZGYJ-D!7$YueHBO()EgLol_ghEW^9On3T*B8=pW2Q6 zJuhPg26szd^VRag8Oa*n{-~rVKNC@!Rc5+a64`Ow7xjDjcW#DrTH0f0;fFWn>upl~ zwC>3)C8{yJwk}W^C7Q9fQ3nM0kzw1tzu>86f<9kreYEFJq!;8&*tmlBQRQvt!{uf> zrY8x8!e}L`6`%d!4`oZ|fMDH0u5|DI3W%phAA zOm@ee^fy*elT)YKsdf>|Hn$zk0FR7KvhM!apdm$7rs^o!hyd8|Bqdw|6px&&|)q*vcq}h{ZD)P=P6O>N@6k%|_Al zeM+RNv=X#fUiQ#gIor9l{4(~tFM7Mzq9O5c<_<@K)1{8FG0t| zH2#{pv{%`kL1WcN14CVnni{c&*Cp|&l=17GY|5l?uL-J+b#2a+z|I@5bw26%H5l_K qO8Ir}F +#include +#include + +#include "subrast.h" + +rw::V3d zero = { 0.0f, 0.0f, 0.0f }; +rw::EngineOpenParams engineOpenParams; +float FOV = 70.0f; + +rw::RGBA ForegroundColor = { 200, 200, 200, 255 }; +rw::RGBA BackgroundColor = { 64, 64, 64, 0 }; +rw::RGBA BorderColor = { 128, 128, 128, 0 }; + +rw::Clump *Clump = nil; +rw::Light *MainLight = nil; +rw::Light *AmbientLight = nil; + +rw::World *World; +rw::Charset *Charset; + +const char *SubCameraCaption[4] = { + "Perspective view", + "Parallel view: Z-axis", + "Parallel view: X-axis", + "Parallel view: Y-axis" +}; + +rw::V3d Xaxis = { 1.0f, 0.0, 0.0f }; +rw::V3d Yaxis = { 0.0f, 1.0, 0.0f }; +rw::V3d Zaxis = { 0.0f, 0.0, 1.0f }; + +rw::World* +CreateWorld(void) +{ + rw::BBox bb; + + bb.inf.x = bb.inf.y = bb.inf.z = -100.0f; + bb.sup.x = bb.sup.y = bb.sup.z = 100.0f; + + return rw::World::create(&bb); +} + +rw::Light* +CreateAmbientLight(rw::World *world) +{ + rw::Light *light = rw::Light::create(rw::Light::AMBIENT); + assert(light); + World->addLight(light); + return light; +} + +rw::Light* +CreateMainLight(rw::World *world) +{ + rw::Light *light = rw::Light::create(rw::Light::DIRECTIONAL); + assert(light); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 30.0f, rw::COMBINEREPLACE); + frame->rotate(&Yaxis, 30.0f, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + World->addLight(light); + return light; +} + +rw::Clump* +CreateClump(rw::World *world) +{ + rw::Clump *clump; + rw::StreamFile in; + + rw::Image::setSearchPath("files/textures/"); + const char *filename = "files/clump.dff"; + if(in.open(filename, "rb") == NULL){ + printf("couldn't open file\n"); + return false; + } + if(!rw::findChunk(&in, rw::ID_CLUMP, NULL, NULL)) + return false; + clump = rw::Clump::streamRead(&in); + in.close(); + if(clump == nil) + return false; + + rw::Frame *frame = clump->getFrame(); + frame->rotate(&Xaxis, -120.0f, rw::COMBINEREPLACE); + frame->rotate(&Yaxis, 45.0f, rw::COMBINEPOSTCONCAT); + World->addClump(clump); + return clump; +} + +void +RotateClump(float xAngle, float yAngle) +{ + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *frame = Clump->getFrame(); + rw::V3d pos = frame->matrix.pos; + + pos = rw::scale(pos, -1.0f); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + + frame->rotate(&cameraMatrix->up, xAngle, rw::COMBINEPOSTCONCAT); + frame->rotate(&cameraMatrix->right, yAngle, rw::COMBINEPOSTCONCAT); + + pos = rw::scale(pos, -1.0f); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +Initialize(void) +{ + sk::globals.windowtitle = "Sub-raster example"; + sk::globals.width = 1280; + sk::globals.height = 800; + sk::globals.quit = 0; +} + +bool +Initialize3D(void) +{ + if(!sk::InitRW()) + return false; + + Charset = rw::Charset::create(&ForegroundColor, &BackgroundColor); + + World = CreateWorld(); + + AmbientLight = CreateAmbientLight(World); + MainLight = CreateMainLight(World); + Clump = CreateClump(World); + + CreateCameras(World); +UpdateSubRasters(Camera, sk::globals.width, sk::globals.height); + + rw::SetRenderState(rw::CULLMODE, rw::CULLBACK); + rw::SetRenderState(rw::ZTESTENABLE, 1); + rw::SetRenderState(rw::ZWRITEENABLE, 1); + + ImGui_ImplRW_Init(); + ImGui::StyleColorsClassic(); + + return true; +} + +void +Terminate3D(void) +{ + DestroyCameras(World); + + if(AmbientLight){ + World->removeLight(AmbientLight); + AmbientLight->destroy(); + AmbientLight = nil; + } + + if(MainLight){ + World->removeLight(MainLight); + rw::Frame *frame = MainLight->getFrame(); + MainLight->setFrame(nil); + frame->destroy(); + MainLight->destroy(); + MainLight = nil; + } + + if(Clump){ + World->removeClump(Clump); + Clump->destroy(); + Clump = nil; + } + + if(World){ + World->destroy(); + World = nil; + } + + if(Charset){ + Charset->destroy(); + Charset = nil; + } + + sk::TerminateRW(); +} + +bool +attachPlugins(void) +{ + rw::ps2::registerPDSPlugin(40); + rw::ps2::registerPluginPDSPipes(); + + rw::registerMeshPlugin(); + rw::registerNativeDataPlugin(); + rw::registerAtomicRightsPlugin(); + rw::registerMaterialRightsPlugin(); + rw::xbox::registerVertexFormatPlugin(); + rw::registerSkinPlugin(); + rw::registerUserDataPlugin(); + rw::registerHAnimPlugin(); + rw::registerMatFXPlugin(); + rw::registerUVAnimPlugin(); + rw::ps2::registerADCPlugin(); + return true; +} + +void +DisplayOnScreenInfo(void) +{ + for(int i = 0; i < 4; i++){ + rw::Raster *scr = SubCameras[i]->frameBuffer; + + rw::int32 scrw = scr->width; + rw::int32 scrh = scr->height; + + rw::int32 captionWidth = strlen(SubCameraCaption[i])*Charset->desc.width; + + if(captionWidth < scrw && scrh > Charset->desc.height*2){ + rw::int32 x = scr->offsetX + (scrw - captionWidth)/2; + rw::int32 y = scr->offsetY + Charset->desc.height; + Charset->print(SubCameraCaption[i], x, y, 0); + } + } +} + +rw::RGBA BackgroundColors[] = { + { 64, 64, 64, 0 }, + { 128, 0, 0, 0 }, + { 0, 128, 0, 0 }, + { 0, 0, 128, 0 }, +}; +void +Render(float timeDelta) +{ + Camera->clear(&BorderColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + + for(int i = 0; i < 4; i++){ + SubCameras[i]->clear(&BackgroundColor, rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); +// SubCameras[i]->clear(&BackgroundColors[i], rw::Camera::CLEARIMAGE|rw::Camera::CLEARZ); + SubCameras[i]->beginUpdate(); + World->render(); + SubCameras[i]->endUpdate(); + } + + Camera->beginUpdate(); + DisplayOnScreenInfo(); + Camera->endUpdate(); + + Camera->showRaster(0); +} + +void +Idle(float timeDelta) +{ + Render(timeDelta); +} + +int MouseX, MouseY; +int MouseDeltaX, MouseDeltaY; +int MouseButtons; + +bool Rotating; + +void +KeyUp(int key) +{ +} + +void +KeyDown(int key) +{ + switch(key){ + case sk::KEY_ESC: + sk::globals.quit = 1; + break; + } +} + +void +MouseBtn(sk::MouseState *mouse) +{ + MouseButtons = mouse->buttons; + Rotating = !!(MouseButtons&1); +} + +void +MouseMove(sk::MouseState *mouse) +{ + MouseDeltaX = mouse->posx - MouseX; + MouseDeltaY = mouse->posy - MouseY; + MouseX = mouse->posx; + MouseY = mouse->posy; + if(Rotating) + RotateClump(-MouseDeltaX, MouseDeltaY); +} + +sk::EventStatus +AppEventHandler(sk::Event e, void *param) +{ + using namespace sk; + Rect *r; + MouseState *ms; + + ImGuiEventHandler(e, param); + ImGuiIO &io = ImGui::GetIO(); + + switch(e){ + case INITIALIZE: + Initialize(); + return EVENTPROCESSED; + case RWINITIALIZE: + return Initialize3D() ? EVENTPROCESSED : EVENTERROR; + case RWTERMINATE: + Terminate3D(); + return EVENTPROCESSED; + case PLUGINATTACH: + return attachPlugins() ? EVENTPROCESSED : EVENTERROR; + case KEYDOWN: + KeyDown(*(int*)param); + return EVENTPROCESSED; + case KEYUP: + KeyUp(*(int*)param); + return EVENTPROCESSED; + case MOUSEBTN: + if(!io.WantCaptureMouse){ + ms = (MouseState*)param; + MouseBtn(ms); + }else + MouseButtons = 0; + return EVENTPROCESSED; + case MOUSEMOVE: + MouseMove((MouseState*)param); + return EVENTPROCESSED; + case RESIZE: + r = (Rect*)param; + // TODO: register when we're minimized + if(r->w == 0) r->w = 1; + if(r->h == 0) r->h = 1; + + sk::globals.width = r->w; + sk::globals.height = r->h; + if(::Camera){ + sk::CameraSize(::Camera, r); + ::Camera->setFOV(FOV, (float)sk::globals.width/sk::globals.height); + + UpdateSubRasters(::Camera, r->w, r->h); + } + break; + case IDLE: + Idle(*(float*)param); + return EVENTPROCESSED; + } + return sk::EVENTNOTPROCESSED; +} diff --git a/tools/subrast/subrast.cpp b/tools/subrast/subrast.cpp new file mode 100644 index 0000000..0ee4027 --- /dev/null +++ b/tools/subrast/subrast.cpp @@ -0,0 +1,592 @@ +#include +#include + +#include "subrast.h" + +rw::Camera *Camera; +rw::Camera *SubCameras[4]; + +void +CameraSetViewWindow(rw::Camera *camera, float width, float height, float vw) +{ + rw::V2d viewWindow; + + // TODO: aspect ratio when fullscreen + if(width > height){ + viewWindow.x = vw; + viewWindow.y = vw / (width/height); + }else{ + viewWindow.x = vw / (height/width); + viewWindow.y = vw; + } + + camera->setViewWindow(&viewWindow); +} + +void +UpdateSubRasters(rw::Camera *mainCamera, rw::int32 mainWidth, rw::int32 mainHeight) +{ + rw::Rect rect[4]; + float width, height, border; + + border = mainHeight*0.05f; + + width = (mainWidth - border*3.0f) / 2.0f; + height = (mainHeight - border*3.0f) / 2.0f; + + // top left + rect[0].x = border; + rect[0].y = border; + rect[0].w = width; + rect[0].h = height; + + // top right + rect[1].x = border*2 + width; + rect[1].y = border; + rect[1].w = width; + rect[1].h = height; + + // bottom left + rect[2].x = border; + rect[2].y = border*2 + height; + rect[2].w = width; + rect[2].h = height; + + // bottom left + rect[3].x = border*2 + width; + rect[3].y = border*2 + height; + rect[3].w = width; + rect[3].h = height; + + CameraSetViewWindow(SubCameras[0], width, height, 0.5f); + for(int i = 1; i < 4; i++) + CameraSetViewWindow(SubCameras[i], width, height, 0.5f + 0.4f); + + for(int i = 0; i < 4; i++){ + SubCameras[i]->frameBuffer->subRaster(mainCamera->frameBuffer, &rect[i]); + SubCameras[i]->zBuffer->subRaster(mainCamera->zBuffer, &rect[i]); + } +} + +void +PositionSubCameras(void) +{ + rw::Frame *frame; + rw::V3d pos; + const float dist = 2.5f; + + // perspective + pos.x = pos.y = 0.0f; + pos.z = -4.0f; + frame = SubCameras[0]->getFrame(); + frame->translate(&pos, rw::COMBINEREPLACE); + + // look along z + pos.x = pos.y = 0.0f; + pos.z = -dist; + frame = SubCameras[1]->getFrame(); + frame->translate(&pos, rw::COMBINEREPLACE); + + // look along x + pos.x = -dist; + pos.y = pos.z = 0.0f; + frame = SubCameras[2]->getFrame(); + frame->rotate(&Yaxis, 90.0f, rw::COMBINEREPLACE); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + + // look along y + pos.x = pos.z = 0.0f; + pos.y = -dist; + frame = SubCameras[3]->getFrame(); + frame->rotate(&Xaxis, -90.0f, rw::COMBINEREPLACE); + frame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +CreateCameras(rw::World *world) +{ + Camera = sk::CameraCreate(sk::globals.width, sk::globals.height, 1); + assert(Camera); + + for(int i = 0; i < 4; i++){ + SubCameras[i] = sk::CameraCreate(0, 0, 1); + assert(SubCameras[i]); + + SubCameras[i]->setNearPlane(0.1f); + SubCameras[i]->setFarPlane(30.0f); + + world->addCamera(SubCameras[i]); + + if(i > 0) + SubCameras[i]->setProjection(rw::Camera::PARALLEL); + } + + PositionSubCameras(); +} + +void +DestroyCameras(rw::World *world) +{ +} + +/* + +rw::Light *BaseAmbientLight; +bool BaseAmbientLightOn; + +rw::Light *CurrentLight; +rw::Light *AmbientLight; +rw::Light *PointLight; +rw::Light *DirectLight; +rw::Light *SpotLight; +rw::Light *SpotSoftLight; + +float LightRadius = 100.0f; +float LightConeAngle = 45.0f; +rw::RGBAf LightColor = { 1.0f, 1.0f, 1.0f, 1.0f }; + +rw::RGBA LightSolidColor = { 255, 255, 0, 255 }; +bool LightOn = true; +bool LightDrawOn = true; +rw::V3d LightPos = {0.0f, 0.0f, 75.0f}; +rw::int32 LightTypeIndex = 1; + +rw::BBox RoomBBox; + +rw::Light* +CreateBaseAmbientLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::AMBIENT); + assert(light); + light->setColor(0.5f, 0.5f, 0.5f); + return light; +} + +rw::Light* +CreateAmbientLight(void) +{ + return rw::Light::create(rw::Light::AMBIENT); +} + +rw::Light* +CreateDirectLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::DIRECTIONAL); + assert(light); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +rw::Light* +CreatePointLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::POINT); + assert(light); + light->radius = LightRadius; + rw::Frame *frame = rw::Frame::create(); + assert(frame); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEREPLACE); + light->setFrame(frame); + return light; +} + +rw::Light* +CreateSpotLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::SPOT); + assert(light); + light->radius = LightRadius; + light->setAngle(LightConeAngle/180.0f*M_PI); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +rw::Light* +CreateSpotSoftLight(void) +{ + rw::Light *light = rw::Light::create(rw::Light::SOFTSPOT); + assert(light); + light->radius = LightRadius; + light->setAngle(LightConeAngle/180.0f*M_PI); + rw::Frame *frame = rw::Frame::create(); + assert(frame); + frame->rotate(&Xaxis, 45.0f, rw::COMBINEREPLACE); + rw::V3d pos = LightPos; + frame->translate(&pos, rw::COMBINEPOSTCONCAT); + light->setFrame(frame); + return light; +} + +void +DestroyLight(rw::Light **light) +{ + if(*light == nil) + return; + rw::World *world = (*light)->world; + if(world) + world->removeLight(*light); + rw::Frame *frame = (*light)->getFrame(); + if(frame){ + (*light)->setFrame(nil); + frame->destroy(); + } + + (*light)->destroy(); + *light = nil; +} + +void +LightsDestroy(void) +{ + DestroyLight(&SpotSoftLight); + DestroyLight(&SpotLight); + DestroyLight(&PointLight); + DestroyLight(&DirectLight); + DestroyLight(&AmbientLight); + DestroyLight(&BaseAmbientLight); +} + +void +LightsUpdate(void) +{ + static rw::int32 oldLightTypeIndex = -1; + + // Switch to a different light + if((LightOn && oldLightTypeIndex != LightTypeIndex) || CurrentLight == nil){ + oldLightTypeIndex = LightTypeIndex; + + // remove first + if(CurrentLight) + CurrentLight->world->removeLight(CurrentLight); + + switch(LightTypeIndex){ + case 0: CurrentLight = AmbientLight; break; + case 1: CurrentLight = PointLight; break; + case 2: CurrentLight = DirectLight; break; + case 3: CurrentLight = SpotLight; break; + case 4: CurrentLight = SpotSoftLight; break; + } + World->addLight(CurrentLight); + } + + if(CurrentLight){ + CurrentLight->setColor(LightColor.red, LightColor.green, LightColor.blue); + CurrentLight->radius = LightRadius; + CurrentLight->setAngle(LightConeAngle / 180.0f * M_PI); + } + + // Remove light from world if not used + if(!LightOn && CurrentLight){ + CurrentLight->world->removeLight(CurrentLight); + CurrentLight = nil; + } +} + +#define POINT_LIGHT_RADIUS_FACTOR 0.05f + +void +DrawPointLight(void) +{ + enum { NUMVERTS = 50 }; + rw::RWDEVICE::Im3DVertex shape[NUMVERTS]; + rw::int32 i; + rw::V3d point; + + rw::V3d *pos = &CurrentLight->getFrame()->getLTM()->pos; + for(i = 0; i < NUMVERTS; i++){ + point.x = pos->x + + cosf(i/(NUMVERTS/2.0f) * M_PI) * LightRadius * POINT_LIGHT_RADIUS_FACTOR; + point.y = pos->y + + sinf(i/(NUMVERTS/2.0f) * M_PI) * LightRadius * POINT_LIGHT_RADIUS_FACTOR; + point.z = pos->z; + + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, LightSolidColor.alpha); + shape[i].setX(point.x); + shape[i].setY(point.y); + shape[i].setZ(point.z); + } + + rw::im3d::Transform(shape, NUMVERTS, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderPrimitive(rw::PRIMTYPEPOLYLINE); + rw::im3d::RenderLine(NUMVERTS-1, 0); + rw::im3d::End(); +} + +void +DrawCone(float coneAngle, float coneSize, float coneRatio) +{ + enum { NUMVERTS = 10 }; + rw::RWDEVICE::Im3DVertex shape[NUMVERTS+1]; + rw::int16 indices[NUMVERTS*3]; + rw::int32 i; + + rw::Matrix *matrix = CurrentLight->getFrame()->getLTM(); + rw::V3d *pos = &matrix->pos; + + // cone + for(i = 1; i < NUMVERTS+1; i++){ + float cosValue = cosf(i/(NUMVERTS/2.0f) * M_PI) * + sinf(coneAngle/180.0f*M_PI); + float sinValue = sinf(i/(NUMVERTS/2.0f) * M_PI) * + sinf(coneAngle/180.0f*M_PI); + + float coneAngleD = cosf(coneAngle/180.0f*M_PI); + + rw::V3d up = rw::scale(matrix->up, sinValue*coneSize); + rw::V3d right = rw::scale(matrix->right, cosValue*coneSize); + rw::V3d at = rw::scale(matrix->at, coneAngleD*coneSize*coneRatio); + + shape[i].setX(pos->x + at.x + up.x + right.x); + shape[i].setY(pos->y + at.y + up.y + right.y); + shape[i].setZ(pos->z + at.z + up.z + right.z); + } + + for(i = 0; i < NUMVERTS; i++){ + indices[i*3 + 0] = 0; + indices[i*3 + 1] = i+2; + indices[i*3 + 2] = i+1; + } + indices[NUMVERTS*3-2] = 1; + + for(i = 0; i < NUMVERTS+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 128); + + shape[0].setX(pos->x); + shape[0].setY(pos->y); + shape[0].setZ(pos->z); + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + + rw::im3d::Transform(shape, NUMVERTS+1, nil, 0); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRIFAN); + rw::im3d::RenderTriangle(0, NUMVERTS, 1); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, NUMVERTS*3); + rw::im3d::End(); + + + for(i = 0; i < NUMVERTS+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 255); + + float coneAngleD = cosf(coneAngle/180.0f*M_PI); + rw::V3d at = rw::scale(matrix->at, coneAngleD*coneSize*coneRatio); + shape[0].setX(pos->x + at.x); + shape[0].setY(pos->y + at.y); + shape[0].setZ(pos->z + at.z); + + rw::im3d::Transform(shape, NUMVERTS+1, nil, rw::im3d::ALLOPAQUE); + if(coneRatio > 0.0f){ + rw::im3d::RenderPrimitive(rw::PRIMTYPETRIFAN); + rw::im3d::RenderTriangle(0, NUMVERTS, 1); + }else + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRIFAN, indices, NUMVERTS*3); + rw::im3d::End(); + + + // lines + at = rw::scale(matrix->at, -0.05f); + shape[0].setX(pos->x + at.x); + shape[0].setY(pos->y + at.y); + shape[0].setZ(pos->z + at.z); + rw::im3d::Transform(shape, NUMVERTS+1, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPEPOLYLINE, indices, NUMVERTS*3); + rw::im3d::End(); +} + +void +DrawDirectLight(void) +{ + enum { NUMVERTS = 20 }; + const float DIAMETER = 1.5f; + const float CONE_ANGLE = 45.0f; + const float CONE_SIZE = 3.0f; + const float LENGTH = 5.0f; + rw::RWDEVICE::Im3DVertex shape[NUMVERTS*2+1]; + rw::int16 indices[NUMVERTS*3]; + rw::int32 i; + + rw::Matrix *matrix = CurrentLight->getFrame()->getLTM(); + rw::V3d *pos = &matrix->pos; + + // cylinder + for(i = 0; i < NUMVERTS*2; i += 2){ + float cosValue = cosf(i/(NUMVERTS/2.0f) * M_PI); + float sinValue = sinf(i/(NUMVERTS/2.0f) * M_PI); + + rw::V3d up = rw::scale(matrix->up, sinValue*DIAMETER); + rw::V3d right = rw::scale(matrix->right, cosValue*DIAMETER); + rw::V3d at = rw::scale(matrix->at, -(CONE_SIZE + 1.0f)); + + shape[i].setX(pos->x + at.x + up.x + right.x); + shape[i].setY(pos->y + at.y + up.y + right.y); + shape[i].setZ(pos->z + at.z + up.z + right.z); + + + at = rw::scale(matrix->at, -(LENGTH + CONE_SIZE)); + + shape[i+1].setX(pos->x + at.x + up.x + right.x); + shape[i+1].setY(pos->y + at.y + up.y + right.y); + shape[i+1].setZ(pos->z + at.z + up.z + right.z); + } + + for(i = 0; i < NUMVERTS*2+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 128); + + rw::SetRenderState(rw::VERTEXALPHA, 1); + rw::SetRenderState(rw::SRCBLEND, rw::BLENDSRCALPHA); + rw::SetRenderState(rw::DESTBLEND, rw::BLENDINVSRCALPHA); + + rw::im3d::Transform(shape, NUMVERTS*2, nil, 0); + rw::im3d::RenderPrimitive(rw::PRIMTYPETRISTRIP); + rw::im3d::RenderTriangle(2*NUMVERTS-2, 2*NUMVERTS-1, 0); + rw::im3d::RenderTriangle(2*NUMVERTS-1, 1, 0); + rw::im3d::End(); + + + // bottom cap + for(i = 0; i < NUMVERTS*2+1; i++) + shape[i].setColor(LightSolidColor.red, LightSolidColor.green, + LightSolidColor.blue, 255); + + rw::V3d at = rw::scale(matrix->at, -(LENGTH + CONE_SIZE)); + shape[NUMVERTS*2].setX(pos->x + at.x); + shape[NUMVERTS*2].setY(pos->y + at.y); + shape[NUMVERTS*2].setZ(pos->z + at.z); + + for(i = 0; i < NUMVERTS; i++){ + indices[i*3+0] = NUMVERTS*2; + indices[i*3+1] = (i+1)*2 + 1; + indices[i*3+2] = i*2 + 1; + } + indices[NUMVERTS*3-2] = 1; + + rw::im3d::Transform(shape, NUMVERTS*2+1, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, NUMVERTS*3); + rw::im3d::End(); + + + // top cap + at = rw::scale(matrix->at, -(CONE_SIZE + 1.0f)); + shape[NUMVERTS*2].setX(pos->x + at.x); + shape[NUMVERTS*2].setY(pos->y + at.y); + shape[NUMVERTS*2].setZ(pos->z + at.z); + + for(i = 0; i < NUMVERTS; i++){ + indices[i*3+0] = NUMVERTS*2; + indices[i*3+1] = i*2; + indices[i*3+2] = (i+1)*2; + } + + rw::im3d::Transform(shape, NUMVERTS*2+1, nil, rw::im3d::ALLOPAQUE); + rw::im3d::RenderIndexedPrimitive(rw::PRIMTYPETRILIST, indices, NUMVERTS*3); + rw::im3d::End(); + + + // cone + DrawCone(CONE_ANGLE, CONE_SIZE, -2.0f); +} + +void +DrawCurrentLight(void) +{ + rw::SetRenderState(rw::TEXTURERASTER, nil); + rw::SetRenderState(rw::CULLMODE, rw::CULLBACK); + rw::SetRenderState(rw::ZTESTENABLE, 1); + + switch(LightTypeIndex){ + case 1: DrawPointLight(); break; + case 2: DrawDirectLight(); break; + case 3: + case 4: DrawCone(LightConeAngle, LightRadius*POINT_LIGHT_RADIUS_FACTOR, 1.0f); break; + } +} + + + +void +LightRotate(float xAngle, float yAngle) +{ + if(CurrentLight == nil || CurrentLight == AmbientLight || CurrentLight == PointLight) + return; + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *lightFrame = CurrentLight->getFrame(); + rw::V3d pos = lightFrame->matrix.pos; + + pos = rw::scale(pos, -1.0f); + lightFrame->translate(&pos, rw::COMBINEPOSTCONCAT); + + lightFrame->rotate(&cameraMatrix->up, xAngle, rw::COMBINEPOSTCONCAT); + lightFrame->rotate(&cameraMatrix->right, yAngle, rw::COMBINEPOSTCONCAT); + + pos = rw::scale(pos, -1.0f); + lightFrame->translate(&pos, rw::COMBINEPOSTCONCAT); +} + +void +ClampPosition(rw::V3d *pos, rw::V3d *delta, rw::BBox *bbox) +{ + if(pos->x + delta->x < bbox->inf.x) + delta->x = bbox->inf.x - pos->x; + else if(pos->x + delta->x > bbox->sup.x) + delta->x = bbox->sup.x - pos->x; + + if(pos->y + delta->y < bbox->inf.y) + delta->y = bbox->inf.y - pos->y; + else if(pos->y + delta->y > bbox->sup.y) + delta->y = bbox->sup.y - pos->y; + + if(pos->z + delta->z < bbox->inf.z) + delta->z = bbox->inf.z - pos->z; + else if(pos->z + delta->z > bbox->sup.z) + delta->z = bbox->sup.z - pos->z; +} + +void +LightTranslateXY(float xDelta, float yDelta) +{ + if(CurrentLight == nil || CurrentLight == AmbientLight || CurrentLight == DirectLight) + return; + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *lightFrame = CurrentLight->getFrame(); + rw::V3d right = rw::scale(cameraMatrix->right, xDelta); + rw::V3d up = rw::scale(cameraMatrix->up, yDelta); + rw::V3d delta = rw::add(right, up); + + ClampPosition(&lightFrame->matrix.pos, &delta, &RoomBBox); + + lightFrame->translate(&delta, rw::COMBINEPOSTCONCAT); +} + +void +LightTranslateZ(float zDelta) +{ + if(CurrentLight == nil || CurrentLight == AmbientLight || CurrentLight == DirectLight) + return; + + rw::Matrix *cameraMatrix = &Camera->getFrame()->matrix; + rw::Frame *lightFrame = CurrentLight->getFrame(); + rw::V3d delta = rw::scale(cameraMatrix->at, zDelta); + + ClampPosition(&lightFrame->matrix.pos, &delta, &RoomBBox); + + lightFrame->translate(&delta, rw::COMBINEPOSTCONCAT); +} +*/ \ No newline at end of file diff --git a/tools/subrast/subrast.h b/tools/subrast/subrast.h new file mode 100644 index 0000000..b7c8537 --- /dev/null +++ b/tools/subrast/subrast.h @@ -0,0 +1,10 @@ +extern rw::Camera *Camera; +extern rw::Camera *SubCameras[4]; + +void CreateCameras(rw::World *world); +void DestroyCameras(rw::World *world); +void UpdateSubRasters(rw::Camera *mainCamera, rw::int32 mainWidth, rw::int32 mainHeight); + +extern rw::V3d Xaxis; +extern rw::V3d Yaxis; +extern rw::V3d Zaxis;