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)