From adc1bcdfb84c247c3619dc243d630f91a96bdc98 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Thu, 16 Nov 2017 12:27:19 +1300 Subject: [PATCH 01/23] Animation etc updates. --- modules/mojo/graphics/glexts/glexts.cpp | 3 + modules/mojo/graphics/glexts/glexts.h | 1 + modules/mojo/graphics/glexts/glexts.monkey2 | 1 + modules/mojo/graphics/glutil.monkey2 | 2 + modules/mojo/graphics/shader.monkey2 | 2 +- modules/mojo3d-loaders/loaders/assimp.monkey2 | 94 +++++++-- modules/mojo3d-loaders/tests/castle.monkey2 | 30 +-- modules/mojo3d-loaders/tests/turtle.monkey2 | 50 +++-- .../assets/shaders/lighting-deferred.glsl | 13 +- .../assets/shaders/material-pbr-deferred.glsl | 8 +- .../assets/shaders/material-sprite.glsl | 10 +- modules/mojo3d/components/animation.monkey2 | 73 ++++++- modules/mojo3d/components/animator.monkey2 | 108 ++++++---- modules/mojo3d/entities/camera.monkey2 | 12 +- modules/mojo3d/entities/light.monkey2 | 6 +- modules/mojo3d/entities/model.monkey2 | 21 +- .../mojo3d/entities/particlesystem.monkey2 | 8 +- modules/mojo3d/entities/renderable.monkey2 | 6 +- modules/mojo3d/entities/sprite.monkey2 | 4 +- modules/mojo3d/geometry/loader.monkey2 | 5 + .../mojo3d/render/deferredrenderer.monkey2 | 6 +- modules/mojo3d/render/forwardrenderer.monkey2 | 2 +- .../render/materials/spritematerial.monkey2 | 11 + modules/mojo3d/render/renderer.monkey2 | 151 ++++++++------ modules/mojo3d/scene/component.monkey2 | 18 +- modules/mojo3d/scene/entity.monkey2 | 189 +++++++++--------- modules/mojo3d/scene/entityexts.monkey2 | 2 +- modules/mojo3d/tests/sprites.monkey2 | 15 +- 28 files changed, 549 insertions(+), 302 deletions(-) diff --git a/modules/mojo/graphics/glexts/glexts.cpp b/modules/mojo/graphics/glexts/glexts.cpp index b5f9980e..42648706 100644 --- a/modules/mojo/graphics/glexts/glexts.cpp +++ b/modules/mojo/graphics/glexts/glexts.cpp @@ -18,6 +18,7 @@ namespace bbGLexts{ bool GL_depth_texture; bool GL_seamless_cube_map; bool GL_texture_filter_anisotropic; + bool GL_standard_derivatives; PFNGLDRAWBUFFERSPROC glDrawBuffers; @@ -69,6 +70,8 @@ namespace bbGLexts{ GL_seamless_cube_map=SDL_GL_ExtensionSupported( "GL_ARB_seamless_cube_map" ); GL_texture_filter_anisotropic=SDL_GL_ExtensionSupported( "GL_EXT_texture_filter_anisotropic" ); + + GL_standard_derivatives=SDL_GL_ExtensionSupported( "GL_OES_standard_derivatives" ); // bb_printf( "GL_draw_buffers=%i\n",int( GL_draw_buffers ) ); // bb_printf( "GL_texture_float=%i\n",int( GL_texture_float ) ); diff --git a/modules/mojo/graphics/glexts/glexts.h b/modules/mojo/graphics/glexts/glexts.h index 4592e626..b4e1cce4 100644 --- a/modules/mojo/graphics/glexts/glexts.h +++ b/modules/mojo/graphics/glexts/glexts.h @@ -16,6 +16,7 @@ namespace bbGLexts{ extern bool GL_depth_texture; extern bool GL_seamless_cube_map; extern bool GL_texture_filter_anisotropic; + extern bool GL_standard_derivatives; typedef void (GL_APIENTRY *PFNGLDRAWBUFFERSPROC)( GLsizei n,const GLenum *bufs ); diff --git a/modules/mojo/graphics/glexts/glexts.monkey2 b/modules/mojo/graphics/glexts/glexts.monkey2 index 4fc45b8e..460e8ec2 100644 --- a/modules/mojo/graphics/glexts/glexts.monkey2 +++ b/modules/mojo/graphics/glexts/glexts.monkey2 @@ -56,6 +56,7 @@ Const GL_depth_texture:bool="bbGLexts::GL_depth_texture" Const GL_texture_float:Bool="bbGLexts::GL_texture_float" Const GL_texture_half_float:bool="bbGLexts::GL_texture_half_float" Const GL_texture_filter_anisotropic:Bool="bbGLexts::GL_texture_filter_anisotropic" +Const GL_standard_derivatives:Bool="bbGLexts::GL_standard_derivatives" Function glDrawBuffers( n:Int,bufs:GLenum Ptr )="bbGLexts::glDrawBuffers" diff --git a/modules/mojo/graphics/glutil.monkey2 b/modules/mojo/graphics/glutil.monkey2 index 4134a2ef..557d7d76 100644 --- a/modules/mojo/graphics/glutil.monkey2 +++ b/modules/mojo/graphics/glutil.monkey2 @@ -151,6 +151,8 @@ precision mediump float; If glexts.GL_draw_buffers source="#extension GL_EXT_draw_buffers : require~n"+source + If glexts.GL_standard_derivatives source="#extension GL_OES_standard_derivatives : require~n"+source + #ElseIf __TARGET__="macos" or __TARGET__="linux" Const prefix:=" diff --git a/modules/mojo/graphics/shader.monkey2 b/modules/mojo/graphics/shader.monkey2 index c9bc1167..af556862 100644 --- a/modules/mojo/graphics/shader.monkey2 +++ b/modules/mojo/graphics/shader.monkey2 @@ -160,7 +160,7 @@ Class GLProgram If size>1 size=ublock.GetMat4fArray( u.uniformId ).Length - glUniformMatrix4fv( u.location,u.size,False,ublock.GetMat4fv( u.uniformId ) ) + glUniformMatrix4fv( u.location,size,False,ublock.GetMat4fv( u.uniformId ) ) Case GL_SAMPLER_2D,GL_SAMPLER_CUBE diff --git a/modules/mojo3d-loaders/loaders/assimp.monkey2 b/modules/mojo3d-loaders/loaders/assimp.monkey2 index aafea007..1fe8e272 100644 --- a/modules/mojo3d-loaders/loaders/assimp.monkey2 +++ b/modules/mojo3d-loaders/loaders/assimp.monkey2 @@ -96,12 +96,37 @@ Class AssimpLoader Method LoadBonedModel:Model() - Local model:=LoadNode( _scene.mRootNode,Null,True ) +' #rem + Local model:=New Model + +' _nodes[""]=model +' _entityIds[""]=_entities.Length +' _entities.Add( model ) + + LoadNode( _scene.mRootNode,model ) +' #end + +' Local model:=LoadNode( _scene.mRootNode,Null ) LoadAnimator( model ) Return model End + + Method LoadAnimation:Animation() + + If Not _scene.mNumAnimations Return Null + + _nodes[""]=Null + _entityIds[""]=_entities.Length + _entities.Add( Null ) + + EnumEntityIds( _scene.mRootNode ) + + Local animation:=LoadAnimation( _scene.mAnimations[0] ) + + Return animation + End Private @@ -155,7 +180,7 @@ Class AssimpLoader End Method LoadMesh( aimesh:aiMesh,mesh:Mesh,model:Model,boned:bool ) - + Local vertices:=New Vertex3f[ aimesh.mNumVertices ] Local vp:=aimesh.mVertices @@ -195,7 +220,7 @@ Class AssimpLoader If index<_materials.Length And _materials[index] Return _materials[index] If index>=_materials.Length _materials.Resize( index+1 ) - + _materials[index]=LoadMaterial( _scene.mMaterials[index],boned ) Return _materials[index] @@ -229,7 +254,17 @@ Class AssimpLoader Return material End - Method LoadNode:Model( node:aiNode,parent:Model,boned:bool ) + Method EnumEntityIds( node:aiNode ) + + _entityIds[ node.mName.data ]=_entityIds.Count() + + For Local i:=0 Until node.mNumChildren + + EnumEntityIds( node.mChildren[i] ) + Next + End + + Method LoadNode:Model( node:aiNode,parent:Model ) Local model:=New Model( parent ) @@ -251,7 +286,7 @@ Class AssimpLoader For Local i:=0 Until node.mNumChildren - LoadNode( node.mChildren[i],model,boned ) + LoadNode( node.mChildren[i],model ) Next Local mesh:=New Mesh @@ -264,9 +299,9 @@ Class AssimpLoader mesh.AddMaterials( 1 ) - LoadMesh( aimesh,mesh,model,boned ) + LoadMesh( aimesh,mesh,model,aimesh.mNumBones>0 ) - materials.Push( LoadMaterial( aimesh,boned ) ) + materials.Push( LoadMaterial( aimesh,aimesh.mNumBones>0 ) ) Next If materials.Length @@ -312,9 +347,11 @@ Class AssimpLoader Method LoadAnimation:Animation( aianim:aiAnimation ) - Local channels:=New AnimationChannel[ _entities.Length ] - -' Print "Num anim channels="+aianim.mNumChannels +' Print "_entities.Length="+_entities.Length +' Print "_entityIds.Count="+_entityIds.Count() +' Print "mNumChannels="+aianim.mNumChannels + + Local channels:=New AnimationChannel[ _entityIds.Count() ] For Local i:=0 Until aianim.mNumChannels @@ -322,7 +359,11 @@ Class AssimpLoader Local id:=_entityIds[ aichan.mNodeName.data ] - channels[id]=LoadAnimationChannel( aichan ) + Local channel:=LoadAnimationChannel( aichan ) + + channels[id]=channel + +' Print "channel "+id+", numposkeys="+channel.PositionKeys.Length+", numrotkeys="+channel.RotationKeys.Length+", numsclkeys="+channel.ScaleKeys.Length Next @@ -340,11 +381,11 @@ Class AssimpLoader animations[i]=LoadAnimation( _scene.mAnimations[i] ) Next - Local animator:=entity.AddComponent() + Local animator:=New Animator( entity ) - animator.Animations=animations + animator.Skeleton=_entities.ToArray() - animator.Entities=_entities.ToArray() + animator.Animations.AddAll( animations ) Return animator End @@ -429,6 +470,31 @@ Class AssimpMojo3dLoader Extends Mojo3dLoader Return model End + Method LoadAnimation:Animation( path:String ) Override + + Local flags:UInt=0 + + flags|=aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs + 'flags|=aiProcess_JoinIdenticalVertices | aiProcess_RemoveRedundantMaterials | aiProcess_FindDegenerates | aiProcess_SortByPType + flags|=aiProcess_JoinIdenticalVertices | aiProcess_RemoveRedundantMaterials | aiProcess_SortByPType +' flags|=aiProcess_GenSmoothNormals | aiProcess_FixInfacingNormals | aiProcess_Triangulate + flags|=aiProcess_GenSmoothNormals |aiProcess_Triangulate +' flags|=aiProcess_SplitByBoneCount + flags|=aiProcess_LimitBoneWeights + flags|=aiProcess_FindInvalidData + flags|=aiProcess_OptimizeMeshes +' flags|=aiProcess_OptimizeGraph 'fails quite spectacularly! + + Local scene:=LoadScene( path,flags ) + If Not scene Return Null + + Local loader:=New AssimpLoader( scene,ExtractDir( path ) ) + + Local animation:=loader.LoadAnimation() + + Return animation + End + Private Function LoadScene:aiScene( path:String,flags:UInt ) diff --git a/modules/mojo3d-loaders/tests/castle.monkey2 b/modules/mojo3d-loaders/tests/castle.monkey2 index 5f6bc9fd..a414ab5b 100644 --- a/modules/mojo3d-loaders/tests/castle.monkey2 +++ b/modules/mojo3d-loaders/tests/castle.monkey2 @@ -5,8 +5,6 @@ Namespace myapp #Import "" #Import "" -#Import "../mojo3d" - #Import "assets/" #Import "../../mojo3d/tests/assets/miramar-skybox.jpg" @@ -43,46 +41,36 @@ Class MyWindow Extends Window _camera=New Camera _camera.Near=.1 _camera.Far=100 - _camera.Move( 0,10,-20 ) + _camera.Move( 0,10,-10 ) + + New FlyBehaviour( _camera ) 'create light ' _light=New Light - _light.Rotate( 60,80,0 ) 'aim directional light 'down' - Pi/2=90 degrees. + _light.Rotate( 60,30,0 ) 'aim directional light 'down' - Pi/2=90 degrees. + _light.CastsShadow=True 'create ground ' _ground=Model.CreateBox( New Boxf( -50,-1,-50,50,0,50 ),8,1,8,New PbrMaterial( Color.Green*.1,0,1 ) ) + _ground.CastsShadow=False 'create model ' _model=Model.Load( "asset::castle/CASTLE1.X" ) -' _model=Model.Load( "desktop::Temple.3DS" ) -' _model=Model.Load( "desktop::FairyHouse/FairyHouse.3DS" ) - - For Local material:=Eachin _model.Materials - material.CullMode=CullMode.None - Next - Const sz:=30 + Const cheight:=30.0 - _model.Mesh.FitVertices( New Boxf( -10000,0,-10000,10000,sz,10000 ),True ) - - _model.Position=Null - - _camera.Position=New Vec3f( 0,10,-10 ) + _model.Mesh.FitVertices( New Boxf( -10000,0,-10000,10000,cheight,10000 ),True ) End Method OnRender( canvas:Canvas ) Override - Global time:=0.0 - RequestRender() - util.Fly( _camera,Self ) + _scene.Update() - If Keyboard.KeyDown( Key.Space ) time+=12.0/60.0 - _scene.Render( canvas,_camera ) canvas.Scale( Width/640.0,Height/480.0 ) diff --git a/modules/mojo3d-loaders/tests/turtle.monkey2 b/modules/mojo3d-loaders/tests/turtle.monkey2 index 7b863c1f..158a660b 100644 --- a/modules/mojo3d-loaders/tests/turtle.monkey2 +++ b/modules/mojo3d-loaders/tests/turtle.monkey2 @@ -6,9 +6,8 @@ Namespace myapp #Import "" #Import "" -#Import "../mojo3d" - -#Import "assets/" +#Import "assets/turtle1.b3d" +#Import "assets/turtle1.png" #Import "util" @@ -44,10 +43,18 @@ Class MyWindow Extends Window _camera.Far=100 _camera.Move( 0,10,-20 ) + New FlyBehaviour( _camera ) + 'create light ' _light=New Light - _light.Rotate( Pi/2,0,0 ) 'aim directional light 'down' - Pi/2=90 degrees. + _light.Rotate( 75,15,0 ) 'aim directional light 'down' - Pi/2=90 degrees. + _light.CastsShadow=True + + 'create ground + ' + _ground=Model.CreateBox( New Boxf( -50,-5,-50,50,0,50 ),1,1,1,New PbrMaterial( Color.Green ) ) + _ground.CastsShadow=False 'create ground ' @@ -56,28 +63,31 @@ Class MyWindow Extends Window 'create turtle ' _turtle=Model.LoadBoned( "asset::turtle1.b3d" ) - - Local animator:=_turtle.GetComponent() - - animator.Paused=False - + _turtle.Scale=New Vec3f( .125 ) + + Local walk:=_turtle.Animator.Animations[0].Slice( 1,11 ) + _turtle.Animator.Animations.Add( walk ) + + For Local i:=0 Until 360 Step 15 + + Local copy:=_turtle.Copy() + + copy.RotateY( i ) + copy.MoveZ( 12 ) + + copy.Animator.Animate( 0,Rnd(.5,1.0) ) + + Next + + _turtle.Animator.Animate( 1,1 ) End Method OnRender( canvas:Canvas ) Override - Global time:=0.0 - RequestRender() - - util.Fly( _camera,Self ) - - If Keyboard.KeyHit( Key.Space ) - Local animator:=_turtle.GetComponent() - - animator.Paused=Not animator.Paused - Endif + If Keyboard.KeyDown( Key.Space ) _turtle.Animator.Time+=.01 _scene.Update() @@ -85,7 +95,7 @@ Class MyWindow Extends Window canvas.Scale( Width/640.0,Height/480.0 ) - canvas.DrawText( "Width="+Width+", Height="+Height+", FPS="+App.FPS,0,0 ) + canvas.DrawText( "Width="+Width+", Height="+Height+", anim time="+_turtle.Animator.Time+", FPS="+App.FPS,0,0 ) End End diff --git a/modules/mojo3d/assets/shaders/lighting-deferred.glsl b/modules/mojo3d/assets/shaders/lighting-deferred.glsl index fab14504..ae6580cb 100644 --- a/modules/mojo3d/assets/shaders/lighting-deferred.glsl +++ b/modules/mojo3d/assets/shaders/lighting-deferred.glsl @@ -82,9 +82,16 @@ float viewDepth( float depth ){ float shadowColor(){ -// vec4 vpos=vec4( v_Position + v_Normal * .05,1.0 ); - vec4 vpos=vec4( v_Position + v_Normal * .06,1.0 ); - vec4 lpos; +// vec3 dx=dFdx( v_Position ); +// vec3 dy=dFdy( v_Position ); +// vec3 vn=vec4( cross( dx,dy ),0.0 ); +// vec4 vpos=vec4( v_Position + v_Normal*0.01,1.0 ); + + vec4 vpos=vec4( v_Position,1.0 ); + + if( vpos.z>=r_ShadowCSMSplits.w ) return 0.5; + + vec4 llpos,lpos; vec2 off; if( vpos.z Class Animation Method New( channels:AnimationChannel[],duration:Float,hertz:Float ) - _channels=channels - _duration=duration - - _hertz=hertz + _hertz=hertz ?Else 24 End + #rem monkeydoc Animation channels. + + There is a channel for each bone in the animation's skeleton. + + #end Property Channels:AnimationChannel[]() Return _channels End + #rem monkeydoc Duration. + + The duration of the animation in seconds + + #end Property Duration:Float() Return _duration End + #rem monkeydoc Hertz. + + The frequency of the animation in seconds + + #end Property Hertz:Float() Return _hertz End + Method Slice:Animation( begin:Float,term:Float ) + + Local newchannels:=New Stack + + For Local channel:=Eachin _channels + + If Not channel + newchannels.Add( Null ) + Continue + End + + Local posKeys:=New Stack + For Local key:=Eachin channel.PositionKeys + If key.Time>term Exit + If key.Time>=begin posKeys.Add( New PositionKey( key.Time-begin,key.Value ) ) + Next + + Local rotKeys:=New Stack + For Local key:=Eachin channel.RotationKeys + If key.Time>term Exit + If key.Time>=begin rotKeys.Add( New RotationKey( key.Time-begin,key.Value ) ) + Next + + Local sclKeys:=New Stack + For Local key:=Eachin channel.ScaleKeys + If key.Time>term Exit + If key.Time>=begin sclKeys.Add( New ScaleKey( key.Time-begin,key.Value ) ) + Next + + Local newchannel:=New AnimationChannel( posKeys.ToArray(),rotKeys.ToArray(),sclKeys.ToArray() ) + newchannels.Add( newchannel ) + + Next + + Local animation:=New Animation( newchannels.ToArray(),term-begin,_hertz ) + + Return animation + + End + + Function Load:Animation( path:String ) + + For Local loader:=Eachin Mojo3dLoader.Instances + + Local animation:=loader.LoadAnimation( path ) + + If animation Return animation + Next + + Return Null + End + Private Field _channels:AnimationChannel[] Field _duration:Float Field _hertz:Float - End #rem monkeydoc @hidden diff --git a/modules/mojo3d/components/animator.monkey2 b/modules/mojo3d/components/animator.monkey2 index 5077b829..d6af9fe7 100644 --- a/modules/mojo3d/components/animator.monkey2 +++ b/modules/mojo3d/components/animator.monkey2 @@ -1,6 +1,15 @@ Namespace mojo3d +Class Entity Extension + + Property Animator:Animator() + + Return GetComponent() + End + +End + #rem monkeydoc The Animator class. #end Class Animator Extends Component @@ -8,26 +17,44 @@ Class Animator Extends Component Const Type:=New ComponentType( "Animator",0,ComponentTypeFlags.Singleton ) Method New( entity:Entity ) - Super.New( entity,Type ) End - Property Animations:Animation[]() + Method New( entity:Entity,animator:Animator ) + Self.New( entity ) + + _skeleton=animator._skeleton.Slice( 0 ) + For Local i:=0 Until _skeleton.Length + _skeleton[i]=_skeleton[i].LastCopy + End + _animations=animator._animations + _playing=animator._playing + _paused=animator._paused + _speed=animator._speed + _time=animator._time + End + + Property Skeleton:Entity[]() + + Return _skeleton + + Setter( skeleton:Entity[] ) + + _skeleton=skeleton + End + + Property Animations:Stack() Return _animations - Setter( animations:Animation[] ) + Setter( animations:Stack ) _animations=animations End - Property Entities:Entity[]() - - Return _entities - - Setter( entities:Entity[] ) + Property Playing:Bool() - _entities=entities + Return _playing<>Null End Property Paused:Bool() @@ -57,45 +84,58 @@ Class Animator Extends Component _time=time End - Method Animate( animationId:Int,time:Float ) + Method Animate( animationId:Int,speed:Float=1.0 ) - Local animation:=_animations[animationId] - - For Local i:=0 Until animation.Channels.Length - - Local channel:=animation.Channels[i] - If Not channel continue - - _entities[i].LocalPosition=channel.GetPosition( time ) - _entities[i].LocalBasis=New Mat3f( channel.GetRotation( time ) ) - _entities[i].LocalScale=channel.GetScale( time ) - End + DebugAssert( animationId>=0 And animationId<_animations.Length,"Animation id out of range" ) + + _playing=_animations[animationId] + + _speed=speed + _time=0 End - Method OnUpdate( elapsed:Float ) Override + Method Stop() - If _paused or not _animations Return + _playing=Null + End + + Protected + + Method OnCopy:Animator( entity:Entity ) Override - Local anim:=_animations[0] + Return New Animator( entity,Self ) + End + + Method OnUpdate( elapsed:Float ) Override - _time+=anim.Hertz*elapsed + If _paused Or Not _playing Return - If _time>=anim.Duration _time-=anim.Duration Else If _time<0 _time+=anim.Duration + Local hertz:=_playing.Hertz + Local timeScale:=1.0/hertz + + _time+=elapsed * _speed + + If _time>=_playing.Duration * timeScale _time-=_playing.Duration * timeScale Else If _time<0 _time+=_playing.Duration * timeScale + + For Local i:=0 Until _playing.Channels.Length + + Local channel:=_playing.Channels[i] + If Not channel continue - Animate( 0,_time ) + _skeleton[i].LocalPosition=channel.GetPosition( _time * hertz ) + _skeleton[i].LocalBasis=New Mat3f( channel.GetRotation( _time * hertz ) ) + _skeleton[i].LocalScale=channel.GetScale( _time * hertz ) + End End Private - - Field _animations:Animation[] - - Field _entities:Entity[] - - Field _paused:Bool=True + Field _skeleton:Entity[] + Field _animations:=New Stack + Field _playing:Animation + Field _paused:Bool=False Field _speed:Float=1 - Field _time:Float=0 End diff --git a/modules/mojo3d/entities/camera.monkey2 b/modules/mojo3d/entities/camera.monkey2 index bebe205f..431de87d 100644 --- a/modules/mojo3d/entities/camera.monkey2 +++ b/modules/mojo3d/entities/camera.monkey2 @@ -15,7 +15,7 @@ Class Camera Extends Entity Far=1000 FOV=90 - Show() + Visible=True End #rem monkeydoc Copies the camera. @@ -24,7 +24,7 @@ Class Camera Extends Entity Local copy:=New Camera( Self,parent ) - CopyComplete( copy ) + CopyTo( copy ) Return copy End @@ -144,8 +144,6 @@ Class Camera Extends Entity Protected - #rem monkeydoc @hidden - #end Method New( camera:Camera,parent:Entity ) Super.New( camera,parent ) @@ -153,19 +151,13 @@ Class Camera Extends Entity Near=camera.Near Far=camera.Far FOV=camera.FOV - - Show() End - #rem monkeydoc @hidden - #end Method OnShow() Override Scene.Cameras.Add( Self ) End - #rem monkeydoc @hidden - #end Method OnHide() Override Scene.Cameras.Remove( Self ) diff --git a/modules/mojo3d/entities/light.monkey2 b/modules/mojo3d/entities/light.monkey2 index 41817d45..45293e84 100644 --- a/modules/mojo3d/entities/light.monkey2 +++ b/modules/mojo3d/entities/light.monkey2 @@ -31,7 +31,7 @@ Class Light Extends Entity Range=10 CastsShadow=False - Show() + Visible=True End #rem monkeydoc Copies the light. @@ -40,7 +40,7 @@ Class Light Extends Entity Local copy:=New Light( Self,parent ) - CopyComplete( copy ) + CopyTo( copy ) Return copy End @@ -99,8 +99,6 @@ Class Light Extends Entity Type=light.Type Color=light.Color Range=light.Range - - Show() End #rem monkeydoc @hidden diff --git a/modules/mojo3d/entities/model.monkey2 b/modules/mojo3d/entities/model.monkey2 index 1f552cc3..2d10264d 100644 --- a/modules/mojo3d/entities/model.monkey2 +++ b/modules/mojo3d/entities/model.monkey2 @@ -17,15 +17,16 @@ Class Model Extends Renderable Method New( parent:Entity=Null ) Super.New( parent ) - Show() + Visible=True End Method New( mesh:Mesh,material:Material,parent:Entity=Null ) - Self.New( parent ) + super.New( parent ) _mesh=mesh + _materials=New Material[]( material ) - _material=material + Visible=True End #rem monkeydoc Copies the model. @@ -34,7 +35,14 @@ Class Model Extends Renderable Local copy:=New Model( Self,parent ) - CopyComplete( copy ) + CopyTo( copy ) + + copy._bones=_bones.Slice( 0 ) + + For Local i:=0 Until _bones.Length + + copy._bones[i].entity=_bones[i].entity.LastCopy + Next Return copy End @@ -109,9 +117,8 @@ Class Model Extends Renderable Next Return Null - End - + #rem monkeydoc Loads a boned model from a file path. On its own, mojo3d can only load gltf2 format mesh and model files. @@ -159,7 +166,7 @@ Class Model Extends Renderable If Not _mesh Return If _bones - + If _boneMatrices.Length<>_bones.Length _boneMatrices=New Mat4f[ _bones.Length ] For Local i:=0 Until _bones.Length diff --git a/modules/mojo3d/entities/particlesystem.monkey2 b/modules/mojo3d/entities/particlesystem.monkey2 index a34ef6b4..d7401d85 100644 --- a/modules/mojo3d/entities/particlesystem.monkey2 +++ b/modules/mojo3d/entities/particlesystem.monkey2 @@ -9,16 +9,18 @@ Class ParticleSystem Extends Renderable Super.New( parent ) _pbuffer=New ParticleBuffer( particleCount ) - _material=New ParticleMaterial + + Visible=True End Method New( particleBuffer:ParticleBuffer,material:ParticleMaterial,parent:Entity=Null ) Super.New( parent ) _pbuffer=particleBuffer - _material=material + + Visible=True End #rem monkeydoc Copies the particle system. @@ -27,7 +29,7 @@ Class ParticleSystem Extends Renderable Local copy:=New ParticleSystem( Self,parent ) - CopyComplete( copy ) + CopyTo( copy ) Return copy End diff --git a/modules/mojo3d/entities/renderable.monkey2 b/modules/mojo3d/entities/renderable.monkey2 index 8ba8403e..ac2cac16 100644 --- a/modules/mojo3d/entities/renderable.monkey2 +++ b/modules/mojo3d/entities/renderable.monkey2 @@ -4,10 +4,8 @@ Namespace mojo3d Class Renderable Extends Entity Abstract Method New( parent:Entity=Null ) - Super.New( parent ) - - Show() + End Property CastsShadow:bool() @@ -26,8 +24,6 @@ Class Renderable Extends Entity Abstract Super.New( renderable,parent ) _castsShadow=renderable.CastsShadow - - Show() End Method OnShow() Override diff --git a/modules/mojo3d/entities/sprite.monkey2 b/modules/mojo3d/entities/sprite.monkey2 index 278a6e3b..f0ae8a09 100644 --- a/modules/mojo3d/entities/sprite.monkey2 +++ b/modules/mojo3d/entities/sprite.monkey2 @@ -28,6 +28,8 @@ Class Sprite Extends Renderable Self.New( parent ) _material=material + + Visible=True End #rem monkeydoc Copies the sprite. @@ -36,7 +38,7 @@ Class Sprite Extends Renderable Local copy:=New Sprite( Self,parent ) - CopyComplete( copy ) + CopyTo( copy ) Return copy End diff --git a/modules/mojo3d/geometry/loader.monkey2 b/modules/mojo3d/geometry/loader.monkey2 index 3ef6d397..52c3e8ce 100644 --- a/modules/mojo3d/geometry/loader.monkey2 +++ b/modules/mojo3d/geometry/loader.monkey2 @@ -26,5 +26,10 @@ Class Mojo3dLoader Return Null End + + Method LoadAnimation:Animation( path:String ) Virtual + + Return Null + End End diff --git a/modules/mojo3d/render/deferredrenderer.monkey2 b/modules/mojo3d/render/deferredrenderer.monkey2 index 45463209..0c5157ec 100644 --- a/modules/mojo3d/render/deferredrenderer.monkey2 +++ b/modules/mojo3d/render/deferredrenderer.monkey2 @@ -165,7 +165,7 @@ Class DeferredRenderer Extends Renderer Method RenderTransparent() _device.ColorMask=ColorMask.All - _device.DepthMask=false + _device.DepthMask=True'false _device.DepthFunc=DepthFunc.LessEqual _device.RenderPass=0 @@ -173,9 +173,9 @@ Class DeferredRenderer Extends Renderer End Method RenderSprites() - + _device.ColorMask=ColorMask.All - _device.DepthMask=False + _device.DepthMask=True'False _device.DepthFunc=DepthFunc.LessEqual _device.RenderPass=0 diff --git a/modules/mojo3d/render/forwardrenderer.monkey2 b/modules/mojo3d/render/forwardrenderer.monkey2 index cbbd6c77..dd2e2b19 100644 --- a/modules/mojo3d/render/forwardrenderer.monkey2 +++ b/modules/mojo3d/render/forwardrenderer.monkey2 @@ -186,7 +186,7 @@ Class ForwardRenderer Extends Renderer Method RenderSprites() _device.ColorMask=ColorMask.All - _device.DepthMask=False + _device.DepthMask=True'False _device.DepthFunc=DepthFunc.LessEqual _device.RenderPass=0 diff --git a/modules/mojo3d/render/materials/spritematerial.monkey2 b/modules/mojo3d/render/materials/spritematerial.monkey2 index 6294fe58..ec92e859 100644 --- a/modules/mojo3d/render/materials/spritematerial.monkey2 +++ b/modules/mojo3d/render/materials/spritematerial.monkey2 @@ -19,6 +19,8 @@ Class SpriteMaterial Extends Material ColorTexture=Texture.ColorTexture( Color.White ) ColorFactor=Color.White + + AlphaDiscard=.5 End Method New( material:SpriteMaterial ) @@ -50,6 +52,15 @@ Class SpriteMaterial Extends Material Uniforms.SetColor( "ColorFactor",color ) End + + Property AlphaDiscard:Float() + + Return Uniforms.GetFloat( "AlphaDiscard" ) + + Setter( discard:Float ) + + Uniforms.SetFloat( "AlphaDiscard",discard ) + End #rem monkeydoc Loads a sprite material from an image file. #end diff --git a/modules/mojo3d/render/renderer.monkey2 b/modules/mojo3d/render/renderer.monkey2 index 7c22e8e8..569ecde4 100644 --- a/modules/mojo3d/render/renderer.monkey2 +++ b/modules/mojo3d/render/renderer.monkey2 @@ -22,7 +22,7 @@ Class Renderer #rem monkeydoc Array containing the cascaded shadow map frustum splits for directional light shadows. - Defaults to Float[]( 1.0/64.0,1.0/16.0,1.0/4.0 ) + Defaults to Float[]( 8.0,16.0,64.0,256.0 ) Must have length 3. @@ -32,7 +32,7 @@ Class Renderer Return _csmSplits Setter( splits:Float[] ) - Assert( splits.Length=3,"CSMSplits array must have 3 elements" ) + Assert( splits.Length=4,"CSMSplits array must have 4 elements" ) _csmSplits=splits.Slice( 0 ) End @@ -99,10 +99,9 @@ Class Renderer ValidateShadowMaps() _csmSplitDepths[0]=camera.Near - For Local i:=1 Until 4 - _csmSplitDepths[i]=camera.Near+_csmSplits[i-1]*(camera.Far-camera.Near) + For Local i:=1 Until 5 + _csmSplitDepths[i]=_csmSplitDepths[i-1]+_csmSplits[i-1] Next - _csmSplitDepths[4]=camera.Far Local time:=Float( Now() ) @@ -146,6 +145,20 @@ Class Renderer r.OnRender( _renderQueue ) Next + Local ops:=_renderQueue.OpaqueOps + +#rem + ops.Sort( Lambda:Int( x:RenderOp,y:RenderOp ) + If x.instancey.instance Return 1 + If x.materialy.material Return 1 + If xy Return 1 + Return 0 + End ) +#end + '***** Set render camera ***** _renderCamera=camera @@ -211,19 +224,6 @@ Class Renderer New Mat3f( +1,0, 0, 0,0,-1, 0,-1,0 ), '-Y New Mat3f( +1,0, 0, 0,-1,0, 0,0,+1 ), '+Z New Mat3f( -1,0, 0, 0,-1,0, 0,0,-1 ) ) '-Z - - #rem - Matxf tforms[]={ - { {0,0,+1},{0,-1,0},{-1,0,0},{0,0,0} }, //+X - { {0,0,-1},{0,-1,0},{+1,0,0},{0,0,0} }, //-X - { {+1,0,0},{0,0,+1},{0,+1,0},{0,0,0} }, //+Y test me! - { {+1,0,0},{0,0,-1},{0,-1,0},{0,0,0} }, //-Y - { {+1,0,0},{0,-1,0},{0,0,+1},{0,0,0} }, //+Z - { {-1,0,0},{0,-1,0},{0,0,-1},{0,0,0} } //-Z - }; - - #end - End @@ -304,29 +304,6 @@ Class Renderer RenderRenderOps( _renderQueue.SpriteOps,_renderCamera.InverseMatrix,_renderCamera.ProjectionMatrix ) End - #rem - Method RenderAmbient() - - _device.ColorMask=ColorMask.All - _device.DepthMask=True - _device.DepthFunc=DepthFunc.LessEqual - _device.BlendMode=BlendMode.Opaque - _device.RenderPass=1 - - RenderRenderOps( _renderQueue.OpaqueOps,_renderCamera.InverseMatrix,_renderCamera.ProjectionMatrix ) - End - - Method RenderSprites() - - _device.ColorMask=ColorMask.All - _device.DepthMask=False - _device.DepthFunc=DepthFunc.LessEqual - _device.RenderPass=0 - - RenderRenderOps( _spriteQueue.TransparentOps,_renderCamera.InverseMatrix,_renderCamera.ProjectionMatrix ) - End - #end - Method RenderCSMShadows( light:Light ) 'Perhaps use a different device for CSM...? @@ -341,13 +318,10 @@ Class Renderer _device.ColorMask=ColorMask.All _device.DepthMask=True _device.Clear( Color.White,1.0 ) -' _device.ColorMask=ColorMask.None -' _device.DepthMask=True -' _device.Clear( Null,1.0 ) _device.DepthFunc=DepthFunc.LessEqual _device.BlendMode=BlendMode.Opaque - _device.CullMode=CullMode.Back + _device.CullMode=CullMode.Front 'CullMode.Back _device.RenderPass=16 Local invLightMatrix:=light.InverseMatrix @@ -394,7 +368,7 @@ Class Renderer _device.Scissor=_device.Viewport - RenderRenderOps( _renderQueue.ShadowOps,invLightMatrix,lightProj ) + RenderShadowOps( _renderQueue.ShadowOps,invLightMatrix,lightProj ) Next @@ -415,10 +389,10 @@ Class Renderer _device.Scissor=_device.Viewport _device.ColorMask=ColorMask.All _device.DepthMask=True + _device.DepthFunc=DepthFunc.LessEqual - ' _device.BlendMode=BlendMode.Opaque - _device.CullMode=CullMode.Back + _device.CullMode=CullMode.Front _device.RenderPass=17 Local lightProj:=Mat4f.Frustum( -1,+1,-1,+1,1,light.Range ) @@ -437,7 +411,7 @@ Class Renderer Local viewMatrix:=New AffineMat4f( _psmFaceTransforms[i] ) * invLightMatrix - RenderRenderOps( _renderQueue.ShadowOps,viewMatrix,lightProj ) + RenderShadowOps( _renderQueue.ShadowOps,viewMatrix,lightProj ) Next @@ -475,8 +449,8 @@ Class Renderer Field _psmFaceTransforms:Mat3f[] - Field _csmSize:=2048 - Field _csmSplits:=New Float[]( 1.0/64.0,1.0/16.0,1.0/4.0 ) + Field _csmSize:=4096 + Field _csmSplits:=New Float[]( 8.0,16.0,64.0,256.0 ) Field _csmSplitDepths:=New Float[5] Field _csmTexture:Texture Field _csmDepth:Texture @@ -547,49 +521,97 @@ Class Renderer Method RenderRenderOps( ops:Stack,viewMatrix:AffineMat4f,projMatrix:Mat4f ) + Local viewProjMatrix:=projMatrix * viewMatrix + _runiforms.SetMat4f( "ViewMatrix",viewMatrix ) _runiforms.SetMat4f( "ProjectionMatrix",projMatrix ) - _runiforms.SetMat4f( "ViewProjectionMatrix",projMatrix * viewMatrix ) + _runiforms.SetMat4f( "ViewProjectionMatrix",viewProjMatrix ) _runiforms.SetMat4f( "InverseProjectionMatrix",-projMatrix ) + '_iuniforms.SetMat4fArray( "ModelBoneMatrices",Null ) + Local instance:Entity=_renderCamera + Local bones:Mat4f[] Local material:Material For Local op:=Eachin ops If op.instance<>instance - instance=op.instance - - Local modelMat:= instance ? instance.Matrix Else New AffineMat4f + Local modelMat:=instance ? instance.Matrix Else New AffineMat4f Local modelViewMat:=viewMatrix * modelMat + Local modelViewNormMat:=modelViewMat.m.Cofactor() Local modelViewProjMat:=projMatrix * modelViewMat - Local modelViewNormMat:=~-modelViewMat.m - _iuniforms.SetMat4f( "ModelMatrix",modelMat ) _iuniforms.SetMat4f( "ModelViewMatrix",modelViewMat ) - _iuniforms.SetMat4f( "ModelViewProjectionMatrix",modelViewProjMat ) _iuniforms.SetMat3f( "ModelViewNormalMatrix",modelViewNormMat ) + _iuniforms.SetMat4f( "ModelViewProjectionMatrix",modelViewProjMat ) + Endif - _iuniforms.SetMat4fArray( "ModelBoneMatrices",op.bones ) + If op.bones _iuniforms.SetMat4fArray( "ModelBoneMatrices",op.bones ) - Endif - + If op.uniforms _device.BindUniformBlock( op.uniforms ) + If op.material<>material - material=op.material - _device.Shader=material.ValidateShader() _device.BindUniformBlock( material.Uniforms ) If material.BlendMode<>BlendMode.Opaque _device.BlendMode=material.BlendMode Endif _device.CullMode=material.CullMode - Endif + _device.VertexBuffer=op.vbuffer + If op.ibuffer + _device.IndexBuffer=op.ibuffer + _device.RenderIndexed( op.order,op.count,op.first ) + Else + _device.Render( op.order,op.count,op.first ) + Endif + + Next + End + + Method RenderShadowOps( ops:Stack,viewMatrix:AffineMat4f,projMatrix:Mat4f ) + + Local viewProjMatrix:=projMatrix * viewMatrix + + _runiforms.SetMat4f( "ViewMatrix",viewMatrix ) + _runiforms.SetMat4f( "ProjectionMatrix",projMatrix ) + _runiforms.SetMat4f( "ViewProjectionMatrix",viewProjMatrix ) + _runiforms.SetMat4f( "InverseProjectionMatrix",-projMatrix ) + + '_iuniforms.SetMat4fArray( "ModelBoneMatrices",Null ) + + Local instance:Entity=_renderCamera + Local bones:Mat4f[] + Local material:Material + + For Local op:=Eachin ops + + If op.instance<>instance + instance=op.instance + Local modelMat:=instance ? instance.Matrix Else New AffineMat4f + Local modelViewMat:=viewMatrix * modelMat + Local modelViewNormMat:=modelViewMat.m.Cofactor() + Local modelViewProjMat:=projMatrix * modelViewMat + _iuniforms.SetMat4f( "ModelMatrix",modelMat ) + _iuniforms.SetMat4f( "ModelViewMatrix",modelViewMat ) + _iuniforms.SetMat3f( "ModelViewNormalMatrix",modelViewNormMat ) + _iuniforms.SetMat4f( "ModelViewProjectionMatrix",modelViewProjMat ) + Endif + + If op.bones _iuniforms.SetMat4fArray( "ModelBoneMatrices",op.bones ) + If op.uniforms _device.BindUniformBlock( op.uniforms ) + If op.material<>material + material=op.material + _device.Shader=material.ValidateShader() + _device.BindUniformBlock( material.Uniforms ) + Endif + _device.VertexBuffer=op.vbuffer If op.ibuffer _device.IndexBuffer=op.ibuffer @@ -599,6 +621,7 @@ Class Renderer Endif Next + End End diff --git a/modules/mojo3d/scene/component.monkey2 b/modules/mojo3d/scene/component.monkey2 index ad1b10b8..b6a49cb3 100644 --- a/modules/mojo3d/scene/component.monkey2 +++ b/modules/mojo3d/scene/component.monkey2 @@ -61,17 +61,21 @@ Class Component Return _type End - - Method Copy:Component( entity:Entity ) - - Return OnCopy( entity ) - End + Internal + Method OnCopy:Component( entity:Entity ) Virtual + + RuntimeError( "Don't know how to copy component of type "+Type.Name ) + Return Null End - Internal + Method OnShow() virtual + End + + Method OnHide() Virtual + End Method OnBeginUpdate() Virtual End @@ -86,8 +90,6 @@ Class Component Field _entity:Entity - Field _priority:Int - Field _type:ComponentType End diff --git a/modules/mojo3d/scene/entity.monkey2 b/modules/mojo3d/scene/entity.monkey2 index 204219d6..93704e81 100644 --- a/modules/mojo3d/scene/entity.monkey2 +++ b/modules/mojo3d/scene/entity.monkey2 @@ -41,11 +41,9 @@ Class Entity Extends DynamicObject If _parent _scene=_parent._scene - _parent._children.Add( Self ) Else _scene=Scene.GetCurrent() - _scene.RootEntities.Add( Self ) Endif @@ -57,13 +55,16 @@ Class Entity Extends DynamicObject Method Copy:Entity( parent:Entity=Null ) Virtual Local copy:=New Entity( Self,parent ) - - CopyComplete( copy ) - Return copy + CopyTo( copy ) + + return copy End - #rem monkeydoc @hidden + #rem monkeydoc Sequence id. + + The sequence id is an integer that is incremented whenever the entity's matrix is modified. + #end Property Seq:Int() @@ -111,6 +112,8 @@ Class Entity Extends DynamicObject Else _scene.RootEntities.Add( Self ) Endif + + ValidateVisibility() Invalidate() End @@ -137,20 +140,27 @@ Class Entity Extends DynamicObject Setter( visible:Bool ) - If visible Show() Else Hide() - End - - #rem monkeydoc Entity animator. - #end - Property Animator:Animator() + If visible=_visible Return - Return _animator - - Setter( animator:Animator ) + _visible=visible - _animator=animator + ValidateVisibility() End + #rem monkeydoc True if entity and all parents are visible. + #end + Property ReallyVisible:Bool() + + Return _rvisible + End + + #rem monkeydoc Last copy. + #end + Property LastCopy:Entity() + + Return _lastCopy + End + '***** World space properties ***** #rem monkeydoc World space transformation matrix. @@ -285,34 +295,6 @@ Class Entity Extends DynamicObject Invalidate() End - #rem monkeydoc Hides the entity and all of its children - #end - Method Hide() - - If _visible - _visible=False - OnHide() - Endif - - For Local child:=Eachin _children - child.Hide() - Next - End - - #rem monkeydoc Shows the entity and all of its children - #end - Method Show() - - If Not _visible - _visible=True - OnShow() - Endif - - For Local child:=Eachin _children - child.Show() - Next - End - #rem monkeydoc Destroys the entity and all of its children. #end Method Destroy() @@ -361,30 +343,7 @@ Class Entity Extends DynamicObject Next Return n End - - Internal - - Method AddComponent( c:Component ) - - If c.Type.Flags & ComponentTypeFlags.Singleton And NumComponents( c.Type ) RuntimeError( "Duplicate component" ) - - For Local i:=0 Until _components.Length - - If c.Type.Priority>_components[i].Type.Priority - - _components.Insert( i,c ) - - Return - Endif - Next - _components.Add( c ) - - Return - End - - Public - Method AddComponent:T() Where T Extends Component Local c:=New T( Self ) @@ -417,12 +376,9 @@ Class Entity Extends DynamicObject index-=1 Next End - -Protected + Protected - #rem monkeydoc @hidden - #end Method New( entity:Entity,parent:Entity ) Self.New( parent ) @@ -433,43 +389,67 @@ Protected Invalidate() End - #rem monkeydoc @hidden - #end Method OnShow() Virtual End - #rem monkeydoc @hidden - #end Method OnHide() Virtual End - - #rem monkeydoc @hidden + + #rem monkeydoc OnCopy + + 1) Recursively copies all child entities. + + 2) Invokes OnCopy for each component. + + 3) Copies visibility. + + 4) Invokes Copied signal. + #end - Method CopyComplete( copy:Entity ) + Method CopyTo( copy:Entity ) + + _lastCopy=copy For Local child:=Eachin _children child.Copy( copy ) Next - + + 'should really be different pass...ie: ALL entities should be copied before ANY components. For Local c:=Eachin _components - c.Copy( copy ) + c.OnCopy( copy ) Next - + + copy.Visible=Visible + Copied( copy ) End + + Internal -Internal + Method AddComponent( c:Component ) + + If c.Type.Flags & ComponentTypeFlags.Singleton + If NumComponents( c.Type ) RuntimeError( "Duplicate component" ) + Endif + + For Local i:=0 Until _components.Length + If c.Type.Priority>_components[i].Type.Priority + _components.Insert( i,c ) + Return + Endif + Next + + _components.Add( c ) + End 'bottom up Method BeginUpdate() For Local e:=Eachin _children - e.BeginUpdate() Next For Local c:=Eachin _components - c.OnBeginUpdate() Next @@ -479,12 +459,10 @@ Internal Method Update( elapsed:Float ) For Local c:=Eachin _components - c.OnUpdate( elapsed ) End For Local e:=Eachin _children - e.Update( elapsed ) Next End @@ -502,8 +480,10 @@ Private Field _scene:Scene Field _parent:Entity Field _children:=New Stack + Field _components:=New Stack + Field _lastCopy:Entity + Field _rvisible:Bool Field _visible:Bool - Field _animator:Animator Field _t:Vec3f=New Vec3f Field _r:Mat3f=New Mat3f @@ -515,8 +495,6 @@ Private Field _W:AffineMat4f Field _IW:AffineMat4f - Field _components:=New Stack - Method InvalidateWorld() If _dirty & _dirty.W Return @@ -537,5 +515,38 @@ Private InvalidateWorld() End + + Method ValidateVisibility() + + If _visible And (Not _parent Or _parent._rvisible) + + If _rvisible Return + + _rvisible=True + OnShow() + + For Local c:=Eachin _components + c.OnShow() + Next + + Else + + If Not _rvisible Return + + _rvisible=False + + OnHide() + + For Local c:=Eachin _components + c.OnHide() + Next + Endif + + For Local child:=Eachin _children + + child.ValidateVisibility() + Next + + End End diff --git a/modules/mojo3d/scene/entityexts.monkey2 b/modules/mojo3d/scene/entityexts.monkey2 index 3e2d3716..9fb275bf 100644 --- a/modules/mojo3d/scene/entityexts.monkey2 +++ b/modules/mojo3d/scene/entityexts.monkey2 @@ -13,7 +13,7 @@ Public #end Class Entity Extension - #rem monkeydoc Local space rotation in degrees. + #rem monkeydoc World space rotation in degrees. #end Property Rotation:Vec3f() diff --git a/modules/mojo3d/tests/sprites.monkey2 b/modules/mojo3d/tests/sprites.monkey2 index ff0dee04..4c9ed73b 100644 --- a/modules/mojo3d/tests/sprites.monkey2 +++ b/modules/mojo3d/tests/sprites.monkey2 @@ -17,6 +17,8 @@ Class MyWindow Extends Window Field _scene:Scene + Field _fog:FogEffect + Field _camera:Camera Field _light:Light @@ -33,6 +35,13 @@ Class MyWindow Extends Window _scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap ) + 'create fog + ' + _fog=New FogEffect + _fog.Near=0 + _fog.Far=50 + _scene.AddPostEffect( _fog ) + 'create camera ' _camera=New Camera @@ -53,6 +62,8 @@ Class MyWindow Extends Window ' Local material:=SpriteMaterial.Load( "asset::Acadia-Tree-Sprite.png" ) + material.AlphaDiscard=1.0/255.0 + For Local i:=0 Until 1000 Local sprite:=New Sprite( material ) @@ -70,9 +81,9 @@ Class MyWindow Extends Window For Local i:=0 Until 100 - Local box:=Model.CreateBox( New Boxf( -5,0,-5,5,Rnd(2,10),5 ),1,1,1,New PbrMaterial( New Color( Rnd(),Rnd(),Rnd() ) ) ) +' Local box:=Model.CreateBox( New Boxf( -5,0,-5,5,Rnd(2,10),5 ),1,1,1,New PbrMaterial( New Color( Rnd(),Rnd(),Rnd() ) ) ) - box.Move( Rnd(-50,50),0,Rnd(-50,50) ) +' box.Move( Rnd(-50,50),0,Rnd(-50,50) ) next From a43c6e1600c068c843483891459d0eee56aff1b3 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Thu, 16 Nov 2017 12:31:47 +1300 Subject: [PATCH 02/23] Updated. --- VERSIONS.TXT | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSIONS.TXT b/VERSIONS.TXT index b66f1fe4..fe5d4d64 100644 --- a/VERSIONS.TXT +++ b/VERSIONS.TXT @@ -1,9 +1,9 @@ ***** Monkey2 v1.1.09 ***** -Added experimental selective reflection. +Added SDL GameController class. Tweaked Joystick/GameController Open logic so devices can be added/removed more cleanly. See joystick and gamecontroller samples in bananas. -Use '#Reflect namepsace_path' to enable reflection for an entire namespace. Using same system as 'Using' ie: can end with '..' to 'reflect all'. +Added experimental selective reflection. Use '#Reflect namepsace_path' to enable reflection for an entire namespace. Using same system as 'Using' ie: can end with '..' to 'reflect all'. ***** Monkey2 v1.1.08 ***** From 0dfb300ece50b9caa8a1683c718126b79261ae28 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Thu, 16 Nov 2017 12:33:09 +1300 Subject: [PATCH 03/23] Updated. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index be103178..4d189a7e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ __NEWPAGES__/ /src/ted2go-v* /src/ted2go-dev /src/ted2go-master +/src/ted2go-github /modules/cmark /modules/lua From 4399d4be80fd8354460197026869a7a0393bf4e0 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Fri, 17 Nov 2017 06:54:22 +1300 Subject: [PATCH 04/23] Fix for safe member access operator on LHS of an assignment. --- src/mx2cc/mx2cc.monkey2 | 2 +- src/mx2cc/stmtexpr.monkey2 | 23 ++++++++++++++++++++++- src/mx2cc/test.monkey2 | 26 ++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/mx2cc/mx2cc.monkey2 b/src/mx2cc/mx2cc.monkey2 index 0827699f..179adc14 100644 --- a/src/mx2cc/mx2cc.monkey2 +++ b/src/mx2cc/mx2cc.monkey2 @@ -34,7 +34,7 @@ Global opts_time:Bool Global StartDir:String -Const TestArgs:="mx2cc makemods -target=ios" +Const TestArgs:="mx2cc makemods" 'Const TestArgs:="mx2cc makedocs mojo" 'Const TestArgs:="pyro-framework pyro-gui pyro-scenegraph pyro-tiled" diff --git a/src/mx2cc/stmtexpr.monkey2 b/src/mx2cc/stmtexpr.monkey2 index 9a436134..30b46775 100644 --- a/src/mx2cc/stmtexpr.monkey2 +++ b/src/mx2cc/stmtexpr.monkey2 @@ -73,8 +73,29 @@ Class AssignStmtExpr Extends StmtExpr Return lhs.ToString()+op+rhs.ToString() End - Method OnSemant:Stmt( block:Block ) Override + 'special case for SafeMemberExpr on lhs of assignment... + ' + Method OnSemant:Stmt( smexpr:SafeMemberExpr,block:Block ) + + Local value:=smexpr.expr.SemantRValue( block ).RemoveSideEffects( block ) + + Local mexpr:=New MemberExpr( New ValueExpr( value,0,0 ),smexpr.ident,smexpr.srcpos,smexpr.endpos ) + + Local asexpr:=New AssignStmtExpr( Self.op,mexpr,Self.rhs,srcpos,endpos ) + + Local tblock:=New Block( block ) + + tblock.Semant( New StmtExpr[]( asexpr ) ) + + Return New IfStmt( self,value.UpCast( Type.BoolType ),tblock ) + End + Method OnSemant:Stmt( block:Block ) Override + + Local smexpr:=Cast( lhs ) + + If smexpr Return OnSemant( smexpr,block ) + Local op:=Self.op Local lhs:=Self.lhs.Semant( block ) Local rhs:Value=Null diff --git a/src/mx2cc/test.monkey2 b/src/mx2cc/test.monkey2 index 43a46ffe..1d63af0f 100644 --- a/src/mx2cc/test.monkey2 +++ b/src/mx2cc/test.monkey2 @@ -1,7 +1,29 @@ +Class C + + Field c:C + Field f:Int + + Property F:Int() + + Return f + + Setter( f:Int ) + + Self.f=f + + End +End + Function Main() - If String<>Null Print "oops" + Local c:C=New C + + c.c=c + + c?.c?.f=10 + + c?.c?.F=100 + Print c.f End - \ No newline at end of file From fe900b720d248bf73d5d935b4af57e380eef0f51 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 20 Nov 2017 10:14:53 +1300 Subject: [PATCH 05/23] Added animation transitions. --- modules/mojo3d/components/animation.monkey2 | 37 +++--- modules/mojo3d/components/animator.monkey2 | 131 ++++++++++++++++---- 2 files changed, 128 insertions(+), 40 deletions(-) diff --git a/modules/mojo3d/components/animation.monkey2 b/modules/mojo3d/components/animation.monkey2 index 3a7fd6aa..7bc5d185 100644 --- a/modules/mojo3d/components/animation.monkey2 +++ b/modules/mojo3d/components/animation.monkey2 @@ -55,42 +55,45 @@ Class Animation Method Slice:Animation( begin:Float,term:Float ) - Local newchannels:=New Stack + Local channels:=_channels.Slice( 0 ) - For Local channel:=Eachin _channels + Local duration:=term-begin + + For Local i:=0 Until _channels.Length - If Not channel - newchannels.Add( Null ) - Continue - End + Local channel:=_channels[i] + If Not channel Continue Local posKeys:=New Stack + posKeys.Add( New PositionKey( 0,channel.GetPosition( begin ) ) ) For Local key:=Eachin channel.PositionKeys - If key.Time>term Exit - If key.Time>=begin posKeys.Add( New PositionKey( key.Time-begin,key.Value ) ) + If key.Time>=term Exit + If key.Time>begin posKeys.Add( New PositionKey( key.Time-begin,key.Value ) ) Next + posKeys.Add( New PositionKey( duration,channel.GetPosition( term ) ) ) Local rotKeys:=New Stack + rotKeys.Add( New RotationKey( 0,channel.GetRotation( begin ) ) ) For Local key:=Eachin channel.RotationKeys - If key.Time>term Exit - If key.Time>=begin rotKeys.Add( New RotationKey( key.Time-begin,key.Value ) ) + If key.Time>=term Exit + If key.Time>begin rotKeys.Add( New RotationKey( key.Time-begin,key.Value ) ) Next + rotKeys.Add( New RotationKey( duration,channel.GetRotation( term ) ) ) Local sclKeys:=New Stack + sclKeys.Add( New ScaleKey( 0,channel.GetScale( begin ) ) ) For Local key:=Eachin channel.ScaleKeys - If key.Time>term Exit - If key.Time>=begin sclKeys.Add( New ScaleKey( key.Time-begin,key.Value ) ) + If key.Time>=term Exit + If key.Time>begin sclKeys.Add( New ScaleKey( key.Time-begin,key.Value ) ) Next + sclKeys.Add( New ScaleKey( duration,channel.GetScale( term ) ) ) - Local newchannel:=New AnimationChannel( posKeys.ToArray(),rotKeys.ToArray(),sclKeys.ToArray() ) - newchannels.Add( newchannel ) - + channels[i]=New AnimationChannel( posKeys.ToArray(),rotKeys.ToArray(),sclKeys.ToArray() ) Next - Local animation:=New Animation( newchannels.ToArray(),term-begin,_hertz ) + Local animation:=New Animation( channels,duration,_hertz ) Return animation - End Function Load:Animation( path:String ) diff --git a/modules/mojo3d/components/animator.monkey2 b/modules/mojo3d/components/animator.monkey2 index d6af9fe7..ec3d5e5a 100644 --- a/modules/mojo3d/components/animator.monkey2 +++ b/modules/mojo3d/components/animator.monkey2 @@ -84,15 +84,27 @@ Class Animator Extends Component _time=time End - Method Animate( animationId:Int,speed:Float=1.0 ) + Method Animate( animationId:Int,speed:Float=1.0,transition:Float=0.0 ) DebugAssert( animationId>=0 And animationId<_animations.Length,"Animation id out of range" ) - _playing=_animations[animationId] + Local anim:=_animations[animationId] + If anim<>_playing + If _playing And transition>0 + _playing0=_playing + _speed0=_speed + _time0=_time + _transdur=transition + _transtime=0 + _trans=True + Else + _trans=False + Endif + _playing=anim + _speed=speed + _time=0 + Endif - _speed=speed - - _time=0 End Method Stop() @@ -111,31 +123,104 @@ Class Animator Extends Component If _paused Or Not _playing Return - Local hertz:=_playing.Hertz - Local timeScale:=1.0/hertz - - _time+=elapsed * _speed + Local blend:=0.0 + + If _trans + _transtime+=elapsed + If _transtime<_transdur + blend=_transtime/_transdur + Else + _trans=False + Endif + Endif + + _time=UpdateTime( _playing,_time,_speed,elapsed ) + + If _trans + _time0=UpdateTime( _playing0,_time0,_speed0,elapsed ) + UpdateSkeleton( _playing0,_time0,_playing,_time,blend ) + Else + UpdateSkeleton( _playing,_time,Null,0,0 ) + Endif - If _time>=_playing.Duration * timeScale _time-=_playing.Duration * timeScale Else If _time<0 _time+=_playing.Duration * timeScale - - For Local i:=0 Until _playing.Channels.Length - - Local channel:=_playing.Channels[i] - If Not channel continue - - _skeleton[i].LocalPosition=channel.GetPosition( _time * hertz ) - _skeleton[i].LocalBasis=New Mat3f( channel.GetRotation( _time * hertz ) ) - _skeleton[i].LocalScale=channel.GetScale( _time * hertz ) - End End Private Field _skeleton:Entity[] Field _animations:=New Stack - Field _playing:Animation Field _paused:Bool=False - Field _speed:Float=1 - Field _time:Float=0 + Field _transtime:Float + Field _transdur:Float + Field _trans:Bool + + Field _playing0:Animation + Field _speed0:Float + Field _time0:Float + + Field _playing:Animation + Field _speed:Float + Field _time:Float + + Method UpdateTime:Float( playing:Animation,time:Float,speed:Float,elapsed:Float ) + + Local period:=1.0/playing.Hertz + + time+=elapsed * speed + + If time>=playing.Duration * period time-=playing.Duration * period Else If time<0 time+=playing.Duration * period + + Return time + End + + Method UpdateSkeleton( playing0:Animation,time0:Float,playing1:Animation,time1:Float,alpha:Float ) + + time0*=playing0?.Hertz + time1*=playing1?.Hertz + + For Local i:=0 Until _skeleton.Length + + Local chan0:=playing0 ? playing0.Channels[i] Else Null + Local chan1:=playing1 ? playing1.Channels[i] Else Null + + If playing0 And playing1 + + Local pos0:=chan0 ? chan0.GetPosition( time0 ) Else New Vec3f + Local rot0:=chan0 ? chan0.GetRotation( time0 ) Else New Quatf + Local scl0:=chan0 ? chan0.GetScale( time0 ) Else New Vec3f( 1 ) + + Local pos1:=chan1 ? chan1.GetPosition( time1 ) Else New Vec3f + Local rot1:=chan1 ? chan1.GetRotation( time1 ) Else New Quatf + Local scl1:=chan1 ? chan1.GetScale( time1 ) Else New Vec3f( 1 ) + + _skeleton[i].LocalPosition=pos0.Blend( pos1,alpha ) + _skeleton[i].LocalBasis=rot0.Slerp( rot1,alpha ) + _skeleton[i].LocalScale=scl0.Blend( scl1,alpha ) + + Else If playing0 + + Local pos0:=chan0 ? chan0.GetPosition( time0 ) Else New Vec3f + Local rot0:=chan0 ? chan0.GetRotation( time0 ) Else New Quatf + Local scl0:=chan0 ? chan0.GetScale( time0 ) Else New Vec3f( 1 ) + + _skeleton[i].LocalPosition=pos0 + _skeleton[i].LocalBasis=rot0 + _skeleton[i].LocalScale=scl0 + + Else If playing1 + + Local pos1:=chan1 ? chan1.GetPosition( time1 ) Else New Vec3f + Local rot1:=chan1 ? chan1.GetRotation( time1 ) Else New Quatf + Local scl1:=chan1 ? chan1.GetScale( time1 ) Else New Vec3f( 1 ) + + _skeleton[i].LocalPosition=pos1 + _skeleton[i].LocalBasis=rot1 + _skeleton[i].LocalScale=scl1 + + Endif + + Next + End + End From d86bd96b8d84411e00a9173b5faf7809be46b650 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 20 Nov 2017 10:19:04 +1300 Subject: [PATCH 06/23] Updated tests. --- .../assets/psionic/ninja animation ranges.txt | 34 +++++++ .../tests/assets/psionic/ninja.b3d | Bin 0 -> 106407 bytes .../tests/assets/psionic/nskinbl.jpg | Bin 0 -> 39906 bytes .../psionic/turtle animation ranges.txt | 22 ++++ .../tests/assets/{ => psionic}/turtle1.b3d | Bin .../tests/assets/{ => psionic}/turtle1.png | Bin modules/mojo3d-loaders/tests/ninja.monkey2 | 95 ++++++++++++++++++ modules/mojo3d-loaders/tests/turtle.monkey2 | 5 +- 8 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 modules/mojo3d-loaders/tests/assets/psionic/ninja animation ranges.txt create mode 100644 modules/mojo3d-loaders/tests/assets/psionic/ninja.b3d create mode 100644 modules/mojo3d-loaders/tests/assets/psionic/nskinbl.jpg create mode 100644 modules/mojo3d-loaders/tests/assets/psionic/turtle animation ranges.txt rename modules/mojo3d-loaders/tests/assets/{ => psionic}/turtle1.b3d (100%) rename modules/mojo3d-loaders/tests/assets/{ => psionic}/turtle1.png (100%) create mode 100644 modules/mojo3d-loaders/tests/ninja.monkey2 diff --git a/modules/mojo3d-loaders/tests/assets/psionic/ninja animation ranges.txt b/modules/mojo3d-loaders/tests/assets/psionic/ninja animation ranges.txt new file mode 100644 index 00000000..ea09f50c --- /dev/null +++ b/modules/mojo3d-loaders/tests/assets/psionic/ninja animation ranges.txt @@ -0,0 +1,34 @@ +hey guys heres all the ranges for the ninja model, 20 ranges and 300 frames....Phew!! + +you may need to scale or rotate the model and change animation speeds for various 3D game engines... + +Please check the numbers carefully cus they dont follow any order and in between ranges often skip a few frames, this as how I achieved certain moves in Character FX its not a mistake :) + +1-14 Walk (normal) +15-30 Stealth Walk +32-44 Punch and swipe sword +45-59 Swipe and spin sword +60-68 Overhead twohanded downswipe +69-72 Up to block position (play backwards to lower sword if you want) +73-83 Forward kick +84-93 Pick up from floor (or down to crouch at frame 87) +94-102 Jump +103-111 Jump without height (for programmer controlled jumps) +112-125 High jump to Sword Kill (Finish em off move??) +126-133 Side Kick +134-145 Spinning Sword attack (might wanna speed this up in game) +146-158 Backflip +159-165 Climb wall +166-173 Death 1 - Fall back onto ground +174-182 Death 2 - Fall forward onto ground +184-205 Idle 1 - Breathe heavily +206-250 Idle 2 +251-300 Idle 3 + +Ok there it is, have fun and maybe drop by my forums hang out, ask questions, post your own work etc + +Feel free to use however you like, commercial etc, credits are Appreciated as a LOT of work went into this! ;-) + +Psionic + +http://www.psionic3d.co.uk diff --git a/modules/mojo3d-loaders/tests/assets/psionic/ninja.b3d b/modules/mojo3d-loaders/tests/assets/psionic/ninja.b3d new file mode 100644 index 0000000000000000000000000000000000000000..fbc78724588d5279ddb2bf07cfcc8766402a0e50 GIT binary patch literal 106407 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 literal 0 HcmV?d00001 diff --git a/modules/mojo3d-loaders/tests/assets/psionic/nskinbl.jpg b/modules/mojo3d-loaders/tests/assets/psionic/nskinbl.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b701adb97570a5d406c449097bae8a19c17c8b14 GIT binary patch literal 39906 zcmb??Wmp`|w(j8W?gR}GT!Xv2JHg%ET|x+M!QI^km*5aw26qYWF1eF$?{oHZ?w@mi z+?uDm-&$2&RlT}ub=A9S{w)330-(!E%18nrAOQd~@DK3k6o4V_ZfffdfB--P004fl zXb19>jF^~_vZ|7#jJyN@0s;VlBo+}Dm4HMC0PG#yTvVk*$+dNK$>HX}_uv39z&~1J zQ&%SiHA!`F82|@I2lKzz{~7KV!F7Pc9OK`z{%6|%8bdU7a&ZIadIcX;X6CM@000Cz z7+ZL{If3O6QDBU1V*3xy`iouERmH&aIxt4H{1@*1i%tH8r~hIX@I!#}Y=JSh<-hRl zUu^OZhJ^o%-ORoI)(iM(N=MGd!op52V&-UKPOj$d>Sk{5N-pDI$^`gVd;aDEAc?uV zx;fgDgH`lT{Qv9eug?Ef;LXo}bO~YR@E_TKsxozV@%VQP0&)-l_}}9HDI*Nr2Sg?T z06571$kcxU01Z3<0LAD3$Vg$qkNOM%lwbWh2cZ6=5`bARxK4;atAH>7EHpGU3^XhZ z3@idH_&|b(g@s2#MnptHL_|hM{wJU#qoSdsp(0~pVPj)q5fc#+5tIFQfPjO8LqSHt zM@Ppe#KFKJ{O^SSSMaA7fB_E)fCNB6U;rR7AfPZH{tN)>!R>>A_}icVD?kDuprB!3 z;o!m2zu~{+;41_qI0YS?4hanj0|$oy3k3%S1J*hu6bAGMR!kTX6=P=#EZE?L{602p zxcV7U)kI31Z4;M}26i<8cE_bJB4#@O&H)hAR!=OAt9jv z-~(JC1O_A&E9M6g6*eqr3T$KN-~`or_Dj+HK1!HBD*$A07Dx;z41f^eX_9$O;tv2N zw*hZu`(Ekj#`D4GRKtLPK2I7GmRf>S8G-(zjEboK_b&@7PBta-ILf`DDe~~xX71ZD(0PhS&?GU#NQCHVK=srn_F*`pX0$K%gohz?FZ2&UejPQO#V^bQA%m4J>_YUd zc$Yr;=R@TvP?nuTp4mmnDW!G31a)ub48(jdBBOM`mz z2T*$6+A*E3c&GOVfYj|pMjV65ddBEW43%-vsvHRw3~hl?^LW)m^h(keH0oR5kenZ| z;ZH-0wLj})HGlzp4M+_^RJwr1NZxH?pPm$w#fLp-36J1lwNal z%Y1(mg&JcY4ewV>fMoU-x$c;~zCAMDp#9);yVbWp{@@IUuXlhr!Er0cbtg^R@&_;% z*MQqCC?x3&Ecd+Me0IJU?I9y8Fb!1kUGI7Sk!#$;)|C-Bx(X~OYNdPQ6YLK#X9CAL zR~PWQr79mV=5C2@KA9>>zIfcpGHsRK{(3=qN`2w@14!lbxD)fHQWeu2SXu)ii-|dr zUmqI=Hr6!A%kw&qAG*whEyDL~H{OdrT=~-8UR|8`ETt|}iweiHM}LD1p)0{SsBc+v zo0AVtw1JMLmtB~@6Gw1iLY9p{B?-ZW$5H-LF@dieldVo}rLI!V<|g6amr+d`P>oiE zrhEN1`WToNSbJ_7Bm+Q9iZ^0B!>#5(>5W-quN9re=Sb;tV^ktGKx%1kJ8PI%PHdmh zuhxqNup7`+t)x~v0Msj!{h2RW7ajv0;PdTnp!Pj#bsvpf{{YPWnb7QfP?5%tO~uvO zCKsc#AWN`Ix{UG3K60p(V_SAwK_?0GBz~99dl{wT_*y*tV-yd8vLzX)K3U59Avx^q z{?d2gnu`ne_{>-RvvGzo+hEUQaQ*W5Ck%{F^&t??G$OaQGq-UtkkJNM4ZNAm*mfPiL~4vfv=RwPVL5QBB}5P1 zsp^NlZnhVPyPL)?TDz=XI+lW%ia$aP5w3#bC|Gsp5|1;lCjJ0`VP4;Uz6|xbkgrh` z8W|q!@V3*@<)kT_@Pvv@cV#$o{l0Q6UVLoOT>4ih#sc?QH8S0!{)^mI;+&P0*$ol@ zD!~BJd!S4&oypj*w!8qjsSeQa^ktA2O-a93UN3`AMC@-MB&LJ?aPnCz+6d!=`Q%Y} z#8F-ul7m~3ty>nPn%+)PFW*`k-NNrKyp_c{@($_imFl)8BxT`00M;JLw7Byr_*;M9 zUE#wu_`>rE!1q?Q8_}cUu13=9WIojWiLI*y zW0pG3!4d+~gi(vrta+OD4fZ8HvsN`>Kbl%9)%@DdhQq$uVIq3=Q?ybTl8=SM`)>Uk#MG49?|Mi{B zIC8NYZT$~m*+}W+g|aj8%Ni(U6Y;DIjU1J;HUSS2c zTw1kTmTe@p|E|L1**Q12Vv$k%_mU>(el6`S;L7|LInMo2)`8iTd+nK6xEE(#M*Bz4 z-j3|G9hnRtVwd4~omCIBVXZBm&Fs z{I*Bm`pN4>*1R`*WJpiuQT>*i%Y&WiA9kaR_}{6To^j3{92p2FcU|kff$hGVWQX$^ zcZtOg5*;I-F3zZ$JHs1SCOJm221Q528X41iS9L5H#`*OwyU)ivyO-_^mMvbC zY`h`~)gHdXrCGUfFd23Wdiya9j^CYLWfLQw-y}i@lS|VyI#i}2&Jn+UD@E%Lv~%&b z)ob;w&2yoBh1TQU|g(j-*|VLEBLsB;xLjN0mgUX|0?k z4ZxKtAIYWW{#6FG@82fkK71ozD-sM3;Ai)C-7^@kd>N*--h3swst%riJ4v&yy}kjz!^Qbk@LP+``))J#w=I&V^BQy)!T z#Ga{{@<~TW*kelcMzPj0Np?h)srmQr?`xMk63{s@59S!?_UG`@ePHctDPCLh5FT$^ z(hoy~%4M59++ntklggB37oH3@9EPG8b9QZ>Tyow1&-i(x-Ku4=I{mIZ*E$`T^n#uO zD|)Jm0GJqH5=!Mbyi=@mf9~43SACKm>?BfE5D;`6?_*@=-ra1Aw zvE42nT(dL0?lT1Uz-S(nwWQOTP3u|exoO_vY8F9w<@b zxulG3#nNE_4DLB(q6 zZQu>j3zH#p$64$jfL=9CvX`4o5TpCcj)PE#Lywr7- zA*i&Suwk!T{DB888Q9S}rVC3fySut`=lRGiI)PkpQq@1vH9_}V8)Bp@<;B=EclEvJ z8bsEn$`u?x=v~;QAHk+2gWqhQplo6Vj9^prvm3|!5rJdbQDQk#-!d_wUYv%a$|t5s zZn47)caIeDn&|EZ($HZhJxG=0YavEK(f64u_Xh4agAAzriQZTDoF07xRizz{SV*AO zw|JQ0gtTfS3(Gbyj)=e4P@zL^lXjWLS{$Fa_^RjY_G_CzrVS`|Hl9Pb8%*VjHqelI z*af9Z;n{dVo2n|{+0_R%Ol;>#y3mqJ(ce`jArv;C3`vEm=^|sF>oe=Z7Yezp1=Q;_ z>*4B|Ps{~VMda{nGP0a-1)`vfy>yBDQ{GhVd1$WI^fic*wQlOI1b?-5LcLb6q^|K* zi;LMrqb##uEv7__+83hKlBvFR`DNiiM$8YPN@zh#4DEnZN9{Vj-B z0#A9qWSqIT#W>Q;;eaRiF&;<;kpa2(EuHpz&eO-F^JYofMJ0r*Lph;A4_9aFY)@+$ z_iL=UOH{ZX7blc&j>`mvcJM!4 zvBT)cH=7s2%zjq&kdcmy?iY2c#F1H$Hm2V{xb6!YRX-?yCwQpHD$LeBr5 z1Pw}y8@Y)Eqih?7|MWV&)%&g`F7F`X**~>zjUL>@%}W#HLl&I{`d0_-w{_5y{e91>OP0h38L~`DG@*fJA6Llmz>imoA`=j7KC#m ze4J-Uzv4={um9B5^LpnPSAnlRlW}uGQmt>gG8182qTN@N1))XgMqPq!-tIb?KKvHS zIc&d9Q<13OoHId2yU5ez4wZXKs*s6fmlyy&Xi*DnsWyFyj`Z zJB6BBDVhuRg_>l2c^cqZh#Eqyr>^IWWUQ}1BL$i%SSw&qHOJ}E@InivxG!BS627F%BCHCnM3`~ zlNxT4V83O8l}8zaOk@wvBQ1l~7!pI-;*^8&QZa=USRl zFDzs?sdNEVyJA>d0XzD~h^G3{Db)^k9v7FJ-|t0fxqvD|AH};?NAiwvkg|PPaqV9X z+U3Zco$(V@PNmOuBMn^j&+E$>T!!6zcQD#|$4*yZ1k7F9TbfaqT{(gf0j?#~$Q@*8 zXIw4w&ZT}{t?Ey6Ouky#etM6sjL55K5yc=6`th?#{`@V|@c!3H$7g;%ZD0=`uAlA1 z==eS$I~|N zfQyzuhg`jiN>7vPI2W}${QpXao*28ju+_gRYK4+yA$|&~I^0-hWX5eQ(nX!i zxHBKXG>nMm=45)bu}<-nJw$QgljCuwEo0Ji@d0v*Q0@22Nk+ZbVm(Vm-<);TBrG@FI7KEj$IK0>bL31H3qx}H;l#3Rt0-#%6+oUV&hXG=q zJ;?p>yCM*4*XQci3O#GctFY_9Q=I;g2JLdNaK8RqHJ(2nh}7M;z+?8p*DCq(Ne+a%c}=Y-gX(^jkv zZsr-6?FT0J+~vFd%zvmqFt4Rohs858GQoo8d<-UvI=hUt1QtKLnkeBE;%r_)KI~ee zp82D!ftNGrbgT92wfQkxfddGuz={Xk-=-*_E9|qSbI|nq1PFw%+VC$KxT$;|hG+iR zB`}TBsKx`N?X^Ef&wEz8yU1|~|3yQ92{mUz>Hd+>Bi*?wxf#BF-}U04L?-~V!t$g| zOk34tTz~*0nb1g^eK*lh?$47RrGz>Daw%8JWkuk%p5Tu^0LS%OC<9cj>%cwHPQN$9 zSf<$XqoNQzJWHgVC<$V#rjx=chfEpSHEUmM%1#JNbN72?TMH56jLuKNzZg&-c_J}3Sf83#7y4#a5) z7CwABn&K^6YHhbn%Q>+I9X^p=Aw6xkG?oSTXUD9Zz)Jzd2PH{gk2|LK)ko5>^PEVF zjpEJKK&a{Lx#`o@`60c)t!FIu8H)~|bUDXYGEuO&{p?sNV=d-G{;`~8KA4*X_!%$E zL{g8pTNaiuSIF#tT`Xb~fYY<_i`9RhNYl=+wb*?o18#h1*(kW;KUDpn9ZswaYb@kUysxk{onc)OY?ezzsj29qnIpF4QOX zhpx?M0+vh(Y)nybVTOu*fOZ;m-0d#EQ4;eF%N?Y<+EhEp2|vdSL@&SE)Oae*Gizdf zzy9*;JHk+86hX?AFXxFa$nQ2PW5RI;!*8f{u3^Qs{4RWWe`2VuCY$$>)?&G$HhZsY zDSiz=OFW`xRGmvw$(=SG7_Rqb={@|b`FZ6J;M40o_@gIsmwQOY7SK{UA#Ja@Cil8~ zpmoQq^+?BK1XR&2Fm1?g{ za(a?2xBd^{SpN?|>&f=u9dK5lo6YQy13m8pV8A=k?6!(ksM#)T(0z|cI)Siw^G|gd zq`-qy`-q8F{0FdFzOeOeX6)?`;0e8!R!A#=DNdp_j?&(9Q>Tat`beFcwNPF(F}AW62X8ZxcJD$$LV_-OX_=Q^hZwv%*{Fq#BC*QY%s<3{FE@ztey-(IhiuojOgqhabv%-%h*&8d^);M4Y{w ztp)aPg`KN=q3)Bg&tn+Yr+!{08nmt;W)MyeldaIL0t;T-pX<)~ZMVuSSY_JS)tsnf zQYD{=hj_>+dT=))r)n$o0?8gBifq<1;9ik({{X%Ul5}AmE{u9j4JSRuYMtq+Wjk&^ zX3G>0HW=`@5=8iGJ{f$6^6$|)J~-$MG(Gi}BqAJQVGV)>0w7=5WLU|#Ruwfwp~;;@ z;pWll!zFAN0W(en)t8YZoW;5a@!oee{Ec9Ow$R6jS(CpbiS*-#yd!si5vLwkLB+;U zQsd}@<(kUS^68nIa>4mqTL(VP9*nvY3Z=TuE2f^d+QPIxV432!d0(x7z2jrj1IKMC zSn#7&Cu6btgp5qURH8z^y5=M#37qJtk>}=AnM$cdsbpzB5mEw z86$sGzJ5)MyMtPT^$xTYWjd;H4dtFpb2bw7aY{${l9fZ9@WEozVX>CXJ$H_sA){lW z;f-rLro;H%CQ1*CL^?DokorP^ldEUy($D{8`^A3h;H{Of4&l}}H=7JjvTSCHK z!kJ$0vu zS;3R0Ks|xB8u^(Y&Xv(X4tjmgupN5$iZ}G6*(p=uZA9WMk}E)~+;ub7Zn0x&HFr01 zAajvNH$9Jeuz?GHGRt=P0xI)RckbA-h*^+-`NnH*2jx=m51^fOf+ykp{_a+C;Wy|H z;O0&LVy&^@Lx<5J)%S!~eUm63DA6;)IAW8xiAm4QYY>MtJ~2FXj43M57hMQaiSpal zH-a9=r1_Z(qCL?B2jEq#LPsmq^j*)~&aLIW=N%Ss6P*YBY}bkW0A;`L51>1@eE4^6 zeNRQ_!%OE<_Sl6sHk#YD5UN2b4%f4f&Nhppf4b+VjStYxLz=Gkwg-j=?=@g+#9Hc={N+2yzq{-G0P2HjD48p#4!Uo> zEvNE*9JP?8+ICHuAN7^W>P`>l3X%1mn@X#^f}B2~iz8cZWBKK@Ll2UlO!Kk*{$QpV z2AhRAwX-Yu;VhL*1>0%QYa7kOj9`9}= z-S3!P8sDJ<&HTpvbe|T9za3Ih824C^RApu|U04=GU@IbLgF-b>34?F*{6FG{LFxt}sjEd4z;kNg+kEI% z7eHr4gtz#~ose!#`Ku8q+gJ%{F-`6lWQJOXi2na z=M!i_@&FJLUuA`v8mT_(`|+NT3bZlm+u1h4ed@!2x>OOJQw@;-dnl>U^|e>l0v|wx0xdn_s>(c1ugwZ4&tILsZ+g659VZjan&DqvzkH9yX0(FFIo`Qk zl|2k)W(W7z0px+wGUCHTw5X-ymQ2I8R^r}U7qUq7* z#jK^7eYqb!OL3_`0~8T)oY<;yjcMg&ewugc(XnK{bU!48WO9DCyfJ-@8c(9=BtUf| zlDQ+-vgXK#?6iVF>*%%Y7IfGNQvU)aYvNxu?QJ>8m~s3x_n7|EIAPAZJ*cVqmvy0t zR|$3i{*6fg>C-PCq7%c~%Ei_jZ>|sS5_s)e8ASzUPQeTRIHv!eR5yf55UBM-4TpGTyoeK!V*YwPAJSg1q6torcf(k zVR~X{6&3V&Y9|t?kI)4fQIQbX!W1ORzcFD&VETi;9_GdI4n6#AuGK_7#JK_b6ntdd zZ%vlMFaMkaaAhDo@@W0mGOL6qXKIR5rAzQFuI1(j!_n210u8CaLx;v<6UGqjUh*sGAa3@r~e_e67EWDZ;Z< zw@kDsAv&BoY&(0L;y<>l|3= zUwFqDNMJ+Hm*MZIh&yRO&z2=rpq5F4)su`Oju=G(ez5`?h-LUGFnS=>^~1`2 zq+G^Vy}3$)_;F{JSXM3KDZuBHt&H^=${D*P&S~u>34791lijn}$;#eVy71Gze}Zag#OvqugD#3?#&&hZneGw1$v{fG|M~X z4*U#U%hv5Ome#GQlOr4m@HrGF3AN16%%*XqJbR(P?j`SOg$e{UPM>oeg@h8%K5CT@ zQWA^Ki|21uq~j=4!KfoB|J|S=BK1Gj?haOac>@dS)0$*FfAdpX?XeQ!_LL7NgK$?Z zFB^gY61{o>oK-nI#-OqSVu3IYbToOC`(JB<07dRYt}Z+%JOWlf$H$N7lQVX_`Vco* zfA>o`SJvWbj5F|@?ar?0JG-_fwqIRMnC86bX#w&K@R8yXVJqmq3e$2R2mO1~2+E-F zU@t|XU$~q+<%Ah~9dtq7eNNpRx0^*~(D9dp7@s;rp?V?O!stiNSE$x0(;aDKwkRVf zkSG=7B17oXI;Xw6z;_4R(88Mal8h5a;AZfuV=iM~lPP&J+a8H9q=}VvVPyzvhv^1m zwk<)}EF&~az;{tp-yyyxMLT&rh1T@c;|o&lKH}IJEp#V{0q<$4b|d{$U;p>{@rkbL z*wMz%se}$fJ^)+!>}V1P6nB|oW$*VP!5y`y{yoEs^4ZR{H(NeDOrtpt48VDG?Z(v2_O!z+LMVT7AQa{3xjp;LoOQ@OfCT_PNgbNg{-cI ziB7*2JOd9+;CoY!tZ=a7*MD70yV{Mh8RE6rgj!|i$%2;e1NLS5R5|II6U8Hp0t$XW zsR)PUPyfR)u+l@gmSK;CwWhG;t0+l0MICrx>#Q_G;15+umwEDtrv`6%6mnh@gP7|} z+@}it^h_E|Ql*Q{>RVf+*NBIa`!j41F9n!oRvo!~CracVPpcUT9HI6SW{zXN3eO(I zZ*T}?s6nDTtlFclI^5se`^Mnir)BDU%A47_iZdmqe1EQADDSU^`Osv4wPJ_o*^$z< zetylg!(}EA8$p91IdXq55?te4zOB#8BBEsW zo+|eAp4SCl#H|JbGjl86vp~fI(Bvk0vd}eRk=P?FcOo|8j56_|@#yr?d^7Lj@9~C# z&8P0$51;RDa$3%Zgpxd8#jas)1#5w?ot=HON0J3!d?*3@vD5AkCfHF!i`j2inEX1t(aEu~4yJ}P9 zZHuumIH(`Dv`=*<;D6JBdd=TaJY-6;IeU}R4Vf30e?NgY-N0S}<;{SJ0!%-(jkF{$ zc|JGgPZ%w%J}PBu1@L=Q{Q(5J+ATSjWBL9TcGtmJ5HlcXh0=9GQaiRk&gHjs?56MR zt|RgfW7S@1k&+<_b(fw!UW}zhMqwB=n(r>1dga;drI+#VquTN10ZrB({Q=N{olZy| zE#I4?hCq0b?&^0C#|Dp&FEqq0o9$I#LmG2X@S{(cK)5U&K{!|)w+KZY6)~@wytI>S zFYXBD@I?>=9s)3a59KqbXHbqYH%vksl=Izbi@9OmpB?v_v^+UfaWV>mvrBS$>**!$ zvkqO$5g=x(=LYD5MyB(m;s07^Xap$YdDJq~M98=$=VRev4#?Aa1gNZdAs8{_c;k!7 z>N%obwr&@#sTa@d+_{Cm8HZulM)N9_QJS`2AW#`}r zQdf+#d^&f)Wb)Y7WOgaJ><<>mhpccc^4@;p1ySL7VP+0T-4qkZ759`B);`(%k+M(X z$Mp`>va?MiDF{#+B1AVTUosQRRfOO^trR)K6DZ#bmjySaunM8ECIP2)S$3zgoiYizL<4y##1tC8CFK{PiPl)A!vBB(438R zy&b_y;k#$h#0&S0&!c|@(&Vu;lf9?G;GB&wria1)$P>r`<;BBwAH+I+1bu~c)EQaK zQ-IqLd`E5r%!iysgTdy7qxxy~3`f<=bdj$QQ-n+x4H17tJR1U=M{0#hM*)b=2})5R zR?t+3))=Sw%wCaVV(Y3I-&~>oi>I1ex+%;>gh-Lvj6LFGT}24@iyV$vC7?#6*@^@M zF$zgq16dmjqNA<^IagQ_y+`AhDeC=>@|5BTC$J@awCk^p4p{4sv zcMn-hpc3kvHP6D{C1QRdVNQr`oO#u6@YryqP!IXq6s2JU{Yo4Z@j(PDr~>QX=sm=A7^n)Y24O~JOo*MJ6l53SSZQMN1E8A0Plr1G38ekEIDcQBu6h+`H!4&J zDgeDAfc(UGaY=ibPsvu;`M|1ssPU)yR+r=3QGAq-L<&boN0U~e@pD_aa?^j zXyvWbn)1nZl}zs?1|XeI#=#sDdic!1IgEAG0^&=2@5Yo+No48!Bh6q+R7Ss6(6P)w zNwd~M=E0$UgeQi~C;t+hr4Dy1njZpv8Vz9s{&=Iph#Xhs$p#arpoXS)dsL229kzOZ z#~a=6NqOHm;}z*^*-*ewd{%Nx3l708+ioRe)(nr7OeDuE>cr-7aOM1 z{WU|Flrci4QINbsg<%*yR@bnXT{pDFQaDc2qEA8yX^8T!EA(F^ldhyK!E~^&$ zN3DYU_-QAE_ftrSrgm5BRD#L+Qqd){hBZH>rKkleOvQIHVM=Cm!~R< zK)*$jf+*y`Org=~bJy!ddS)IQ@oE+}t~dsv>vr6u$*z_l(BB zG^bBmb`(Mn=+CsQj4pG&xofr1*l70I{7T>PJHG$HJ_=P&!aov4Y)*>_mv*2cKHTch z%&7{=<_flS2-!hfKW}DB@4}Vs$F(*4Pe+`KW z8i~U3nEB)rn>|IAuOint`!4~@Z{QJay3DPyMtQS(U5iKjT6%x$INM&6a<_q=pcRxy zH$e%NL%W9{?W;K-7LHv~Sd~}-cp-O7Ru$gbQij;dIOW4_e(AZgZVB@J- za%IKc)EQ*O=Cv28#)7bIc7zst86d`@K|VSO!d6}QNsH;7k^=a8E;yAf&!h&w^X1IM zn<0#quq$PGf5R5#fYekSu zLA1>RR2e)yF=7Q(u_)WupW)cL9Usv>u!Y5_g}8ImBODgI77L%9Ql#=xWh3Gv#$$L@W-mABEOWbqwiRw*5`a}Dbz_BCha zsP&Gy>bOd9_q+!1;F z9uB-&kZkbJ{OmsAR~%GWSy;hYIMUeyhEnU6l(qET10>=!!p);K>AL5ZJ|lp5kdD&RBxzEDF4z#pI&9Dip}FRZy6=P_USfS zHU#G^nFSZHjGTq~a|RmQ7j)BrPC%mQibsFfu z4uNo^k?3o11<@KBepl>E4h4;N`V`EaUZ=rC-dT&TTIdujmV|BR0-Sk@D{Wtku&oH@ zU@AuFqs<}b)S(v{;ZRi2AcYcdLD}9(Gm7msfViK^WZ(TeJL$%z~Gl!1ES}ayW{Rm!A5v#)np<0oY z!M5hbABsb>(Ff5(LMTs72(P7Jsx2$S&3;7R(ZG3tvu;R#HzxJNW3qkJe0-Z3uT8#h z7bwykVyy~}g+itFXRbYsBV%s!l0qYFy{kDlwNtTxfrO*Mjp?FGz^ci2ME6NP*Qb0K zH8|yX0wZFsb9tFrURkfw;FpNF4LQSz`~)`P{s(!g8~PB}iy~~Qpd8u|Ay3Y#xLVQd z67i+hj&>$8cqt9o<=^UHWorc5RPb`G778kagY=J zCwLACr05jnl?C~-Zz&^Rhl+)J!4Eu;$I@R?sY)ZvuA){4i5tHkWyXunNR2I07EN?< zJM(Xa4jZDD@$+sT?0G_vpe4usaCuI;?wKnUlKeec`I|pKezpuEw2A*1$?-Huungzq zz0VrQ;H!=b#8-7H#B^+cI;M&WQKbg<76s7}G%AYz?$2`Q(C9o=8z@H{aR;6hs91Qa z&tVv!xA>|dIHFZx1lHyp(@Le^FZBba8rq$GvsRwsOskg}p;~?$J^U(~riqo*!^# zD{zPfqfXQr9f+;xgTIabN(>>WvEL>0x^Jx7X>7T6ujB+4#B{7@vc&L;O@9<%h}g8% znHyYmTA%~2rWq6I;UAunDUyZ{J2p2TZ5{DgW@G_9*#oYnct~;jCla@TG@xp?jM14H z-Hhd*N|&ofY|r{&JIe3-Qf45m0SyYO%6plvQG?kCXZzAI{e#8fPCuPB1&n)3gwvqM zf-=g8c_5;rA$Mej&v4XbIaD+iip#@!rZG^>unXE)LRzaTGzFvgSp>oNaeHBX@C;_d z^ZG&XGS{rvc>5;d9RQL(>!0uBWo_kedY*p{u$|glCaATYL|~`UD`Nb_I($#;HNnEtnhS3=wgD6T}T;3EWNNLta`-< zCTMkMWg>Qw55Mw)m&Pzup$T4g%gvRlE(O)V+(UC9oFbWPuF>Zcw-gYnPQqgxgUxYc7UIS?J&@A_gjQP2~gpGCT) zA!Wow+#05xsiu9@TNI0asAhWB#Ws0+6BEUiQja^<7rABWY0v>5GNMeN5}a%ERGe1N zHb0*52&nI`)tcWIle;)t?&)(KtUR?V3YgS`|7QfE`2{BQc=_OL>cZab`G>w8U2cM# zlyqI{tXmE#qs-NWq#_?)R-(Q#W{y&p=XCu8l~^`^ye!CFeXHPWVMaZ|WL_T|oQ{0t zqS}Y%BM3XC8u|G^4Vr2Tz#Dn(t9mp+{3hF&+eq%)P2Wny zi`2!qFufIY$ARvplG4_G^mYC`aq2~gvAE>Vw^%-I&0|{)6=Q(d)5rB~zWes=x5GQ= z@z;w8-`(A}Rexj$#=z()9;GBBHK3z#W{1eTt@@MO9Us^22Y0K$3CEFKrkqlXjSxKC zo?M`<@}o6~u>=6RehJvM!*9YTX?VZ9sPkb3O+<%avkXRpb1XG|_AY zo<|K9^?LHpy^e--HiuL28mC>JfZHvdO(MurI1f$`_eGV%Js!J5Nn`L9A!j}?W2y-^ zK*3+DbnNF#Z?rD*$==Q^5k(PutMhd& z$$IN#xHwza7s=X8Y0+CSNI-7x&T?mWYN5cZRxVyyA^D55V3W8j+1WyfMvx8oPb%`> z>Zq>j=pu%DTRw|60~227p{*+FQ0vNwbP>`Kg8j*Ay3q6GOsEP6)zUOE8c^Jg5_CAR zA`;rbdz%WX(Pj~7?KIL99CA%c$^p0ssN1-a&On!2Fh{al+N`K6x&~EpjW8HM1}X{dOkhT_Le<<9bSMTv^>*=u4xn zN$NJ|n=2C(b;pBKzY;*|AzhAH>T9lVZ7Yw0B>F*x3@TJ)Z23L3I=WzGrxnS~;;rT~Io04BFd+>!L|Jm6{Za6YGM!|nSrqon!di4Y5z zo;l7Hkmkx{v0VpM4zeH|TXlg~E3;=TE z#5`#5eb1QHO+`IgTTT1nym?~l{tFGr53!^gmo61H0v3-TAhCQ#N$ue$epbDj*Qcg?#q*o1w zPcRgN(#zK|xj-)OA8r}tpGz$B*R9(yzKxy~w04)esV5HYPf`zbWDM?(t9jLOb|xI) zdhGV=Uc~o79cJ*D4#^PX&&=*hi~@_!a?fstfwO}oq^)?cwqYM>B=$)(KH`-7Ssoj z_f_8uqq!6YTjsyx0*e?C5k%YYcVyJvSC>`&rYtX%qK0sy&&POB{8-XNP>!gIc@(sl zV`2eQBz%Y`@EQ^wcB!muE(SQLAXSrojiQW%Eh}`8EyN1++#J}Dug!Xw*aov#aY}HK zy4QSN?B0_2pm@N!t!|PI=I>3{znMt^WspMy@ zH^dwLulP>GJb%J$C2a1eihkGvG50#op z?2Q&B`-8S}J6Z_!vP2vSZyq5+>hBtNf+ZVV{GB&_&8l5ftQ8CyK|#7`!ogpH!Gw4j ziOQtIdCfMhWWw8Yx8+h#Hkd=HkPFQxm&Sy{uK7%b7}2y-m8@-Ee3MyT-=oB@B~x-Y zc~0k`?{pR(9as?FCYaIZGdKq&3QGk8N^Q(jL zV<6*MQzDxRwvy|{VaTuv9SJHWA-b|(n{&r`X7erjxta`P^;`@iroa@L|1xSmkBPDq z@cv-q+{^939(938rhNKTCr(Zu5v4lq8UA_wMZ#aG8oWl}Zu5S{^JM4dfbQia6@wO6 z;^aIi_}YmBMFUfzd``;&16L<20yFfH z)Buq<@Vr<&%SYOYE<7Z%`OVjo*y)_4U3yuB7>cO8`MLttv{?`9;>Kaoyfte5(-i6( zUi~C#^3^-^jNw{TSj(J(B~o34!sG`^y1TIN9woVR^k3AwaIeogeniW?4W2f&UexN) z;kFMj(biYzHMwzA+b-f9a7k=jPOq(N#(nIc?ONl$cHa9v7yI*AO{dt&>a%9$D$m)8 zp>Jn4#jBG92~WbHd&bD2jTresb8=ryFqk$;VQWQ*FOLr$Gn1x5-0RO>PnS4Z@7v<> zEOT8c%q{NkspC-j#=2WXY7~ki#+*q1IMv@k7NeA*Rwm^LT%9^>~8(tA&H&!F0W4 z;s0UkEyLRAqP9^eP~3_;#fudS?(SAxOQBebyIXOG0>#~}6iIM*cM0wm+^t`p_dVY^ z=TCl2X0AP%z4prNb<1+DfzC<}xyr6#+hf~|5tM%@w+~uOI%;?;5VMy^4qzTnr)R6! zUgzIHUm zN^-Wynu6)4yOSjk>Yc{yXTY%({I!fC7vB6<(Nk|!-ITLiO4a>I?@djDhoVOfWs~LJ zXY4HJo5&TfcuG0MEozTuxHa(R_cVA?be?M?n_bHJyy$K{dfJDnn*RuGfCJ>UE|>1( zQ^L1&vl?1x4*pEEaiJIj;e-(NPk_=Kn>vE4gRh8ijEl0AQ|IX2TJ4`$>lec+)@QsIKs1ow+A!tBWgYhDBh`j?gxl zB1y9P&o*JY1b268R;o7j>oAKr^|-u_&YRfpm^Z7cS|Z53TwzEdn-|4;Cr2Eg#}${) z*JE73OHFnoO-ueyQ8fV**%2i#I{$Jzg=WY697Y|MFzoL=gkas1D|7X=vA5b(TvKU}%G zvO;->vh|($pl%KexuPvm+d}|g2P)7X`>AhqPuS|k8d;6g@>iKzIPWoKu|hXR^6gAV z?3iC@%(mTo)u?&-_fgITm#N8OD6W_O$mU8^x)EVPkga$lgA`EtH=9L?3=pDg1jvxb zvKhM)s1a`So5*nZsl06KqiNO$|EMc)8<1MdcTq<<`a`5z^+XInq<`!7?OHqHZj|>s zo|-9eui}UwkfXe=@*6W~0*r%g-6~fSy+7{acze*x20C?D9X0fhpFq3csH_|nXwv9h zf!N`=P#Tfj6y3Z7hHGw(b}w_M84Czi7-ugL&NL3OUAmg5Rcc%eBgVQez3MumPi?N$ zwVJ1wDB6?~EE*hqRr8)Xkg8opeeU95%=z^XuIupbsQPuSgxN#uG%cNcU!4>Y1zZ*54r9q@cR1uc0HeR3Na z4U%gE{Q13SnHlEplRsGG%ILIG=(sqnU+S@*YgWh*no*bH<@y{Kl46+|Ic0CS>bltn#a0giYKp35QOma>vhKtyBWJzcki10bQ-~2Qv&Aiy-8T4A z-3d8#RShj~W7KSa<5)5ASw06)`Qg6I!s@+{6UsQOnBrXz@BWxBLjxte*usM_M!9pk ziAOgM98PvzY2WBk@1|dnpfWPA>H>Ri?hdxIpk576(NPx+a(s9mt@N4Jx zwIL@Qrc)Ew@nT5?E-&ZsYPfyJ+u5j{a{Mps2`G5r?lzB?YhA=|jJvlcY}*sHuvdi) z-?*Y~!i&Hb#Y2JWL1(M{G%~&^OIVi2Xp}m1Ej@6!dVF<$vD`$xU0IOD!y|QCw2{7` zv^44|%R_MF(t%SZuiA{uj-I2Ljb0h4sN~(*uri74V|8IoGbzfLPU#q@e(QO%W4j5@Y#=^{1l}8n$ zsy4;$;=Ststrn`$(a( zszGKjtmtUc$(SC~&HCBF%Vaa_R}Ml&eX0Ok|EGc+MfR&^y$R2h+QfyH_P*Q>%$~Lz zR@0qAQ;6*f5T@yRkjI&QN2CzL>5?L_nYy>XS13W*>;LCvFOz$_xhuPcLza~6W$^Du z7~}R#NQ=GNNUlxbAKbW=M#3RX1F2=|S4DY^`rhtfu5NXKmPF7aCGl zJU8KU;jLfi-X845-k9`|rQJCqW7||!OWXsy$X}73=6!p^yNd6+)f?)KBo!WK@66`v z>(#*k#4*2*&SjWdfnaB~j~e0F%zZX)aaH{LggZ<{A8Ul;m_JExZYs!YaU}=t>)2M^ zho zS)RYGGUl%r?>zgNPj}D!)s0;{&a^qZt0ZpZ#S^Kb8W|Dlb;MA)TL-Q+ZtO*c?m;S@ zen8ZDo|A)Orig|sWbxh%Bj+_=vOKkzkP3Lg5IH>MBES7*F?-#H{aJysh4*ZB>hjAq z=Alzu9uUI@cvd2OA}5~Rp)72mjubv_@RXb_G>+h7Edg!NXSRQEfNCCl{>@;D;ac}K zpQ5@rzqUV-^aD4^Waa1s{7&q(e{0$muTFS5?=?8Lt6c&=`grcPDj0#lDKmgX*{K1z zg_yu$KLxsrctShwXeou=vS9RN`q6wge4v|d)B)dkudXRZX+~l%U7%K7ahFS8#rooh z1-DDE*>LMx>+;OxG?j?%B;*uoQr?e5P_p9@y}CuRePy$Yb~4!Us?^caP~GGsZ*3y9 zc}FHzvtH(xw^;6qtwN)0V}eWz->UnS zAhi}``osC2YE#2{35E<^gI)&IT#{K`pJFEjv1da1e{d?g5|jtlqfRB+Ya9qsC#wfx zKE|w?&kIpq#^nqD;O<)%0#Y-$9W*>t&X^u4g)sLPKps|&xhzWl&&Kv~^s9oYEb^FV zO?Q>+-wPv|J%~Rxs?hq}XLIdO@v|NAuss=i_iJDexPSB zulCVog$iCU`|Su#>>{Qe#NKru=eglk8&^njpR<` z%uk+c>)Bh7Ua{~~^wQY4#4uz?YMRFYBO#B-ErTE|Jw^0;fpXXeLaJA$b@kh`Ax^GI zAIyQ3OFltvN|)@y9Qs2Eh9<-ah^*i1%HthUCC)#WFzAe+3N}-R{)IF{Eu4LN3u~Q+ zZJ!?$y2q^fZ~3!u@aSw#|0-3G8;}DQ!cu(VD0Yl>f=uM!>kbTkPv0xxB#&|ZYwk-V84?iOuW5>WsiT&(UXs8RX>4gN{0k7z?m6-p@HTN-cHCbtM`BBYs zIAQ0Ix^cAM62PQ7&GzY>_aEG=Z0T@q$;Oe8_Sa>LsN0#$G9G@ygijkbjdo!6XP{$&M{AA831Cb*i{mO4tHwx+}%QY55*Qhqu|d z`kT9$Uir7Q1pRlF0@-amI0*hv%=12CE%4;6@`q`NODG$cFc4ah6W1emtsSJzDid#QQEg5TZCY@rY z7-&bIrt}_&8RcGrByBm|umpM-%$4hW{u&y4awonG(*K_66YYWOI-j2Ee55}>I`Jj* z01S^iw=)>1y4wS{Y$men<|MkbUn+B)o|F#9R+)85IsrJ+{Sf|DaZiz!YsuNgP9}JKKrW@Euz97XgFf1+TOzX4 zBbZ$eRV@YzHymTc9~iB;8x~TbuN)vKV;3VDHspC47yhbsSjNAlzGmAR&?AB4AGxcC zl2Tu-4ICPom>4zod0Y#OlcSSwAA1QgQI0whUJ4R%Rl79WlElD`4z-gDzt&?JUfz4w zaNQyQfx9%VIK?jZcH3WgY&H?Xt1CjgHa1702V~~4uIDXnMavP$M0-qwtEJ1>v8@R* z{y_c9F}aHIKJI&101TvS{ircZ&>m^t^UP|FM6CA=Yy(;Y9VY5PJh7@m+LrC$=A!xRjlS%jKOKd7&- zXC@L)=gFA>s%@qO*p1rS9~@MKv~?NQ76a}7!QJ_sc&HFGIH?YbTyg8Cb=X+PTv?x0 zs44D@3LC^lUP|E6`ZnF;zT1Xa_FS{9-=lSWSrIIR&qJ$GM1iud3r#mn`f>ju;!vC+ zhVPXHHD2b9m!RhEs?hEO1KaEm5I>~W)eC{GW0GalL}}M?CyEgi&bGW zn!9X#HsS8vd}IcPqtKosfOn94!0E4+5$E9o(i)2x>0^)G=Z~FAk-8>@qh?|HxdjYq zLV`%Ai-wJTE={V{D7-?+QnY=32p`%>{fygQ$eZ?aRG3(4!;glpMC)UPdxE?hwlW?U zF`L;G506{SvI6_Gx_@zTgl3XTYhj^^OAMh9NHp#lm3(O2v%xrGO6Q1gBdzSK+LIk7 zEEYH2u|A)%f`(3Jt}Ie%`=v}pO!}NY`9_Zy*1Q_tVfxNWevS?WJ<{#X>00j@3bjH% zf^c!j@6lwta}ADwAGoTa`@uRLw?*ODt&G%3PsJ)O)IQGcgC>Z?~avz4qw@KV5!Oob#I#%LHAK5jmr!)W7 z7}EiL@US|(Nr|p^(HiWnCw|=>HSVI2_y;F7l@M2$UwDVst0r@a)F3;6$p#gGT3>C3B(2l2 zT};^&EC0niU=ptPX*tUSr#)~ezLg5JL$Dz}>Ry?XYjyea#9&ZzWa-dU84^NX>Zkqa zB`<_DY|d^6d!ujM-_P*=@}7j-4xcNE7j z0k_dXvi$1DrCMCQ0cv}6B`d_f<<}^pk$dxRb;@p+XXmYOiMmKt-l}EJol$++RaQ{ z{=^?p!5;KWzMoq8**`dJJ~7#=C+ttu^2E~;7%hq^-zozu_NV`dOGaSuR&2^IAXR-_ z6-Qwir5UfJ!DV0(FoF>*s}+BFn9d02kgBD3m5)M8a`xErvzyv`zmUpEUR$4Z>H^)s z`YqjmI1BXY@Xe8@g%^J`(?INuZQKX#Rp@8nSBFl;51IQaTVuOg6P65k7Urp$$l)`Y|@SgM#kI z-Bopz3FFX*Ya_yj2*Gc6a(+ne%XE5~Uz4V*o9n#zSO-+(Ap6Z*ZKctD?(3(@dC`f> zXL1e3Fj&5O9cph0tYv>YlRA|ajX2U<25+4iq4)E@!_G?iZ>7MD_o#a7(A)6LT&W;- zV~&S=a*FpZdkwyNVcUzN-Z{87yi$IQEKtQ6U zZL4rN-rzC_TBT}>?69R@G+bb9qIREb>|_X<+qtgs2vOQy9Q`9U+v*dB?c*K6!uOv% zt4|nU^`cAJkgNM;e`&CnH}LJT&lz8Be(v#z3QPaO=N0v-B^P#{Z`G6+j$ z())eN-BtVt*Vh{|Mpjv9UFxoHxuZ5q*E*B)j=@->e1F!__p>8gGsJ_T!-_JMTibsE zh3TQZJ|1gB-mH^UuWNbuht7&AH>sTS;=)f-w5Cb}HhL!Wo&NDs*beq0p<=S;_=|8>=zSIs59y@KF zBL!SJ`@u{(Lq!8%LTX0XLAl;HWRkOZn5vrZh1q{_=^tFm!_BQMG`=fDWWM*!T&$<` z6viMQdGYox6#5(p6HmL`Ex!?nS!KSdMXRa%V{Z!ngPWgRad^PH^J>SAmU_e8WDb~L zJNUl`)HE$;H@S0Q$$_7WMf)|!;ZZGKO)JFmf)S=Fhh^6dm^TS*fZ@xd&*cB$u6$vG`j2m_JFb}?Aio`V)=mC*)1~EMqni$I+Hdi&OJQmLze_!SXYtAi&!NJ@dOpS#cBJ$-JD|)^0Lj7xW-ohq|w>kBzOOvoA1PEta4C*9u=jPUtzaSV_B@ zt4hsD->TfdZog#hRr~69Ad~%=(mb(09Vdp$-I%P)@csr=!s@ttp;f_LCkO5-o%6!O zyLG95YG7G)^@y)T+ym_-?@mr!ndJMq(C?i2=`kK7K4k86qdNy(RL4&&ZQ&ElZHBin z`N+L#=-(z pZ~spiHSgb-s~*4h(W%_{G4h|ojzw@o9KTnsH;+V)hnfJ zlSvFzbVzqE>$umW04vyZNj)zwySYb*>9Ou!}t=^4(mZB4Us_wHk@=R2j<)D-{?wm za5V@&v4W`esEIh^5c!d@sH4g)+7+~mAgo0b9JAw%R1D~;tE8q32Mp8E%?4a6PGqQT zY*D!_l^^bn zPRSMh%-s2@?EDFBE!GsN$;y;Nind67eoKId?O zdDVokE(30Ai(){HT_k8G`{K09c>mhU#hBI}aS@BSv$WhCNVcH+k-K-7o#d-ZUz=_s<6=#JYkxp;@hAUcyZ>I_564r6C)pWB&OXnu-?Pxh zIN$x@4F(j2&E3F-^tM^~JG3da8rx7TXZ7N#{xCQ4`6DJ1GgEdjN5Qblvn;tD^*GFO z>^NpaDXQjXh+h9Y_&=p40S+^@BaX_OyeL-Q$k!ntV-FAGn@o!{%3BOBQ9iT4#1!y!mskT zP_~I32gt|!*ae7JL+m`pqFYgWmk%4zvetx{r3zGnoaI0^zklRN#l3q*ThA#On~xe@ zref^3fynH^qxZeL)tqrW1m*`iR$XO|{6`}U6ZX$c712kKu}#){~l zt$ltn?KXW13%G?`S1uQLoFZ(AVYL#M-T8feisv_O+?mDlNOqlm5bDJuZfzWrC;UuV zt*R3AIiv~xxheVS)?hJOt1Xj0q?-%@qFdMXmX2oEUol~v6c7L4xlMc}rd;)6P&=yh zim-LQ)d~Ie&?suqzv26-(LmZ5!31UhxXNyjY%vMkoI%39#089?lx*{;oE8+3+kD&I z?*~I!IqRMSLksj*$;hPr>SJXA#UCmUQG~fI#`7n^8+N<1BtS%q_T5=h9ve#MysLAv zGar+M!N0@F>+RF+alS!yiR&DVE5t&p;!cGXAwBoeOEqVRvEi+L$ez$|k~Va5 zk4{?Z>aI;-38KUJL_ZU;AD*BSos5=0blB2pp$LINI1%_7h*%NwNF%Js1PGVfvRvAz zI7~1@Aa805RD@|bFcTN3Bo12aB|6)Qj$eyDV}XiT)dWr^ls&-u`k|2rRHW1pjj{4M z`^_YAEP+Vj_P#^e#8`)R$x0NmGClp}n!Wzm?m(~Y(kv67+I`ohC6G$+NVMp>YTX+| z39(q19{Wsgt5)usYbrlDT36826uHaG5XZ(ta9s@?th!Qa>;NKuZQ&SMFZ6I7#Hf^P zd%5n0ggw-aT-tHnWQ4l(r2kJeAq1+*)0v|nV9oxeK<~)diZ%umiOd)Mplwu09pEPB=x)M_e;PHYUaAS|6< ze?EU`?ks|TWNO|h7q0x0^Yl>&BX5hvNYGZ)E&BFzrKzy|sL)|pYQ5e*%5AwPU-)pf zdUNoJ27BmEsX}SC6GWw*f?;;vAk@XKyy*uK(YSdKXC!b<3$G$9?AMXRV*M8Fcs+yr z?h06|t`3~w+aRHli{UZLA2nXD!txq~N1-VknP_sdH5j+OTJ-1puYqksE(PL8G7`+B z7KMfVgQCie-?99ikbiLfN2i4y@n-XO0H0=&vmu@IP0RU14fWoOo$aRSPq=zyUb2Xh zP}JViwoi%NzJSX@r^IP4rD1eO&Zaj89{;lRYTZv^_O};<+ag7(9cw?R^1dDwHG@)C z9~2Lmsy^RzJU?g;K+iUw{qn8BnMQuu4j2wrI)%FY81FEODClSs2i8>(raEfcY5PIi zCG0WuX#cAhWzR#*f|J3(KBuwBqMX{=Blu#No;3DFaGN1KjUQ0=tSX=@NWVf{p}P7r zuB0AAyCiSs2sJ3w&*Qfox5O!ET{BUeR$M3HlUH!$2kfS)94h1XJvMhuD zF<$b#CdTfshema|vA4jjTH{Lk8A5i#;1I#iBHyRsCI+%N0KY}MJ<36UtiuA}n)URL z9X?wAJX!?tPuO2NRvce2>`4y=YQbt1-|J>P85aG+?HfB*RLXw&jB%#-c3Zm)(GiA! zW6{a@+9A4Tax}ec2T|*5EsIGMv-6?-SX|?q7j={B%2YGitLdw-OlLQoY=53E-sLkm zhc`8lr@7)8e+k|3xI{1H10@;;4t+b@!HgMtA5z}mOMW47{EWTsNo(gJHCdE*HHkN& z4<1_CRkwIhb$;D<48HFJ1w70>>>3^+A;*qgZG;)lzE^WaT37lVh0Q53%b_0F(dKE} zWnkf^Ma=Z!Begu!qz_5lF>=E4(&CZT0;w&Ek^62A_xOy>%p+5GJ4%(oxM1nmndzR` zV$>^kQok6Rcs^sW3+Q4j$*Tyymaef=j#Mj4;fSu57Xq1IQ9vE{WW#h*a?S?~Qi>M+ zeIyCv73N3| zs)ZF4DIO#v)b&yE9ixpKzoRA_UOYjG9@$=<#kQjmmt_FIWt*6lkx{#mkf)h(tWAWl zmv#;}ea9yfI3MOlz=#JeZ?{#Q&lsE2mmQOv@kUGn78hCjeO7pmbZO!vi&CT*OHh`a zgpI@5#Z;8Y4rS1=_st?U@8T!%@`4ql8`nIH-=Fr>Vd5(9y#Kjn65^fGgMY8>%e_;@->m&@zEb(&@SyD;eA zj%5&U4IcO}QIqPKR8jE7J({iXyM<%`o7b-Z;x`rix?7e_#I%nVUBl*k+Yvbgz4IE#m zR)dyVr&XatN(mQ4^}h{A=A_!Ess{pIem)erEvP7<-221~(xZ>Pc$%E#gdV?*fox`_ zs^`|YiKfO|%3KDPabRi{=e%z5NNL_89oc_y2t0qc8v_2waYmY`HP*u1ZT!)92Qcxg zZAJmxiH9Se5x;iu_WT`Yj%@*xG`cDQpIPgCg)bHxK-pupZUmk1%L`9fNxJna4n=Wf zb|TL0c<5x3SJz`xzJcsBW)t(6z4meX((`943$5*J#2MdE;LK;h&KIN3N2Lr5_}eQO z32FTt?Bz4EG93lrliO=1MO2J-3xrvl)F_Q(!BXZDU%~ zxBePh4vxWc?+1^`9p5Ygz+klUXQ{|YEov>SGHvmSyl?85>J|=<5>Nl3#{Oz@$QdQG z|KQZaZ((kzRHhpg0)CA%Fs)ZMJjO36<{})LAdD$WGhD=3{88Wk={7b z41$rL-Ai;Xd-lp!HE+KQr*NSDxbK@tTTXjv#@p(vii@f_{n2)_({8vET;j0J2T6G5 zzC3Vxb+<~B@wb}4_v%hY*Qu9b3(M9tR{od&`Mgo3c=O*lZG10K0PrWL8|(3R=|+y0 z!!@YuMvjXg!`ux1;~-O?zo44lWP>LkGc&LsLw#dk=g$@k=U$jbe)Pqt`5Aeql3K6h zoh^N!q(tG@<)O5C+avco=^dxVwfQ(72JnR8P4ytT=*DVHF5wvlAGtX}#S?qtILMZ1 zFU8;hq|X6t7H<Yb6qR?+#-JyA0viQC6o09I)xHjh-jA*J6=sW+8mlKz{53y{ob`F~ z54;F1(k=g)TRi0~nRdjQPUsA$CYEA8%yJ-{9xhl59~5hHQjucK<3tkZ89JD|a@0Q` zYD`mIljP_IPwax;`zpf1OT-59`Tb}zv>{afMyTID_P0`!gZGo=&uE2T-AcKvioVZ8HrYo9f=Rl*wyd4-v)sd|Bw2dDo z8Evhy*~!!ggGnvYXH5t;iE=fnwf0JVP@p z{x|9wSNpJ*8tDgbCVq`QpK8lLhP|7m4e+A9Fh^FPqAV?5K!{~v|N0O^FhsYSiP4vrhK`o zYxR^JZ1uPXDr>`KyDqxX0e}so{@{y)G>dtNfO8K_nT6^#w%eGcT*9qC;j`;uinQNq zXuR8NWeY*I@?*!K%n58aGI9`OfN}w+-b;9sN1)wOzk{mvQvXE{&S$2KAdUDhlI4ZY z-Bf3KyYbH={x$+0Ax2P*Mt2Sh;QdXW;4_cLc0h}#KM_Ni0;n~;0 zQS5e4U%~bhJ95(}`mPwAho}1b0W(i9I6`racRJ&cyd&|I-eW{=CE$InFig3QYS`Oo z9VW}&;GgKmU&9_(=w}x@Fz!UTtGCqf;W+4^LgSJujsi5kt#7u11(ptrC($DBCu|rM z5_sK(7|2nyiFti0&xRo~I>cD6*KYfuHWfXXskQBn=AT&?TmSYpzGHsS;)$u$iP7QJ z)#R`(w=$9E{%P864BKP*4#9S1MNzt5_`Bof(Q*Q-8pyEYqJ-bq^< z^EO(QBGF`anjZjv##)=X+dBGP&$K?s8@fv&MJ~b6uBdON{cGH}E>$OwC%myFPZvYy zdp(IEz5R;usEg3*2=DT5v=}3=+Wb;_SQ^1*f4ZC8GkNO~QE`E@uV`>_Sw9oeW=Z>u z53Aq_LuQjg+~QEG^CPROV)4y(`I8q)MgIcnqWkTdD)k^fy-O=W1sUZzxrr{@v#nIU zhS1Ox?)Dtqb=3J)P*X||1_C3A|xL&XW?*plJ8f@Hu#~)Hn_Jfc$Bb}L#W$(@H;SrzNxG|V zlG>>3-}Jn}jKNLw|K;$imr$3@`+-N1#+ll(%Mb4>U`9&EWsXkbhbW{pNDn`?-8ZiU zw(+$p3Th7QHdb=0#w7PfOI;n}f9Uc^*ii2ija{aks^EwA@nm2Dn=41@yd@ocBF}j zIw65Uhd8KARKf4Xf1zrud(t&_5C+cauCqIeT`E2b=bXH@x7@^QT#V&DlZ6TiLgEug zGl4{0i9nr&mM|k3b+5taSQk^DKTVAuEn*tIAzdV}B9@g^qM=A#N8BAbPfJ!5)I781 z!Dr|B9L4^!Uug919;~sHfsi7(H9T-sGf}@CFaT+P`uO(;7ylqfaUYbYR$MrhXJNhC z;L7W}-EYpmR~I2j0{aXnkkM&GY~hyiV@1*~#Qd9|K96sN)z|Z(esp)1XD5x|$Vqec^HxwyBshGZ zER>j|w`vo7bv5op;X1S6l;QKJPt5Rh?#)`@HuT`UEe$KXUXAc9%)@W(eU!~?Pr8Or z-Bx%eqctXcH9i6yGToN~T$pi*II}?Y^wW8%OCZ^*oCbypC{T98I;!m{ZFb?Cu>0fEAYNSyQ&t>n;Cpw@7+K7;epTd!fzJVcZ)3n4iXmU z_2-Jk$Xzc}j^j${*oj`_{Z_o|d(Wp-0U~Ys@us+eClkV~oSsiJC@izL4!b+!!elFw zFibPd8iN^bKhH5J3gdO)j$bO9nDR>Wls`tCR)copKaHM#`q|;hU`arUP__$sk6apR zrkt%)Webb;cKgi!t+y}hU{bGTODlEY5u>HFI;IFqO)IqXu73geN~zK~g{k{tkeC)u zNL55KYMpY!Es>GITgoo6pMJ?V{#{rVFpk{=iJz|3a(=H#?fpqw`(Bh#6_yW8c5qF^ zUAU}&ac{1nlT~sp<(gHcKn8T&1RV}5k9K7aF5krK!Og8Rk=F+_V7eS3>&a-N$sS420ZIei?7*0X}mlU+ZDSj?a z56=gujygsSZDR}%+d`%l8GN%bWl3HO0D(*r51(0!?BD~ZY>~&%_E*d}-NSTJ9Q*QM zSk*nciNBTXSFm8RLTjh}&^v|HR(cm&E><0gcheVI z+-^;%v*r*9Ft(zCsMX%5pl}q`x3{$V6x&36+qTgNEJo%IzE~FlK?b{zGEb^5q%g~x zTh6wffX*((B^8(5E?Iuh-vS=(p%7t%rdEDX%3qS`8;dEP7~f*=hX)}CUCd^e-Fz=Q zp1?_J`s+5X_jt)Fsx0B3DLVKj3u`hJwhpNrIgN(xZ4ZcV`}PmYzKHZ ze;l!yOU25=;f?#4ILRf|G4+xue!(&#=uQrNFCXImE-E6-_MLd5JPjFF(3BKdVGVjM zl>_Ro*--|!cYd+2J2+SzuOX^C5a^>OkO0x- zF<5A$N4-PA6-R2UK$~f(dkX)>Iv+{v`B7gpRwK8WM}g=ti-=v}_j($E<#6^|eSpwh zV^NO=O=<+p!GGSD>I7SfyO0IQF1D{-#diAb^t5<>diKhy)ut8-K9&;-o;raL9qhXx zUjC5VBluNV)2(kLO24(lA0Rrp<e+zqoA}BO+=(0ywX*gTJW@?O6?d{H}Hz+AC~y^?0s9J+(|#@_=14BjR6t z_yoFk-p`Hp^CL=tj+P_fN(QY2_T(99VJ&_uW5mZ$gi95XP?S;MA!{M@=E-eCYDCxn zo(pb^JLb&HVg_HCB+kmuV9pHiB^Vd}&Meia5y#s{mBvBa8CTIMrA1QT)M(rfbYa4V z!;#&I#o-@EOh=N|E>|baV5Zn$4#dJ@5>mrNctC2V>dU+vdCclKzO#YGom;!HiVIu& zYxhqJBYIro^$!Uj>=b3RnOm}`LkPF}K1i^XQt7xKUO(*mI`hh}8r)?x_Hsy`?!%f0 zFf8xb-~RAhxDfV(NHr*P1K_4yHk%!)91JSk&KKX9R^#?sM|{FEr0h|Pk>O96Z3@=X zi9E0}{N&!pkWL4}S{xgGS92Wsyl~5U<;7m1lxz`FUgzH`Z)}hahd0AvY8Z8(cX7^X z+uN^mLXCn#{UcI>1ygRM;$un?DQkJgDZ$Wh=6B-7xFA3ZMtY@GUqX4H4s`}o1^h7+ z7QA>bk48lhLos|)AT?ejLIy@Ht|Fip=ZPBp=y>~?2m%?9D-HJi9@A#O8$7Y{N;H)_ zQTqR;l-yX?LBK5~jm=;+0@!RWh-WI#Jqw2s14ouZN*a&cP+NQ+%q0pZGRQ8Xcv>WrgH9SzVDj@AR)@LB*rN`Ro<}7DLZ(fg2`wc6!zW zmx&wN?c^h{YD40ZFVBNl3#`KBW7j`tAN{=v;7QCAwJ3l4KNW|?hvtg~%#z1NA7vUQ zl)_DW3_2$V+UeKWo!zVQrdnE08bw@5tdDNrO&*TVy0#2*xd!Qh+A`yX)Q9f6$8`w* zyB$+HUOl{WQ5!D&Z4$K`6Gec~upOAipGUk=JVck?2;YsQt6igCQBP9I+{jGPldl2S zJ&H!cElEkQ_~+dW)nT!;>bML-y@u*k1;rBL7apy$V8(EIsd~Y@{d+H;(MJ_qGTC_Q z$HlP&h|pqvl({ybe=31oZJLGZ)(HFeysnS$i>&t9JwJ5(|+Pc>datQ}Mi3_n4JLYveC~Z$nnA!t9L?+hBj-J7QFmP_c$OUS9 zNo}(z<>@*Fq>b^%S^`JkQdm{R8SvMSb>54!>6Qm?Cl_)@LLRUX4JKzrDt1JhArMjM zbRjkF!+UV78SK^jhq=93-Cw56WH(z6?%?(70^3QW6__HKD!|hE5oc?!cdort^EO^g z_;A^5AbREcR~OnRY4Pn~#rbw78h1C1FMUvz8SRKL*>9;h)13;XNWU9700f58><{anNA2IzceSx&+x4i#poF_DL$Q5gO zmbC;4ffi|*3jPORmrzn!l5F^3c}0=T$Ya)^M>1JrPWNY)s-1lPTR^KC*UYJU>ol0U)v@s66air*eA+h1uRtxH|0nstt zQ{lK-epK;CY{P5mzAu(h9{ExUD3U$`kX~&5E#j~(Gt0o5HM4#pTwZlk2tOgX_G-U9 z*|{6khM#S02vJeJvH%NK+r=>)GaL`9PhRC+P3*WiPY=ZER3ZMamjo`CJmQ9;oVZ)+Je!1N`9LALo-Xl8k{(A2DxgJ5D_Zc zJejnOC!kGtd!74==mr8VENNnl_TcO4OPh=X-Ep7)zztW3`R!7l7aGjZZ*H@BM%XC% zs{9x}f7s0ofBTo?Rn_UAX+?6LR(g6vOlmfwV!t zCgLMZv5pgH$VO&IQ~Y}7pFHKF98~_kkPao=0e>{AfY@m93^$n5T8z1`Gg-G#p(l>I zX2SMfbYx(ZU`sFy*i|)9aaQFx#KwgztdrwPr{yjT8Vpy=P5@!YE9$Gsh)MGoCwD#Dbtw2T?SJ0J16u_e zia~C3oS1ZH^X(-cBb)Ir_N5d?IqJS7oi_ z-(oM<6egB40U20%K2D8(Ucu!@0v&0l>?VyUtmgy>MzY0bFK9P4&7_Jw+!-CrC7$+! zwlb4%1>YI(dhIuAt@5-Td9v{b)~&98_^Vbjh!)fHx85xD)nevF06(+V)_rK^I3##o zzx(w-qQp!n25z*7vV6Wq9#>LY#}pyzlsZtI=Y;u@gtASpkfeMA|C5 zNn6}j63ckDHk5rO>s!^0ZtP5Z9z%NnPEmOfS(i>zz7zRaWj7K z!ir9%@$gT3#%krj^Bi&(BmQ#REt@m~(t0X+HkI`55(4j|TFc+EJ~Y+j zZ#OHhMN=G@Ik0Zw9hk!*ZhNw>{h5nUZr{z2h4WD&f^m97Vf{DBNn7Omxoa{OI#s`Z zP5UN|BO3B$f?c5MM5oGV^?l%gr6-$Ry8f@^cThRO*oCj24L2(LsfUq`fO^FHf%b+16Gdp)5bteGloWj0?2>m zYc+3F{tnScdkZCXweNRGuaW&;w;(BDN=r~$6I_&kMm~hyZ zrK&9lpLSoxzb)FU!n3K8xVwA5ZS5MVxtEqGX*#y4>sguZqVHwPG>XxW`|d>Q6!@JV zA-tsgvbQ6>^?Mz%;l{Kgr+DwtsMf?TJEV0a%cFsBQtQLCa3`hVl{7ehI+Ntexjj(|-QA_kCOw|78%p6rnod$35q7E4=7%orwl7XX zM`5!d-4B;@$4|-I;eKD%+@JY>tUrv7cH`4UeX|Dp#R&jhx7SyCOpwd|mv3jI_eLvh z)j~gRiN4(HvBK}H@?HNPb2I8*^(5c8)!pT6o~-G$SN;D4UKgS0GDc~|V(GJ8+JV+v zkoFr;ITf3(>o(OJtv7tkPV_8U$kucShjhzByvaU8@J$pOfcBz2VQXIRHg9S}72wIP z9bs=Zp8y+Mw;JiEN}em2V`qQB92CaOolPsRAz{?rB&FKINm0jPxJusmGL1Pn;ey1Q zUly*~&jh;n2$<8^m6f1y#)px~YHz$}8qv#jb2BuSQkap=Yi>(YYa+C6xuqK&8a*o$ zZJM$rGhK=S6@2Sm+j=G^syR*MhN2^0Ydc-9lF6w<2IkyZ1CLIUroH})dwo(qUD>0z z2>|nHTDx!PlU+~)cwJX8(C328yDgfgGTfHvWOQ#jVz-@{k|Sum*Q~}!3Qp{=;-BAc z^?vAqzzVJR?dZ>B^)Gw5%)m)b`{;I0QjY%jQ9a1Rg$@ZhHuNr<`m5h=7Z((YKo}Zo zS~>Kt9Q?O$2TnaC@p?~%olgo_0zZk5>y^SfI*FbR+iEeOt!dJtt?b%Ft~JOS1Rg)) zY2E&;y`8{5god=#gF*+HZ%(Z2R$J5J-0d!Bj2La)9LU{>CySgp{EF?Zq_4An9Q;Ds zd_&>rB4`YqtPKrs15Sm*K25Ip-^9B<)8Wgf-w`7;mWrQffyoEN+x6Qwz4GE~X0{t` zM7ihL0C6;3G}W6}yRURRrbX;Fy`z~l7_IVoCam?%qCk*34dJ?4v;b>>UruuC9zTwY z@tZ!MGQU2j#Y^$4KA$pt`kxlYJKdwGz#Spr0*B*54h7K;PlYvg?VCM8R zdq5@LL$zE;*`8!@61k?055x>J&2pDDtYI{p&W86=w7KU1TS4+I?Y0tXrL{!oT)HP? z6!i{nSmq5mt~yT~H?{CryoPIcnv9Jr)R)0$isgji?5PbsVRqALhdrX`mYD_*)eCuy z;4YSjQFs;VqIW|FGCki<4z%uu0$hHwnTn0tc>O61scLEv<2w1=XJeO4^&9Xp{Y+Bed2s_D} zs%QHxKSX@%-NnP(GkUz+@@C?6DXo|qDABFUrrNVMG#g&I2QDi!i^Z$RE1L^;NJ#B; zWEH$|CiOkFiz!mkS7f)Yk(;t(YaC20V9esR+lF(MQECCyKseE3wXBV>HPt$BR|ZLz z&1oY*>meqkp?$n$fkiH`%6NA8!0iE#Le$bF*z#J&(@h~>)XG=d!qzdtgDRXA?S@S2 zc*`(o=uXmAM%yx=7K&A4wXBW1b-6D(1}!>5myR9vR7Swzse?^caMn#VlzsUjv|$y= zO}OS(Z*ZJhLlao=!H#uXnn=ys)wpc!WvFy*tvN0BOXSCOVD2_fwYV++04>N8Pz!6PEsYrp)f@mvMi>p$I*f=(>aR-1?<2%gDLeo& zA!n0pu91-yw34=(%PGlo*GpYHu#wi%v`|Xp$gTW!p5=2Krd=+1@~n3Fx1RxZlJ9JvN zh0@9z(Od~VKvj0GZ5^{|-1qIy9fxXZkTsxxY8`dK zdOo(f+xl|m+YzZVwaw7Q?!8`V&{0BMb)OsSs)Hg@%^FtzEz^#tkcVP^A($QYQ|oDgnO8 z2qG=Vp+>TeMO}4ERIg!@ z+Gytvqs}cHM@r|s#mjo!#1}h(o-)=)-Mi^iB3C`O%Trt`TU^}g?!h>&dRv?~wy~l% z`O9@nfu&b}jmx+Yg3?>at?E>{m8L;)2s&Ke3T+lqmCD?ZhZA69m5#}&g{4%uwV?zg47*~T`z*=giR#H+T%kE^~7d?mB+rpC+L zrnUQp%Qee66(F2f8{*i$DttGOcG@Mnf$f@Rf?g@W;-^9KF7dTVW4S)r!j5SmwB|n| z@2>e`#%AdwOCgPdjcrLh&KRcVto6;JLA9{QNM@Ir4f`U#weI?p!q=Og_{g&hb-dRq z)UNZz&aSiuw*}HLic`T#u^qfrCC4&Uk!@O=1~sFK(=hA-t*Qv*#YzQ&l}M!FQ$bgLB#PhcPha6m5LqM5ij6bez=LII7^{)I5B3;;3h;7`i65~9;U zIusTG)XyiSX_qUxMcv;@-UR9&t%~_v+T(inaop<_S0>2>w$XRD5yzNwfIU^kYfW=x zO|B!WkcPLb74_@DuMB#sf;l@PgW4%bgtnDA zl`yMz%I+sq!$8v*hyQ`+O8j|XIR}S1)PVC0@&3j!#k&2M)=Jywr3F-tvW?fL|*o6H*U(2y93mq@asE{>Un1Zvl@tpClU>Oi&|9KcG_F0 zE}MGUUGJLv!zn$Y>ejVgE}jf`KB)LJ@s+#A+})pXk*&Omx+D+4kT`|rxs}#~KR zQ``JGuu90@({VU0h8)gRs!tK}F7dhCUfjufas5TnCZAQGm3G%$F>4!e$}iUSE6L<) zq`9<6Iz75R?`~%|0`Ikm^e?BuF{F6;E_O|7$}+r4>8mw4T4fz7R8*0+t_q-~#!7NP ziR^%=0-#_sK*a!&n#!b)Ahi;u0}3jEkakc;!-A1%ot1i2SYUzV#Gq6}Ou<%FSX6Hv zlm!v&k;@c?VfcbAHxbFf?WdUjRpI-3_{#L~Ukoyrk=&4I>~U+p4mn2B?=D`o==$2{ zcP?xAwyp^@9Lo9|Jh3cI($s}9H9;8m>%c6iuxKtS#6kfixaLt!qNEOGL1&N|Eg_}C zBThmByRoFi;y$fPFW6MQUqh&+3_Gjkx@ZZJg{t^}0w#L^maQ$+)x;ozy056&jVXdV(QDs%{WYOWP z-XC=Qe^j9G-M9leuLhDl;dJBb&fOYovzp^)yV_>Ay|P zGyx#$^ae!M44MEQK}Gf?#KXay)5a*!5)@9!R0~k4q8NgR9NyxJPT&wX1Sya;5DDV5 zR7}7vL{0%2=S5I2$pkFOsT!vPvWQ@TJmjLn87hflJ%iSergkkVfrR$xq$-a802Zq@ zzl?4K^&A(U?Q5NL=l;^i+yzd63cM$JV{yQ(p^SZhPY*{DQCQ3)Ng= zVS_;Ps(^JkC;~B4PL&!IjV>Xhtg41A0bChJN=Q1E6*F;KN;iNF6+~_sAaDTSf;VFz z9F$adbN4pn{U>%?)>e1h5p|iOfu z;pLtbym7DYq|AP4LE^h_@7?+*k=cdy9wUSZimDjk6cRAdbp=4N+4QIwoKQH(pkxY|AQxn%Ae)k~N)Kcc z#|1+?;E0X6DHP`=Kn#=!;#3bsLX4bJ6nTQG0CrvvN{TCnvPPCTRi`kf(=!CPWM5f8 z*vV@{XuN#9Qb(gr>l^}21}nz&I&HE(=E^qlBT@3=zOM$j(mY(3J1jQ`n( z$Rvy$ zJSBJFzBTDz4iYpt%57FS;(&LDU)bio$ zLF0Q<-piLchK>w0Inxn}>BrTbn_E+@hUIK8LH9REJKAPIrE&RFlPRn2F9BWna$gHu zehgU~Sr`ptX^EoaE22*tTkSVq9=UGlV`yV)Fam~(QSlFp{mq-i(OMAr^&aGL&-PbM zEsWL<;R%7UnHyJ{>93QAC3Yh{&MqXl)SSs#R+h%lCVY;_HiN8{>iVlv1juK-w53xjV=D-yRLO!b4HZCx+IXv!z=PXLRRed7=9yCOqvn|~ z10xB|X43)_MmVdJ1Mk{E6~HT$!Q_#+W5F_E4}8{&DV4!{!ww1DVB$v6GGO|`7&&-X zCIjBJs${{$?HCi9xiAOJGZ?99AVjh_D3u zx9PaxdU>q0=KOObV(@LyamDRxS=L_OJxDxh0a>m`IbgyIFYEG>DiOJ-ev)4A; z8*4RN5-`S|W$ya^*PEBB=exKC=);yr98Rt($XcI3TEii9Eh&Y`z1^yInIix=6M#H)42!#OB*GA5M+CsY-kQODOGqTN7gK@Dzw z`&1X5)gxz}qK%Qj424S6+NL4R&B6ZwG2U{W_tfo%_lf@ii0?U0`|5VX`_0Gk9p_H_ z>UP7n?s4?V>sE4Xosl~?2Mp>uis?D47mX`-AI|fw&UE6->?6-;98D)oy)_eNypNhF-w@8zTwjs8w{Fy5BY~l_s}yZ=Gu?gNO5TN0l;R z3-4IhX{bFQtoOz3A$ZYcS#UB=e9$y3MzS1q~q^=@(0e~iKf8>sc*vHMZ4hN6>rBz9ZpSifnh!hzeI z=b>}IV`irm0o*1z{Hr&bwei3kxk{IkuVe|hnEfT5`n|B-{J&VIbvs~%)WA@htMXLs zkETD{?2%b`ZQH?tw-+(Tq5%AxSDi$x+Nbev#19JET+c4+ac?ETCsnjGd(D208|" +#Import "" +#Import "" +#Import "" + +#Import "assets/psionic/ninja.b3d" +#Import "assets/psionic/nskinbl.jpg" + +Using std.. +Using mojo.. +Using mojo3d.. + +Class MyWindow Extends Window + + Field _scene:Scene + + Field _camera:Camera + + Field _light:Light + + Field _ground:Model + + Field _ninja:Model + + Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable ) + + Super.New( title,width,height,flags ) + + 'create scene + ' + _scene=Scene.GetCurrent() + _scene.SkyTexture=Texture.Load( "asset::miramar-skybox.jpg",TextureFlags.FilterMipmap|TextureFlags.Cubemap ) + + 'create camera + ' + _camera=New Camera + _camera.AddComponent() + _camera.Near=.1 + _camera.Far=100 + _camera.Move( 0,10,-20 ) + + 'create light + ' + _light=New Light + _light.Rotate( 75,15,0 ) 'aim directional light 'down' - Pi/2=90 degrees. + _light.CastsShadow=True + + 'create ground + ' + _ground=Model.CreateBox( New Boxf( -50,-5,-50,50,0,50 ),1,1,1,New PbrMaterial( Color.Green ) ) + _ground.CastsShadow=False + + 'create turtle + ' + _ninja=Model.LoadBoned( "asset::ninja.b3d" ) +' _ninja.Scale=New Vec3f( .125 ) + + Local anim1:=_ninja.Animator.Animations[0].Slice( 1,14 ) + Local anim2:=_ninja.Animator.Animations[0].Slice( 206,250 ) + _ninja.Animator.Animations.Add( anim1 ) + _ninja.Animator.Animations.Add( anim2 ) + + End + + Method OnRender( canvas:Canvas ) Override + + RequestRender() + + If Keyboard.KeyDown( Key.Enter ) + _ninja.Animator.Animate( 1,.4,.2 ) + Else + _ninja.Animator.Animate( 2,.4,.2 ) + Endif + + _scene.Update() + + _scene.Render( canvas,_camera ) + + canvas.Scale( Width/640.0,Height/480.0 ) + + canvas.DrawText( "Width="+Width+", Height="+Height+", anim time="+_ninja.Animator.Time+", FPS="+App.FPS,0,0 ) + End + +End + +Function Main() + + New AppInstance + + New MyWindow + + App.Run() +End diff --git a/modules/mojo3d-loaders/tests/turtle.monkey2 b/modules/mojo3d-loaders/tests/turtle.monkey2 index 158a660b..c72281d9 100644 --- a/modules/mojo3d-loaders/tests/turtle.monkey2 +++ b/modules/mojo3d-loaders/tests/turtle.monkey2 @@ -6,8 +6,8 @@ Namespace myapp #Import "" #Import "" -#Import "assets/turtle1.b3d" -#Import "assets/turtle1.png" +#Import "assets/psionic/turtle1.b3d" +#Import "assets/psionic/turtle1.png" #Import "util" @@ -80,7 +80,6 @@ Class MyWindow Extends Window Next _turtle.Animator.Animate( 1,1 ) - End Method OnRender( canvas:Canvas ) Override From f2c67127f24d60ae0b7e98aaaeb8f15677038e91 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 20 Nov 2017 10:32:34 +1300 Subject: [PATCH 07/23] Cleanups. --- .../tests/{dockingview_text.monkey2 => dockingview_test.monkey2} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/mojox/tests/{dockingview_text.monkey2 => dockingview_test.monkey2} (100%) diff --git a/modules/mojox/tests/dockingview_text.monkey2 b/modules/mojox/tests/dockingview_test.monkey2 similarity index 100% rename from modules/mojox/tests/dockingview_text.monkey2 rename to modules/mojox/tests/dockingview_test.monkey2 From 6cf4ab9633aac3ba0df8e8357d5d72e2654bb91b Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 20 Nov 2017 11:49:39 +1300 Subject: [PATCH 08/23] Added textureflags to Font.Load --- modules/mojo/graphics/font.monkey2 | 12 ++++++------ modules/mojo/graphics/freetypefont.monkey2 | 17 ++++++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/modules/mojo/graphics/font.monkey2 b/modules/mojo/graphics/font.monkey2 index 93170522..16cc82df 100644 --- a/modules/mojo/graphics/font.monkey2 +++ b/modules/mojo/graphics/font.monkey2 @@ -105,12 +105,12 @@ Class Font Extends Resource #rem monkeydoc Loads a font from a file. #end - Function Load:Font( path:String,height:Float,shader:Shader=Null ) + Function Load:Font( path:String,height:Float,shader:Shader=Null,textureFlags:TextureFlags=TextureFlags.FilterMipmap ) If Not shader shader=Shader.GetShader( "font" ) - Local font:=FreeTypeFont.Load( path,height,shader ) - If Not font And Not ExtractRootDir( path ) font=FreeTypeFont.Load( "font::"+path,height,shader ) + Local font:=FreeTypeFont.Load( path,height,shader,textureFlags ) + If Not font And Not ExtractRootDir( path ) font=FreeTypeFont.Load( "font::"+path,height,shader,textureFlags ) Return font End @@ -157,14 +157,14 @@ End Class ResourceManager Extension - Method OpenFont:Font( path:String,height:Float,shader:Shader=Null ) + Method OpenFont:Font( path:String,height:Float,shader:Shader=Null,textureFlags:TextureFlags=TextureFlags.FilterMipmap ) - Local slug:="Font:name="+StripDir( StripExt( path ) )+"&height="+height+"&shader="+(shader ? shader.Name Else "") + Local slug:="Font:name="+StripDir( StripExt( path ) )+"&height="+height+"&shader="+(shader ? shader.Name Else "")+"&textureFlags="+Int(textureFlags) Local font:=Cast( OpenResource( slug ) ) If font Return font - font=Font.Load( path,height ) + font=Font.Load( path,height,shader,textureFlags ) AddResource( slug,font ) Return font diff --git a/modules/mojo/graphics/freetypefont.monkey2 b/modules/mojo/graphics/freetypefont.monkey2 index 40a12146..3ec319a3 100644 --- a/modules/mojo/graphics/freetypefont.monkey2 +++ b/modules/mojo/graphics/freetypefont.monkey2 @@ -19,13 +19,13 @@ Public #end Class FreeTypeFont Extends Font - Function Load:FreeTypeFont( path:String,fheight:Float,shader:Shader ) + Function Load:FreeTypeFont( path:String,fheight:Float,shader:Shader,textureFlags:TextureFlags ) Local ext:=ExtractExt( path ) If Not ext - Local font:=Load( path+".otf",fheight,shader ) - If Not font font=Load( path+".ttf",fheight,shader ) - If Not font font=Load( path+".fon",fheight,shader ) + Local font:=Load( path+".otf",fheight,shader,textureFlags ) + If Not font font=Load( path+".ttf",fheight,shader,textureFlags ) + If Not font font=Load( path+".fon",fheight,shader,textureFlags ) Return font Endif @@ -40,7 +40,7 @@ Class FreeTypeFont Extends Font Return Null Endif - Local font:=New FreeTypeFont( data,face,fheight,shader ) + Local font:=New FreeTypeFont( data,face,fheight,shader,textureFlags ) Return font End @@ -132,7 +132,7 @@ Class FreeTypeFont Extends Font tx+=gw+1 Next - gpage.image=New Image( pixmap,TextureFlags.Filter|TextureFlags.Mipmap,_shader ) + gpage.image=New Image( pixmap,_textureFlags,_shader ) gpage.glyphs=glyphs ' Print "Loading glyph page "+page+", image size="+gpage.image.Rect.Size @@ -155,14 +155,17 @@ Class FreeTypeFont Extends Font Field _data:DataBuffer Field _face:FT_Face Field _shader:Shader + Field _textureFlags:TextureFlags + Field _height:Int Field _ascent:Int - Method New( data:DataBuffer,face:FT_Face,fheight:Float,shader:Shader ) + Method New( data:DataBuffer,face:FT_Face,fheight:Float,shader:Shader,textureFlags:TextureFlags ) _data=data _face=face _shader=shader + _textureFlags=textureFlags Local size_req:FT_Size_RequestRec From 2da20deaa4148e31c36eb0ed167531213b145107 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 20 Nov 2017 17:43:14 +1300 Subject: [PATCH 09/23] Animation tweaks + FileStream "asset::" fix. --- modules/assimp/assimp.monkey2 | 2 + modules/mojo3d-loaders/loaders/assimp.monkey2 | 4 +- modules/mojo3d-loaders/tests/ninja.monkey2 | 43 ++++-- modules/mojo3d/components/animation.monkey2 | 33 ++++- modules/mojo3d/components/animator.monkey2 | 131 +++++++++++------- modules/std/stream/filestream.monkey2 | 2 + 6 files changed, 146 insertions(+), 69 deletions(-) diff --git a/modules/assimp/assimp.monkey2 b/modules/assimp/assimp.monkey2 index a8294fbc..197f4331 100644 --- a/modules/assimp/assimp.monkey2 +++ b/modules/assimp/assimp.monkey2 @@ -146,6 +146,8 @@ End Class aiAnimation Extends Void + Field mName:aiString + Field mDuration:Double Field mTicksPerSecond:Double diff --git a/modules/mojo3d-loaders/loaders/assimp.monkey2 b/modules/mojo3d-loaders/loaders/assimp.monkey2 index 1fe8e272..dbeed6cb 100644 --- a/modules/mojo3d-loaders/loaders/assimp.monkey2 +++ b/modules/mojo3d-loaders/loaders/assimp.monkey2 @@ -367,7 +367,9 @@ Class AssimpLoader Next - Return New Animation( channels,aianim.mDuration,aianim.mTicksPerSecond ) + Local animation:=New Animation( aianim.mName.data,channels,aianim.mDuration,aianim.mTicksPerSecond,AnimationMode.Looping ) + + Return animation End Method LoadAnimator:Animator( entity:Entity ) diff --git a/modules/mojo3d-loaders/tests/ninja.monkey2 b/modules/mojo3d-loaders/tests/ninja.monkey2 index 0827b82f..bad8d0d4 100644 --- a/modules/mojo3d-loaders/tests/ninja.monkey2 +++ b/modules/mojo3d-loaders/tests/ninja.monkey2 @@ -24,6 +24,8 @@ Class MyWindow Extends Window Field _ninja:Model + Field _animator:Animator + Method New( title:String="Simple mojo app",width:Int=640,height:Int=480,flags:WindowFlags=WindowFlags.Resizable ) Super.New( title,width,height,flags ) @@ -39,7 +41,7 @@ Class MyWindow Extends Window _camera.AddComponent() _camera.Near=.1 _camera.Far=100 - _camera.Move( 0,10,-20 ) + _camera.Move( 0,5,-7 ) 'create light ' @@ -55,12 +57,20 @@ Class MyWindow Extends Window 'create turtle ' _ninja=Model.LoadBoned( "asset::ninja.b3d" ) + + _animator=_ninja.Animator + + _ninja.RotateY( 165 ) ' _ninja.Scale=New Vec3f( .125 ) - Local anim1:=_ninja.Animator.Animations[0].Slice( 1,14 ) - Local anim2:=_ninja.Animator.Animations[0].Slice( 206,250 ) - _ninja.Animator.Animations.Add( anim1 ) - _ninja.Animator.Animations.Add( anim2 ) + _animator.MasterSpeed=0.5 + + Local anim1:=_animator.Animations[0].Slice( "Walk",1,14,AnimationMode.Looping ) + Local anim2:=_animator.Animations[0].Slice( "Idle",206,250,AnimationMode.Looping ) + Local anim3:=_animator.Animations[0].Slice( "Attack",134,145,AnimationMode.OneShot ) + _animator.Animations.Add( anim1 ) + _animator.Animations.Add( anim2 ) + _animator.Animations.Add( anim3 ) End @@ -68,19 +78,30 @@ Class MyWindow Extends Window RequestRender() - If Keyboard.KeyDown( Key.Enter ) - _ninja.Animator.Animate( 1,.4,.2 ) - Else - _ninja.Animator.Animate( 2,.4,.2 ) + If _animator.Animating?.Name<>"Attack" + + If Keyboard.KeyHit( Key.Space ) 'attack! + + _animator.Animate( "Attack",.2 ) + + Else If Keyboard.KeyDown( Key.Enter ) 'walk + + _animator.Animate( "Walk",.2 ) + Else 'idle + + _animator.Animate( "Idle",.2 ) + + Endif + Endif - + _scene.Update() _scene.Render( canvas,_camera ) canvas.Scale( Width/640.0,Height/480.0 ) - canvas.DrawText( "Width="+Width+", Height="+Height+", anim time="+_ninja.Animator.Time+", FPS="+App.FPS,0,0 ) + canvas.DrawText( "Hold [Enter] to strut, [Space] to attack! anim time="+_ninja.Animator.Time,0,0 ) End End diff --git a/modules/mojo3d/components/animation.monkey2 b/modules/mojo3d/components/animation.monkey2 index 7bc5d185..ebd870df 100644 --- a/modules/mojo3d/components/animation.monkey2 +++ b/modules/mojo3d/components/animation.monkey2 @@ -13,14 +13,30 @@ Alias RotationKey:AnimationKey #end Alias ScaleKey:AnimationKey +Enum AnimationMode + + OneShot=1 + Looping + PingPoong +End + #rem monkeydoc @hidden #end Class Animation - Method New( channels:AnimationChannel[],duration:Float,hertz:Float ) + Method New( name:String,channels:AnimationChannel[],duration:Float,hertz:Float,mode:AnimationMode ) + _name=name _channels=channels _duration=duration _hertz=hertz ?Else 24 + _mode=mode + End + + #rem monkeydoc Animation name. + #end + Property Name:String() + + Return _name End #rem monkeydoc Animation channels. @@ -53,7 +69,14 @@ Class Animation Return _hertz End - Method Slice:Animation( begin:Float,term:Float ) + #rem monkeydoc Animation mode. + #end + Property Mode:AnimationMode() + + Return _mode + End + + Method Slice:Animation( name:String,begin:Float,term:Float,mode:AnimationMode ) Local channels:=_channels.Slice( 0 ) @@ -91,7 +114,7 @@ Class Animation channels[i]=New AnimationChannel( posKeys.ToArray(),rotKeys.ToArray(),sclKeys.ToArray() ) Next - Local animation:=New Animation( channels,duration,_hertz ) + Local animation:=New Animation( name,channels,duration,_hertz,mode ) Return animation End @@ -110,9 +133,11 @@ Class Animation Private + Field _name:String Field _channels:AnimationChannel[] Field _duration:Float Field _hertz:Float + Field _mode:AnimationMode End #rem monkeydoc @hidden @@ -232,7 +257,7 @@ Class AnimationKey End Private - + Field _time:float Field _value:T End diff --git a/modules/mojo3d/components/animator.monkey2 b/modules/mojo3d/components/animator.monkey2 index ec3d5e5a..914f66ad 100644 --- a/modules/mojo3d/components/animator.monkey2 +++ b/modules/mojo3d/components/animator.monkey2 @@ -16,6 +16,8 @@ Class Animator Extends Component Const Type:=New ComponentType( "Animator",0,ComponentTypeFlags.Singleton ) + Field Finished:Void() + Method New( entity:Entity ) Super.New( entity,Type ) End @@ -28,10 +30,6 @@ Class Animator Extends Component _skeleton[i]=_skeleton[i].LastCopy End _animations=animator._animations - _playing=animator._playing - _paused=animator._paused - _speed=animator._speed - _time=animator._time End Property Skeleton:Entity[]() @@ -51,12 +49,21 @@ Class Animator Extends Component _animations=animations End + + Property MasterSpeed:Float() + + Return _speed - Property Playing:Bool() + Setter( speed:Float ) - Return _playing<>Null + _speed=speed End + Property Animating:Animation() + + Return _playing ? _animation Else Null + End + Property Paused:Bool() Return _paused @@ -66,15 +73,6 @@ Class Animator Extends Component _paused=paused End - Property Speed:Float() - - Return _speed - - Setter( speed:Float ) - - _speed=speed - End - Property Time:Float() Return _time @@ -84,32 +82,46 @@ Class Animator Extends Component _time=time End - Method Animate( animationId:Int,speed:Float=1.0,transition:Float=0.0 ) + Method FindAnimation:Animation( name:String ) - DebugAssert( animationId>=0 And animationId<_animations.Length,"Animation id out of range" ) + For Local animation:=Eachin _animations + + If animation.Name=name Return animation + Next - Local anim:=_animations[animationId] - If anim<>_playing - If _playing And transition>0 - _playing0=_playing - _speed0=_speed - _time0=_time - _transdur=transition - _transtime=0 - _trans=True - Else - _trans=False - Endif - _playing=anim - _speed=speed - _time=0 + Return Null + End + + Method Animate( name:String,transition:Float=0.0,finished:Void()=Null ) + + Animate( FindAnimation( name ),transition,finished ) + End + + Method Animate( animation:Animation,transition:Float=0.0,finished:Void()=Null ) + + If Not animation return + + If _playing And _animation=animation Return + + If _playing And transition>0 + _animation0=_animation + _time0=_time + _transition=transition + _transtime=0 + _trans=True + Else + _trans=False Endif + _animation=animation + _time=0 + _finished=finished + _playing=True End Method Stop() - _playing=Null + _playing=False End Protected @@ -127,20 +139,40 @@ Class Animator Extends Component If _trans _transtime+=elapsed - If _transtime<_transdur - blend=_transtime/_transdur + If _transtime<_transition + blend=_transtime/_transition Else _trans=False Endif Endif - _time=UpdateTime( _playing,_time,_speed,elapsed ) + Local duration:=_animation.Duration/_animation.Hertz + _time+=_speed*elapsed + If _time>=duration + Select _animation.Mode + Case AnimationMode.OneShot + _time=duration + _playing=False + Case AnimationMode.Looping + _time-=duration + End + _finished() + Endif If _trans - _time0=UpdateTime( _playing0,_time0,_speed0,elapsed ) - UpdateSkeleton( _playing0,_time0,_playing,_time,blend ) + Local duration0:=_animation0.Duration/_animation0.Hertz + _time0+=_speed0*elapsed + If _time0>=duration0 + Select _animation0.Mode + Case AnimationMode.OneShot + _time0=duration0 + Case AnimationMode.Looping + _time0-=duration0 + End + Endif + UpdateSkeleton( _animation0,_time0,_animation,_time,blend ) Else - UpdateSkeleton( _playing,_time,Null,0,0 ) + UpdateSkeleton( _animation,_time,Null,0,0 ) Endif End @@ -149,31 +181,24 @@ Class Animator Extends Component Field _skeleton:Entity[] Field _animations:=New Stack + + Field _playing:Bool=False Field _paused:Bool=False Field _transtime:Float - Field _transdur:Float + Field _transition:Float Field _trans:Bool - Field _playing0:Animation + Field _animation0:Animation Field _speed0:Float Field _time0:Float - Field _playing:Animation + Field _animation:Animation Field _speed:Float Field _time:Float - - Method UpdateTime:Float( playing:Animation,time:Float,speed:Float,elapsed:Float ) - - Local period:=1.0/playing.Hertz - - time+=elapsed * speed - - If time>=playing.Duration * period time-=playing.Duration * period Else If time<0 time+=playing.Duration * period - - Return time - End + Field _finished:Void() + Method UpdateSkeleton( playing0:Animation,time0:Float,playing1:Animation,time1:Float,alpha:Float ) time0*=playing0?.Hertz diff --git a/modules/std/stream/filestream.monkey2 b/modules/std/stream/filestream.monkey2 index 690085e1..abd510a4 100644 --- a/modules/std/stream/filestream.monkey2 +++ b/modules/std/stream/filestream.monkey2 @@ -108,6 +108,8 @@ Class FileStream Extends Stream Return Null End + path=RealPath( path ) + Local file:=fopen( path,mode ) If Not file Return Null From 71d1831f0fe119f13194c00ab13949a530c24895 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Wed, 22 Nov 2017 14:50:57 +1300 Subject: [PATCH 10/23] Added bunnymark banana. --- bananas/bunnymark/assets/wabbit_alpha.png | Bin 0 -> 2283 bytes bananas/bunnymark/bunnymark.monkey2 | 152 ++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 bananas/bunnymark/assets/wabbit_alpha.png create mode 100644 bananas/bunnymark/bunnymark.monkey2 diff --git a/bananas/bunnymark/assets/wabbit_alpha.png b/bananas/bunnymark/assets/wabbit_alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..570a4fa81e663c47882688676c36580ee5f22619 GIT binary patch literal 2283 zcmbVO3s4kg9RKcs1Jsiv5fZ7^rH7C1cK0|AZsX+O4u$gIf~%kzlVxx3Sh>4hZx=XF z^T%fRzZHvgsAub&7`bxiG?DFkrYA(`wZap%!YqBIQ?mh2${}7Dk|bEHAR07m+klWn86b zM4_i6A$a(ZTCXrl6C@1tQ#?j!a4Do=po0$K_)4F9*tmnn7&qf#yrKZHLuuN1a=P+jl}U0 zsFicD&YC|#2|Y=qkT^Ux4hl=gL5bAAf*myJvB+Z(yEfG3>6@O6GY5k@C_^p2dhlax^UKAPHT1 zT8h)4RTEA}x>{GJrPT(!j8g0A^fVeLbd=tqA3Ja1=t`*s#?Cwb>^{;h@LFX~4VMpS?{utu$-nix-gTsj-iJ?&iKSINf@xlq`gQM)JH>Rxz zAZ%%#CClzV)!%!MH!e%1_+q0!H>s{`Ps*b3`Xu?Hx?}`>d{tkPEOy3*Kn#FEj z^X8qd%P)w%?Z13^wBdYA?1~RRyIOEyb9eWS^u6zPu05T;!7!`#^SFycXU+r%E?j&d zzI8>?;ieOVZL+=w#qsy2MYK+hQMNo|2J!{rYr6}gRhzlVPfl2HIy!5^2TeWTm5!aw zs=n5)C9*eGMF#GGrtb}+1)|Mm7@Y@jfqWX|B_-5K-1y$fH8ZdqNboMW1}FYy7;U%+;2B|cUqv&{MY`izrds^BM&s`gJr;+MXfq1qqOV&7{S z2%k9>iyh@-E2;DnG25CIY0pJA-LM?dDzC`DZC13&%B@qr@yn%WtL*7@hcmE1;SY`H z9T$49mb`q^EI;NqPJ9WDp};6qx@+_aV@bxVZ!z-9OM2BZMjUotUAYctDuj zrPQsuc<`eil!FW3brIpVZ_$c_myk7Jf|QT?(n)hVy!|dy|H>1-sP*XjTjg7#4xGKs zX0d4k3&~ zNRbALniabnBR|ex`9fFNu}4uyBg}=Ezw!*&`Es<7A*;IOiKnKUUh#MRB%e98p+*Bv zweP#{%$7*g_rHeW)w4~#UiBk~u1^bYExP}m;8tdE;JYPq)fZi0@ztoy@#iWVRPC98 i-S5Tzz9$;vhCpTl{?@|l5&hEdNM269WqWq%W4{6Q+8=@d literal 0 HcmV?d00001 diff --git a/bananas/bunnymark/bunnymark.monkey2 b/bananas/bunnymark/bunnymark.monkey2 new file mode 100644 index 00000000..f7f8ec12 --- /dev/null +++ b/bananas/bunnymark/bunnymark.monkey2 @@ -0,0 +1,152 @@ +Namespace bunnies + +#Import "assets/wabbit_alpha.png" + +#Import "" +#Import "" + +Using std.. +Using mojo.. + +Global VirtualSize:=New Vec2i( 640,480 ) + +Const initialCount := 10000 + +Function Main() + New AppInstance + New Bunnymark + App.Run() +End Function + +'****************************************************************************************************** + +Class Bunnymark Extends Window + + Field atlas:Image + + Field frames:=New Image[4] + + Field bunnies := New Stack + + Method New() + Super.New("Bunnymark", 1024, 768, WindowFlags.Resizable ) + + Layout="letterbox" + + atlas=Image.Load( "asset::wabbit_alpha.png" ) + + For Local j:=0 Until 2 + For Local i:=0 Until 2 + frames[ j*2+i ]=New Image( atlas,i*32,j*64,32,64 ) + frames[ j*2+i ].Handle=New Vec2f( .5,.5 ) + Next + Next + + For Local n:= 0 Until initialCount + bunnies.Push( New Bunny( 512, 384,frames[ Rnd(4) ] ) ) + Next + End + + Method OnMeasure:Vec2i() Override + + Return VirtualSize + End + + Method OnRender( canvas:Canvas ) Override + + RequestRender() + + If Keyboard.KeyReleased(Key.Escape) Then App.Terminate() + + canvas.Color = Color.White + canvas.DrawRect( 0, 0, App.ActiveWindow.Width , 25 ) + + For Local bunny:=Eachin bunnies + bunny.Update( canvas ) + Next + + canvas.Color = Color.Black + canvas.DrawText( "Bunnymark="+bunnies.Length+" FPS="+App.FPS+" (LMB=+10 MMB=+100 RMB=+1000 +Alt=Remove)",10,5 ) + + End + + Method OnMouseEvent( event:MouseEvent ) Override + + If event.Type = EventType.MouseDown + Local _len := 0 + If event.Button = MouseButton.Left + _len = 10 + Elseif event.Button = MouseButton.Middle + _len = 100 + Elseif event.Button = MouseButton.Right + _len = 1000 + End + + If Keyboard.KeyDown( Key.LeftAlt ) Or Keyboard.KeyDown( Key.RightAlt ) + For Local n := 1 To _len + If bunnies.Length Then bunnies.Pop() + Next + Else + For Local n := 1 To _len + bunnies.Push( New Bunny( Mouse.X, Mouse.Y,frames[ Rnd(4) ] ) ) + Next + End + End + + End + +End + + +'****************************************************************************************************** + + +Class Bunny + + Global gravity := 0.1 + Global border := 32.0 + + Field x: Float + Field y: Float + Field xspeed: Float + Field yspeed: Float + Field maxBounce:= 5.0 + + Field image:Image + + Method New( x:Float,y:Float,image:Image ) + Self.x = x + Self.y = y + + xspeed = Rnd( -10, 10 ) + + Self.image=image + End + + + Method Update:Void( canvas:Canvas ) + yspeed += gravity + + y += yspeed + x += xspeed + + If y < border*2 + y = border*2 + yspeed *= -1 + yspeed = Clamp( yspeed, 0.0, Float( maxBounce ) ) + End + + If y > App.ActiveWindow.Height - border + y = App.ActiveWindow.Height - border + yspeed = -random.Rnd( maxBounce * 3 ) + End + + If( x < border ) Or ( x > App.ActiveWindow.Width - border ) + xspeed *= -1 + x = Clamp( x, border, Float(App.ActiveWindow.Width - border ) ) + End + + canvas.DrawImage( image,x,y ) + End + +End \ No newline at end of file From d5b886bb15786fd19bf053c668463189a1fb85fe Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Wed, 22 Nov 2017 14:51:24 +1300 Subject: [PATCH 11/23] Updates. --- src/mx2cc/test.monkey2 | 71 +++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/src/mx2cc/test.monkey2 b/src/mx2cc/test.monkey2 index 1d63af0f..df266fbe 100644 --- a/src/mx2cc/test.monkey2 +++ b/src/mx2cc/test.monkey2 @@ -1,29 +1,70 @@ +Namespace myapp -Class C +#Import "" - Field c:C - Field f:Int +Using std.. + +Function Main() + + Local stack:=New Stack + + Local compare:=Lambda:Bool( i1:Int,i2:String ) + Return Int(i2)=i1 + End + + ' 1. compile error + stack.Sort( compare ) + ' 2. compile error +' stack.Sort( compare,10 ) + ' 3. works (the name is different) +' stack.Sort2( compare ) - Property F:Int() +End + +Class Stack Extension + + ' I want to compare different types, why not + ' like a compare wrapper with its internal walue + + Method Sort( compareFunc:Bool( v:V,t:T ) ) - Return f + Print "Here!" + + ' custom sorting is here + For Local v:=Eachin Self - Setter( f:Int ) + Next + End + + Method Sort3( compareFunc:Bool( v:V,t:T ) ) - Self.f=f + Print "Here!" + + ' custom sorting is here + For Local v:=Eachin Self + Next End -End - -Function Main() - Local c:C=New C + ' try to change method signature by additional parameter + ' error is still here - c.c=c + Method Sort( compareFunc:Bool( v:V,t:T ),someVar:V ) + + Print "Here2!" - c?.c?.f=10 + ' custom sorting is here + For Local v:=Eachin Self - c?.c?.F=100 + Next + End + + Method Sort2( compareFunc:Bool( v:V,t:T ) ) + + ' custom sorting is here + For Local v:=Eachin Self + + Next + End - Print c.f End From 9e54f7f516126ea70d0a80702af974d9310cb73d Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Thu, 23 Nov 2017 17:10:22 +1300 Subject: [PATCH 12/23] Docs. --- modules/std/newdocs/manual.md | 2 ++ modules/std/stream/filestream.monkey2 | 3 +++ 2 files changed, 5 insertions(+) diff --git a/modules/std/newdocs/manual.md b/modules/std/newdocs/manual.md index 02f26411..b4a02620 100644 --- a/modules/std/newdocs/manual.md +++ b/modules/std/newdocs/manual.md @@ -22,3 +22,5 @@ The standard module includes the following namespaces: | [[std.socket]] | The Socket and SocketStream classes for UDP networking. | [[std.json]] | Classes for loading and saving JSON data. +@import streams.md + diff --git a/modules/std/stream/filestream.monkey2 b/modules/std/stream/filestream.monkey2 index abd510a4..10e24931 100644 --- a/modules/std/stream/filestream.monkey2 +++ b/modules/std/stream/filestream.monkey2 @@ -88,6 +88,9 @@ Class FileStream Extends Stream #rem monkeydoc Opens a file and returns a new filestream. + Note: This method should not be used to open an 'asset stream' because assets are not always files. You should instead use + [[Stream.Open]] for streams that have a stream path prefix such as `asset::`, `internal::`, `external::` etc. + @param path The path of the file to open. @param mode The mode to open the file in: "r", "w" or "rw". From 23d87683e08d0daf8636c8479c2f24fb6a415e0a Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Fri, 24 Nov 2017 16:27:58 +1300 Subject: [PATCH 13/23] Added MX2_MODULE_DIRS support to simple ted2. --- src/ted2/modulemanager.monkey2 | 93 ++++++++++++++++++++-------------- src/ted2/mx2ccenv.monkey2 | 18 ++++++- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/src/ted2/modulemanager.monkey2 b/src/ted2/modulemanager.monkey2 index 9c4da2d9..b5eb7d34 100644 --- a/src/ted2/modulemanager.monkey2 +++ b/src/ted2/modulemanager.monkey2 @@ -6,6 +6,7 @@ Private Const MONKEY2_DOMAIN:="monkeycoder.co.nz" Class Module + Field dir:String Field name:String Field about:String Field author:String @@ -22,6 +23,8 @@ Class ModuleManager Extends Dialog Method New( console:Console ) Super.New( "Module Manager" ) + LoadEnv() + _console=console _table=New TableView @@ -143,7 +146,9 @@ Class ModuleManager Extends Dialog For Local module:=Eachin _procmods - Local src:="modules/"+module.name +' Local src:="modules/"+module.name + Local src:=module.dir+module.name + Local dst:=backupDir+module.name Select GetFileType( src ) @@ -166,7 +171,9 @@ Class ModuleManager Extends Dialog For Local module:=Eachin _procmods Local src:=backupDir+module.name - Local dst:="modules/"+module.name + +' Local dst:="modules/"+module.name + Local dst:=module.dir+module.name Select GetFileType( src ) Case FileType.Directory @@ -239,12 +246,14 @@ Class ModuleManager Extends Dialog Local zip:=module.name+"-v"+module.new_version+".zip" Local dst:=downloadDir+zip - If Not DeleteDir( "modules/"+module.name,True ) +' If Not DeleteDir( "modules/"+module.name,True ) + If Not DeleteDir( module.dir+module.name,True ) Alert( "Error deleting module directory '"+module.name+"'" ) Return False End - If Not ExtractZip( dst,"modules",module.name+"/" ) +' If Not ExtractZip( dst,"modules",module.name+"/" ) + If Not ExtractZip( dst,module.dir,module.name+"/" ) Alert( "Error extracting zip to '"+dst+"'" ) Return False Endif @@ -275,6 +284,7 @@ Class ModuleManager Extends Dialog Local cmd:=MainWindow.Mx2ccPath+" makedocs" For Local module:=Eachin _procmods + cmd+=" "+module.name Next @@ -486,6 +496,7 @@ Class ModuleManager Extends Dialog Endif Else module=New Module + module.dir=ModuleDirs[0] module.version=version module.new_version=version module.status="Uninstalled" @@ -505,45 +516,49 @@ Class ModuleManager Extends Dialog End Method EnumLocalModules() - - For Local f:=Eachin LoadDir( "modules" ) - Local dir:="modules/"+f+"/" - If GetFileType( dir )<>FileType.Directory Continue - - Local str:=LoadString( dir+"module.json" ) - If Not str Continue - - Local obj:=JsonObject.Parse( str ) - If Not obj Continue - - Local jname:=obj["module"] - If Not jname Or Not Cast( jname ) Continue + For Local moddir:=Eachin ModuleDirs - Local jabout:=obj["about"] - If Not jabout Or Not Cast( jabout ) Continue + For Local f:=Eachin LoadDir( moddir ) - Local jauthor:=obj["author"] - If Not jauthor Or Not Cast( jauthor ) Continue - - Local jversion:=obj["version"] - If Not jversion Or Not Cast( jversion ) Continue - - Local name:=jname.ToString() - Local about:=jabout.ToString() - Local author:=jauthor.ToString() - Local version:=jversion.ToString() - - Local module:=New Module - module.name=name - module.about=about - module.author=author - module.version=version - module.status="Local" + Local dir:=moddir+f+"/" + If GetFileType( dir )<>FileType.Directory Continue + + Local str:=LoadString( dir+"module.json" ) + If Not str Continue + + Local obj:=JsonObject.Parse( str ) + If Not obj Continue + + Local jname:=obj["module"] + If Not jname Or Not Cast( jname ) Continue + + Local jabout:=obj["about"] + If Not jabout Or Not Cast( jabout ) Continue + + Local jauthor:=obj["author"] + If Not jauthor Or Not Cast( jauthor ) Continue + + Local jversion:=obj["version"] + If Not jversion Or Not Cast( jversion ) Continue + + Local name:=jname.ToString() + Local about:=jabout.ToString() + Local author:=jauthor.ToString() + Local version:=jversion.ToString() + + Local module:=New Module + module.dir=moddir + module.name=name + module.about=about + module.author=author + module.version=version + module.status="Local" + + _modules[name]=module - _modules[name]=module - - Next + Next + End End Method UpdateTable() diff --git a/src/ted2/mx2ccenv.monkey2 b/src/ted2/mx2ccenv.monkey2 index 0d083dea..31710c12 100644 --- a/src/ted2/mx2ccenv.monkey2 +++ b/src/ted2/mx2ccenv.monkey2 @@ -1,6 +1,8 @@ Namespace ted2 +Global ModuleDirs:String[] + Function GetEnv:String( name:String ) Local p:=libc.getenv( name ) @@ -50,6 +52,8 @@ Function ReplaceEnv:String( str:String,lineid:Int ) Return str End +Public + Function LoadEnv() Local path:="bin/env_"+HostOS+".txt" @@ -78,10 +82,20 @@ Function LoadEnv() SetEnv( name,value ) Next + + Local moddirs:=New StringStack + moddirs.Add( CurrentDir()+"modules/" ) + For Local moddir:=Eachin GetEnv( "MX2_MODULE_DIRS" ).Split( ";" ) + moddir=moddir.Replace( "\","/" ) + If GetFileType( moddir )<>FileType.Directory Continue + moddir=RealPath( moddir ) + If Not moddir.EndsWith( "/" ) moddir+="/" + If Not moddirs.Contains( moddir ) moddirs.Add( moddir ) + Next + ModuleDirs=moddirs.ToArray() + End -Public - Function EnumValidTargets:StringStack( console:Console ) LoadEnv() From 82845922d7df30f07ce6ed0b51293464717ea65e Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Fri, 24 Nov 2017 16:29:18 +1300 Subject: [PATCH 14/23] Added MX2_MODULE_DIRS and simple getter inlining. --- src/mx2cc/builder.monkey2 | 16 +-- src/mx2cc/buildproduct.monkey2 | 7 +- src/mx2cc/func.monkey2 | 13 +++ src/mx2cc/module.monkey2 | 6 +- src/mx2cc/mx2cc.monkey2 | 176 ++++++++++--------------------- src/mx2cc/test.monkey2 | 67 +++--------- src/mx2cc/translator_cpp.monkey2 | 17 +-- src/mx2cc/util.monkey2 | 29 +++-- 8 files changed, 127 insertions(+), 204 deletions(-) diff --git a/src/mx2cc/builder.monkey2 b/src/mx2cc/builder.monkey2 index c3545645..b2f75039 100644 --- a/src/mx2cc/builder.monkey2 +++ b/src/mx2cc/builder.monkey2 @@ -133,8 +133,6 @@ Class BuilderInstance profileName=opts.target+"_"+opts.config If opts.target="windows" And Int( GetEnv( "MX2_USE_MSVC" ) ) profileName+="_msvc" - MODULES_DIR=CurrentDir()+"modules/" - If opts.productType="app" APP_DIR=ExtractDir( opts.mainSource ) ClearPrimTypes() @@ -182,19 +180,25 @@ Class BuilderInstance parsingModule=Null If MX2_LIBS.Empty - If modulesMap["monkey"] Exit - MX2_LIBS.Push( "monkey" ) Endif Local name:=MX2_LIBS.Pop() - Local srcPath:=MODULES_DIR+name+"/"+name+".monkey2" + Local srcPath:="" + For Local moddir:=Eachin module.Dirs + srcPath=moddir+name+"/"+name+".monkey2" + If GetFileType( srcPath )=FileType.File Exit + srcPath="" + Next + If Not srcPath + New BuildEx( "Can't find module '"+name+"'" ) + Continue + Endif module=New Module( name,srcPath,MX2CC_VERSION,profileName ) modulesMap[name]=module modules.Push( module ) - parsingModule=module MX2_SRCS.Push( module.srcPath ) Endif diff --git a/src/mx2cc/buildproduct.monkey2 b/src/mx2cc/buildproduct.monkey2 index f3950975..87555927 100644 --- a/src/mx2cc/buildproduct.monkey2 +++ b/src/mx2cc/buildproduct.monkey2 @@ -32,8 +32,11 @@ Class BuildProduct toolchain=opts.target="windows" And Int( GetEnv( "MX2_USE_MSVC" ) ) ? "msvc" Else "gcc" Local copts:="" - copts+=" -I~q"+MODULES_DIR+"~q" - copts+=" -I~q"+MODULES_DIR+"monkey/native~q" + For Local moddir:=Eachin Module.Dirs + copts+=" -I~q"+moddir+"~q" + Next + copts+=" -I~q"+Module.Dirs[0]+"monkey/native~q" + If APP_DIR copts+=" -I~q"+APP_DIR+"~q" CC_OPTS+=copts diff --git a/src/mx2cc/func.monkey2 b/src/mx2cc/func.monkey2 index b11b3b3e..2e3d67d0 100644 --- a/src/mx2cc/func.monkey2 +++ b/src/mx2cc/func.monkey2 @@ -59,6 +59,8 @@ Class FuncValue Extends Value Field invokeNew:InvokeNewValue 'call to Super.New or Self.new + Field simpleGetter:Bool + Field used:Bool Method New( fdecl:FuncDecl,scope:Scope,types:Type[],instanceOf:FuncValue ) @@ -422,6 +424,17 @@ Class FuncValue Extends Value Endif If block.reachable And ftype.retType<>Type.VoidType Throw New SemantEx( "Missing return statement" ) + + If IsMethod And Not ftype.argTypes And block.stmts.Length=1 + Local retstmt:=Cast( block.stmts[0] ) + If retstmt + Local mvar:=Cast( retstmt.value ) + If mvar And mvar.instance=selfValue + simpleGetter=True +' Print "Return Self."+mvar.member.ToString() + Endif + Endif + Endif Endif diff --git a/src/mx2cc/module.monkey2 b/src/mx2cc/module.monkey2 index d26c64c7..ee6dc1c5 100644 --- a/src/mx2cc/module.monkey2 +++ b/src/mx2cc/module.monkey2 @@ -15,7 +15,9 @@ cfileDir: mojo/mojo.buildv1.0.3/desktop_windows_debug/src #end Class Module - + + Global Dirs:String[] + Field name:String Field srcPath:String @@ -61,5 +63,7 @@ Class Module rfile=hfileDir+"_r.cpp" End + + End diff --git a/src/mx2cc/mx2cc.monkey2 b/src/mx2cc/mx2cc.monkey2 index 179adc14..4b9762a7 100644 --- a/src/mx2cc/mx2cc.monkey2 +++ b/src/mx2cc/mx2cc.monkey2 @@ -5,7 +5,6 @@ Namespace mx2cc #Import "mx2" -'Use newdocs #Import "newdocs/docsnode" #Import "newdocs/docsbuffer" #Import "newdocs/docsmaker" @@ -13,15 +12,6 @@ Namespace mx2cc Using mx2.newdocs -'Use olddocs -'#Import "docs/docsmaker" -'#Import "docs/jsonbuffer" -'#Import "docs/minimarkdown" -'#Import "docs/markdownbuffer" -'#Import "docs/manpage" -' -'Using mx2.docs - #Import "geninfo/geninfo" Using libc.. @@ -34,7 +24,7 @@ Global opts_time:Bool Global StartDir:String -Const TestArgs:="mx2cc makemods" +Const TestArgs:="mx2cc makemods" ' -clean gles20" 'Const TestArgs:="mx2cc makedocs mojo" 'Const TestArgs:="pyro-framework pyro-gui pyro-scenegraph pyro-tiled" @@ -134,6 +124,17 @@ Function Main() LoadEnv( env ) + Local moddirs:=New StringStack + moddirs.Add( CurrentDir()+"modules/" ) + For Local moddir:=Eachin GetEnv( "MX2_MODULE_DIRS" ).Split( ";" ) + moddir=moddir.Replace( "\","/" ) + If GetFileType( moddir )<>FileType.Directory Continue + moddir=RealPath( moddir ) + If Not moddir.EndsWith( "/" ) moddir+="/" + If Not moddirs.Contains( moddir ) moddirs.Add( moddir ) + Next + Module.Dirs=moddirs.ToArray() + Local ok:=False Try @@ -253,10 +254,14 @@ Function MakeMods:Bool( args:String[] ) Local target:=opts.target For Local modid:=Eachin args - - Local path:="modules/"+modid+"/"+modid+".monkey2" - If GetFileType( path )<>FILETYPE_FILE Fail( "Module file '"+path+"' not found" ) + Local path:="" + For Local moddir:=Eachin Module.Dirs + path=moddir+modid+"/"+modid+".monkey2" + If GetFileType( path )=FileType.File Exit + path="" + Next + If Not path Fail( "Module '"+modid+"' not found" ) Print "" Print "***** Making module '"+modid+"' *****" @@ -286,80 +291,6 @@ Function MakeMods:Bool( args:String[] ) Return errs=0 End -'olddocs... -#rem -Function MakeDocs:Bool( args:String[] ) - - Local opts:=New BuildOpts - opts.productType="module" - opts.target="desktop" - opts.config="debug" - opts.clean=False - opts.fast=True - opts.verbose=0 - opts.passes=2 - opts.makedocs=true - - args=ParseOpts( opts,args ) - - opts.clean=False - - If Not args args=EnumModules() - - Local docsMaker:=New DocsMaker - - Local errs:=0 - - For Local modid:=Eachin args - - Local path:="modules/"+modid+"/"+modid+".monkey2" - If GetFileType( path )<>FILETYPE_FILE Fail( "Module file '"+path+"' not found" ) - - Print "" - Print "***** Doccing module '"+modid+"' *****" - Print "" - - opts.mainSource=RealPath( path ) - - New BuilderInstance( opts ) - - Builder.Parse() - If Builder.errors.Length errs+=1;Continue - - Builder.Semant() - If Builder.errors.Length errs+=1;Continue - - docsMaker.MakeDocs( Builder.modules.Top ) - - Next - - Local api_indices:=New StringStack - Local man_indices:=New StringStack - - For Local modid:=Eachin EnumModules() - - Local index:=LoadString( "modules/"+modid+"/docs/__MANPAGES__/index.js" ) - If index man_indices.Push( index ) - - index=LoadString( "modules/"+modid+"/docs/__PAGES__/index.js" ) - If index api_indices.Push( index ) - - Next - - Local tree:=man_indices.Join( "," ) - If tree tree+="," - tree+="{ text:'Modules reference',children:["+api_indices.Join( "," )+"] }" - - Local page:=LoadString( "docs/docs_template.html" ) - page=page.Replace( "${DOCS_TREE}",tree ) - SaveString( page,"docs/docs.html" ) - - Return True -End -#end - - -'newdocs... Function MakeDocs:Bool( args:String[] ) Local opts:=New BuildOpts @@ -388,8 +319,13 @@ Function MakeDocs:Bool( args:String[] ) For Local modid:=Eachin args - Local path:="modules/"+modid+"/"+modid+".monkey2" - If GetFileType( path )<>FILETYPE_FILE Fail( "Module file '"+path+"' not found" ) + Local path:="" + For Local moddir:=Eachin Module.Dirs + path=moddir+modid+"/"+modid+".monkey2" + If GetFileType( path )=FileType.File Exit + path="" + Next + If Not path Fail( "Module '"+modid+"' not found" ) Print "" Print "***** Doccing module '"+modid+"' *****" @@ -542,35 +478,39 @@ End Function EnumModules:String[]() Local mods:=New StringMap - - For Local f:=Eachin LoadDir( "modules" ) - Local dir:="modules/"+f+"/" - If GetFileType( dir )<>FileType.Directory Continue - - Local str:=LoadString( dir+"module.json" ) - If Not str Continue - - Local obj:=JsonObject.Parse( str ) - If Not obj - Print "Error parsing json:"+dir+"module.json" - Continue - Endif - - Local name:=obj["module"].ToString() - If name<>f Continue - - Local deps:=New StringStack - If name<>"monkey" deps.Push( "monkey" ) - - Local jdeps:=obj["depends"] - If jdeps - For Local dep:=Eachin jdeps.ToArray() - deps.Push( dep.ToString() ) - Next - Endif - - mods[name]=deps + For Local moddir:=Eachin Module.Dirs + + For Local f:=Eachin LoadDir( moddir ) + + Local dir:=moddir+f+"/" + If GetFileType( dir )<>FileType.Directory Continue + + Local str:=LoadString( dir+"module.json" ) + If Not str Continue + + Local obj:=JsonObject.Parse( str ) + If Not obj + Print "Error parsing json:"+dir+"module.json" + Continue + Endif + + Local name:=obj["module"].ToString() + If name<>f Continue + + Local deps:=New StringStack + If name<>"monkey" deps.Push( "monkey" ) + + Local jdeps:=obj["depends"] + If jdeps + For Local dep:=Eachin jdeps.ToArray() + deps.Push( dep.ToString() ) + Next + Endif + + mods[name]=deps + Next + Next Local out:=New StringStack diff --git a/src/mx2cc/test.monkey2 b/src/mx2cc/test.monkey2 index df266fbe..21ee626e 100644 --- a/src/mx2cc/test.monkey2 +++ b/src/mx2cc/test.monkey2 @@ -1,70 +1,29 @@ Namespace myapp #Import "" +#Import "" Using std.. -Function Main() - - Local stack:=New Stack - - Local compare:=Lambda:Bool( i1:Int,i2:String ) - Return Int(i2)=i1 - End - - ' 1. compile error - stack.Sort( compare ) - ' 2. compile error -' stack.Sort( compare,10 ) - ' 3. works (the name is different) -' stack.Sort2( compare ) - -End - -Class Stack Extension +Class C - ' I want to compare different types, why not - ' like a compare wrapper with its internal walue + Field x:Int - Method Sort( compareFunc:Bool( v:V,t:T ) ) + Property X:Int() - Print "Here!" - - ' custom sorting is here - For Local v:=Eachin Self - - Next - End - - Method Sort3( compareFunc:Bool( v:V,t:T ) ) + Return x - Print "Here!" - - ' custom sorting is here - For Local v:=Eachin Self - - Next - End - - ' try to change method signature by additional parameter - ' error is still here - - Method Sort( compareFunc:Bool( v:V,t:T ),someVar:V ) + Setter( x:Int ) - Print "Here2!" - - ' custom sorting is here - For Local v:=Eachin Self - - Next + Self.x=x End +End + +Function Main() - Method Sort2( compareFunc:Bool( v:V,t:T ) ) - - ' custom sorting is here - For Local v:=Eachin Self + Local c:=New C - Next - End + c.X=10 + Print c.X End diff --git a/src/mx2cc/translator_cpp.monkey2 b/src/mx2cc/translator_cpp.monkey2 index 99f3a137..906e5a58 100644 --- a/src/mx2cc/translator_cpp.monkey2 +++ b/src/mx2cc/translator_cpp.monkey2 @@ -621,7 +621,12 @@ Class Translator_CPP Extends Translator If func.fdecl.ident="<=>" hasCmp=True Refs( func.ftype ) - Emit( FuncProto( func,True )+";" ) + + If func.simpleGetter And Not _debug + EmitFunc( func,False,True ) + Else + Emit( FuncProto( func,True )+";" ) + Endif Next If cdecl.kind="struct" @@ -795,9 +800,9 @@ Class Translator_CPP Extends Translator For Local func:=Eachin ctype.methods - If func.fdecl.ident="<=>" - hasCmp=True - Endif + If func.fdecl.ident="<=>" hasCmp=True + + If func.simpleGetter And Not _debug Continue EmitBr() EmitFunc( func ) @@ -1136,13 +1141,13 @@ Class Translator_CPP Extends Translator Next End - Method EmitFunc( func:FuncValue,init:Bool=False ) + Method EmitFunc( func:FuncValue,init:Bool=False,header:Bool=False ) Decls( func ) If func.fdecl.IsAbstract Return - Local proto:=FuncProto( func,False ) + Local proto:=FuncProto( func,header ) If func.invokeNew diff --git a/src/mx2cc/util.monkey2 b/src/mx2cc/util.monkey2 index 25c49d43..1adce1e4 100644 --- a/src/mx2cc/util.monkey2 +++ b/src/mx2cc/util.monkey2 @@ -51,19 +51,14 @@ Global STRING_MX2RETURN:=STRING_TILDE+"r" Global STRING_MX2TAB:=STRING_TILDE+"t" Global APP_DIR:String -Global MODULES_DIR:String Function MakeIncludePath:String( path:String,baseDir:String ) - If MODULES_DIR -' If baseDir And baseDir.StartsWith( MODULES_DIR ) Return MakeRelativePath( path,baseDir ) - If path.StartsWith( MODULES_DIR ) Return path.Slice( MODULES_DIR.Length ) - Endif - - If APP_DIR -' If baseDir And baseDir.StartsWith( APP_DIR ) Return MakeRelativePath( path,baseDir ) - If path.StartsWith( APP_DIR ) Return path.Slice( APP_DIR.Length ) - Endif + For Local moddir:=Eachin Module.Dirs + If path.StartsWith( moddir ) Return path.Slice( moddir.Length ) + Next + + If APP_DIR And path.StartsWith( APP_DIR ) Return path.Slice( APP_DIR.Length ) Return path End @@ -98,13 +93,13 @@ End Function MakeRelativePath:String( path:String,baseDir:String ) -' Print "MakeRelativepath("+path+","+baseDir+")" - - While baseDir.EndsWith( "/" ) - baseDir=baseDir.Slice( 0,-1 ) - Wend - baseDir+="/" - + If Not baseDir.EndsWith( "/" ) + Print "Invalid baseDir:"+baseDir + baseDir+="/" + End + + If path.StartsWith( baseDir ) Return path.Slice( baseDir.Length ) + Local relpath:="" While Not path.StartsWith( baseDir ) From b64edeb9fa9a559475a02771817a26db9ec5c711 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Fri, 24 Nov 2017 16:29:46 +1300 Subject: [PATCH 15/23] Updates. --- bin/env_windows.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/env_windows.txt b/bin/env_windows.txt index f784a416..d2f0f51b 100644 --- a/bin/env_windows.txt +++ b/bin/env_windows.txt @@ -3,6 +3,11 @@ ' MX2_WHOLE_ARCHIVE=0 +'Semi-colon separated list of module root dirs, relative to MX2_HOME or absolute. +' +'mx2cc always adds local modules/ dir to start of list. +' +'MX2_MODULE_DIRS=modules;modules_ext 'If you change anything below, you should rebuild all! @@ -35,7 +40,7 @@ MX2_CPP_OPTS_WINDOWS_RELEASE=-O3 -DNDEBUG '***** WINDOWS DESKTOP TARGET - MSVC ***** -MX2_USE_MSVC=0 +MX2_USE_MSVC=1 'https://en.wikipedia.org/wiki/Microsoft_Windows_SDK ' @@ -54,12 +59,12 @@ MX2_LD_OPTS_MSVC_RELEASE= MX2_CC_OPTS_MSVC=-EHs -W0 -MT MX2_CC_OPTS_MSVC_DEBUG=-O1 -MX2_CC_OPTS_MSVC_RELEASE=-Ox -DNDEBUG +MX2_CC_OPTS_MSVC_RELEASE=-O2 -DNDEBUG 'C++ Compiler options MX2_CPP_OPTS_MSVC=-EHs -W0 -MT MX2_CPP_OPTS_MSVC_DEBUG=-O1 -MX2_CPP_OPTS_MSVC_RELEASE=-Ox -DNDEBUG +MX2_CPP_OPTS_MSVC_RELEASE=-O2 -DNDEBUG '***** EMSCRIPTEN/WASM ***** From 5204391f53825d8058e4c0075ae64c6279c67f6c Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Sat, 25 Nov 2017 11:40:52 +1300 Subject: [PATCH 16/23] Making libc wchar friendly. --- modules/libc/libc.monkey2 | 40 +++--- modules/libc/native/libc.cpp | 246 +++++++++++++++++++++++++++-------- modules/libc/native/libc.h | 83 ++++++++++++ 3 files changed, 299 insertions(+), 70 deletions(-) diff --git a/modules/libc/libc.monkey2 b/modules/libc/libc.monkey2 index 505e3ae9..dbed881d 100644 --- a/modules/libc/libc.monkey2 +++ b/modules/libc/libc.monkey2 @@ -94,7 +94,7 @@ Const SEEK_SET:Int Const SEEK_CUR:Int Const SEEK_END:Int -Function fopen:FILE Ptr( path:CString,mode:CString ) +Function fopen:FILE Ptr( path:CString,mode:CString )="fopen_utf8" Function rewind:Void( stream:FILE ) Function ftell:Int( stream:FILE Ptr ) @@ -104,29 +104,31 @@ Function fread:Int( buf:Void Ptr,size:Int,count:Int,stream:FILE Ptr ) Function fwrite:Int( buf:Void Ptr,size:Int,count:Int,stream:FILE Ptr ) Function fflush:Int( stream:FILE Ptr ) Function fclose:Int( stream:FILE Ptr ) -Function fputs:Int( str:CString,stream:FILE Ptr ) +Function fputs:Int( str:CString,stream:FILE Ptr )="fputs_utf8" -Function remove:Int( path:CString ) -Function rename:Int( oldPath:CString,newPath:CString ) +Function remove:Int( path:CString )="remove_utf8" +Function rename:Int( oldPath:CString,newPath:CString )="rename_utf8" -Function puts:Int( str:CString ) +Function puts:Int( str:CString )="puts_utf8" '***** stdlib.h ***** Function malloc:Void Ptr( size:Int ) Function free:Void( mem:Void Ptr ) + #If __TARGET__<>"ios" 'gone in ios11! -Function system:Int( cmd:CString )="system_" +Function system:Int( cmd:CString )="system_utf8" #endif -Function setenv:Int( name:CString,value:CString,overwrite:Int )="setenv_" -Function getenv:char_t ptr( name:CString ) + +Function setenv:Int( name:CString,value:CString,overwrite:Int )="setenv_utf8" +Function getenv:char_t Ptr( name:CString )="getenv_utf8" Function exit_:Void( status:Int )="exit" Function atexit:Int( func:Void() )="atexit" Function abort:Void() -Function realpath:char_t Ptr( path:CString,resolved_path:char_t Ptr ) +Function realpath:char_t Ptr( path:CString,resolved_path:char_t Ptr )="realpath_utf8" '***** string.h ***** @@ -172,13 +174,13 @@ Function time:time_t( timer:time_t Ptr ) Function localtime:tm_t Ptr( timer:time_t Ptr ) Function gmtime:tm_t Ptr( timer:time_t Ptr ) Function difftime:Double( endtime:time_t,starttime:time_t ) -Function gettimeofday:Int( tv:timeval Ptr )="gettimeofday_" +Function gettimeofday:Int( tv:timeval Ptr )="gettimeofday_utf8" '***** unistd.h ***** -Function getcwd:char_t Ptr( buf:char_t Ptr,size:Int ) -Function chdir:Int( path:CString ) -Function rmdir:Int( path:CString ) +Function getcwd:char_t Ptr( buf:char_t Ptr,size:Int )="getcwd_utf8" +Function chdir:Int( path:CString )="chdir_utf8" +Function rmdir:Int( path:CString )="rmdir_utf8" '***** sys/stat.h ***** @@ -200,8 +202,8 @@ Struct stat_t Field st_ctime:time_t 'status change End -Function stat:Int( path:CString,buf:stat_t Ptr ) -Function mkdir:Int( path:CString,mode:Int )="mkdir_" +Function stat:Int( path:CString,buf:stat_t Ptr )="stat_utf8" +Function mkdir:Int( path:CString,mode:Int )="mkdir_utf8" '***** dirent.h ***** @@ -209,9 +211,9 @@ Struct DIR End Struct dirent - Field d_name:Void Ptr + Field d_name:CString End -Function opendir:DIR Ptr( path:CString ) -Function readdir:dirent Ptr( dir:DIR Ptr ) -Function closedir( dir:DIR Ptr ) +Function opendir:DIR Ptr( path:CString )="opendir_utf8" +Function readdir:dirent Ptr( dir:DIR Ptr )="readdir_utf8" +Function closedir( dir:DIR Ptr )="closedir_utf8" diff --git a/modules/libc/native/libc.cpp b/modules/libc/native/libc.cpp index bc953add..e6f9aadb 100644 --- a/modules/libc/native/libc.cpp +++ b/modules/libc/native/libc.cpp @@ -1,6 +1,7 @@ #include "libc.h" +/* #if _WIN32 #include #include @@ -10,32 +11,121 @@ #elif __APPLE__ #include #endif - -void setenv_( const char *name,const char *value,int overwrite ){ +*/ #if _WIN32 - if( !overwrite && getenv( name ) ) return; +struct DIR{ + WIN32_FIND_DATAW ffd; + HANDLE hfind; + dirent entry; +}; - bbString tmp=bbString( name )+BB_T( "=" )+bbString( value ); - putenv( tmp.c_str() ); +static void *tmps[32]; +static int tmpsi; -#else - setenv( name,value,overwrite ); -#endif +static const WCHAR *widen( const char *p ){ + + int n=MultiByteToWideChar( CP_UTF8,0,p,-1,0,0 ); + + WCHAR *w=(WCHAR*)malloc( n*2 ); + + MultiByteToWideChar( CP_UTF8,0,p,-1,w,n ); + + free(tmps[tmpsi&31]); + tmps[(tmpsi++)&31]=w; + + return w; } -int system_( const char *cmd ){ +static const char *narrow( const WCHAR *w ){ -#if _WIN32 + int n=WideCharToMultiByte( CP_UTF8,0,w,-1,0,0,0,0 ); + + char *p=(char*)malloc( n ); + + WideCharToMultiByte( CP_UTF8,0,w,-1,p,n,0,0 ); + + free(tmps[tmpsi&31]); + tmps[(tmpsi++)&31]=p; + + return p; +} + +const wchar_t *widen_utf8( const char *p ){ + + return widen( p ); +} + +const char *narrow_utf8( const wchar_t *w ){ + + return narrow( w ); +} + +FILE *fopen_utf8( const char *filename,const char *mode ){ + + return _wfopen( widen( filename ),widen( mode ) ); +} + +int fputs_utf8( const char *str,FILE *stream ){ + + return fputws( widen( str ),stream ); +} + +int remove_utf8( const char *path ){ + + return _wremove( widen( path ) ); +} + +int rename_utf8( const char *oldpath,const char *newpath ){ + + return _wrename( widen( oldpath ),widen( newpath ) ); +} + +int puts_utf8( const char *str ){ + + return _putws( widen( str ) ); +} + +int setenv_utf8( const char *name,const char *value,int overwrite ){ + + const WCHAR *wname=widen( name ); + + if( !overwrite && _wgetenv( wname ) ) return -1; + + WCHAR *wbuf=(WCHAR*)malloc( (strlen(name)+strlen(value)+2)*2 ); + + wcscpy( wbuf,wname ); + wcscat( wbuf,L"=" ); + wcscat( wbuf,widen( value ) ); + + int n=_wputenv( wbuf ); + + return n; +} + +char *getenv_utf8( const char *name ){ + + static char *p; + + const WCHAR *wname=widen( name ); + + WCHAR *w=_wgetenv( wname ); + if( !w ) return 0; + + free( p ); + p=strdup( narrow( w ) ); + + return p; +} + +int system_utf8( const char *cmd ){ bool inherit=false; DWORD flags=CREATE_NO_WINDOW; - STARTUPINFOA si={sizeof(si)}; + STARTUPINFOW si={sizeof(si)}; PROCESS_INFORMATION pi={0}; - bbString tmp=BB_T( "cmd /S /C\"" )+BB_T( cmd )+BB_T( "\"" ); - if( GetStdHandle( STD_OUTPUT_HANDLE ) ){ inherit=true; @@ -49,8 +139,19 @@ int system_( const char *cmd ){ flags=0; } + +// bbString tmp=BB_T( "cmd /S /C\"" )+BB_T( cmd )+BB_T( "\"" ); + + const WCHAR *wopts=L"cmd /S /C\""; + const WCHAR *wcmd=widen( cmd ); - if( !CreateProcessA( 0,(LPSTR)tmp.c_str(),0,0,inherit,flags,0,0,&si,&pi ) ) return -1; + WCHAR *wtmp=(WCHAR*)malloc( (wcslen( wopts )+wcslen( wcmd )+2)*2 ); + + wcscpy( wtmp,wopts ); + wcscat( wtmp,wcmd ); + wcscat( wtmp,L"\"" ); + + if( !CreateProcessW( 0,wtmp,0,0,inherit,flags,0,0,&si,&pi ) ) return -1; WaitForSingleObject( pi.hProcess,INFINITE ); @@ -60,61 +161,104 @@ int system_( const char *cmd ){ CloseHandle( pi.hThread ); return res; +} + +char *realpath_utf8( const char *path,char *rpath ){ + + if( !rpath ){ + rpath=(char*)malloc( PATH_MAX ); + if( realpath_utf8( path,rpath ) ) return rpath; + free( rpath ); + return 0; + } -#elif __APPLE__ + WCHAR wbuf[PATH_MAX]; + + if( !GetFullPathNameW( widen( path ),PATH_MAX,wbuf,0 ) ) return 0; + + WideCharToMultiByte( CP_UTF8,0,wbuf,-1,rpath,PATH_MAX,0,0 ); + + return rpath; +} -#if !TARGET_OS_IPHONE - return system( cmd ); -#endif +int mkdir_utf8( const char *path,int mode ){ -#else + return _wmkdir( widen( path ) ); +} - return system( cmd ); +char *getcwd_utf8( char *buf,int size ){ -#endif + WCHAR wbuf[PATH_MAX]; + + if( !GetCurrentDirectoryW( size,wbuf ) ) return 0; + + WideCharToMultiByte( CP_UTF8,0,wbuf,-1,buf,size,0,0 ); + + return buf; +} - return -1; +int chdir_utf8( const char *path ){ + return SetCurrentDirectoryW( widen( path ) ) ? 0 : -1; } -int mkdir_( const char *path,int mode ){ -#if _WIN32 +int rmdir_utf8( const char *path ){ + + return _wrmdir( widen( path ) ); +} + +int stat_utf8( const char *path,stat_t *buf ){ + + return _wstat( widen( path ),buf ); +} - return mkdir( path ); +DIR *opendir_utf8( const char *path ){ + + const WCHAR *wpath=widen( path ); + + WCHAR *wbuf=(WCHAR*)malloc( (wcslen(wpath)+3)*2 ); + + wcscpy( wbuf,wpath ); + wcscat( wbuf,L"\\*" ); -#else + DIR *dir=(DIR*)malloc( sizeof( DIR ) ); + memset( dir,0,sizeof(DIR) ); + + dir->hfind=FindFirstFileW( wbuf,&dir->ffd ); - return mkdir( path,0777 ); + if( !dir->hfind ){ + free( dir ); + dir=0; + } -#endif + free( wbuf ); + + return dir; } -int gettimeofday_( timeval *tv ){ -#if _MSC_VER - - // https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows +dirent *readdir_utf8( DIR *dir ){ - // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's - // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) - // until 00:00:00 January 1, 1970 - static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); + if( !dir->hfind ) return 0; - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; + const char *p=narrow( dir->ffd.cFileName ); + free( dir->entry.d_name ); + dir->entry.d_name=strdup( p ); + + if( !FindNextFileW( dir->hfind,&dir->ffd ) ){ + FindClose( dir->hfind ); + dir->hfind=0; + } + + return &dir->entry; +} - GetSystemTime( &system_time ); - SystemTimeToFileTime( &system_time, &file_time ); - time = ((uint64_t)file_time.dwLowDateTime ) ; - time += ((uint64_t)file_time.dwHighDateTime) << 32; +void closedir_utf8( DIR *dir ){ - tv->tv_sec = (long) ((time - EPOCH) / 10000000L); - tv->tv_usec = (long) (system_time.wMilliseconds * 1000); - return 0; - -#else + if( dir->hfind ) FindClose( dir->hfind ); + + free( dir->entry.d_name ); + free( dir ); +} - return gettimeofday( tv,0 ); - #endif -} \ No newline at end of file + diff --git a/modules/libc/native/libc.h b/modules/libc/native/libc.h index 29b11f23..78e87a78 100644 --- a/modules/libc/native/libc.h +++ b/modules/libc/native/libc.h @@ -4,6 +4,86 @@ #ifndef BB_LIB_C_H #define BB_LIB_C_H +#include +#include +#include +#include +#include +#include +#include + +#if _WIN32 + +#include +#include +//#include //for struct timeval?!? + +#define PATH_MAX 260 + +struct dirent{ + char *d_name; +}; + +struct DIR; + +typedef int mode_t; + +typedef struct tm tm_t; + +typedef struct _stat stat_t; + +const wchar_t *widen_utf8( const char *p ); +const char *narrow_utf8( const wchar_t *w ); + +FILE *fopen_utf8( const char *path,char const *mode ); +int fputs_utf8( const char *str,FILE *stream ); +int remove_utf8( const char *path ); +int rename_utf8( const char *oldpath,const char *newpath ); +int puts_utf8( const char *str ); +int setenv_utf8( const char *name,const char *value,int overwrite ); +char *getenv_utf8( const char *name ); +int system_utf8( const char *cmd ); +char *realpath_utf8( const char *path,char *resolved_path ); +int mkdir_utf8( const char *path,int mode ); +int gettimeofday_utf8( timeval *tv ); +char *getcwd_utf8( char *buf,int size ); +int chdir_utf8( const char *path ); +int rmdir_utf8( const char *path ); +int stat_utf8( const char *path,stat_t *buf ); +DIR *opendir_utf8( const char *path ); +dirent *readdir_utf8( DIR *dir ); +void closedir_utf8( DIR *dir ); + +#else + +typedef struct tm tm_t; + +typedef struct stat stat_t; + +#define fopen_utf8 fopen +#define fputs_utf8 fputs +#define remove_utf8 remove +#define rename_utf8 rename +#define puts_utf8 puts +#define setenv_utf8 setenv +#define getenv_utf8 getenv +#define system_utf8 system +#define realpath_utf8 realpath +#define mkdir_utf8 mkdir +#define gettimeofday_utf8 gettimeofday +#define getcwd_utf8 getcwd +#define chdir_utf8 chdir +#define rmdir_utf8 rmdir +#define stat_utf8 stat +#define opendir_utf8 opendir +#define readdir_utf8 readdir +#define closedir_utf8 closedir + +#endif + +#endif + +/* #include #include #include @@ -40,3 +120,6 @@ int mkdir_( const char *path,int mode ); int gettimeofday_( timeval *tv ); #endif + +*/ + From e497a0f3591227bf9d1c80dd2a050a6d1492b900 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Sat, 25 Nov 2017 11:41:23 +1300 Subject: [PATCH 17/23] Making libc wchar friendly. --- modules/std/filesystem/filesystem.monkey2 | 2 +- modules/std/filesystem/native/filesystem.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/std/filesystem/filesystem.monkey2 b/modules/std/filesystem/filesystem.monkey2 index 124769ca..784116a0 100644 --- a/modules/std/filesystem/filesystem.monkey2 +++ b/modules/std/filesystem/filesystem.monkey2 @@ -610,7 +610,7 @@ Function LoadDir:String[]( path:String ) Local ent:=readdir( dir ) If Not ent Exit - Local file:=String.FromCString( ent[0].d_name ) + Local file:=ent[0].d_name 'String.FromCString( ent[0].d_name ) If file="." Or file=".." Continue files.Push( file ) diff --git a/modules/std/filesystem/native/filesystem.cpp b/modules/std/filesystem/native/filesystem.cpp index 4d9462fb..66406db2 100644 --- a/modules/std/filesystem/native/filesystem.cpp +++ b/modules/std/filesystem/native/filesystem.cpp @@ -4,6 +4,8 @@ #if _WIN32 #include + +#include "../../../libc/native/libc.h" #elif __APPLE__ @@ -126,7 +128,8 @@ namespace bbFileSystem{ #if _WIN32 - return CopyFileW( bbWString( srcPath ),bbWString( dstPath ),FALSE ); +// return CopyFileW( bbWString( srcPath ),bbWString( dstPath ),FALSE ); + return CopyFileW( widen_utf8( bbCString( srcPath ) ),widen_utf8( bbCString( dstPath ) ),FALSE ); #elif __APPLE__ From ab95849fe01afd170cb74229d6bc06ccf31aeeb5 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Sat, 25 Nov 2017 12:13:11 +1300 Subject: [PATCH 18/23] Fixing libc. --- modules/libc/native/libc.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/libc/native/libc.h b/modules/libc/native/libc.h index 78e87a78..f95570d4 100644 --- a/modules/libc/native/libc.h +++ b/modules/libc/native/libc.h @@ -56,6 +56,10 @@ void closedir_utf8( DIR *dir ); #else +#include +#include +#include + typedef struct tm tm_t; typedef struct stat stat_t; From 53b289df86d577e333fa1a39e4608a9d3c46cc70 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 27 Nov 2017 07:12:57 +1300 Subject: [PATCH 19/23] Fixed libc build errors in mingw. --- modules/libc/native/libc.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/libc/native/libc.h b/modules/libc/native/libc.h index f95570d4..3c53a2d3 100644 --- a/modules/libc/native/libc.h +++ b/modules/libc/native/libc.h @@ -16,18 +16,19 @@ #include #include -//#include //for struct timeval?!? #define PATH_MAX 260 +#if _MSC_VER +typedef int mode_t; +#endif + struct dirent{ char *d_name; }; struct DIR; -typedef int mode_t; - typedef struct tm tm_t; typedef struct _stat stat_t; From 662e132f724a3e554cde7d0c9837dd1e95f07714 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 27 Nov 2017 07:14:25 +1300 Subject: [PATCH 20/23] Added -utf-8 flag to msvc tools so msvc uses utf-8 source files always. --- bin/env_windows.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/env_windows.txt b/bin/env_windows.txt index d2f0f51b..882ac612 100644 --- a/bin/env_windows.txt +++ b/bin/env_windows.txt @@ -40,7 +40,7 @@ MX2_CPP_OPTS_WINDOWS_RELEASE=-O3 -DNDEBUG '***** WINDOWS DESKTOP TARGET - MSVC ***** -MX2_USE_MSVC=1 +MX2_USE_MSVC=0 'https://en.wikipedia.org/wiki/Microsoft_Windows_SDK ' @@ -57,12 +57,12 @@ MX2_LD_OPTS_MSVC= MX2_LD_OPTS_MSVC_DEBUG= MX2_LD_OPTS_MSVC_RELEASE= -MX2_CC_OPTS_MSVC=-EHs -W0 -MT +MX2_CC_OPTS_MSVC=-EHs -W0 -MT -utf-8 MX2_CC_OPTS_MSVC_DEBUG=-O1 MX2_CC_OPTS_MSVC_RELEASE=-O2 -DNDEBUG 'C++ Compiler options -MX2_CPP_OPTS_MSVC=-EHs -W0 -MT +MX2_CPP_OPTS_MSVC=-EHs -W0 -MT -utf-8 MX2_CPP_OPTS_MSVC_DEBUG=-O1 MX2_CPP_OPTS_MSVC_RELEASE=-O2 -DNDEBUG From 3b9121413544a8f47803b9cc930c179394713bb7 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 27 Nov 2017 07:22:51 +1300 Subject: [PATCH 21/23] wchar-ized windows std.process --- modules/std/process/native/process.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/std/process/native/process.cpp b/modules/std/process/native/process.cpp index 6c393886..7dea3467 100644 --- a/modules/std/process/native/process.cpp +++ b/modules/std/process/native/process.cpp @@ -2,8 +2,10 @@ #include "process.h" #include "procutil.h" +#include "../../../libc/native/libc.h" #include "../../../std/async/native/async.h" + struct bbProcess::Rep{ struct FinishedEvent : public bbAsync::Event{ @@ -113,7 +115,7 @@ bbBool bbProcess::start( bbString cmd ){ CreatePipe( &err[0],&err[1],&sa,0 ); HANDLE breakEvent=CreateEvent( &sa,0,0,"MX2_BREAK_EVENT" ); - STARTUPINFOA si={sizeof(si)}; + STARTUPINFOW si={sizeof(si)}; si.dwFlags=STARTF_USESTDHANDLES; si.hStdInput=in[0]; si.hStdOutput=out[1]; @@ -123,7 +125,8 @@ bbBool bbProcess::start( bbString cmd ){ DWORD flags=CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; - int res=CreateProcessA( 0,(LPSTR)cmd.c_str(),0,0,TRUE,flags,0,0,&si,&pi ); +// int res=CreateProcessA( 0,(LPSTR)cmd.c_str(),0,0,TRUE,flags,0,0,&si,&pi ); + int res=CreateProcessW( 0,(LPWSTR)widen_utf8( bbCString( cmd ) ),0,0,TRUE,flags,0,0,&si,&pi ); CloseHandle( in[0] ); CloseHandle( out[1] ); From 0a748757e062f0e688800d81417265b6e2da1bee Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Mon, 27 Nov 2017 07:31:45 +1300 Subject: [PATCH 22/23] Updates. --- modules/std/process/native/process.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/std/process/native/process.cpp b/modules/std/process/native/process.cpp index 7dea3467..cf8c55cb 100644 --- a/modules/std/process/native/process.cpp +++ b/modules/std/process/native/process.cpp @@ -125,8 +125,11 @@ bbBool bbProcess::start( bbString cmd ){ DWORD flags=CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; -// int res=CreateProcessA( 0,(LPSTR)cmd.c_str(),0,0,TRUE,flags,0,0,&si,&pi ); - int res=CreateProcessW( 0,(LPWSTR)widen_utf8( bbCString( cmd ) ),0,0,TRUE,flags,0,0,&si,&pi ); + wchar_t *wstr=_wcsdup( widen_utf8( bbCString( cmd ) ) ); + + int res=CreateProcessW( 0,wstr,0,0,TRUE,flags,0,0,&si,&pi ); + + free( wstr ); CloseHandle( in[0] ); CloseHandle( out[1] ); From 9bf2154270edbf752483a199b383ef669fc24a73 Mon Sep 17 00:00:00 2001 From: Mark Sibly Date: Tue, 28 Nov 2017 14:56:16 +1300 Subject: [PATCH 23/23] Fixed mojo3d Morpher test. --- modules/mojo3d/tests/morpher.monkey2 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/mojo3d/tests/morpher.monkey2 b/modules/mojo3d/tests/morpher.monkey2 index 2602f419..44abef5e 100644 --- a/modules/mojo3d/tests/morpher.monkey2 +++ b/modules/mojo3d/tests/morpher.monkey2 @@ -32,6 +32,8 @@ Class Morpher Extends Renderable Local indices:=mesh0.GetIndices( 0 ) _ibuffer=New IndexBuffer( IndexFormat.UINT32,indices.Length ) _ibuffer.SetIndices( indices.Data,0,indices.Length ) + + Visible=true End Protected @@ -126,6 +128,8 @@ Class MyWindow Extends Window Local material:=New PbrMaterial( Color.Green ) _morpher=New Morpher( mesh1,mesh2,material ) + _morpher.Visible=True + End Method OnRender( canvas:Canvas ) Override