#Author: Kwasi Mensah
#Date: 7/11/2005
#
# This is a tutorial to show some of the more advanced things
# you can do with Cg. Specifically, with Non Photo Realistic
# effects like Toon Shading. It also shows how to implement
# multiple buffers in Panda.


import direct.directbase.DirectStart
from pandac.PandaModules import FrameBufferProperties, WindowProperties
from pandac.PandaModules import GraphicsEngine,GraphicsPipe,GraphicsOutput
from pandac.PandaModules import PandaNode,LightNode,TextNode
from pandac.PandaModules import NodePath
from pandac.PandaModules import Filename,Texture,Shader
from pandac.PandaModules import Point3,Vec4
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
from direct.actor.Actor import Actor
from direct.gui.OnscreenText import OnscreenText
import sys,os

# Figure out what directory this program is in.
MYDIR=os.path.abspath(sys.path[0])
MYDIR=Filename.fromOsSpecific(MYDIR).getFullpath()

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1),
			pos=(-1.3, pos), align=TextNode.ALeft, scale = .05)

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1),
	                pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)


class ToonMaker(DirectObject):
    def __init__(self):
	base.disableMouse()
	camera.setPos(0, -50, 0)

        # Preliminary capabilities check.
        if (base.win.getGsg().getSupportsBasicShaders()==0):
            self.t=addTitle("Toon Shader: Video driver reports that shaders are not supported.")
            return
        
        # We want to create an offscreen buffer containing an auxiliary
        # color bitplane.  This is not a common request, so we have to
        # use the full-fledged most-complicated version of the
        # buffer-creation API.  This involves creating a FrameBufferProperties
        # structure that describes the features we need, and then
        # calling GraphicsEngine.makeOutput

        winprops = WindowProperties()
        props = FrameBufferProperties()
        props.setRgbColor(1)
        props.setDepthBits(1)
        props.setAlphaBits(1)
        props.setAuxRgba(1)
        self.buffer=base.graphicsEngine.makeOutput(
             base.pipe, "offscreen", -1,
             props, winprops, GraphicsPipe.BFSizeTrackHost | GraphicsPipe.BFRefuseWindow,
             base.win.getGsg(), base.win)

        # Creation of a high-powered buffer can fail, if the graphics card
        # doesn't support the necessary OpenGL extensions.

        if (self.buffer == None):
            self.t=addTitle("Toon Shader: Video driver does not support multiple render targets")
            return

        # Set up the render-textures.
        
        self.normalsTexture = Texture("normals")
        self.diffuseTexture = Texture("diffuse")
        self.buffer.addRenderTexture(self.normalsTexture, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPAuxRgba0)
        self.buffer.addRenderTexture(self.diffuseTexture, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
        self.buffer.setClearColor(Vec4(0.5,0.5,0.5,1))

        # Create a camera to render the model into the offscreen buffer.

        modelCamera=base.makeCamera(self.buffer, aspectRatio=base.getAspectRatio())
        modelCamera.node().setScene(render)
        tempnode = NodePath(PandaNode("temp node"))
        tempnode.setShader(Shader.load(MYDIR+"/modelGen.sha"))
        modelCamera.node().setInitialState(tempnode.getState())
        
        # By default, panda usually clears the screen before every
        # camera and before every window.  Tell it not to do that.
        # Then, tell it specifically when to clear and what to clear.

        modelCamera.node().getDisplayRegion(0).disableClears()
        base.cam.node().getDisplayRegion(0).disableClears()
        base.cam2d.node().getDisplayRegion(0).disableClears()
        self.buffer.disableClears()
        base.win.disableClears()

        self.buffer.setClearColorActive(1)
        self.buffer.setClearDepthActive(1)
        self.buffer.setClearColor(Vec4(0.5,0.5,0.5,1))

        # Disable the main panda camera.  Our model will only be rendered by modelCamera

        base.cam.node().setActive(0)
        
        # This is the object that represents the single "light", as far
        # the shader is concerned.  It's not a real Panda3D LightNode, but
        # the shader doesn't care about that.

        light = render.attachNewNode("light")
        light.setPos(30,-50,0)

        # this call puts the light's nodepath into the render state.
        # this enables the shader to access this light by name.

        render.setShaderInput("light", light)
        
        # Create a full-screen quad for the postprocessing stage.

        fsQuad=self.buffer.getTextureCard()
        fsQuad.setColor(1,1,1,0)
        fsQuad.reparentTo(render2d)
        self.fsQuad = fsQuad

        # the postprocessing shader accepts, as input, the two textures from 
        # the offscreen buffer.  The normals texture is analyzed, comparing
        # each adjacent pixel, looking for discontinuities.
        # wherever a discontinuity exists, postprocessing emits black ink.
        
        self.separation = 0.001
        self.cutoff = 0.3
        postGen=Shader.load(MYDIR+"/postGen.sha")
        self.fsQuad.setShader(postGen)
        self.fsQuad.setShaderInput("normalsTexture", self.normalsTexture)
        self.fsQuad.setShaderInput("diffuseTexture", self.diffuseTexture)
        self.fsQuad.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        self.fsQuad.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        
	# Post the instructions.

	self.title = addTitle("Panda3D: Tutorial - Toon Shading with Normals-Based Inking")
	self.inst1 = addInstructions(0.95,"ESC: Quit")
	self.inst2 = addInstructions(0.90,"Up/Down: Increase/Decrease Line Thickness")
	self.inst3 = addInstructions(0.85,"Left/Right: Decrease/Increase Line Darkness")
	self.inst4 = addInstructions(0.80,"V: View the render-to-texture results")

        # Panda contains a built-in viewer that lets you view the results of
        # your render-to-texture operations.  This code configures the viewer.

        self.accept("v", base.bufferViewer.toggleEnable)
        self.accept("V", base.bufferViewer.toggleEnable)
        base.bufferViewer.setPosition("llcorner")

	# Load a dragon model and animate it.

	self.character=Actor()
	self.character.loadModel(MYDIR+'/nik-dragon')
	self.character.reparentTo(render)
	self.character.loadAnims({'win': MYDIR+'/nik-dragon'})
	self.character.loop('win')
	self.character.hprInterval(15, Point3(360, 0,0)).loop()

        # these allow you to change cartooning parameters in realtime

        self.accept("escape", sys.exit, [0])
        self.accept("arrow_up", self.increaseSeparation)
        self.accept("arrow_down", self.decreaseSeparation)
        self.accept("arrow_left", self.increaseCutoff)
        self.accept("arrow_right", self.decreaseCutoff)
        
    def increaseSeparation(self):
        self.separation = self.separation * 1.11111111;
        print self.separation
        self.fsQuad.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        
    def decreaseSeparation(self):
        self.separation = self.separation * 0.90000000;
        print self.separation
        self.fsQuad.setShaderInput("separation", Vec4(self.separation,0,self.separation,0));
        
    def increaseCutoff(self):
        self.cutoff = self.cutoff * 1.11111111;
        print self.cutoff
        self.fsQuad.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        
    def decreaseCutoff(self):
        self.cutoff = self.cutoff * 0.90000000;
        print self.cutoff
        self.fsQuad.setShaderInput("cutoff", Vec4(self.cutoff,self.cutoff,self.cutoff,self.cutoff));
        

t=ToonMaker()

run()

