%% Shooting Game with Hitmap
%
% This example demonstrates the following 3D Foundation concepts:
% - Using object collisions callbacks
% - Camera switching and control
% - Using MATLAB graphs as textures for virtual world objects
%
% First, a simple scene containing a target plane and a tube is created.
% The tube will emit icosphere objects towards the target plane like a
% cannon.
%
% The target plane has two callbacks defined:
% The OnActivate callback shoots colored "bullets" in the direction of the
% axis of the cannon tube. The created bullets are assigned initial linear
% velocity, then move respecting the physics. With the cannon tube in its
% initial position, the bullets hit the target plane, bounce from it and
% fall on the scene floor.
% The OnHit callback registers the position of bullet impact on the target
% plane and builds the "hitmap" graph in a MATLAB figure, using Gaussian
% windows around the places of impact. This graph is used as the target
% plane texture, updated dynamically on each bullet impact.
%
% The cannon tube has the OnActivate callback defined, that sets both the
% current camera and its control to be attached to the cannon itself. This
% callback is activated using double-click on the tube. In effect, user
% navigation is coupled with cannon tube, you look through the tube along
% its axis in the direction of emitted cannon balls. By navigating in the
% scene, you move the cannon and shoot the balls at different directions,
% creating an interesting graphic pattern on the target plane.
%
% You can return camera and control to the default observer (free, not
% attached to any shape) by clicking on the "Default viewpoint" button on
% the main viewer toolbar.
%
% In case you shoot some bullets that would eventually fall down from the
% scene floor, the "View all" button takes into account all scene
% objects including these bullets that would continue falling endlessly
% downwards.

%   Copyright 1998-2021 HUMUSOFT s.r.o.

%% Initialize the 3D Foundation object
FW = F3D.World(mfilename);
initialize(FW, [-1 -8 6], [0 0 4]);

% Units and coordinate system
FW.CoordinateSystem = 'unreal';
FW.AngleUnits = 'deg';

% Set the viewport properties
VP = FW.Viewports.Main;
VP.Title = mfilename;
Path = fileparts(which(mfilename));
setLogo(VP, fullfile(Path, 'data', 'MatlabLogo.png'));
VP.OnClose = 'close(findall(0, ''tag'', ''F3D_HIT''));';

message(VP, ['Double click on the inclined plane to shoot.' ,...
    newline 'Double click on the tube to aim using mouse or arrow keys.']);

F3D.Geometry.templateFloor(FW.Root, [50 50 0.1]);

FW.UserData.Map = zeros(64);

Wall = addChild(FW.Root, 'Wall');
createShape(Wall, 'box', [0.20 10 10]);
Wall.Color = [1 1 1];
Wall.Shininess = 0.3;
Wall.Location = [5 0 5];
Wall.Rotation(2) = -30;
Wall.TextureMapping.Blend = 1;
Wall.OnActivate = @Shot;
Wall.OnHit = @HitEvent;
displayImage(Wall, FW.UserData.Map);
Wall.TextureTransform.Scale = [-1 1];

Cannon = addChild(FW.Root, 'Cannon');
createShape(Cannon, 'tube', [1 1 1], 0.9, 32, 1);
Cannon.Color = [1 0 1];
Cannon.Location = [-4 0 5];
Cannon.TextureMapping.Blend = 1/2;
Cannon.OnActivate = @SetControl;

figure('Tag', 'F3D_HIT');
FW.UserData.hA = gca;

%% Callback functions
function SetControl(Actor, ~)
    grabCamera(Actor, 1);
    grabControl(Actor);
end

function Shot(Actor, ~)
    Bullet = addChild(Actor.ParentWorld.Root.Children.Cannon);
    createShape(Bullet, 'icosphere', [0.4 0.4 0.4] + rand(1, 3)*0.6);
    Bullet.Color = rand(1, 3);
    Bullet.Shininess = 0.8;
    Bullet.Flat = 1;
    Bullet.Location = [0.5 0 0];
    Bullet.Rotation = rand(1, 3)*180;
    Bullet.Physics = true;

    % Give the bullet initial linear velocity along the Actor X axis, with
    % slight random deviation from the center beam
    Bullet.LinearVelocity = [20 rand(1, 2)*4 - 2];
end

function HitEvent(Self, Other, Loc, Frc)
    s = size(Self.ParentWorld.UserData.Map, 1);
    w = gausswin(s, s/5);
    [r, c] = meshgrid(w, w);
    F = r.*c*norm(Frc);
    s = size(Self.ParentWorld.UserData.Map, 1) - 1;
    x = round(s*(Loc(3) + 5)/10 - s/2);
    y = round(s*(Loc(2) + 5)/10 - s/2);
    F = circshift(F, [x y]);
    Self.ParentWorld.UserData.Map = Self.ParentWorld.UserData.Map + F;
    hA = Self.ParentWorld.UserData.hA;
    colormap('hot');
    contourf(hA, Self.ParentWorld.UserData.Map);
    hA.Color = [0 0 0];
    displayImage(Self, hA);
end