-----------------------------------------------------------------------
-- An example of Actor Prolog program.                               --
-- (c) 2013 IRE RAS Alexei A. Morozov                                --
-----------------------------------------------------------------------
--
import .. from "morozov/Java3D";
--
-----------------------------------------------------------------------
DOMAINS:
--
reference:
--
RefYesNoUnknown = 'yes'; 'no'; 'unknown'.
--
ground:
--
Counter         = INTEGER.
--
-----------------------------------------------------------------------
PREDICATES:
--
imperative:
--
crystal_lattice(
        Counter,
        Counter,
        RefYesNoUnknown) = NodeList     - (i,i,i);
--
random_coordinate(INTEGER,Numerical)    - (i,o);
random_angle(Angle)                     - (o);
--
crystal(Radius,
        Numerical,
        Numerical,
        RefYesNoUnknown) = Node         - (i,i,i,i);
--
billboard(
        STRING,
        Numerical,
        RefYesNoUnknown) = Node         - (i,i,i);
--
select_node(RefYesNoUnknown,Node,Node)  - (i,i,o);
--
crystal_ends(
        Radius,
        Numerical,
        Numerical,
        Appearance) = Shape3D           - (i,i,i,i);
--
bottom_fan_coordinates(
        Angle,
        Angle,
        Radius,
        StripCount,
        Vertices,
        Vertices)                       - (i,i,i,i,i,o);
top_fan_coordinates(
        Angle,
        Angle,
        Radius,
        Numerical,
        StripCount,
        Vertices,
        Vertices)                       - (i,i,i,i,i,i,o);
--
crystal_thunk(
        Radius,
        Numerical,
        Appearance) = Shape3D           - (i,i,i);
--
thunk_coordinates(
        Angle,
        Angle,
        Radius,
        Numerical,
        Numerical,
        Numerical,
        Numerical,
        Vertices)                       - (i,i,i,i,i,i,i,o);
--
point_light(
        Angle,
        TimeInterval,
        Color,
        Radius) = Node                  - (i,i,i,i);
--
-----------------------------------------------------------------------
class 'Main' (specialized 'Dialog'):
--
internal:
--
        main_slot               = ('Test3D');
--
variable:
--
        show_billboards         : RefYesNoUnknown;
--
[
goal:-!,
        maximize.
--
action("remake"):-!,
        main_slot ? create_scene(show_billboards).
--
modified_control('show_billboards'):-!,
        main_slot ? modify_scene(show_billboards).
]
-----------------------------------------------------------------------
class 'Test3D' (specialized 'Canvas3D'):
--
constant:
--
        crystal_radius                  = 0.45;
        crystal_total_height            = 5;
--
        fontName        : FontName              = 'helvetica';
        tessellationTolerance
                        : TessellationTolerance = 1.0e-2;
--
        bold_font       : Font3D
                        = 'Font3D'({
                                fontName,
                                fontSize: 32,
                                fontStyle: ['bold'],
                                extrudePath: 'FontExtrusion'({
                                        depth: 0
                                        }),
                                tessellationTolerance
                                });
--
        enable_scene_antialiasing       = 'yes';
--
internal:
--
        database                = ('Store');
--
[
PREDICATES:
--
imperative:
--
create_scene(RefYesNoUnknown)           - (i);
modify_scene(RefYesNoUnknown)           - (i);
--
imperative:
--
'-'(Angle,Angle) = Angle                - (i,i);
'-'(Numerical,Numerical) = Numerical    - (i,i);
'-'(Numerical) = Numerical              - (i);
'+'(Angle,Angle) = Angle                - (i,i);
'+'(Numerical,Numerical) = Numerical    - (i,i);
'*'(Radius,REAL) = REAL                 - (i,i);
'/'(Numerical,Numerical) = Numerical    - (i,i);
--
sin(Angle) = REAL                       - (i);
cos(Angle) = REAL                       - (i);
--
CLAUSES:
--
goal:-!.        
--
initialize:-!,
        create_scene('yes').
--
create_scene(ShowBillboards):-
        database ? retract_all(),
        show([
                'TransformGroup'({
                        transform3D: 'Transform3D'({
                                scale: 0.7,
                                rotX: ?pi()/3
                                }),
                        branches: ?crystal_lattice(1,15,ShowBillboards)
                        }),
                'OrbitBehavior'({
                        reverseAll: 'yes',
                        stopZoom: 'yes',
                        minRadius: 1.5,
                        schedulingBounds: 'BoundingSphere'({})
                        })
                ]).
--
crystal_lattice(N,Limit,ShowBillboards) = [Cell|Rest] :-
        N <= Limit,!,
        Height==
                ?random() * crystal_total_height
                + crystal_radius * 2,
        random_coordinate(7,X),
        random_coordinate(4,Z),
        Cell== 'TransformGroup'({
                transform3D: 'Transform3D'({
                        translation: [X*0.1,0.0,Z*0.1]
                        }),
                branches: [
                        ?crystal(
                                crystal_radius,
                                Height,
                                -(crystal_total_height/2),
                                ShowBillboards)
                        ]}),
        Rest== ?crystal_lattice(N+1,Limit,ShowBillboards).
crystal_lattice(_,_,_) = [].
--
random_coordinate(Width,Z):-
        Z== ?random(Width*2+1) - Width.
--
random_angle(A):-
        A== (?random()-0.5)*?pi().
--
crystal(Radius,TotalHeight,Y0,ShowBillboards) = 'TransformGroup'({
                transform3D: 'Transform3D'({
                        scale: 0.2
                        }),
                branches: [
                        'TransformGroup'({
                                transform3D: 'Transform3D'({
                                        translation: [0.0,Y0,0.0]
                                        }),
                                branches: [
                                        ?billboard(Text,TotalHeight+Radius,ShowBillboards),
                                        ?crystal_ends(Radius,ThunkHeight,ConeHeight,Appearance),
                                        ?crystal_thunk(Radius,ThunkHeight,Appearance)
                                        ]
                                }),
                        'TransformGroup'({
                                transform3D: 'Transform3D'({
                                        translation: [0.0,HalfHeight,0.0]
                                        }),
                                branches: [
                                        ?point_light(A1,10200,color3(0.5,0.0,0.0),HalfHeight),
                                        ?point_light(A2,67000,color3(0.0,0.5,0.0),HalfHeight),
                                        ?point_light(A3,42000,color3(0.0,0.0,0.5),HalfHeight),
                                        ?point_light(A4,18000,color3(0.5,0.0,0.5),HalfHeight),
                                        ?point_light(A5,25000,color3(0.5,0.5,0.0),HalfHeight),
                                        ?point_light(A6,30000,color3(0.0,0.5,0.5),HalfHeight),
                                        ?point_light(A7,70000,color3(0.1,0.1,0.1),HalfHeight)
                                        ]
                                })
                        ]
                }) :-
        ConeHeight== Radius,
        ThunkHeight== TotalHeight - ConeHeight,
        Text== ?convert_to_string(TotalHeight),
        Appearance== 'Appearance'({
                material: 'Material'({
                        diffuseColor: color3(0.7,0.7,0.7),
                        emissiveColor: 'Black',
                        ambientColor: 'White',
                        specularColor: 'White',
                        shininess: 179
                        }),
                transparencyAttributes: 'TransparencyAttributes'({
                        transparency: 0.3,
                        transparencyMode: 'NICEST'
                        }),
                polygonAttributes: 'PolygonAttributes'({
                        cullFace: 'CULL_NONE'
                        })
                }),
        random_angle(A1),
        random_angle(A2),
        random_angle(A3),
        random_angle(A4),
        random_angle(A5),
        random_angle(A6),
        random_angle(A7),
        HalfHeight== TotalHeight / 2.
--
billboard(Text,Height,ShowBillboards)
        = 'TransformGroup'({
                transform3D: 'Transform3D'({
                        scale: 0.01,
                        translation: [0,Height,0]
                        }),
                allowChildrenRead: 'yes',
                allowChildrenWrite: 'yes',
                branches: [
                        InnerGroup
                        ]}):-
        database ? get_new_label(Label),
        Billboard== 'Billboard'({
                mode: 'ROTATE_ABOUT_POINT',
                point: p(0,0,0),
                geometry: 'Text3D'({
                        string: Text,
                        font3D: bold_font,
                        horizontalAlignment: 'ALIGN_CENTER',
                        verticalAlignment: 'ALIGN_CENTER'
                        }),
                appearance: 'Appearance'({})
                }),
        select_node(ShowBillboards,Billboard,Node),
        InnerGroup== 'BranchGroup'({
                label: Label,
                allowDetach: 'yes',
                compile: 'yes',
                branches: [
                        Node
                        ]
                }),
        database ? save_billboard(Label,Billboard).
--
select_node('yes',Billboard,Billboard):-!.
select_node(_,_,'Sphere'({radius: 0.0})).
--
crystal_ends(Radius,ThunkHeight,ConeHeight,Appearance)
        = 'Shape3D'({   geometry: geometryArray(
                                'GeometryInfo'({
                                        primitive: 'TRIANGLE_FAN_ARRAY',
                                        stripCounts: [VertexCount,VertexCount],
                                        coordinates: Coordinates,
                                        generateNormals: 'yes',
                                        creaseAngle: 0
                                        })),
                        appearance: Appearance
                }) :-
        VertexCount== 10,
        AngleStep== 2 * ?pi() / 8,
        CurrentAngle== AngleStep * 0.5,
        List1== [],
        bottom_fan_coordinates(
                CurrentAngle,AngleStep,Radius,
                VertexCount,List1,List2),
        List3== [p(0,0,0)|List2],
        top_fan_coordinates(
                CurrentAngle,AngleStep,Radius,ThunkHeight,
                VertexCount,List3,List4),
        Coordinates== [p(0,ThunkHeight+ConeHeight,0)|List4].
--
bottom_fan_coordinates(Angle,Step,Radius,Counter,Rest1,[C|Rest2]):-
        Counter > 1,!,
        X== Radius * ?sin(Angle),
        Y== Radius * ?cos(Angle),
        C== p(X,0,Y),
        bottom_fan_coordinates(
                Angle-Step,Step,Radius,Counter-1,Rest1,Rest2).
bottom_fan_coordinates(_,_,_,_,Rest,Rest).
--
top_fan_coordinates(
                Angle,Step,Radius,ThunkHeight,
                Counter,Rest1,[C|Rest2]):-
        Counter > 1,!,
        X== Radius * ?sin(Angle),
        Y== Radius * ?cos(Angle),
        C== p(X,ThunkHeight,Y),
        top_fan_coordinates(
                Angle+Step,Step,Radius,ThunkHeight,
                Counter-1,Rest1,Rest2).
top_fan_coordinates(_,_,_,_,_,Rest,Rest).
--
crystal_thunk(Radius,ThunkHeight,Appearance)
        = 'Shape3D'({   geometry: geometryArray(
                                'GeometryInfo'({
                                        primitive: 'QUAD_ARRAY',
                                        coordinates: Coordinates,
                                        generateNormals: 'yes',
                                        creaseAngle: 0
                                        })),
                        appearance: Appearance
                }) :-
        FacetCount== 8,
        AngleStep== 2 * ?pi() / 8,
        Angle0== AngleStep * 0.5,
        X0== Radius * ?sin(Angle0),
        Y0== Radius * ?cos(Angle0),
        Angle1== Angle0 - AngleStep,
        thunk_coordinates(
                Angle1,AngleStep,Radius,ThunkHeight,
                FacetCount,X0,Y0,Coordinates).
--
thunk_coordinates(
                Angle,Step,Radius,ThunkHeight,Counter,
                X1,Y1,[C1,C2,C3,C4|Rest]):-
        Counter > 0,!,
        X2== Radius * ?sin(Angle),
        Y2== Radius * ?cos(Angle),
        C1== p(X1,0,Y1),
        C2== p(X1,ThunkHeight,Y1),
        C3== p(X2,ThunkHeight,Y2),
        C4== p(X2,0,Y2),
        thunk_coordinates(
                Angle-Step,Step,Radius,ThunkHeight,Counter-1,
                X2,Y2,Rest).
thunk_coordinates(_,_,_,_,_,_,_,[]).
--
point_light(RotZ,Increasing,C,Radius) =
        'TransformGroup'({
                allowTransformRead: 'yes',
                allowTransformWrite: 'yes',
                branches: [
                        'TransformGroup'({
                                transform3D: 'Transform3D'({
                                        translation: [0.0,0.0,Z]
                                        }),
                                branches: [
                                        'Sphere'({radius: 0.1}),
                                        'PointLight'({
                                                color: C,
                                                influencingBounds: Bounds
                                                })
                                        ]}),
                        'RotationInterpolator'({
                                alpha: 'Alpha3D'({
                                        increasingAlphaDuration: Increasing
                                        }),
                                transformAxis: 'Transform3D'({
                                        rotZ: RotZ
                                        }),
                                schedulingBounds: Bounds
                                })
                        ]
                }) :-
        Z== Radius * 1.5,
        Bounds == 'BoundingSphere'({radius: Radius}).
--
modify_scene('yes'):-
        LN== database?match(labeled_node(_,_)),
                LN == labeled_node(Label,Billboard),
                set_node(Label,
                        'BranchGroup'({
                                label: Label,
                                allowDetach: 'yes',
                                compile: 'yes',
                                branches: [
                                        Billboard
                                        ]
                                })),
                fail.
modify_scene('no'):-
        LN== database?match(labeled_node(_,_)),
                LN == labeled_node(Label,_),
                set_node(Label,
                        'BranchGroup'({
                                label: Label,
                                allowDetach: 'yes',
                                compile: 'yes',
                                branches: [
                                        'Sphere'({radius: 0.0})
                                        ]
                                })),
                fail.
modify_scene(_).
]
-----------------------------------------------------------------------
class 'Store' (specialized 'Database'):
[
DOMAINS:
--
Target          =
                recent_label(INTEGER);
                labeled_node(NodeLabel,Node).
--
PREDICATES:
--
imperative:
--
get_new_label(NodeLabel)                - (o);
--
save_billboard(NodeLabel,Node)          - (i,i);
--
CLAUSES:
--
get_new_label(S):-
        RV== ?match(recent_label(_)),
        RV == recent_label(N1),!,
        retract_all(recent_label(N1)),
        N2== N1 + 1,
        insert(recent_label(N2)),
        S== ?convert_to_string(N2).
get_new_label(S):-
        N1== 1,
        insert(recent_label(N1)),
        S== ?convert_to_string(N1).
--
save_billboard(Label,Node):-
        append(labeled_node(Label,Node)).
]
-----------------------------------------------------------------------