#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"
enum Render_Option { RO_Normally, RO_Simple, RO_Mirrored, RO_Shadow_Volumes };
#include <gp/colors.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 render_objects(vk::CommandBuffer& cb, Render_Option render_option);
void cb_keyboard();
void modelview_update();
void finish();
pVulkan_Helper& vh;
VFixed_Function_State_Manager ff_state;
pFrame_Timer& frame_timer;
Shapes shapes;
Sphere sphere;
Cone cone;
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_head_lock, opt_tail_lock;
bool opt_time_step_easy;
float platform_xmin, platform_xmax, platform_zmin, platform_zmax;
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;
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> uni_light_reflected;
VBufferV<Uni_Lighting> *puni_light_curr;
pMatrix modelview;
void ball_setup_1();
void ball_setup_2();
void ball_setup_3();
void ball_setup_4();
void ball_setup_5();
void time_step_cpu_easy(double);
void time_step_cpu_full(double);
void balls_stop();
void balls_freeze();
void balls_translate(pVect amt, int idx);
void balls_translate(pVect amt);
void balls_push(pVect amt, int idx);
void balls_push(pVect amt);
float opt_spring_constant;
float opt_air_resistance;
float distance_relaxed;
int chain_length;
Ball *balls;
};
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_head_lock = false;
opt_tail_lock = false;
eye_location = pCoor(24.2,11.6,-38.7);
eye_direction = pVect(-0.42,-0.09,0.9);
platform_xmin = -40; platform_xmax = 40;
platform_zmin = -40; platform_zmax = 40;
texid_syl.init( vh.qs, P_Image_Read("gpup.png",255) );
texid_emacs.init( vh.qs, P_Image_Read("mult.png",-1) );
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_light_intensity = 100.2;
light_location = pCoor(platform_xmax,platform_xmax,platform_zmin);
for ( auto& ul: { &uni_light, &uni_light_all,
&uni_light_ambient, &uni_light_reflected } )
ul->init(vh.qs,vk::BufferUsageFlagBits::eUniformBuffer);
variable_control.insert(opt_light_intensity,"Light Intensity");
opt_move_item = MI_Eye;
opt_pause = false;
opt_single_time_step = false;
opt_single_frame = false;
sphere.transform = &transform;
sphere.ppuni_light = &puni_light_curr;
sphere.texture_set(sampler, texid_emacs);
sphere.init(40);
cone.transform = &transform;
cone.ppuni_light = &puni_light_curr;
platform_update();
modelview_update();
}
void
World::run()
{
vh.message_loop_spin();
}
void
World::platform_update()
{
const float tile_count = 19;
const float ep = 1.00001;
const float delta_x = ( platform_xmax - platform_xmin ) / tile_count * ep;
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;
if ( !pipe_platform_mir )
pipe_platform_mir
.init( ff_state )
.color_uniform_set( color_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( color_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 );
const pVect platform_normal(0,1,0);
pTCoor t00(trmin,tsmin), t01(trmin,tsmax), t10(trmax,tsmin), t11(trmax,tsmax);
bool even = true;
for ( int i = 0; i < tile_count; i++ )
{
const float x0 = platform_xmin + i * delta_x;
const float x1 = x0 + delta_x;
const float y = 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,y,z), p01(x0,y,z1), p10(x1,y,z), p11(x1,y,z1);
bset << t11 << platform_normal << p00;
bset << t10 << platform_normal << p01;
bset << t00 << platform_normal << p11;
bset << t11 << platform_normal << p00;
bset << t00 << platform_normal << p11;
bset << t01 << platform_normal << 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;
}
void
World::render_objects(vk::CommandBuffer& cb, Render_Option option)
{
pColor spec_color(0.2,0.2,0.2);
cone.apex_radius = 1; cone.set_color(color_lsu_spirit_purple);
if ( option == RO_Shadow_Volumes )
{
cone.light_pos = light_location;
sphere.light_pos = light_location;
for ( int i=0; i<chain_length; i++ )
sphere.render_shadow_volume(cb,balls[i].radius,balls[i].position);
for ( int i=1; i<chain_length; i++ )
{
Ball *const ball1 = &balls[i-1];
Ball *const ball2 = &balls[i];
cone.render_shadow_volume
(cb,ball1->position,0.3*ball1->radius,
ball2->position-ball1->position);
}
}
else
{
for ( int i=0; i<chain_length; i++ )
{
if ( balls[i].contact )
sphere.color = color_gray;
else if ( i == 0 && opt_head_lock
|| i == chain_length-1 && opt_tail_lock )
sphere.color = color_pale_green;
else
sphere.color = color_lsu_spirit_gold;
sphere.render(cb,balls[i].radius,balls[i].position);
}
for ( int i=1; i<chain_length; i++ )
{
Ball *const ball1 = &balls[i-1];
Ball *const ball2 = &balls[i];
cone.render(cb,ball1->position,0.3*ball1->radius,
ball2->position-ball1->position);
}
}
if ( option == RO_Shadow_Volumes ) return;
transform.use_global_for(pipe_platform_tex);
pipe_platform_tex.record_draw(cb,bset_platform_tex);
}
void
World::render(vk::CommandBuffer& cb)
{
cb_keyboard();
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 );
const float xr = .08;
transform.clip_from_eye_set
( pMatrix_Scale(1,-1,1)
* pMatrix_Frustum(-xr,xr,-xr/aspect,xr/aspect,.1,5000) );
vh.clearValues[0].color =
vk::ClearColorValue( array<float, 4>( { { 0, 0, 0, 0.5 } } ) );
auto& light_all = uni_light_all->cgl_LightSource[0];
light_all.position = transform.eye_from_global * light_location;
light_all.constantAttenuation = 0.5;
light_all.linearAttenuation = 1.0;
light_all.quadraticAttenuation = 0;
pColor ambient_color(0x555555);
uni_light_all->cgl_LightModel.ambient = ambient_color;
light_all.diffuse = opt_light_intensity * color_white;
light_all.ambient = color_black;
light_all.specular = opt_light_intensity * color_white;
uni_light_reflected = uni_light_all.get_val();
uni_light_reflected->cgl_LightSource[0].position =
transform.eye_from_global * pMatrix_Scale(1,-1,1) * light_location;
uni_light_ambient = uni_light_all.get_val();
auto& light_amb = uni_light_ambient->cgl_LightSource[0];
light_amb.diffuse = color_black;
light_amb.specular = color_black;
uni_light = uni_light_all.get_val();
uni_light->cgl_LightModel.ambient = color_black;
uni_light.to_dev();
uni_light_ambient.to_dev();
uni_light_all.to_dev();
uni_light_reflected.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
("Physics: %s ('v') Time Step: %8d World Time: %11.6f %s\n",
opt_time_step_easy ? "EASY" : "FULL",
time_step_count, world_time,
opt_pause ? BLINK("PAUSED, 'p' to unpause, SPC or S-SPC to step.","") :
"Press 'p' to pause."
);
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);
Ball& ball = balls[0];
vh.fbprintf
("Head 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]);
transform.use_global_for(pipe_platform_mir);
vk::ColorComponentFlags clr_all
( vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA );
const bool hide_platform = false;
const bool opt_mirror = true;
const bool opt_shadows = true;
const bool opt_shadow_volumes = false;
if ( !hide_platform )
{
ff_state.push();
if ( opt_mirror )
{
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);
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);
puni_light_curr = &uni_light_reflected;
render_objects(cb,RO_Mirrored);
transform.eye_from_global = prev;
ff_state.pipelineRasterizationStateCreateInfo_get()
.setFrontFace(vk::FrontFace::eCounterClockwise);
}
ff_state.pipelineColorBlendStateCreateInfo_get()
.setBlendConstants( array<float,4>{.5,.5,.5,.5} );
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 );
puni_light_curr = &uni_light_all;;
pipe_platform_mir.record_draw(cb,bset_platform_mir);
ff_state.pop();
}
if ( !opt_shadows )
{
render_objects(cb,RO_Normally);
}
else
{
puni_light_curr = &uni_light_ambient;
render_objects(cb,RO_Normally);
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);
if ( !opt_shadow_volumes )
{
ff_state.pipelineColorBlendAttachmentState_get()
.setColorWriteMask( {} );
}
else
{
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 );
ff_state.pipelineColorBlendStateCreateInfo_get()
.setBlendConstants( array<float,4>{.5,.5,.5,.5} );
ff_state.pipelineDepthStencilStateCreateInfo_get()
.setDepthWriteEnable(true);
}
render_objects(cb,RO_Shadow_Volumes);
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;
render_objects(cb,RO_Normally);
ff_state.pop();
}
puni_light_curr = &uni_light_all;
shapes.record_tetrahedron(cb,transform,light_location,0.15);
}
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 '1': ball_setup_1(); break;
case '2': ball_setup_2(); break;
case '3': ball_setup_3(); break;
case '4': ball_setup_4(); break;
case '5': ball_setup_5(); 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 'g': case 'G': opt_gravity = !opt_gravity; break;
case 'h': case 'H': opt_head_lock = !opt_head_lock; break;
case 't': case 'T': opt_tail_lock = !opt_tail_lock; break;
case 'l': case 'L': opt_move_item = MI_Light; break;
case 'p': case 'P': opt_pause = !opt_pause; break;
case 's': case 'S': balls_stop(); break;
case 'v': case 'V': opt_time_step_easy = !opt_time_step_easy; 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: balls_translate(adjustment,0); break;
case MI_Ball_V: balls_push(adjustment,0); break;
case MI_Light: light_location += adjustment; break;
case MI_Eye: eye_location += adjustment; break;
default: break;
}
modelview_update();
}
}
void
World::finish()
{
uni_light.destroy();
uni_light_all.destroy();
uni_light_ambient.destroy();
uni_light_reflected.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();
}