DanisRace/Scripts/Opt.py
2025-03-16 04:47:56 +02:00

845 lines
31 KiB
Python

import bge
import math
import numpy
import mathutils
r = math.radians
from Scripts import Reuse
from Scripts.Common import *
# Chunks to store data about parts of the world
chunks = {}
bge.logic.globalDict["Opt.chunks"] = chunks
camspeed = 0.0
camposition = mathutils.Vector((0,0,0))
camspeedframe = 0.0
impact = {"test":None,
"lastTestTime":0,
"data":{},
"frame":0,
"timer":1,
"estimate":None}
def CalculateImpact(previous, current):
result = {}
for i in previous:
p = previous[i][0]
c = current [i][0]
result[i] = c - p
return result
def GoodFPS(func="", factor=1.0, boolean=True, traceback=False ):
# This will be used in an if statement before executing
# some, potentially intense work. And if the performance
# is dropped below a certain amount. It will stop executing
# that command.
settings = bge.logic.globalDict.get("settings", {})
frame = round(bge.logic.getRealTime(), 2)
currentMetrics = bge.logic.getProfileInfo()
targetFPS = settings.get("fps", 30)
if not boolean:
return bge.logic.getAverageFrameRate() / targetFPS
# We need to let the game initialize for a few seconds.
# So we limit all executions in that time.
if frame < 5: return 0
# Controlling the amount of how aggressively this function is executed.
optset = settings.get("opt", 1.0) ** 0.1
choice = numpy.random.choice([False, True], p=[optset, 1-optset])
if choice:
# Restraining over-bombardment.
if func in impact["data"]:
if impact["data"][func].get("lastframe",0) + impact["timer"] > frame:
return 0.0
return 1.0
# Calculating impact timer ( which will time how often one function can be
# exectuted ).
impact["timer"] = 10 - (10 * ( bge.logic.getAverageFrameRate() / targetFPS ))
# Recalculating if FPS goes too low.
# TODO: figure out a way to actually do that reasonably.
#if bge.logic.getAverageFrameRate() < targetFPS * 0.5:
# impact["data"] = {}
# We want to record FPS impacts of various executed things.
# 1 | On first 2 frames it should tell everyone to do nothing.
# 2 | On second frame it should record profile. And execute.
# 3 | Then on third comparing the profiles should give us impact.
if not impact["test"] and func not in impact["data"] and \
impact["lastTestTime"] + impact["timer"] < frame:
impact["test"] = {
"func" :func,
"stage" :0,
"frame" :frame,
"profile":None
}
# This is needed to not bombard the player with slow tests
# immediately.
impact["lastTestTime"] = frame
if impact["test"]:
test = impact["test"]
if test["stage"] == 0 and test["frame"] < frame:
test["frame"] = frame
test["stage"] = 1
if test["stage"] == 1:
test["frame"] = frame
test["profile"] = currentMetrics.copy()
test["stage"] = 2
return 1.0
if test["stage"] == 2 and test["frame"] < frame:
impactIs = CalculateImpact(test["profile"], currentMetrics)
impact["test"] = None
impact["data"][func] = impactIs
return 0.0
elif func in impact["data"]:
# If we have data about how agressive the changes are,
# we can use that data to estimate next frames FPS with it.
# If the function was recently activate, skip it.
if impact["data"][func].get("lastframe",0) + impact["timer"] > frame:
return 0.0
# Clearing out the estimates on new frames
if impact["frame"] != frame:
impact["frame"] = frame
impact["estimate"] = None
# Making a fresh estimate
if not impact["estimate"]:
impact["estimate"] = {}
for i in currentMetrics:
impact["estimate"][i] = currentMetrics[i][0]
# Creating a temporary estimate only for this function
tempEstimate = impact["estimate"].copy()
# Calculating it's function impact
totalMS = 0
for i in tempEstimate:
tempEstimate[i] += impact["data"][func][i]
totalMS += tempEstimate[i]
# Deciding whether we have speed to spare on this calculation
targetMS = 1 / ( ( targetFPS ) / 1000 )
if totalMS > targetMS:
return 0.0
else:
impact["estimate"] = tempEstimate
impact["data"][func]["lastframe"] = frame
return 1.0
def CameraSpeed():
# This function gives the average speed of the
# camera at any given frame.
frame = round(bge.logic.getRealTime(), 2)
global camspeed
global camposition
global camspeedframe
if frame != camspeedframe:
camspeedframe = frame
# Getting velocity of the camera
p = bge.logic.getCurrentScene().active_camera.worldPosition
v = p - camposition
camposition = p.copy()
# Averaging the velocity
av = 0
for i in v:
if i > 0: av += i
else : av -= i
av /= 3
# Storing the value
camspeed = av
# Returning the value
return camspeed
def ToFPS():
# Return a fraction that is useful to adjust FPS
# sensitive values like gravity.
return 60/bge.logic.getAverageFrameRate()
def Address(location, precision):
# This function will return an adress of any given location
# Which could be used to compare between objects to see if they
# are close enough together.
ret = ""
for axis in location[:2]:
ret = ret + str(round(axis/precision))+":"
return ret
def Surround(location, precision, camera=None):
# This function will give a list of addresses around a certain point.
ret = []
ORL = []
addedtypes = []
for axis in location[:2]:
ORL.append(round(axis/precision))
ret.append(Address(ORL, 1))
if not camera:
# X
if (location[0]/precision) - round(location[0]/precision) < 0.5:
ret.append(Address([ORL[0]-1, ORL[1]], 1))
addedtypes.append("-x")
elif (location[0]/precision) - round(location[0]/precision) > 0.5:
ret.append(Address([ORL[0]+1, ORL[1]], 1))
addedtypes.append("+x")
# Y
if (location[1]/precision) - round(location[1]/precision) < 0.5:
ret.append(Address([ORL[0], ORL[1]-1], 1))
addedtypes.append("-y")
elif (location[1]/precision) - round(location[1]/precision) > 0.5:
ret.append(Address([ORL[0], ORL[1]+1], 1))
addedtypes.append("+y")
# Diagonals
if "+x" in addedtypes and "+y" in addedtypes:
ret.append(Address([ORL[0]+1, ORL[1]+1], 1))
elif "-x" in addedtypes and "-y" in addedtypes:
ret.append(Address([ORL[0]-1, ORL[1]-1], 1))
elif "-x" in addedtypes and "+y" in addedtypes:
ret.append(Address([ORL[0]-1, ORL[1]+1], 1))
elif "+x" in addedtypes and "-y" in addedtypes:
ret.append(Address([ORL[0]+1, ORL[1]-1], 1))
else:
if r(70) >= camera[2] >= r(-70):
ret.append(Address([ORL[0], ORL[1]+1], 1))
if r(25) >= camera[2] >= r(-115):
ret.append(Address([ORL[0]+1, ORL[1]+1], 1))
if r(-20) >= camera[2] >= r(-160):
ret.append(Address([ORL[0]+1, ORL[1]], 1))
if r(-65) >= camera[2] >= r(-180) or r(180) > camera[2] >= r(155):
ret.append(Address([ORL[0]+1, ORL[1]-1], 1))
if r(-110) >= camera[2] >= r(-180) or r(180) > camera[2] >= r(110):
ret.append(Address([ORL[0], ORL[1]-1], 1))
if r(-155) >= camera[2] >= r(180) or r(180) > camera[2] >= r(65):
ret.append(Address([ORL[0]-1, ORL[1]-1], 1))
if r(160) >= camera[2] >= r(20):
ret.append(Address([ORL[0]-1, ORL[1]], 1))
if r(115) >= camera[2] >= r(-25):
ret.append(Address([ORL[0]-1, ORL[1]+1], 1))
return ret
previousSurround = {}
def SurroundChanged(key, surround):
# This returns whether surround was changed between frames
if key not in previousSurround:
previousSurround[key] = surround.copy()
changed = surround != previousSurround[key]
previousSurround[key] = surround.copy()
return changed
def RegisterObjects(object, precision, Type="LightSpawn"):
# Precalculate dynamically loading objects
# into their chunks, so they could be
# dynamically loaded.
settings = bge.logic.globalDict.get("settings", {})
if Type == "LightSpawn":
name = object["LightSpawn"]
if name == "LightStand":
if settings.get("poles"):
spawnObject = RegisterObject(object, precision)
spawnObject["name"] = object["LightSpawn"]
spawnObject["scaling"] = [1,1,1]
spawnObject["suspendDynamics"] = object.get("suspendDynamics", True)
else:
object.endObject()
elif "Gate" in name:
if settings.get("fences"):
spawnObject = RegisterObject(object, precision)
spawnObject["name"] = object["LightSpawn"]
spawnObject["scaling"] = [1,1,1]
spawnObject["suspendDynamics"] = object.get("suspendDynamics", True)
else:
object.endObject()
else:
spawnObject = RegisterObject(object, precision)
spawnObject["name"] = object["LightSpawn"]
spawnObject["scaling"] = [1,1,1]
spawnObject["suspendDynamics"] = object.get("suspendDynamics", True)
elif Type == "AreaLamp":
obj = object.blenderObject.data
spawnObject = RegisterObject(object, precision)
spawnObject["name"] = "Area_Lamp"
spawnObject["light_data"] = obj
elif Type == "Tree":
if settings.get("trees"):
treeObject = RegisterObject(object, precision)
treeObject["lods"] = {
"NormalTreeTrunk": 100,
#"TreeLowres": 200, # The model is ugly
"TreeBillboard": 5000
}
treeObject["name"] = "TreeBillboard"
treeObject["scaling"] /= 1.603
else:
object.endObject()
elif Type == "Palm":
if settings.get("trees"):
treeObject = RegisterObject(object, precision)
treeObject["lods"] = {
"NormalPalmTrunk": 100,
"PalmLow": 200,
"PalmCutout": 5000
}
treeObject["name"] = "PalmCutout"
treeObject["scaling"] /= 1.445
else:
object.endObject()
def RegisterObject(object, precision, delete=True):
# This will move an object from scene.objects into
# chunks. So that they could be stored without
# effecting the BGE Depsgraph performance.
# Creating chunk
addr = Address(object.position, precision)
if addr not in chunks:
chunks[addr] = {"loaded":False,
"objects":[]}
# Adding properties
virtualObject = {}
virtualObject["name"] = object.name
virtualObject["position"] = object.position.copy()
virtualObject["orientation"] = object.orientation.to_euler()
virtualObject["scaling"] = object.scaling.copy()
for i in object.blenderObject.game.properties:
virtualObject[i.name] = i.value
# Adding the object to the chunk
chunks[addr]["objects"].append(virtualObject)
# Deleting real object
if delete:
object.endObject()
# Returning
return virtualObject
def SortByDistance(l, cam, withdistance=False):
# This sorts a chunk by the distance
distanced = []
for n, i in enumerate(l):
d = cam.getDistanceTo(i["position"])
distanced.append([d, n, i])
distanced = sorted(distanced)
if withdistance:
return distanced
ret = []
for i in distanced:
ret.append(i[2])
return ret
def UpdateScene(camobject, precision, camera=None):
# This function will update the scene. According to the camera
# view.
location = camobject.position
# Getting addresses
addrs = Surround(location, precision, camera)
# Checking if any of addresses are not up to date.
for addr in chunks:
# Delete object if address not in view
# It is done in a separate loop and first,
# because we might need elements from it
# to spawn later. Otherwise we might cache
# way too many objects into memory.
if addr not in addrs and chunks[addr]["loaded"]:
for object in chunks[addr]["objects"]:
if object.get("object"):
Reuse.Delete(object["object"])
object["object"] = None
chunks[addr]["loaded"] = False
# Breaking to skip to next frame and
# re-evaluate whether the FPS is good
return
lookthroughchunks = chunks
if addrs[0] in chunks:
lookthroughchunks = [chunks[addrs[0]]]+list(chunks.keys())
# Fixing random items at the chunks.
for i in range(5):
addr = random.choice(list(chunks.keys()))
item = random.choice(chunks[addr]["objects"])
if item.get("broken"):
item["broken"] = False
print(consoleForm(item["name"]), "Fixed")
for addr in chunks:
# Create objects when addres in view
if addr in addrs and not chunks[addr]["loaded"]:
for object in SortByDistance(chunks[addr]["objects"], camobject):
if not object.get("object") and not object.get("broken"):
object["object"] = Reuse.Create(object["name"])
#object["object"].position = object["position"]
Reuse.Move(object["object"], object["position"])
object["object"].orientation = object["orientation"]
object["object"].scaling = object["scaling"]
# Some objects have a height adjustment value
#object["object"].position[2] += object["object"].get("movez", 0)
object["object"].applyMovement( (0, 0, object["object"].get("movez", 0) ) )
# Some effects require a reference point
object["object"]["spawned_by"] = object
# Some objects need dynamics to be suspended
if object.get("suspendDynamics"):
object["object"].suspendDynamics(True)
# Some objects might change mesh during game
if object["object"].get("good"):
object["object"].replaceMesh(object["object"]["good"])
# Some objects are lights and have light data
if object.get("light_data"):
object["object"].blenderObject.data = object["light_data"]
chunks[addr]["loaded"] = True
# Breaking to skip to next frame and
# re-evaluate whether the FPS is good
return
# Level Of Details for spawn objects
# This is needed because normal LOD system
# with those objects sometimes gives a
# segmentation fault.
elif addr in addrs and chunks[addr]["loaded"]:
sortedObjects = SortByDistance(chunks[addr]["objects"], camobject, True)
# closestUnspawned = [1000000, 0, {}]
# for object in sortedObjects:
# if not object[2].get("object") and object[0] < closestUnspawned[0]:
# closestUnspawned = object
#
# if Reuse.reuse.get(closestUnspawned[2].get("name")):
# closestUnspawned[2]["object"] = Reuse.Create(closestUnspawned[2].get("name"))
# closestUnspawned[2]["object"].position = closestUnspawned[2]["position"]
# closestUnspawned[2]["object"].orientation = closestUnspawned[2]["orientation"]
# closestUnspawned[2]["object"].scaling = closestUnspawned[2]["scaling"]
#
# # Some objects have a height adjustment value
# closestUnspawned[2]["object"].position[2] += closestUnspawned[2]["object"].get("movez", 0)
#
# # Some effects require a reference point
# closestUnspawned[2]["object"]["spawned_by"] = closestUnspawned[2]
#
# # Some objects need dynamics to be suspended
# if closestUnspawned[2].get("suspendDynamics"):
# closestUnspawned[2]["object"].suspendDynamics(True)
for distance, n, object in sortedObjects:
#
# if object["name"] == closestUnspawned[2].get("name") and distance > closestUnspawned[0] and object.get("object"):
# Reuse.Delete(object["object"])
# object["object"] = None
# Optimizing Dani not being to go through benches
if object.get("tempDynamics"):
object["object"].suspendDynamics()
object["tempDynamics"] = False
if "lods" in object and object.get("object"):
distance = object["object"].getDistanceTo(location)
for lod in object["lods"]:
lodDistance = object["lods"][lod]
if int(distance) <= lodDistance:
break
if object["object"].name != lod:
Reuse.Delete(object["object"])
object["object"] = Reuse.Create(lod)
Reuse.Move(object["object"], object["position"])
#object["object"].position = object["position"]
object["object"].orientation = object["orientation"]
object["object"].scaling = object["scaling"]
# Some objects have a height adjustment value
object["object"].applyMovement( (0, 0, object["object"].get("movez", 0) ) )
#object["object"].position[2] += object["object"].get("movez", 0)
# Some effects require a reference point
object["object"]["spawned_by"] = object
# Some objects need dynamics to be suspended
if object.get("suspendDynamics"):
object["object"].suspendDynamics(True)
taskschedule = {}
def ScheduleTask(taskname, FPSfactor, task, *args):
# This function adds a function to the schedule
taskObject = {"FPSfactor":FPSfactor,
"task":task,
"args":args}
taskschedule[taskname] = taskObject
def ExecuteScheduled():
# This function executes a scheduled task
# for taskname in taskschedule:
# print("Scheduled:", taskname)
# print()
# Trying to execute something
for taskname in taskschedule:
taskObject = taskschedule[taskname]
if GoodFPS("[Scheduled] "+taskname, taskObject["FPSfactor"]):
del taskschedule[taskname]
taskObject["task"](*taskObject["args"])
return
def Force(value):
# This function adjusts acceleration
# to fps
return value * ( ToFPS() ** 2 )
def Velocity(value):
# This function adjusts velocity
# to fps
return value * ToFPS()
def PrintTime():
# This function is used for testing
print(bge.logic.getRealTime())
def BuildingsLOD():
# Function deals with levels of details
# of buildings.
scene = bge.logic.getCurrentScene()
dani = scene.objects["Dani_Box"]
cam = scene.active_camera
if "LODchunks" not in bge.logic.globalDict:
# bge.logic.globalDict["LODchunks"] = {"TheRacetrack":{
# "object":scene.objects["TheRacetrack"],
# "high":"TheRacetrackHigh",
# "radius":1000,
# "now":"high",
# "name":"Papses Racetrack"
# }, #
# "TheCity":{
# "object":scene.objects["TheCity"],
# "high":"TheCityHigh",
# "radius":1500,
# "now":"high",
# "name":"Dune Town"
# }, #
# "TheHouse":{
# "object":scene.objects["TheHouse"],
# "high":"TheHouseGood",
# "low":"TheHouseBorked",
# "parent":"TheCity",
# "radius":200,
# "now":"high",
# "name":"Looparound 8\nDani's Home"
# }, #
# "HallwayPictures":{
# "object":scene.objects["HallwayPictures"],
# "high":"HallwayPictures",
# "parent":"TheHouse",
# "radius":180,
# "now":"high"
# },
# "Computer":{
# "object":scene.objects["Computer"],
# "high":"Computer",
# "parent":"TheHouse",
# "radius":180,
# "now":"high"
# },
# "Just3000Wreck":{
# "object":scene.objects["Just3000Wreck"],
# "high":"Just3000Wreck",
# "parent":"TheHouse",
# "radius":180,
# "now":"high"
# },
# "KartingTrack":{
# "object":scene.objects["KartingTrack"],
# "high":"KartingTrack_High",
# "low":"KartingTrack_Low",
# "parent":"TheCity",
# "radius":200,
# "now":"high",
# "name":"Karting Track"
# },
# "Karting_Decorations":{
# "object":scene.objects["Karting_Decorations"],
# "high":"Karting_Decorations",
# "parent":"KartingTrack",
# "radius":180,
# "now":"high"
# },
# "KartingGamesCollider":{
# "object":scene.objects["KartingGamesCollider"],
# "high":"KartingGamesCollider",
# "parent":"KartingTrack",
# "radius":180,
# "now":"high"
# },
# "KartingGamesObject":{
# "object":scene.objects["KartingGamesObject"],
# "high":"KartingGamesObject",
# "parent":"KartingTrack",
# "radius":180,
# "now":"high"
# },
# "PoliceStation":{
# "object":scene.objects["PoliceStation"],
# "high":"PoliceStationHigh",
# "low":"PoliceStationLow",
# "parent":"TheCity",
# "radius":180,
# "now":"high",
# "name":"Police Station"
# },
# "PoliceStation_Ghost":{
# "object":scene.objects["PoliceStation_Ghost"],
# "high":"PoliceStation_Ghost",
# "parent":"PoliceStation",
# "radius":180,
# "now":"high"
# },
# }
bge.logic.globalDict["LODchunks"] = {}
for object in scene.objects:
if "LOD" in object:
lod = {}
for key in ["high", "low", "parent", "radius", "name"]:
if "LOD_"+key in object:
lod[key] = object["LOD_"+key]
lod["now"] = "high"
lod["object"] = object
lod["name_radius"] = lod.get("radius", 100)*0.8
bge.logic.globalDict["LODchunks"][object["LOD"]] = lod
bge.logic.globalDict["LODcurrent"] = "Home"
chunks = bge.logic.globalDict["LODchunks"]
for chunkname in chunks:
chunk = chunks[chunkname]
# Making sure to not overdo the LOD system
if chunk.get("timer", 0) + 5 > bge.logic.getRealTime():
continue
chunk["timer"] = bge.logic.getRealTime()
# Making low when out of bounds
forcehide = False
if chunks.get(chunk.get("parent", ""), {}).get("now") == "low":
forcehide = True
if chunk["object"].visible:
chunk["now"] = "high"
if chunk["now"] == "low" and dani.getDistanceTo(chunk["object"]) < chunk["radius"]:
print(consoleForm(chunk["object"]), "is now highres.")
if "low" in chunk:
chunk["object"].replaceMesh(chunk["high"])
chunk["object"].visible = True
chunk["object"].restorePhysics()
chunk["now"] = "high"
# if "name" in chunk:
# bge.logic.globalDict["print"] = chunk["name"]
elif chunk["now"] == "high" and ( dani.getDistanceTo(chunk["object"]) > chunk["radius"] or forcehide):
print(consoleForm(chunk["object"]), "is now lowres.")
if "low" in chunk and not forcehide:
print("making mesh low")
chunk["object"].replaceMesh(chunk["low"])
else:
print("hiding mesh")
chunk["object"].visible = False
if not dani.get("race"):
chunk["object"].suspendPhysics()
chunk["now"] = "low"
if chunk["now"] == "high" and chunk.get("name") \
and bge.logic.globalDict["LODcurrent"] != chunk.get("name")\
and dani.getDistanceTo(chunk["object"]) < chunk["name_radius"]:
bge.logic.globalDict["print"] = chunk["name"]
bge.logic.globalDict["LODcurrent"] = chunk["name"]
def Precalculate():
# Precalculating objects for smoothness
settings = bge.logic.globalDict.get("settings", {})
preData = {
"House_Shelf":4,
"Moria's Bed":1,
"Moria's Bed.001":1,
"Sparkle": 100,
"Smoke": 20,
"Fire": 200,
"NitroCone":5,
"Road Blocker": 12,
"Metal_Bench_Seat":15,
"Metal_Bench_Beam":3,
"Metal_Bench_Handle":6
}
if settings.get("trees"):
preData["NormalTreeTrunk"] = 30
preData["TreeBillboard"] = 50
preData["PalmCutout"] = 32
preData["PalmLow"] = 16
preData["NormalPalmTrunk"] = 16
if settings.get("poles"):
preData["LightStand"] = 50
preData["LightStand.Borked.Head"] = 5
preData["LightStand.Borked.Tail"] = 5
if settings.get("fences"):
preData["MetalGate_good"] = 100
preData["GatePart"] = 100
preData["GatePart.Level0"] = 50
preData["GatePart.Level1"] = 50
bge.logic.globalDict["preData"] = preData
print("Precalculating... ")
for n, object in enumerate(preData):
for i in range( preData[object] ):
Reuse.Create(object, selfDestructFrames = 1, selfDestructInactive = False )