mirror of
https://github.com/RandomityGuy/MBHaxe.git
synced 2025-10-30 08:11:25 +00:00
609 lines
17 KiB
Haxe
609 lines
17 KiB
Haxe
package src;
|
|
|
|
import collision.CollisionWorld;
|
|
import mis.MissionElement.MissionElementSky;
|
|
import shapes.Astrolabe;
|
|
import src.Sky;
|
|
import h3d.Vector;
|
|
import h3d.Quat;
|
|
import mis.MissionElement.MissionElementSpawnSphere;
|
|
import h3d.mat.Material;
|
|
import shaders.DirLight;
|
|
import mis.MissionElement.MissionElementSun;
|
|
import shapes.Glass;
|
|
import shapes.MegaMarble;
|
|
import shapes.Checkpoint;
|
|
import shapes.EasterEgg;
|
|
import Macros.MarbleWorldMacros;
|
|
import mis.MissionElement.MissionElementStaticShape;
|
|
import mis.MissionElement.MissionElementInteriorInstance;
|
|
import src.InteriorObject;
|
|
import src.InstanceManager;
|
|
import mis.MissionElement.MissionElementSimGroup;
|
|
import mis.MisFile;
|
|
import mis.MisParser;
|
|
import src.ResourceLoader;
|
|
import src.Mission;
|
|
import h3d.scene.Scene;
|
|
import src.MarbleWorld.Scheduler;
|
|
import src.Util;
|
|
import h3d.Matrix;
|
|
import src.ResourceLoaderWorker;
|
|
import src.DtsObject;
|
|
import shapes.Trapdoor;
|
|
import shapes.TimeTravel;
|
|
import shapes.SuperSpeed;
|
|
import shapes.AntiGravity;
|
|
import shapes.SmallDuctFan;
|
|
import shapes.DuctFan;
|
|
import shapes.Helicopter;
|
|
import shapes.RoundBumper;
|
|
import shapes.SignCaution;
|
|
import shapes.SuperJump;
|
|
import shapes.Gem;
|
|
import shapes.SignPlain;
|
|
import shapes.EndPad;
|
|
import shapes.StartPad;
|
|
import shapes.PowerUp;
|
|
import shapes.Blast;
|
|
import src.Console;
|
|
import src.TimeState;
|
|
import src.MissionList;
|
|
import src.Settings;
|
|
import src.Marble;
|
|
|
|
class PreviewWorld extends Scheduler {
|
|
var scene:Scene;
|
|
var instanceManager:InstanceManager;
|
|
var collisionWorld:CollisionWorld;
|
|
|
|
var misFile:MisFile;
|
|
var currentMission:String;
|
|
|
|
var levelGroups:Map<String, MissionElementSimGroup> = [];
|
|
|
|
public var timeState:TimeState = new TimeState();
|
|
|
|
var interiors:Array<InteriorObject> = [];
|
|
var dtsObjects:Array<DtsObject> = [];
|
|
var marbles:Array<Marble> = [];
|
|
|
|
var sky:Sky;
|
|
|
|
var itrAddTime:Float = 0;
|
|
|
|
var _loadToken = 0;
|
|
|
|
public function new(scene:Scene) {
|
|
this.scene = scene;
|
|
}
|
|
|
|
public function init(onFinish:() -> Void) {
|
|
var entry = ResourceLoader.getFileEntry("data/missions/megaMission.mis").entry;
|
|
var misText = entry.getText();
|
|
var mparser = new MisParser(misText);
|
|
misFile = mparser.parse();
|
|
|
|
// Parse the mis file and get the simgroups of each levels
|
|
for (elem in misFile.root.elements) {
|
|
if (elem._type == SimGroup)
|
|
levelGroups.set(elem._name.toLowerCase(), cast elem);
|
|
}
|
|
|
|
initScene();
|
|
onFinish();
|
|
}
|
|
|
|
function initScene() {
|
|
for (element in misFile.root.elements) {
|
|
if (element._type != Sun)
|
|
continue;
|
|
|
|
var sunElement:MissionElementSun = cast element;
|
|
|
|
var directionalColor = MisParser.parseVector4(sunElement.color);
|
|
var ambientColor = MisParser.parseVector4(sunElement.ambient);
|
|
ambientColor.r *= 1.18;
|
|
ambientColor.g *= 1.06;
|
|
ambientColor.b *= 0.95;
|
|
var sunDirection = MisParser.parseVector3(sunElement.direction);
|
|
sunDirection.x = -sunDirection.x;
|
|
// sunDirection.x = 0;
|
|
// sunDirection.y = 0;
|
|
// sunDirection.z = -sunDirection.z;
|
|
var ls = cast(scene.lightSystem, h3d.scene.fwd.LightSystem);
|
|
|
|
ls.ambientLight.load(ambientColor);
|
|
// ls.shadowLight.setDirection(new Vector(0, 0, -1));
|
|
// ls.perPixelLighting = false;
|
|
|
|
var shadow = scene.renderer.getPass(h3d.pass.DefaultShadowMap);
|
|
shadow.power = 1;
|
|
shadow.mode = Dynamic;
|
|
shadow.minDist = 0.1;
|
|
shadow.maxDist = 200;
|
|
shadow.bias = 0;
|
|
|
|
var sunlight = new DirLight(sunDirection, scene);
|
|
sunlight.color = directionalColor;
|
|
return;
|
|
}
|
|
}
|
|
|
|
public function loadMission(misname:String, onFinish:() -> Void, physics:Bool = false) {
|
|
if (currentMission == misname) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
_loadToken++;
|
|
var groupName = (misname + "group").toLowerCase();
|
|
var group = levelGroups.get(groupName);
|
|
if (group != null) {
|
|
destroyAllObjects();
|
|
this.currentMission = misname;
|
|
this.instanceManager = new InstanceManager(scene);
|
|
if (physics)
|
|
this.collisionWorld = new CollisionWorld();
|
|
else
|
|
this.collisionWorld = null;
|
|
|
|
var p = new h3d.prim.Cube(0.001, 0.001, 0.001);
|
|
p.addUVs();
|
|
p.addTangents();
|
|
var mat = Material.create();
|
|
var m = new h3d.scene.Mesh(p, mat, scene);
|
|
|
|
var itrpaths = [];
|
|
var shapeDbs = [];
|
|
for (elem in group.elements) {
|
|
if (elem._type == InteriorInstance) {
|
|
var itrElem:MissionElementInteriorInstance = cast elem;
|
|
itrpaths.push(itrElem);
|
|
}
|
|
if (elem._type == StaticShape) {
|
|
var shapeElem:MissionElementStaticShape = cast elem;
|
|
shapeDbs.push(shapeElem);
|
|
}
|
|
if (elem._type == SpawnSphere) {
|
|
var shapeElem:MissionElementSpawnSphere = cast elem;
|
|
if (shapeElem.datablock.toLowerCase() == 'cameraspawnspheremarker') {
|
|
// Set camera position
|
|
var camPos = MisParser.parseVector3(shapeElem.position);
|
|
camPos.x *= -1;
|
|
var camRot = MisParser.parseRotation(shapeElem.rotation);
|
|
camRot.x *= -1;
|
|
camRot.w *= -1;
|
|
|
|
var camMat = camRot.toMatrix();
|
|
|
|
var off = new Vector(0, 0, 1.5);
|
|
off.transform3x3(camMat);
|
|
|
|
var directionVector = new Vector(0, 1, 0);
|
|
directionVector.transform3x3(camMat);
|
|
|
|
scene.camera.setFovX(90, Settings.optionsSettings.screenWidth / Settings.optionsSettings.screenHeight);
|
|
|
|
scene.camera.up.set(0, 0, 1);
|
|
scene.camera.pos.load(camPos.add(off));
|
|
scene.camera.target.load(scene.camera.pos.add(directionVector));
|
|
}
|
|
}
|
|
}
|
|
|
|
var difficulty = "beginner";
|
|
var mis = MissionList.missionsFilenameLookup.get((misname + '.mis').toLowerCase());
|
|
if (misname == "marblepicker")
|
|
difficulty = "advanced";
|
|
else
|
|
difficulty = ["beginner", "intermediate", "advanced"][mis.difficultyIndex];
|
|
|
|
var curToken = _loadToken;
|
|
|
|
var worker = new ResourceLoaderWorker(onFinish);
|
|
|
|
worker.addTask(fwd -> {
|
|
if (_loadToken != curToken)
|
|
fwd();
|
|
else
|
|
addScenery(difficulty, fwd);
|
|
});
|
|
itrAddTime = 0;
|
|
for (elem in itrpaths) {
|
|
worker.addTask(fwd -> {
|
|
if (_loadToken != curToken)
|
|
fwd();
|
|
else {
|
|
var startTime = Console.time();
|
|
addInteriorFromMis(cast elem, curToken, () -> {
|
|
itrAddTime += Console.time() - startTime;
|
|
fwd();
|
|
}, physics);
|
|
}
|
|
});
|
|
}
|
|
for (elem in shapeDbs) {
|
|
worker.addTask(fwd -> {
|
|
if (_loadToken != curToken)
|
|
fwd();
|
|
else
|
|
addStaticShape(cast elem, curToken, fwd);
|
|
});
|
|
}
|
|
worker.addTask(fwd -> {
|
|
timeState.timeSinceLoad = 0;
|
|
timeState.dt = 0;
|
|
Console.log('ITR ADD TIME: ' + itrAddTime);
|
|
fwd();
|
|
});
|
|
worker.run();
|
|
} else {
|
|
onFinish();
|
|
}
|
|
}
|
|
|
|
function addScenery(difficulty:String, onFinish:() -> Void) {
|
|
var worker = new ResourceLoaderWorker(onFinish);
|
|
var astrolabe = new Astrolabe();
|
|
astrolabe.setPosition(0, 0, -612.988);
|
|
worker.addTask(fwd -> {
|
|
astrolabe.init(null, () -> {
|
|
scene.addChild(astrolabe);
|
|
dtsObjects.push(astrolabe);
|
|
fwd();
|
|
});
|
|
});
|
|
var clouds = new shapes.Sky('astrolabeclouds' + difficulty + 'shape');
|
|
worker.addTask(fwd -> {
|
|
clouds.init(null, () -> {
|
|
scene.addChild(clouds);
|
|
fwd();
|
|
});
|
|
});
|
|
|
|
var skyElem = new MissionElementSky();
|
|
skyElem.fogcolor = "0.600000 0.600000 0.600000 1.000000";
|
|
skyElem.skysolidcolor = "0.600000 0.600000 0.600000 1.000000";
|
|
|
|
var skyDml = "data/skies/sky_" + difficulty + '.dml';
|
|
|
|
sky = new Sky();
|
|
sky.dmlPath = ResourceLoader.getProperFilepath(skyDml);
|
|
worker.addTask(fwd -> {
|
|
sky.init(null, () -> {
|
|
scene.addChild(sky);
|
|
fwd();
|
|
}, skyElem);
|
|
});
|
|
|
|
worker.run();
|
|
}
|
|
|
|
public function destroyAllObjects() {
|
|
currentMission = null;
|
|
collisionWorld = null;
|
|
for (itr in interiors) {
|
|
itr.dispose();
|
|
}
|
|
for (shape in dtsObjects) {
|
|
shape.dispose();
|
|
}
|
|
for (marb in marbles) {
|
|
marb.dispose();
|
|
}
|
|
interiors = [];
|
|
dtsObjects = [];
|
|
marbles = [];
|
|
scene.removeChildren();
|
|
}
|
|
|
|
public function addInteriorFromMis(element:MissionElementInteriorInstance, token:Int, onFinish:Void->Void, physics:Bool = false) {
|
|
var difPath = getDifPath(element.interiorfile);
|
|
if (difPath == "" || token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
Console.log('Adding interior: ${difPath}');
|
|
var interior = new InteriorObject();
|
|
interior.interiorFile = difPath;
|
|
interior.isCollideable = physics;
|
|
// DifBuilder.loadDif(difPath, interior);
|
|
// this.interiors.push(interior);
|
|
this.addInterior(interior, token, () -> {
|
|
var interiorPosition = MisParser.parseVector3(element.position);
|
|
interiorPosition.x = -interiorPosition.x;
|
|
var interiorRotation = MisParser.parseRotation(element.rotation);
|
|
interiorRotation.x = -interiorRotation.x;
|
|
interiorRotation.w = -interiorRotation.w;
|
|
var interiorScale = MisParser.parseVector3(element.scale);
|
|
var hasCollision = interiorScale.x * interiorScale.y * interiorScale.z != 0; // Don't want to add buggy geometry
|
|
|
|
// Fix zero-volume interiors so they receive correct lighting
|
|
if (interiorScale.x == 0)
|
|
interiorScale.x = 0.0001;
|
|
if (interiorScale.y == 0)
|
|
interiorScale.y = 0.0001;
|
|
if (interiorScale.z == 0)
|
|
interiorScale.z = 0.0001;
|
|
|
|
var mat = Matrix.S(interiorScale.x, interiorScale.y, interiorScale.z);
|
|
var tmp = new Matrix();
|
|
interiorRotation.toMatrix(tmp);
|
|
mat.multiply3x4(mat, tmp);
|
|
var tmat = Matrix.T(interiorPosition.x, interiorPosition.y, interiorPosition.z);
|
|
mat.multiply(mat, tmat);
|
|
|
|
interior.setTransform(mat);
|
|
interior.isCollideable = hasCollision && physics;
|
|
onFinish();
|
|
}, physics);
|
|
}
|
|
|
|
function addInterior(obj:InteriorObject, token:Int, onFinish:Void->Void, physics:Bool = false) {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
this.interiors.push(obj);
|
|
obj.init(null, () -> {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
if (obj.useInstancing)
|
|
this.instanceManager.addObject(obj);
|
|
else
|
|
this.scene.addChild(obj);
|
|
if (physics) {
|
|
this.collisionWorld.addEntity(obj.collider);
|
|
obj.collisionWorld = this.collisionWorld;
|
|
}
|
|
onFinish();
|
|
});
|
|
}
|
|
|
|
public function addStaticShape(element:MissionElementStaticShape, token:Int, onFinish:Void->Void) {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
var shape:DtsObject = null;
|
|
|
|
// Add the correct shape based on type
|
|
var dataBlockLowerCase = element.datablock.toLowerCase();
|
|
if (dataBlockLowerCase == "") {} // Make sure we don't do anything if there's no data block
|
|
else if (dataBlockLowerCase == "startpad")
|
|
shape = new StartPad();
|
|
else if (dataBlockLowerCase == "endpad") {
|
|
shape = new EndPad();
|
|
} else if (StringTools.startsWith(dataBlockLowerCase, "arrow"))
|
|
shape = new SignPlain(cast element);
|
|
else if (StringTools.startsWith(dataBlockLowerCase, "gemitem")) {
|
|
shape = new Gem(cast element);
|
|
} else if (dataBlockLowerCase == "superjumpitem")
|
|
shape = new SuperJump(cast element);
|
|
else if (StringTools.startsWith(dataBlockLowerCase, "signcaution"))
|
|
shape = new SignCaution(cast element);
|
|
else if (dataBlockLowerCase == "roundbumper")
|
|
shape = new RoundBumper();
|
|
else if (dataBlockLowerCase == "helicopteritem")
|
|
shape = new Helicopter(cast element);
|
|
else if (dataBlockLowerCase == "eastereggitem")
|
|
shape = new EasterEgg(cast element);
|
|
else if (dataBlockLowerCase == "checkpointshape") {
|
|
shape = new Checkpoint(cast element);
|
|
} else if (dataBlockLowerCase == "ductfan")
|
|
shape = new DuctFan();
|
|
else if (dataBlockLowerCase == "smallductfan")
|
|
shape = new SmallDuctFan();
|
|
else if (dataBlockLowerCase == "antigravityitem")
|
|
shape = new AntiGravity(cast element);
|
|
else if (dataBlockLowerCase == "norespawnantigravityitem")
|
|
shape = new AntiGravity(cast element, true);
|
|
else if (dataBlockLowerCase == "superspeeditem")
|
|
shape = new SuperSpeed(cast element);
|
|
else if (dataBlockLowerCase == "timetravelitem" || dataBlockLowerCase == "timepenaltyitem")
|
|
shape = new TimeTravel(cast element);
|
|
else if (dataBlockLowerCase == "blastitem")
|
|
shape = new Blast(cast element);
|
|
else if (dataBlockLowerCase == "megamarbleitem")
|
|
shape = new MegaMarble(cast element);
|
|
else if (dataBlockLowerCase == "trapdoor")
|
|
shape = new Trapdoor();
|
|
else if ([
|
|
"glass_3shape",
|
|
"glass_6shape",
|
|
"glass_9shape",
|
|
"glass_12shape",
|
|
"glass_15shape",
|
|
"glass_18shape"
|
|
].contains(dataBlockLowerCase))
|
|
shape = new Glass(cast element);
|
|
else if ([
|
|
"astrolabecloudsbeginnershape",
|
|
"astrolabecloudsintermediateshape",
|
|
"astrolabecloudsadvancedshape"
|
|
].contains(dataBlockLowerCase))
|
|
shape = new shapes.Sky(dataBlockLowerCase);
|
|
else if (dataBlockLowerCase == "astrolabeshape")
|
|
shape = new shapes.Astrolabe();
|
|
else {
|
|
Console.error("Unknown item: " + element.datablock);
|
|
onFinish();
|
|
return;
|
|
}
|
|
|
|
var shapePosition = MisParser.parseVector3(element.position);
|
|
shapePosition.x = -shapePosition.x;
|
|
var shapeRotation = MisParser.parseRotation(element.rotation);
|
|
shapeRotation.x = -shapeRotation.x;
|
|
shapeRotation.w = -shapeRotation.w;
|
|
var shapeScale = MisParser.parseVector3(element.scale);
|
|
|
|
// Apparently we still do collide with zero-volume shapes
|
|
if (shapeScale.x == 0)
|
|
shapeScale.x = 0.0001;
|
|
if (shapeScale.y == 0)
|
|
shapeScale.y = 0.0001;
|
|
if (shapeScale.z == 0)
|
|
shapeScale.z = 0.0001;
|
|
|
|
var mat = Matrix.S(shapeScale.x, shapeScale.y, shapeScale.z);
|
|
var tmp = new Matrix();
|
|
shapeRotation.toMatrix(tmp);
|
|
mat.multiply3x4(mat, tmp);
|
|
mat.setPosition(shapePosition);
|
|
|
|
shape.isCollideable = false;
|
|
shape.isBoundingBoxCollideable = false;
|
|
|
|
this.addDtsObject(shape, token, () -> {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
shape.setTransform(mat);
|
|
onFinish();
|
|
});
|
|
}
|
|
|
|
public function addDtsObject(obj:DtsObject, token:Int, onFinish:Void->Void, isTsStatic:Bool = false) {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
function parseIfl(path:String, onFinish:Array<String>->Void) {
|
|
ResourceLoader.load(path).entry.load(() -> {
|
|
var text = ResourceLoader.getFileEntry(path).entry.getText();
|
|
var lines = text.split('\n');
|
|
var keyframes = [];
|
|
for (line in lines) {
|
|
line = StringTools.trim(line);
|
|
if (line.substr(0, 2) == "//")
|
|
continue;
|
|
if (line == "")
|
|
continue;
|
|
|
|
var parts = line.split(' ');
|
|
var count = parts.length > 1 ? Std.parseInt(parts[1]) : 1;
|
|
|
|
for (i in 0...count) {
|
|
keyframes.push(parts[0]);
|
|
}
|
|
}
|
|
|
|
onFinish(keyframes);
|
|
});
|
|
}
|
|
|
|
ResourceLoader.load(obj.dtsPath).entry.load(() -> {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
var dtsFile = ResourceLoader.loadDts(obj.dtsPath);
|
|
var texToLoad = obj.getPreloadMaterials(dtsFile.resource);
|
|
|
|
var worker = new ResourceLoaderWorker(() -> {
|
|
obj.isTSStatic = isTsStatic;
|
|
this.dtsObjects.push(obj);
|
|
obj.init(null, () -> {
|
|
if (token != _loadToken) {
|
|
onFinish();
|
|
return;
|
|
}
|
|
obj.update(this.timeState);
|
|
if (obj.useInstancing) {
|
|
this.instanceManager.addObject(obj);
|
|
} else
|
|
this.scene.addChild(obj);
|
|
|
|
onFinish();
|
|
});
|
|
});
|
|
|
|
for (texPath in texToLoad) {
|
|
worker.loadFile(texPath);
|
|
}
|
|
|
|
worker.run();
|
|
});
|
|
}
|
|
|
|
function getDifPath(rawElementPath:String) {
|
|
if (StringTools.contains(rawElementPath, "$usermods")) {
|
|
rawElementPath = rawElementPath.split("@").slice(1).map(x -> {
|
|
var a = StringTools.trim(x);
|
|
a = Util.unescape(a.substr(1, a.length - 2));
|
|
return a;
|
|
}).join('');
|
|
}
|
|
var fname = rawElementPath.substring(rawElementPath.lastIndexOf('/') + 1);
|
|
rawElementPath = rawElementPath.toLowerCase();
|
|
if (StringTools.startsWith(rawElementPath, "./")) {
|
|
rawElementPath = rawElementPath.substring(2);
|
|
rawElementPath = 'data/missions/' + rawElementPath;
|
|
}
|
|
var path = StringTools.replace(rawElementPath.substring(rawElementPath.indexOf('data/')), "\"", "");
|
|
#if (js || android)
|
|
path = StringTools.replace(path, "data/", "");
|
|
#end
|
|
if (!StringTools.endsWith(path, ".dif"))
|
|
path += ".dif";
|
|
if (ResourceLoader.exists(path))
|
|
return path;
|
|
if (StringTools.contains(path, 'interiors_mbg/'))
|
|
path = StringTools.replace(path, 'interiors_mbg/', 'interiors/');
|
|
var dirpath = path.substring(0, path.lastIndexOf('/') + 1);
|
|
if (ResourceLoader.exists(path))
|
|
return path;
|
|
if (ResourceLoader.exists(dirpath + fname))
|
|
return dirpath + fname;
|
|
path = StringTools.replace(path, "lbinteriors", "interiors"); // This shit ew
|
|
if (ResourceLoader.exists(path))
|
|
return path;
|
|
Console.error("Interior resource not found: " + rawElementPath);
|
|
return "";
|
|
}
|
|
|
|
public function spawnMarble(onFinish:Marble->Void) {
|
|
var marb = new Marble();
|
|
marb.controllable = false;
|
|
marb.init(null, () -> {
|
|
marb.collisionWorld = this.collisionWorld;
|
|
this.collisionWorld.addMovingEntity(marb.collider);
|
|
this.scene.addChild(marb);
|
|
this.marbles.push(marb);
|
|
onFinish(marb);
|
|
});
|
|
}
|
|
|
|
public function removeMarble(marb:Marble) {
|
|
if (this.marbles.remove(marb)) {
|
|
this.scene.removeChild(marb);
|
|
this.collisionWorld.removeMovingEntity(marb.collider);
|
|
marb.dispose();
|
|
}
|
|
}
|
|
|
|
public function update(dt:Float) {
|
|
timeState.dt = dt;
|
|
timeState.timeSinceLoad += dt;
|
|
for (dts in dtsObjects) {
|
|
dts.update(timeState);
|
|
}
|
|
for (marb in marbles) {
|
|
marb.update(timeState, this.collisionWorld, []);
|
|
}
|
|
this.instanceManager.render();
|
|
}
|
|
|
|
public function render(e:h3d.Engine) {
|
|
for (marble in marbles) {
|
|
if (marble != null && marble.cubemapRenderer != null) {
|
|
marble.cubemapRenderer.position.load(marble.getAbsPos().getPosition());
|
|
marble.cubemapRenderer.render(e, 0.002);
|
|
}
|
|
}
|
|
}
|
|
}
|