2024-07-13 15:15:50 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
2024-08-27 14:34:26 +02:00
|
|
|
|
2024-07-13 15:15:50 +02:00
|
|
|
# Global Variables for GoodFPS function to work.
|
|
|
|
fpshistory = {} # History of last 200 or so frames FPS
|
|
|
|
targetFPS = 1 # Target FPS which to aim to preserve
|
|
|
|
FPStargets = {} # Database of how much a given function effects FPS
|
|
|
|
testingTarget = False # Fuction that is being tested for how much it effects FPS
|
|
|
|
testingTargetFrame = 0 # Frame where the test is started
|
|
|
|
testFPS = 60 # The FPS before the test started
|
|
|
|
limitframe = 0 # The Frame that is currently being calculated for expected FPS
|
|
|
|
expectedfps = 60 # Expected FPS with running functions tested effects removed from current FPS
|
|
|
|
|
|
|
|
|
|
|
|
def GoodFPS(func="", factor=1, boolean=True, traceback=False, ):
|
|
|
|
|
2024-08-27 14:34:26 +02:00
|
|
|
settings = bge.logic.globalDict.get("settings", {})
|
|
|
|
|
2024-07-13 15:15:50 +02:00
|
|
|
|
2024-08-27 14:34:26 +02:00
|
|
|
|
2024-07-13 15:15:50 +02:00
|
|
|
global targetFPS
|
|
|
|
|
|
|
|
# Recording the framerate into buffer
|
|
|
|
# This buffer will be used to regulate the target FPS
|
|
|
|
|
|
|
|
fps = bge.logic.getAverageFrameRate()
|
|
|
|
frame = round(bge.logic.getRealTime(), 2)
|
|
|
|
camspeed = min(CameraSpeed(), 1)
|
|
|
|
global fpshistory
|
|
|
|
fpshistory[frame] = fps
|
|
|
|
|
|
|
|
# Limiting execution based on camera speed.
|
|
|
|
if boolean and numpy.random.choice([True, False], p=[camspeed, 1-camspeed]):
|
|
|
|
return False
|
|
|
|
|
|
|
|
# Setting the target fps
|
|
|
|
if frame > 5:# and fps > targetFPS:
|
2024-08-27 14:34:26 +02:00
|
|
|
|
|
|
|
if not settings.get("autofps"):
|
|
|
|
targetFPS = bge.logic.getLogicTicRate()
|
|
|
|
|
|
|
|
else:
|
|
|
|
targetFPS = max(fpshistory.values())
|
|
|
|
targetFPS *= 1.1
|
|
|
|
if not boolean and int(frame) % 30 == 0:
|
|
|
|
bge.logic.setLogicTicRate(int(targetFPS))
|
|
|
|
|
|
|
|
|
2024-07-13 15:15:50 +02:00
|
|
|
# Slicing buffer to contain only last 300 records
|
|
|
|
newfpshistory = {}
|
|
|
|
for i in list(fpshistory.keys())[-300:]:
|
|
|
|
newfpshistory[i] = fpshistory[i]
|
|
|
|
fpshistory = newfpshistory
|
|
|
|
|
|
|
|
# Calculating the fraction ( of how much are we within the target FPS )
|
|
|
|
fraction = ( sum( min(f, targetFPS) for f in fpshistory.values()) / len(fpshistory) ) / targetFPS
|
|
|
|
|
|
|
|
# Testing FPS impacts of various functions to see
|
|
|
|
# how much to limit them.
|
|
|
|
global FPStargets
|
|
|
|
global testFPS
|
|
|
|
global testingTarget
|
|
|
|
global testingTargetFrame
|
2024-08-27 14:34:26 +02:00
|
|
|
|
|
|
|
# Controlling the amount of how aggressively this function is executed.
|
|
|
|
optset = settings.get("opt", 1.0)
|
|
|
|
choice = numpy.random.choice([False, True], p=[optset, 1-optset])
|
|
|
|
if choice:
|
|
|
|
if boolean: return True
|
|
|
|
else: return 1.0
|
|
|
|
|
2024-07-13 15:15:50 +02:00
|
|
|
|
|
|
|
# This runs when the target for testing is already setup
|
|
|
|
if testingTarget:
|
|
|
|
|
|
|
|
# If we have some decrease in performace, we calculate how much it effects the FPS
|
|
|
|
if frame > testingTargetFrame and fps+1 < testFPS:
|
|
|
|
FPStargets[testingTarget] = {"fraction":fps / testFPS, "frame":frame}
|
|
|
|
testingTarget = False
|
|
|
|
|
|
|
|
# Otherwise we cancel the test
|
|
|
|
elif frame -2 > testingTargetFrame:
|
|
|
|
testingTarget = False
|
|
|
|
|
|
|
|
# While we testing, we want to return False, so nothing else will effect the FPS
|
|
|
|
if boolean: return False
|
|
|
|
else: return 0.0
|
|
|
|
|
|
|
|
# This runs to start the test
|
|
|
|
if ( func and func not in FPStargets and 10 < frame or FPStargets.get(func, {}).get("frame", frame) + 60 < frame ) and fraction > factor:
|
|
|
|
testFPS = fps
|
|
|
|
testingTarget = func
|
|
|
|
testingTargetFrame = frame
|
|
|
|
|
|
|
|
# And we want to return True so the function will get launched.
|
|
|
|
if boolean: return True
|
|
|
|
else: return 1.0
|
|
|
|
|
|
|
|
# Limiting functions based on the tests
|
|
|
|
global limitframe
|
|
|
|
global expectedfps
|
|
|
|
|
|
|
|
# Findinf the limiting factor
|
|
|
|
try:
|
|
|
|
targetFactor = min(n.get("fraction") for n in FPStargets.values()) * factor
|
|
|
|
except:
|
|
|
|
targetFactor = factor
|
2024-08-27 14:34:26 +02:00
|
|
|
|
2024-07-13 15:15:50 +02:00
|
|
|
# Limiting
|
|
|
|
if expectedfps * FPStargets.get(func, {}).get("fraction", 1) < targetFPS * targetFactor:
|
|
|
|
if boolean: return False
|
|
|
|
|
|
|
|
# If we allow the function to run
|
|
|
|
# we want to recalculate the expected FPS
|
|
|
|
# based on the tests
|
|
|
|
if limitframe != frame:
|
|
|
|
|
|
|
|
# If the tests data is flawed
|
|
|
|
if int(expectedfps) > int(fps)+2:
|
|
|
|
FPStargets = {}
|
|
|
|
|
|
|
|
# Calculating expected FPS
|
|
|
|
expectedfps = fps*FPStargets.get(func, {}).get("fraction", 1)
|
|
|
|
limitframe = frame
|
|
|
|
else:
|
|
|
|
expectedfps *= FPStargets.get(func, {}).get("fraction", 1)
|
|
|
|
|
|
|
|
# Returning
|
|
|
|
if boolean:
|
|
|
|
return True#fraction > factor
|
|
|
|
else:
|
|
|
|
return fraction
|
|
|
|
|
|
|
|
|
|
|
|
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 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"]
|
|
|
|
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)
|
|
|
|
|
|
|
|
# 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"])
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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)
|
|
|
|
object["object"].position = 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)
|
|
|
|
|
|
|
|
# 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())
|