####################################################################################################
#
# Invoking X3D model self-test:
#
#   $ python ChangingFog.py
#
# Python package x3d.py package is available on PyPI for import.
#   This approach simplifies Python X3D deployment and use.
#   https://pypi.org/project/x3d
#
# Installation:
#       pip install x3d
# or
#       python -m pip install x3d
#
# Developer options for loading x3d package in other Python programs:
#
#    from x3d import *  # preferred approach, terser source that avoids x3d.* class prefixes
#
# or
#    import x3d         # traditional way to subclass x3d package, all classes require x3d.* prefix,
#                       # but python source is very verbose, for example x3d.Material x3d.Shape etc.
#                       # X3dToPython.xslt stylesheet insertPackagePrefix=true supports this option.
#
# Project home page:    # X3D Python Scene Access Interface Library (X3DPSAIL)
#                       # https://www.web3d.org/x3d/stylesheets/python/python.html
# Conversion generator: # https://www.web3d.org/x3d/stylesheets/X3dToPython.xslt
#
####################################################################################################

from x3d import *

newModel=X3D(profile='Immersive',version='3.3',
  head=head(
    children=[
    Comment('javascript code for rotation calculations was derived from:'),
    meta(content='ChangingFog.x3d',name='title'),
    meta(content="A Fog node that adjusts as the viewer's orientation and position changes. This is a good candidate to become a Prototype since Fog does not automatically bind when inlined.",name='description'),
    meta(content='Matthew Braun',name='creator'),
    meta(content='20 September 2001',name='created'),
    meta(content='20 October 2019',name='modified'),
    meta(content='http://astronomy.swin.edu.au/pbourke/geometry/rotate/',name='reference'),
    meta(content='Copyright (c) Matthew Braun 2001',name='rights'),
    meta(content='Fog',name='subject'),
    meta(content='https://www.web3d.org/x3d/content/examples/X3dForWebAuthors/KelpForestExhibit/ChangingFog.x3d',name='identifier'),
    meta(content='X3D-Edit 3.3, https://www.web3d.org/x3d/tools/X3D-Edit',name='generator'),
    meta(content='../license.html',name='license')]),
  Scene=Scene(
    children=[
    WorldInfo(title='ChangingFog.x3d'),
    NavigationInfo(avatarSize=[0.01,0,0]),
    Viewpoint(description='Start',fieldOfView=0.9,position=(0,0,0)),
    Viewpoint(description='Looking up from start',orientation=(1,0,0,1.57),position=(0,0,0)),
    Viewpoint(description='10m above, looking straight up',orientation=(1,0,0,1.57),position=(0,10,0)),
    Viewpoint(description='10m above start',position=(0,10,0)),
    Viewpoint(description='10m above, looking straight down',orientation=(1,0,0,-1.57),position=(0,10,0)),
    Viewpoint(description='10m below, looking down',orientation=(1,0,0,-1.57),position=(0,-10,0)),
    Viewpoint(description='10m below start',position=(0,-10,0)),
    Viewpoint(description='10m below, looking up',orientation=(1,0,0,1.57),position=(0,-10,0)),
    Fog(DEF='Water',color=(0.2,0.2,0.4),fogType='EXPONENTIAL'),
    Comment('Proximity sensor must be large enough to encompass the entire scene'),
    ProximitySensor(DEF='ProxSensor',size=(1000,1000,1000)),
    Comment('TimeSensor triggering reduces frequency of calculations for performance reasons.'),
    TimeSensor(DEF='Clock',loop=True),
    Script(DEF='ChangeVisibility',
      field=[
      field(accessType='inputOnly',name='get_clock_hit',type='SFTime'),
      field(accessType='initializeOnly',name='run_script',type='SFBool',value=False),
      field(accessType='inputOnly',name='get_depth',type='SFVec3f'),
      field(accessType='outputOnly',name='visibility_changed',type='SFFloat'),
      field(accessType='inputOnly',name='set_visibility',type='SFRotation'),
      Comment('<field accessType=\'initializeOnly\' name=\'checked\' type=\'SFBool\' value=\'false\'/> <field accessType=\'initializeOnly\' name=\'moved\' type=\'SFBool\' value=\'false\'/>'),,

    sourceCode="""
ecmascript:
// REF: http://astronomy.swin.edu.au/pbourke/geometry/rotate/

function initialize () {
   visibility = 20;
   depth = 0;
   pos = (0,0,0);
   Browser.println ('Position output from ProximitySensor.');
}

function get_clock_hit (clock_msg) {
     run_script = true;
}

function get_depth ( position ) {

   pos = position;
   depth = position[1] - 30;   

}

function set_visibility( rotation ) {

 if (run_script) {

//z coordinate of the default viewpoint direction(0,0,-1)
   initZ = -1;  

   rX = rotation[0];  // x coordinate of the rotation
   rY = rotation[1];  // y coordinate of the rotation
   rZ = rotation[2];  // z coordinate of the rotation

   theta = rotation[3];  // angle of rotation in radians
	
Browser.println ('theta:' + theta);
     
   cosTheta = Math.cos(theta);
   sinTheta = Math.sin(theta);

Browser.println ('cosTheta:' + cosTheta + ' sinTheta:'+ sinTheta);


// calculate the y coordinate of the point after rotation
/* there are 8 other terms in the full conversion, but 6 are equal
to zero because of the choice of a starting point on the z-axis. The
other two are not calculated since all we need is the y coordinate
*/
   finalY = ((1 - cosTheta) * rY * rZ - rX * sinTheta) * initZ;

Browser.println ('final y:' + finalY);

//calculate the elevation/depression angle of the final point location

   elevation = Math.asin(finalY);

Browser.println ('elevation:' + elevation);

   directionFactor = 1 + 0.2 * (4 * elevation / Math.PI);
   depthAdjust = (60 + depth)/60
   depthFactor = Math.max(depthAdjust,0.05);

   visibility_changed =  60 * depthFactor * directionFactor; 
   Browser.println ('depth=' + depth + ', elevation=' + elevation + 
        ', visibility_changed=' + visibility_changed);
   run_script = false;
   
 }
}
"""),
    ROUTE(fromField='cycleTime',fromNode='Clock',toField='get_clock_hit',toNode='ChangeVisibility'),
    ROUTE(fromField='position_changed',fromNode='ProxSensor',toField='get_depth',toNode='ChangeVisibility'),
    ROUTE(fromField='orientation_changed',fromNode='ProxSensor',toField='set_visibility',toNode='ChangeVisibility'),
    ROUTE(fromField='visibility_changed',fromNode='ChangeVisibility',toField='visibilityRange',toNode='Water'),
    Comment('A set of arrows is used to show visibility and direction'),
    Transform(DEF='Pointer',translation=(0,0,-15),
      children=[
      Transform(translation=(0,4,0),
        children=[
        Shape(
          geometry=Cone(bottomRadius=0.4),
          appearance=Appearance(DEF='ShapeApp',
            material=Material(ambientIntensity=0.8,diffuseColor=(1,1,0.3),shininess=0.6)))]),
      Shape(
        geometry=Cylinder(height=6,radius=0.2),
        appearance=Appearance(USE='ShapeApp'))]),
    Transform(translation=(0,10,0),
      children=[
      Transform(USE='Pointer')]),
    Transform(translation=(0,-10,0),
      children=[
      Transform(USE='Pointer')]),
    Comment('A pair of disks used to show visibility'),
    Transform(translation=(0,15,0),
      children=[
      Shape(DEF='Disk',
        geometry=Cylinder(height=0.01),
        appearance=Appearance(USE='ShapeApp'))]),
    Transform(translation=(0,-15,0),
      children=[
      Shape(USE='Disk')]),
    Comment('An indexed face set box used to bound the working area'),
    Transform(scale=(20,20,20),
      children=[
      Shape(DEF='IFSBox',
        appearance=Appearance(
          material=Material(diffuseColor=(1,1,1))),
        geometry=IndexedFaceSet(ccw=False,colorIndex=[0,2,2,2,2,1],colorPerVertex=False,coordIndex=[0,1,2,3,-1,7,6,5,4,-1,0,4,5,1,-1,1,5,6,2,-1,2,6,7,3,-1,3,7,4,0],
          coord=Coordinate(point=[(-1.0,1.0,1.0),(1.0,1.0,1.0),(1.0,1.0,-1.0),(-1.0,1.0,-1.0),(-1.0,-1.0,1.0),(1.0,-1.0,1.0),(1.0,-1.0,-1.0),(-1.0,-1.0,-1.0)]),
          color=Color(color=[(1,1,1),(0,0,0),(0.2,0.2,0.8)])))])])
)

### X3D model conversion complete ###

####################################################################################################
# Self-test diagnostics
####################################################################################################

print('Self-test diagnostics for ChangingFog.py:')
if        metaDiagnostics(newModel): # built-in utility method in X3D class
    print(metaDiagnostics(newModel)) # display meta info, hint, warning, error, TODO values in this model
# print('check newModel.XML() serialization...')
newModelXML= newModel.XML() # test export method XML() for exceptions during export
newModel.XMLvalidate()
# print(newModelXML) # diagnostic

try:
#   print('check newModel.VRML() serialization...')
    newModelVRML=newModel.VRML() # test export method VRML() for exceptions during export
    # print(prependLineNumbers(newModelVRML)) # debug
    print("Python-to-VRML export of VRML output successful", flush=True)
except Exception as err: # usually BaseException
    # https://stackoverflow.com/questions/18176602/how-to-get-the-name-of-an-exception-that-was-caught-in-python
    print("*** Python-to-VRML export of VRML output failed:", type(err).__name__, err)
    if newModelVRML: # may have failed to generate
        print(prependLineNumbers(newModelVRML, err.lineno))

try:
#   print('check newModel.JSON() serialization...')
    newModelJSON=newModel.JSON() # test export method JSON() for exceptions during export
#   print(prependLineNumbers(newModelJSON)) # debug
    print("Python-to-JSON export of JSON output successful (under development)")
except Exception as err: # usually SyntaxError
    print("*** Python-to-JSON export of JSON output failed:", type(err).__name__, err)
    if newModelJSON: # may have failed to generate
        print(prependLineNumbers(newModelJSON,err.lineno))

print("python ChangingFog.py load and self-test diagnostics complete.")
