#define MAIN_INCLUDE
#include <vhelper.h>
#include <vstroke.h>
#include <gp/coord.h>
#include <gp/pstring.h>
#include <gp/misc.h>
#include <vutil-pipeline.h>
#include <vutil-texture.h>
#include "shapes.h"
class World {
public:
World(pVulkan_Helper &fb)
:vh(fb), ff_state(fb.qs),
frame_timer(vh.frame_timer),
shapes(ff_state), sphere(ff_state), cone(ff_state)
{init();}
World() = delete;
void init();
void init_graphics();
void run();
void frame_callback();
void render(vk::CommandBuffer& cb);
void cb_keyboard();
void modelview_update();
void shadow_update();
void shadow_transform_create(pMatrix& m, pCoor light);
pVulkan_Helper& vh;
VFixed_Function_State_Manager ff_state;
pFrame_Timer& frame_timer;
Shapes shapes;
pVariable_Control variable_control;
double world_time;
double last_frame_wall_time;
int time_step_count;
float opt_gravity_accel; pVect gravity_accel; bool opt_gravity;
bool opt_time_step_alt;
bool opt_tryout1, opt_tryout2;
float platform_xmin, platform_xmax, platform_zmin, platform_zmax;
float platform_pi_xwidth_inv;
VVertex_Buffer_Set bset_platform_tex, bset_platform_mir;
VPipeline pipe_platform_mir, pipe_platform_tex;
vk::Sampler sampler;
VTexture texid_syl;
VTexture texid_emacs;
bool opt_platform_texture;
bool opt_platform_flat;
void platform_update();
bool platform_collision_possible(pCoor pos);
pCoor light_location;
float opt_light_intensity;
enum { MI_Eye, MI_Light, MI_Ball, MI_Ball_V, MI_COUNT } opt_move_item;
bool opt_pause;
bool opt_single_frame; bool opt_single_time_step;
pCoor eye_location;
pVect eye_direction;
VTransform transform;
VBufferV<Uni_Lighting> uni_light, uni_light_ambient, uni_light_all;
VBufferV<Uni_Lighting> *puni_light_curr;
pMatrix modelview;
pMatrix modelview_shadow;
pMatrix transform_mirror;
void time_step_cpu_v0(double);
Ball ball;
Sphere sphere;
Cone cone;
float guide_distance;
};
void
World::init_graphics()
{
vh.init();
vh.opt_record_every_time = true;
vh.display_cb_set([&](){frame_callback();});
vh.cbs_cmd_record.push_back( [&](vk::CommandBuffer& cb){ render(cb); });
opt_platform_flat = true;
opt_light_intensity = 100.2;
light_location = pCoor(platform_xmax,platform_xmax,platform_zmin);
pCoor platform_center
( ( platform_xmin + platform_xmax ) / 2,
platform_xmax / 2,
( platform_zmin + platform_zmax ) / 2 );
eye_location = pCoor( platform_center.x, platform_center.y, platform_zmin );
pCoor ball_pos_half(ball.position); ball_pos_half.y *= 0.5;
eye_direction = pNorm(ball_pos_half - eye_location);
uni_light.init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);
uni_light_all.init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);
uni_light_ambient.init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);
cone.ppuni_light = &puni_light_curr;
cone.transform = &transform;
opt_platform_texture = true;
texid_syl.init( vh.qs, P_Image_Read("gpup.png",255) );
texid_emacs.init( vh.qs, P_Image_Read("mult.png",-1) );
variable_control.insert(opt_light_intensity,"Light Intensity");
guide_distance = 0.5;
variable_control.insert_linear(guide_distance,"Guide Distance",.05);
time_step_count = 0;
sampler = vh.qs.dev.createSampler
( { {},
vk::Filter::eLinear, vk::Filter::eLinear,
vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eRepeat,
vk::SamplerAddressMode::eRepeat,
vk::SamplerAddressMode::eRepeat,
0.0f,
false, 16.0f,
false,
vk::CompareOp::eNever,
0.0f, VK_LOD_CLAMP_NONE, vk::BorderColor::eFloatOpaqueBlack } );
opt_move_item = MI_Eye;
opt_pause = false;
opt_single_frame = false;
opt_single_time_step = false;
opt_time_step_alt = false;
frame_timer.work_unit_set("Steps / s");
sphere.transform = &transform;
sphere.ppuni_light = &puni_light_curr;
sphere.init(40);
sphere.center = ball.position;
platform_update();
modelview_update();
}
void
World::run()
{
vh.message_loop_spin();
uni_light.destroy();
uni_light_all.destroy();
uni_light_ambient.destroy();
sphere.destroy();
pipe_platform_tex.destroy();
pipe_platform_mir.destroy();
bset_platform_mir.destroy();
bset_platform_tex.destroy();
texid_syl.destroy();
texid_emacs.destroy();
vh.dev.destroySampler(sampler);
shapes.destroy();
cone.destroy();
vh.finish();
}
void
World::platform_update()
{
const float tile_count = 19;
const float ep = 1.00001;
const float platform_delta_x = platform_xmax - platform_xmin;
const float zdelta = ( platform_zmax - platform_zmin ) / tile_count * ep;
const float trmin = 0.05;
const float trmax = 0.7;
const float tsmin = 0;
const float tsmax = 0.4;
pTCoor t00(trmin,tsmin), t01(trmin,tsmax), t10(trmax,tsmin), t11(trmax,tsmax);
pColor white(1,1,1);
const pColor lsu_spirit_purple(0x580da6);
if ( !pipe_platform_mir )
pipe_platform_mir
.init( ff_state )
.color_uniform_set( lsu_spirit_purple )
.follow_uni_light( &puni_light_curr )
.topology_set( vk::PrimitiveTopology::eTriangleList )
.create( vh.qs.render_pass );
bset_platform_mir.reset( pipe_platform_mir ).tolerate_tcoords();
if ( !pipe_platform_tex )
pipe_platform_tex
.init( ff_state )
.color_uniform_set(white)
.follow_uni_light( &puni_light_curr )
.topology_set( vk::PrimitiveTopology::eTriangleList )
.use_texture( sampler, texid_syl )
.create( vh.qs.render_pass );
bset_platform_tex.reset( pipe_platform_tex );
bool even = true;
const bool curved = false;
platform_pi_xwidth_inv = M_PI / platform_delta_x;
const double delta_theta = M_PI / tile_count;
const float delta_x = ( platform_xmax - platform_xmin ) / tile_count;
const float platform_xmid = 0.5 * ( platform_xmax + platform_xmin );
const float platform_xhlf = 0.5 * platform_delta_x;
const double platform_depth = 0;
for ( int i = 0; i < tile_count; i++ )
{
const double theta0 = i * delta_theta;
const double theta1 = theta0 + delta_theta;
const float x0 = curved
? platform_xmid - platform_xhlf * cos(theta0)
: platform_xmin + i * delta_x;
const float x1 = curved
? platform_xmid - platform_xhlf * cos(theta1)
: x0 + delta_x;
const float y0 = -0.01 - platform_depth * sin(theta0);
const float y1 = -0.01 - platform_depth * sin(theta1);
pNorm normc0( platform_depth * cos(theta0), platform_xhlf*sin(theta0), 0);
pNorm normc1( platform_depth * cos(theta1), platform_xhlf*sin(theta1), 0);
pVect norm0 = curved ? normc0 : pVect(0,1,0);
pVect norm1 = curved ? normc1 : pVect(0,1,0);
for ( float z = platform_zmin; z < platform_zmax; z += zdelta )
{
auto& bset = even ? bset_platform_tex : bset_platform_mir;
const float z1 = z+zdelta;
pCoor p00(x0,y0,z), p01(x0,y0,z1), p10(x1,y1,z), p11(x1,y1,z1);
bset << t11 << norm0 << p00;
bset << t10 << norm0 << p01;
bset << t00 << norm1 << p11;
bset << t11 << norm0 << p00;
bset << t00 << norm1 << p11;
bset << t01 << norm1 << p10;
even = !even;
}
}
bset_platform_mir.to_dev();
bset_platform_tex.to_dev();
}
void
World::modelview_update()
{
pMatrix_Translate center_eye(-eye_location);
pMatrix_Rotation rotate_eye(eye_direction,pVect(0,0,-1));
modelview = rotate_eye * center_eye;
shadow_update();
}
void
World::shadow_update()
{
pCoor platform_point(platform_xmin,0,platform_zmin);
pVect platform_normal(0,1,0);
shadow_transform_create(modelview_shadow,light_location);
pCoor eye_loc_mirror(eye_location.x, -eye_location.y, eye_location.z);
pMatrix reflect; reflect.set_identity(); reflect.rc(1,1) = -1;
transform_mirror = modelview * reflect * invert(modelview);
}
void
World::shadow_transform_create(pMatrix& m, pCoor light_location)
{
pVect platform_normal(0,1,0);
pVect eye_normal(0,0,-1);
pMatrix_Translate center_light(-light_location);
pNorm axis(-platform_normal,eye_normal);
const double angle = asin(axis.magnitude);
pMatrix_Rotation rotate_platform(axis,angle);
pMatrix frustum; frustum.set_zero();
frustum.rc(0,0) = frustum.rc(1,1) = light_location.y;
frustum.rc(3,2) = -1;
pMatrix_Translate restore_z(0,0,-light_location.y);
pMatrix step1 = rotate_platform * center_light;
pMatrix to_platform = restore_z * frustum * rotate_platform * center_light;
pMatrix_Rotation un_rotate_platform(axis,-angle);
pMatrix_Translate un_center_light(light_location);
pMatrix from_platform = un_center_light * un_rotate_platform;
pMatrix project = from_platform * to_platform;
modelview_shadow = modelview * from_platform * to_platform;
pCoor test_pt(1.1,0,2.2);
pCoor test_pt2(1.1,1,2.2);
pCoor test_pt_a = step1 * test_pt;
pCoor test_pt_b = to_platform * test_pt; test_pt_b.homogenize();
pCoor test_pt_pr = project * test_pt; test_pt_pr.homogenize();
pCoor test_pt2_pr = project * test_pt2; test_pt2_pr.homogenize();
assert( test_pt_a.v() != nullptr ); }
void
World::render(vk::CommandBuffer& cb)
{
opt_single_frame = opt_single_time_step = false;
cb_keyboard();
sphere.center = ball.position;
const pColor white(0xffffff);
const pColor gray(0x303030);
const pColor lsu_business_purple(0x7f5ca2);
const pColor lsu_spirit_purple(0x580da6);
const pColor lsu_spirit_gold(0xf9b237);
const pColor lsu_official_purple(0x2f0462);
const pColor dark(0);
const int win_width = vh.get_width();
const int win_height = vh.get_height();
const float aspect = float(win_width) / win_height;
transform.at_start();
transform.eye_from_global_set(modelview);
pMatrix_Scale flip(1,-1,1);
transform.clip_from_eye_set
( flip * pMatrix_Frustum(-.8,.8,-.8/aspect,.8/aspect,1,5000) );
vh.clearValues[0].color = vk::ClearColorValue( array{0, 0, 0, 1 } );
vh.clearValues[1].depthStencil = vk::ClearDepthStencilValue( 1, 0 );
auto& light_all = uni_light_all->cgl_LightSource[0];
light_all.position = transform.eye_from_global * light_location;
light_all.constantAttenuation = .3;
light_all.linearAttenuation = 1;
light_all.quadraticAttenuation = 0;
pColor ambient_color(0x999999);
uni_light_all->cgl_LightModel.ambient = ambient_color;
light_all.diffuse = white * opt_light_intensity;
light_all.ambient = dark;
light_all.specular = white * opt_light_intensity;
uni_light_ambient = uni_light_all.get_val();
auto& light_amb = uni_light_ambient->cgl_LightSource[0];
light_amb.diffuse = dark;
light_amb.specular = dark;
uni_light = uni_light_all.get_val();
uni_light->cgl_LightModel.ambient = dark;
uni_light.to_dev();
uni_light_ambient.to_dev();
uni_light_all.to_dev();
puni_light_curr = &uni_light_all;
const double time_now = time_wall_fp();
const bool blink_visible = int64_t(time_now*3) & 1;
# define BLINK(txt,pad) ( blink_visible ? txt : pad )
vh.fbprintf("%s\n",frame_timer.frame_rate_text_get());
vh.fbprintf
("Time Step: %8d World Time: %11.6f %s\n",
time_step_count, world_time,
opt_pause ? BLINK("PAUSED, 'p' to unpause, SPC or S-SPC to step.","") :
"Press 'p' to pause.");
vh.fbprintf
("Tryout 1: %s ('y') Tryout 2: %s ('Y')\n",
opt_tryout1 ? BLINK("ON "," ") : "OFF",
opt_tryout2 ? BLINK("ON "," ") : "OFF" );
vh.fbprintf
("Eye location: [%5.1f, %5.1f, %5.1f] "
"Eye direction: [%+.2f, %+.2f, %+.2f]\n",
eye_location.x, eye_location.y, eye_location.z,
eye_direction.x, eye_direction.y, eye_direction.z);
vh.fbprintf
("Ball Pos [%5.1f,%5.1f,%5.1f] Vel [%+5.1f,%+5.1f,%+5.1f]\n",
ball.position.x,ball.position.y,ball.position.z,
ball.velocity.x,ball.velocity.y,ball.velocity.z );
pVariable_Control_Elt* const cvar = variable_control.current;
vh.fbprintf("VAR %s = %.5f (TAB or '`' to change, +/- to adjust)\n",
cvar->name,cvar->var[0]);
vh.fbprintf("Time Step Routine: time_step_v%d\n", opt_time_step_alt);
pCoor guide_loc =
ball.position + ( eye_location - ball.position ) * guide_distance;
if ( opt_platform_flat )
{
ff_state.push();
vk::StencilOpState s_op_apply
( vk::StencilOp::eReplace, vk::StencilOp::eKeep,
vk::StencilOp::eKeep, vk::CompareOp::eNever, 2, 2, 2 );
ff_state.pipelineDepthStencilStateCreateInfo_get()
.setStencilTestEnable(true)
.setFront(s_op_apply).setBack(s_op_apply);
assert( pipe_platform_mir );
transform.use_global_for(pipe_platform_mir);
pipe_platform_mir.record_draw(cb,bset_platform_mir);
cb.nextSubpass(vk::SubpassContents::eInline); vh.qs.subpass++;
vk::StencilOpState s_op_use
( vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep, vk::CompareOp::eEqual, 2, 2, 2 );
ff_state.pipelineDepthStencilStateCreateInfo_get()
.setFront(s_op_use).setBack(s_op_use);
const pMatrix prev = transform.eye_from_global;
const pMatrix_Scale reflect(1,-1,1);
transform.eye_from_global = prev * reflect;
ff_state.pipelineRasterizationStateCreateInfo_get()
.setFrontFace(vk::FrontFace::eClockwise);
sphere.render(cb);
cone.set_color(pColor(1,0,0));
cone.render(cb, guide_loc, 0.1, pVect(1,0,0));
cone.set_color(pColor(0,1,0));
cone.render(cb, guide_loc, 0.1, pVect(0,1,0));
cone.set_color(pColor(0,0,1));
cone.render(cb, guide_loc, 0.1, pVect(0,0,1));
transform.eye_from_global = prev;
ff_state.pipelineColorBlendStateCreateInfo_get()
.setBlendConstants( array<float,4>{.5,.5,.5,.5} );
vk::ColorComponentFlags clr_all
( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA );
ff_state.pipelineColorBlendAttachmentState_get() =
vk::PipelineColorBlendAttachmentState
( true, vk::BlendFactor::eConstantAlpha, vk::BlendFactor::eConstantAlpha, vk::BlendOp::eAdd, vk::BlendFactor::eOne, vk::BlendFactor::eZero, vk::BlendOp::eAdd, clr_all );
pipe_platform_mir.record_draw(cb,bset_platform_mir);
ff_state.pop();
}
transform.use_global_for(pipe_platform_tex);
puni_light_curr = &uni_light_ambient;
cone.set_color(pColor(1,0,0));
cone.render(cb, guide_loc, 0.1, pVect(1,0,0));
cone.set_color(pColor(0,1,0));
cone.render(cb, guide_loc, 0.1, pVect(0,1,0));
cone.set_color(pColor(0,0,1));
cone.render(cb, guide_loc, 0.1, pVect(0,0,1));
sphere.color = lsu_spirit_gold;
sphere.render(cb,ball.position);
pCoor pos2 = ball.position + pVect(2,0,0);
sphere.color = lsu_spirit_purple;
sphere.render(cb,pos2);
pipe_platform_tex.record_draw(cb,bset_platform_tex);
vk::ClearAttachment clr_att
( vk::ImageAspectFlagBits::eStencil, 0, vk::ClearDepthStencilValue(0,0) );
vk::ClearRect clr_rect( vk::Rect2D({}, vh.s_extent), 0, 1 );
cb.clearAttachments( clr_att, clr_rect );
ff_state.push();
vk::StencilOpState s_op_apply_f
( vk::StencilOp::eKeep, vk::StencilOp::eIncrementAndWrap,
vk::StencilOp::eKeep, vk::CompareOp::eAlways, -1, -1, 0 );
vk::StencilOpState s_op_apply_b
( vk::StencilOp::eKeep, vk::StencilOp::eDecrementAndWrap,
vk::StencilOp::eKeep, vk::CompareOp::eAlways, -1, -1, 0 );
ff_state.pipelineDepthStencilStateCreateInfo_get()
.setStencilTestEnable(true)
.setDepthWriteEnable(false)
.setFront(s_op_apply_f).setBack(s_op_apply_b);
ff_state.pipelineColorBlendAttachmentState_get().setColorWriteMask( {} );
cone.light_pos = sphere.light_pos = light_location;
sphere.render_shadow_volume(cb,sphere.radius,ball.position);
sphere.render_shadow_volume(cb,sphere.radius,pos2);
cone.render_shadow_volume(cb, guide_loc, 0.1, pVect(1,0,0));
cone.render_shadow_volume(cb, guide_loc, 0.1, pVect(0,1,0));
cone.render_shadow_volume(cb, guide_loc, 0.1, pVect(0,0,1));
cb.nextSubpass(vk::SubpassContents::eInline); vh.qs.subpass++;
vk::StencilOpState s_op_apply_fb
( vk::StencilOp::eKeep, vk::StencilOp::eKeep,
vk::StencilOp::eKeep, vk::CompareOp::eEqual, -1, -1, 0 );
ff_state.pipelineDepthStencilStateCreateInfo_get()
.setFront(s_op_apply_fb).setBack(s_op_apply_fb)
.setDepthCompareOp(vk::CompareOp::eEqual);
vk::ColorComponentFlags cmask_all
( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA );
ff_state.pipelineColorBlendAttachmentState_get() =
vk::PipelineColorBlendAttachmentState
( true, vk::BlendFactor::eOne, vk::BlendFactor::eOne, vk::BlendOp::eAdd, vk::BlendFactor::eOne, vk::BlendFactor::eOne, vk::BlendOp::eAdd, cmask_all );
puni_light_curr = &uni_light;
pipe_platform_tex.record_draw(cb,bset_platform_tex);
cone.set_color(pColor(1,0,0));
cone.render(cb, guide_loc, 0.1, pVect(1,0,0));
cone.set_color(pColor(0,1,0));
cone.render(cb, guide_loc, 0.1, pVect(0,1,0));
cone.set_color(pColor(0,0,1));
cone.render(cb, guide_loc, 0.1, pVect(0,0,1));
sphere.color = lsu_spirit_gold;
sphere.render(cb,ball.position);
sphere.color = lsu_spirit_purple;
sphere.render(cb,pos2);
ff_state.pop();
shapes.record_tetrahedron(cb,transform,light_location,0.05);
}
void
World::cb_keyboard()
{
const int key = vh.keyboard_key_get();
if ( !key ) return;
pVect adjustment(0,0,0);
pVect user_rot_axis(0,0,0);
const bool shift = vh.keyboard_shift;
const float move_amt = shift ? 2.0 : 0.4;
switch ( key ) {
case FB_KEY_LEFT: adjustment.x = -move_amt; break;
case FB_KEY_RIGHT: adjustment.x = move_amt; break;
case FB_KEY_PAGE_UP: adjustment.y = move_amt; break;
case FB_KEY_PAGE_DOWN: adjustment.y = -move_amt; break;
case FB_KEY_DOWN: adjustment.z = move_amt; break;
case FB_KEY_UP: adjustment.z = -move_amt; break;
case FB_KEY_DELETE: user_rot_axis.y = 1; break;
case FB_KEY_INSERT: user_rot_axis.y = -1; break;
case FB_KEY_HOME: user_rot_axis.x = 1; break;
case FB_KEY_END: user_rot_axis.x = -1; break;
case 'b': opt_move_item = MI_Ball; break;
case 'B': opt_move_item = MI_Ball_V; break;
case 'e': case 'E': opt_move_item = MI_Eye; break;
case 'f': case 'F': opt_platform_flat = !opt_platform_flat; break;
case 'g': case 'G': opt_gravity = !opt_gravity; break;
case 'l': case 'L': opt_move_item = MI_Light; break;
case 'n': case 'N': opt_platform_texture = !opt_platform_texture; break;
case 'p': case 'P': opt_pause = !opt_pause; break;
case 's': case 'S': ball.stop(); break;
case 'v': case 'V': opt_time_step_alt = !opt_time_step_alt; break;
case 'y': opt_tryout1 = !opt_tryout1; break;
case 'Y': opt_tryout2 = !opt_tryout2; break;
case ' ':
if ( shift ) opt_single_time_step = true; else opt_single_frame = true;
opt_pause = true;
break;
case FB_KEY_TAB:
if ( !shift ) { variable_control.switch_var_right(); break; }
case 96: variable_control.switch_var_left(); break;
case '-':case '_': variable_control.adjust_lower(); break;
case '+':case '=': variable_control.adjust_higher(); break;
default: printf("Unknown key, %d\n",key); break;
}
gravity_accel.y = opt_gravity ? -opt_gravity_accel : 0;
if ( user_rot_axis.x || user_rot_axis.y )
{
pMatrix_Rotation rotall(eye_direction,pVect(0,0,-1));
user_rot_axis *= invert(rotall);
eye_direction *= pMatrix_Rotation(user_rot_axis, M_PI * 0.03);
modelview_update();
}
if ( adjustment.x || adjustment.y || adjustment.z )
{
const double angle =
fabs(eye_direction.y) > 0.99
? 0 : atan2(eye_direction.x,-eye_direction.z);
pMatrix_Rotation rotall(pVect(0,1,0),-angle);
adjustment *= rotall;
switch ( opt_move_item ){
case MI_Ball: ball.translate(adjustment); break;
case MI_Ball_V: ball.push(adjustment); break;
case MI_Light: light_location += adjustment; break;
case MI_Eye: eye_location += adjustment; break;
default: break;
}
modelview_update();
}
}