├── .gitignore ├── LICENSE ├── README.md ├── ShaderDemo └── game │ ├── director.rpy │ ├── gui.rpy │ ├── gui │ ├── bar │ │ ├── bottom.png │ │ ├── left.png │ │ ├── right.png │ │ └── top.png │ ├── button │ │ ├── check_foreground.png │ │ ├── check_selected_foreground.png │ │ ├── choice_hover_background.png │ │ ├── choice_idle_background.png │ │ ├── hover_background.png │ │ ├── idle_background.png │ │ ├── quick_hover_background.png │ │ ├── quick_idle_background.png │ │ ├── radio_foreground.png │ │ ├── radio_selected_foreground.png │ │ ├── slot_hover_background.png │ │ └── slot_idle_background.png │ ├── frame.png │ ├── game_menu.png │ ├── main_menu.png │ ├── namebox.png │ ├── notify.png │ ├── nvl.png │ ├── overlay │ │ ├── confirm.png │ │ ├── game_menu.png │ │ └── main_menu.png │ ├── phone │ │ ├── nvl.png │ │ ├── overlay │ │ │ ├── game_menu.png │ │ │ └── main_menu.png │ │ └── textbox.png │ ├── scrollbar │ │ ├── horizontal_hover_bar.png │ │ ├── horizontal_hover_thumb.png │ │ ├── horizontal_idle_bar.png │ │ ├── horizontal_idle_thumb.png │ │ ├── vertical_hover_bar.png │ │ ├── vertical_hover_thumb.png │ │ ├── vertical_idle_bar.png │ │ └── vertical_idle_thumb.png │ ├── skip.png │ ├── slider │ │ ├── horizontal_hover_bar.png │ │ ├── horizontal_hover_thumb.png │ │ ├── horizontal_idle_bar.png │ │ ├── horizontal_idle_thumb.png │ │ ├── vertical_hover_bar.png │ │ ├── vertical_hover_thumb.png │ │ ├── vertical_idle_bar.png │ │ └── vertical_idle_thumb.png │ ├── textbox.png │ └── window_icon.png │ ├── images │ ├── amy influence.png │ ├── amy sad.png │ ├── amy.png │ ├── amydoll influence.png │ ├── amydoll.png │ ├── cg influence.png │ ├── cg.jpg │ ├── cube.png │ ├── doll │ │ ├── basecrop.png │ │ ├── doll.rpy │ │ ├── haircrop.png │ │ ├── shirtcrop.png │ │ └── skirtcrop.png │ ├── editorbackground.png │ ├── eileen influence.png │ ├── eileen.png │ ├── forest influence.png │ ├── forest.jpg │ ├── room baked.jpg │ ├── room depth.png │ ├── room normal.png │ ├── room.jpg │ ├── sky.jpg │ └── zeroinfluence.png │ ├── mesh │ ├── cube.obj │ └── room.obj │ ├── options.rpy │ ├── rig │ ├── amydoll idle.anim │ ├── amydoll leftarm.anim │ ├── amydoll rightarm.anim │ ├── amydoll.rig │ ├── doll flail.anim │ ├── doll kneel.anim │ └── doll.rig │ ├── screens.rpy │ ├── script.rpy │ ├── script_3d.rpy │ ├── script_deferred.rpy │ ├── script_rig.rpy │ └── shader │ ├── __init__.py │ ├── controller.py │ ├── delaunay.py │ ├── easing.py │ ├── euclid.py │ ├── geometry.py │ ├── gpu │ ├── __init__.py │ ├── framebuffer.py │ ├── shaderprogram.py │ └── texture.py │ ├── mesh.py │ ├── polygonoffset.py │ ├── rendering.py │ ├── rigeditor.py │ ├── rigeditorscreen.rpy │ ├── shadercode.py │ ├── shaderdisplayable.rpy │ ├── skin.py │ ├── skinnedanimation.py │ ├── skinnedmesh.py │ ├── skinnedplayer.py │ └── utils.py ├── doc └── rigeditor.md ├── pythonlib2.7 └── ctypes │ ├── macholib │ ├── README.ctypes │ ├── __init__.py │ ├── dyld.py │ ├── dylib.py │ ├── fetch_macholib │ └── framework.py │ └── util.py └── tools └── create_live_composite.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.rpyc 2 | *.rpyb 3 | *.txt 4 | *.mtl 5 | *.bat 6 | *.blend* 7 | *.json 8 | .DS_Store 9 | 10 | saves/ 11 | OpenGL/ 12 | junk/ 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 bitsawer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # renpy-shader 2 | 3 | OpenGL shader and [skeletal animation](doc/rigeditor.md) support for Ren'Py. The ShaderDemo Ren'Py project included in this repository has many examples about what this library can do. 4 | 5 | An improved and modified version of this library is currently used in adult visual novel [Offcuts](https://stencilbits.itch.io/offcuts) (NSFW link). That version has some general improvements but it's also tightly tied to that game and a specific Ren'Py version and is not for general use. 6 | 7 | # Examples 8 | 9 | * [Shader effects demo video](https://www.youtube.com/watch?v=nyDbvAy0Xa4) 10 | * [Deferred shading demo video](https://www.youtube.com/watch?v=FceQEEXn7Bg) 11 | * [Full 3D character demo video](https://www.youtube.com/watch?v=etJovr7wQiI) 12 | * [Skeletal rig animation demo video](https://www.youtube.com/watch?v=LL2GuJG_2E0) 13 | * [Rig editor basics](https://www.youtube.com/watch?v=NHJu0OYBERE) 14 | 15 | # Requirements 16 | 17 | * Graphics card with a decent OpenGL support. 18 | * Ren'Py version 6.99.x (might work with older ones, but it's not supported). 19 | * Supports Windows, OS X and Linux (tested on Ubuntu). No Android or iOS support (at least not yet). 20 | 21 | # Installation 22 | 23 | If you are updating your local version instead of installing the first time, it is recommended that you first remove all files belonging to this library before copying in the new ones. Otherwise if files are renamed, moved etc. old files might be lying around and cause some hard-to-debug naming or behavior conflicts with new files. 24 | 25 | 1. Clone or download this repository to your machine. 26 | 27 | 2. **This step is not always needed if you are using Ren'Py 7.x or newer.** Copy the contents of the "pythonlib2.7"-directory to your Ren'Py SDK installation subdirectory "lib/pythonlib2.7". This is required because some versions of Ren'Py ship with a stripped down Python standard library which is missing some required files. 28 | 29 | 3. Download [PyOpenGL](https://pypi.python.org/pypi/PyOpenGL/3.1.1a1) and place its uncompressed package subdirectory "OpenGL" (the one which contains the \__init__.py) either under this projects "ShaderDemo/game"-directory or under the Ren'Py SDK's "lib/pythonlib2.7". If you are using Linux you might want to get the latest version directly from the [repository](https://github.com/mcfletch/pyopengl), but don't do this on Windows or Mac unless PyOpenGL does not get imported correctly. 30 | 31 | 4. Start the ShaderDemo and run the demos or the rig editor. 32 | 33 | # Troubleshooting 34 | 35 | **1) An exception occurs when starting the game: "TypeError: 'NoneType' object is not callable"** 36 | 37 | You probably forgot to do installation step 2 or you put the files in a wrong place. 38 | 39 | **2) An exception occurs when starting the game: "ImportError: No module named OpenGL"** 40 | 41 | You probably forgot to do installation step 3 or you put the files in a wrong place. 42 | 43 | **3) An exception occurs when starting the game: "AttributeError: 'NoneType' object has no attribute 'glGetError'"** 44 | 45 | Some versions of PyOpenGL can have issues with certain platforms. Try the latest (or alternatively the older official) PyOpenGL source code. 46 | 47 | **4) Shader effects are not applied and/or rigs are not moving.** 48 | 49 | You are probably seeing the normal, static fallback images. Make sure the computer and RenPy mode (OpenGL vs software) supports shaders. Most should if the computer is not ancient and the graphics card is not blacklisted. Also make sure that the effects have not been disabled by setting "shader.config.enabled" to False or by the user in the preferences screen (persistent.shader_effects_enabled). 50 | -------------------------------------------------------------------------------- /ShaderDemo/game/director.rpy: -------------------------------------------------------------------------------- 1 | 2 | init python: 3 | import shader 4 | 5 | def findLayer(image, layer): 6 | if layer: 7 | return layer 8 | base = getImageBase(image) 9 | if base in config.layers: 10 | return base 11 | return "master" 12 | 13 | def getImageBase(image): 14 | return image.split(" ")[0] #For example "amy dress smile" would turn into "amy" 15 | 16 | #Helper utilities for making showing and managing shader displayables easier. 17 | #This assumes a certain convention for naming your character sprites. 18 | #In this case they should be, for example "amy dress smile". And if that 19 | #image has an influence map associated with it, it should end with " influence" 20 | #Feel free to modify this to suit your own needs. After all, it's not about 21 | #what kind of naming etc. conventions you have, but that you have one at 22 | #all and that it is consistent. 23 | 24 | class CallChain: 25 | @staticmethod 26 | def dissolve(): 27 | renpy.with_statement(dissolve) 28 | return CallChain 29 | 30 | @staticmethod 31 | def fade(): 32 | renpy.with_statement(fade) 33 | return CallChain 34 | 35 | def rig(image, update=None, xalign=0.5, yalign=1.0, layer=None): 36 | rigFile = image + ".rig" 37 | path = shader.utils.findFile(rigFile) 38 | if not path: 39 | raise RuntimeError("No .rig-file '%s' found for image '%s'" % (rigFile, image)) 40 | 41 | active = findLayer(image, layer) 42 | hide(image, layer=active) 43 | 44 | renpy.show_screen("rigScreen", image, shader.PS_SKINNED, 45 | update=update, args={"rigFile": path}, xalign=xalign, yalign=yalign, 46 | _tag=getImageBase(image), _layer=active) 47 | renpy.show_layer_at([], layer=active) #Stop any animations 48 | return CallChain 49 | 50 | def show(image, pixelShader=shader.PS_WIND_2D, uniforms={}, update=None, xalign=0.5, yalign=0.1, layer=None, textures=None): 51 | #TODO use **kwargs and pass them to show_screen... 52 | active = findLayer(image, layer) 53 | 54 | if textures: 55 | textures = textures.copy() 56 | else: 57 | textures = {} 58 | 59 | influence = image + " influence" 60 | if renpy.has_image(influence, exact=True): 61 | #Has an influence image 62 | textures["tex1"] = influence 63 | else: 64 | #No influence image for this image, so use all black zero influence image. 65 | textures["tex1"] = shader.ZERO_INFLUENCE 66 | 67 | #Hide the old one (if any) so animation times are reset. This might not be desirable in all cases. 68 | hide(image, layer=active) 69 | 70 | renpy.show_screen("shaderScreen", image, pixelShader, textures, 71 | uniforms=uniforms, update=update, xalign=xalign, yalign=yalign, 72 | _tag=getImageBase(image), _layer=active) 73 | renpy.show_layer_at([], layer=active) #Stop any animations 74 | return CallChain 75 | 76 | def deferred(name, update=None, sprite=None): 77 | textures = { 78 | "depthMap": name + " depth.png", 79 | "normalMap": name + " normal.png", 80 | } 81 | if sprite: 82 | textures["sprite"] = sprite 83 | 84 | return show(name, pixelShader=shader.PS_DEFERRED, update=update, 85 | textures=textures, xalign=0.5, yalign=0.5) 86 | 87 | def hide(image, layer=None): 88 | renpy.hide_screen(getImageBase(image), layer=findLayer(image, layer)) 89 | return CallChain 90 | 91 | def warp(image, xalign=0.5, yalign=0.1, layer=None): 92 | layer = findLayer(image, layer) 93 | hide(image, layer=layer).dissolve() 94 | show(image, xalign=xalign, yalign=yalign, layer=layer).dissolve() 95 | return CallChain 96 | 97 | def scene(image=None): 98 | config.scene() 99 | if image: 100 | show(image) 101 | return CallChain 102 | 103 | CallChain.show = staticmethod(show) 104 | CallChain.deferred = staticmethod(deferred) 105 | CallChain.hide = staticmethod(hide) 106 | CallChain.warp = staticmethod(warp) 107 | CallChain.scene = staticmethod(scene) 108 | -------------------------------------------------------------------------------- /ShaderDemo/game/gui/bar/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/bar/bottom.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/bar/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/bar/left.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/bar/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/bar/right.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/bar/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/bar/top.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/check_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/check_foreground.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/check_selected_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/check_selected_foreground.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/choice_hover_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/choice_hover_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/choice_idle_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/choice_idle_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/hover_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/hover_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/idle_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/idle_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/quick_hover_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/quick_hover_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/quick_idle_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/quick_idle_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/radio_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/radio_foreground.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/radio_selected_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/radio_selected_foreground.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/slot_hover_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/slot_hover_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/button/slot_idle_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/button/slot_idle_background.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/frame.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/game_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/game_menu.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/main_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/main_menu.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/namebox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/namebox.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/notify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/notify.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/nvl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/nvl.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/overlay/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/overlay/confirm.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/overlay/game_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/overlay/game_menu.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/overlay/main_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/overlay/main_menu.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/phone/nvl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/phone/nvl.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/phone/overlay/game_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/phone/overlay/game_menu.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/phone/overlay/main_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/phone/overlay/main_menu.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/phone/textbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/phone/textbox.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/horizontal_hover_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/horizontal_hover_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/horizontal_hover_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/horizontal_hover_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/horizontal_idle_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/horizontal_idle_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/horizontal_idle_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/horizontal_idle_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/vertical_hover_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/vertical_hover_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/vertical_hover_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/vertical_hover_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/vertical_idle_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/vertical_idle_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/scrollbar/vertical_idle_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/scrollbar/vertical_idle_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/skip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/skip.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/horizontal_hover_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/horizontal_hover_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/horizontal_hover_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/horizontal_hover_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/horizontal_idle_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/horizontal_idle_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/horizontal_idle_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/horizontal_idle_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/vertical_hover_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/vertical_hover_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/vertical_hover_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/vertical_hover_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/vertical_idle_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/vertical_idle_bar.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/slider/vertical_idle_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/slider/vertical_idle_thumb.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/textbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/textbox.png -------------------------------------------------------------------------------- /ShaderDemo/game/gui/window_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/gui/window_icon.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/amy influence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/amy influence.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/amy sad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/amy sad.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/amy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/amy.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/amydoll influence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/amydoll influence.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/amydoll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/amydoll.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/cg influence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/cg influence.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/cg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/cg.jpg -------------------------------------------------------------------------------- /ShaderDemo/game/images/cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/cube.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/doll/basecrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/doll/basecrop.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/doll/doll.rpy: -------------------------------------------------------------------------------- 1 | #Automatically generated file 2 | 3 | image doll = LiveComposite( 4 | (700, 700), 5 | (83, 10), "images/doll/basecrop.png", 6 | (263, 105), "images/doll/shirtcrop.png", 7 | (249, 239), "images/doll/skirtcrop.png", 8 | (294, 5), "images/doll/haircrop.png", 9 | ) 10 | -------------------------------------------------------------------------------- /ShaderDemo/game/images/doll/haircrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/doll/haircrop.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/doll/shirtcrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/doll/shirtcrop.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/doll/skirtcrop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/doll/skirtcrop.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/editorbackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/editorbackground.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/eileen influence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/eileen influence.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/eileen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/eileen.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/forest influence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/forest influence.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/forest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/forest.jpg -------------------------------------------------------------------------------- /ShaderDemo/game/images/room baked.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/room baked.jpg -------------------------------------------------------------------------------- /ShaderDemo/game/images/room depth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/room depth.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/room normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/room normal.png -------------------------------------------------------------------------------- /ShaderDemo/game/images/room.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/room.jpg -------------------------------------------------------------------------------- /ShaderDemo/game/images/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/sky.jpg -------------------------------------------------------------------------------- /ShaderDemo/game/images/zeroinfluence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitsawer/renpy-shader/6c750689a3d7952494a3b98a3297762bb4933308/ShaderDemo/game/images/zeroinfluence.png -------------------------------------------------------------------------------- /ShaderDemo/game/mesh/cube.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.77 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib cube.mtl 4 | o Cube 5 | v 1.000000 -1.000000 -1.000000 6 | v 1.000000 -1.000000 1.000000 7 | v -1.000000 -1.000000 1.000000 8 | v -1.000000 -1.000000 -1.000000 9 | v 1.000000 1.000000 -0.999999 10 | v 0.999999 1.000000 1.000001 11 | v -1.000000 1.000000 1.000000 12 | v -1.000000 1.000000 -1.000000 13 | vn 0.0000 -1.0000 0.0000 14 | vn 0.0000 1.0000 0.0000 15 | vn 1.0000 -0.0000 0.0000 16 | vn 0.0000 -0.0000 1.0000 17 | vn -1.0000 -0.0000 -0.0000 18 | vn 0.0000 0.0000 -1.0000 19 | usemtl Material 20 | s off 21 | f 2//1 4//1 1//1 22 | f 8//2 6//2 5//2 23 | f 5//3 2//3 1//3 24 | f 6//4 3//4 2//4 25 | f 3//5 8//5 4//5 26 | f 1//6 8//6 5//6 27 | f 2//1 3//1 4//1 28 | f 8//2 7//2 6//2 29 | f 5//3 6//3 2//3 30 | f 6//4 7//4 3//4 31 | f 3//5 7//5 8//5 32 | f 1//6 4//6 8//6 33 | -------------------------------------------------------------------------------- /ShaderDemo/game/options.rpy: -------------------------------------------------------------------------------- 1 | ## This file contains options that can be changed to customize your game. 2 | ## 3 | ## Lines beginning with two '#' marks are comments, and you shouldn't uncomment 4 | ## them. Lines beginning with a single '#' mark are commented-out code, and you 5 | ## may want to uncomment them when appropriate. 6 | 7 | 8 | ## Basics ###################################################################### 9 | 10 | ## A human-readable name of the game. This is used to set the default window 11 | ## title, and shows up in the interface and error reports. 12 | ## 13 | ## The _() surrounding the string marks it as eligible for translation. 14 | 15 | define config.name = _("ShaderDemo") 16 | 17 | 18 | ## Determines if the title given above is shown on the main menu screen. Set 19 | ## this to False to hide the title. 20 | 21 | define gui.show_name = True 22 | 23 | 24 | ## The version of the game. 25 | 26 | define config.version = "1.0" 27 | 28 | 29 | ## Text that is placed on the game's about screen. To insert a blank line 30 | ## between paragraphs, write \n\n. 31 | 32 | define gui.about = _("") 33 | 34 | 35 | ## A short name for the game used for executables and directories in the built 36 | ## distribution. This must be ASCII-only, and must not contain spaces, colons, 37 | ## or semicolons. 38 | 39 | define build.name = "ShaderDemo" 40 | 41 | 42 | ## Sounds and music ############################################################ 43 | 44 | ## These three variables control which mixers are shown to the player by 45 | ## default. Setting one of these to False will hide the appropriate mixer. 46 | 47 | define config.has_sound = True 48 | define config.has_music = True 49 | define config.has_voice = True 50 | 51 | define config.quit_action = Quit(confirm=False) 52 | 53 | ## To allow the user to play a test sound on the sound or voice channel, 54 | ## uncomment a line below and use it to set a sample sound to play. 55 | 56 | # define config.sample_sound = "sample-sound.ogg" 57 | # define config.sample_voice = "sample-voice.ogg" 58 | 59 | 60 | ## Uncomment the following line to set an audio file that will be played while 61 | ## the player is at the main menu. This file will continue playing into the 62 | ## game, until it is stopped or another file is played. 63 | 64 | # define config.main_menu_music = "main-menu-theme.ogg" 65 | 66 | 67 | ## Transitions ################################################################# 68 | ## 69 | ## These variables set transitions that are used when certain events occur. Each 70 | ## variable should be set to a transition, or None to indicate that no 71 | ## transition should be used. 72 | 73 | ## Entering or exiting the game menu. 74 | 75 | define config.enter_transition = dissolve 76 | define config.exit_transition = dissolve 77 | 78 | 79 | ## A transition that is used after a game has been loaded. 80 | 81 | define config.after_load_transition = None 82 | 83 | 84 | ## Used when entering the main menu after the game has ended. 85 | 86 | define config.end_game_transition = None 87 | 88 | 89 | ## A variable to set the transition used when the game starts does not exist. 90 | ## Instead, use a with statement after showing the initial scene. 91 | 92 | 93 | ## Window management ########################################################### 94 | ## 95 | ## This controls when the dialogue window is displayed. If "show", it is always 96 | ## displayed. If "hide", it is only displayed when dialogue is present. If 97 | ## "auto", the window is hidden before scene statements and shown again once 98 | ## dialogue is displayed. 99 | ## 100 | ## After the game has started, this can be changed with the "window show", 101 | ## "window hide", and "window auto" statements. 102 | 103 | define config.window = "auto" 104 | 105 | 106 | ## Transitions used to show and hide the dialogue window 107 | 108 | define config.window_show_transition = Dissolve(.2) 109 | define config.window_hide_transition = Dissolve(.2) 110 | 111 | 112 | ## Preference defaults ######################################################### 113 | 114 | ## Controls the default text speed. The default, 0, is infinite, while any other 115 | ## number is the number of characters per second to type out. 116 | 117 | default preferences.text_cps = 0 118 | 119 | 120 | ## The default auto-forward delay. Larger numbers lead to longer waits, with 0 121 | ## to 30 being the valid range. 122 | 123 | default preferences.afm_time = 15 124 | 125 | 126 | ## Save directory ############################################################## 127 | ## 128 | ## Controls the platform-specific place Ren'Py will place the save files for 129 | ## this game. The save files will be placed in: 130 | ## 131 | ## Windows: %APPDATA\RenPy\ 132 | ## 133 | ## Macintosh: $HOME/Library/RenPy/ 134 | ## 135 | ## Linux: $HOME/.renpy/ 136 | ## 137 | ## This generally should not be changed, and if it is, should always be a 138 | ## literal string, not an expression. 139 | 140 | define config.save_directory = "ShaderDemo-1474215741" 141 | 142 | 143 | ## Icon 144 | ## ########################################################################' 145 | 146 | ## The icon displayed on the taskbar or dock. 147 | 148 | define config.window_icon = "gui/window_icon.png" 149 | 150 | 151 | ## Build configuration ######################################################### 152 | ## 153 | ## This section controls how Ren'Py turns your project into distribution files. 154 | 155 | init python: 156 | 157 | ## The following functions take file patterns. File patterns are case- 158 | ## insensitive, and matched against the path relative to the base directory, 159 | ## with and without a leading /. If multiple patterns match, the first is 160 | ## used. 161 | ## 162 | ## In a pattern: 163 | ## 164 | ## / is the directory separator. 165 | ## 166 | ## * matches all characters, except the directory separator. 167 | ## 168 | ## ** matches all characters, including the directory separator. 169 | ## 170 | ## For example, "*.txt" matches txt files in the base directory, 171 | ## "game/**.ogg" matches ogg files in the game directory or any of its 172 | ## subdirectories, and "**.psd" matches psd files anywhere in the project. 173 | 174 | ## Classify files as None to exclude them from the built distributions. 175 | 176 | build.classify('**~', None) 177 | build.classify('**.bak', None) 178 | build.classify('**/.**', None) 179 | build.classify('**/#**', None) 180 | build.classify('**/thumbs.db', None) 181 | 182 | ## To archive files, classify them as 'archive'. 183 | 184 | # build.classify('game/**.png', 'archive') 185 | # build.classify('game/**.jpg', 'archive') 186 | 187 | ## Files matching documentation patterns are duplicated in a mac app build, 188 | ## so they appear in both the app and the zip file. 189 | 190 | build.documentation('*.html') 191 | build.documentation('*.txt') 192 | 193 | ## A Google Play license key is required to download expansion files and perform 194 | ## in-app purchases. It can be found on the "Services & APIs" page of the Google 195 | ## Play developer console. 196 | 197 | # define build.google_play_key = "..." 198 | 199 | 200 | ## The username and project name associated with an itch.io project, separated 201 | ## by a slash. 202 | 203 | # define build.itch_project = "renpytom/test-project" 204 | -------------------------------------------------------------------------------- /ShaderDemo/game/rig/amydoll idle.anim: -------------------------------------------------------------------------------- 1 | { 2 | "animation": { 3 | "boneData": { 4 | "amypose 13": { 5 | "easing": "sineInOut", 6 | "repeat": true, 7 | "reversed": false 8 | }, 9 | "amypose 15": { 10 | "easing": "sineInOut", 11 | "repeat": true, 12 | "reversed": false 13 | }, 14 | "amypose 2": { 15 | "easing": "sineInOut", 16 | "repeat": true, 17 | "reversed": false 18 | }, 19 | "amypose 23": { 20 | "easing": "sineInOut", 21 | "repeat": true, 22 | "reversed": false 23 | }, 24 | "amypose 26": { 25 | "easing": "sineInOut", 26 | "repeat": true, 27 | "reversed": false 28 | }, 29 | "amypose 32": { 30 | "easing": "sineInOut", 31 | "repeat": true, 32 | "reversed": false 33 | }, 34 | "amypose 6": { 35 | "easing": "sineInOut", 36 | "repeat": true, 37 | "reversed": false 38 | } 39 | }, 40 | "frames": [ 41 | { 42 | "keys": { 43 | "amypose 13": { 44 | "rotation": [ 45 | 0.0, 46 | 0.0, 47 | 0.0 48 | ], 49 | "scale": [ 50 | 1.0, 51 | 1.0, 52 | 1.0 53 | ], 54 | "translation": [ 55 | 0.0, 56 | 0.0, 57 | 0.0 58 | ], 59 | "transparency": 0.0, 60 | "visible": true 61 | }, 62 | "amypose 15": { 63 | "rotation": [ 64 | 0.0, 65 | 0.0, 66 | 0.0 67 | ], 68 | "scale": [ 69 | 1.0, 70 | 1.0, 71 | 1.0 72 | ], 73 | "translation": [ 74 | 0.0, 75 | 0.0, 76 | 0.0 77 | ], 78 | "transparency": 0.0, 79 | "visible": true 80 | }, 81 | "amypose 2": { 82 | "rotation": [ 83 | 0.0, 84 | 0.0, 85 | 0.0 86 | ], 87 | "scale": [ 88 | 1.0, 89 | 1.0, 90 | 1.0 91 | ], 92 | "translation": [ 93 | 0.0, 94 | 0.0, 95 | 0.0 96 | ], 97 | "transparency": 0.0, 98 | "visible": true 99 | }, 100 | "amypose 23": { 101 | "rotation": [ 102 | 0.0, 103 | 0.0, 104 | 0.0 105 | ], 106 | "scale": [ 107 | 1.0, 108 | 1.0, 109 | 1.0 110 | ], 111 | "translation": [ 112 | 0.0, 113 | 0.0, 114 | 0.0 115 | ], 116 | "transparency": 0.0, 117 | "visible": true 118 | }, 119 | "amypose 26": { 120 | "rotation": [ 121 | 0.0, 122 | 0.0, 123 | 0.0 124 | ], 125 | "scale": [ 126 | 1.0, 127 | 1.0, 128 | 1.0 129 | ], 130 | "translation": [ 131 | 0.0, 132 | 0.0, 133 | 0.0 134 | ], 135 | "transparency": 0.0, 136 | "visible": true 137 | }, 138 | "amypose 32": { 139 | "rotation": [ 140 | 0.0, 141 | 0.0, 142 | 0.0 143 | ], 144 | "scale": [ 145 | 1.0, 146 | 1.0, 147 | 1.0 148 | ], 149 | "translation": [ 150 | 0.0, 151 | 0.0, 152 | 0.0 153 | ], 154 | "transparency": 0.0, 155 | "visible": true 156 | }, 157 | "amypose 6": { 158 | "rotation": [ 159 | 0.0, 160 | 0.0, 161 | 0.0 162 | ], 163 | "scale": [ 164 | 1.0, 165 | 1.0, 166 | 1.0 167 | ], 168 | "translation": [ 169 | 0.0, 170 | 0.0, 171 | 0.0 172 | ], 173 | "transparency": 0.0, 174 | "visible": true 175 | } 176 | } 177 | }, 178 | { 179 | "keys": {} 180 | }, 181 | { 182 | "keys": {} 183 | }, 184 | { 185 | "keys": {} 186 | }, 187 | { 188 | "keys": {} 189 | }, 190 | { 191 | "keys": {} 192 | }, 193 | { 194 | "keys": {} 195 | }, 196 | { 197 | "keys": {} 198 | }, 199 | { 200 | "keys": {} 201 | }, 202 | { 203 | "keys": {} 204 | }, 205 | { 206 | "keys": {} 207 | }, 208 | { 209 | "keys": {} 210 | }, 211 | { 212 | "keys": {} 213 | }, 214 | { 215 | "keys": {} 216 | }, 217 | { 218 | "keys": {} 219 | }, 220 | { 221 | "keys": {} 222 | }, 223 | { 224 | "keys": {} 225 | }, 226 | { 227 | "keys": {} 228 | }, 229 | { 230 | "keys": {} 231 | }, 232 | { 233 | "keys": {} 234 | }, 235 | { 236 | "keys": {} 237 | }, 238 | { 239 | "keys": {} 240 | }, 241 | { 242 | "keys": {} 243 | }, 244 | { 245 | "keys": { 246 | "amypose 15": { 247 | "rotation": [ 248 | 0.0, 249 | 0.0, 250 | -0.13642672860965854 251 | ], 252 | "scale": [ 253 | 1.0, 254 | 1.0, 255 | 1.0 256 | ], 257 | "translation": [ 258 | 0.0, 259 | 0.0, 260 | 0.0 261 | ], 262 | "transparency": 0.0, 263 | "visible": true 264 | } 265 | } 266 | }, 267 | { 268 | "keys": { 269 | "amypose 23": { 270 | "rotation": [ 271 | 0.0, 272 | 0.0, 273 | -0.19775695600841148 274 | ], 275 | "scale": [ 276 | 1.0, 277 | 1.0, 278 | 1.0 279 | ], 280 | "translation": [ 281 | 0.0, 282 | 0.0, 283 | 0.0 284 | ], 285 | "transparency": 0.0, 286 | "visible": true 287 | } 288 | } 289 | }, 290 | { 291 | "keys": { 292 | "amypose 32": { 293 | "rotation": [ 294 | 0.0, 295 | 0.0, 296 | -0.04891611266678564 297 | ], 298 | "scale": [ 299 | 1.0, 300 | 1.0, 301 | 1.0 302 | ], 303 | "translation": [ 304 | 0.0, 305 | 0.0, 306 | 0.0 307 | ], 308 | "transparency": 0.0, 309 | "visible": true 310 | } 311 | } 312 | }, 313 | { 314 | "keys": {} 315 | }, 316 | { 317 | "keys": { 318 | "amypose 13": { 319 | "rotation": [ 320 | 0.0, 321 | 0.0, 322 | 0.049164091189489056 323 | ], 324 | "scale": [ 325 | 1.0, 326 | 1.0, 327 | 1.0 328 | ], 329 | "translation": [ 330 | 0.0, 331 | 0.0, 332 | 0.0 333 | ], 334 | "transparency": 0.0, 335 | "visible": true 336 | }, 337 | "amypose 2": { 338 | "rotation": [ 339 | 0.0, 340 | 0.0, 341 | 0.10745599841666598 342 | ], 343 | "scale": [ 344 | 1.0, 345 | 1.0, 346 | 1.0 347 | ], 348 | "translation": [ 349 | 0.0, 350 | 0.0, 351 | 0.0 352 | ], 353 | "transparency": 0.0, 354 | "visible": true 355 | } 356 | } 357 | }, 358 | { 359 | "keys": { 360 | "amypose 26": { 361 | "rotation": [ 362 | 0.0, 363 | 0.0, 364 | 0.2571031714965403 365 | ], 366 | "scale": [ 367 | 1.0, 368 | 1.0, 369 | 1.0 370 | ], 371 | "translation": [ 372 | 0.0, 373 | 0.0, 374 | 0.0 375 | ], 376 | "transparency": 0.0, 377 | "visible": true 378 | }, 379 | "amypose 6": { 380 | "rotation": [ 381 | 0.0, 382 | 0.0, 383 | -0.536549670308132 384 | ], 385 | "scale": [ 386 | 1.0, 387 | 1.0, 388 | 1.0 389 | ], 390 | "translation": [ 391 | 0.0, 392 | 0.0, 393 | 0.0 394 | ], 395 | "transparency": 0.0, 396 | "visible": true 397 | } 398 | } 399 | }, 400 | { 401 | "keys": {} 402 | }, 403 | { 404 | "keys": {} 405 | }, 406 | { 407 | "keys": {} 408 | }, 409 | { 410 | "keys": {} 411 | }, 412 | { 413 | "keys": {} 414 | }, 415 | { 416 | "keys": {} 417 | }, 418 | { 419 | "keys": {} 420 | }, 421 | { 422 | "keys": {} 423 | }, 424 | { 425 | "keys": {} 426 | }, 427 | { 428 | "keys": {} 429 | }, 430 | { 431 | "keys": {} 432 | }, 433 | { 434 | "keys": {} 435 | }, 436 | { 437 | "keys": {} 438 | }, 439 | { 440 | "keys": {} 441 | }, 442 | { 443 | "keys": {} 444 | }, 445 | { 446 | "keys": {} 447 | }, 448 | { 449 | "keys": {} 450 | }, 451 | { 452 | "keys": {} 453 | }, 454 | { 455 | "keys": {} 456 | }, 457 | { 458 | "keys": {} 459 | }, 460 | { 461 | "keys": {} 462 | }, 463 | { 464 | "keys": {} 465 | }, 466 | { 467 | "keys": {} 468 | }, 469 | { 470 | "keys": {} 471 | }, 472 | { 473 | "keys": {} 474 | }, 475 | { 476 | "keys": {} 477 | }, 478 | { 479 | "keys": {} 480 | }, 481 | { 482 | "keys": {} 483 | }, 484 | { 485 | "keys": {} 486 | }, 487 | { 488 | "keys": {} 489 | }, 490 | { 491 | "keys": {} 492 | }, 493 | { 494 | "keys": {} 495 | }, 496 | { 497 | "keys": {} 498 | }, 499 | { 500 | "keys": {} 501 | }, 502 | { 503 | "keys": {} 504 | }, 505 | { 506 | "keys": {} 507 | }, 508 | { 509 | "keys": {} 510 | }, 511 | { 512 | "keys": {} 513 | }, 514 | { 515 | "keys": {} 516 | }, 517 | { 518 | "keys": {} 519 | }, 520 | { 521 | "keys": {} 522 | }, 523 | { 524 | "keys": {} 525 | }, 526 | { 527 | "keys": {} 528 | }, 529 | { 530 | "keys": {} 531 | }, 532 | { 533 | "keys": {} 534 | }, 535 | { 536 | "keys": {} 537 | }, 538 | { 539 | "keys": {} 540 | }, 541 | { 542 | "keys": {} 543 | }, 544 | { 545 | "keys": {} 546 | }, 547 | { 548 | "keys": {} 549 | }, 550 | { 551 | "keys": {} 552 | }, 553 | { 554 | "keys": {} 555 | }, 556 | { 557 | "keys": {} 558 | }, 559 | { 560 | "keys": {} 561 | }, 562 | { 563 | "keys": {} 564 | }, 565 | { 566 | "keys": {} 567 | }, 568 | { 569 | "keys": {} 570 | }, 571 | { 572 | "keys": {} 573 | }, 574 | { 575 | "keys": {} 576 | }, 577 | { 578 | "keys": {} 579 | }, 580 | { 581 | "keys": {} 582 | }, 583 | { 584 | "keys": {} 585 | }, 586 | { 587 | "keys": {} 588 | }, 589 | { 590 | "keys": {} 591 | }, 592 | { 593 | "keys": {} 594 | }, 595 | { 596 | "keys": {} 597 | }, 598 | { 599 | "keys": {} 600 | }, 601 | { 602 | "keys": {} 603 | }, 604 | { 605 | "keys": {} 606 | }, 607 | { 608 | "keys": {} 609 | }, 610 | { 611 | "keys": {} 612 | }, 613 | { 614 | "keys": {} 615 | }, 616 | { 617 | "keys": {} 618 | } 619 | ], 620 | "name": "amypose idle.anim" 621 | }, 622 | "version": 1 623 | } -------------------------------------------------------------------------------- /ShaderDemo/game/rig/amydoll leftarm.anim: -------------------------------------------------------------------------------- 1 | { 2 | "animation": { 3 | "boneData": { 4 | "amypose 26": { 5 | "easing": "sineInOut", 6 | "repeat": true, 7 | "reversed": false 8 | }, 9 | "amypose 9": { 10 | "easing": "sineInOut", 11 | "repeat": true, 12 | "reversed": false 13 | } 14 | }, 15 | "frames": [ 16 | { 17 | "keys": { 18 | "amypose 26": { 19 | "rotation": [ 20 | 0.0, 21 | 0.0, 22 | 0.0 23 | ], 24 | "scale": [ 25 | 1.0, 26 | 1.0, 27 | 1.0 28 | ], 29 | "translation": [ 30 | 0.0, 31 | 0.0, 32 | 0.0 33 | ], 34 | "transparency": 0.0, 35 | "visible": true 36 | }, 37 | "amypose 9": { 38 | "rotation": [ 39 | 0.0, 40 | 0.0, 41 | 0.0 42 | ], 43 | "scale": [ 44 | 1.0, 45 | 1.0, 46 | 1.0 47 | ], 48 | "translation": [ 49 | 0.0, 50 | 0.0, 51 | 0.0 52 | ], 53 | "transparency": 0.0, 54 | "visible": true 55 | } 56 | } 57 | }, 58 | { 59 | "keys": {} 60 | }, 61 | { 62 | "keys": {} 63 | }, 64 | { 65 | "keys": {} 66 | }, 67 | { 68 | "keys": {} 69 | }, 70 | { 71 | "keys": {} 72 | }, 73 | { 74 | "keys": {} 75 | }, 76 | { 77 | "keys": {} 78 | }, 79 | { 80 | "keys": {} 81 | }, 82 | { 83 | "keys": {} 84 | }, 85 | { 86 | "keys": {} 87 | }, 88 | { 89 | "keys": {} 90 | }, 91 | { 92 | "keys": {} 93 | }, 94 | { 95 | "keys": {} 96 | }, 97 | { 98 | "keys": {} 99 | }, 100 | { 101 | "keys": {} 102 | }, 103 | { 104 | "keys": {} 105 | }, 106 | { 107 | "keys": {} 108 | }, 109 | { 110 | "keys": {} 111 | }, 112 | { 113 | "keys": {} 114 | }, 115 | { 116 | "keys": {} 117 | }, 118 | { 119 | "keys": {} 120 | }, 121 | { 122 | "keys": {} 123 | }, 124 | { 125 | "keys": {} 126 | }, 127 | { 128 | "keys": {} 129 | }, 130 | { 131 | "keys": {} 132 | }, 133 | { 134 | "keys": {} 135 | }, 136 | { 137 | "keys": {} 138 | }, 139 | { 140 | "keys": { 141 | "amypose 9": { 142 | "rotation": [ 143 | 0.0, 144 | 0.0, 145 | 0.4601844946655713 146 | ], 147 | "scale": [ 148 | 1.0, 149 | 1.0, 150 | 1.0 151 | ], 152 | "translation": [ 153 | 0.0, 154 | 0.0, 155 | 0.0 156 | ], 157 | "transparency": 0.0, 158 | "visible": true 159 | } 160 | } 161 | }, 162 | { 163 | "keys": {} 164 | }, 165 | { 166 | "keys": {} 167 | }, 168 | { 169 | "keys": {} 170 | }, 171 | { 172 | "keys": { 173 | "amypose 26": { 174 | "rotation": [ 175 | 0.0, 176 | 0.0, 177 | 0.5210140747838392 178 | ], 179 | "scale": [ 180 | 1.0, 181 | 1.0, 182 | 1.0 183 | ], 184 | "translation": [ 185 | 0.0, 186 | 0.0, 187 | 0.0 188 | ], 189 | "transparency": 0.0, 190 | "visible": true 191 | } 192 | } 193 | }, 194 | { 195 | "keys": {} 196 | }, 197 | { 198 | "keys": {} 199 | }, 200 | { 201 | "keys": {} 202 | }, 203 | { 204 | "keys": {} 205 | }, 206 | { 207 | "keys": {} 208 | }, 209 | { 210 | "keys": {} 211 | }, 212 | { 213 | "keys": {} 214 | }, 215 | { 216 | "keys": {} 217 | }, 218 | { 219 | "keys": {} 220 | }, 221 | { 222 | "keys": {} 223 | }, 224 | { 225 | "keys": {} 226 | }, 227 | { 228 | "keys": {} 229 | }, 230 | { 231 | "keys": {} 232 | }, 233 | { 234 | "keys": {} 235 | }, 236 | { 237 | "keys": {} 238 | }, 239 | { 240 | "keys": {} 241 | }, 242 | { 243 | "keys": {} 244 | }, 245 | { 246 | "keys": {} 247 | }, 248 | { 249 | "keys": {} 250 | }, 251 | { 252 | "keys": {} 253 | }, 254 | { 255 | "keys": {} 256 | }, 257 | { 258 | "keys": {} 259 | }, 260 | { 261 | "keys": {} 262 | }, 263 | { 264 | "keys": {} 265 | }, 266 | { 267 | "keys": {} 268 | }, 269 | { 270 | "keys": {} 271 | }, 272 | { 273 | "keys": {} 274 | }, 275 | { 276 | "keys": {} 277 | }, 278 | { 279 | "keys": {} 280 | }, 281 | { 282 | "keys": {} 283 | }, 284 | { 285 | "keys": {} 286 | }, 287 | { 288 | "keys": {} 289 | }, 290 | { 291 | "keys": {} 292 | }, 293 | { 294 | "keys": {} 295 | }, 296 | { 297 | "keys": {} 298 | }, 299 | { 300 | "keys": {} 301 | }, 302 | { 303 | "keys": {} 304 | }, 305 | { 306 | "keys": {} 307 | }, 308 | { 309 | "keys": {} 310 | }, 311 | { 312 | "keys": {} 313 | }, 314 | { 315 | "keys": {} 316 | }, 317 | { 318 | "keys": {} 319 | }, 320 | { 321 | "keys": {} 322 | }, 323 | { 324 | "keys": {} 325 | }, 326 | { 327 | "keys": {} 328 | }, 329 | { 330 | "keys": {} 331 | }, 332 | { 333 | "keys": {} 334 | }, 335 | { 336 | "keys": {} 337 | }, 338 | { 339 | "keys": {} 340 | }, 341 | { 342 | "keys": {} 343 | }, 344 | { 345 | "keys": {} 346 | }, 347 | { 348 | "keys": {} 349 | }, 350 | { 351 | "keys": {} 352 | }, 353 | { 354 | "keys": {} 355 | }, 356 | { 357 | "keys": {} 358 | }, 359 | { 360 | "keys": {} 361 | }, 362 | { 363 | "keys": {} 364 | }, 365 | { 366 | "keys": {} 367 | }, 368 | { 369 | "keys": {} 370 | }, 371 | { 372 | "keys": {} 373 | }, 374 | { 375 | "keys": {} 376 | }, 377 | { 378 | "keys": {} 379 | }, 380 | { 381 | "keys": {} 382 | }, 383 | { 384 | "keys": {} 385 | }, 386 | { 387 | "keys": {} 388 | }, 389 | { 390 | "keys": {} 391 | }, 392 | { 393 | "keys": {} 394 | }, 395 | { 396 | "keys": {} 397 | }, 398 | { 399 | "keys": {} 400 | }, 401 | { 402 | "keys": {} 403 | }, 404 | { 405 | "keys": {} 406 | }, 407 | { 408 | "keys": {} 409 | }, 410 | { 411 | "keys": {} 412 | }, 413 | { 414 | "keys": {} 415 | }, 416 | { 417 | "keys": {} 418 | }, 419 | { 420 | "keys": {} 421 | }, 422 | { 423 | "keys": {} 424 | }, 425 | { 426 | "keys": {} 427 | }, 428 | { 429 | "keys": {} 430 | }, 431 | { 432 | "keys": {} 433 | }, 434 | { 435 | "keys": {} 436 | }, 437 | { 438 | "keys": {} 439 | }, 440 | { 441 | "keys": {} 442 | }, 443 | { 444 | "keys": {} 445 | }, 446 | { 447 | "keys": {} 448 | }, 449 | { 450 | "keys": {} 451 | }, 452 | { 453 | "keys": {} 454 | } 455 | ], 456 | "name": "amypose leftarm.anim" 457 | }, 458 | "version": 1 459 | } -------------------------------------------------------------------------------- /ShaderDemo/game/rig/amydoll rightarm.anim: -------------------------------------------------------------------------------- 1 | { 2 | "animation": { 3 | "boneData": { 4 | "amypose 23": { 5 | "easing": "sineInOut", 6 | "repeat": true, 7 | "reversed": false 8 | }, 9 | "amypose 5": { 10 | "easing": "sineInOut", 11 | "repeat": true, 12 | "reversed": false 13 | }, 14 | "amypose 6": { 15 | "easing": "sineInOut", 16 | "repeat": true, 17 | "reversed": false 18 | } 19 | }, 20 | "frames": [ 21 | { 22 | "keys": { 23 | "amypose 23": { 24 | "rotation": [ 25 | 0.0, 26 | 0.0, 27 | 0.0 28 | ], 29 | "scale": [ 30 | 1.0, 31 | 1.0, 32 | 1.0 33 | ], 34 | "translation": [ 35 | 0.0, 36 | 0.0, 37 | 0.0 38 | ], 39 | "transparency": 0.0, 40 | "visible": true 41 | }, 42 | "amypose 5": { 43 | "rotation": [ 44 | 0.0, 45 | 0.0, 46 | 0.0 47 | ], 48 | "scale": [ 49 | 1.0, 50 | 1.0, 51 | 1.0 52 | ], 53 | "translation": [ 54 | 0.0, 55 | 0.0, 56 | 0.0 57 | ], 58 | "transparency": 0.0, 59 | "visible": true 60 | }, 61 | "amypose 6": { 62 | "rotation": [ 63 | 0.0, 64 | 0.0, 65 | 0.0 66 | ], 67 | "scale": [ 68 | 1.0, 69 | 1.0, 70 | 1.0 71 | ], 72 | "translation": [ 73 | 0.0, 74 | 0.0, 75 | 0.0 76 | ], 77 | "transparency": 0.0, 78 | "visible": true 79 | } 80 | } 81 | }, 82 | { 83 | "keys": {} 84 | }, 85 | { 86 | "keys": {} 87 | }, 88 | { 89 | "keys": {} 90 | }, 91 | { 92 | "keys": {} 93 | }, 94 | { 95 | "keys": {} 96 | }, 97 | { 98 | "keys": {} 99 | }, 100 | { 101 | "keys": {} 102 | }, 103 | { 104 | "keys": {} 105 | }, 106 | { 107 | "keys": {} 108 | }, 109 | { 110 | "keys": {} 111 | }, 112 | { 113 | "keys": {} 114 | }, 115 | { 116 | "keys": {} 117 | }, 118 | { 119 | "keys": {} 120 | }, 121 | { 122 | "keys": {} 123 | }, 124 | { 125 | "keys": {} 126 | }, 127 | { 128 | "keys": {} 129 | }, 130 | { 131 | "keys": {} 132 | }, 133 | { 134 | "keys": {} 135 | }, 136 | { 137 | "keys": {} 138 | }, 139 | { 140 | "keys": {} 141 | }, 142 | { 143 | "keys": {} 144 | }, 145 | { 146 | "keys": {} 147 | }, 148 | { 149 | "keys": {} 150 | }, 151 | { 152 | "keys": {} 153 | }, 154 | { 155 | "keys": { 156 | "amypose 5": { 157 | "rotation": [ 158 | 0.0, 159 | 0.0, 160 | 0.9182655058627103 161 | ], 162 | "scale": [ 163 | 1.0, 164 | 1.0, 165 | 1.0 166 | ], 167 | "translation": [ 168 | 0.0, 169 | 0.0, 170 | 0.0 171 | ], 172 | "transparency": 0.0, 173 | "visible": true 174 | } 175 | } 176 | }, 177 | { 178 | "keys": {} 179 | }, 180 | { 181 | "keys": {} 182 | }, 183 | { 184 | "keys": { 185 | "amypose 6": { 186 | "rotation": [ 187 | 0.0, 188 | 0.0, 189 | -1.2729374314777446 190 | ], 191 | "scale": [ 192 | 1.0, 193 | 1.0, 194 | 1.0 195 | ], 196 | "translation": [ 197 | 0.0, 198 | 0.0, 199 | 0.0 200 | ], 201 | "transparency": 0.0, 202 | "visible": true 203 | } 204 | } 205 | }, 206 | { 207 | "keys": {} 208 | }, 209 | { 210 | "keys": { 211 | "amypose 23": { 212 | "rotation": [ 213 | 0.0, 214 | 0.0, 215 | -0.9048379807636997 216 | ], 217 | "scale": [ 218 | 1.0, 219 | 1.0, 220 | 1.0 221 | ], 222 | "translation": [ 223 | 0.0, 224 | 0.0, 225 | 0.0 226 | ], 227 | "transparency": 0.0, 228 | "visible": true 229 | } 230 | } 231 | }, 232 | { 233 | "keys": {} 234 | }, 235 | { 236 | "keys": {} 237 | }, 238 | { 239 | "keys": {} 240 | }, 241 | { 242 | "keys": {} 243 | }, 244 | { 245 | "keys": {} 246 | }, 247 | { 248 | "keys": {} 249 | }, 250 | { 251 | "keys": {} 252 | }, 253 | { 254 | "keys": {} 255 | }, 256 | { 257 | "keys": {} 258 | }, 259 | { 260 | "keys": {} 261 | }, 262 | { 263 | "keys": {} 264 | }, 265 | { 266 | "keys": {} 267 | }, 268 | { 269 | "keys": {} 270 | }, 271 | { 272 | "keys": {} 273 | }, 274 | { 275 | "keys": {} 276 | }, 277 | { 278 | "keys": {} 279 | }, 280 | { 281 | "keys": {} 282 | }, 283 | { 284 | "keys": {} 285 | }, 286 | { 287 | "keys": {} 288 | }, 289 | { 290 | "keys": {} 291 | }, 292 | { 293 | "keys": {} 294 | }, 295 | { 296 | "keys": {} 297 | }, 298 | { 299 | "keys": {} 300 | }, 301 | { 302 | "keys": {} 303 | }, 304 | { 305 | "keys": {} 306 | }, 307 | { 308 | "keys": {} 309 | }, 310 | { 311 | "keys": {} 312 | }, 313 | { 314 | "keys": {} 315 | }, 316 | { 317 | "keys": {} 318 | }, 319 | { 320 | "keys": {} 321 | }, 322 | { 323 | "keys": {} 324 | }, 325 | { 326 | "keys": {} 327 | }, 328 | { 329 | "keys": {} 330 | }, 331 | { 332 | "keys": {} 333 | }, 334 | { 335 | "keys": {} 336 | }, 337 | { 338 | "keys": {} 339 | }, 340 | { 341 | "keys": {} 342 | }, 343 | { 344 | "keys": {} 345 | }, 346 | { 347 | "keys": {} 348 | }, 349 | { 350 | "keys": {} 351 | }, 352 | { 353 | "keys": {} 354 | }, 355 | { 356 | "keys": {} 357 | }, 358 | { 359 | "keys": {} 360 | }, 361 | { 362 | "keys": {} 363 | }, 364 | { 365 | "keys": {} 366 | }, 367 | { 368 | "keys": {} 369 | }, 370 | { 371 | "keys": {} 372 | }, 373 | { 374 | "keys": {} 375 | }, 376 | { 377 | "keys": {} 378 | }, 379 | { 380 | "keys": {} 381 | }, 382 | { 383 | "keys": {} 384 | }, 385 | { 386 | "keys": {} 387 | }, 388 | { 389 | "keys": {} 390 | }, 391 | { 392 | "keys": {} 393 | }, 394 | { 395 | "keys": {} 396 | }, 397 | { 398 | "keys": {} 399 | }, 400 | { 401 | "keys": {} 402 | }, 403 | { 404 | "keys": {} 405 | }, 406 | { 407 | "keys": {} 408 | }, 409 | { 410 | "keys": {} 411 | }, 412 | { 413 | "keys": {} 414 | }, 415 | { 416 | "keys": {} 417 | }, 418 | { 419 | "keys": {} 420 | }, 421 | { 422 | "keys": {} 423 | }, 424 | { 425 | "keys": {} 426 | }, 427 | { 428 | "keys": {} 429 | }, 430 | { 431 | "keys": {} 432 | }, 433 | { 434 | "keys": {} 435 | }, 436 | { 437 | "keys": {} 438 | }, 439 | { 440 | "keys": {} 441 | }, 442 | { 443 | "keys": {} 444 | }, 445 | { 446 | "keys": {} 447 | }, 448 | { 449 | "keys": {} 450 | }, 451 | { 452 | "keys": {} 453 | }, 454 | { 455 | "keys": {} 456 | }, 457 | { 458 | "keys": {} 459 | }, 460 | { 461 | "keys": {} 462 | }, 463 | { 464 | "keys": {} 465 | }, 466 | { 467 | "keys": {} 468 | }, 469 | { 470 | "keys": {} 471 | }, 472 | { 473 | "keys": {} 474 | }, 475 | { 476 | "keys": {} 477 | }, 478 | { 479 | "keys": {} 480 | }, 481 | { 482 | "keys": {} 483 | }, 484 | { 485 | "keys": {} 486 | }, 487 | { 488 | "keys": {} 489 | }, 490 | { 491 | "keys": {} 492 | }, 493 | { 494 | "keys": {} 495 | }, 496 | { 497 | "keys": {} 498 | } 499 | ], 500 | "name": "amypose rightarm.anim" 501 | }, 502 | "version": 1 503 | } -------------------------------------------------------------------------------- /ShaderDemo/game/rig/doll kneel.anim: -------------------------------------------------------------------------------- 1 | { 2 | "animation": { 3 | "boneData": { 4 | "basecrop 10": { 5 | "easing": "sineInOut", 6 | "repeat": true, 7 | "reversed": false 8 | }, 9 | "basecrop 5": { 10 | "easing": "sineInOut", 11 | "repeat": true, 12 | "reversed": false 13 | }, 14 | "basecrop 6": { 15 | "easing": "sineInOut", 16 | "repeat": true, 17 | "reversed": false 18 | }, 19 | "basecrop 7": { 20 | "easing": "sineInOut", 21 | "repeat": true, 22 | "reversed": false 23 | }, 24 | "basecrop 8": { 25 | "easing": "sineInOut", 26 | "repeat": true, 27 | "reversed": false 28 | }, 29 | "basecrop 9": { 30 | "easing": "sineInOut", 31 | "repeat": true, 32 | "reversed": false 33 | }, 34 | "root": { 35 | "easing": "sineInOut", 36 | "repeat": true, 37 | "reversed": false 38 | } 39 | }, 40 | "frames": [ 41 | { 42 | "keys": { 43 | "basecrop 10": { 44 | "rotation": [ 45 | 0.0, 46 | 0.0, 47 | 0.0 48 | ], 49 | "scale": [ 50 | 1.0, 51 | 1.0, 52 | 1.0 53 | ], 54 | "translation": [ 55 | 0.0, 56 | 0.0, 57 | 0.0 58 | ], 59 | "transparency": 0.0, 60 | "visible": true 61 | }, 62 | "basecrop 5": { 63 | "rotation": [ 64 | 0.0, 65 | 0.0, 66 | 0.0 67 | ], 68 | "scale": [ 69 | 1.0, 70 | 1.0, 71 | 1.0 72 | ], 73 | "translation": [ 74 | 0.0, 75 | 0.0, 76 | 0.0 77 | ], 78 | "transparency": 0.0, 79 | "visible": true 80 | }, 81 | "basecrop 6": { 82 | "rotation": [ 83 | 0.0, 84 | 0.0, 85 | 0.0 86 | ], 87 | "scale": [ 88 | 1.0, 89 | 1.0, 90 | 1.0 91 | ], 92 | "translation": [ 93 | 0.0, 94 | 0.0, 95 | 0.0 96 | ], 97 | "transparency": 0.0, 98 | "visible": true 99 | }, 100 | "basecrop 7": { 101 | "rotation": [ 102 | 0.0, 103 | 0.0, 104 | 0.0 105 | ], 106 | "scale": [ 107 | 1.0, 108 | 1.0, 109 | 1.0 110 | ], 111 | "translation": [ 112 | 0.0, 113 | 0.0, 114 | 0.0 115 | ], 116 | "transparency": 0.0, 117 | "visible": true 118 | }, 119 | "basecrop 8": { 120 | "rotation": [ 121 | 0.0, 122 | 0.0, 123 | 0.0 124 | ], 125 | "scale": [ 126 | 1.0, 127 | 1.0, 128 | 1.0 129 | ], 130 | "translation": [ 131 | 0.0, 132 | 0.0, 133 | 0.0 134 | ], 135 | "transparency": 0.0, 136 | "visible": true 137 | }, 138 | "basecrop 9": { 139 | "rotation": [ 140 | 0.0, 141 | 0.0, 142 | 0.0 143 | ], 144 | "scale": [ 145 | 1.0, 146 | 1.0, 147 | 1.0 148 | ], 149 | "translation": [ 150 | 0.0, 151 | 0.0, 152 | 0.0 153 | ], 154 | "transparency": 0.0, 155 | "visible": true 156 | }, 157 | "root": { 158 | "rotation": [ 159 | 0.0, 160 | 0.0, 161 | 0.0 162 | ], 163 | "scale": [ 164 | 1.0, 165 | 1.0, 166 | 1.0 167 | ], 168 | "translation": [ 169 | 0.0, 170 | 0.0, 171 | 0.0 172 | ], 173 | "transparency": 0.0, 174 | "visible": true 175 | } 176 | } 177 | }, 178 | { 179 | "keys": {} 180 | }, 181 | { 182 | "keys": {} 183 | }, 184 | { 185 | "keys": {} 186 | }, 187 | { 188 | "keys": {} 189 | }, 190 | { 191 | "keys": {} 192 | }, 193 | { 194 | "keys": {} 195 | }, 196 | { 197 | "keys": {} 198 | }, 199 | { 200 | "keys": {} 201 | }, 202 | { 203 | "keys": {} 204 | }, 205 | { 206 | "keys": {} 207 | }, 208 | { 209 | "keys": {} 210 | }, 211 | { 212 | "keys": {} 213 | }, 214 | { 215 | "keys": {} 216 | }, 217 | { 218 | "keys": {} 219 | }, 220 | { 221 | "keys": {} 222 | }, 223 | { 224 | "keys": {} 225 | }, 226 | { 227 | "keys": {} 228 | }, 229 | { 230 | "keys": {} 231 | }, 232 | { 233 | "keys": {} 234 | }, 235 | { 236 | "keys": {} 237 | }, 238 | { 239 | "keys": {} 240 | }, 241 | { 242 | "keys": {} 243 | }, 244 | { 245 | "keys": {} 246 | }, 247 | { 248 | "keys": {} 249 | }, 250 | { 251 | "keys": {} 252 | }, 253 | { 254 | "keys": {} 255 | }, 256 | { 257 | "keys": {} 258 | }, 259 | { 260 | "keys": {} 261 | }, 262 | { 263 | "keys": {} 264 | }, 265 | { 266 | "keys": {} 267 | }, 268 | { 269 | "keys": {} 270 | }, 271 | { 272 | "keys": {} 273 | }, 274 | { 275 | "keys": { 276 | "basecrop 10": { 277 | "rotation": [ 278 | 0.0, 279 | 0.0, 280 | -0.7496975966794237 281 | ], 282 | "scale": [ 283 | 1.0, 284 | 1.0, 285 | 1.0 286 | ], 287 | "translation": [ 288 | 0.0, 289 | 0.0, 290 | 0.0 291 | ], 292 | "transparency": 0.0, 293 | "visible": true 294 | }, 295 | "basecrop 5": { 296 | "rotation": [ 297 | 0.0, 298 | 0.0, 299 | -0.5463216582989041 300 | ], 301 | "scale": [ 302 | 1.0, 303 | 1.0, 304 | 1.0 305 | ], 306 | "translation": [ 307 | 0.0, 308 | 0.0, 309 | 0.0 310 | ], 311 | "transparency": 0.0, 312 | "visible": true 313 | }, 314 | "basecrop 6": { 315 | "rotation": [ 316 | 0.0, 317 | 0.0, 318 | 1.2487038402391546 319 | ], 320 | "scale": [ 321 | 1.0, 322 | 1.0, 323 | 1.0 324 | ], 325 | "translation": [ 326 | 0.0, 327 | 0.0, 328 | 0.0 329 | ], 330 | "transparency": 0.0, 331 | "visible": true 332 | }, 333 | "basecrop 7": { 334 | "rotation": [ 335 | 0.0, 336 | 0.0, 337 | -0.6969911773875164 338 | ], 339 | "scale": [ 340 | 1.0, 341 | 1.0, 342 | 1.0 343 | ], 344 | "translation": [ 345 | 0.0, 346 | 0.0, 347 | 0.0 348 | ], 349 | "transparency": 0.0, 350 | "visible": true 351 | }, 352 | "basecrop 8": { 353 | "rotation": [ 354 | 0.0, 355 | 0.0, 356 | -0.5991681048717267 357 | ], 358 | "scale": [ 359 | 1.0, 360 | 1.0, 361 | 1.0 362 | ], 363 | "translation": [ 364 | -4.0, 365 | -18.0, 366 | 0.0 367 | ], 368 | "transparency": 0.0, 369 | "visible": true 370 | }, 371 | "basecrop 9": { 372 | "rotation": [ 373 | 0.0, 374 | 0.0, 375 | 1.1750212827623963 376 | ], 377 | "scale": [ 378 | 1.0, 379 | 1.0, 380 | 1.0 381 | ], 382 | "translation": [ 383 | 0.0, 384 | 0.0, 385 | 0.0 386 | ], 387 | "transparency": 0.0, 388 | "visible": true 389 | }, 390 | "root": { 391 | "rotation": [ 392 | 0.0, 393 | 0.0, 394 | 0.0 395 | ], 396 | "scale": [ 397 | 1.0, 398 | 1.0, 399 | 1.0 400 | ], 401 | "translation": [ 402 | 16.0, 403 | 85.0, 404 | 0.0 405 | ], 406 | "transparency": 0.0, 407 | "visible": true 408 | } 409 | } 410 | }, 411 | { 412 | "keys": {} 413 | }, 414 | { 415 | "keys": {} 416 | }, 417 | { 418 | "keys": {} 419 | }, 420 | { 421 | "keys": {} 422 | }, 423 | { 424 | "keys": {} 425 | }, 426 | { 427 | "keys": {} 428 | }, 429 | { 430 | "keys": {} 431 | }, 432 | { 433 | "keys": {} 434 | }, 435 | { 436 | "keys": {} 437 | }, 438 | { 439 | "keys": {} 440 | }, 441 | { 442 | "keys": {} 443 | }, 444 | { 445 | "keys": {} 446 | }, 447 | { 448 | "keys": {} 449 | }, 450 | { 451 | "keys": {} 452 | }, 453 | { 454 | "keys": {} 455 | }, 456 | { 457 | "keys": {} 458 | }, 459 | { 460 | "keys": {} 461 | }, 462 | { 463 | "keys": {} 464 | }, 465 | { 466 | "keys": {} 467 | }, 468 | { 469 | "keys": {} 470 | }, 471 | { 472 | "keys": {} 473 | }, 474 | { 475 | "keys": {} 476 | }, 477 | { 478 | "keys": {} 479 | }, 480 | { 481 | "keys": {} 482 | }, 483 | { 484 | "keys": {} 485 | }, 486 | { 487 | "keys": {} 488 | }, 489 | { 490 | "keys": {} 491 | }, 492 | { 493 | "keys": {} 494 | }, 495 | { 496 | "keys": {} 497 | }, 498 | { 499 | "keys": {} 500 | }, 501 | { 502 | "keys": {} 503 | }, 504 | { 505 | "keys": {} 506 | }, 507 | { 508 | "keys": {} 509 | }, 510 | { 511 | "keys": {} 512 | }, 513 | { 514 | "keys": {} 515 | }, 516 | { 517 | "keys": {} 518 | }, 519 | { 520 | "keys": {} 521 | }, 522 | { 523 | "keys": {} 524 | }, 525 | { 526 | "keys": {} 527 | }, 528 | { 529 | "keys": {} 530 | }, 531 | { 532 | "keys": {} 533 | }, 534 | { 535 | "keys": {} 536 | }, 537 | { 538 | "keys": {} 539 | }, 540 | { 541 | "keys": {} 542 | }, 543 | { 544 | "keys": {} 545 | }, 546 | { 547 | "keys": {} 548 | }, 549 | { 550 | "keys": {} 551 | }, 552 | { 553 | "keys": {} 554 | }, 555 | { 556 | "keys": {} 557 | }, 558 | { 559 | "keys": {} 560 | }, 561 | { 562 | "keys": {} 563 | }, 564 | { 565 | "keys": {} 566 | }, 567 | { 568 | "keys": {} 569 | }, 570 | { 571 | "keys": {} 572 | }, 573 | { 574 | "keys": {} 575 | }, 576 | { 577 | "keys": {} 578 | }, 579 | { 580 | "keys": {} 581 | }, 582 | { 583 | "keys": {} 584 | }, 585 | { 586 | "keys": {} 587 | }, 588 | { 589 | "keys": {} 590 | }, 591 | { 592 | "keys": {} 593 | }, 594 | { 595 | "keys": {} 596 | }, 597 | { 598 | "keys": {} 599 | }, 600 | { 601 | "keys": {} 602 | }, 603 | { 604 | "keys": {} 605 | }, 606 | { 607 | "keys": {} 608 | }, 609 | { 610 | "keys": {} 611 | }, 612 | { 613 | "keys": {} 614 | }, 615 | { 616 | "keys": {} 617 | }, 618 | { 619 | "keys": {} 620 | }, 621 | { 622 | "keys": {} 623 | }, 624 | { 625 | "keys": {} 626 | }, 627 | { 628 | "keys": {} 629 | }, 630 | { 631 | "keys": {} 632 | }, 633 | { 634 | "keys": {} 635 | }, 636 | { 637 | "keys": {} 638 | }, 639 | { 640 | "keys": {} 641 | }, 642 | { 643 | "keys": {} 644 | }, 645 | { 646 | "keys": {} 647 | }, 648 | { 649 | "keys": {} 650 | }, 651 | { 652 | "keys": {} 653 | }, 654 | { 655 | "keys": {} 656 | }, 657 | { 658 | "keys": {} 659 | }, 660 | { 661 | "keys": {} 662 | }, 663 | { 664 | "keys": {} 665 | }, 666 | { 667 | "keys": {} 668 | } 669 | ], 670 | "name": "doll squat.anim" 671 | }, 672 | "version": 1 673 | } -------------------------------------------------------------------------------- /ShaderDemo/game/script_3d.rpy: -------------------------------------------------------------------------------- 1 | 2 | #Generic screen for showing 3d-content. 3 | 4 | screen shaderScreen3d(name, create=None, update=None, uniforms={}, pixelShader=shader.PS_3D_BAKED): 5 | modal False 6 | add ShaderDisplayable(shader.MODE_3D, name, shader.VS_3D, pixelShader, None, uniforms, create, update): 7 | xalign 0.5 8 | yalign 0.5 9 | 10 | init python: 11 | import os 12 | import shader 13 | from shader import euclid, utils 14 | 15 | TAG_ROOM = "room scene" 16 | TAG_CUBE = "cube" 17 | 18 | def create3dCube(context): 19 | renderer = context.renderer 20 | renderer.loadModel(TAG_CUBE, utils.findFile("cube.obj"), {}) 21 | 22 | def update3dCube(context): 23 | cube = context.renderer.getModel(TAG_CUBE) 24 | cube.matrix = euclid.Matrix4() 25 | cube.matrix.rotatez(math.sin(context.shownTime)) 26 | cube.matrix.rotatey(math.cos(context.shownTime)) 27 | 28 | def create3dRoomScene(context): 29 | #This function will be called when the scene renderer has been created or reset. 30 | #For example showing the screen and changing the window size etc. can trigger this. 31 | renderer = context.renderer 32 | textures = {shader.TEX0: "room baked.jpg"} 33 | renderer.loadModel(TAG_ROOM, utils.findFile("room.obj"), textures) 34 | 35 | def update3dRoomScene(context): 36 | #This function will be called when we need to update the scene for rendering. 37 | #We can hardcode the matching camera settings that were used 38 | #to render the actual static background image. In more serious use 39 | #this information should be exported and imported along with the models. 40 | eye = euclid.Vector3(1.78016, 0.91875, -3.4134) 41 | lookAt = euclid.Vector3(0, eye.y, -1.2) 42 | up = euclid.Vector3(0, 1, 0) 43 | zMin = 0.1 44 | zMax = 100.0 45 | 46 | if cameraDrive: 47 | eye, lookAt = animate3dRoomCameraDrive(context, eye, lookAt) 48 | 49 | if cameraUserControl: 50 | eye, lookAt = handle3dUserInput(context, eye, lookAt) 51 | 52 | if True: 53 | #Values taken from the rendered Blender scene camera. 54 | lens = 30 55 | #Use the rendered image size used so the 3D-view matches up to the 2D-image. 56 | #In most cases they would be the same as renpy.config.screen_width etc. but 57 | #they don't have to be. You could also use an aspect ratio instead of explicit size. 58 | xResolution = 1280 59 | yResolution = 720 60 | projection = shader.utils.createPerspectiveBlender(lens, xResolution, yResolution, 61 | context.width, context.height, zMin, zMax) 62 | else: 63 | #If you have no camera information, use whatever looks good. 64 | fieldOfView = 34 65 | projection = shader.utils.createPerspective(fieldOfView, context.width, context.height, zMin, zMax) 66 | 67 | #Use our custom view and projection matrices 68 | view = euclid.Matrix4.new_look_at(eye, lookAt, up) 69 | context.uniforms[shader.VIEW_MATRIX] = view 70 | context.uniforms[shader.PROJ_MATRIX] = projection 71 | 72 | def animate3dRoomCameraDrive(context, eye, lookAt): 73 | store = context.store 74 | start = store.get("start") 75 | if start: 76 | animationTimeSeconds = 2.0 77 | delta = min((context.shownTime - start) / animationTimeSeconds, 1.0) 78 | 79 | eyeTarget = euclid.Vector3(-2, 1, -1) 80 | atTarget = eyeTarget + euclid.Vector3(0.4, 0.0, 0.5) 81 | eye = shader.utils.interpolate(eye, eyeTarget, delta) 82 | lookAt = shader.utils.interpolate(lookAt, atTarget, delta) 83 | else: 84 | store["start"] = context.shownTime 85 | return eye, lookAt 86 | 87 | def handle3dUserInput(context, eye, lookAt): 88 | return eye, lookAt 89 | -------------------------------------------------------------------------------- /ShaderDemo/game/script_rig.rpy: -------------------------------------------------------------------------------- 1 | 2 | init python: 3 | import math 4 | import shader 5 | 6 | #Our LiveComposite image name (defined in images/doll/doll.rpy). 7 | #We add the .rig extension to this to find the rig file associated with the image. 8 | doll = "doll" 9 | 10 | #Another, simpler rig and its image. 11 | amyDoll = "amydoll" 12 | 13 | debugRig = False #For debugging rig bones etc. 14 | debugAnimations = False #For debugging animation frames etc. 15 | 16 | #Hardcoded bone name for the rig we will be using 17 | ARM_BONE = "basecrop 13" 18 | 19 | #Hardcoded image names 20 | CLOTH_BASE = "basecrop" 21 | CLOTH_SKIRT = "skirtcrop" 22 | CLOTH_SHIRT = "shirtcrop" 23 | CLOTH_HAIR = "haircrop" 24 | 25 | #Indirection to make sure animation track information is not stored by saving the game. 26 | #Makes things easier to update after releasing the game. Note that if you 27 | #rename, delete etc. animations make sure you still keep the entries for them 28 | #in here if you want to support old save games. 29 | FLAIL = "flail" 30 | KNEEL = "kneel" 31 | DOLL_TRACKS = { 32 | FLAIL: shader.TrackInfo("doll flail.anim", cyclic=True), 33 | KNEEL: shader.TrackInfo("doll kneel.anim", cyclic=True), 34 | } 35 | 36 | AMY_IDLE = "idle" 37 | AMY_ARM_LEFT = "waveLeft" 38 | AMY_ARM_RIGHT = "waveRight" 39 | AMY_TRACKS = { 40 | AMY_IDLE: shader.TrackInfo("amydoll idle.anim", repeat=True), 41 | AMY_ARM_LEFT: shader.TrackInfo("amydoll leftarm.anim", cyclic=True), 42 | AMY_ARM_RIGHT: shader.TrackInfo("amydoll rightarm.anim", cyclic=True), 43 | } 44 | 45 | def visualizeRig(context): 46 | #Draw some useful debug visualizations 47 | if debugRig: 48 | context.createOverlayCanvas() 49 | editor = shader.RigEditor(context, editorDebugSettings) 50 | editor.visualizeBones() 51 | 52 | #Only show the wireframes in debug mode 53 | for name, bone in context.renderer.bones.items(): 54 | bone.wireFrame = debugRig 55 | 56 | def showDollClothes(show=True): 57 | for name in clothes: 58 | clothes[name] = show 59 | 60 | def updateDollClothes(context): 61 | for name, visible in clothes.items(): 62 | fullName = "images/doll/" + name 63 | context.renderer.bones[fullName].visible = visible 64 | 65 | def animateDollArm(context): 66 | bone = context.renderer.bones[ARM_BONE] 67 | targetRotation = math.sin(context.shownTime) + 0.5 #In radians, not in degrees. 68 | bone.rotation.z = shader.utils.interpolate(bone.rotation.z, targetRotation, 0.1) 69 | visualizeRig(context) 70 | 71 | def playDollAnimations(context): 72 | #Animate all active tracks. Look up the track infos using the animation names. 73 | player = shader.AnimationPlayer(context, doll) 74 | player.setDebug(debugAnimations) #Enable visual debug help 75 | player.play([DOLL_TRACKS[name] for name in anims]) 76 | updateDollClothes(context) 77 | visualizeRig(context) 78 | 79 | def playAmyAnimations(context): 80 | #Mostly same as playDollAnimations(), exepct this doll doesn't have "clothes" 81 | player = shader.AnimationPlayer(context, amyDoll) 82 | player.setDebug(debugAnimations) #Enable visual debug help 83 | player.play([AMY_TRACKS[name] for name in amyAnims]) 84 | animateEyesAndMouth(context) #Animate expressione like in the first demo 85 | visualizeRig(context) 86 | 87 | #The screen for showing rigged images. It is usually best to use the rig() function which will show this. 88 | screen rigScreen(name, pixelShader, textures={}, uniforms={}, update=None, args=None, xalign=0.5, yalign=1.0): 89 | add ShaderDisplayable(shader.MODE_SKINNED, name, shader.VS_SKINNED, pixelShader, textures, uniforms, None, update, args): 90 | xalign xalign 91 | yalign yalign 92 | 93 | #A helper screen for enabling or disabling debug information 94 | screen animationDebugScreen(): 95 | frame: 96 | xalign 1.0 97 | yalign 0.0 98 | xpadding 10 99 | ypadding 10 100 | vbox: 101 | textbutton "Animation debug" action ToggleVariable("debugAnimations") 102 | textbutton "Rig debug" action ToggleVariable("debugRig") 103 | 104 | label start_rig_demo: 105 | 106 | # Active animation set. The update function uses this to play animations contained in the set. 107 | # If you use multiple rigs, each one should have its own set of active animations. 108 | $ anims = set() 109 | $ amyAnims = set() 110 | 111 | # Visible "clothes" for the doll. By default make them all visible. 112 | $ clothes = {CLOTH_BASE : True, CLOTH_SKIRT: True, CLOTH_SHIRT: True, CLOTH_HAIR: True} 113 | 114 | # Expression animation targets for Amy. Same as in the first demo. 115 | $ eyeTarget = (0, 0) 116 | $ mouthTarget = (0, 0) 117 | 118 | scene room 119 | 120 | "This demo will show you how to use and animate rigged and skinned images." 121 | 122 | #TODO Link to docs and video. 123 | 124 | "First, let's show the image itself." 125 | 126 | $ rig(doll, update=visualizeRig).dissolve() 127 | 128 | "It looks like a normal image because it is not being animated. Let's visualize it a bit more." 129 | 130 | $ debugRig = True 131 | 132 | "There. Looks quite a bit more complex than a normal, static image." 133 | "Currently, the character is not moving. There are many ways to animate it." 134 | "First, we can manually force the bones to move. As an example, let's rotate the arm." 135 | 136 | $ rig(doll, update=animateDollArm) 137 | 138 | "Now, the arm should be moving around." 139 | "Animating bones manually can be useful in many cases, but it can also be tedious and error-prone." 140 | "We will hide the rig debug visualization for now. It can slow things down and be distracting." 141 | 142 | $ debugRig = False 143 | $ rig(doll, update=playDollAnimations) 144 | 145 | "Let's stop the manual animation and change the way we animate the rig." 146 | "Next, we use an animation that has been created using the rig editor." 147 | "You can access the editor from the main screen and create your own rigs and animations." 148 | "Alright, let's play a more complex animation." 149 | 150 | # Enable animation debug stats and start the animation. 151 | $ debugAnimations = True 152 | $ anims.add(FLAIL) 153 | 154 | # Allow the user to toggle animation debug stats on and off 155 | show screen animationDebugScreen() 156 | 157 | "The buttons at the top right can now be used to toggle debug visualization on and off." 158 | "You can wave back if you want to. Just don't do it if there are any people around you." 159 | "Next, we remove the animation from the active set and the character will return to the default pose." 160 | 161 | $ anims.remove(FLAIL) 162 | 163 | "We can also \"change\" the clothes of the doll by toggling bone visibility." 164 | "Let's hide her shirt first." 165 | 166 | $ clothes[CLOTH_SHIRT] = False 167 | 168 | "There. Let's make her bald!" 169 | 170 | $ clothes[CLOTH_HAIR] = False 171 | 172 | "And hide the skirt too." 173 | 174 | $ clothes[CLOTH_SKIRT] = False 175 | 176 | "All right, I think that is enough for now. Let's put them all back." 177 | 178 | $ showDollClothes() 179 | 180 | "Now, let's mix some animations together. Let's start flailing around again." 181 | 182 | $ anims.add(FLAIL) 183 | 184 | "There. Next we add a kneeling animation into the mix." 185 | 186 | $ anims.add(KNEEL) 187 | 188 | "..." 189 | "Active lifestyle is important, don't you think?" 190 | "If you have watched that modern dance long enough, we can proceed and let her rest." 191 | 192 | $ anims.clear() 193 | 194 | "This is a relatively complex rig, but you don't have to create complex LiveComposite setups to animate images." 195 | "Often it is enough to rig a single, static image." 196 | "First, let's hide the doll completely..." 197 | 198 | $ hide(doll).dissolve() 199 | 200 | "... And show a simpler rig which contains only one image." 201 | 202 | $ rig(amyDoll, yalign=0.1, update=playAmyAnimations).dissolve() 203 | 204 | "Just like in the first demo, this image has also an influence image." 205 | "This makes it possible to apply pixel shader wind and expression effects." 206 | "For example, we can make her frown." 207 | 208 | $ eyeTarget = (0, 2) 209 | $ mouthTarget = (0, -1) 210 | 211 | "..." 212 | "Yeah, that's enough. Reset the expression." 213 | 214 | $ eyeTarget = (0, 0) 215 | $ mouthTarget = (0, 0) 216 | 217 | "Rigs will load influence images automatically for each rig image if they exist." 218 | "This should all be familiar to you if you have also checked out the first demo." 219 | "To make the rig look a bit more alive, let's play an idle animation." 220 | 221 | $ amyAnims.add(AMY_IDLE) 222 | 223 | "Ideally idling should be reasonably subtle. As a hint you can use TrackInfo.weight to adjust animation strength." 224 | "You can also mix different idling tracks or even add some manual bone control to make it look good and non-repeating." 225 | "Let's animate it a bit more. Arm movement is always a fan favorite." 226 | 227 | $ amyAnims.add(AMY_ARM_RIGHT) 228 | 229 | "Yeah, smooth moves. Two animations at the same time." 230 | "Let's move the second hand, too." 231 | 232 | $ amyAnims.add(AMY_ARM_LEFT) 233 | 234 | "There you go. Three tracks mixed together." 235 | "That is all for now. Go and make your own rigs and animations!" 236 | "Remember to check out the documentation and watch the videos if you haven't already." 237 | "Good luck!" 238 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import renpy 3 | 4 | import utils 5 | from controller import RenderController, RenderContext, ControllerContextStore 6 | from rendering import Renderer2D, Renderer3D, SkinnedRenderer 7 | from rigeditor import RigEditor 8 | from skinnedplayer import TrackInfo, AnimationPlayer 9 | from shadercode import * 10 | 11 | PROJECTION = "projection" 12 | 13 | WORLD_MATRIX = "worldMatrix" 14 | VIEW_MATRIX = "viewMatrix" 15 | PROJ_MATRIX = "projMatrix" 16 | 17 | TEX0 = "tex0" 18 | TEX1 = "tex1" 19 | 20 | MODE_2D = "2d" 21 | MODE_3D = "3d" 22 | MODE_SKINNED = "skinned" 23 | 24 | ZERO_INFLUENCE = "zeroinfluence.png" 25 | 26 | class config: 27 | enabled = True 28 | fps = 60 29 | flipMeshX = True 30 | 31 | def log(message): 32 | renpy.display.log.write("Shaders: " + message) 33 | 34 | def isSupported(verbose=False): 35 | if not config.enabled: 36 | if verbose: 37 | log("Disabled because of 'config.enabled'") 38 | return False 39 | 40 | if not renpy.config.gl_enable: 41 | if verbose: 42 | log("Disabled because of 'renpy.config.gl_enable'") 43 | return False 44 | 45 | renderer = renpy.display.draw.info.get("renderer") #TODO renpy.get_renderer_info() 46 | if renderer != "gl": 47 | if verbose: 48 | log("Disabled because the renderer is '%s'" % renderer) 49 | return False 50 | 51 | if verbose: 52 | log("Supported!") 53 | 54 | return True 55 | 56 | _controllerContextStore = ControllerContextStore() 57 | 58 | _coreSetMode = None 59 | _coreSetModeCounter = 0 60 | 61 | def _wrapSetMode(*args): 62 | global _coreSetModeCounter 63 | _coreSetModeCounter += 1 64 | 65 | _coreSetMode(*args) 66 | 67 | def getModeChangeCount(): 68 | return _coreSetModeCounter 69 | 70 | #TERRBILE HACK! 71 | #Mode change can reset the OpenGL context, so we need to track the 72 | #mode change count in order to know when we must free and reload 73 | #any OpenGL resources. 74 | 75 | def _setupRenpyHooks(): 76 | global _coreSetMode 77 | if _coreSetMode: 78 | #Already hooked 79 | return 80 | 81 | _coreSetMode = renpy.display.interface.set_mode 82 | renpy.display.interface.set_mode = _wrapSetMode 83 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/controller.py: -------------------------------------------------------------------------------- 1 | 2 | import renpy 3 | from OpenGL import GL as gl 4 | 5 | import shader 6 | import gpu 7 | 8 | class RenderContext(object): 9 | def __init__(self, renderer, w, h, time, shownTime, animationTime, uniforms, mousePos, events, store, overlayRender): 10 | self.renderer = renderer 11 | self.width = w 12 | self.height = h 13 | self.time = time 14 | self.shownTime = shownTime 15 | self.animationTime = animationTime 16 | self.uniforms = uniforms 17 | self.mousePos = mousePos 18 | self.events = events 19 | self.store = store 20 | self.continueRendering = True 21 | self.overlayRender = overlayRender 22 | self.overlayCanvas = None 23 | 24 | def createOverlayCanvas(self): 25 | if self.overlayCanvas is not None: 26 | return 27 | self.overlayCanvas = self.overlayRender.canvas() 28 | self.overlayCanvas.rect("#f00", (0, 0, self.width - 1, self.height - 1), 1) 29 | 30 | 31 | class ControllerContext: 32 | def __init__(self): 33 | self.controller = None 34 | self.createCalled = False 35 | self.contextStore = {} 36 | self.mousePos = (0, 0) 37 | self.modeChangeCount = 0 38 | self.delayFree = False 39 | self.persist = False 40 | self.updateModeChangeCount() 41 | 42 | def updateModeChangeCount(self): 43 | self.modeChangeCount = shader.getModeChangeCount() 44 | 45 | def freeController(self): 46 | if self.controller and self.modeChangeCount == shader.getModeChangeCount(): 47 | self.controller.free() 48 | self.controller = None 49 | 50 | 51 | class ControllerContextStore: 52 | def __init__(self): 53 | self.store = {} 54 | 55 | def get(self, tag): 56 | context = self.store.get(tag, None) 57 | if not context: 58 | context = ControllerContext() 59 | self.store[tag] = context 60 | 61 | #context.delayFree = False #Not really needed... 62 | 63 | return context 64 | 65 | def removeContext(self, tag): 66 | if tag in self.store: 67 | del self.store[tag] 68 | 69 | def getAllShaderDisplayables(self, displayType): 70 | displayables = [] 71 | for disp in renpy.exports.scene_lists().get_all_displayables(): 72 | try: 73 | disp.visit_all(lambda x: displayables.append(x)) 74 | except AttributeError: 75 | #TODO child is sometimes None somewhere, we could do this manually... 76 | #Could renpy.showing(name, layer) work here? 77 | pass 78 | return [d for d in displayables if isinstance(d, displayType)] 79 | 80 | def checkDisplayableVisibility(self, displayType): 81 | tagged = {} 82 | for d in self.getAllShaderDisplayables(displayType): 83 | tagged[d.tag] = d 84 | 85 | removal = [] 86 | for tag, context in self.store.items(): 87 | if context.delayFree: 88 | if tag in tagged: 89 | #Went missing for one interaction, but now it is visible again. 90 | context.delayFree = False 91 | else: 92 | removal.append((tag, context)) 93 | elif not tag in tagged and not context.persist: 94 | #Not visible, free on next interaction 95 | context.delayFree = True 96 | 97 | for tag, context in removal: 98 | context.freeController() 99 | self.removeContext(tag) 100 | 101 | shader.log("Controller count: %s" % len(self.store)) 102 | 103 | def _clear(self): 104 | #Usually there is no need to call this in normal use 105 | for tag, context in self.store.copy().items(): 106 | context.freeController() 107 | self.removeContext(tag) 108 | self.store.clear() 109 | 110 | 111 | class RenderController(object): 112 | def __init__(self): 113 | self.renderer = None 114 | self.frameBuffer = None 115 | 116 | def init(self, renderer): 117 | self.renderer = renderer 118 | 119 | w, h = self.renderer.getSize() 120 | self.frameBuffer = gpu.FrameBuffer(w, h, renderer.useDepth) 121 | 122 | def isValid(self): 123 | return self.renderer is not None 124 | 125 | def free(self): 126 | if self.renderer: 127 | self.renderer.free() 128 | self.renderer = None 129 | 130 | if self.frameBuffer: 131 | self.frameBuffer.free() 132 | self.frameBuffer = None 133 | 134 | def getSize(self): 135 | return self.renderer.getSize() 136 | 137 | def renderImage(self, context): 138 | width, height = self.getSize() 139 | gl.glViewport(0, 0, width, height) 140 | 141 | gl.glDisable(gl.GL_SCISSOR_TEST) 142 | 143 | gl.glEnable(gl.GL_ALPHA_TEST) 144 | gl.glAlphaFunc(gl.GL_GREATER, 0) 145 | 146 | gl.glEnable(gl.GL_BLEND) 147 | gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) 148 | 149 | self.frameBuffer.bind() 150 | 151 | self.renderer.render(context) 152 | 153 | self.frameBuffer.unbind() 154 | 155 | #TODO Restore blend state. Any other states that need restoring...? 156 | gl.glBlendFunc(gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA) 157 | 158 | def copyRenderBufferToSurface(self, surface): 159 | surface.lock() 160 | 161 | gl.glPixelStorei(gl.GL_PACK_ROW_LENGTH, surface.get_pitch() // surface.get_bytesize()) 162 | 163 | gl.glBindTexture(gl.GL_TEXTURE_2D, self.frameBuffer.texture) 164 | gl.glGetTexImage(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, surface._pixels_address) 165 | 166 | gl.glBindTexture(gl.GL_TEXTURE_2D, 0) 167 | gl.glPixelStorei(gl.GL_PACK_ROW_LENGTH, 0) 168 | 169 | surface.unlock() 170 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/easing.py: -------------------------------------------------------------------------------- 1 | 2 | import math 3 | 4 | def linear(pos): 5 | return pos 6 | 7 | def quadIn(pos): 8 | return pow(pos, 2) 9 | 10 | def quadOut(pos): 11 | return -(pow((pos - 1), 2) -1) 12 | 13 | def quadInOut(pos): 14 | pos /= 0.5 15 | if pos < 1: 16 | return 0.5 * pow(pos, 2); 17 | pos -= 2 18 | return -0.5 * (pos * pos - 2); 19 | 20 | def sineIn(pos): 21 | return -math.cos(pos * (math.pi / 2)) + 1 22 | 23 | def sineOut(pos): 24 | return math.sin(pos * (math.pi / 2)) 25 | 26 | def sineInOut(pos): 27 | return (-0.5 * (math.cos(math.pi * pos) - 1)) 28 | 29 | def circIn(pos): 30 | return -(math.sqrt(1 - (pos * pos)) - 1) 31 | 32 | def circOut(pos): 33 | return math.sqrt(1 - pow(pos - 1, 2)) 34 | 35 | def circInOut(pos): 36 | pos /= 0.5 37 | if pos < 1: 38 | return -0.5 * (math.sqrt(1 - pos * pos) - 1) 39 | pos -= 2 40 | return 0.5 * (math.sqrt(1 - pos*pos) + 1) 41 | 42 | def backIn(pos): 43 | s = 1.70158 44 | return pos * pos * ((s + 1) * pos - s) 45 | 46 | def backOut(pos): 47 | s = 1.70158 48 | pos = pos - 1 49 | return pos * pos * ((s + 1) * pos + s) + 1 50 | 51 | def backInOut(pos): 52 | s = 1.70158 * 1.525 53 | pos /= 0.5 54 | if pos < 1: 55 | return 0.5 * (pos * pos * ((s + 1) * pos - s)) 56 | pos -= 2 57 | return 0.5 * (pos * pos * ((s + 1) * pos +s) + 2) 58 | 59 | def swingFrom(pos): 60 | s = 1.70158 61 | return pos * pos * ((s + 1) * pos - s) 62 | 63 | def swingTo(pos): 64 | s = 1.70158 65 | pos -= 1 66 | return pos * pos * ((s + 1) * pos + s) + 1 67 | 68 | def swingFromTo(pos): 69 | s = 1.70158 * 1.525 70 | pos /= 0.5 71 | if pos < 1: 72 | return 0.5 * (pos * pos * ((s + 1) * pos - s)) 73 | pos -= 2 74 | return 0.5 * (pos * pos * ((s + 1) * pos + s) + 2) 75 | 76 | def elastic(pos): 77 | return -1 * pow(4, -8 * pos) * math.sin((pos * 6 - 1) * (2 * math.pi) / 2) + 1 78 | 79 | def bounce(pos): 80 | if pos < (1 / 2.75): 81 | return 7.5625 * pos * pos 82 | elif pos < (2 / 2.75): 83 | pos -= (1.5/2.75) 84 | return 7.5625 * pos * pos + 0.75 85 | elif (pos < (2.5 / 2.75)): 86 | pos -= 2.25 / 2.75 87 | return 7.5625 * pos * pos + 0.9375 88 | else: 89 | pos -= 2.625 / 2.75 90 | return 7.5625 * (pos) * pos + 0.984375 91 | 92 | EASINGS = [ 93 | ("linear", linear), 94 | 95 | ("quadIn", quadIn), 96 | ("quadOut", quadOut), 97 | ("quadInOut", quadInOut), 98 | 99 | ("sineIn", sineIn), 100 | ("sineOut", sineOut), 101 | ("sineInOut", sineInOut), 102 | 103 | ("circIn", circIn), 104 | ("circOut", circOut), 105 | ("circInOut", circInOut), 106 | 107 | ("backIn", backIn), 108 | ("backOut", backOut), 109 | ("backInOut", backInOut), 110 | 111 | ("swingFrom", swingFrom), 112 | ("swingTo", swingTo), 113 | ("swingFromTo", swingFromTo), 114 | 115 | ("elastic", elastic), 116 | ("bounce", bounce) 117 | ] 118 | EASINGS.sort(key=lambda e: e[0]) 119 | 120 | MAP = {} 121 | for name, func in EASINGS: 122 | MAP[name] = func 123 | 124 | def getNames(): 125 | return [e[0] for e in EASINGS] 126 | 127 | def getEasing(name): 128 | return MAP[name] 129 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/geometry.py: -------------------------------------------------------------------------------- 1 | 2 | import polygonoffset 3 | import math 4 | import random 5 | 6 | def simplifyEdgePixels(pixels, minDistance): 7 | results = [] 8 | 9 | i = 0 10 | while i < len(pixels): 11 | results.append((float(pixels[i][0]), float(pixels[i][1]))) 12 | 13 | distance = 0 14 | i2 = i + 1 15 | while i2 < len(pixels): 16 | previous = (pixels[i2 - 1][0], pixels[i2 - 1][1]) 17 | current = (pixels[i2][0], pixels[i2][1]) 18 | distance += math.hypot(current[0] - previous[0], current[1] - previous[1]) 19 | if distance > minDistance: 20 | break 21 | i2 += 1 22 | i = i2 23 | 24 | return results 25 | 26 | def offsetPolygon(points, size): 27 | results = [] 28 | for p in points: 29 | current = (float(p[0]) + random.random() * 0.00001, float(p[1]) + random.random() * 0.00001) #TODO Fix this 30 | results.append(current) 31 | return polygonoffset.offsetpolygon(results, size) 32 | 33 | def findEdge(surface, xStart, yStart, xStep, yStep, xDir, yDir): 34 | w = surface.get_width() 35 | h = surface.get_height() 36 | 37 | x = xStart 38 | y = yStart 39 | 40 | while 1: 41 | pixel = surface.get_at((x, y)) 42 | if pixel[3] > 0: 43 | return x, y 44 | 45 | x += xStep 46 | y += yStep 47 | 48 | if x >= w: 49 | x = xStart 50 | y += yDir 51 | elif y >= h: 52 | y = yStart 53 | x += xDir 54 | 55 | if x < 0 or x >= w or y < 0 or y >= h: 56 | break 57 | 58 | return (xStart, yStart) 59 | 60 | def findCropRect(surface, pad=0): 61 | w = surface.get_width() 62 | h = surface.get_height() 63 | 64 | x1 = max(findEdge(surface, 0, 0, 0, 1, 1, 0)[0] - pad, 0) 65 | y1 = max(findEdge(surface, 0, 0, 1, 0, 0, 1)[1] - pad, 0) 66 | x2 = min(findEdge(surface, w - 1, 0, 0, 1, -1, 0)[0] + pad, w) 67 | y2 = min(findEdge(surface, 0, h - 1, 1, 0, 0, -1)[1] + pad, h) 68 | return x1, y1, x2 - x1, y2 - y1 69 | 70 | OFFSETS = [ 71 | (-1, -1), (0, -1), (1, -1), 72 | (-1, 0), (1, 0), 73 | (-1, 1), (0, 1), (1, 1) 74 | ] 75 | 76 | def findEdgePixels(surface): 77 | edgePixels = [] 78 | 79 | w, h = surface.get_size() 80 | 81 | for y in range(h): 82 | for x in range(w): 83 | pixel = surface.get_at((x, y)) 84 | if pixel[3] > 0: 85 | #Not transparent, check nearby 86 | isEdge = False 87 | 88 | for offset in OFFSETS: 89 | n = (x + offset[0], y + offset[1]) 90 | if n[0] > 0 and n[0] < w and n[1] > 0 and n[1] < h: 91 | near = surface.get_at(n) 92 | if near[3] == 0: 93 | #Transparent near pixel, this is an edge 94 | isEdge = True 95 | break 96 | else: 97 | #Outside image bounds 98 | isEdge = True 99 | break 100 | 101 | if isEdge: 102 | edgePixels.append((x, y)) 103 | 104 | return edgePixels 105 | 106 | def _getNearby(surface, x, y, size): 107 | w, h = surface.get_size() 108 | xStart = max(x - size, 0) 109 | xEnd = min(x + size + 1, w - 1) 110 | yStart = max(y - size, 0) 111 | yEnd = min(y + size + 1, h - 1) 112 | 113 | pixels = [] 114 | for yp in range(yStart, yEnd): 115 | for xp in range(xStart, xEnd): 116 | if xp != x or yp != y: 117 | point = (xp, yp) 118 | pixels.append((point, surface.get_at(point))) 119 | return pixels 120 | 121 | def _isEdgePixel(surface, x, y, size): 122 | color = surface.get_at((x, y)) 123 | if color[3] != 0: 124 | #Not transparent 125 | return False 126 | 127 | w, h = surface.get_size() 128 | isAlpha = False 129 | isColor = False 130 | 131 | pixels = _getNearby(surface, x, y, size) 132 | if len(pixels) < 8: 133 | return True 134 | 135 | for pixel in pixels: 136 | color = pixel[1] 137 | if color[3] > 0: 138 | isColor = True 139 | if color[3] == 0: 140 | isAlpha = True 141 | return isAlpha and isColor 142 | 143 | def findEdgePixelsOrdered(surface): 144 | size = 1 145 | start = findEdge(surface, 0, 0, 0, 1, 1, 0) 146 | start = (start[0] - size, start[1]) #Must have empty alpha padding! 147 | current = start 148 | 149 | results = [] 150 | seen = set() 151 | backtrack = [] 152 | undo = [] 153 | w, h = surface.get_size() 154 | 155 | steps = 0 156 | while current: 157 | if current in seen: 158 | if backtrack and current != start: 159 | current = backtrack.pop() 160 | index = undo.pop() 161 | results = results[:index] 162 | else: 163 | break 164 | else: 165 | seen.add(current) 166 | results.append(current) 167 | 168 | edges = [] 169 | for pixel in _getNearby(surface, current[0], current[1], size): 170 | point = pixel[0] 171 | if _isEdgePixel(surface, point[0], point[1], size): 172 | edges.append(point) 173 | 174 | if edges: 175 | current = edges.pop() 176 | backtrack.extend(edges) 177 | undo.extend([len(results)] * len(edges)) 178 | 179 | steps += 1 180 | 181 | return results 182 | 183 | TURN_LEFT, TURN_RIGHT, TURN_NONE = (1, -1, 0) 184 | 185 | def _turn(p, q, r): 186 | return cmp((q[0] - p[0]) * (r[1] - p[1]) - (r[0] - p[0]) * (q[1] - p[1]), 0) 187 | 188 | def _keepLeft(hull, r): 189 | while len(hull) > 1 and _turn(hull[-2], hull[-1], r) != TURN_LEFT: 190 | hull.pop() 191 | if not len(hull) or hull[-1] != r: 192 | hull.append(r) 193 | return hull 194 | 195 | def convexHull(points): 196 | """Returns points on convex hull of an array of points in CCW order using Graham scan.""" 197 | points = sorted(points) 198 | l = reduce(_keepLeft, points, []) 199 | u = reduce(_keepLeft, reversed(points), []) 200 | return l.extend(u[i] for i in xrange(1, len(u) - 1)) or l 201 | 202 | 203 | RIGHT = "RIGHT" 204 | LEFT = "LEFT" 205 | 206 | def insideConvexHull(point, vertices): 207 | previous_side = None 208 | n_vertices = len(vertices) 209 | for n in xrange(n_vertices): 210 | a, b = vertices[n], vertices[(n+1)%n_vertices] 211 | affine_segment = _vSub(b, a) 212 | affine_point = _vSub(point, a) 213 | current_side = _getSide(affine_segment, affine_point) 214 | if current_side is None: 215 | return False #outside or over an edge 216 | elif previous_side is None: #first segment 217 | previous_side = current_side 218 | elif previous_side != current_side: 219 | return False 220 | return True 221 | 222 | def _getSide(a, b): 223 | x = _xProduct(a, b) 224 | if x < 0: 225 | return LEFT 226 | elif x > 0: 227 | return RIGHT 228 | else: 229 | return None 230 | 231 | def _vSub(a, b): 232 | return (a[0]-b[0], a[1]-b[1]) 233 | 234 | def _xProduct(a, b): 235 | return a[0]*b[1]-a[1]*b[0] 236 | 237 | def insidePolygon(x, y, poly): 238 | n = len(poly) 239 | inside = False 240 | 241 | p1x, p1y = poly[0] 242 | for i in range(n+1): 243 | p2x, p2y = poly[i % n] 244 | if y > min(p1y, p2y): 245 | if y <= max(p1y, p2y): 246 | if x <= max(p1x, p2x): 247 | if p1y != p2y: 248 | xinters = (y-p1y)*(p2x-p1x)/(p2y-p1y)+p1x 249 | if p1x == p2x or x <= xinters: 250 | inside = not inside 251 | p1x, p1y = p2x, p2y 252 | 253 | return inside 254 | 255 | def _interpolate(a, b, s): 256 | return a + s * (b - a) 257 | 258 | def createGrid(rect, xCount, yCount): 259 | vertices = [] 260 | uvs = [] 261 | indices = [] 262 | 263 | for y in range(yCount): 264 | yUv = y / float(yCount - 1) 265 | yPos = _interpolate(rect[1], rect[1] + rect[3], yUv) 266 | for x in range(xCount): 267 | xUv = x / float(xCount - 1) 268 | xPos = _interpolate(rect[0], rect[0] + rect[2], xUv) 269 | vertices.append((xPos, yPos)) 270 | uvs.append((xUv, yUv)) 271 | 272 | if y < yCount -1 and x < xCount - 1: 273 | index = x * xCount + y 274 | indices.extend([index, index + 1, index + xCount]) 275 | indices.extend([index + xCount, index + 1, index + xCount + 1]) 276 | 277 | return vertices, uvs, indices 278 | 279 | def pointToLineDistance(point, a, b): 280 | x1, y1 = a 281 | x2, y2 = b 282 | x3, y3 = point 283 | 284 | px = x2 - x1 285 | py = y2 - y1 286 | value = px*px + py*py 287 | 288 | u = ((x3 - x1) * px + (y3 - y1) * py) / float(value) 289 | if u > 1: 290 | u = 1 291 | elif u < 0: 292 | u = 0 293 | 294 | x = x1 + u * px 295 | y = y1 + u * py 296 | dx = x - x3 297 | dy = y - y3 298 | return math.sqrt(dx*dx + dy*dy) 299 | 300 | def pointDistance(p1, p2): 301 | return math.hypot(p2[0] - p1[0], p2[1] - p1[1]) 302 | 303 | def triangleArea(p1, p2, p3): 304 | a = pointDistance(p1, p2) 305 | b = pointDistance(p2, p3) 306 | c = pointDistance(p3, p1) 307 | 308 | #Heron's formula 309 | s = (a + b + c) / 2.0 310 | return (s * (s - a) * (s - b) * (s - c)) ** 0.5 311 | 312 | def interpolate2d(p1, p2, s): 313 | x = _interpolate(p1[0], p2[0], s) 314 | y = _interpolate(p1[1], p2[1], s) 315 | return (x, y) 316 | 317 | def shortenLine(a, b, relative): 318 | aShort = shortenLineEnd(a, b, relative) 319 | bShort = shortenLineEnd(b, a, relative) 320 | return aShort, bShort 321 | 322 | def shortenLineEnd(a, b, relative): 323 | x1, y1 = a 324 | x2, y2 = b 325 | 326 | dx = x2 - x1 327 | dy = y2 - y1 328 | length = math.sqrt(dx * dx + dy * dy) 329 | if length > 0: 330 | dx /= length 331 | dy /= length 332 | 333 | dx *= length - (length * relative) 334 | dy *= length - (length * relative) 335 | x3 = x1 + dx 336 | y3 = y1 + dy 337 | return x3, y3 338 | 339 | def _triSign(p1, p2, p3): 340 | return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) 341 | 342 | def pointInTriangle(point, v1, v2, v3): 343 | b1 = _triSign(point, v1, v2) < 0.0 344 | b2 = _triSign(point, v2, v3) < 0.0 345 | b3 = _triSign(point, v3, v1) < 0.0 346 | return (b1 == b2) and (b2 == b3) 347 | 348 | def triangleCentroid(v1, v2, v3): 349 | centerX = (v1[0] + v2[0] + v3[0]) / 3.0 350 | centerY = (v1[1] + v2[1] + v3[1]) / 3.0 351 | return centerX, centerY 352 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/gpu/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from framebuffer import FrameBuffer 3 | from shaderprogram import ShaderProgram 4 | from texture import Texture 5 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/gpu/framebuffer.py: -------------------------------------------------------------------------------- 1 | 2 | from OpenGL import GL as gl 3 | 4 | class FrameBuffer: 5 | def __init__(self, width, height, depth=False): 6 | self.texture = self.createEmptyTexture(width, height) 7 | if self.texture == 0: 8 | raise RuntimeError("Can't create FrameBuffer textures") 9 | 10 | self.depthBuffer = 0 11 | if depth: 12 | self.depthBuffer = self.createDepthBuffer(width, height) 13 | if self.depthBuffer == 0: 14 | raise RuntimeError("Can't create FrameBuffer depth buffer") 15 | 16 | self.buffer = self.createFrameBuffer(self.texture, self.depthBuffer) 17 | if self.buffer == 0: 18 | raise RuntimeError("Can't create FrameBuffer buffer") 19 | 20 | def free(self): 21 | if self.texture: 22 | gl.glDeleteTextures(1, self.texture) 23 | self.texture = 0 24 | if self.depthBuffer: 25 | gl.glDeleteRenderbuffers(1, self.depthBuffer) 26 | self.depthBuffer = 0 27 | if self.buffer: 28 | gl.glDeleteFramebuffers(1, self.buffer) 29 | self.buffer = 0 30 | 31 | def createEmptyTexture(self, width, height): 32 | textureId = (gl.GLuint * 1)() 33 | gl.glGenTextures(1, textureId) 34 | gl.glBindTexture(gl.GL_TEXTURE_2D, textureId[0]) 35 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) 36 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) 37 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST) 38 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST) 39 | #None means reserve texture memory, but texels are undefined 40 | gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA8, width, height, 0, gl.GL_BGRA, gl.GL_UNSIGNED_BYTE, None) 41 | gl.glBindTexture(gl.GL_TEXTURE_2D, 0) 42 | return textureId[0] 43 | 44 | def createDepthBuffer(self, width, height): 45 | textureId = (gl.GLuint * 1)() 46 | gl.glGenRenderbuffers(1, textureId) 47 | gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, textureId[0]) 48 | gl.glRenderbufferStorage(gl.GL_RENDERBUFFER, gl.GL_DEPTH_COMPONENT, width, height) 49 | gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, 0) 50 | return textureId[0] 51 | 52 | def createFrameBuffer(self, texture, depthBuffer): 53 | bufferId = (gl.GLuint * 1)() 54 | gl.glGenFramebuffers(1, bufferId); 55 | gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, bufferId[0]) 56 | gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, texture, 0) 57 | if depthBuffer: 58 | gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT, gl.GL_RENDERBUFFER, depthBuffer); 59 | gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) 60 | return bufferId[0] 61 | 62 | def bind(self): 63 | gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.buffer) 64 | 65 | def unbind(self): 66 | gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) 67 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/gpu/shaderprogram.py: -------------------------------------------------------------------------------- 1 | 2 | import ctypes 3 | from OpenGL import GL as gl 4 | 5 | #Possible compatibility defines for GLSL ES etc. 6 | DEFINES = """ 7 | #define ATTRIBUTE attribute 8 | #define VARYING varying 9 | #define UNIFORM uniform 10 | """ 11 | 12 | def wrapShaderCode(code): 13 | return DEFINES + "\n\n" + code 14 | 15 | class ShaderProgram: 16 | def __init__(self, vsCode, psCode): 17 | self.handle = gl.glCreateProgram() 18 | self.linked = False 19 | 20 | self.createShader(vsCode, gl.GL_VERTEX_SHADER) 21 | self.createShader(psCode, gl.GL_FRAGMENT_SHADER) 22 | 23 | self.link() 24 | 25 | if not self.linked: 26 | raise RuntimeError("Shader not linked") 27 | 28 | def createShader(self, shaderCode, type): 29 | shader = gl.glCreateShader(type) 30 | gl.glShaderSource(shader, wrapShaderCode(shaderCode)) 31 | gl.glCompileShader(shader) 32 | 33 | status = gl.glGetShaderiv(shader, gl.GL_COMPILE_STATUS) 34 | if status: 35 | gl.glAttachShader(self.handle, shader) 36 | else: 37 | raise RuntimeError("Shader compile error: %s" % gl.glGetShaderInfoLog(shader)) 38 | 39 | def link(self): 40 | gl.glLinkProgram(self.handle) 41 | 42 | status = gl.glGetProgramiv(self.handle, gl.GL_LINK_STATUS) 43 | if status: 44 | self.linked = True 45 | else: 46 | raise RuntimeError("Link error: %s" % gl.glGetProgramInfoLog(self.handle)) 47 | 48 | def free(self): 49 | if self.handle: 50 | gl.glDeleteProgram(self.handle) 51 | self.handle = 0 52 | self.linked = False 53 | 54 | def bind(self): 55 | gl.glUseProgram(self.handle) 56 | 57 | def unbind(self): 58 | gl.glUseProgram(0) 59 | 60 | def uniformf(self, name, *values): 61 | {1 : gl.glUniform1f, 62 | 2 : gl.glUniform2f, 63 | 3 : gl.glUniform3f, 64 | 4 : gl.glUniform4f 65 | }[len(values)](gl.glGetUniformLocation(self.handle, name), *values) 66 | 67 | def uniformi(self, name, *values): 68 | {1 : gl.glUniform1i, 69 | 2 : gl.glUniform2i, 70 | 3 : gl.glUniform3i, 71 | 4 : gl.glUniform4i 72 | }[len(values)](gl.glGetUniformLocation(self.handle, name), *values) 73 | 74 | def uniformMatrix4f(self, name, matrix): 75 | loc = gl.glGetUniformLocation(self.handle, name) 76 | gl.glUniformMatrix4fv(loc, 1, False, (ctypes.c_float * 16)(*matrix)) 77 | 78 | def uniformMatrix4fArray(self, name, values): 79 | loc = gl.glGetUniformLocation(self.handle, name) 80 | count = len(values) / 16 81 | gl.glUniformMatrix4fv(loc, count, False, (ctypes.c_float * len(values))(*values)) 82 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/gpu/texture.py: -------------------------------------------------------------------------------- 1 | 2 | import ctypes 3 | from OpenGL import GL as gl 4 | 5 | class Texture: 6 | def __init__(self, surface): 7 | self.width = 0 8 | self.height = 0 9 | self.textureId = 0 10 | 11 | self._load(surface) 12 | 13 | def _load(self, surface): 14 | self.free() 15 | 16 | self.width = surface.get_width() 17 | self.height = surface.get_height() 18 | self.textureId = 0 19 | 20 | textureId = (gl.GLuint * 1)() 21 | 22 | surface.lock() 23 | 24 | BYTEP = ctypes.POINTER(ctypes.c_ubyte) 25 | ptr = ctypes.cast(surface._pixels_address, BYTEP) 26 | 27 | gl.glGenTextures(1, textureId) 28 | gl.glEnable(gl.GL_TEXTURE_2D) 29 | gl.glActiveTexture(gl.GL_TEXTURE0) 30 | 31 | gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, surface.get_pitch() // surface.get_bytesize()) 32 | gl.glBindTexture(gl.GL_TEXTURE_2D, textureId[0]) 33 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE) 34 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE) 35 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR) 36 | gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR) 37 | gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, self.width, self.height, 0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, ptr) 38 | gl.glBindTexture(gl.GL_TEXTURE_2D, 0); 39 | gl.glPixelStorei(gl.GL_UNPACK_ROW_LENGTH, 0) 40 | 41 | surface.unlock() 42 | 43 | self.textureId = textureId[0] 44 | 45 | def free(self): 46 | if self.textureId: 47 | gl.glDeleteTextures(1, self.textureId) 48 | self.textureId = 0 49 | 50 | def valid(self): 51 | return self.textureId != 0 52 | 53 | def bind(self, index): 54 | gl.glActiveTexture(gl.GL_TEXTURE0 + index) 55 | gl.glBindTexture(gl.GL_TEXTURE_2D, self.textureId) 56 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/mesh.py: -------------------------------------------------------------------------------- 1 | 2 | import shader 3 | import utils 4 | 5 | def loadObj(filename): 6 | verts = [] 7 | norms = [] 8 | uvs = [] 9 | vertsOut = [] 10 | normsOut = [] 11 | uvsOut = [] 12 | flipX = shader.config.flipMeshX 13 | 14 | for line in utils.openFile(filename): 15 | values = line.split() 16 | if values[0] == "v": 17 | v = map(float, values[1:4]) 18 | if flipX: 19 | v[0] = -v[0] 20 | verts.append(v) 21 | elif values[0] == "vt": 22 | t = map(float, values[1:3]) 23 | t[1] = 1.0 - t[1] #Flip texture y 24 | uvs.append(t) 25 | elif values[0] == "vn": 26 | n = map(float, values[1:4]) 27 | if flipX: 28 | n[0] = -n[0] 29 | norms.append(n) 30 | elif values[0] == "f": 31 | if len(values) != 4: 32 | raise RuntimeError("Mesh is not triangulated?") 33 | 34 | for face in values[1:]: 35 | pointers = face.split("/") 36 | vertsOut.append(list(verts[int(pointers[0]) - 1])) 37 | if pointers[1]: 38 | #Has texture coordinates 39 | uvsOut.append(list(uvs[int(pointers[1]) - 1])) 40 | else: 41 | uvsOut.append((0, 0)) 42 | normsOut.append(list(norms[int(pointers[2]) - 1])) 43 | 44 | return vertsOut, normsOut, uvsOut 45 | 46 | 47 | class MeshObj(object): 48 | def __init__(self, path): 49 | self.path = path 50 | self.vertices = None 51 | self.normals = None 52 | self.uvs = None 53 | 54 | def load(self): 55 | if self.vertices: 56 | return 57 | 58 | verts, normals, uvs = loadObj(self.path) 59 | self.vertices = utils.makeFloatArray(verts, 3) 60 | self.normals = utils.makeFloatArray(normals, 3) 61 | self.uvs = utils.makeFloatArray(uvs, 2) 62 | 63 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/polygonoffset.py: -------------------------------------------------------------------------------- 1 | 2 | import math 3 | 4 | def calcoffsetpoint(pt1, pt2, offset): 5 | """ 6 | Get a point offset from the line 7 | segment pt1-pt2 distance "offset". 8 | 9 | Return two tuple of coordinates. 10 | """ 11 | theta = math.atan2(pt2[1] - pt1[1], 12 | pt2[0] - pt1[0]) 13 | theta += math.pi/2.0 14 | return (pt1[0] - math.cos(theta) * offset, 15 | pt1[1] - math.sin(theta) * offset) 16 | 17 | def getoffsetintercept(pt1, pt2, m, offset): 18 | """ 19 | From points pt1 and pt2 defining a line 20 | in the Cartesian plane, the slope of the 21 | line m, and an offset distance, 22 | calculates the y intercept of 23 | the new line offset from the original. 24 | """ 25 | x, y = calcoffsetpoint(pt1, pt2, offset) 26 | return y - m * x 27 | 28 | def getpt(pt1, pt2, pt3, offset): 29 | """ 30 | Gets intersection point of the two 31 | lines defined by pt1, pt2, and pt3; 32 | offset is the distance to offset 33 | the point from the polygon. 34 | 35 | Valid for lines with slopes other 36 | than zero or infinity. 37 | 38 | Returns a two tuple of coordinates. 39 | """ 40 | # get first offset intercept 41 | m = (pt2[1] - pt1[1])/(pt2[0] - pt1[0]) 42 | boffset = getoffsetintercept(pt1, pt2, m, offset) 43 | # get second offset intercept 44 | mprime = (pt3[1] - pt2[1])/(pt3[0] - pt2[0]) 45 | boffsetprime = getoffsetintercept(pt2, pt3, mprime, offset) 46 | # get intersection of two offset lines 47 | newx = (boffsetprime - boffset)/(m - mprime) 48 | newy = m * newx + boffset 49 | return newx, newy 50 | 51 | def getslopeandintercept(pt1, pt2, offset): 52 | """ 53 | Gets the slope and the intercept of the 54 | offset line. 55 | Result returned as a two tuple. 56 | """ 57 | m = (pt2[1] - pt1[1])/(pt2[0] - pt1[0]) 58 | b = getoffsetintercept(pt1, pt2, m, offset) 59 | return m, b 60 | 61 | def getoffsetcornerpoint(pt1, pt2, pt3, offset): 62 | """ 63 | Gets intersection point of the two 64 | lines defined by pt1, pt2, and pt3; 65 | offset is the distance to offset 66 | the point from the polygon. 67 | 68 | Returns a two tuple of coordinates. 69 | """ 70 | # starting out with horizontal line 71 | if (pt2[1] - pt1[1]) == 0.0: 72 | ycoord = pt1[1] - math.cos(math.atan2(0.0, pt2[0] - pt1[0])) * offset 73 | # a vertical line follows 74 | if (pt3[0] - pt2[0]) == 0.0: 75 | xcoord = pt2[0] + math.sin(math.atan2(pt3[1] - pt2[1], 0.0)) * offset 76 | # a sloped line follows 77 | else: 78 | m, offsetintercept = getslopeandintercept(pt2, pt3, offset) 79 | # calculate for x with ycoord 80 | xcoord = (ycoord - offsetintercept)/m 81 | # starting out with a vertical line 82 | if (pt2[0] - pt1[0]) == 0.0: 83 | xcoord = pt1[0] + math.sin(math.atan2(pt2[1] - pt1[1], 0.0)) * offset 84 | # a horizontal line follows 85 | if (pt3[1] - pt2[1]) == 0.0: 86 | ycoord = pt2[1] - math.cos(math.atan2(0.0, pt3[0] - pt2[0])) * offset 87 | # a sloped line follows 88 | else: 89 | m, offsetintercept = getslopeandintercept(pt2, pt3, offset) 90 | # calculate for y with xcoord 91 | ycoord = m * xcoord + offsetintercept 92 | # starting out with sloped line 93 | if (pt2[1] - pt1[1]) != 0.0 and (pt2[0] - pt1[0]) != 0.0: 94 | # if second line is horizontal 95 | if (pt3[1] - pt2[1]) == 0.0: 96 | ycoord = pt2[1] - math.cos(math.atan2(0.0, pt3[0] - pt2[0])) * offset 97 | m, offsetintercept = getslopeandintercept(pt1, pt2, offset) 98 | # calculate for x with y coord 99 | xcoord = (ycoord - offsetintercept)/m 100 | # if second line is vertical 101 | elif (pt3[0] - pt2[0]) == 0.0: 102 | xcoord = pt2[0] + math.sin(math.atan2(pt3[1] - pt2[1], 0.0)) * offset 103 | m, offsetintercept = getslopeandintercept(pt1, pt2, offset) 104 | # solve for y with x coordinate 105 | ycoord = m * xcoord + offsetintercept 106 | # if both lines are sloped 107 | else: 108 | xcoord, ycoord = getpt(pt1, pt2, pt3, offset) 109 | return xcoord, ycoord 110 | 111 | def offsetpolygon(polyx, offset): 112 | """ 113 | Offsets a clockwise list of coordinates 114 | polyx distance offset to the inside of 115 | the polygon. 116 | Returns list of offset points. 117 | """ 118 | polyy = [] 119 | # need three points at a time 120 | for counter in range(0, len(polyx) - 3): 121 | # get first offset intercept 122 | 123 | pt = getoffsetcornerpoint(polyx[counter], 124 | polyx[counter + 1], 125 | polyx[counter + 2], 126 | offset) 127 | # append new point to polyy 128 | polyy.append(pt) 129 | 130 | 131 | #try: 132 | # last three points 133 | pt = getoffsetcornerpoint(polyx[-3], polyx[-2], polyx[-1], offset) 134 | polyy.append(pt) 135 | pt = getoffsetcornerpoint(polyx[-2], polyx[-1], polyx[0], offset) 136 | polyy.append(pt) 137 | pt = getoffsetcornerpoint(polyx[-1], polyx[0], polyx[1], offset) 138 | polyy.append(pt) 139 | #except ZeroDivisionError: 140 | # pass 141 | 142 | return polyy 143 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/shadercode.py: -------------------------------------------------------------------------------- 1 | 2 | VS_2D = """ 3 | 4 | ATTRIBUTE vec4 inVertex; 5 | 6 | VARYING vec2 varUv; 7 | 8 | UNIFORM mat4 projection; 9 | 10 | void main() 11 | { 12 | varUv = inVertex.zw; 13 | gl_Position = projection * vec4(inVertex.xy, 0.0, 1.0); 14 | } 15 | """ 16 | 17 | PS_WALK_2D = """ 18 | 19 | VARYING vec2 varUv; 20 | 21 | UNIFORM sampler2D tex0; 22 | UNIFORM sampler2D tex1; 23 | UNIFORM float shownTime; 24 | UNIFORM float animationTime; 25 | 26 | void main() 27 | { 28 | vec4 color1 = texture2D(tex0, varUv); 29 | vec4 weights = texture2D(tex1, varUv); 30 | float influence = weights.r; 31 | 32 | if (influence > 0.0) { 33 | float speed = sin(animationTime * 5.0); 34 | float xShift = sin(speed + varUv.x * varUv.y * 10) * influence * 0.01; 35 | float yShift = cos(speed + varUv.x * varUv.y * 5) * influence * 0.01; 36 | 37 | gl_FragColor = texture2D(tex0, varUv + vec2(xShift, yShift)); 38 | } 39 | else { 40 | gl_FragColor = color1; 41 | } 42 | } 43 | """ 44 | 45 | LIB_WIND = """ 46 | 47 | UNIFORM sampler2D tex0; 48 | UNIFORM sampler2D tex1; 49 | 50 | UNIFORM float mouseEnabled; 51 | UNIFORM vec2 mousePos; 52 | 53 | UNIFORM vec2 eyeShift; 54 | UNIFORM vec2 mouthShift; 55 | 56 | const float WIND_SPEED = 5.0; 57 | const float DISTANCE = 0.0075; 58 | const float FLUIDNESS = 0.75; 59 | const float TURBULENCE = 15.0; 60 | const float FADE_IN = 1.0; //In seconds 61 | 62 | vec4 applyWind(vec2 uv, float time) 63 | { 64 | float movement = 0.1; 65 | 66 | vec4 weights = texture2D(tex1, uv); 67 | 68 | if (weights.g > 0.0) { 69 | vec2 eyeCoords = uv + (eyeShift * weights.g); 70 | if (texture2D(tex1, eyeCoords).g > 0.0) { 71 | return texture2D(tex0, eyeCoords); 72 | } 73 | } 74 | 75 | if (weights.b > 0.0) { 76 | vec2 smileCoords = uv + (mouthShift * weights.b); 77 | if (texture2D(tex1, smileCoords).b > 0.0) { 78 | return texture2D(tex0, smileCoords); 79 | } 80 | } 81 | 82 | float timeFade = min(time / FADE_IN, 1.0); 83 | float influence = weights.r * (0.5 + (movement * 1.25)) * timeFade; 84 | 85 | if (mouseEnabled > 0.0) { 86 | //Use mouse position to set influence 87 | influence = (1.0 - distance(mousePos, uv) * 5.0) * 2.0; 88 | } 89 | 90 | if (influence > 0.0) { 91 | float modifier = sin(uv.x + time) / 2.0 + 1.5; 92 | float xShift = sin((uv.y * 20.0) * FLUIDNESS + (time * WIND_SPEED)) * modifier * influence * DISTANCE; 93 | float yShift = cos((uv.x * 50.0) * FLUIDNESS + (time * WIND_SPEED)) * influence * DISTANCE; 94 | return texture2D(tex0, uv + vec2(xShift, yShift)); 95 | } 96 | else { 97 | return texture2D(tex0, uv); 98 | } 99 | } 100 | """ 101 | 102 | PS_WIND_2D = LIB_WIND + """ 103 | 104 | VARYING vec2 varUv; 105 | 106 | UNIFORM float shownTime; 107 | UNIFORM float animationTime; 108 | 109 | void main() 110 | { 111 | gl_FragColor = applyWind(varUv, shownTime); 112 | } 113 | """ 114 | 115 | PS_BEAM_FADE_2D = """ 116 | 117 | VARYING vec2 varUv; 118 | 119 | UNIFORM sampler2D tex0; 120 | UNIFORM float shownTime; 121 | 122 | const float intensity = 1.0; 123 | 124 | float rand(vec2 co){ 125 | return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 126 | } 127 | 128 | void main() 129 | { 130 | float f = rand(vec2(0.0, varUv.y)) * rand(vec2(0.0, gl_FragCoord.y + shownTime)); 131 | float fade = shownTime / 2.0; 132 | 133 | vec4 color = vec4(-f * 0.5, f * 0.5, f, 0.0); 134 | vec4 diffuse = texture2D(tex0, varUv); 135 | gl_FragColor = vec4((diffuse * gl_Color + color * intensity).rgb, max(diffuse.a - fade, 0.0)); 136 | } 137 | """ 138 | 139 | PS_BLUR_2D = """ 140 | 141 | VARYING vec2 varUv; 142 | 143 | UNIFORM sampler2D tex0; 144 | UNIFORM float blurSize; 145 | UNIFORM float shownTime; 146 | UNIFORM vec2 imageSize; 147 | 148 | vec4 blur(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) { 149 | vec4 color = vec4(0.0); 150 | vec2 off1 = vec2(1.411764705882353) * direction; 151 | vec2 off2 = vec2(3.2941176470588234) * direction; 152 | vec2 off3 = vec2(5.176470588235294) * direction; 153 | color += texture2D(image, uv) * 0.1964825501511404; 154 | color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344; 155 | color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344; 156 | color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732; 157 | color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732; 158 | color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057; 159 | color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057; 160 | return color; 161 | } 162 | 163 | void main() 164 | { 165 | gl_FragColor = blur(tex0, varUv, imageSize.xy, vec2(blurSize, blurSize)); 166 | } 167 | """ 168 | 169 | VS_3D = """ 170 | 171 | ATTRIBUTE vec4 inPosition; 172 | ATTRIBUTE vec3 inNormal; 173 | ATTRIBUTE vec2 inUv; 174 | 175 | VARYING vec3 varNormal; 176 | VARYING vec2 varUv; 177 | 178 | UNIFORM mat4 worldMatrix; 179 | UNIFORM mat4 viewMatrix; 180 | UNIFORM mat4 projMatrix; 181 | 182 | void main() 183 | { 184 | varUv = inUv; 185 | varNormal = inNormal; 186 | gl_Position = projMatrix * viewMatrix * worldMatrix * inPosition; 187 | } 188 | """ 189 | 190 | PS_3D_BAKED = """ 191 | 192 | VARYING vec2 varUv; 193 | 194 | UNIFORM sampler2D tex0; 195 | 196 | void main() 197 | { 198 | gl_FragColor = texture2D(tex0, varUv); 199 | } 200 | """ 201 | 202 | PS_3D_NORMALS = """ 203 | 204 | VARYING vec3 varNormal; 205 | VARYING vec2 varUv; 206 | 207 | UNIFORM sampler2D tex0; 208 | 209 | void main() 210 | { 211 | float r = (varNormal.x + 1.0) / 2.0; 212 | float g = (varNormal.y + 1.0) / 2.0; 213 | float b = (varNormal.z + 1.0) / 2.0; 214 | gl_FragColor = vec4(r, g, b, 1.0); 215 | } 216 | """ 217 | 218 | VS_SKINNED = """ 219 | 220 | ATTRIBUTE vec2 inVertex; 221 | ATTRIBUTE vec2 inUv; 222 | ATTRIBUTE vec4 inBoneWeights; 223 | ATTRIBUTE vec4 inBoneIndices; 224 | 225 | VARYING vec2 varUv; 226 | VARYING float varAlpha; 227 | 228 | UNIFORM mat4 projection; 229 | UNIFORM mat4 boneMatrices[MAX_BONES]; 230 | UNIFORM vec2 screenSize; 231 | UNIFORM float shownTime; 232 | 233 | vec2 toScreen(vec2 point) 234 | { 235 | return vec2(point.x / (screenSize.x / 2.0) - 1.0, point.y / (screenSize.y / 2.0) - 1.0); 236 | } 237 | 238 | void main() 239 | { 240 | varUv = inUv; 241 | 242 | vec2 pos = vec2(0.0, 0.0); 243 | float transparency = 0.0; 244 | vec4 boneWeights = inBoneWeights; 245 | ivec4 boneIndex = ivec4(inBoneIndices); 246 | 247 | for (int i = 0; i < 4; i++) { 248 | mat4 boneMatrix = boneMatrices[boneIndex.x]; 249 | pos += (boneMatrix * vec4(inVertex, 0.0, 1.0) * boneWeights.x).xy; 250 | 251 | //Apply damping 252 | vec2 boneDelta = vec2(boneMatrix[0][3], boneMatrix[1][3]); 253 | pos += (boneDelta * boneWeights.x) * boneMatrix[2][3]; 254 | 255 | //Apply transparency 256 | transparency += boneMatrix[3][3] * boneWeights.x; 257 | 258 | boneWeights = boneWeights.yzwx; 259 | boneIndex = boneIndex.yzwx; 260 | } 261 | varAlpha = max(1.0 - transparency, 0.0); 262 | 263 | gl_Position = projection * vec4(toScreen(pos.xy), 0.0, 1.0); 264 | } 265 | """ 266 | 267 | PS_SKINNED = LIB_WIND + """ 268 | 269 | VARYING vec2 varUv; 270 | VARYING float varAlpha; 271 | 272 | UNIFORM float wireFrame; 273 | UNIFORM float shownTime; 274 | 275 | void main() 276 | { 277 | vec4 color = applyWind(varUv, shownTime); 278 | 279 | color.rgb *= 1.0 - wireFrame; 280 | color.a = (color.a * varAlpha) + wireFrame; 281 | gl_FragColor = color; 282 | } 283 | """ 284 | 285 | DEFERRED_MAX_LIGHTS = 8 286 | 287 | PS_DEFERRED = """ 288 | 289 | VARYING vec2 varUv; 290 | 291 | UNIFORM sampler2D tex0; 292 | UNIFORM sampler2D depthMap; 293 | UNIFORM sampler2D normalMap; 294 | UNIFORM sampler2D sprite; 295 | 296 | UNIFORM vec2 imageSize; 297 | UNIFORM vec2 mousePos; 298 | UNIFORM float shownTime; 299 | 300 | UNIFORM vec2 fogRange; 301 | UNIFORM float fogRainEnabled; 302 | UNIFORM vec3 fogColor; 303 | UNIFORM vec2 dofRange; 304 | UNIFORM vec3 ambientLight; 305 | UNIFORM vec4 spriteArea; 306 | UNIFORM float spriteDepth; 307 | UNIFORM float shadowStrength; 308 | 309 | UNIFORM mat4 lights[MAX_LIGHTS]; 310 | UNIFORM float lightCount; 311 | 312 | const float minLightness = 0.25; 313 | const float sunOffsetZ = -0.5; 314 | 315 | vec2 readDepthAlpha(vec2 uv) { 316 | vec4 depth = texture2D(depthMap, uv); 317 | return vec2((depth.r + depth.g + depth.b) / 3.0, depth.a); 318 | } 319 | 320 | float remap(float value, float oldMin, float oldMax, float newMin, float newMax) { 321 | return (((value - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin; 322 | } 323 | 324 | bool isPixelComposited(vec3 pos) { 325 | if (pos.z >= spriteDepth && spriteDepth > 0.0) { 326 | if (pos.x > spriteArea.x && pos.x < spriteArea.y && pos.y > spriteArea.z && pos.y < spriteArea.w) { 327 | return true; 328 | } 329 | } 330 | return false; 331 | } 332 | 333 | vec4 pixelCompositeLookup(vec2 uv) { 334 | float u = remap(uv.x, spriteArea.x, spriteArea.y, 0.0, 1.0); 335 | float v = remap(uv.y, spriteArea.z, spriteArea.w, 0.0, 1.0); 336 | return texture2D(sprite, vec2(u, v)); 337 | } 338 | 339 | vec4 zComposite(vec3 pos, vec4 color, vec2 uv) { 340 | if (isPixelComposited(pos)) { 341 | vec4 diffuse = pixelCompositeLookup(uv); 342 | return mix(color, vec4(diffuse.rgb, 1.0), diffuse.a); 343 | } 344 | return color; 345 | } 346 | 347 | vec4 pixelPosRaw(vec2 uv) { 348 | vec2 za = readDepthAlpha(uv); 349 | return vec4(uv, za.x, za.y); 350 | } 351 | 352 | vec4 pixelPos(vec2 uv) { 353 | vec4 pos = pixelPosRaw(uv); 354 | if (isPixelComposited(pos.xyz) && pixelCompositeLookup(uv).a > 0.1) { 355 | return vec4(uv, spriteDepth, 1.0); 356 | } 357 | return pos; 358 | } 359 | 360 | vec3 pixelNormal(vec3 pos) { 361 | if (isPixelComposited(pos) && pos.z == spriteDepth) { 362 | return normalize(vec3(0.0, 0.0, 1.0)); 363 | } 364 | 365 | vec3 normal = texture2D(normalMap, pos.xy).rgb; 366 | return normalize(vec3( 367 | (normal.x * 2.0 - 1.0), 368 | (normal.y * 2.0 - 1.0), 369 | (normal.z * 2.0 - 0.0) //Z is only half 370 | )); 371 | } 372 | 373 | vec3 unproject(vec3 pos) { 374 | vec4 screen; 375 | screen.xy = pos.xy * 2.0 - 1.0; 376 | screen.z = pos.z; 377 | screen.w = 1.0; 378 | 379 | screen.x *= imageSize.x / imageSize.y; 380 | 381 | //vec4 world = cameraMatrix * screen; 382 | //world /= world.w; 383 | //return world.xyz; 384 | 385 | return screen.xyz; 386 | } 387 | 388 | vec3 pointLight(vec3 pos, vec3 normal, vec3 lightPos, vec3 color, float dist) { 389 | vec3 light = pixelPosRaw(lightPos.xy).xyz; 390 | light.z += lightPos.z; 391 | 392 | vec3 posWorld = unproject(pos); 393 | vec3 lightWorld = unproject(light); 394 | 395 | vec3 delta = normalize(posWorld - lightWorld); 396 | delta.x = -delta.x; 397 | 398 | float strength = max(dot(normal, delta), 0.1); 399 | float att = dist < 0.0 ? 1.0 : max(1.0 - (distance(posWorld, lightWorld) * 1.0), 0.0); 400 | //float att = 1.0 - min( distance(posWorld, lightWorld) / dist, 1.0); 401 | 402 | return color * (strength * att); 403 | } 404 | 405 | float rand(vec2 co){ 406 | return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); 407 | } 408 | 409 | float shadow(vec3 pos, float alpha, vec3 light, float seed) { 410 | //light.z += sunOffsetZ * 0.1; 411 | light.z -= 0.1; 412 | 413 | const int steps = 10; 414 | float stepJitter = rand(pos.xy + vec2(seed, seed * seed)) * 1.0; 415 | 416 | for (int i=0; i < steps; ++i) { 417 | vec3 step = mix(pos, light, i / float(steps) * stepJitter); 418 | 419 | float jitter = rand(step.xy + pos.xy) * (step.z * 0.005); 420 | step.x += jitter; 421 | step.y += jitter; 422 | 423 | vec4 stepPos = pixelPos(step.xy); 424 | stepPos.z *= 1.05; 425 | float bias = rand(step.xy) * 0.01; //0; 426 | float finalZ = step.z - bias; 427 | 428 | if (finalZ > stepPos.z) { 429 | return 1.0 - max(alpha, 0.1) * shadowStrength; 430 | } 431 | } 432 | return 1.0; 433 | } 434 | 435 | float fog(vec3 pos, float alpha, float start, float end) { 436 | float depth = pos.z - (alpha * 0.1); //Window rain etc. better 437 | float f = 1.0 - clamp((end - depth) / (end - start), 0.0, 1.0); 438 | 439 | if (fogRainEnabled != 0.0) { 440 | return f * (rand(vec2(pos.x * depth, shownTime)) * 0.2 + 1.0); 441 | } 442 | return f; 443 | } 444 | 445 | vec4 depthOfField(vec4 data) { 446 | vec2 uv = data.xy; 447 | float focus = dofRange.x; // pixelPos(mousePos).z; 448 | if (focus > 0.0) { 449 | float depth = 1.0; 450 | float distance = abs((focus - data.z) * depth) * 2.5; 451 | 452 | vec2 offset = vec2(2.0 / imageSize.x * distance, 2.0 / imageSize.y * distance); 453 | vec4 color = texture2D(tex0, uv); 454 | color += texture2D(tex0, uv + offset * vec2(0, 1)); 455 | color += texture2D(tex0, uv + offset * vec2(-1, -1)); 456 | color += texture2D(tex0, uv + offset * vec2(1, -1)); 457 | return color / 4.0; 458 | } 459 | else { 460 | return texture2D(tex0, uv); 461 | } 462 | } 463 | 464 | void main() 465 | { 466 | vec4 data = pixelPos(varUv); 467 | vec3 normal = pixelNormal(data.xyz); 468 | vec4 color = depthOfField(data); 469 | vec3 pos = data.xyz; 470 | float alpha = data.w; 471 | 472 | if (spriteDepth >= 0.0) { 473 | color.rgba = zComposite(pos, color, varUv).rgba; 474 | } 475 | 476 | vec3 light = ambientLight; 477 | for (int i=0; i < lightCount; ++i) { 478 | mat4 lightData = lights[i]; 479 | vec3 lPos = vec3(lightData[0][0], lightData[1][0], lightData[2][0]); 480 | vec3 lColor = vec3(lightData[0][1], lightData[1][1], lightData[2][1]); 481 | float dist = lightData[0][2]; 482 | light.rgb += pointLight(pos, normal, lPos, lColor, dist); 483 | } 484 | 485 | if (shadowStrength > 0.0) { 486 | vec3 mouse = pixelPos(mousePos).xyz; 487 | float w = 2.0 / imageSize.x; 488 | float h = 2.0 / imageSize.y; 489 | 490 | float strength = 1.0; 491 | strength *= shadow(pos, alpha, mouse, 0); 492 | strength *= shadow(pos, alpha, mouse + vec3(w, 0.0, 0.0), 1); 493 | strength *= shadow(pos, alpha, mouse + vec3(0.0, h, 0.0), 2); 494 | strength *= shadow(pos, alpha, mouse + vec3(w, h, 0.0), 3); 495 | color.rgb *= strength; 496 | } 497 | 498 | vec4 result = color * vec4(light.rgb, 1.0); 499 | 500 | if (abs(fogRange.x - fogRange.y) > 0.0) { 501 | result.rgb = mix(result.rgb, light * fogColor, fog(pos, alpha, fogRange.x, fogRange.y)); 502 | } 503 | 504 | /* 505 | if (pos.z >= 0.5 && pos.z <= 0.51) { 506 | result.rgb += vec3(1.0, 0, 0); 507 | }*/ 508 | 509 | gl_FragColor = result; 510 | } 511 | 512 | """.replace("MAX_LIGHTS", str(DEFERRED_MAX_LIGHTS)) 513 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/shaderdisplayable.rpy: -------------------------------------------------------------------------------- 1 | 2 | init python: 3 | import time 4 | import pygame 5 | import shader 6 | from OpenGL import GL as gl 7 | 8 | if persistent.shader_effects_enabled is None: 9 | persistent.shader_effects_enabled = True 10 | 11 | def _interactCallback(): 12 | shader._controllerContextStore.checkDisplayableVisibility(ShaderDisplayable) 13 | 14 | def _initContextCallbacks(): 15 | shader._setupRenpyHooks() 16 | if not _interactCallback in renpy.config.interact_callbacks: 17 | renpy.config.interact_callbacks.append(_interactCallback) 18 | 19 | class ShaderDisplayable(renpy.Displayable): 20 | def __init__(self, mode, image, vertexShader, pixelShader, textures=None, uniforms=None, create=None, update=None, args=None, **properties): 21 | super(ShaderDisplayable, self).__init__(**properties) 22 | 23 | self.mode = mode 24 | self.image = renpy.displayable(image) 25 | self.vertexShader = vertexShader 26 | self.pixelShader = pixelShader 27 | self.textures = textures 28 | self.uniforms = uniforms 29 | self.createCallback = create 30 | self.updateCallback = update 31 | self.tag = mode + "/" + image + "/" + vertexShader + "/" + pixelShader + "/" + str(textures) + "/" + str(uniforms) + "/" + str(args) 32 | self.args = args or {} 33 | 34 | self.events = [] 35 | 36 | if not renpy.predicting(): 37 | _initContextCallbacks() 38 | 39 | def getContext(self): 40 | return shader._controllerContextStore.get(self.tag) 41 | 42 | def setController(self, controller): 43 | context = self.getContext() 44 | context.freeController() 45 | context.persist = self.args.get("persist") 46 | context.controller = controller 47 | context.createCalled = False 48 | if controller: 49 | context.updateModeChangeCount() 50 | 51 | def createController(self): 52 | renderer = None 53 | if self.mode == shader.MODE_2D: 54 | renderer = shader.Renderer2D() 55 | renderer.init(self.image, self.vertexShader, self.pixelShader) 56 | elif self.mode == shader.MODE_3D: 57 | w, h = renpy.display.im.load_surface(self.image).get_size() 58 | renderer = shader.Renderer3D() 59 | renderer.init(self.vertexShader, self.pixelShader, w, h) 60 | elif self.mode == shader.MODE_SKINNED: 61 | renderer = shader.SkinnedRenderer() 62 | renderer.init(self.image, self.vertexShader, self.pixelShader, self.args) 63 | else: 64 | raise RuntimeError("Unknown mode: %s" % self.mode) 65 | 66 | renderController = shader.RenderController() 67 | renderController.init(renderer) 68 | 69 | return renderController 70 | 71 | def freeController(self): 72 | self.setController(None) 73 | 74 | def resetController(self): 75 | self.freeController() 76 | 77 | try: 78 | controller = self.createController() 79 | 80 | if self.textures: 81 | for sampler, name in self.textures.items(): 82 | controller.renderer.setTexture(sampler, renpy.displayable(name)) 83 | 84 | self.setController(controller) 85 | except gl.GLError as e: 86 | #Try again later 87 | shader.log("Render controller reset error: %s" % e) 88 | 89 | def checkModeChangeCount(self): 90 | if self.getContext().modeChangeCount != shader.getModeChangeCount(): 91 | self.resetController() 92 | 93 | def checkOpenGLState(self): 94 | oldError = gl.glGetError() #Get and clear error flag 95 | if oldError != gl.GL_NO_ERROR and config.developer: 96 | #This is not vital, but if RenPy (or us) has previously caused an error state 97 | #this will let us know about it so we can investigate more. Also otherwise 98 | #it will be automatically converted into an exception which we don't want. 99 | shader.log("Unknown developer mode OpenGL error: %s" % oldError) 100 | 101 | def render(self, width, height, st, at): 102 | result = None 103 | 104 | if persistent.shader_effects_enabled and not renpy.predicting() and shader.isSupported(): 105 | self.checkOpenGLState() 106 | 107 | context = self.getContext() 108 | if not context.controller: 109 | self.resetController() 110 | 111 | self.checkModeChangeCount() 112 | 113 | context = self.getContext() 114 | if context.controller: 115 | controller = context.controller 116 | 117 | renderWidth, renderHeight = controller.getSize() 118 | result = renpy.Render(renderWidth, renderHeight) 119 | canvas = result.canvas() #TODO Slow, allocates one surface every time... 120 | surface = canvas.get_surface() 121 | 122 | uniforms = { 123 | "shownTime": st, 124 | "animationTime": at, 125 | "mousePos": self.screenToTexture(context.mousePos, width, height), 126 | } 127 | if self.uniforms: 128 | uniforms.update(self.uniforms) 129 | 130 | overlayRender = renpy.Render(renderWidth, renderHeight) 131 | renderContext = shader.RenderContext(controller.renderer, 132 | renderWidth, renderHeight, time.time(), st, at, uniforms, 133 | context.mousePos, self.events, context.contextStore, overlayRender) 134 | 135 | self.events = [] 136 | 137 | if self.createCallback and not context.createCalled: 138 | context.createCalled = True 139 | self.createCallback(renderContext) 140 | 141 | if self.updateCallback: 142 | self.updateCallback(renderContext) 143 | 144 | if renderContext.overlayCanvas: 145 | #Overlay canvas was created and used 146 | result.blit(overlayRender, (0, 0)) 147 | 148 | continueRendering = renderContext.continueRendering 149 | 150 | try: 151 | controller.renderImage(renderContext) 152 | controller.copyRenderBufferToSurface(surface) 153 | except gl.GLError as e: 154 | shader.log("Render controller render error: %s" % e) 155 | #Free controller and try again later 156 | self.freeController() 157 | continueRendering = False 158 | result = None 159 | 160 | if continueRendering: 161 | renpy.redraw(self, 1.0 / shader.config.fps) 162 | 163 | if not result: 164 | #Original image 165 | result = renpy.render(self.image, width, height, st, at) 166 | 167 | return result 168 | 169 | def screenToTexture(self, pos, width, height): 170 | if pos: 171 | #Only makes sense with untransformed fullscreen images... 172 | return (pos[0] / float(width), pos[1] / float(height)) 173 | return (0.0, 0.0) 174 | 175 | def event(self, ev, x, y, st): 176 | self.events.append((ev, (x, y))) 177 | while len(self.events) > 100: 178 | #Too many, remove oldest 179 | self.events.pop(0) 180 | 181 | if ev.type == pygame.MOUSEMOTION or ev.type == pygame.MOUSEBUTTONDOWN or ev.type == pygame.MOUSEBUTTONUP: 182 | self.getContext().mousePos = (x, y) 183 | 184 | def visit(self): 185 | return [self.image] 186 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/skin.py: -------------------------------------------------------------------------------- 1 | 2 | import ctypes 3 | import json 4 | from OpenGL import GL as gl 5 | 6 | import euclid 7 | import geometry 8 | import delaunay 9 | import skinnedmesh 10 | import utils 11 | 12 | VERSION = 1 13 | MAX_BONES = 64 14 | 15 | def makeArray(tp, values): 16 | return (tp * len(values))(*values) 17 | 18 | class SkinnedImage: 19 | jsonIgnore = [] 20 | 21 | def __init__(self, name, x, y, width, height, originalWidth, originalHeight): 22 | self.name = name 23 | self.x = x 24 | self.y = y 25 | self.width = width 26 | self.height = height 27 | self.originalWidth = originalWidth 28 | self.originalHeight = originalHeight 29 | 30 | class SkinningBone: 31 | jsonIgnore = ["wireFrame"] 32 | 33 | def __init__(self, name): 34 | self.name = name 35 | self.children = [] 36 | self.parent = None 37 | self.image = None 38 | self.pos = (0, 0) 39 | self.pivot = (0, 0) 40 | self.translation = euclid.Vector3(0.0, 0.0, 0.0) 41 | self.rotation = euclid.Vector3(0.0, 0.0, 0.0) 42 | self.scale = euclid.Vector3(1.0, 1.0, 1.0) 43 | self.zOrder = -1 44 | self.visible = True 45 | self.wireFrame = False 46 | self.blocker = False 47 | self.tessellate = False 48 | self.transparency = 0.0 49 | self.damping = 0.0 50 | self.points = [] 51 | self.mesh = None 52 | 53 | def getAllChildren(self, bones, results=None): 54 | if not results: 55 | results = [] 56 | 57 | for name in self.children: 58 | child = bones[name] 59 | results.append(child) 60 | child.getAllChildren(bones, results) 61 | return results 62 | 63 | def getParents(self, bones): 64 | parents = [] 65 | parent = self.parent 66 | while parent: 67 | bone = bones[parent] 68 | parents.append(bone) 69 | parent = bone.parent 70 | return parents 71 | 72 | def walkChildren(self, bones, func, args): 73 | for name in self.children: 74 | child = bones[name] 75 | if func(child, *args): 76 | child.walkChildren(bones, func, args) 77 | 78 | def walkParents(self, bones, func, args): 79 | if self.parent: 80 | parent = bones[self.parent] 81 | if func(parent, *args): 82 | parent.walkParents(bones, func, args) 83 | 84 | def updatePoints(self, surface, pointSimplify): 85 | points = geometry.findEdgePixelsOrdered(surface) 86 | simplified = geometry.simplifyEdgePixels(points, pointSimplify) 87 | self.points = geometry.offsetPolygon(simplified, -5) #TODO Increase this once better weighting is in? 88 | 89 | def triangulatePoints(self, gridResolution): 90 | points = self.points[:] 91 | if gridResolution > 0: 92 | verts, uvs, indices = geometry.createGrid((0, 0, self.image.width, self.image.height), gridResolution, gridResolution) 93 | points.extend(verts) 94 | 95 | pointsSegments = delaunay.ToPointsAndSegments() 96 | pointsSegments.add_polygon([points]) 97 | triangulation = delaunay.triangulate(pointsSegments.points, pointsSegments.infos, pointsSegments.segments) 98 | 99 | triangles = [] 100 | for tri in delaunay.TriangleIterator(triangulation, True): 101 | a, b, c = tri.vertices 102 | centroid = geometry.triangleCentroid(a, b, c) 103 | if geometry.insidePolygon(centroid[0], centroid[1], self.points): 104 | triangles.append(((a[0], a[1]), (b[0], b[1]), (c[0], c[1]))) 105 | 106 | return triangles 107 | 108 | def updateMeshFromTriangles(self, triangles): 109 | MERGE_VERTICES = True 110 | 111 | duplicates = {} 112 | verts = [] 113 | indices = [] 114 | for tri in triangles: 115 | for v in tri: 116 | #Consider vertices within one pixel identical 117 | v = (int(round(v[0])), int(round(v[1]))) 118 | if v in duplicates and MERGE_VERTICES: 119 | indices.append(duplicates[v]) 120 | else: 121 | verts.extend([v[0], v[1]]) 122 | index = len(verts) / 2 - 1 123 | indices.append(index) 124 | duplicates[v] = index 125 | 126 | if len(indices) % 3 != 0: 127 | raise RuntimeError("Invalid index count: %i" % len(indices)) 128 | 129 | self.mesh = skinnedmesh.SkinnedMesh(verts, indices) 130 | 131 | JSON_IGNORES = [] 132 | 133 | class JsonEncoder(json.JSONEncoder): 134 | def default(self, obj): 135 | if isinstance(obj, (SkinningBone, SkinnedImage, skinnedmesh.SkinnedMesh)): 136 | d = obj.__dict__.copy() 137 | for ignore in JSON_IGNORES + getattr(obj, "jsonIgnore", []): 138 | if ignore in d: 139 | del d[ignore] 140 | return d 141 | elif isinstance(obj, euclid.Vector3): 142 | return (obj.x, obj.y, obj.z) 143 | elif isinstance(obj, ctypes.Array): 144 | return list(obj) 145 | return json.JSONEncoder.default(self, obj) 146 | 147 | def saveToFile(context, bones, path): 148 | size = context.renderer.getSize() 149 | data = { 150 | "version": VERSION, 151 | "bones": bones, 152 | "width": size[0], 153 | "height": size[1], 154 | } 155 | 156 | with open(path, "w") as f: 157 | json.dump(data, f, indent=1, cls=JsonEncoder, separators=(",", ": "), sort_keys=True) 158 | 159 | def _getArray(tp, obj, key): 160 | data = obj.get(key) 161 | if data: 162 | return makeArray(tp, data) 163 | return None 164 | 165 | def loadFromFile(path): 166 | data = None 167 | with utils.openFile(path) as f: 168 | data = json.load(f) 169 | 170 | if data["version"] != VERSION: 171 | raise RuntimeError("Incompatible file format version, should be %i" % VERSION) 172 | 173 | bones = {} 174 | for name, raw in data["bones"].items(): 175 | bone = SkinningBone(raw["name"]) 176 | bone.children = raw["children"] 177 | bone.parent = raw["parent"] 178 | 179 | image = raw.get("image") 180 | if image: 181 | bone.image = SkinnedImage(image["name"], image["x"], image["y"], 182 | image["width"], image["height"], image["originalWidth"], image["originalHeight"]) 183 | 184 | bone.pos = raw["pos"] 185 | bone.pivot = raw["pivot"] 186 | bone.translation = euclid.Vector3(*raw["translation"]) 187 | bone.rotation = euclid.Vector3(*raw["rotation"]) 188 | bone.scale = euclid.Vector3(*raw["scale"]) 189 | bone.zOrder = raw["zOrder"] 190 | bone.visible = raw["visible"] 191 | bone.blocker = raw["blocker"] 192 | bone.tessellate = raw["tessellate"] 193 | bone.transparency = raw["transparency"] 194 | bone.damping = raw["damping"] 195 | bone.points = [tuple(p) for p in raw["points"]] 196 | 197 | mesh = raw.get("mesh") 198 | if mesh: 199 | vertices = _getArray(gl.GLfloat, mesh, "vertices") 200 | indices = _getArray(gl.GLuint, mesh, "indices") 201 | boneWeights = _getArray(gl.GLfloat, mesh, "boneWeights") 202 | boneIndices = _getArray(gl.GLfloat, mesh, "boneIndices") 203 | bone.mesh = skinnedmesh.SkinnedMesh(vertices, indices, boneWeights, boneIndices) 204 | 205 | bones[bone.name] = bone 206 | 207 | return bones, data 208 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/skinnedmesh.py: -------------------------------------------------------------------------------- 1 | 2 | from OpenGL import GL as gl 3 | 4 | import geometry 5 | import utils 6 | 7 | def makeArray(tp, values): 8 | return (tp * len(values))(*values) 9 | 10 | def roundPoint(x, y): 11 | return (int(round(x)), int(round(y))) 12 | 13 | class SkinnedMesh: 14 | jsonIgnore = ["uvs"] 15 | 16 | def __init__(self, vertices, indices, boneWeights=None, boneIndices=None): 17 | self.setGeometry(vertices, indices) 18 | self.boneWeights = boneWeights 19 | self.boneIndices = boneIndices 20 | 21 | def setGeometry(self, verts, indices): 22 | self.vertices = makeArray(gl.GLfloat, verts) 23 | self.indices = makeArray(gl.GLuint, indices) 24 | self.uvs = None 25 | self.boneWeights = None 26 | self.boneIndices = None 27 | 28 | def getTriangleIndices(self): 29 | triangles = [] 30 | if self.indices: 31 | for i in range(0, len(self.indices), 3): 32 | triangles.append((self.indices[i], self.indices[i + 1], self.indices[i + 2])) 33 | return triangles 34 | 35 | def getIndexAdjacency(self, tris): 36 | adjacency = {} 37 | for i, tri in enumerate(tris): 38 | for i2, tri2 in enumerate(tris): 39 | if i != i2: 40 | for index in tri: 41 | if index in tri2: 42 | adj = adjacency.get(index, []) 43 | adj.append((tri2, i2)) 44 | adjacency[index] = adj 45 | return adjacency 46 | 47 | def getTriangleAdjacency(self, tris): 48 | adjacency = {} 49 | for i, tri in enumerate(tris): 50 | for i2, tri2 in enumerate(tris): 51 | if i != i2: 52 | a, b, c = tri 53 | for edge in ((a, b), (b, c), (c, a)): 54 | if edge[0] in tri2 and edge[1] in tri2: 55 | adj = adjacency.get(i, []) 56 | adj.append(i2) 57 | adjacency[i] = adj 58 | break 59 | return adjacency 60 | 61 | def getVertexCount(self): 62 | return len(self.vertices) // 2 63 | 64 | def getVertex(self, index): 65 | return (self.vertices[index * 2], self.vertices[index * 2 + 1]) 66 | 67 | def subdivideAdaptive(self, transforms): 68 | verts = self.vertices[:] 69 | indices = self.indices[:] 70 | tris = self.getTriangleIndices() 71 | adjacency = self.getTriangleAdjacency(tris) 72 | 73 | mapping = {} 74 | for trans in transforms: 75 | mapping[trans.bone.name] = trans 76 | 77 | subivisions = {} 78 | 79 | for trans in transforms: 80 | bone = trans.bone 81 | if bone.parent and bone.tessellate: 82 | for i, (a, b, c) in enumerate(tris): 83 | v1 = self.getVertex(a) 84 | v2 = self.getVertex(b) 85 | v3 = self.getVertex(c) 86 | if geometry.pointInTriangle(bone.pivot, v1, v2, v3): 87 | subivisions[(a, b, c)] = (a, b, c, verts, indices, i) 88 | 89 | for triIndex in adjacency.get(i, []): 90 | n = tris[triIndex] 91 | subivisions[n] = (n[0], n[1], n[2], verts, indices, triIndex) 92 | 93 | for sub in subivisions.values(): 94 | self.subdivideTriangle(*sub) 95 | 96 | self.setGeometry(verts, [x for x in indices if x is not None]) 97 | 98 | def subdivide(self, maxSize): 99 | verts = self.vertices[:] 100 | indices = self.indices[:] 101 | 102 | for i, (a, b, c) in enumerate(self.getTriangleIndices()): 103 | v1 = self.getVertex(a) 104 | v2 = self.getVertex(b) 105 | v3 = self.getVertex(c) 106 | area = geometry.triangleArea(v1, v2, v3) 107 | if area > maxSize: #TODO Also if triangle has a too long side 108 | self.subdivideTriangle(a, b, c, verts, indices, i) 109 | 110 | self.setGeometry(verts, [x for x in indices if x is not None]) 111 | 112 | def subdivideTriangle(self, a, b, c, verts, indices, index): 113 | v1 = self.getVertex(a) 114 | v2 = self.getVertex(b) 115 | v3 = self.getVertex(c) 116 | 117 | new1 = geometry.interpolate2d(v1, v2, 0.5) 118 | new2 = geometry.interpolate2d(v2, v3, 0.5) 119 | new3 = geometry.interpolate2d(v3, v1, 0.5) 120 | 121 | indices[index * 3] = None 122 | indices[index * 3 + 1] = None 123 | indices[index * 3 + 2] = None 124 | 125 | for v in [new1, new2, new3]: 126 | verts.extend(v) 127 | 128 | vertexCount = len(verts) // 2 129 | d = vertexCount - 3 130 | e = vertexCount - 2 131 | f = vertexCount - 1 132 | 133 | indices.extend([d, e, f]) 134 | indices.extend([a, d, f]) 135 | indices.extend([d, b, e]) 136 | indices.extend([f, e, c]) 137 | 138 | def splitEdge(self, a, b, tri, verts, indices, index): 139 | v1 = self.getVertex(a) 140 | v2 = self.getVertex(b) 141 | 142 | new1 = geometry.interpolate2d(v1, v2, 0.5) 143 | 144 | indices[index * 3] = None 145 | indices[index * 3 + 1] = None 146 | indices[index * 3 + 2] = None 147 | 148 | verts.extend(new1) 149 | 150 | c = (len(verts) // 2) - 1 151 | d = set(tri).difference((a, b)).pop() 152 | 153 | indices.extend([d, a, c]) 154 | indices.extend([d, b, c]) 155 | 156 | def fixTJunctions(self): 157 | tris = self.getTriangleIndices() 158 | indexAdj = self.getIndexAdjacency(tris) 159 | 160 | split = {} 161 | for i, (a, b, c) in enumerate(tris): 162 | for edge in ((a, b), (b, c), (c, a)): 163 | start = self.getVertex(edge[0]) 164 | end = self.getVertex(edge[1]) 165 | if geometry.pointDistance(start, end) > 0.0: 166 | for tri, index in indexAdj.get(edge[0], []): 167 | for n in tri: 168 | v2 = self.getVertex(n) 169 | if geometry.pointDistance(start, v2) > 1.0 and geometry.pointDistance(end, v2) > 1.0: 170 | edgeDist = geometry.pointToLineDistance(v2, start, end) 171 | if edgeDist < 1.0: 172 | #Vertex is overlapping this edge 173 | splitting = split.get(i, set()) 174 | splitting.add(edge) 175 | split[i] = splitting 176 | 177 | verts = self.vertices[:] 178 | indices = self.indices[:] 179 | for index, edges in split.items(): 180 | edges = list(edges) 181 | tri = tris[index] 182 | if len(edges) == 1: 183 | self.splitEdge(edges[0][0], edges[0][1], tri, verts, indices, index) 184 | elif len(edges) > 1: 185 | #TODO This can create new edges to be splitted... 186 | self.subdivideTriangle(tri[0], tri[1], tri[2], verts, indices, index) 187 | 188 | self.setGeometry(verts, [x for x in indices if x is not None]) 189 | 190 | def weldVertices(self): 191 | duplicates = {} 192 | verts = [] 193 | indices = [] 194 | 195 | for i in range(self.getVertexCount()): 196 | v = self.getVertex(i) 197 | v = roundPoint(v[0], v[1]) 198 | if not v in duplicates: 199 | duplicates[v] = len(verts) // 2 200 | verts.extend(v) 201 | 202 | for tri in self.getTriangleIndices(): 203 | for i in tri: 204 | v = self.getVertex(i) 205 | newIndex = duplicates[roundPoint(v[0], v[1])] 206 | indices.append(newIndex) 207 | 208 | self.setGeometry(verts, indices) 209 | 210 | def sortTriangles(self, transforms): 211 | triangles = [] 212 | for a, b, c in self.getTriangleIndices(): 213 | zSum = 0.0 214 | for i in (a, b, c): 215 | for x in range(4): 216 | index = i * 4 + x 217 | boneIndex = int(self.boneIndices[index]) 218 | boneWeight = self.boneWeights[index] 219 | zSum += transforms[boneIndex].bone.zOrder * boneWeight 220 | triangles.append((zSum / (3.0 * 4.0), a, b, c)) 221 | triangles.sort(key=lambda b: b[0]) 222 | 223 | indices = [] 224 | for tri in triangles: 225 | indices.extend(tri[1:]) 226 | 227 | self.indices = makeArray(gl.GLuint, indices) 228 | 229 | def updateUvs(self, bone): 230 | w = bone.image.width 231 | h = bone.image.height 232 | uvs = [] 233 | for i in range(0, len(self.vertices), 2): 234 | xUv = (self.vertices[i] - bone.pos[0]) / float(w) 235 | yUv = (self.vertices[i + 1] - bone.pos[1]) / float(h) 236 | uvs.extend([xUv, yUv]) 237 | self.uvs = makeArray(gl.GLfloat, uvs) 238 | 239 | def moveVertices(self, offset): 240 | for i in range(0, len(self.vertices), 2): 241 | self.vertices[i] = self.vertices[i] + offset[0] 242 | self.vertices[i + 1] = self.vertices[i + 1] + offset[1] 243 | 244 | def updateVertexWeights(self, index, transforms, bones): 245 | mapping = {} 246 | for i, trans in enumerate(transforms): 247 | trans.index = i 248 | mapping[trans.bone.name] = trans 249 | 250 | blockers = findBlockerNames(transforms[index].bone, bones) 251 | 252 | weights = [] 253 | indices = [] 254 | for i in range(0, len(self.vertices), 2): 255 | x = self.vertices[i] 256 | y = self.vertices[i + 1] 257 | 258 | nearby = findBoneInfluences((x, y), mapping, blockers) 259 | if len(nearby) > 0: 260 | for x in range(4): 261 | if x < len(nearby): 262 | weights.append(nearby[x].weight) 263 | indices.append(float(nearby[x].index)) 264 | else: 265 | weights.append(0.0) 266 | indices.append(0.0) 267 | else: 268 | weights.extend([1.0, 0.0, 0.0, 0.0]) 269 | indices.extend([float(index), 0.0, 0.0, 0.0]) 270 | 271 | self.boneWeights = makeArray(gl.GLfloat, weights) 272 | self.boneIndices = makeArray(gl.GLfloat, indices) 273 | 274 | def findBoneImageBone(bone, bones): 275 | for parent in [bone] + bone.getParents(bones): 276 | if parent.image: 277 | return parent 278 | return None 279 | 280 | def blockerFunc(bone, results): 281 | if bone.blocker: 282 | results.add(bone.name) 283 | return False 284 | return True 285 | 286 | def findBlockerNames(meshBone, bones): 287 | blockers = set() 288 | meshBone.walkChildren(bones, blockerFunc, (blockers,)) 289 | 290 | results = set() 291 | for name in blockers: 292 | blocker = bones[name] 293 | imageBone = findBoneImageBone(blocker, bones) 294 | children = blocker.getAllChildren(bones) 295 | if imageBone.name != meshBone.name: 296 | for child in children: 297 | results.add(child.name) 298 | else: 299 | names = set([b.name for b in children]) 300 | for bone in bones.values(): 301 | if bone.name not in names: 302 | results.add(bone.name) 303 | 304 | return results 305 | 306 | class BoneWeight: 307 | def __init__(self, distance, index, transform): 308 | self.distance = distance 309 | self.index = index 310 | self.transform = transform 311 | self.bone = transform.bone 312 | self.weight = 0.0 313 | 314 | #Shorten bones, otherwise their points can be at the same position which 315 | #can make weight calculation random and order-dependant. 316 | SHORTEN_LINE = 0.95 317 | 318 | def findBoneInfluences(vertex, transforms, blockers): 319 | weights = [] 320 | nearest = findNearestBone(vertex, transforms, blockers) 321 | if nearest: 322 | nearest.weight = calculateWeight(vertex, nearest.transform, transforms[nearest.bone.parent]) 323 | weights.append(nearest) 324 | 325 | parent = transforms[nearest.bone.parent] 326 | weights.append(BoneWeight(-1, parent.index, parent)) 327 | weights[-1].weight = 1.0 - sum([w.weight for w in weights]) 328 | 329 | weights.sort(key=lambda w: -w.weight) 330 | return weights[:4] 331 | 332 | def calculateWeight(vertex, a, b, bendyLength=0.75): 333 | minWeight = 0.0 334 | maxWeight = 0.1 335 | 336 | distance = geometry.pointDistance(vertex, a.bone.pivot) 337 | boneLength = geometry.pointDistance(a.bone.pivot, b.bone.pivot) * bendyLength 338 | if boneLength == 0: 339 | return 0 340 | vertexDistance = min(distance / boneLength, 1.0) 341 | return utils.clamp(1 - vertexDistance, minWeight, maxWeight) 342 | 343 | def findNearestBone(vertex, transforms, blockers): 344 | nearest = None 345 | minDistance = None 346 | 347 | for trans in transforms.values(): 348 | if not trans.bone.parent or not transforms[trans.bone.parent].bone.parent: 349 | #Skip root bones 350 | continue 351 | if trans.bone.name in blockers: 352 | continue 353 | 354 | distance = pointToBoneDistance(vertex, trans.bone, transforms) 355 | if minDistance is None or distance < minDistance: 356 | minDistance = distance 357 | nearest = BoneWeight(distance, trans.index, trans) 358 | 359 | return nearest 360 | 361 | def pointToBoneDistance(point, bone, transforms): 362 | return pointToShortenedLineDistance(point, bone.pivot, transforms[bone.parent].bone.pivot, SHORTEN_LINE) 363 | 364 | def pointToShortenedLineDistance(point, start, end, shorten): 365 | startShort, endShort = geometry.shortenLine(start, end, shorten) 366 | return geometry.pointToLineDistance(point, startShort, endShort) 367 | 368 | -------------------------------------------------------------------------------- /ShaderDemo/game/shader/skinnedplayer.py: -------------------------------------------------------------------------------- 1 | 2 | import skin 3 | import skinnedanimation 4 | import euclid 5 | import utils 6 | 7 | class TrackInfo: 8 | def __init__(self, name, repeat=False, cyclic=False, reverse=False, autoEnd=False, 9 | clip=False, weight=1.0, speed=1.0, fps=30, easingOverride=None): 10 | self.name = name 11 | self.repeat = repeat 12 | self.cyclic = cyclic 13 | self.reverse = reverse 14 | self.autoEnd = autoEnd 15 | self.clip = clip 16 | self.weight = weight 17 | self.speed = speed 18 | self.fps = float(fps) 19 | self.easingOverride = easingOverride 20 | 21 | class Track: 22 | def __init__(self, info, startTime): 23 | self.info = info 24 | self.startTime = startTime 25 | self.animation = skinnedanimation.loadAnimationFromFile(utils.findFile(info.name)) 26 | if self.info.clip: 27 | self.animation.clipEnd() 28 | 29 | def getFrameIndex(self, currentTime): 30 | delta = (currentTime - self.startTime) * self.info.speed 31 | return int(round(delta * self.info.fps)) 32 | 33 | def getFrameIndexClamped(self, currentTime): 34 | index = self.getFrameIndex(currentTime) 35 | frameCount = len(self.animation.frames) 36 | return min(frameCount - 1, index) 37 | 38 | def getFrameIndexRepeat(self, currentTime): 39 | index = self.getFrameIndex(currentTime) 40 | return index % len(self.animation.frames) 41 | 42 | def getFrameIndexCyclic(self, currentTime): 43 | index = self.getFrameIndex(currentTime) 44 | frameCount = len(self.animation.frames) 45 | reversing = (index // frameCount) % 2 46 | realIndex = index % frameCount 47 | if reversing: 48 | return (frameCount - 1) - realIndex 49 | else: 50 | return realIndex 51 | 52 | def isAtEnd(self, currentTime): 53 | index = self.getFrameIndexClamped(currentTime) 54 | lastFrame = len(self.animation.frames) - 1 55 | return index >= lastFrame 56 | 57 | class AnimationData: 58 | def __init__(self): 59 | self.tracks = {} 60 | 61 | class AnimationPlayer: 62 | def __init__(self, context, tag, reset=False): 63 | self.context = context 64 | self.tag = tag 65 | self.debug = False 66 | self.debugY = 10 67 | 68 | fullTag = "animationPlayer-" + tag 69 | if reset: 70 | self.data = AnimationData() 71 | else: 72 | self.data = context.store.get(fullTag, AnimationData()) 73 | context.store[fullTag] = self.data 74 | 75 | def setDebug(self, debug): 76 | self.debug = debug 77 | 78 | def getTime(self): 79 | return self.context.shownTime 80 | 81 | def getTrackInfo(self, name): 82 | track = self.data.tracks.get(name) 83 | if track: 84 | return track.info 85 | return None 86 | 87 | def startAnimation(self, info): 88 | track = Track(info, self.getTime()) 89 | self.data.tracks[info.name] = track 90 | 91 | def stopAnimation(self, name): 92 | del self.data.tracks[name] 93 | 94 | def updateAnimations(self): 95 | tracks = list(self.data.tracks.values()) 96 | tracks.sort(key=lambda t: t.info.name) 97 | 98 | mix = {} 99 | for track in tracks: 100 | keys = self.updateTrack(track) 101 | if keys: 102 | for name, key in keys.items(): 103 | d = mix.get(name, []) 104 | d.append((track, key)) 105 | mix[name] = d 106 | 107 | self.mixTrackKeys(mix) 108 | 109 | def updateTrack(self, track): 110 | currentTime = self.getTime() 111 | if track.info.autoEnd and track.isAtEnd(currentTime): 112 | self.debugDraw(track, "(Autoend)") 113 | return None 114 | 115 | if track.info.cyclic: 116 | frameIndex = track.getFrameIndexCyclic(currentTime) 117 | elif track.info.repeat: 118 | frameIndex = track.getFrameIndexRepeat(currentTime) 119 | else: 120 | frameIndex = track.getFrameIndexClamped(currentTime) 121 | 122 | if track.info.reverse: 123 | frameIndex = (len(track.animation.frames) - 1) - frameIndex 124 | 125 | keys = track.animation.interpolate(frameIndex, self.context.renderer.getBones(), track.info.easingOverride) 126 | 127 | self.debugDraw(track, frameIndex) 128 | 129 | return keys 130 | 131 | def mixTrackKeys(self, mix): 132 | bones = self.context.renderer.getBones() 133 | for name, bone in bones.items(): 134 | data = mix.get(name) 135 | if data: 136 | weights = [d[0].info.weight for d in data] 137 | keys = [d[1] for d in data] 138 | mixed = skinnedanimation.mixKeys(keys, weights) 139 | skinnedanimation.copyKeyData(mixed, bone) 140 | 141 | def debugDraw(self, track, frameIndex): 142 | if self.debug: 143 | self.context.createOverlayCanvas() 144 | #TODO Also tell how many keys reference missing bones 145 | text = "%s (%s) Speed: %.1f, Frame %s / %i" % (self.tag, track.info.name, 146 | track.info.speed, frameIndex, len(track.animation.frames) - 1) 147 | pos = (10, self.debugY) 148 | color = (0, 0, 0) 149 | self.debugY += utils.drawText(self.context.overlayCanvas, text, pos, color)[1] 150 | 151 | def play(self, infos, rest=True): 152 | for info in infos: 153 | if not info.name in self.data.tracks: 154 | self.startAnimation(info) 155 | 156 | self.updateAnimations() 157 | 158 | names = [i.name for i in infos] 159 | for name in self.data.tracks.copy(): 160 | if not name in names: 161 | self.stopAnimation(name) 162 | 163 | if rest: 164 | self.restBones() 165 | 166 | def restBones(self): 167 | animated = self.getAnimatedBoneNames() 168 | bones = self.context.renderer.getBones() 169 | target = skin.SkinningBone(None) 170 | for name in bones: 171 | if not name in animated: 172 | self.restBone(bones[name], target) 173 | 174 | def restBone(self, a, b): 175 | weight = 0.1 #TODO Different speed for bones, use parent count etc.? 176 | a.translation = euclid.Vector3(*utils.interpolate3d(a.translation, b.translation, weight)) 177 | a.rotation = euclid.Vector3(*utils.interpolate3d(a.rotation, b.rotation, weight)) 178 | a.scale = euclid.Vector3(*utils.interpolate3d(a.scale, b.scale, weight)) 179 | 180 | def getAnimatedBoneNames(self): 181 | names = set() 182 | for track in self.data.tracks.values(): 183 | active = True 184 | if track.info.autoEnd: 185 | active = not track.isAtEnd(self.getTime()) 186 | 187 | if active: 188 | for frame in track.animation.frames: 189 | for name in frame.keys: 190 | names.add(name) 191 | return names -------------------------------------------------------------------------------- /ShaderDemo/game/shader/utils.py: -------------------------------------------------------------------------------- 1 | 2 | import renpy 3 | import pygame 4 | import os 5 | import math 6 | import ctypes 7 | import euclid 8 | from OpenGL import GL as gl 9 | 10 | FONT_SIZE = 18 11 | FONT = None 12 | 13 | def drawText(canvas, text, pos, color, align=-1, background=(128, 128, 128)): 14 | global FONT 15 | if FONT is None: 16 | pygame.font.init() 17 | FONT = pygame.font.Font(None, FONT_SIZE) 18 | 19 | surface = FONT.render(text, True, color, background) 20 | if align == 1: 21 | pos = (pos[0] - surface.get_width(), pos[1]) 22 | canvas.get_surface().blit(surface, pos) 23 | return surface.get_size() 24 | 25 | def drawLinesSafe(canvas, color, connect, points, width=1): 26 | #Workaround for hang if two points are the same 27 | safe = [] 28 | i = 0 29 | while i < len(points): 30 | p = (round(points[i][0]), round(points[i][1])) 31 | safe.append(p) 32 | i2 = i + 1 33 | while i2 < len(points): 34 | p2 = (round(points[i2][0]), round(points[i2][1])) 35 | if p != p2: 36 | break 37 | i2 += 1 38 | i = i2 39 | 40 | if connect and len(safe) > 0: 41 | first = safe[0] 42 | safe.append((first[0], first[1] - 1)) #Wrong by one pixel to be sure... 43 | 44 | canvas.lines(color, False, safe, width) 45 | 46 | def createTransform2d(): 47 | eye = euclid.Vector3(0, 0, 1) 48 | at = euclid.Vector3(0, 0, 0) 49 | up = euclid.Vector3(0, 1, 0) 50 | view = euclid.Matrix4.new_look_at(eye, at, up) 51 | perspective = euclid.Matrix4.new_perspective(math.radians(90), 1.0, 0.1, 10) 52 | return perspective * view 53 | 54 | def createPerspective(fov, width, height, zMin, zMax): 55 | #TODO This negates fov to flip y-axis in the framebuffer. 56 | return euclid.Matrix4.new_perspective(-math.radians(fov), width / float(height), zMin, zMax) 57 | 58 | def createPerspectiveBlender(lens, xResolution, yResolution, width, height, zMin, zMax): 59 | factor = lens / 32.0 60 | ratio = xResolution / float(yResolution) 61 | fov = math.atan(0.5 / ratio / factor) 62 | fov = fov * 360 / math.pi 63 | return createPerspective(fov, width, height, zMin, zMax) 64 | 65 | def createPerspectiveOrtho(left, right, bottom, top, near, far): 66 | projection = [0] * 16 67 | projection[0] = 2 / (right - left) 68 | projection[4] = 0 69 | projection[8] = 0 70 | projection[12] = -(right + left) / (right - left) 71 | 72 | projection[1] = 0 73 | projection[5] = 2 / (top - bottom) 74 | projection[9] = 0 75 | projection[13] = -(top + bottom) / (top - bottom) 76 | 77 | projection[2] = 0 78 | projection[6] = 0 79 | projection[10] = -2 / (far - near) 80 | projection[14] = -(far + near) / (far - near) 81 | 82 | projection[3] = 0 83 | projection[7] = 0 84 | projection[11] = 0 85 | projection[15] = 1 86 | 87 | return projection 88 | 89 | def clamp(value, small, large): 90 | return max(min(value, large), small) 91 | 92 | def interpolate(a, b, s): 93 | #Simple linear interpolation 94 | return a + s * (b - a) 95 | 96 | def interpolate2d(p1, p2, s): 97 | x = interpolate(p1[0], p2[0], s) 98 | y = interpolate(p1[1], p2[1], s) 99 | return (x, y) 100 | 101 | def interpolate3d(p1, p2, s): 102 | x = interpolate(p1[0], p2[0], s) 103 | y = interpolate(p1[1], p2[1], s) 104 | z = interpolate(p1[2], p2[2], s) 105 | return (x, y, z) 106 | 107 | def makeFloatArray(elements, count): 108 | raw = (gl.GLfloat * (len(elements) * count))() 109 | for i in range(len(elements)): 110 | v = elements[i] 111 | for x in range(count): 112 | raw[(i * count) + x] = v[x] 113 | return raw 114 | 115 | def matrixToList(m): 116 | return [m.a, m.e, m.i, m.m, 117 | m.b, m.f, m.j, m.n, 118 | m.c, m.g, m.k, m.o, 119 | m.d, m.h, m.l, m.p] 120 | 121 | def getTexParameteriv(glTex, param): 122 | result = ctypes.c_int(0) 123 | gl.glGetTexLevelParameteriv(gl.GL_TEXTURE_2D, 0, param, ctypes.byref(result)) 124 | return result.value 125 | 126 | def listFiles(): 127 | results = [] 128 | for root, dirs, files in os.walk(renpy.config.gamedir): 129 | dirs[:] = [d for d in dirs if not d[0] == "."] #Ignore dot directories 130 | for f in files: 131 | match = os.path.join(root, f).replace("\\", "/") 132 | results.append(match) 133 | results.sort() 134 | return results 135 | 136 | def scanForFiles(extension): 137 | results = [] 138 | for f in listFiles(): 139 | if f.split(".")[-1].lower() == extension.lower(): 140 | results.append(f) 141 | results.sort() 142 | return results 143 | 144 | def findFile(name): 145 | #First try fast and bundle supporting listing 146 | for f in renpy.exports.list_files(): 147 | if f.split("/")[-1] == name: 148 | return f 149 | 150 | #Scan all game directories 151 | for f in listFiles(): 152 | if f.split("/")[-1] == name: 153 | return f 154 | return None 155 | 156 | def openFile(path): 157 | return renpy.exports.file(path) 158 | -------------------------------------------------------------------------------- /doc/rigeditor.md: -------------------------------------------------------------------------------- 1 | 2 | # Rig Editor 3 | 4 | This document provides some basic instructions for the rig editor. You can access the rig editor from the main menu. Make sure you have watched the [basic editor workflow video](https://www.youtube.com/watch?v=NHJu0OYBERE). 5 | 6 | Note: Starting the editor disables some screens (like quick menu) and keyboard shortcuts to prevent them from interfering with the editor controls. Restart the game to get them back. 7 | 8 | ## Rigging walkthrough 9 | 10 | You can play around with the demo project editor, but you probably want to rig your own files at some point. To do so: 11 | 12 | * Place your image file(s) in the "images"-folder (or anywhere where they can be found by RenPy) and depending from your project config create an explicit image tag (normal or a LiveComposite) for them. It would be nice to work with all files and folders, but RenPy can do some very useful work for us this way. 13 | 14 | * Add buttons to your game main menu (or anywhere else you like) 15 | 16 | ```python 17 | if renpy.config.developer: 18 | textbutton _("Start Rig Editor") action Start("start_editor") 19 | textbutton _("Reset window") action Function(renpy.reset_physical_size) 20 | ``` 21 | 22 | * The editor can change the size of the application window and sometimes it gets stuck in a wrong size. Press the "Reset Window" button to fix it. 23 | 24 | * Start the editor. 25 | 26 | * In the first dialog select the image tag you want to rig. Only choose a tag that references a real image file or a LiveComposite that contains image files. 27 | 28 | * In the second dialog choose "Create a new rig". If you have previously saved a rig, you can choose it here. The editor can crash here if the image has some problematic areas (constructing the triangulated mesh from an arbitary image is tricky business). File a bug report (or contact me in another way) and send me the image that causes problems so I can hopefully fix the issue. 29 | 30 | * If everything worked, rig away! 31 | 32 | * Save the rig you created to a file. Now you can use the rig you just saved. 33 | 34 | The saved .rig-file contains information about the RenPy images it was created from, so updating the image files afterwards can cause issues. Most importantly the image resolutions should not be changed. Changing colors or making small adjustments is usually fine, but you might need to re-adjust image edge points in the editor to re-triangulate the mesh if the image silhouette changed. 35 | 36 | ## Rigging tips 37 | 38 | If some mesh part deforms in a bad way... 39 | 40 | * Move the bone slightly. Sometimes the bone is just in a bad location and mesh deformation (like rotation) looks bad especially with large triangles nearby. 41 | 42 | * Activate bone tessellation in spots that must deform alot, but be aware it can cause "holes" and tearing in the mesh if deformed enough. (To be fixed). 43 | 44 | * Create "helper" bones to control the deformation between the bones you actually care about. Possibly enable tessellation on them, too. 45 | 46 | * Combine translation with rotation. For example an elbow (or any other joint) bend animation can both rotate the joint and also move it slightly to fix or hide any bad spots. 47 | 48 | * Adjust bone z-order to make sure the "better" bone hides the "bad" geometry under it. 49 | 50 | * Try removing edge points around the important joint. Sometimes less dense mesh is actually better. 51 | 52 | ## Animation walkthrough 53 | 54 | Bone attributes that can be animated: 55 | 56 | * Rotation (x, y and z axis) 57 | * Scaling (x, y and z axis) 58 | * Translation (x and y axis) 59 | * Visibility (visible / hidden) 60 | * Transparency (alpha) 61 | 62 | Keyframe animation is used. This means that keyframes for bones are inserted at important positions in the timeline and the frames between those keysframes are interpolated. 63 | 64 | Playing the animation in the editor can be much slower (because of RenPy screen updates etc.) than playing it using the AnimationPlayer. 65 | 66 | Track-related options in the animation UI are available only if the animation has been saved to a file. This emulates how the TrackInfo-class will behave during the gameplay. All track animation data is loaded from the file and any changes in the editor (like adding or deleting keyframes etc.) will not take effect until you save the file again! 67 | 68 | ## Keyboard Shortcuts 69 | 70 | The editor relies heavily on keyboard shortcuts. 71 | 72 | ### Rigging 73 | 74 | * s: Scale active bone. While in this mode press x, y or z to only affect that axis. Left mouse click accepts, right cancels. 75 | * s + alt: Clear active bone scaling. 76 | * r: Rotate active bone. While in this mode press x, y or z to only affect that axis. Left mouse click accepts, right cancels. 77 | * r + alt: Clear active bone rotation. 78 | * g: Grab and translate active bone. While in this mode press x or y to only affect that axis. Left mouse click accepts, right cancels. 79 | * g + alt: Clear active bone translation. 80 | * a: Change the alpha transparency of active bone. Left mouse click accepts, right cancels. 81 | * a + alt: Clear active bone alpha transparency. 82 | * Mouse wheel: Increase or decrease active bone z-order. Changing the order also changes the order of all child bones. 83 | * x: Delete the active bone (or an edge point if one is under the cursor). 84 | * h: Toggle active bone hidden state. 85 | * b: Toggle active bone blocker state. 86 | * t: Toggle active bone tessellation state. This can be used to add more mesh detail to places that are heavily deformed (like joints). Can cause small "tearing" (or holes) to appear in the mesh when deformed. You can try add other tessellating bones nearby for a temporary workaround. To be fixed. 87 | * d: Toggle active bone damping state. Very experimental! Might be removed in the future. 88 | * e: Extrude a new child bone from the active bone. Left mouse click accepts, right cancels. 89 | * c: Connect active bone into another parent bone. Left mouse click accepts, right cancels. 90 | 91 | ### Animation 92 | 93 | * i: Insert a keyframe for the active bone at the current frame. 94 | * i + alt: Delete active bone keyframe at the current frame. 95 | * o: Toggle active bone cycle repeat animation state. 96 | * p: Toggle active bone reverse animation state. 97 | * j: Move one frame backwards. 98 | * k: Start and stop the animation. 99 | * l: Move one frame forward. 100 | 101 | -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/macholib/README.ctypes: -------------------------------------------------------------------------------- 1 | Files in this directory come from Bob Ippolito's py2app. 2 | 3 | License: Any components of the py2app suite may be distributed under 4 | the MIT or PSF open source licenses. 5 | 6 | This is version 1.0, SVN revision 789, from 2006/01/25. 7 | The main repository is http://svn.red-bean.com/bob/macholib/trunk/macholib/ -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/macholib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Enough Mach-O to make your head spin. 3 | 4 | See the relevant header files in /usr/include/mach-o 5 | 6 | And also Apple's documentation. 7 | """ 8 | 9 | __version__ = '1.0' 10 | -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/macholib/dyld.py: -------------------------------------------------------------------------------- 1 | """ 2 | dyld emulation 3 | """ 4 | 5 | import os 6 | from framework import framework_info 7 | from dylib import dylib_info 8 | from itertools import * 9 | 10 | __all__ = [ 11 | 'dyld_find', 'framework_find', 12 | 'framework_info', 'dylib_info', 13 | ] 14 | 15 | # These are the defaults as per man dyld(1) 16 | # 17 | DEFAULT_FRAMEWORK_FALLBACK = [ 18 | os.path.expanduser("~/Library/Frameworks"), 19 | "/Library/Frameworks", 20 | "/Network/Library/Frameworks", 21 | "/System/Library/Frameworks", 22 | ] 23 | 24 | DEFAULT_LIBRARY_FALLBACK = [ 25 | os.path.expanduser("~/lib"), 26 | "/usr/local/lib", 27 | "/lib", 28 | "/usr/lib", 29 | ] 30 | 31 | def ensure_utf8(s): 32 | """Not all of PyObjC and Python understand unicode paths very well yet""" 33 | if isinstance(s, unicode): 34 | return s.encode('utf8') 35 | return s 36 | 37 | def dyld_env(env, var): 38 | if env is None: 39 | env = os.environ 40 | rval = env.get(var) 41 | if rval is None: 42 | return [] 43 | return rval.split(':') 44 | 45 | def dyld_image_suffix(env=None): 46 | if env is None: 47 | env = os.environ 48 | return env.get('DYLD_IMAGE_SUFFIX') 49 | 50 | def dyld_framework_path(env=None): 51 | return dyld_env(env, 'DYLD_FRAMEWORK_PATH') 52 | 53 | def dyld_library_path(env=None): 54 | return dyld_env(env, 'DYLD_LIBRARY_PATH') 55 | 56 | def dyld_fallback_framework_path(env=None): 57 | return dyld_env(env, 'DYLD_FALLBACK_FRAMEWORK_PATH') 58 | 59 | def dyld_fallback_library_path(env=None): 60 | return dyld_env(env, 'DYLD_FALLBACK_LIBRARY_PATH') 61 | 62 | def dyld_image_suffix_search(iterator, env=None): 63 | """For a potential path iterator, add DYLD_IMAGE_SUFFIX semantics""" 64 | suffix = dyld_image_suffix(env) 65 | if suffix is None: 66 | return iterator 67 | def _inject(iterator=iterator, suffix=suffix): 68 | for path in iterator: 69 | if path.endswith('.dylib'): 70 | yield path[:-len('.dylib')] + suffix + '.dylib' 71 | else: 72 | yield path + suffix 73 | yield path 74 | return _inject() 75 | 76 | def dyld_override_search(name, env=None): 77 | # If DYLD_FRAMEWORK_PATH is set and this dylib_name is a 78 | # framework name, use the first file that exists in the framework 79 | # path if any. If there is none go on to search the DYLD_LIBRARY_PATH 80 | # if any. 81 | 82 | framework = framework_info(name) 83 | 84 | if framework is not None: 85 | for path in dyld_framework_path(env): 86 | yield os.path.join(path, framework['name']) 87 | 88 | # If DYLD_LIBRARY_PATH is set then use the first file that exists 89 | # in the path. If none use the original name. 90 | for path in dyld_library_path(env): 91 | yield os.path.join(path, os.path.basename(name)) 92 | 93 | def dyld_executable_path_search(name, executable_path=None): 94 | # If we haven't done any searching and found a library and the 95 | # dylib_name starts with "@executable_path/" then construct the 96 | # library name. 97 | if name.startswith('@executable_path/') and executable_path is not None: 98 | yield os.path.join(executable_path, name[len('@executable_path/'):]) 99 | 100 | def dyld_default_search(name, env=None): 101 | yield name 102 | 103 | framework = framework_info(name) 104 | 105 | if framework is not None: 106 | fallback_framework_path = dyld_fallback_framework_path(env) 107 | for path in fallback_framework_path: 108 | yield os.path.join(path, framework['name']) 109 | 110 | fallback_library_path = dyld_fallback_library_path(env) 111 | for path in fallback_library_path: 112 | yield os.path.join(path, os.path.basename(name)) 113 | 114 | if framework is not None and not fallback_framework_path: 115 | for path in DEFAULT_FRAMEWORK_FALLBACK: 116 | yield os.path.join(path, framework['name']) 117 | 118 | if not fallback_library_path: 119 | for path in DEFAULT_LIBRARY_FALLBACK: 120 | yield os.path.join(path, os.path.basename(name)) 121 | 122 | def dyld_find(name, executable_path=None, env=None): 123 | """ 124 | Find a library or framework using dyld semantics 125 | """ 126 | name = ensure_utf8(name) 127 | executable_path = ensure_utf8(executable_path) 128 | for path in dyld_image_suffix_search(chain( 129 | dyld_override_search(name, env), 130 | dyld_executable_path_search(name, executable_path), 131 | dyld_default_search(name, env), 132 | ), env): 133 | if os.path.isfile(path): 134 | return path 135 | raise ValueError("dylib %s could not be found" % (name,)) 136 | 137 | def framework_find(fn, executable_path=None, env=None): 138 | """ 139 | Find a framework using dyld semantics in a very loose manner. 140 | 141 | Will take input such as: 142 | Python 143 | Python.framework 144 | Python.framework/Versions/Current 145 | """ 146 | try: 147 | return dyld_find(fn, executable_path=executable_path, env=env) 148 | except ValueError, e: 149 | pass 150 | fmwk_index = fn.rfind('.framework') 151 | if fmwk_index == -1: 152 | fmwk_index = len(fn) 153 | fn += '.framework' 154 | fn = os.path.join(fn, os.path.basename(fn[:fmwk_index])) 155 | try: 156 | return dyld_find(fn, executable_path=executable_path, env=env) 157 | except ValueError: 158 | raise e 159 | 160 | def test_dyld_find(): 161 | env = {} 162 | assert dyld_find('libSystem.dylib') == '/usr/lib/libSystem.dylib' 163 | assert dyld_find('System.framework/System') == '/System/Library/Frameworks/System.framework/System' 164 | 165 | if __name__ == '__main__': 166 | test_dyld_find() 167 | -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/macholib/dylib.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generic dylib path manipulation 3 | """ 4 | 5 | import re 6 | 7 | __all__ = ['dylib_info'] 8 | 9 | DYLIB_RE = re.compile(r"""(?x) 10 | (?P^.*)(?:^|/) 11 | (?P 12 | (?P\w+?) 13 | (?:\.(?P[^._]+))? 14 | (?:_(?P[^._]+))? 15 | \.dylib$ 16 | ) 17 | """) 18 | 19 | def dylib_info(filename): 20 | """ 21 | A dylib name can take one of the following four forms: 22 | Location/Name.SomeVersion_Suffix.dylib 23 | Location/Name.SomeVersion.dylib 24 | Location/Name_Suffix.dylib 25 | Location/Name.dylib 26 | 27 | returns None if not found or a mapping equivalent to: 28 | dict( 29 | location='Location', 30 | name='Name.SomeVersion_Suffix.dylib', 31 | shortname='Name', 32 | version='SomeVersion', 33 | suffix='Suffix', 34 | ) 35 | 36 | Note that SomeVersion and Suffix are optional and may be None 37 | if not present. 38 | """ 39 | is_dylib = DYLIB_RE.match(filename) 40 | if not is_dylib: 41 | return None 42 | return is_dylib.groupdict() 43 | 44 | 45 | def test_dylib_info(): 46 | def d(location=None, name=None, shortname=None, version=None, suffix=None): 47 | return dict( 48 | location=location, 49 | name=name, 50 | shortname=shortname, 51 | version=version, 52 | suffix=suffix 53 | ) 54 | assert dylib_info('completely/invalid') is None 55 | assert dylib_info('completely/invalide_debug') is None 56 | assert dylib_info('P/Foo.dylib') == d('P', 'Foo.dylib', 'Foo') 57 | assert dylib_info('P/Foo_debug.dylib') == d('P', 'Foo_debug.dylib', 'Foo', suffix='debug') 58 | assert dylib_info('P/Foo.A.dylib') == d('P', 'Foo.A.dylib', 'Foo', 'A') 59 | assert dylib_info('P/Foo_debug.A.dylib') == d('P', 'Foo_debug.A.dylib', 'Foo_debug', 'A') 60 | assert dylib_info('P/Foo.A_debug.dylib') == d('P', 'Foo.A_debug.dylib', 'Foo', 'A', 'debug') 61 | 62 | if __name__ == '__main__': 63 | test_dylib_info() 64 | -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/macholib/fetch_macholib: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | svn export --force http://svn.red-bean.com/bob/macholib/trunk/macholib/ . 3 | -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/macholib/framework.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generic framework path manipulation 3 | """ 4 | 5 | import re 6 | 7 | __all__ = ['framework_info'] 8 | 9 | STRICT_FRAMEWORK_RE = re.compile(r"""(?x) 10 | (?P^.*)(?:^|/) 11 | (?P 12 | (?P\w+).framework/ 13 | (?:Versions/(?P[^/]+)/)? 14 | (?P=shortname) 15 | (?:_(?P[^_]+))? 16 | )$ 17 | """) 18 | 19 | def framework_info(filename): 20 | """ 21 | A framework name can take one of the following four forms: 22 | Location/Name.framework/Versions/SomeVersion/Name_Suffix 23 | Location/Name.framework/Versions/SomeVersion/Name 24 | Location/Name.framework/Name_Suffix 25 | Location/Name.framework/Name 26 | 27 | returns None if not found, or a mapping equivalent to: 28 | dict( 29 | location='Location', 30 | name='Name.framework/Versions/SomeVersion/Name_Suffix', 31 | shortname='Name', 32 | version='SomeVersion', 33 | suffix='Suffix', 34 | ) 35 | 36 | Note that SomeVersion and Suffix are optional and may be None 37 | if not present 38 | """ 39 | is_framework = STRICT_FRAMEWORK_RE.match(filename) 40 | if not is_framework: 41 | return None 42 | return is_framework.groupdict() 43 | 44 | def test_framework_info(): 45 | def d(location=None, name=None, shortname=None, version=None, suffix=None): 46 | return dict( 47 | location=location, 48 | name=name, 49 | shortname=shortname, 50 | version=version, 51 | suffix=suffix 52 | ) 53 | assert framework_info('completely/invalid') is None 54 | assert framework_info('completely/invalid/_debug') is None 55 | assert framework_info('P/F.framework') is None 56 | assert framework_info('P/F.framework/_debug') is None 57 | assert framework_info('P/F.framework/F') == d('P', 'F.framework/F', 'F') 58 | assert framework_info('P/F.framework/F_debug') == d('P', 'F.framework/F_debug', 'F', suffix='debug') 59 | assert framework_info('P/F.framework/Versions') is None 60 | assert framework_info('P/F.framework/Versions/A') is None 61 | assert framework_info('P/F.framework/Versions/A/F') == d('P', 'F.framework/Versions/A/F', 'F', 'A') 62 | assert framework_info('P/F.framework/Versions/A/F_debug') == d('P', 'F.framework/Versions/A/F_debug', 'F', 'A', 'debug') 63 | 64 | if __name__ == '__main__': 65 | test_framework_info() 66 | -------------------------------------------------------------------------------- /pythonlib2.7/ctypes/util.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | import sys 5 | 6 | # find_library(name) returns the pathname of a library, or None. 7 | if os.name == "nt": 8 | 9 | def _get_build_version(): 10 | """Return the version of MSVC that was used to build Python. 11 | 12 | For Python 2.3 and up, the version number is included in 13 | sys.version. For earlier versions, assume the compiler is MSVC 6. 14 | """ 15 | # This function was copied from Lib/distutils/msvccompiler.py 16 | prefix = "MSC v." 17 | i = sys.version.find(prefix) 18 | if i == -1: 19 | return 6 20 | i = i + len(prefix) 21 | s, rest = sys.version[i:].split(" ", 1) 22 | majorVersion = int(s[:-2]) - 6 23 | if majorVersion >= 13: 24 | majorVersion += 1 25 | minorVersion = int(s[2:3]) / 10.0 26 | # I don't think paths are affected by minor version in version 6 27 | if majorVersion == 6: 28 | minorVersion = 0 29 | if majorVersion >= 6: 30 | return majorVersion + minorVersion 31 | # else we don't know what version of the compiler this is 32 | return None 33 | 34 | def find_msvcrt(): 35 | """Return the name of the VC runtime dll""" 36 | version = _get_build_version() 37 | if version is None: 38 | # better be safe than sorry 39 | return None 40 | if version <= 6: 41 | clibname = 'msvcrt' 42 | elif version <= 13: 43 | clibname = 'msvcr%d' % (version * 10) 44 | else: 45 | # CRT is no longer directly loadable. See issue23606 for the 46 | # discussion about alternative approaches. 47 | return None 48 | 49 | # If python was built with in debug mode 50 | import importlib.machinery 51 | if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES: 52 | clibname += 'd' 53 | return clibname+'.dll' 54 | 55 | def find_library(name): 56 | if name in ('c', 'm'): 57 | return find_msvcrt() 58 | # See MSDN for the REAL search order. 59 | for directory in os.environ['PATH'].split(os.pathsep): 60 | fname = os.path.join(directory, name) 61 | if os.path.isfile(fname): 62 | return fname 63 | if fname.lower().endswith(".dll"): 64 | continue 65 | fname = fname + ".dll" 66 | if os.path.isfile(fname): 67 | return fname 68 | return None 69 | 70 | if os.name == "posix" and sys.platform == "darwin": 71 | from ctypes.macholib.dyld import dyld_find as _dyld_find 72 | def find_library(name): 73 | possible = ['lib%s.dylib' % name, 74 | '%s.dylib' % name, 75 | '%s.framework/%s' % (name, name)] 76 | for name in possible: 77 | try: 78 | return _dyld_find(name) 79 | except ValueError: 80 | continue 81 | return None 82 | 83 | elif os.name == "posix": 84 | # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump 85 | import re, tempfile 86 | 87 | def _findLib_gcc(name): 88 | # Run GCC's linker with the -t (aka --trace) option and examine the 89 | # library name it prints out. The GCC command will fail because we 90 | # haven't supplied a proper program with main(), but that does not 91 | # matter. 92 | expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)) 93 | 94 | c_compiler = shutil.which('gcc') 95 | if not c_compiler: 96 | c_compiler = shutil.which('cc') 97 | if not c_compiler: 98 | # No C compiler available, give up 99 | return None 100 | 101 | temp = tempfile.NamedTemporaryFile() 102 | try: 103 | args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name] 104 | 105 | env = dict(os.environ) 106 | env['LC_ALL'] = 'C' 107 | env['LANG'] = 'C' 108 | try: 109 | proc = subprocess.Popen(args, 110 | stdout=subprocess.PIPE, 111 | stderr=subprocess.STDOUT, 112 | env=env) 113 | except OSError: # E.g. bad executable 114 | return None 115 | with proc: 116 | trace = proc.stdout.read() 117 | finally: 118 | try: 119 | temp.close() 120 | except FileNotFoundError: 121 | # Raised if the file was already removed, which is the normal 122 | # behaviour of GCC if linking fails 123 | pass 124 | res = re.search(expr, trace) 125 | if not res: 126 | return None 127 | return os.fsdecode(res.group(0)) 128 | 129 | 130 | if sys.platform == "sunos5": 131 | # use /usr/ccs/bin/dump on solaris 132 | def _get_soname(f): 133 | if not f: 134 | return None 135 | 136 | try: 137 | proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f), 138 | stdout=subprocess.PIPE, 139 | stderr=subprocess.DEVNULL) 140 | except OSError: # E.g. command not found 141 | return None 142 | with proc: 143 | data = proc.stdout.read() 144 | res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data) 145 | if not res: 146 | return None 147 | return os.fsdecode(res.group(1)) 148 | else: 149 | def _get_soname(f): 150 | # assuming GNU binutils / ELF 151 | if not f: 152 | return None 153 | objdump = shutil.which('objdump') 154 | if not objdump: 155 | # objdump is not available, give up 156 | return None 157 | 158 | try: 159 | proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f), 160 | stdout=subprocess.PIPE, 161 | stderr=subprocess.DEVNULL) 162 | except OSError: # E.g. bad executable 163 | return None 164 | with proc: 165 | dump = proc.stdout.read() 166 | res = re.search(br'\sSONAME\s+([^\s]+)', dump) 167 | if not res: 168 | return None 169 | return os.fsdecode(res.group(1)) 170 | 171 | if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")): 172 | 173 | def _num_version(libname): 174 | # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ] 175 | parts = libname.split(b".") 176 | nums = [] 177 | try: 178 | while parts: 179 | nums.insert(0, int(parts.pop())) 180 | except ValueError: 181 | pass 182 | return nums or [sys.maxsize] 183 | 184 | def find_library(name): 185 | ename = re.escape(name) 186 | expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename) 187 | expr = os.fsencode(expr) 188 | 189 | try: 190 | proc = subprocess.Popen(('/sbin/ldconfig', '-r'), 191 | stdout=subprocess.PIPE, 192 | stderr=subprocess.DEVNULL) 193 | except OSError: # E.g. command not found 194 | data = b'' 195 | else: 196 | with proc: 197 | data = proc.stdout.read() 198 | 199 | res = re.findall(expr, data) 200 | if not res: 201 | return _get_soname(_findLib_gcc(name)) 202 | res.sort(key=_num_version) 203 | return os.fsdecode(res[-1]) 204 | 205 | elif sys.platform == "sunos5": 206 | 207 | def _findLib_crle(name, is64): 208 | if not os.path.exists('/usr/bin/crle'): 209 | return None 210 | 211 | env = dict(os.environ) 212 | env['LC_ALL'] = 'C' 213 | 214 | if is64: 215 | args = ('/usr/bin/crle', '-64') 216 | else: 217 | args = ('/usr/bin/crle',) 218 | 219 | paths = None 220 | try: 221 | proc = subprocess.Popen(args, 222 | stdout=subprocess.PIPE, 223 | stderr=subprocess.DEVNULL, 224 | env=env) 225 | except OSError: # E.g. bad executable 226 | return None 227 | with proc: 228 | for line in proc.stdout: 229 | line = line.strip() 230 | if line.startswith(b'Default Library Path (ELF):'): 231 | paths = os.fsdecode(line).split()[4] 232 | 233 | if not paths: 234 | return None 235 | 236 | for dir in paths.split(":"): 237 | libfile = os.path.join(dir, "lib%s.so" % name) 238 | if os.path.exists(libfile): 239 | return libfile 240 | 241 | return None 242 | 243 | def find_library(name, is64 = False): 244 | return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) 245 | 246 | else: 247 | 248 | def _findSoname_ldconfig(name): 249 | import struct 250 | if struct.calcsize('l') == 4: 251 | machine = os.uname().machine + '-32' 252 | else: 253 | machine = os.uname().machine + '-64' 254 | mach_map = { 255 | 'x86_64-64': 'libc6,x86-64', 256 | 'ppc64-64': 'libc6,64bit', 257 | 'sparc64-64': 'libc6,64bit', 258 | 's390x-64': 'libc6,64bit', 259 | 'ia64-64': 'libc6,IA-64', 260 | } 261 | abi_type = mach_map.get(machine, 'libc6') 262 | 263 | # XXX assuming GLIBC's ldconfig (with option -p) 264 | regex = r'\s+(lib%s\.[^\s]+)\s+\(%s' 265 | regex = os.fsencode(regex % (re.escape(name), abi_type)) 266 | try: 267 | with subprocess.Popen(['/sbin/ldconfig', '-p'], 268 | stdin=subprocess.DEVNULL, 269 | stderr=subprocess.DEVNULL, 270 | stdout=subprocess.PIPE, 271 | env={'LC_ALL': 'C', 'LANG': 'C'}) as p: 272 | res = re.search(regex, p.stdout.read()) 273 | if res: 274 | return os.fsdecode(res.group(1)) 275 | except OSError: 276 | pass 277 | 278 | def _findLib_ld(name): 279 | # See issue #9998 for why this is needed 280 | expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name) 281 | cmd = ['ld', '-t'] 282 | libpath = os.environ.get('LD_LIBRARY_PATH') 283 | if libpath: 284 | for d in libpath.split(':'): 285 | cmd.extend(['-L', d]) 286 | cmd.extend(['-o', os.devnull, '-l%s' % name]) 287 | result = None 288 | try: 289 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, 290 | stderr=subprocess.PIPE, 291 | universal_newlines=True) 292 | out, _ = p.communicate() 293 | res = re.search(expr, os.fsdecode(out)) 294 | if res: 295 | result = res.group(0) 296 | except Exception as e: 297 | pass # result will be None 298 | return result 299 | 300 | def find_library(name): 301 | # See issue #9998 302 | return _findSoname_ldconfig(name) or \ 303 | _get_soname(_findLib_gcc(name) or _findLib_ld(name)) 304 | 305 | ################################################################ 306 | # test code 307 | 308 | def test(): 309 | from ctypes import cdll 310 | if os.name == "nt": 311 | print(cdll.msvcrt) 312 | print(cdll.load("msvcrt")) 313 | print(find_library("msvcrt")) 314 | 315 | if os.name == "posix": 316 | # find and load_version 317 | print(find_library("m")) 318 | print(find_library("c")) 319 | print(find_library("bz2")) 320 | 321 | # getattr 322 | ## print cdll.m 323 | ## print cdll.bz2 324 | 325 | # load 326 | if sys.platform == "darwin": 327 | print(cdll.LoadLibrary("libm.dylib")) 328 | print(cdll.LoadLibrary("libcrypto.dylib")) 329 | print(cdll.LoadLibrary("libSystem.dylib")) 330 | print(cdll.LoadLibrary("System.framework/System")) 331 | else: 332 | print(cdll.LoadLibrary("libm.so")) 333 | print(cdll.LoadLibrary("libcrypt.so")) 334 | print(find_library("crypt")) 335 | 336 | if __name__ == "__main__": 337 | test() 338 | -------------------------------------------------------------------------------- /tools/create_live_composite.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Helper script for cropping images and creating a RenPy LiveComposite for them. 4 | Quite specific and mostly useful for processing images exported from a 5 | rendering program like Blender or from Photoshop layers. 6 | 7 | Requires Pillow Python image processing library to be installed. 8 | 9 | Command line example (current working directory at the base of this project): 10 | 11 | python tools/create_live_composite.py ShaderDemo/game/images/doll 12 | 13 | This assumes all images in the source directory have the same size. The script 14 | crops them and creates an efficient LiveComposite that can be used for rigging 15 | or just normally. The resulting LiveComposite is written into a .rpy-file 16 | in the target directory. 17 | """ 18 | 19 | import sys 20 | import os 21 | from PIL import Image 22 | 23 | IMAGES = ["png", "jpg"] 24 | POSTFIX = "crop" 25 | PAD = 5 26 | 27 | sourceDir = sys.argv[1] 28 | sourceImages = [os.path.join(sourceDir, name) for name in os.listdir(sourceDir) if name.lower().split(".")[-1] in IMAGES] 29 | sourceImages.sort() 30 | 31 | def findValidImages(images): 32 | valid = [] 33 | size = None 34 | for path in sourceImages: 35 | image = Image.open(path) 36 | if POSTFIX and POSTFIX in path.lower(): 37 | print("Skipping already cropped: %s" % path) 38 | elif size is None or image.size == size: 39 | size = image.size 40 | valid.append((path, image)) 41 | else: 42 | print("Image %s has size %s, should be %s? Skipped." % (path, str(image.size), str(size))) 43 | return valid 44 | 45 | def getCropRect(image): 46 | x = 0 47 | y = 0 48 | x2 = image.size[0] 49 | y2 = image.size[1] 50 | box = image.getbbox() 51 | if box: 52 | return max(box[0] - PAD, 0), max(box[1] - PAD, 0), min(box[2] + PAD, image.size[0]), min(box[3] + PAD, image.size[1]) 53 | return x, y, x2, y2 54 | 55 | def createName(path): 56 | parts = path.rsplit(".", 1) 57 | return parts[0] + POSTFIX + "." + parts[1] 58 | 59 | results = [] 60 | for path, image in findValidImages(sourceImages): 61 | rect = getCropRect(image) 62 | cropped = image.crop(rect) 63 | name = createName(path) 64 | cropped.save(name) 65 | print("Saved: %s. Cropped: %s" % (name, str(rect))) 66 | results.append((name, image, rect)) 67 | 68 | name = os.path.normcase(sourceDir).split(os.sep)[-1] 69 | with open(os.path.join(sourceDir, name + ".rpy"), "w") as f: 70 | base = results[0] 71 | 72 | f.write("#Automatically generated file\n\n") 73 | f.write("image %s = LiveComposite(\n" % name) 74 | f.write(" (%i, %i),\n" % base[1].size) 75 | for result in results: 76 | name, image, crop = result 77 | name = name[name.find("images"):].replace("\\", "/") 78 | f.write(" (%i, %i), \"%s\",\n" % (crop[0], crop[1], name)) 79 | f.write(")\n") 80 | --------------------------------------------------------------------------------