js chunkgen + fixes + scriptengine work
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-11-09 20:04:44 -05:00
parent 86e61b7a0e
commit cc60818e35
32 changed files with 819 additions and 72 deletions

View File

@ -4,6 +4,7 @@ import { HookManager } from '/Scripts/engine/hooks/hook-manager'
import { SceneLoader } from '/Scripts/engine/scene/scene-loader' import { SceneLoader } from '/Scripts/engine/scene/scene-loader'
import { Engine } from '/Scripts/types/engine' import { Engine } from '/Scripts/types/engine'
import { clientHooks } from '/Scripts/client/clienthooks' import { clientHooks } from '/Scripts/client/clienthooks'
import { ChunkGeneratorManager } from '/Scripts/server/chunk/chunkgeneratormanager'
/** /**
* The core engine values * The core engine values
@ -13,6 +14,7 @@ export const engine: Engine = {
singletons: {}, singletons: {},
hookManager: new HookManager(), hookManager: new HookManager(),
sceneLoader: new SceneLoader(), sceneLoader: new SceneLoader(),
chunkGeneratorManager: new ChunkGeneratorManager(),
} }
/** /**
@ -25,6 +27,7 @@ export const ENGINE_onInit = () => {
let client: NamespaceClient = Client let client: NamespaceClient = Client
engine.sceneLoader.engine = engine engine.sceneLoader.engine = engine
engine.hookManager.engine = engine engine.hookManager.engine = engine
engine.chunkGeneratorManager.engine = engine
//load global hooks //load global hooks
clientHooks.forEach(hook => { clientHooks.forEach(hook => {

View File

@ -0,0 +1,39 @@
import { TestGen } from "/Scripts/server/chunk/generators/testgen"
import { Engine } from "/Scripts/types/engine"
import { ChunkGenerator, VoxelFunction } from "/Scripts/types/host/server/chunk/chunkgenerator"
/**
* Manages all the chunk generators defined script-side
*/
export class ChunkGeneratorManager {
/**
* The parent engine object
*/
engine: Engine
/**
* The list of registered chunk generators
*/
readonly registeredGenerators: ChunkGenerator[] = [
TestGen,
]
/**
* Gets the voxel function for the tag
* @param tag The tag
* @returns The voxel function if it exists, null otherwise
*/
readonly getVoxelFunction = (tag: string): VoxelFunction => {
let rVal: VoxelFunction = null
this.registeredGenerators.forEach(generator => {
if(generator.getTag() === tag){
rVal = generator.getVoxelFunction(this.engine)
}
})
return rVal
}
}

View File

@ -0,0 +1,159 @@
import { Engine } from "/Scripts/types/engine";
import { CHUNK_WIDTH, ChunkGenerator, GeneratedVoxel, VoxelFunction } from "/Scripts/types/host/server/chunk/chunkgenerator";
import { BiomeData } from "/Scripts/types/host/server/data/biomedata";
/**
* Converts a chunk coordinate to a real coordinate
* @param chunk The chunk pos
* @param world The world pos
* @returns The real pos
*/
const voxelToReal = (chunk: number, world: number) => {
return world * CHUNK_WIDTH + chunk
}
/**
* Gets the voxel on the surface
* @return The voxel
*/
const getSurfaceVoxel = (
voxel: GeneratedVoxel,
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
realX: number, realY: number, realZ: number,
surfacePercent: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => {
voxel.weight = surfacePercent * 2 - 1;
voxel.type = 2;
return voxel;
}
/**
* Gets the voxel below the surface
* @return The voxel
*/
const getSubsurfaceVoxel = (
voxel: GeneratedVoxel,
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
realX: number, realY: number, realZ: number,
surfacePercent: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => {
if(realY < surfaceHeight - 5){
voxel.weight = 1;
voxel.type = 6;
} else {
voxel.weight = 1;
voxel.type = 1;
}
return voxel;
}
/**
* Gets the voxel above the service
* @return The voxel
*/
const getOverSurfaceVoxel = (
voxel: GeneratedVoxel,
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
realX: number, realY: number, realZ: number,
surfacePercent: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => {
voxel.weight = -1;
voxel.type = 0;
return voxel;
}
/**
* A test generator
*/
export const TestGen: ChunkGenerator = {
/**
* Gets the tag for this generator
* @returns The tag
*/
getTag: () => "test",
/**
* The elevation function
* @param worldX The world x coordinate
* @param worldZ The world z coordinate
* @param chunkX The chunk x coordinate
* @param chunkZ The chunk z coordinate
*/
getElevation: (worldX: number, worldZ: number, chunkX: number, chunkZ: number): number => {
return 1
},
/**
* Gets the function to actually get voxels
* @param engine The engine reference
*/
getVoxelFunction: (
engine: Engine
): VoxelFunction => {
const rVal = (
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
stride: number,
surfaceHeight: number,
surfaceBiome: BiomeData
): GeneratedVoxel => {
let rVal: GeneratedVoxel = {
type: 0,
weight: -1,
}
const realX = voxelToReal(chunkX,worldX)
const realY = voxelToReal(chunkY,worldY)
const realZ = voxelToReal(chunkZ,worldZ)
const strideMultiplier = engine.classes.math.static.pow(2,stride)
// const strideMultiplier = 1
const heightDiff = realY - surfaceHeight
const surfacePercent = (surfaceHeight - realY) / strideMultiplier
if(heightDiff < -strideMultiplier){
return getSubsurfaceVoxel(
rVal,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfacePercent,
surfaceHeight,
surfaceBiome
);
} else if(heightDiff > 0) {
return getOverSurfaceVoxel(
rVal,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,-
surfacePercent,
surfaceHeight,
surfaceBiome
);
} else {
return getSurfaceVoxel(
rVal,
worldX, worldY, worldZ,
chunkX, chunkY, chunkZ,
realX, realY, realZ,
surfacePercent,
surfaceHeight,
surfaceBiome
);
}
}
return rVal
},
}

View File

@ -1,5 +1,6 @@
import { HookManager } from "/Scripts/engine/hooks/hook-manager"; import { HookManager } from "/Scripts/engine/hooks/hook-manager";
import { SceneLoader } from "/Scripts/engine/scene/scene-loader"; import { SceneLoader } from "/Scripts/engine/scene/scene-loader";
import { ChunkGeneratorManager } from "/Scripts/server/chunk/chunkgeneratormanager";
import { SingletonsMap } from "/Scripts/types/host/singletons"; import { SingletonsMap } from "/Scripts/types/host/singletons";
import { StaticClasses } from "/Scripts/types/host/static-classes"; import { StaticClasses } from "/Scripts/types/host/static-classes";
@ -29,5 +30,10 @@ export interface Engine {
*/ */
readonly sceneLoader: SceneLoader, readonly sceneLoader: SceneLoader,
/**
* The chunk generator manager
*/
readonly chunkGeneratorManager: ChunkGeneratorManager,
} }

View File

@ -0,0 +1,95 @@
import { Engine } from "/Scripts/types/engine";
import { BiomeData } from "/Scripts/types/host/server/data/biomedata";
/**
* The width of a chunk
*/
export const CHUNK_WIDTH = 16;
/**
* A voxel generated by a ChunkGenerator
*/
export interface GeneratedVoxel {
/**
* The id of the voxel type
*/
type: number,
/**
* The weight of the voxel
*/
weight: number,
}
/**
* Gets a voxel for a given position
* @param worldX The world x coordinate of the chunk
* @param worldY The world y coordinate of the chunk
* @param worldZ The world z coordinate of the chunk
* @param chunkX The chunk x coordinate
* @param chunkY The chunk y coordinate
* @param chunkZ The chunk z coordinate
* @param stride The stride of the data (this is used for LOD, it ranges 0->4 with 0 being the highest level of detail and 4 being the lowest)
* @param surfaceHeight The height of the surface at the given x,z coordinate
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome
* @returns The voxel at the provided position
*/
export type VoxelFunction = (
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
stride: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => GeneratedVoxel;
/**
* Gets a chunk for a given world position
* @param worldX The world x coordinate of the chunk
* @param worldY The world y coordinate of the chunk
* @param worldZ The world z coordinate of the chunk
* @param stride The stride of the data (this is used for LOD, it ranges 0->4 with 0 being the highest level of detail and 4 being the lowest)
* @param surfaceHeight The height of the surface at the given x,z coordinate
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome
* @returns The voxel at the provided position
*/
export type ChunkFunction = (
worldX: number, worldY: number, worldZ: number,
chunkX: number, chunkY: number, chunkZ: number,
stride: number,
surfaceHeight: number,
surfaceBiome: BiomeData
) => number[][][];
/**
* A chunk generator
*/
export interface ChunkGenerator {
/**
* Gets the tag for this generator
* @returns The tag
*/
getTag: () => string
/**
* Retrieves the elevation for the world at a given x,z coordinate
* @param worldX The world x coordinate
* @param worldZ The world z coordinate
* @param chunkX The x coordinate of the chunk within the specified world coordinate
* @param chunkZ The z coordinate of the chunk within the specified world coordinate
* @returns The elevation at that specific position
*/
getElevation: (worldX: number, worldZ: number, chunkX: number, chunkZ: number) => number
/**
* The function to get a voxel for a given position
*/
getVoxelFunction: (engine: Engine) => VoxelFunction
}

View File

@ -0,0 +1,95 @@
/**
* Describes a type of foliage that can be generated in a biome
*/
export interface BiomeFoliageDescription {
/**
* The list of entity IDs in the type
*/
entityIDs: string[],
/**
* How regular the placement of the foliage is.
* Low values make the placement more random.
* High values make the placement more orderly (aligned with a grid).
*/
regularity: number,
/**
* The percentage of the ground to cover with foliage
*/
threshold: number,
/**
* The priority of this type in particular
*/
priority: number,
/**
* The scale of the noise used to place foliage
*/
scale: number,
}
/**
* The parameters for generating the surface of a biome
*/
export interface BiomeSurfaceGenerationParams {
/**
* The tag for the generation algorithm to generate the surface of the biome
*/
surfaceGenTag: string,
/**
* The offset added to the generated heightfield to get the final height of the surface at a given position.
* This is most useful for cases like plateaus where you really want to put the surface noise higher than its surroundings.
*/
heightOffset: number,
/**
* The different foliage types that can be generated on this surface
*/
foliageDescriptions: BiomeFoliageDescription[],
}
/**
* Biome data
*/
export interface BiomeData {
/**
* The id of the biome type
*/
id: string,
/**
* The display name of the biome
*/
displayName: string,
/**
* True if this is an aerial biome
*/
isAerial: boolean,
/**
* True if this is a surface biome
*/
isSurface: boolean,
/**
* True if this is a subterranean biome
*/
isSubterranean: boolean,
/**
* The parameters for generating the surface of this biome
*/
surfaceGenerationParams: BiomeSurfaceGenerationParams,
}

View File

@ -46,6 +46,11 @@ export interface StaticClasses {
*/ */
readonly levelEditorUtils?: Class<ClientLevelEditorUtils>, readonly levelEditorUtils?: Class<ClientLevelEditorUtils>,
/**
* Math functions
*/
readonly math?: Class<Math>
} }
/** /**

View File

@ -0,0 +1,14 @@
/**
* Math functions
*/
export interface Math {
/**
* Computes an arbitrary power of a number
* @param num1 The number
* @param num2 The power
* @returns The result of the power
*/
readonly pow: (num1: number, num2: number) => number
}

View File

@ -963,6 +963,15 @@ Fix foliage rendering
Fix async physics gen on client Fix async physics gen on client
Convert volumetric + shadow pass to entity tags Convert volumetric + shadow pass to entity tags
(11/09/2024)
Fix eighth res chunk radius
Fix chunk gen debug ui regenerate button
Script-defined chunk generators
Script engine synchronization utility
Convert ScriptEngine to service
ScriptEngine full re-initialization signal
Add surface width to test generator
# TODO # TODO

View File

@ -14,9 +14,11 @@ public class ClientScriptUtils {
* @param args The arguments provided alongside the signal * @param args The arguments provided alongside the signal
*/ */
public static void fireSignal(String signalName, Object ... args){ public static void fireSignal(String signalName, Object ... args){
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){ Globals.scriptEngine.executeSynchronously(() -> {
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args); if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
} Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
}
});
} }
} }

View File

@ -97,6 +97,9 @@ public class ClientTerrainCache {
this.cacheList.clear(); this.cacheList.clear();
this.cacheMapFullRes.clear(); this.cacheMapFullRes.clear();
this.cacheMapHalfRes.clear(); this.cacheMapHalfRes.clear();
this.cacheMapQuarterRes.clear();
this.cacheMapEighthRes.clear();
this.cacheMapSixteenthRes.clear();
this.chunkPositionMap.clear(); this.chunkPositionMap.clear();
lock.release(); lock.release();
} }

View File

@ -98,6 +98,11 @@ public class ClientDrawCellManager {
*/ */
Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap = new HashMap<FloatingChunkTreeNode<DrawCell>,Boolean>(); Map<FloatingChunkTreeNode<DrawCell>,Boolean> evaluationMap = new HashMap<FloatingChunkTreeNode<DrawCell>,Boolean>();
/**
* All draw cells currently tracked
*/
List<DrawCell> activeCells = new LinkedList<DrawCell>();
/** /**
* Tracks whether the cell manager updated last frame or not * Tracks whether the cell manager updated last frame or not
*/ */
@ -227,6 +232,7 @@ public class ClientDrawCellManager {
child.getMinBound().z child.getMinBound().z
); );
DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos); DrawCell drawCell = DrawCell.generateTerrainCell(cellWorldPos);
activeCells.add(drawCell);
child.convertToLeaf(drawCell); child.convertToLeaf(drawCell);
evaluationMap.put(child,true); evaluationMap.put(child,true);
}); });
@ -246,6 +252,7 @@ public class ClientDrawCellManager {
//do creations //do creations
DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound()); DrawCell drawCell = DrawCell.generateTerrainCell(node.getMinBound());
activeCells.add(drawCell);
newLeaf.convertToLeaf(drawCell); newLeaf.convertToLeaf(drawCell);
//update neighbors //update neighbors
@ -544,7 +551,7 @@ public class ClientDrawCellManager {
( (
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&& &&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
) )
|| ||
( (
@ -591,7 +598,7 @@ public class ClientDrawCellManager {
( (
node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD node.getLevel() == this.chunkTree.getMaxLevel() - EIGHTH_RES_LOD
&& &&
this.getMinDistance(pos, node) <= EIGHTH_RES_DIST this.getMinDistance(pos, node) <= SIXTEENTH_RES_DIST
) )
|| ||
( (
@ -631,6 +638,7 @@ public class ClientDrawCellManager {
node.getChildren().forEach(child -> recursivelyDestroy(child)); node.getChildren().forEach(child -> recursivelyDestroy(child));
} }
if(node.getData() != null){ if(node.getData() != null){
activeCells.remove(node.getData());
node.getData().destroy(); node.getData().destroy();
} }
} }
@ -659,6 +667,7 @@ public class ClientDrawCellManager {
* Evicts all cells * Evicts all cells
*/ */
public void evictAll(){ public void evictAll(){
this.recursivelyDestroy(this.chunkTree.getRoot());
this.chunkTree.clear(); this.chunkTree.clear();
} }

View File

@ -1,6 +1,7 @@
package electrosphere.client.ui.menu.debug; package electrosphere.client.ui.menu.debug;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.engine.signal.Signal.SignalType;
import electrosphere.renderer.ui.imgui.ImGuiWindow; import electrosphere.renderer.ui.imgui.ImGuiWindow;
import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback; import electrosphere.renderer.ui.imgui.ImGuiWindow.ImGuiWindowCallback;
import electrosphere.server.datacell.GriddedDataCellManager; import electrosphere.server.datacell.GriddedDataCellManager;
@ -45,10 +46,19 @@ public class ImGuiTestGen {
//regenerate the test area //regenerate the test area
if(ImGui.button("Regenerate")){ if(ImGui.button("Regenerate")){
GriddedDataCellManager gridManager = (GriddedDataCellManager)Globals.realmManager.first().getDataCellManager(); //recompile script engine
gridManager.evictAll(); Globals.signalSystem.post(SignalType.SCRIPT_RECOMPILE, () -> {
Globals.clientDrawCellManager.evictAll();
Globals.clientTerrainManager.evictAll(); //run once script recompilation has completed
//clear server
GriddedDataCellManager gridManager = (GriddedDataCellManager)Globals.realmManager.first().getDataCellManager();
gridManager.evictAll();
//clear client
Globals.clientDrawCellManager.evictAll();
Globals.clientTerrainManager.evictAll();
});
} }
//set macro data scale in terrain model //set macro data scale in terrain model
@ -82,7 +92,7 @@ public class ImGuiTestGen {
} }
//Toggles whether the client draws cell manager should update or not //Toggles whether the client draws cell manager should update or not
if(ImGui.button("Toggle ClientDrawCellManager updates")){ if(ImGui.button("Toggle ClientDrawCellManager updates " + (Globals.clientDrawCellManager.getShouldUpdate() ? "off" : "on"))){
Globals.clientDrawCellManager.setShouldUpdate(!Globals.clientDrawCellManager.getShouldUpdate()); Globals.clientDrawCellManager.setShouldUpdate(!Globals.clientDrawCellManager.getShouldUpdate());
} }

View File

@ -481,8 +481,6 @@ public class Globals {
skyboxColors = new ArrayList<Vector3f>(); skyboxColors = new ArrayList<Vector3f>();
//load asset manager //load asset manager
assetManager = new AssetManager(); assetManager = new AssetManager();
//script engine
scriptEngine = new ScriptEngine();
//ai manager //ai manager
aiManager = new AIManager(0); aiManager = new AIManager(0);
//realm & data cell manager //realm & data cell manager
@ -526,6 +524,7 @@ public class Globals {
Globals.signalSystem = (SignalSystem)serviceManager.registerService(new SignalSystem()); Globals.signalSystem = (SignalSystem)serviceManager.registerService(new SignalSystem());
Globals.elementService = (ElementService)serviceManager.registerService(new ElementService()); Globals.elementService = (ElementService)serviceManager.registerService(new ElementService());
Globals.particleService = (ParticleService)serviceManager.registerService(new ParticleService()); Globals.particleService = (ParticleService)serviceManager.registerService(new ParticleService());
Globals.scriptEngine = (ScriptEngine)serviceManager.registerService(new ScriptEngine());
serviceManager.instantiate(); serviceManager.instantiate();
// //
//End service manager //End service manager
@ -537,6 +536,7 @@ public class Globals {
Globals.signalSystem.registerService(SignalType.YOGA_DESTROY, Globals.elementService); Globals.signalSystem.registerService(SignalType.YOGA_DESTROY, Globals.elementService);
Globals.signalSystem.registerService(SignalType.UI_MODIFICATION, Globals.elementService); Globals.signalSystem.registerService(SignalType.UI_MODIFICATION, Globals.elementService);
Globals.signalSystem.registerService(SignalType.RENDERING_ENGINE_READY, Globals.particleService); Globals.signalSystem.registerService(SignalType.RENDERING_ENGINE_READY, Globals.particleService);
Globals.signalSystem.registerService(SignalType.SCRIPT_RECOMPILE, Globals.scriptEngine);
} }

View File

@ -291,7 +291,13 @@ public class Main {
RenderingEngine.recaptureIfNecessary(); RenderingEngine.recaptureIfNecessary();
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
} }
///
/// S Y N C H R O N O U S S I G N A L H A N D L I N G
///
Globals.scriptEngine.handleAllSignals();
/// ///
/// ///

View File

@ -14,6 +14,7 @@ import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.server.ServerConnectionHandler; import electrosphere.net.server.ServerConnectionHandler;
import electrosphere.renderer.ui.elements.Window; import electrosphere.renderer.ui.elements.Window;
import electrosphere.server.saves.SaveUtils; import electrosphere.server.saves.SaveUtils;
import electrosphere.server.terrain.generation.TestGenerationChunkGenerator;
/** /**
* Loads the chunk generation testing realm * Loads the chunk generation testing realm
@ -42,6 +43,17 @@ public class ChunkGenerationTestLoading {
loadingWindow.setVisible(true); loadingWindow.setVisible(true);
}); });
//wait on script engine to load
if(TestGenerationChunkGenerator.DEFAULT_USE_JAVASCRIPT){
WindowUtils.updateLoadingWindow("Waiting on scripting engine");
while(!Globals.scriptEngine.isInitialized()){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException ex) {}
}
WindowUtils.updateLoadingWindow("LOADING");
}
String saveName = "generation_testing"; String saveName = "generation_testing";

View File

@ -11,7 +11,7 @@ public class EngineInitLoading {
* Loads the core assets of the scripting engine from disk and initializes the engine * Loads the core assets of the scripting engine from disk and initializes the engine
*/ */
protected static void loadScriptingEngine(Object[] params){ protected static void loadScriptingEngine(Object[] params){
Globals.scriptEngine.init(); Globals.scriptEngine.initScripts();
} }
} }

View File

@ -37,6 +37,11 @@ public class Signal {
REQUEST_CHUNK_EDIT, REQUEST_CHUNK_EDIT,
CHUNK_EDITED, CHUNK_EDITED,
//
//Script
//
SCRIPT_RECOMPILE,
} }
/** /**

View File

@ -124,8 +124,11 @@ public class SceneLoader {
} }
//load scripts //load scripts
if(!isLevelEditor && file.getInitScriptPath() != null){ if(!isLevelEditor && file.getInitScriptPath() != null){
int sceneInstanceId = Globals.scriptEngine.initScene(file.getInitScriptPath()); Realm finalRealm = realm;
realm.setSceneInstanceId(sceneInstanceId); Globals.scriptEngine.executeSynchronously(() -> {
int sceneInstanceId = Globals.scriptEngine.initScene(file.getInitScriptPath());
finalRealm.setSceneInstanceId(sceneInstanceId);
});
} }
//TODO: integrate scripts for client side of scenes //TODO: integrate scripts for client side of scenes
// for(String scriptPath : file.getScriptPaths()){ // for(String scriptPath : file.getScriptPaths()){

View File

@ -2,6 +2,8 @@ package electrosphere.game.data.biome;
import java.util.List; import java.util.List;
import org.graalvm.polyglot.HostAccess.Export;
/** /**
* Data about a given biome * Data about a given biome
*/ */
@ -10,11 +12,13 @@ public class BiomeData {
/** /**
* The id of the biome * The id of the biome
*/ */
@Export
String id; String id;
/** /**
* The display name of the biome * The display name of the biome
*/ */
@Export
String displayName; String displayName;
/** /**
@ -25,21 +29,25 @@ public class BiomeData {
/** /**
* True if this region applies above the surface * True if this region applies above the surface
*/ */
@Export
Boolean isAerial; Boolean isAerial;
/** /**
* True if this region applies to the surface * True if this region applies to the surface
*/ */
@Export
Boolean isSurface; Boolean isSurface;
/** /**
* True if this region applies below the surface * True if this region applies below the surface
*/ */
@Export
Boolean isSubterranean; Boolean isSubterranean;
/** /**
* The surface generation params * The surface generation params
*/ */
@Export
BiomeSurfaceGenerationParams surfaceGenerationParams; BiomeSurfaceGenerationParams surfaceGenerationParams;

View File

@ -2,34 +2,41 @@ package electrosphere.game.data.biome;
import java.util.List; import java.util.List;
import org.graalvm.polyglot.HostAccess.Export;
/** /**
* Describes behavior for spawning a specific type of foliage in the biome * Describes behavior for spawning a specific type of foliage in the biome
*/ */
public class BiomeFoliageDescription { public class BiomeFoliageDescription {
/** /**
* The liust of entity IDs of this foliage type in particular * The list of entity IDs of this foliage type in particular
*/ */
@Export
List<String> entityIDs; List<String> entityIDs;
/** /**
* How regular the placement of foliage is. Low values will create very uneven foliage, while high values will place them along a grid. * How regular the placement of foliage is. Low values will create very uneven foliage, while high values will place them along a grid.
*/ */
@Export
Double regularity; Double regularity;
/** /**
* The percentage of the ground to cover with foliage * The percentage of the ground to cover with foliage
*/ */
@Export
Double threshold; Double threshold;
/** /**
* The priority of this floor element in particular * The priority of this floor element in particular
*/ */
@Export
Double priority; Double priority;
/** /**
* The scale of the noise used to place foliage * The scale of the noise used to place foliage
*/ */
@Export
Double scale; Double scale;
/** /**

View File

@ -2,6 +2,8 @@ package electrosphere.game.data.biome;
import java.util.List; import java.util.List;
import org.graalvm.polyglot.HostAccess.Export;
/** /**
* Params for the surface generation algorithm * Params for the surface generation algorithm
*/ */
@ -10,11 +12,13 @@ public class BiomeSurfaceGenerationParams {
/** /**
* The tag for the generation algorithm for generating the surface * The tag for the generation algorithm for generating the surface
*/ */
@Export
String surfaceGenTag; String surfaceGenTag;
/** /**
* The offset from baseline for height generation with this biome * The offset from baseline for height generation with this biome
*/ */
@Export
Float heightOffset; Float heightOffset;
/** /**
@ -25,6 +29,7 @@ public class BiomeSurfaceGenerationParams {
/** /**
* The list of foliage descriptions available to this biome type * The list of foliage descriptions available to this biome type
*/ */
@Export
List<BiomeFoliageDescription> foliageDescriptions; List<BiomeFoliageDescription> foliageDescriptions;
/** /**

View File

@ -153,7 +153,7 @@ public class ServerWorldData {
//test terrain gen //test terrain gen
{ {
TestGenerationChunkGenerator chunkGen = new TestGenerationChunkGenerator(serverWorldData); TestGenerationChunkGenerator chunkGen = new TestGenerationChunkGenerator(serverWorldData, TestGenerationChunkGenerator.DEFAULT_USE_JAVASCRIPT);
serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, chunkGen); serverTerrainManager = new ServerTerrainManager(serverWorldData, 0, chunkGen);
serverTerrainManager.genTestData(chunkGen); serverTerrainManager.genTestData(chunkGen);
} }

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine; import org.graalvm.polyglot.Engine;
@ -19,15 +20,18 @@ import electrosphere.client.ui.menu.script.ScriptMenuUtils;
import electrosphere.client.ui.menu.tutorial.TutorialMenus; import electrosphere.client.ui.menu.tutorial.TutorialMenus;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.engine.Main; import electrosphere.engine.Main;
import electrosphere.engine.signal.Signal;
import electrosphere.engine.signal.SignalServiceImpl;
import electrosphere.logger.LoggerInterface; import electrosphere.logger.LoggerInterface;
import electrosphere.script.translation.JSServerUtils; import electrosphere.script.translation.JSServerUtils;
import electrosphere.script.utils.ScriptMathInterface;
import electrosphere.util.FileUtils; import electrosphere.util.FileUtils;
import electrosphere.util.math.SpatialMathUtils; import electrosphere.util.math.SpatialMathUtils;
/** /**
* Interface for executing scripts in the game engine * Interface for executing scripts in the game engine
*/ */
public class ScriptEngine { public class ScriptEngine extends SignalServiceImpl {
//the default namespaces for //the default namespaces for
public static String SCRIPT_NAMESPACE_ENGINE = "engine"; //namespace for the engine functions exposed to the script engine public static String SCRIPT_NAMESPACE_ENGINE = "engine"; //namespace for the engine functions exposed to the script engine
@ -67,6 +71,11 @@ public class ScriptEngine {
*/ */
boolean initialized = false; boolean initialized = false;
/**
* Locks the script engine to enforce synchronization
*/
ReentrantLock lock = new ReentrantLock();
//The files that are loaded on init to bootstrap the script engine //The files that are loaded on init to bootstrap the script engine
static final String[] filesToLoadOnInit = new String[]{ static final String[] filesToLoadOnInit = new String[]{
//polyfills //polyfills
@ -98,6 +107,7 @@ public class ScriptEngine {
{"menuUtils",ScriptMenuUtils.class}, {"menuUtils",ScriptMenuUtils.class},
{"voxelUtils",ScriptClientVoxelUtils.class}, {"voxelUtils",ScriptClientVoxelUtils.class},
{"levelEditorUtils",ScriptLevelEditorUtils.class}, {"levelEditorUtils",ScriptLevelEditorUtils.class},
{"math",ScriptMathInterface.class},
}; };
//singletons from the host that are provided to the javascript context //singletons from the host that are provided to the javascript context
@ -107,10 +117,18 @@ public class ScriptEngine {
{"loggerScripts",LoggerInterface.loggerScripts}, {"loggerScripts",LoggerInterface.loggerScripts},
}; };
/**
* Constructor
*/
public ScriptEngine(){
super("ScriptEngine");
}
/** /**
* Initializes the engine * Initializes the engine
*/ */
public void init(){ public void initScripts(){
//init datastructures //init datastructures
sourceMap = new HashMap<String,Source>(); sourceMap = new HashMap<String,Source>();
initialized = false; initialized = false;
@ -320,6 +338,22 @@ public class ScriptEngine {
invokeMemberFunction("COMPILER", "run"); invokeMemberFunction("COMPILER", "run");
} }
/**
* Recompiles the scripting engine
*/
private void recompile(Runnable onCompletion){
Thread recompileThread = new Thread(() -> {
Globals.scriptEngine.executeSynchronously(() -> {
this.initScripts();
});
if(onCompletion != null){
onCompletion.run();
}
});
recompileThread.setName("Recompile Script Engine");
recompileThread.start();
}
/** /**
* Initializes a scene script * Initializes a scene script
* @param scenePath The scene's init script path * @param scenePath The scene's init script path
@ -398,6 +432,40 @@ public class ScriptEngine {
invokeFunction("require", filePath); invokeFunction("require", filePath);
} }
/**
* Invokes a function on a member of arbitrary depth on the engine object
* @param memberName The member name
* @param functionName The function's name
* @param className The class of the expected return value
* @param args The args to pass to the function
* @return The results of the invocation or null if there was no result
*/
public Value invokeEngineMember(String memberName, String functionName, Object ... args){
Value member = this.engineObject.getMember(memberName);
if(member == null){
throw new Error("Member is null!");
}
Value function = member.getMember(functionName);
if(function == null || !function.canExecute()){
throw new Error("Function is not executable! " + function);
}
Value executionResult = function.execute(args);
if(executionResult == null){
return null;
}
return executionResult;
}
/**
* Executes some code synchronously that requires script engine access
* @param function The function
*/
public void executeSynchronously(Runnable function){
lock.lock();
function.run();
lock.unlock();
}
/** /**
* Defines host members within javascript context * Defines host members within javascript context
*/ */
@ -431,5 +499,23 @@ public class ScriptEngine {
return this.initialized; return this.initialized;
} }
@Override
public boolean handle(Signal signal){
boolean rVal = false;
switch(signal.getType()){
case SCRIPT_RECOMPILE: {
if(signal.getData() != null && signal.getData() instanceof Runnable){
this.recompile((Runnable)signal.getData());
} else {
this.recompile(null);
}
rVal = true;
} break;
default: {
} break;
}
return rVal;
}
} }

View File

@ -0,0 +1,21 @@
package electrosphere.script.utils;
import org.graalvm.polyglot.HostAccess.Export;
/**
* Script access to specific math functions
*/
public class ScriptMathInterface {
/**
* Power function
* @param val1 The number
* @param val2 The exponent
* @return The power
*/
@Export
public static double pow(double val1, double val2){
return Math.pow(val1,val2);
}
}

View File

@ -421,9 +421,6 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
/** /**
* Evicts all loaded chunks. * Evicts all loaded chunks.
* <p>
* Note: Does not save to disk.
* </p>
*/ */
public void evictAll(){ public void evictAll(){
//TODO: improve to make have less performance impact //TODO: improve to make have less performance impact

View File

@ -274,11 +274,13 @@ public class Realm {
*/ */
public void fireSignal(String signalName, Object ... args){ public void fireSignal(String signalName, Object ... args){
if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){ if(Globals.scriptEngine != null && Globals.scriptEngine.isInitialized()){
if(this.sceneInstanceId != NO_SCENE_INSTANCE){ Globals.scriptEngine.executeSynchronously(() -> {
Globals.scriptEngine.fireSignal(signalName, sceneInstanceId, args); if(this.sceneInstanceId != NO_SCENE_INSTANCE){
} else { Globals.scriptEngine.fireSignal(signalName, sceneInstanceId, args);
Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args); } else {
} Globals.scriptEngine.fireSignal(signalName, ScriptEngine.GLOBAL_SCENE, args);
}
});
} }
} }

View File

@ -3,6 +3,8 @@ package electrosphere.server.terrain.generation;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.graalvm.polyglot.Value;
import electrosphere.engine.Globals; import electrosphere.engine.Globals;
import electrosphere.game.data.biome.BiomeData; import electrosphere.game.data.biome.BiomeData;
import electrosphere.game.data.biome.BiomeSurfaceGenerationParams; import electrosphere.game.data.biome.BiomeSurfaceGenerationParams;
@ -12,6 +14,7 @@ import electrosphere.server.terrain.generation.heightmap.HeightmapGenerator;
import electrosphere.server.terrain.generation.heightmap.HillsGen; import electrosphere.server.terrain.generation.heightmap.HillsGen;
import electrosphere.server.terrain.generation.heightmap.PlainsGen; import electrosphere.server.terrain.generation.heightmap.PlainsGen;
import electrosphere.server.terrain.generation.interfaces.ChunkGenerator; import electrosphere.server.terrain.generation.interfaces.ChunkGenerator;
import electrosphere.server.terrain.generation.interfaces.GeneratedVoxel;
import electrosphere.server.terrain.manager.ServerTerrainChunk; import electrosphere.server.terrain.manager.ServerTerrainChunk;
import electrosphere.server.terrain.models.TerrainModel; import electrosphere.server.terrain.models.TerrainModel;
@ -29,6 +32,21 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* The default biome index * The default biome index
*/ */
public static final int DEFAULT_BIOME_INDEX = 1; public static final int DEFAULT_BIOME_INDEX = 1;
/**
* The width of the surface in number of voxels
*/
public static final int SURFACE_VOXEL_WIDTH = 2;
/**
* Tag for the test generator
*/
public static final String SCRIPT_GEN_TEST_TAG = "test";
/**
* Controls the default setting for whether to use javascript or not
*/
public static final boolean DEFAULT_USE_JAVASCRIPT = false;
/** /**
* The terreain model for the generator * The terreain model for the generator
@ -45,14 +63,20 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
*/ */
Map<String,HeightmapGenerator> tagGeneratorMap = new HashMap<String,HeightmapGenerator>(); Map<String,HeightmapGenerator> tagGeneratorMap = new HashMap<String,HeightmapGenerator>();
/**
* Tracks whether to use javascript generation or not
*/
boolean useJavascript = false;
/** /**
* Constructor * Constructor
*/ */
public TestGenerationChunkGenerator(ServerWorldData serverWorldData){ public TestGenerationChunkGenerator(ServerWorldData serverWorldData, boolean useJavascript){
this.serverWorldData = serverWorldData; this.serverWorldData = serverWorldData;
registerGenerator(new EmptySkyGen()); registerGenerator(new EmptySkyGen());
registerGenerator(new HillsGen()); registerGenerator(new HillsGen());
registerGenerator(new PlainsGen()); registerGenerator(new PlainsGen());
this.useJavascript = useJavascript;
} }
/** /**
@ -101,22 +125,61 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
} }
} }
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){ if(this.useJavascript){
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice"); Globals.scriptEngine.executeSynchronously(() -> {
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){ Value getVoxelFunc = Globals.scriptEngine.invokeEngineMember("chunkGeneratorManager", "getVoxelFunction", SCRIPT_GEN_TEST_TAG);
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){ for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION); Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION); for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION); for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION; int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION; int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION; int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
GeneratedVoxel voxel = this.getVoxel(finalWorldX, finalWorldY, finalWorldZ, finalChunkX, finalChunkY, finalChunkZ, heightfield[x][z], this.terrainModel, surfaceBiome); int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
weights[x][y][z] = voxel.weight; int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
values[x][y][z] = voxel.type; int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
Value result = getVoxelFunc.execute(
finalWorldX, finalWorldY, finalWorldZ,
finalChunkX, finalChunkY, finalChunkZ,
stride,
heightfield[x][z],
surfaceBiome
);
if(result != null){
weights[x][y][z] = result.getMember("weight").asFloat();
values[x][y][z] = result.getMember("type").asInt();
}
}
}
Globals.profiler.endCpuSample();
} }
});
} else {
for(int x = 0; x < ServerTerrainChunk.CHUNK_DIMENSION; x++){
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator - Generate slice");
for(int y = 0; y < ServerTerrainChunk.CHUNK_DIMENSION; y++){
for(int z = 0; z < ServerTerrainChunk.CHUNK_DIMENSION; z++){
int finalWorldX = worldX + ((x * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldY = worldY + ((y * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalWorldZ = worldZ + ((z * strideValue) / ServerTerrainChunk.CHUNK_DIMENSION);
int finalChunkX = (x * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkY = (y * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
int finalChunkZ = (z * strideValue) % ServerTerrainChunk.CHUNK_DIMENSION;
GeneratedVoxel voxel = this.getVoxel(
finalWorldX, finalWorldY, finalWorldZ,
finalChunkX, finalChunkY, finalChunkZ,
stride,
heightfield[x][z],
surfaceBiome
);
if(voxel != null){
weights[x][y][z] = voxel.weight;
values[x][y][z] = voxel.type;
}
}
}
Globals.profiler.endCpuSample();
} }
Globals.profiler.endCpuSample();
} }
} catch(Exception ex){ } catch(Exception ex){
ex.printStackTrace(); ex.printStackTrace();
@ -156,16 +219,16 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
* @param chunkX The chunk x pos * @param chunkX The chunk x pos
* @param chunkY The chunk y pos * @param chunkY The chunk y pos
* @param chunkZ The chunk z pos * @param chunkZ The chunk z pos
* @param stride The stride of the data
* @param surfaceHeight The height of the surface at x,z * @param surfaceHeight The height of the surface at x,z
* @param terrainModel The terrain model
* @param surfaceBiome The surface biome of the chunk * @param surfaceBiome The surface biome of the chunk
* @return The value of the chunk * @return The value of the chunk
*/ */
private GeneratedVoxel getVoxel( private GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ, int chunkX, int chunkY, int chunkZ,
int stride,
float surfaceHeight, float surfaceHeight,
TerrainModel terrainModel,
BiomeData surfaceBiome BiomeData surfaceBiome
){ ){
Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.getChunkValue"); Globals.profiler.beginAggregateCpuSample("TestGenerationChunkGenerator.getChunkValue");
@ -179,24 +242,26 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY); double realY = this.serverWorldData.convertVoxelToRealSpace(chunkY,worldY);
double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ); double realZ = this.serverWorldData.convertVoxelToRealSpace(chunkZ,worldZ);
double flooredSurfaceHeight = Math.floor(surfaceHeight); double strideMultiplier = Math.pow(2,stride);
double heightDiff = realY - surfaceHeight;
double surfacePercent = TestGenerationChunkGenerator.getSurfaceWeight(surfaceHeight,realY,strideMultiplier);
Globals.profiler.endCpuSample(); Globals.profiler.endCpuSample();
if(realY < surfaceHeight - 1){ if(heightDiff < -strideMultiplier * SURFACE_VOXEL_WIDTH){
return getSubsurfaceVoxel( return getSubsurfaceVoxel(
worldX, worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfacePercent,
terrainModel, surfaceHeight,
surfaceBiome surfaceBiome
); );
} else if(realY > flooredSurfaceHeight) { } else if(heightDiff > 0) {
return getOverSurfaceVoxel( return getOverSurfaceVoxel(
worldX, worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,-
surfaceHeight, flooredSurfaceHeight, surfacePercent,
terrainModel, surfaceHeight,
surfaceBiome surfaceBiome
); );
} else { } else {
@ -204,8 +269,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
worldX, worldY, worldZ, worldX, worldY, worldZ,
chunkX, chunkY, chunkZ, chunkX, chunkY, chunkZ,
realX, realY, realZ, realX, realY, realZ,
surfaceHeight, flooredSurfaceHeight, surfacePercent,
terrainModel, surfaceHeight,
surfaceBiome surfaceBiome
); );
} }
@ -219,12 +284,12 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ, int chunkX, int chunkY, int chunkZ,
double realX, double realY, double realZ, double realX, double realY, double realZ,
float surfaceHeight, double flooredSurfaceHeight, double surfacePercent,
TerrainModel terrainModel, float surfaceHeight,
BiomeData surfaceBiome BiomeData surfaceBiome
){ ){
GeneratedVoxel voxel = new GeneratedVoxel(); GeneratedVoxel voxel = new GeneratedVoxel();
voxel.weight = (float)(surfaceHeight - flooredSurfaceHeight) * 2 - 1; voxel.weight = (float)surfacePercent * 2 - 1;
voxel.type = 2; voxel.type = 2;
return voxel; return voxel;
} }
@ -237,8 +302,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ, int chunkX, int chunkY, int chunkZ,
double realX, double realY, double realZ, double realX, double realY, double realZ,
float surfaceHeight, double flooredSurfaceHeight, double surfacePercent,
TerrainModel terrainModel, float surfaceHeight,
BiomeData surfaceBiome BiomeData surfaceBiome
){ ){
GeneratedVoxel voxel = new GeneratedVoxel(); GeneratedVoxel voxel = new GeneratedVoxel();
@ -260,8 +325,8 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
int worldX, int worldY, int worldZ, int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ, int chunkX, int chunkY, int chunkZ,
double realX, double realY, double realZ, double realX, double realY, double realZ,
float surfaceHeight, double flooredSurfaceHeight, double surfacePercent,
TerrainModel terrainModel, float surfaceHeight,
BiomeData surfaceBiome BiomeData surfaceBiome
){ ){
GeneratedVoxel voxel = new GeneratedVoxel(); GeneratedVoxel voxel = new GeneratedVoxel();
@ -271,17 +336,14 @@ public class TestGenerationChunkGenerator implements ChunkGenerator {
} }
/** /**
* A voxel that was generated * Calculates the weight of a voxel on the surface based on the surface height, the position of the voxel, and the stride multiplier
* @param surfaceHeight The surface height
* @param realPosY The position of the voxel
* @param strideMultiplier The stride multiplier
* @return The weight of the voxel
*/ */
static class GeneratedVoxel { protected static double getSurfaceWeight(double surfaceHeight, double realPosY, double strideMultiplier){
/** return ((surfaceHeight - realPosY) / strideMultiplier);
* The type of the voxel
*/
int type;
/**
* The weight of the voxel
*/
float weight;
} }
} }

View File

@ -0,0 +1,22 @@
package electrosphere.server.terrain.generation.interfaces;
import org.graalvm.polyglot.HostAccess.Export;
/**
* A voxel generated by a chunk generator
*/
public class GeneratedVoxel {
/**
* The type of the voxel
*/
@Export
public int type;
/**
* The weight of the voxel
*/
@Export
public float weight;
}

View File

@ -0,0 +1,37 @@
package electrosphere.server.terrain.generation.interfaces;
import electrosphere.game.data.biome.BiomeData;
/**
* A script-defined chunk generator
*/
public interface JSChunkGenerator {
/**
* Gets the tag for this generator
* @returns The tag
*/
public String getTag();
/**
* Retrieves the elevation for the world at a given x,z coordinate
* @param worldX The world x coordinate
* @param worldZ The world z coordinate
* @param chunkX The x coordinate of the chunk within the specified world coordinate
* @param chunkZ The z coordinate of the chunk within the specified world coordinate
* @returns The elevation at that specific position
*/
public float getElevation(int worldX, int worldZ, int chunkX, int chunkZ);
/**
* The function to get a voxel for a given position
*/
public GeneratedVoxel getVoxel(
int worldX, int worldY, int worldZ,
int chunkX, int chunkY, int chunkZ,
int stride,
double surfaceHeight,
BiomeData surfaceBiome
);
}

View File

@ -84,6 +84,9 @@ public class ServerChunkCache {
lock.acquireUninterruptibly(); lock.acquireUninterruptibly();
cacheMapFullRes.clear(); cacheMapFullRes.clear();
cacheMapHalfRes.clear(); cacheMapHalfRes.clear();
cacheMapQuarterRes.clear();
cacheMapEighthRes.clear();
cacheMapSixteenthRes.clear();
lock.release(); lock.release();
} }

View File

@ -0,0 +1,22 @@
package electrosphere.server.terrain.generation;
import static org.junit.jupiter.api.Assertions.assertEquals;
import electrosphere.test.annotations.FastTest;
import electrosphere.test.annotations.UnitTest;
/**
* Unit tests for the test generation chunk generator
*/
public class TestGenerationChunkGeneratorTests {
@UnitTest
@FastTest
public void getSurfaceWeight_ValueTests(){
assertEquals(0.5,TestGenerationChunkGenerator.getSurfaceWeight(0.5, 0, 1));
assertEquals(0.1,TestGenerationChunkGenerator.getSurfaceWeight(0.1, 0, 1));
assertEquals(0.9,TestGenerationChunkGenerator.getSurfaceWeight(0.9, 0, 1));
assertEquals(0.95,TestGenerationChunkGenerator.getSurfaceWeight(1.9, 0, 2));
}
}