toon style outline

heres a minimal (almost no) setup way of getting an outline effect on objects
for people who like outlines on selected stuff but don’t want to hassle future moddes with extra work
[ATTACH]360132[/ATTACH]
let me know if any one has any improvements and thanks to all the people who helped (not that any of my code is left lol)

updated 6/2/15

Attachments


thanks i will check it out tomorrow
there is a bit more to the scaling as it needs to be flat shaded to flip normals which make each face unconnected and because of that i need to find all of the verts that are in the same place and add the normals together (as if it was smooth shaded)


face flipping with smooth shaded / flat shaded normal scaling without the doubles normals being added

and yes as i said in the info doc thing you can preload the outlines on scene start up or what ever but as for running it without a loading screen it could be done but it could have some undesired results depending on what your doing with it and how many outlines there were to make… like if you clicked an obj before it had finished generating the mesh

Why is there a need for fliping normals as it only affects shading
and the point of highlight is to have a shaderless glowing outline?
The inverting of normals was included in the thread as we weren’t sure for what you were planning to use it.

Could you supply the problematic model or similarly modified susanne(monkey)?
Don’t really get the how to reproduce the smooth shaded glitch or how the picture relates.

What i meant by fancier scaling is to : 1 move along normals,2 scale the face bigger,3 average the corners of different faces back together. 1 and 3 is trivial and done.

“heres a minimal (almost no) setup way of getting an outline effect on objects” its all on line one lol
i would like it to have no setup at all to work but for that it seems like i will need to rework bge itself

to get the smooth shaded glitch simply make an obj in the textmodels.blend smooth shaded and turn off the color change in makeOutline() to see it better
and the other image shows what happens if i just scale along the normal (skipping step 2 and 3)

the way i have it set up dos all steps 1,3 at the same time skipping 2 by moving each vertex along the normal of the average of the corners

Is this about the normals, as I sure hope not…?

to get the smooth shaded glitch simply make an obj in the textmodels.blend smooth shaded and turn off the color change in makeOutline() to see it better
and the other image shows what happens if i just scale along the normal (skipping step 2 and 3)

(wow, getting new models into your resource took time, hard code much?)
Reading the api makes things clear now:
http://www.blender.org/api/blender_python_api_2_73a_release/bge.types.KX_MeshProxy.html

To use mesh objects effectively, you should know a bit about how the game engine handles them.

  • 1Mesh Objects are converted from Blender at scene load.
  • 2The Converter groups polygons by Material. This means they can be sent to the renderer efficiently. A material holds:
    [LIST]
  • The texture.
  • The Blender material.
  • The Tile properties
  • The face properties - (From the “Texture Face” panel)
  • Transparency & z sorting
  • Light layer
  • Polygon shape (triangle/quad)
  • Game Object
  • 3Vertices will be split by face if necessary. Vertices can only be shared between faces if:

  • They are at the same position

  • 2UV coordinates are the same

  • Their normals are the same (both polygons are “Set Smooth”)

  • They are the same color, for example: a cube has 24 vertices: 6 faces with 4 vertices per face.

[/LIST]

Which translates into:
All faces are split(don’t share vertices) unless they violate rules in point 3.
A normal cube has 24 verts, while a smooth shaded one has 8 verts.
Meaning:
A simple uv light map unwrap(violate rule 3.2) solves it.

the way i have it set up dos all steps 1,3 at the same time skipping 2 by moving each vertex along the normal of the average of the corners

This is actually quite smart! :smiley:
Not well implemented, but probably solves the detail problem I had.
Will modify my script asap.

Is this about the normals, as I sure hope not…?

its about everything
i dont want people modding my game to need to worry about things getting the outline to work lol all they should need to do is give the right names and drop it in the folder as an obj or whatever and that’s that which is what i am aiming for

(wow, getting new models into your resource took time, hard code much?)

ya sorry about that i seemed to of overlooked it i will update it after i finish adapting it to my game to see what updates it needs

but for now you just need to have the obj added in the init.py and if you want it to be loaded from a difrent file change the file path on line 6 and line 113 of outline.py

Reading the api makes things clear now:

wow yes it does i did not know about that thanks

This is actually quite smart! :smiley:

thanks :smiley:
did you see the bit where i used sorted(zip()) to find doubles… at first i was using loops but it took over 5mins to make the outline for something and detailed as that sphere lol i was not expecting sorted(zip()) to work at all let alone be as fast as it is… i wonder how it works?

Am I to,understand you found a better way to compare items in a list?

outline.py


import time
from bge            import logic
from mathutils      import Vector
from collections    import defaultdict


scene = logic.getCurrentScene()


def makeUniqueMeshCopy(refObject, ObjId):


    objName         = refObject.name
    donorMeshName   = scene.objectsInactive[objName].meshes[0].name
    pre_mesh        = logic.LibNew("NewMesh"+str(time.time()), "Mesh", [donorMeshName]) 
    newObject       = scene.addObject(objName, refObject)
    
    newObject.replaceMesh(pre_mesh[0].name,True,True)     
    newObject.setParent(refObject,0,1) 
    
    return newObject 
    
def makeOutline(refObject,outline_size,color): 
    
    outlineObj              = makeUniqueMeshCopy(refObject, "outline")     
    refObject["outlineObj"] = outlineObj        
    outlineObj['mtype']     = 'outline'     
    outlineObj.color        = Vector( color )*10000
    
    mesh        = outlineObj.meshes[0]        
    pairDict    = defaultdict( list ) 
       
    for vertIndex in range(0, mesh.getVertexArrayLength(0),4 ):
           
        faceVerts       = [ mesh.getVertex(0, vI) for vI in range(vertIndex,vertIndex+4)]
        vertexPositions = [ (vert.XYZ,vert.normal)for vert in faceVerts ]        
        #calculate and assign new pos
        for index,vert in enumerate(reversed(faceVerts)):
            pos,normal = vertexPositions[ index ]          
            pairDict[tuple(pos)].append( (vert,normal) )             
    
    #assign average expanded positions
    for pos,pairs in pairDict.items():        
        pos             = Vector(pos)        
        averageNormal   = Vector()        
        for vert,normal in pairs:
            averageNormal += normal                        
        averageNormal   = averageNormal/len(pairs)
        newPos          =  pos + averageNormal*outline_size          
        for vert,normal in pairs:                     
            vert.XYZ = newPos     
                
    #recalculate mesh hitbox so you can click outline <- why?       
    #refObject.reinstancePhysicsMesh()          



useCaseExample.py



from bge        import logic
from outline    import makeOutline
import time
cont        = logic.getCurrentController()
own         = cont.owner
scene       = logic.getCurrentScene()


def main():
    
    rotateAllObjects()    
       
    leftButton  = cont.sensors['leftButton']
    mouseOver   = cont.sensors['mouseOver']
    
    if not 'init' in own:
        own['init'] = init()
        own["last"] = None
        
    if leftButton.positive and mouseOver.positive:
        hitObject = parent(mouseOver.hitObject)
        
        if hitObject.name in scene.objectsInactive:           
        
            if own["last"]:
                outline(own["last"],0)            
                
            if own["last"] != hitObject:            
                own["last"] = outline(hitObject,1)
                
            else:
                own["last"] = None
                
def outline(owner,toggle):
    if toggle:
        if not "outlineObj" in owner:         
            print("GenNewOutline")
            startTime = time.time()
            makeOutline(owner,0.1,(0,1,1,1))
            print ('GenTime:',time.time()-startTime)
        owner["outlineObj"].visible = 1
        print('OutlineActivated')   
    else:
        owner["outlineObj"].visible = 0
        print('OutlineRemoved')
    return owner
        
def parent(obj):
    if obj.parent: return parent( obj.parent )     
    return obj        
        
def rotateAllObjects():
    for object in scene.objects:    
        if 'mtype' in object and object['mtype']=='base':
            rot     =   object.localOrientation.to_euler()
            rot[2] +=.01
            object.localOrientation=rot
def init():
    
    file    =   logic.expandPath('//')+'testmodels.blend'
    try:
        #load external inactive base models if possible
        logic.LibLoad(file, 'Scene')
    except:
        print("DEBUG: no external blend file:",file," found")
        
    #add the inactive base models in to the scene
    for i,model in enumerate(scene.objectsInactive): 
        obj = scene.addObject( model.name, own)
        obj['mtype'] = 'base'
        x ,y = (i%5)*4-5,int(i/5)*4
        obj.worldPosition=[x,y,0]        
        print("object:",obj.name,"found")
        
main()

This takes at most 0.20s as opposed to 0.80s to create outline.
I have taken liberty in scrapping most of the code as it was a mess of object properties, scene object look ups and libnews…
Split the code into two: the outline script and an example of how to use it ( use case ).

I feel that a single blend would be easier for people to try out.
I moved the example models to the main file, but left the ability to load extra models from a blend.

Attachments

toonoutline.blend (3.47 MB)

I have been searching for a clean way to do this for some time for my own project.

Why does the logic spike so much on mouse over?

it would seem you are triggering more logic then needed? ( maybe add a object that runs the add highlight on mouse over and click?)

form the minimal testing i have done i think it sorts to best fit whatever is in the first zipped index but i could be wrong
it worked first try and it was some what faster than a for loop so i did not look into how lol

…and your comment changed lol
i think thats just how mouseover works as it seems to uses the objs mesh so them more polys the more the spike
i was using the logic brick way at first but that is even worse as it seems to be x ray and hit every obj behind the one you mouse over witch would kill my game lol

you can adapt the mouse.py script to activate logic bricks with the ‘this[‘clicked’]’ prop that it toggles

o ok your comment did not change there was another post and wow thats cool i will pick this apart then lol theres a few things i have ever seen before like ‘try’ and ‘except’

but from a quick click around it seems if you click the rightmost obj first the outline will be green and spam clicking the smooth shaded things only sometimes toggles the outline

Bugs?
Sometimes toggles?

About the color:
I didn’t get how the “shaderless” hack worked so I will whip up a shader script now.

Well, here it is:

def shaderless( mesh,color ):     
    VertexShader = """
       void main()  { 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;        
       }"""     
    FragmentShader = """
       uniform vec4 color;
       void main() {   
          gl_FragColor = color;         
       }"""  
    
    for mat in mesh.materials:
        shader = mat.getShader()
        if shader != None:
            if not shader.isValid():
                shader.setSource(VertexShader, FragmentShader, 1)  
                shader.setUniformfv('color',color)  

I wreck the shader -> shaderless.

Attachments

toonoutline.blend (3.46 MB)

ok so
1: in makeUniqueMeshCopy what is ObjId for ?
2: how do i add stuff from an external .blend ? it just seems to give it a monkey outline or just copy the mesh but appending does not have the same problems
3: uncommenting ‘refObject.reinstancePhysicsMesh()’ does nothing… i am assuming that ‘spam clicking’ glitch also has something to do with this?

dam it with this code my loading screens will be giving people epilepsy again lol also where can i find out more about making shader scripts ?

  1. OH!, forgot to delete it, heh :smiley:
    Obviously nothing.

2. haven’t tested.

  1. it does nothing. it does what you said, i didn’t get why it was needed, so i left it.
    ( why on earth would you want to click on the outline? )

3.2 The clicking bug has nothing to do with that.

I don’t have clicking bugs, i can smash mouse and everything works as it should.
If you could pin it down, that would be great. It works on my two pcs.

Is the shader having bugs?
What epilepsy?

learned everything from here:
http://en.wikibooks.org/wiki/GLSL_Programming/Blender

EDIT:

  1. OK. By having a blend named testmodels.blend ( basically your blend) next to the toonoutline.blend, it will import all the models from there.
    Basically what you did, but it’s optional now.

I see, each object needs a simple physics bounds “hit box” and the mesh parented to it?

60,000 triangles !

:stuck_out_tongue:

There is 6x my whole game in triangles here!

This is a showcase, or a stress test if you will.

The models are subdivided because that is the only sensible/failproof way to only have quads.

Yeah, the collision box should be there, but that would have made the initial asset creation bit more tedious.
This is also why the mouse over sensor gets mad when mouse is over the models.

Wasn’t a real issue, so will be dealing with it later.

EDIT: if you are at it, can you confirm / make sense of the clicking bug red has?

it’s worked great for me, except the logic spikes from the mesh size,

what distro are you on?

I am on win 7 - 32 bit

i am on win 7 64bit

i put the refObject.reinstancePhysicsMesh() thing there so it can tell if it was outlined by what mesh you clicked on as i dont know how to make the mouseover not see it and without it you would have 2 meshes in the same place so i would need to code more to tell what one you clicked and that saved me like 2 lines lol
but it looks like the problem with the clicking was from using blender 2.68 (the default blender that opens .blends for me) what v of blender are you using ?

sorry by ‘epilepsy’ i mean the loading screen is going to be so fast it will just flash lol

and that shader script is epic :smiley: now you dont need to click ‘object color’ or have the emit > 0 so now the question is can you import something other than a .blend in to bge?

and last thing when you tested the testmodels.blend did the outlines work ? i still just get the monkey

If it doesn’t look like this:
http://i.imgur.com/rM7Y21F.gif
(The outline size is excessive to make up for low gif size)

Re-install software or change hardware…

I am on blender 2.71 win7.