%% Shapes
%
% This example creates instances of several simple object types in the
% virtual scene and sets them various properties like Color, Shininess,
% etc.
%
% By double clicking on a ball you can run a callback that temporarily
% makes the ball shine (by setting the Color property greater than one).
% Other objects in the scene have also callbacks define that set and
% animate their various properties like turning physics on, rotate, animate
% texture transformation, etc.
%
% In addition, the floor and the violet spotting scope tube have also
% right-click custom functions defined - right-click on a place on the
% floor can move the transparent box to that location, and the tube light
% can be switched on and off, and change its color.
%
% The C user button is programmed to invoke the Capture app that captures
% the default viewpoint camera output and displays it in a MATLAB figure.
% The app allows you also to record a video that appears as an MP4 file in
% the working directory.
%
% The script also displays the contents of a text file in an overlay
% message box. The text contains extended ASCII characters.

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

%% Initialize the 3D Foundation object
FW = F3D.World(mfilename);
%FW = F3D.World(mfilename, 'remote', '127.0.0.1', 30000, 1); % alternatively, connect to a remote client
initialize(FW, [4 -8 3], [0 0 -0.5]);

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

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

% Create a floor from template
Floor = F3D.Geometry.templateFloor(FW.Root, [10 10 0.1]);
Floor.TextureFile = fullfile(Path, 'data', 'RubberMat.png');
Floor.TextureMapping.Blend = 1/2;
Floor.Shininess = 0.8;
% Define custom menu with 1 item
Floor.MenuItems = {'Move the box here'};
Floor.OnMenu = @FloorMenu;

Box = addChild(FW.Root, "Box" + 1);
createShape(Box, 'box', [1 1 1]);
Box.Color = [1 1 1];
Box.Location = [-2 2 0.51];
Box.TextureFile = fullfile(Path, 'data', 'MatlabLogo.png');
Box.TextureMapping.Blend = 1;
Box.TextureTransform = F3D.Type.TextureTransform('Scale', [0.5 0.5], 'Angle', 45);
Box.TwoSided = 1;
Box.Transparency = 0.001;
Box.Masked = true;
Box.OnActivate = @(this, ~) interpolate(this, 'TextureTransform.Angle', -45, 'hill');

Prm = addChild(FW.Root, 'Pyramid');
createShape(Prm, 'pyramid', [1 1 1]);
Prm.Color = rand(1, 3);
Prm.Location = [-4 4 0.5];

Cyl1 = addChild(FW.Root, 'Cylinder');
createShape(Cyl1, 'cylinder', [1 1 1]);
Cyl1.Color = rand(1, 3);
Cyl1.Location = [4 -4 0.5];

Cone1 = addChild(Cyl1);
createShape(Cone1, 'cone', [1 1 1], 64);
Cone1.Color = [0.5 0.5 1];
Cone1.Location = [0 0 1];

Plane1 = addChild(Cone1, 'Display');
createShape(Plane1, 'plane', [2.40 1.80 0]);
Plane1.Location = [0 0 1.40];
Plane1.Rotation(1) = -90;
displayImage(Plane1, repelem(1 - reshape(isprime(1:10000), [100 100]), 3, 3));
Plane1.TextureMapping.Blend = 1;
Plane1.TextureTransform.Velocity = [0.1 0];
Plane1.Transparency = 0.1;
Plane1.Shininess = 0;
Plane1.OnActivate = @(this, ~) interpolate(this, 'Rotation', [-90 0 360], 'hill', 4);   % Callback using simple anonymous function

Cone2 = addChild(FW.Root);
createShape(Cone2, 'cone', [1 1 1], 32, 2);
Cone2.Color = [0.5 1 0.5];
Cone2.Location = [2.5 -4 0.5];
Cone2.OnActivate = @TurnOnPhysics;   % Callback using function handle

Cone3 = addChild(FW.Root);
createShape(Cone3, 'cone', [1 1 1], 8, 1);
Cone3.Color = [1 0.5 0.5];
Cone3.Flat = 1;
Cone3.Location = [1 -4 0.5];
Cone3.OnActivate = @TurnOnPhysics;

Tor1 = addChild(FW.Root);
createShape(Tor1, 'torus', [1 1 0.25]);
Tor1.Color = [0.8 0 0];
Tor1.Location = [0 4 0.125];

Tor2 = addChild(FW.Root);
createShape(Tor2, 'torus', [0.1 1 1], 0.8, 32, 1);
Tor2.Color = [0.8 0 0.4];
Tor2.Location = [0 4 0.8];
Tor2.OnActivate = @TurnOnPhysics;

Tb1 = addChild(FW.Root);
createShape(Tb1, 'tube', [1 1 1]);
Tb1.Color = [1 1 0];
Tb1.Location = [-2 0 0.5];

Tb2 = addChild(FW.Root);
createShape(Tb2, 'tube', [1 1 1], 0.5, 4, 2);
Tb2.Color = [0 1 1];
Tb2.Flat = 1;
Tb2.Location = [-2 0 1.5];
Tb2.Rotation(2) = 1;
Tb2.OnActivate = @TurnOnPhysics;

Tb3 = addChild(FW.Root);
Tb3.Instances.Locations = [0 0 0; 50 0 0; 100 0 0];
Tb3.Instances.Scales = [1 1 1; 0.8 1.2 1.2; 0.6 1.5 1.5];
Tb3.Instances.Colors = [0 0 0; 0.5 0.5 0.5; 1 1 1];
createShape(Tb3, 'tube', [1 1 1], 0.9, 32, 1);
Tb3.Color = [1 0 1];
Tb3.VertexBlend = 0.5;
Tb3.Location = [-2 0 2.5];
Tb3.TextureMapping.Blend = 1/2;
Tb3.SpotLight.Color = [2 0 2];
Tb3.ViewpointName = 'From the tube';
Tb3.OnActivate = @TurnOnPhysics;
% Define custom menu with 3 items
Tb3.MenuItems = {'Turn off', 'Turn on', 'Change color'};
Tb3.OnMenu = @ReflectorMenu;

for i = 1:10
    Arr = addChild(Box);
    createShape(Arr, 'arrow', [1 0.2 0.2], 5, 1);
    Arr.Color = [0 rand rand];
    stretch(Arr, [0 0 0.55], [-1 -1 0.65] + rand(1, 3)*2);
end

n = 11;
Clr = parula(n);
Ball = F3D.Actor.empty(n, 0);
for i = 1:n
    Ball(i) = addChild(FW.Root, ['Sphere' num2str(i)]);
    createShape(Ball(i), 'sphere', [1 1 1], 32);
    Ball(i).Color = Clr(i, :);
    Ball(i).Flat = (mod(i, 2) == 0);
    Ball(i).Shininess = (i - 1)/10;
    Ball(i).Location = [0.8*i - 4.8 0.8*i - 4.8 0.5];
    Ball(i).OnActivate = @LetItShine;
    Ball(i).SemanticID = i;
end

n = 4;
Ico = F3D.Actor.empty(n, 0);
for i = 1:n
    Ico(i) = addChild(FW.Root, ['Ico' num2str(i)]);
    createShape(Ico(i), 'icosphere', [1 1 1], i);
    Ico(i).Color = 1;
    Ico(i).Flat = 1;
    Ico(i).Shininess = 0.8;
    Ico(i).Location = [0.8*i - 1 0.8*i - 3 0.5];
    Ico(i).OnActivate = @(this, ~) interpolate(this, 'Flat', 0, 'hill', 2);
end

message(VP, readUTF8File(fullfile(Path, 'data', 'instructions.txt')));
VP.Button5 = F3D.Type.Button('Symbol', '[C]', 'ToolTip', 'Capture viewport', 'Callback', @(this, ~) this.ControlledActor.capture);

%% Callback functions
function LetItShine(Actor, CP)
    label(Actor.ParentWorld.Viewports.Main, ['Surface coordinates: ' num2str(CP)], Actor.Color/2)
    interpolate(Actor, 'Color', Actor.Color*1000, 'hill', 2.0);
end

function TurnOnPhysics(Actor, ~)
    Actor.Physics = true;
end

% Custom menu functions have the following arguments: 
% (Associated_Actor, ItemID, Click_Position)
function FloorMenu(Actor, ~, CurPos)
    Actor.ParentWorld.Root.Children.Box1.interpolate('Location', CurPos + [0 0 0.51]);
end

function ReflectorMenu(Actor, ItemID, ~)
    switch ItemID
        case 1
            Actor.SpotLight.Color = 0;
        case 2
            Actor.SpotLight.Color = [2 0 2];
        case 3
            Actor.SpotLight.Color = circshift(Actor.SpotLight.Color, 1, 2);
    end
end