Fix scene collision mesh generation
This commit is contained in:
parent
ac2d76cd01
commit
478acb1f54
@ -30,6 +30,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"objectId" : "geometrytest1",
|
||||||
|
"modelPath" : "Models/geometry1.fbx",
|
||||||
|
"tokens" : [
|
||||||
|
"DISABLE_COLLISION_REACTION",
|
||||||
|
"GENERATE_COLLISION_TERRAIN"
|
||||||
|
],
|
||||||
|
"collidable": null,
|
||||||
|
"graphicsTemplate": null
|
||||||
}
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|||||||
BIN
assets/Models/geometry1.fbx
Normal file
BIN
assets/Models/geometry1.fbx
Normal file
Binary file not shown.
Binary file not shown.
@ -3,9 +3,9 @@
|
|||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"subtype": "terrain1",
|
"subtype": "terrain1",
|
||||||
"posX": 3,
|
"posX": 2,
|
||||||
"posY": 5,
|
"posY": 1,
|
||||||
"posZ": 5,
|
"posZ": 2,
|
||||||
"rotX": -0.7071068,
|
"rotX": -0.7071068,
|
||||||
"rotY": 0,
|
"rotY": 0,
|
||||||
"rotZ": 0,
|
"rotZ": 0,
|
||||||
|
|||||||
@ -761,11 +761,11 @@ public class LoadingThread extends Thread {
|
|||||||
// EntityUtils.getPosition(myCube).set(3,1,3);
|
// EntityUtils.getPosition(myCube).set(3,1,3);
|
||||||
|
|
||||||
//work on smoke shader
|
//work on smoke shader
|
||||||
Entity myCube = EntityUtils.spawnDrawableEntity("Models/unitcube.fbx");
|
// Entity myCube = EntityUtils.spawnDrawableEntity("Models/unitcube.fbx");
|
||||||
EntityUtils.getActor(myCube).maskShader("Cube", "Shaders/smoke1/smoke1.vs", "Shaders/smoke1/smoke1.fs");
|
// EntityUtils.getActor(myCube).maskShader("Cube", "Shaders/smoke1/smoke1.vs", "Shaders/smoke1/smoke1.fs");
|
||||||
Globals.assetManager.addShaderToQueue("Shaders/smoke1/smoke1.vs", "Shaders/smoke1/smoke1.fs");
|
// Globals.assetManager.addShaderToQueue("Shaders/smoke1/smoke1.vs", "Shaders/smoke1/smoke1.fs");
|
||||||
myCube.putData(EntityDataStrings.DRAW_TRANSPARENT_PASS, true);
|
// myCube.putData(EntityDataStrings.DRAW_TRANSPARENT_PASS, true);
|
||||||
EntityUtils.getPosition(myCube).set(3,1,3);
|
// EntityUtils.getPosition(myCube).set(3,1,3);
|
||||||
|
|
||||||
SceneLoader loader = new SceneLoader();
|
SceneLoader loader = new SceneLoader();
|
||||||
loader.serverInstantiateSceneFile("Scenes/testscene1/testscene1.json");
|
loader.serverInstantiateSceneFile("Scenes/testscene1/testscene1.json");
|
||||||
|
|||||||
@ -56,7 +56,7 @@ public class AssetManager {
|
|||||||
if(physicsMeshesToLoad.contains(currentPath)){
|
if(physicsMeshesToLoad.contains(currentPath)){
|
||||||
//create physics
|
//create physics
|
||||||
physicsMeshesToLoad.remove(currentPath);
|
physicsMeshesToLoad.remove(currentPath);
|
||||||
physicsMeshesLoadedIntoMemory.put(currentPath,PhysicsUtils.generateRigidBodyFromAISCene(scene));
|
physicsMeshesLoadedIntoMemory.put(currentPath,PhysicsUtils.generateRigidBodyFromAIScene(scene));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(String currentPath : texturesInQueue){
|
for(String currentPath : texturesInQueue){
|
||||||
@ -282,6 +282,18 @@ public class AssetManager {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
//COLLISION MESH
|
||||||
|
//
|
||||||
|
public void addCollisionMeshToQueue(String path){
|
||||||
|
if(!physicsMeshesToLoad.contains(path) && !physicsMeshesLoadedIntoMemory.containsKey(path)){
|
||||||
|
physicsMeshesToLoad.add(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CollisionObject fetchCollisionObject(String path){
|
||||||
|
return physicsMeshesLoadedIntoMemory.get(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -170,6 +170,37 @@ public class CollisionObjUtils {
|
|||||||
return rVal;
|
return rVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach a collision object to a provided entity
|
||||||
|
* @param entity The entity to attach a collision object to
|
||||||
|
* @param collisionObject The jBulletF collision object to attach to the entity
|
||||||
|
* @param mass The mass of the collidable
|
||||||
|
* @param collidableType The type of collidable we are attaching. For instance, "Terrain", "Creature", "Item", etc. Refer to Collidable class for options.
|
||||||
|
*/
|
||||||
|
public static void attachCollisionObjectToEntity(Entity entity, CollisionObject collisionObject, float mass, String collidableType){
|
||||||
|
Vector3d position = EntityUtils.getPosition(entity);
|
||||||
|
Vector3f scale = EntityUtils.getScale(entity);
|
||||||
|
Collidable collidable = new Collidable(entity, collidableType);
|
||||||
|
Globals.collisionEngine.registerCollisionObject(collisionObject, collidable);
|
||||||
|
|
||||||
|
Globals.collisionEngine.registerPhysicsEntity(entity);
|
||||||
|
entity.putData(EntityDataStrings.PHYSICS_COLLISION_BODY, collisionObject);
|
||||||
|
|
||||||
|
//update world transform of collision object
|
||||||
|
positionCharacter(entity,position);
|
||||||
|
|
||||||
|
entity.putData(EntityDataStrings.COLLISION_ENTITY_COLLISION_OBJECT, collisionObject);
|
||||||
|
entity.putData(EntityDataStrings.COLLISION_ENTITY_COLLIDABLE, collidable);
|
||||||
|
entity.putData(EntityDataStrings.PHYSICS_MASS, mass);
|
||||||
|
//inertia tensor
|
||||||
|
//https://scienceworld.wolfram.com/physics/MomentofInertiaCylinder.html
|
||||||
|
Matrix4f inertiaTensor = new Matrix4f();
|
||||||
|
inertiaTensor.m00(mass * scale.y * scale.y / 12.0f + mass * scale.x * scale.x / 4.0f);
|
||||||
|
inertiaTensor.m11(mass * scale.y * scale.y / 12.0f + mass * scale.x * scale.x / 4.0f);
|
||||||
|
inertiaTensor.m22(mass * scale.x * scale.x / 2.0f);
|
||||||
|
entity.putData(EntityDataStrings.PHYSICS_INVERSE_INERTIA_TENSOR, inertiaTensor.invert());
|
||||||
|
}
|
||||||
|
|
||||||
public static CollisionObject getCollisionBody(Entity e){
|
public static CollisionObject getCollisionBody(Entity e){
|
||||||
return (CollisionObject)e.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
|
return (CollisionObject)e.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import electrosphere.collision.dispatch.CollisionObject;
|
|||||||
import electrosphere.entity.Entity;
|
import electrosphere.entity.Entity;
|
||||||
import electrosphere.entity.EntityDataStrings;
|
import electrosphere.entity.EntityDataStrings;
|
||||||
import electrosphere.entity.EntityUtils;
|
import electrosphere.entity.EntityUtils;
|
||||||
|
import electrosphere.entity.state.BehaviorTree;
|
||||||
import electrosphere.entity.state.IdleTree;
|
import electrosphere.entity.state.IdleTree;
|
||||||
import electrosphere.entity.state.collidable.CollidableTree;
|
import electrosphere.entity.state.collidable.CollidableTree;
|
||||||
import electrosphere.entity.state.gravity.GravityTree;
|
import electrosphere.entity.state.gravity.GravityTree;
|
||||||
@ -36,8 +37,25 @@ public class ObjectUtils {
|
|||||||
case "DISABLE_COLLISION_REACTION": {
|
case "DISABLE_COLLISION_REACTION": {
|
||||||
collisionMakeDynamic = false;
|
collisionMakeDynamic = false;
|
||||||
} break;
|
} break;
|
||||||
|
case "GENERATE_COLLISION_OBJECT": {
|
||||||
|
Globals.assetManager.addCollisionMeshToQueue(rawType.getModelPath());
|
||||||
|
Globals.entityManager.registerBehaviorTree(new BehaviorTree() {public void simulate() {
|
||||||
|
CollisionObject collisionObject = Globals.assetManager.fetchCollisionObject(rawType.getModelPath());
|
||||||
|
if(collisionObject != null){
|
||||||
|
Globals.entityManager.removeBehaviorTree(this);
|
||||||
|
CollisionObjUtils.attachCollisionObjectToEntity(rVal, collisionObject, 0, Collidable.TYPE_OBJECT);
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
} break;
|
||||||
case "GENERATE_COLLISION_TERRAIN": {
|
case "GENERATE_COLLISION_TERRAIN": {
|
||||||
|
Globals.assetManager.addCollisionMeshToQueue(rawType.getModelPath());
|
||||||
|
Globals.entityManager.registerBehaviorTree(new BehaviorTree() {public void simulate() {
|
||||||
|
CollisionObject collisionObject = Globals.assetManager.fetchCollisionObject(rawType.getModelPath());
|
||||||
|
if(collisionObject != null){
|
||||||
|
Globals.entityManager.removeBehaviorTree(this);
|
||||||
|
CollisionObjUtils.attachCollisionObjectToEntity(rVal, collisionObject, 0, Collidable.TYPE_TERRAIN);
|
||||||
|
}
|
||||||
|
}});
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -178,32 +178,54 @@ public class PhysicsUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static RigidBody generateRigidBodyFromAISCene(AIScene scene){
|
//
|
||||||
|
// This has to use arrays and I have no fucking clue why
|
||||||
Vector3d position = EntityUtils.getPosition(terrain);
|
// If you buffer one at a time it just breaks???????????
|
||||||
|
//
|
||||||
int arrayLength = heightfield.length;
|
/**
|
||||||
int arrayWidth = heightfield[0].length;
|
* Generates a rigid body from an AIScene
|
||||||
|
* @param scene The AIScene to generate a rigid body off of
|
||||||
|
* @return A rigid body based on the AIScene
|
||||||
|
*/
|
||||||
|
public static RigidBody generateRigidBodyFromAIScene(AIScene scene){
|
||||||
|
|
||||||
|
//was 0.08
|
||||||
float collisionMargin = 0.08f;
|
float collisionMargin = 0.08f;
|
||||||
|
|
||||||
/*
|
|
||||||
Traditional buffer code not working for some reason
|
|
||||||
the approach of
|
//http://jbullet.advel.cz/javadoc/com/bulletphysics/collision/shpaes/TriangleIndexVertexArray.html
|
||||||
https://stackoverflow.com/questions/40855945/lwjgl-mesh-to-jbullet-collider
|
electrosphere.collision.shapes.TriangleIndexVertexArray triangleIndexArray = new electrosphere.collision.shapes.TriangleIndexVertexArray();
|
||||||
works much better
|
|
||||||
IDK why
|
|
||||||
*/
|
|
||||||
|
|
||||||
PointerBuffer meshesBuffer = scene.mMeshes();
|
PointerBuffer meshesBuffer = scene.mMeshes();
|
||||||
while(meshesBuffer.hasRemaining()){
|
while(meshesBuffer.hasRemaining()){
|
||||||
AIMesh aiMesh = AIMesh.create(meshesBuffer.get());
|
AIMesh aiMesh = AIMesh.create(meshesBuffer.get());
|
||||||
|
//create indexed mesh
|
||||||
|
//http://jbullet.advel.cz/javadoc/com/bulletphysics/collision/shapes/IndexedMesh.html
|
||||||
|
electrosphere.collision.shapes.IndexedMesh indexedMesh = new electrosphere.collision.shapes.IndexedMesh();
|
||||||
|
//allocate buffer for vertices
|
||||||
|
indexedMesh.vertexBase = ByteBuffer.allocateDirect(aiMesh.mNumVertices()*3*Float.BYTES).order(ByteOrder.nativeOrder());
|
||||||
|
indexedMesh.numVertices = aiMesh.mNumVertices();
|
||||||
|
indexedMesh.vertexStride = 3 * Float.BYTES;
|
||||||
|
float[] verts = new float[indexedMesh.numVertices * 3];
|
||||||
//read vertices
|
//read vertices
|
||||||
AIVector3D.Buffer vertexBuffer = aiMesh.mVertices();
|
AIVector3D.Buffer vertexBuffer = aiMesh.mVertices();
|
||||||
|
int vertPos = 0;
|
||||||
while(vertexBuffer.hasRemaining()){
|
while(vertexBuffer.hasRemaining()){
|
||||||
vertexBuffer.get();
|
AIVector3D vector = vertexBuffer.get();
|
||||||
// numVertices++;
|
verts[vertPos+0] = vector.x();
|
||||||
|
verts[vertPos+1] = vector.y();
|
||||||
|
verts[vertPos+2] = vector.z();
|
||||||
|
vertPos = vertPos + 3;
|
||||||
}
|
}
|
||||||
|
indexedMesh.vertexBase.asFloatBuffer().put(verts);
|
||||||
|
//allocate buffer for indices
|
||||||
|
indexedMesh.triangleIndexBase = ByteBuffer.allocateDirect(aiMesh.mNumFaces()*3*Float.BYTES).order(ByteOrder.nativeOrder());
|
||||||
|
indexedMesh.numTriangles = aiMesh.mNumFaces();
|
||||||
|
indexedMesh.triangleIndexStride = 3 * Float.BYTES;
|
||||||
|
int[] indices = new int[indexedMesh.numTriangles * 3];
|
||||||
|
int indicesPos = 0;
|
||||||
//read faces
|
//read faces
|
||||||
AIFace.Buffer faceBuffer = aiMesh.mFaces();
|
AIFace.Buffer faceBuffer = aiMesh.mFaces();
|
||||||
while(faceBuffer.hasRemaining()){
|
while(faceBuffer.hasRemaining()){
|
||||||
@ -211,85 +233,19 @@ public class PhysicsUtils {
|
|||||||
IntBuffer indexBuffer = currentFace.mIndices();
|
IntBuffer indexBuffer = currentFace.mIndices();
|
||||||
while(indexBuffer.hasRemaining()){
|
while(indexBuffer.hasRemaining()){
|
||||||
int index = indexBuffer.get();
|
int index = indexBuffer.get();
|
||||||
|
indices[indicesPos] = index;
|
||||||
|
indicesPos++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int numberTriangles = (arrayLength - 1) * (arrayWidth - 1) * 2;
|
|
||||||
int triangleStride = 0;
|
|
||||||
|
|
||||||
int numberVertices = arrayLength * arrayWidth;
|
|
||||||
int vertexStride = 0;
|
|
||||||
|
|
||||||
float[] vertices = new float[numberVertices * 3];
|
|
||||||
int vertexInserterPos = 0;
|
|
||||||
int[] indices = new int[numberTriangles * 3];
|
|
||||||
int indexInserterPos = 0;
|
|
||||||
|
|
||||||
for(int x = 0; x < arrayLength; x++){
|
|
||||||
for(int y = 0; y < arrayWidth; y++){
|
|
||||||
vertices[vertexInserterPos] = x;
|
|
||||||
vertexInserterPos++;
|
|
||||||
vertices[vertexInserterPos] = heightfield[x][y] - collisionMargin;
|
|
||||||
vertexInserterPos++;
|
|
||||||
vertices[vertexInserterPos] = y;
|
|
||||||
vertexInserterPos++;
|
|
||||||
if(x < arrayLength - 1 && y < arrayWidth - 1){
|
|
||||||
//if we should also add a triangle index
|
|
||||||
/*
|
|
||||||
as copied from ModelUtil's terrain mesh generation function
|
|
||||||
faces.put((x / stride + 0) * actualHeight + (y / stride + 0));
|
|
||||||
faces.put((x / stride + 0) * actualHeight + (y / stride + 1));
|
|
||||||
faces.put((x / stride + 1) * actualHeight + (y / stride + 0));
|
|
||||||
faces.put((x / stride + 1) * actualHeight + (y / stride + 0));
|
|
||||||
faces.put((x / stride + 0) * actualHeight + (y / stride + 1));
|
|
||||||
faces.put((x / stride + 1) * actualHeight + (y / stride + 1));
|
|
||||||
*/
|
|
||||||
indices[indexInserterPos] = (x + 0) * arrayWidth + (y + 0);
|
|
||||||
indexInserterPos++;
|
|
||||||
indices[indexInserterPos] = (x + 0) * arrayWidth + (y + 1);
|
|
||||||
indexInserterPos++;
|
|
||||||
indices[indexInserterPos] = (x + 1) * arrayWidth + (y + 0);
|
|
||||||
indexInserterPos++;
|
|
||||||
indices[indexInserterPos] = (x + 1) * arrayWidth + (y + 0);
|
|
||||||
indexInserterPos++;
|
|
||||||
indices[indexInserterPos] = (x + 0) * arrayWidth + (y + 1);
|
|
||||||
indexInserterPos++;
|
|
||||||
indices[indexInserterPos] = (x + 1) * arrayWidth + (y + 1);
|
|
||||||
indexInserterPos++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
javax.vecmath.Vector3f aabbMin = new javax.vecmath.Vector3f();
|
|
||||||
javax.vecmath.Vector3f aabbMax = new javax.vecmath.Vector3f();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//http://jbullet.advel.cz/javadoc/com/bulletphysics/collision/shapes/IndexedMesh.html
|
|
||||||
electrosphere.collision.shapes.IndexedMesh indexedMesh = new electrosphere.collision.shapes.IndexedMesh();
|
|
||||||
|
|
||||||
indexedMesh.numTriangles = indices.length / 3;
|
|
||||||
indexedMesh.triangleIndexBase = ByteBuffer.allocateDirect(indices.length*Float.BYTES).order(ByteOrder.nativeOrder());
|
|
||||||
indexedMesh.triangleIndexBase.asIntBuffer().put(indices);
|
indexedMesh.triangleIndexBase.asIntBuffer().put(indices);
|
||||||
indexedMesh.triangleIndexStride = 3 * Float.BYTES;
|
//push indexed mesh into triangle index array
|
||||||
|
|
||||||
indexedMesh.numVertices = vertices.length / 3;
|
|
||||||
indexedMesh.vertexBase = ByteBuffer.allocateDirect(vertices.length*Float.BYTES).order(ByteOrder.nativeOrder());
|
|
||||||
indexedMesh.vertexBase.asFloatBuffer().put(vertices);
|
|
||||||
indexedMesh.vertexStride = 3 * Float.BYTES;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//http://jbullet.advel.cz/javadoc/com/bulletphysics/collision/shpaes/TriangleIndexVertexArray.html
|
|
||||||
electrosphere.collision.shapes.TriangleIndexVertexArray triangleIndexArray = new electrosphere.collision.shapes.TriangleIndexVertexArray();
|
|
||||||
triangleIndexArray.addIndexedMesh(indexedMesh); //this assumes the scalar type is integer (assumes bytebuffer is actually integer
|
triangleIndexArray.addIndexedMesh(indexedMesh); //this assumes the scalar type is integer (assumes bytebuffer is actually integer
|
||||||
// triangleIndexArray.calculateAabbBruteForce(aabbMin, aabbMax);
|
// triangleIndexArray.calculateAabbBruteForce(aabbMin, aabbMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -302,22 +258,16 @@ public class PhysicsUtils {
|
|||||||
//uncomment if we start falling through things again
|
//uncomment if we start falling through things again
|
||||||
terrainShape.setMargin(collisionMargin);
|
terrainShape.setMargin(collisionMargin);
|
||||||
|
|
||||||
DefaultMotionState defaultMotionState = new DefaultMotionState(new Transform(new javax.vecmath.Matrix4f(new javax.vecmath.Quat4f(0,0,0,1),new javax.vecmath.Vector3f((float)position.x,(float)position.y,(float)position.z),1.0f)));
|
// terrainShape.localGetSupportingVertex(new javax.vecmath.Vector3f(1,0,0), aabbMin);
|
||||||
|
// terrainShape.recalcLocalAabb();
|
||||||
|
// terrainShape.getLocalAabbMin(aabbMin);
|
||||||
|
// terrainShape.getLocalAabbMax(aabbMax);
|
||||||
|
|
||||||
|
|
||||||
|
DefaultMotionState defaultMotionState = new DefaultMotionState(new Transform(new javax.vecmath.Matrix4f(new javax.vecmath.Quat4f(0,0,0,1),new javax.vecmath.Vector3f(0,0,0),1.0f)));
|
||||||
RigidBodyConstructionInfo terrainRigidBodyCI = new RigidBodyConstructionInfo(0, defaultMotionState, terrainShape);
|
RigidBodyConstructionInfo terrainRigidBodyCI = new RigidBodyConstructionInfo(0, defaultMotionState, terrainShape);
|
||||||
RigidBody terrainRigidBody = new RigidBody(terrainRigidBodyCI);
|
RigidBody terrainRigidBody = new RigidBody(terrainRigidBodyCI);
|
||||||
|
|
||||||
// terrainRigidBody.setFriction(1f);
|
|
||||||
|
|
||||||
|
|
||||||
Globals.collisionEngine.registerCollisionObject(terrainRigidBody, new Collidable(terrain,Collidable.TYPE_TERRAIN));
|
|
||||||
|
|
||||||
// terrainRigidBody.getAabb(aabbMin, aabbMax);
|
|
||||||
//
|
|
||||||
// System.out.println("aabbMin: " + aabbMin + " aabbMax: " + aabbMax);
|
|
||||||
|
|
||||||
|
|
||||||
terrain.putData(EntityDataStrings.PHYSICS_COLLISION_BODY, terrainRigidBody);
|
|
||||||
|
|
||||||
return terrainRigidBody;
|
return terrainRigidBody;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import org.lwjgl.glfw.GLFWErrorCallback;
|
|||||||
|
|
||||||
import electrosphere.audio.AudioEngine;
|
import electrosphere.audio.AudioEngine;
|
||||||
import electrosphere.auth.AuthenticationManager;
|
import electrosphere.auth.AuthenticationManager;
|
||||||
|
import electrosphere.collision.dispatch.CollisionObject;
|
||||||
import electrosphere.controls.CameraHandler;
|
import electrosphere.controls.CameraHandler;
|
||||||
import electrosphere.controls.ControlCallback;
|
import electrosphere.controls.ControlCallback;
|
||||||
import electrosphere.controls.ControlHandler;
|
import electrosphere.controls.ControlHandler;
|
||||||
@ -223,7 +224,6 @@ public class Globals {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
//Engine - Main managers/variables
|
//Engine - Main managers/variables
|
||||||
//
|
//
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user