Version 0.07a | May 31, 2025
- Introduction
- Engine Architecture
- Core Components
- Entity System
- Scene Management
- Rendering System
- Input Handling
- User Interface System
- Debug Tools
- Utility Classes
- Character Controller
- Best Practices
- Future Development
- Advanced Development Guide
j3D is a lightweight 3D game engine built with LWJGL, OpenGL, JOML, and other supporting libraries. It provides a simple yet powerful framework for creating 3D applications and games.
This manual documents the refactored architecture of the engine, highlighting the improvements made to enhance scalability, maintainability, and performance.
- Scene-based rendering system
- Entity management
- First person camera controls
- Character controller with physics-based movement
- Basic lighting system (ambient, diffuse, specular)
- OBJ model loading
- Texture management
- Shader system for custom rendering effects
The j3D engine follows a component-based architecture with clear separation of concerns:
com.discardsoft.j3D
├── Main # Application entry point
├── core # Core engine systems
│ ├── EngineManager # Main game loop and timing
│ ├── IGameLogic # Game implementation interface
│ ├── ObjectLoader # Resource loading
│ ├── RenderManager # Rendering pipeline
│ ├── ShaderManager # Shader program handling
│ ├── WindowManager # Window and input management
│ ├── entity # Entity component system
│ │ ├── Camera # Camera entity
│ │ ├── Entity # Base entity class
│ │ ├── Light # Light entity
│ │ ├── Model # 3D model data
│ │ ├── Player # Player character controller
│ │ ├── Texture # Texture data
│ │ └── terrain # Terrain system
│ │ ├── Terrain # Terrain generation
│ │ └── TerrainEntity # Terrain entity wrapper
│ ├── scene # Scene management
│ │ ├── BaseScene # Abstract scene base class
│ │ ├── IScene # Scene interface
│ │ └── TestScene # Concrete scene implementation
│ ├── ui # User interface system
│ │ ├── UIElement # Base UI element
│ │ ├── UIManager # UI management
│ │ ├── Panel # UI panel component
│ │ ├── Menu # Menu system
│ │ └── PauseMenu # Pause menu implementation
│ └── utils # Helper utilities
│ ├── Consts # Engine constants
│ ├── DebugHUD # Debug information display
│ ├── LoadModel # Model loading utilities
│ ├── Settings # Engine settings
│ ├── TextRenderer # Text rendering
│ ├── Transformation # Matrix transformations
│ └── Utils # General utilities
└── game # Game implementation
└── TestGame # Example game implementation
The EngineManager class is the core of the engine, managing the game loop timing, FPS calculation, and coordinating initialization, updates, rendering, and cleanup processes.
Key Methods:
init(): Initializes engine componentsstart(): Begins the game looprun(): Executes the main game loop with fixed timestep updatescleanup(): Releases all resources
This interface defines the contract that any game implementation must follow to operate within the engine's lifecycle.
Required Methods:
init(): Initializes game resourcesinput(): Process user inputupdate(): Update game staterender(): Render the game scenecleanup(): Release game resources
Handles window creation, input processing, and viewport management with GLFW.
Key Features:
- Window creation and configuration
- Mouse and keyboard input handling
- Viewport and projection management
The entity system provides the fundamental objects that make up a scene:
Represents a renderable object in 3D space with:
- Position
- Rotation
- Scale
- 3D Model reference
- Billboard flags (Y axis and Full)
Key Methods:
incrementPosition(x, y, z): Move the entityincrementRotation(x, y, z): Rotate the entitysetPosition(x, y, z): Set absolute positionsetRotation(x, y, z): Set absolute rotationsetBillboardY(boolean): Enable Y axis (horizontal) billboardingsetBillboardFull(boolean): Enable full billboardingsetHasTransparentTexture(boolean): Set transparency flag for proper rendering
The billboarding system allows entities to automatically orient themselves toward the camera, which is useful for vegetation, particles, and other 2D elements in a 3D world.
j3D supports two types of billboarding:
-
Y axis Billboarding: The entity rotates only around the Y axis to face the camera, maintaining its vertical orientation. This is ideal for trees, grass, and other objects that should remain upright.
-
Full Billboarding: The entity completely faces the camera from all angles. This is suitable for particles, icons, and other elements that should always be fully visible to the player.
To create billboarded entities:
// Create a standard entity first
Entity entity = new Entity(
model,
new Vector3f(x, y, z), // position
new Vector3f(0, 0, 0), // rotation (will be managed by billboarding)
new Vector3f(1, 1, 1) // scale
);
// For Y axis billboarding (horizontal rotation only)
entity.setBillboardY(true);
// Or for full billboarding (always facing camera)
entity.setBillboardFull(true);
// Most billboarded entities have transparent textures
entity.setHasTransparentTexture(true);The billboarding system automatically calculates the correct orientation based on the camera's position:
- Y axis billboarding calculates the angle between the entity and camera in the XZ plane
- Full billboarding creates a complete look-at matrix so the entity always faces the camera
Billboarded entities are automatically rendered with proper transparency if the hasTransparentTexture flag is set.
The j3D engine includes a robust terrain system for creating large-scale ground surfaces and landscapes.
The terrain system generates terrain meshes from height data and applies textures:
// Create a terrain with specified parameters
Terrain terrain = new Terrain(
512.0f, // terrain size (width and depth)
64, // grid resolution (64x64 vertices)
0.0f, // base height (Y position)
new Vector3f(-256, 0, -256), // position (centered at origin)
objectLoader, // object loader instance
"src/main/resources/textures/ground2.png", // texture path
64.0f // texture repeat factor
);TerrainEntity wraps the terrain data into an entity that can be added to scenes:
// Create a terrain entity from terrain data
TerrainEntity terrainEntity = new TerrainEntity(terrain);
// Add terrain to the scene
scene.addEntity(terrainEntity);Key Features:
- Procedural Generation: Generate terrain meshes with customizable resolution
- Texture Mapping: Apply textures with configurable repeat factors
- Scene Integration: Seamlessly integrate with the entity/scene system
- Performance Optimized: Efficient mesh generation and rendering
Represents a 3D model with:
- Vertex Array Object (VAO) ID
- Vertex count
- Texture reference
Provides a view into the 3D world with:
- Position
- Rotation
- First person movement controls
Key Methods:
movePosition(x, y, z): Move relative to camera's orientationrotateCamera(deltaYaw, deltaPitch): Rotate the camera
Defines a light source with:
- Position
- Color
- Ambient light intensity
Represents a player character in the game world with:
- Physics-based movement with inertia
- First person camera at eye level
- Collision detection via a bounding capsule
- Support for both physics-based and free camera modes
Key Methods:
update(deltaTime): Update player position based on physics and inputsetMovementInput(forwardMovement, sidewaysMovement): Set movement directiontoggleFreeCamera(): Switch between player-bound and free camera modessetPosition(x, y, z): Set absolute player position (feet position)getPosition(): Get current player positiongetVelocity(): Get player's current velocity vectorgetCamera(): Get the player's camera instancegetBoundingEntity(): Get the player's collision entity
The Main class provides global access to the current camera:
// Get the current camera from anywhere in the application
Camera currentCamera = Main.getCurrentCamera();
// This method returns the player's camera if the game and player are initialized
// Returns null if either is not availableThe scene system organizes entities for efficient management:
A concrete scene implementation that:
- Manages collections of entities
- Provides central access to the scene lighting
- Updates all entities in batch
Key Methods:
initialize(): Set up the initial scene contentsaddEntity(entity): Add an entity to the sceneupdate(deltaTime): Update all entities with time-based movementgetEntities(): Get all entities for renderinggetLight(): Get the scene's light source
The rendering system handles the display of 3D objects:
Manages the OpenGL rendering process:
- Initializes shaders
- Sets uniform variables
- Renders entities and entire scenes
Key Methods:
render(scene, camera): Render an entire scenerender(entity, camera, light): Render a single entity (legacy)
Handles GLSL shader programs:
- Compiles vertex and fragment shaders
- Links shader programs
- Sets uniform variables
The j3D engine provides comprehensive input handling through the WindowManager class, supporting both keyboard and mouse input for games and applications.
The WindowManager provides several methods for handling keyboard input:
// Check if a key is currently being held down
if (window.isKeyPressed(GLFW.GLFW_KEY_W)) {
// Move forward while W is held
player.moveForward();
}
// Check for single key press (buffered input)
if (window.isKeyPressedBuffered(GLFW.GLFW_KEY_SPACE)) {
// Jump only once per key press
player.jump();
}Key Methods:
isKeyPressed(int keyCode): Returns true while a key is held downisKeyPressedBuffered(int keyCode): Returns true only once per key press- Built-in support for force quit with backslash key
The mouse input system handles both movement and button presses:
// Process mouse movement for camera control
Vector2f mouseMovement = window.processMouseMovement();
camera.processMouseMovement(mouseMovement.x, mouseMovement.y);// Check if left mouse button is pressed
if (window.isMouseButtonPressed()) {
// Handle continuous input (e.g., shooting)
handleShooting();
}
// Check for single click events
if (window.isMouseButtonClicked()) {
// Handle one-time actions (e.g., menu selection)
selectMenuItem();
}// Handle scroll wheel input
if (window.hasScrolled()) {
double scrollOffset = window.getScrollOffset();
// Zoom camera or change weapon
camera.adjustZoom(scrollOffset);
}The engine automatically manages cursor capture for camera control:
// Release cursor (makes it visible, stops camera control)
window.releaseCursor();
// Capture cursor for camera control
window.captureCursor();
// Check cursor state
if (window.isCursorCaptured()) {
// Camera control is active
}
if (window.isWaitingForClick()) {
// Cursor released, waiting for click to recapture
}The engine handles window focus automatically:
// Check if window has focus
if (window.isWindowFocused()) {
// Process input normally
} else {
// Window is not focused, pause game or reduce processing
}
// Add listeners for focus events
window.addWindowFocusListener(focused -> {
if (focused) {
System.out.println("Window gained focus");
} else {
System.out.println("Window lost focus");
}
});- Use buffered input for discrete actions: Use
isKeyPressedBuffered()for actions that should only trigger once per key press - Handle window focus: The engine automatically pauses cursor capture when the window loses focus
- Mouse sensitivity: Configure mouse sensitivity in the Settings class
- Raw mouse input: The engine automatically uses raw mouse input when available for better camera control
The WindowManager also provides:
- Automatic cursor capture/release on window focus/unfocus
- Event listeners for cursor capture and window focus changes
- Support for raw mouse motion when available
- Configurable mouse sensitivity through Settings
The j3D engine provides a comprehensive UI system for creating game interfaces, menus, and HUD elements.
The UI system is built around the following components:
The base class for all UI components:
/**
* Base UI element with position, size, and parent-child relationships
*/
public abstract class UIElement {
protected Vector2f position; // Normalized screen coordinates (0-1)
protected Vector2f size; // Normalized size
protected UIElement parent; // Parent element
protected List<UIElement> children; // Child elements
// Core methods
public abstract void render();
public abstract void update(float deltaTime);
public abstract boolean handleInput(WindowManager window);
}Manages all UI elements and coordinates rendering:
// Create and use a UI manager
UIManager uiManager = new UIManager();
// Add UI elements
uiManager.addElement(panel);
uiManager.addElement(menu);
// Update and render in game loop
uiManager.update(deltaTime);
uiManager.render();Rectangular UI elements for backgrounds and containers:
// Create a panel with position, size, and color
Panel panel = new Panel(
new Vector2f(0.1f, 0.1f), // position (10% from top-left)
new Vector2f(0.8f, 0.8f), // size (80% of screen)
new Vector4f(0.2f, 0.2f, 0.2f, 0.8f) // color (dark gray, 80% opacity)
);Create interactive menus with buttons and navigation:
// Create a main menu
Menu mainMenu = new Menu(
new Vector2f(0.5f, 0.5f), // centered position
new Vector2f(0.4f, 0.6f) // menu size
);
// Add menu items with callbacks
mainMenu.addMenuItem("Start Game", () -> {
gameState.setState(GameState.PLAYING);
});
mainMenu.addMenuItem("Settings", () -> {
showSettingsMenu();
});
mainMenu.addMenuItem("Exit", () -> {
System.exit(0);
});A specialized menu for game pause functionality:
// Create and show pause menu
PauseMenu pauseMenu = new PauseMenu();
pauseMenu.setVisible(true);
// Handle pause menu input
if (window.isKeyPressedBuffered(GLFW.GLFW_KEY_ESCAPE)) {
pauseMenu.toggle();
}UI elements can capture and handle input events:
// Override handleInput in custom UI elements
@Override
public boolean handleInput(WindowManager window) {
// Check if mouse is over this element
if (isMouseOver(window.getMousePosition())) {
// Handle click
if (window.isMouseButtonPressed(GLFW.GLFW_MOUSE_BUTTON_LEFT)) {
onClick();
return true; // Input consumed
}
}
return false; // Input not consumed
}The j3D engine includes comprehensive debugging tools to assist with development and troubleshooting.
The DebugHUD displays real-time information about the game state:
// Create and initialize debug HUD
DebugHUD debugHUD = new DebugHUD(window);
// Render debug information in the game loop
debugHUD.render(
currentFps, // Current FPS
player, // Player entity for position/velocity info
scene, // Scene for entity count
cameraMoveSpeed // Current camera movement speed
);Debug Information Displayed:
- FPS: Real-time frames per second
- Position: Player position coordinates
- Velocity: Player movement velocity vector
- Camera Rotation: Camera pitch and yaw angles
- Entity Count: Number of entities in the current scene
- Camera Speed: Current movement speed setting
The TextRenderer provides functionality for drawing text on screen:
// Create text renderer
TextRenderer textRenderer = new TextRenderer();
// Render text at specific positions
textRenderer.renderText(
"Debug Information", // text to render
10, // x position (pixels)
10, // y position (pixels)
new Vector3f(1, 1, 1) // color (white)
);Toggle wireframe rendering to inspect mesh structure:
// Toggle wireframe mode
if (window.isKeyPressedBuffered(GLFW.GLFW_KEY_APOSTROPHE)) {
wireframeMode = !wireframeMode;
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK,
wireframeMode ? GL11.GL_LINE : GL11.GL_FILL);
}Use the player's free camera mode for scene inspection:
// Toggle free camera for development
if (window.isKeyPressedBuffered(GLFW.GLFW_KEY_F)) {
player.toggleFreeCamera();
}General utility methods for the engine:
- Buffer management for OpenGL data transfer
- Resource loading and file handling
- Memory management utilities
Matrix calculations for 3D transformations:
createTransformationMatrix(): Create transformation matrices for entitiescreateViewMatrix(): Create view matrices for camerasupdateProjectionMatrix(): Update projection matrices for rendering
Configurable engine settings stored as constants:
Development Settings:
DEV: Development mode flag (enables debug features)
Window Settings:
RESIZABLE: Window resizability flagVSYNC: Vertical synchronization flag
Camera Settings:
CAMERA_MOVE_SPEED: Speed multiplier for camera movementMOUSE_SENSITIVITY: Sensitivity multiplier for mouse movementFOV: Field of view angle in radians
UI Settings:
UI_SCALE: Global scale multiplier for UI elements
// Example usage of settings
if (Settings.DEV) {
// Enable development features
enableWireframeMode();
}
// Use camera settings
float moveSpeed = Settings.CAMERA_MOVE_SPEED * deltaTime;Engine-wide constants for version information and rendering:
Version and Branding:
VERSION: Engine version string ("v0.07a")AUTHOR: Author/company name ("DiscardSoft")AUTHOR_STYLIZED: Stylized author name ("DISCVRD")TITLE: Engine name ("j3D")
Rendering Constants:
Z_NEAR: Near clipping plane distance (0.001f)Z_FAR: Far clipping plane distance (10000f)
Helper methods for loading 3D models and creating primitive shapes:
loadOBJModel(): Load OBJ models with texturesmodel(): Load predefined primitive models by name- Built-in primitives: "P_Cube", "P_Sphere_Medium", "P_Sphere_Small", "suzanne"
// Load a built-in primitive model
Model cubeModel = LoadModel.model("P_Cube");
// Load a custom OBJ model
Model customModel = LoadModel.loadOBJModel("/models/character.obj");The j3D engine provides a character controller system for implementing player movement and interaction:
The Player class serves as a character controller with the following features:
- Momentum and inertia simulation
- Maximum velocity capping
- Delta-time independent movement
- Eye-level camera positioning
- First person perspective
- Smooth movement
- Toggle between character-bound and free-floating camera
- Camera position and rotation preservation when switching modes
- Development-friendly camera controls
// Create a player at position (0,0,0)
Player player = new Player(new Vector3f(0, 0, 0));
// Handle keyboard input for player movement
public void handleInput() {
// Get movement input (-1, 0, or 1 for each axis)
float forwardInput = 0;
float sidewaysInput = 0;
if (window.isKeyPressed(GLFW.GLFW_KEY_W)) forwardInput = 1;
if (window.isKeyPressed(GLFW.GLFW_KEY_S)) forwardInput = -1;
if (window.isKeyPressed(GLFW.GLFW_KEY_A)) sidewaysInput = -1;
if (window.isKeyPressed(GLFW.GLFW_KEY_D)) sidewaysInput = 1;
// Apply input to player movement
player.setMovementInput(forwardInput, sidewaysInput);
// Toggle free camera with F key
if (window.isKeyPressed(GLFW.GLFW_KEY_F)) {
player.toggleFreeCamera();
}
}
// Update player in game loop
public void update(float deltaTime) {
player.update(deltaTime);
}
// In render method, use player's camera
public void render() {
renderer.render(scene, player.getCamera());
}When working with the j3D engine, follow these best practices:
- Create ReUsable and improvable tools: Think of the future of these tools while creating them. Avoid developing unique systems for simple things. Instead, build on existing features before adding to the pile so to speak.
- Build tools for dumb people: When you create tools and methods for repeated use, you should attempt to create them usable by less technically inclined, sleep deprived, engineers. Simple and easy to comprehend.
- Reuasable: If you make a function and only use it once, it shouldnt need its own class. Keep methods to their own class and try to build on currently existing classes if the subject matter is similar. Only create new classes if its a completely foreign topic that hasn'e been implemented yet.
- Use the scene-based approach: Add entities to scenes and render entire scenes rather than individual entities.
- Organize related entities: Group related entities together in appropriate scenes.
- Reuse models: Share model data between similar entities to reduce memory usage.
- Clean up unused resources: Call cleanup methods when entities are no longer needed.
- Batch similar entities: Keep entities with the same model/texture together for more efficient rendering.
- Use delta time: Scale animations and movements by delta time for consistent speed.
- Follow naming conventions: Use the established naming patterns for consistency.
- Document with JavaDoc: Add JavaDoc comments to describe classes, methods, and parameters.
- Use the Player class for first person games: The Player class provides a complete character controller with physics-based movement.
- Scale movement by delta time: Use the provided update method with proper delta time to ensure consistent movement across different frame rates.
- Use free camera mode for development: Toggle free camera mode during development to easily navigate and debug scenes.
- Customize physics parameters: Modify player constants to adjust movement feel (inertia, maximum velocity, etc.)
The j3D engine has several planned areas for future enhancement:
- Scene transition system
- Hierarchical scene graph
- Improved entity organization
- Dynamic loading/unloading of scenes
- Multiple shader support for different material types
- Post-processing effects pipeline
- Shadow mapping system
- Instanced rendering for better performance
- Level of Detail (LOD) system
- Enhanced collision detection beyond simple ground collision
- Rigid body physics system
- Constraint systems for joints and connections
- Advanced character controllers with slope handling
- Centralized resource cache with reference counting
- Asynchronous loading system
- Automatic resource optimization
- Model and texture compression support
- Layout management system
- Animation and transition effects for UI elements
- Rich text rendering support
- Input focus and navigation system
- 3D positional audio
- Audio streaming for music and large sound files
- Audio effects and filters
- Dynamic audio mixing
- Client-server architecture
- Entity synchronization
- Network prediction and lag compensation
- Multiplayer framework
- In-engine scene editor
- Real-time asset hot-reloading
- Performance profiler
- Visual debugging tools
The j3D engine implements a fixed timestep game loop which ensures consistent physics calculations and frame-independent gameplay mechanics.
// Main game loop in EngineManager
public void run() {
isRunning = true;
int frames = 0;
long frameCounter = 0;
long lastTime = System.nanoTime();
double unprocessedTime = 0.0;
// Main game loop
while(isRunning) {
boolean render = false;
long startTime = System.nanoTime();
long passedTime = startTime - lastTime;
lastTime = startTime;
// Accumulate time since last frame
unprocessedTime += passedTime / (double) NANOSECOND;
frameCounter += passedTime;
// Always process input
input();
// Update game state at fixed intervals
while(unprocessedTime > frameTime) {
render = true;
unprocessedTime -= frameTime;
// Handle window close request
if (window.windowShouldClose()) {
stop();
}
}
// Render if needed
if(render) {
update();
render();
frames++;
}
}
cleanup();
}Key Benefits:
- Frame-Independent Physics: Calculations remain consistent regardless of the frame rate, preventing physics anomalies on different hardware
- Predictable Gameplay: Game logic operates at fixed time steps, ensuring deterministic behavior
- Efficient Rendering: Rendering occurs only when necessary, conserving processing power
- Smooth Motion: Objects move smoothly even when frame rates fluctuate
The j3D engine implements careful memory management practices to optimize performance and prevent leaks.
j3D uses direct memory buffers for efficient communication with the GPU:
// Creating a buffer for vertex data
FloatBuffer vertexBuffer = Utils.storeDataInFloatBuffer(vertices);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, GL15.GL_STATIC_DRAW);
MemoryUtil.memFree(vertexBuffer); // Explicitly free the buffer memoryKey Concepts:
- Direct Buffers: Used for vertex data, indices, and uniform variables
- Manual Memory Management: Resources are explicitly freed when no longer needed
- Buffer Pooling: For frequently allocated buffers to reduce garbage collection pressure
All engine resources follow a clear lifecycle to prevent memory leaks:
- Allocation: Resources are allocated during initialization or when first needed
- Usage: Resources are used for rendering or processing
- Cleanup: Resources are explicitly freed when no longer needed
// Example cleanup method from RenderManager
public void cleanup() {
if (shader != null) {
shader.cleanup();
}
}j3D uses a right-handed coordinate system with the following conventions:
- X axis: Positive to the right
- Y axis: Positive upward
- Z axis: Positive toward the viewer (out of the screen)
Standard Units:
- Distance: 1 unit roughly corresponds to 1 meter
- Rotation: Measured in radians
- Time: Measured in seconds
The asset pipeline defines how external resources are imported, processed, and accessed.
- 3D Models: OBJ format with material support
- Textures: PNG, JPEG, and TGA formats with automatic mipmapping
- Shaders: GLSL vertex and fragment shaders
// Loading a model with the ObjectLoader
Model model = objectLoader.loadOBJModel("/models/character.obj");
// Loading a texture
Texture texture = textureLoader.loadTexture("/textures/character_diffuse.png");
// Applying the texture to the model
Entity character = new Entity(model, position, rotation, scale);
character.getModel().setTexture(texture);j3D uses GLSL shaders for flexible, GPU-accelerated rendering. The ShaderManager class provides an interface to compile, link, and use shader programs.
The vertex shader transforms vertex positions from model space to clip space and passes other attributes to the fragment shader.
// Basic vertex shader (vertex.glsl)
#version 330
layout (location=0) in vec3 position;
layout (location=1) in vec2 texCoord;
layout (location=2) in vec3 normal;
out vec2 outTexCoord;
out vec3 outNormal;
out vec3 outFragPos;
uniform mat4 transformationMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
void main()
{
// Transform vertex position to world space
vec4 worldPos = transformationMatrix * vec4(position, 1.0);
// Output position in clip space
gl_Position = projectionMatrix * viewMatrix * worldPos;
// Pass texture coordinates to fragment shader
outTexCoord = texCoord;
// Pass normal to fragment shader
outNormal = normalize(mat3(transformationMatrix) * normal);
// Pass fragment position for lighting calculations
outFragPos = worldPos.xyz;
}The fragment shader determines the final color of each pixel, handling texturing and lighting calculations.
// Basic fragment shader (fragment.glsl)
#version 330
in vec2 outTexCoord;
in vec3 outNormal;
in vec3 outFragPos;
out vec4 fragColor;
uniform sampler2D textureSampler;
uniform vec3 lightPosition;
uniform vec3 lightColor;
uniform float ambientLight;
uniform vec3 cameraPosition;
uniform bool useTransparency;
void main()
{
// Sample texture color
vec4 textureColor = texture(textureSampler, outTexCoord);
// Handle transparency if enabled
if (useTransparency && textureColor.a < 0.5) {
discard;
}
// Calculate lighting
vec3 normal = normalize(outNormal);
vec3 lightDir = normalize(lightPosition - outFragPos);
// Diffuse lighting
float diff = max(dot(normal, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// Ambient lighting
vec3 ambient = ambientLight * lightColor;
// Calculate specular lighting
vec3 viewDir = normalize(cameraPosition - outFragPos);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = 0.5 * spec * lightColor;
// Combine lighting with texture color
vec3 result = (ambient + diffuse + specular) * textureColor.rgb;
fragColor = vec4(result, textureColor.a);
}The ShaderManager provides a flexible interface for creating and using custom shaders:
// Creating and initializing a shader program
ShaderManager shader = new ShaderManager();
shader.createVertexShader(Utils.loadResource("/shaders/custom_vertex.glsl"));
shader.createFragmentShader(Utils.loadResource("/shaders/custom_fragment.glsl"));
shader.link();
// Creating uniform variables
shader.createUniform("transformationMatrix");
shader.createUniform("viewMatrix");
shader.createUniform("projectionMatrix");
// Using the shader program
shader.bind();
shader.setUniform("transformationMatrix", transformationMatrix);
// ... set other uniforms and render
shader.unbind();Materials in j3D define how surfaces appear when rendered, encapsulating properties like texture, color, and light response.
- Diffuse Texture: The base color texture of the surface
- Ambient Factor: How much ambient light the material reflects
- Diffuse Factor: How much diffuse light the material reflects
- Specular Factor: How much specular light the material reflects
- Shininess: How concentrated specular highlights appear
// Creating a material with custom properties
Material material = new Material();
material.setTexture(textureLoader.loadTexture("/textures/metal.png"));
material.setAmbientFactor(0.1f);
material.setDiffuseFactor(0.8f);
material.setSpecularFactor(1.0f);
material.setShininess(32.0f);
// Applying the material to an entity
entity.setMaterial(material);The rendering pipeline processes scenes in stages to ensure efficient and correct rendering.
- Preparation: Clear buffers and set up rendering state
- Scene Setup: Apply camera and global settings
- Sorting: Sort entities by transparency and distance
- Opaque Pass: Render solid objects front-to-back
- Transparent Pass: Render transparent objects back-to-front
- Post-Processing: Apply any global effects (future enhancement)
- UI Rendering: Render 2D user interface elements
Transparent objects require special handling to render correctly:
// Mark an entity as having a transparent texture
entity.setHasTransparentTexture(true);
// In the render method, transparent entities are sorted and rendered last
if (entity.hasTransparentTexture()) {
transparentEntities.add(entity);
} else {
// Render opaque entity immediately
renderEntity(entity, camera, light);
}
// After all opaque entities, sort transparent entities by distance
transparentEntities.sort(Comparator.comparingDouble(e ->
Vector3f.distanceSquared(e.getPosition(), camera.getPosition())));
// Render transparent entities from back to front
Collections.reverse(transparentEntities);
for (Entity entity : transparentEntities) {
renderEntity(entity, camera, light);
}The j3D engine supports a flexible lighting system to create realistic scenes.
Point lights emit light in all directions from a single position, simulating light bulbs, lanterns, etc.
// Create a point light at a specific position
Light pointLight = new Light(
new Vector3f(5.0f, 3.0f, 2.0f), // position
new Vector3f(1.0f, 0.9f, 0.8f), // color (warm white)
0.1f // ambient intensity
);
// Add the light to the scene
scene.setLight(pointLight);Directional lights simulate distant light sources like the sun, where all light rays are considered parallel.
Ambient lighting provides a base level of illumination to prevent completely dark areas.
// Set ambient light intensity for a scene
light.setAmbientIntensity(0.2f);Specular highlights create shiny reflections on surfaces based on the view angle.
The j3D engine includes several debugging tools to help identify and resolve issues.
Toggle wireframe rendering to inspect mesh structure:
// Toggle wireframe mode in the game logic class
if (window.isKeyPressedBuffered(GLFW.GLFW_KEY_APOSTROPHE)) {
wireframeMode = !wireframeMode;
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, wireframeMode ? GL11.GL_LINE : GL11.GL_FILL);
if (wireframeMode) {
GL11.glDisable(GL11.GL_CULL_FACE);
} else {
GL11.glEnable(GL11.GL_CULL_FACE);
}
}The debug HUD displays real-time information about the game state:
// Create and configure the debug HUD
debugHUD = new DebugHUD(window);
// Update debug HUD information
debugHUD.addInfo("FPS", String.valueOf(currentFps));
debugHUD.addInfo("Position", player.getPosition().toString());
debugHUD.addInfo("Camera Rot", player.getCamera().getRotation().toString());Available Information:
- FPS: Current frames per second
- Position: Player or camera position coordinates
- Rotation: Camera rotation angles
- Velocity: Player movement velocity
- Custom Values: Any game-specific debug information
Monitor performance to identify bottlenecks:
// Get current FPS from the engine manager
int currentFps = (int) EngineManager.getFps();
// Log when FPS drops below a threshold
if (currentFps < 30) {
System.out.println("Performance warning: Low FPS detected");
}j3D provides a simple UI system for creating game interfaces.
Panels are rectangular UI elements that can contain text or images.
// Create a panel with position, size, and color
Panel panel = new Panel(
new Vector2f(0.5f, 0.5f), // center position (normalized 0-1)
new Vector2f(0.4f, 0.3f), // size (normalized 0-1)
new Vector4f(0.2f, 0.2f, 0.2f, 0.8f) // color (RGBA)
);
// Add the panel to the UI manager
uiManager.addElement(panel);Create interactive menus for game navigation:
// Create a menu
Menu mainMenu = new Menu();
// Add buttons to the menu
mainMenu.addButton("Start Game", () -> startGame());
mainMenu.addButton("Options", () -> showOptions());
mainMenu.addButton("Exit", () -> exitGame());
// Show the menu
uiManager.addElement(mainMenu);UI elements can respond to mouse clicks and keyboard input:
// In the UI element's update method
public void update(WindowManager window) {
if (isMouseOver(window.getMouseX(), window.getMouseY())) {
if (window.isMouseButtonPressed(GLFW.GLFW_MOUSE_BUTTON_1)) {
onClick();
}
}
}The j3D engine includes basic physics and collision detection systems.
The Player class implements physics-based movement with momentum and inertia:
// In the Player.update() method
// Apply movement input to velocity with inertia - frame rate independent
float frameInertia = (float) Math.pow(INERTIA_FACTOR, deltaTime * 60.0f);
// Apply horizontal movement
velocity.x = velocity.x * frameInertia + movementInput.x * MOVEMENT_SPEED;
velocity.z = velocity.z * frameInertia + movementInput.z * MOVEMENT_SPEED;Key Physics Parameters:
- Movement Speed: Base movement velocity
- Inertia Factor: How quickly the player accelerates and decelerates (0-1)
- Gravity: Downward acceleration for jumping and falling
- Max Velocity: Maximum movement speed cap
Status: Not implemented in j3D - Example code only
j3D supports several collision detection techniques:
Status: Not implemented - Example code only
Simplified shapes that approximate the entity for faster collision detection:
// Create a bounding sphere for an entity
BoundingSphere sphere = new BoundingSphere(
entity.getPosition(), // center
2.0f // radius
);
// Check for collision with another sphere
if (sphere.intersects(otherSphere)) {
// Handle collision
}Status: Not implemented - Example code only
Ray casting can detect collisions along a line, useful for selections and weapons:
// Create a ray from the camera position in the view direction
Ray ray = new Ray(
camera.getPosition(), // origin
camera.getViewDirection() // direction
);
// Test for intersection with entities
for (Entity entity : scene.getEntities()) {
if (ray.intersects(entity.getBoundingVolume())) {
// Entity was hit by the ray
handleRayHit(entity);
}
}Status: Not implemented in j3D - Example code only
The j3D engine includes a basic audio system for game sound effects and music.
Status: Not implemented - Example code only
Status: Not implemented - Example code only
Sound sources represent individual sounds that can be played in the game:
// Create a sound source
SoundSource soundSource = new SoundSource("explosion.wav");
soundSource.setPosition(entity.getPosition()); // 3D position
soundSource.setGain(0.8f); // volume
soundSource.setLooping(false); // play once
// Play the sound
soundSource.play();
// Update position in the game loop
soundSource.setPosition(entity.getPosition());Status: Not implemented - Example code only
The audio listener represents the player's position and orientation for 3D audio:
// Update the audio listener position and orientation to match the camera
audioManager.setListenerPosition(camera.getPosition());
audioManager.setListenerOrientation(camera.getViewDirection(), camera.getUpVector());Note: j3D has a basic terrain system implemented, but the examples below show enhanced features that are not yet implemented.
Status: Enhanced version - Example code only
Height map terrains use a 2D grid of height values to define the surface:
// Create a terrain from a height map image
Terrain terrain = new Terrain(
0, 0, // grid position
128, // size
16, // max height
"heightmap.png" // height map image
);
// Add the terrain to the scene
scene.addTerrain(terrain);Status: Not implemented - Example code only
Multiple textures can be blended on terrain based on height and slope:
// Add textures to the terrain
terrain.addTexture("grass.png", 0.0f, 0.3f); // low areas
terrain.addTexture("rock.png", 0.3f, 0.7f); // mid areas
terrain.addTexture("snow.png", 0.7f, 1.0f); // high areasStatus: Not implemented in j3D - Example code only
Status: Not implemented - Example code only
The j3D engine includes tools to help optimize game performance:
// Start profiling a section of code
Profiler.start("Physics Update");
// Run the code to profile
physicsSystem.update(deltaTime);
// End profiling and record the time
Profiler.end("Physics Update");
// Later, retrieve profiling results
float physicsTime = Profiler.getAverageTime("Physics Update");
System.out.println("Physics update time: " + physicsTime + "ms");Status: Not implemented - Example code only
Save and restore game state for persistence between sessions:
// Save game state to a file
SaveManager.saveGameState("savegame.dat", gameState);
// Load game state from a file
GameState loadedState = SaveManager.loadGameState("savegame.dat");For developers looking to extend j3D or add advanced features to their games.
Important Note: The following sections contain example implementations and theoretical code that demonstrate how various advanced features could be added to the j3D engine. These features are not currently implemented in the base engine and are provided as educational examples and starting points for custom development.
Status: Example code - demonstrates how to extend the existing Entity class
Creating custom entity types allows you to encapsulate specific behaviors and appearance:
/**
* A specialized entity for character representation.
*/
public class CharacterEntity extends Entity {
/** Character health points */
private float health;
/** Character movement speed */
private float speed;
/**
* Creates a new character entity.
*
* @param model The 3D model
* @param position Initial position
* @param rotation Initial rotation
* @param scale Size scaling
* @param health Initial health
* @param speed Movement speed
*/
public CharacterEntity(Model model, Vector3f position, Vector3f rotation,
Vector3f scale, float health, float speed) {
super(model, position, rotation, scale);
this.health = health;
this.speed = speed;
}
/**
* Updates the character state.
*
* @param deltaTime Time elapsed since last update
*/
public void update(float deltaTime) {
// Custom character behavior here
}
/**
* Applies damage to the character.
*
* @param amount Damage amount
* @return True if the character died from this damage
*/
public boolean takeDamage(float amount) {
health -= amount;
return health <= 0;
}
}Status: Not implemented in j3D - Example code only
The scene graph system allows organizing entities in a logical hierarchy:
// Create a parent entity
Entity parent = new Entity(parentModel, parentPosition, parentRotation, parentScale);
// Create child entities
Entity child1 = new Entity(childModel, childPosition, childRotation, childScale);
Entity child2 = new Entity(childModel, childPosition, childRotation, childScale);
// Attach children to parent
parent.addChild(child1);
parent.addChild(child2);
// When the parent transforms, children will transform relative to it
parent.setPosition(newPosition); // Children move with parent
parent.setRotation(newRotation); // Children rotate around parentStatus: Not implemented in j3D - Example code only
Game state management allows controlling the flow between different screens or modes:
/**
* Simple game state enum
*/
public enum GameState {
MAIN_MENU,
PLAYING,
PAUSED,
GAME_OVER
}
/**
* State manager implementation
*/
public class StateManager {
/** Current game state */
private GameState currentState;
/** Previous game state for returning from paused */
private GameState previousState;
/**
* Sets a new game state.
*/
public void setState(GameState newState) {
if (newState == GameState.PAUSED) {
// Remember the state we're pausing from
previousState = currentState;
}
currentState = newState;
// Update UI or other systems based on state
updateStateDependent();
}
/**
* Returns from paused state to previous state.
*/
public void resumeFromPause() {
if (currentState == GameState.PAUSED && previousState != null) {
currentState = previousState;
updateStateDependent();
}
}
/**
* Updates systems that depend on game state.
*/
private void updateStateDependent() {
switch (currentState) {
case MAIN_MENU:
showMainMenu();
break;
case PLAYING:
hideAllMenus();
enableGameplay();
break;
case PAUSED:
showPauseMenu();
disableGameplay();
break;
case GAME_OVER:
showGameOverScreen();
disableGameplay();
break;
}
}
}Status: Example code - extends the existing WindowManager input system
The input system supports complex mapping for different control schemes:
/**
* Input mapping for configurable controls
*/
public class InputMapper {
/** Maps action names to key codes */
private final Map<String, Integer> keyBindings;
/** Maps action names to mouse buttons */
private final Map<String, Integer> mouseBindings;
/**
* Creates a new input mapper with default bindings.
*/
public InputMapper() {
keyBindings = new HashMap<>();
mouseBindings = new HashMap<>();
// Default key bindings
keyBindings.put("MOVE_FORWARD", GLFW.GLFW_KEY_W);
keyBindings.put("MOVE_BACKWARD", GLFW.GLFW_KEY_S);
keyBindings.put("MOVE_LEFT", GLFW.GLFW_KEY_A);
keyBindings.put("MOVE_RIGHT", GLFW.GLFW_KEY_D);
keyBindings.put("JUMP", GLFW.GLFW_KEY_SPACE);
// Default mouse bindings
mouseBindings.put("SHOOT", GLFW.GLFW_MOUSE_BUTTON_LEFT);
mouseBindings.put("AIM", GLFW.GLFW_MOUSE_BUTTON_RIGHT);
}
/**
* Checks if an action is triggered by a key press.
*/
public boolean isActionTriggered(String actionName, WindowManager window) {
Integer keyCode = keyBindings.get(actionName);
if (keyCode != null) {
return window.isKeyPressed(keyCode);
}
Integer mouseButton = mouseBindings.get(actionName);
if (mouseButton != null) {
return window.isMouseButtonPressed(mouseButton);
}
return false;
}
/**
* Rebinds a keyboard action to a different key.
*/
public void rebindKeyAction(String actionName, int newKeyCode) {
keyBindings.put(actionName, newKeyCode);
}
}Status: Not implemented in j3D - Example code only
Particle systems can be implemented for effects like fire, smoke, or magic:
/**
* Basic particle system for visual effects
*/
public class ParticleSystem {
/** List of active particles */
private final List<Particle> particles;
/** Particle emission rate (particles per second) */
private float emissionRate;
/** Particle model (typically a quad with texture) */
private final Model particleModel;
/** Particle texture */
private final Texture particleTexture;
/** Emission point */
private Vector3f position;
/**
* Updates all particles and emits new ones.
*/
public void update(float deltaTime) {
// Emit new particles based on emission rate
int newParticles = (int)(emissionRate * deltaTime);
for (int i = 0; i < newParticles; i++) {
emitParticle();
}
// Update existing particles
Iterator<Particle> it = particles.iterator();
while (it.hasNext()) {
Particle particle = it.next();
particle.update(deltaTime);
// Remove dead particles
if (particle.getLifetime() <= 0) {
it.remove();
}
}
}
/**
* Renders all particles.
*/
public void render(Camera camera) {
for (Particle particle : particles) {
// Create a billboarded entity for the particle
Entity particleEntity = new Entity(
particleModel,
particle.getPosition(),
new Vector3f(), // rotation will be handled by billboarding
new Vector3f(particle.getSize(), particle.getSize(), particle.getSize())
);
// Make the particle always face the camera
particleEntity.setBillboardFull(true);
// Render with transparency
particleEntity.setHasTransparentTexture(true);
// Render the particle
renderer.render(particleEntity, camera, light);
}
}
/**
* Emits a single particle.
*/
private void emitParticle() {
// Create particle with random velocity and lifetime
Vector3f velocity = new Vector3f(
(float)Math.random() * 2 - 1, // -1 to 1
(float)Math.random() * 3, // 0 to 3 (upward bias)
(float)Math.random() * 2 - 1 // -1 to 1
).normalize().mul(getRandomSpeed());
float lifetime = getRandomLifetime();
float size = getRandomSize();
Particle particle = new Particle(position, velocity, lifetime, size);
particles.add(particle);
}
}Status: Example code - extends the existing ShaderManager system
Creating custom shader effects allows for unique visual styles:
/**
* Toon (cel) shader effect
*/
public class ToonShaderEffect {
/** The shader program */
private final ShaderManager shader;
/**
* Creates and initializes the toon shader.
*/
public ToonShaderEffect() throws Exception {
// Create shader program
shader = new ShaderManager();
shader.createVertexShader(Utils.loadResource("/shaders/toon_vertex.glsl"));
shader.createFragmentShader(Utils.loadResource("/shaders/toon_fragment.glsl"));
shader.link();
// Create uniform variables
shader.createUniform("transformationMatrix");
shader.createUniform("projectionMatrix");
shader.createUniform("viewMatrix");
shader.createUniform("lightPosition");
shader.createUniform("lightColor");
shader.createUniform("textureSampler");
shader.createUniform("numLevels");
}
/**
* Renders an entity with the toon shader effect.
*/
public void render(Entity entity, Camera camera, Light light) {
// Bind the shader program
shader.bind();
// Set transformation uniforms
Matrix4f transformationMatrix = Transformation.createTransformationMatrix(
entity.getPosition(),
entity.getRotation(),
entity.getScale()
);
shader.setUniform("transformationMatrix", transformationMatrix);
shader.setUniform("projectionMatrix", window.getProjectionMatrix());
shader.setUniform("viewMatrix", Transformation.createViewMatrix(camera));
// Set lighting uniforms
shader.setUniform("lightPosition", light.getPosition());
shader.setUniform("lightColor", light.getColor());
// Set toon shader specific uniforms
shader.setUniform("numLevels", 3.0f); // Number of shading levels
// Bind the texture
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, entity.getModel().getTexture().getId());
// Render the mesh
GL30.glBindVertexArray(entity.getModel().getId());
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
GL11.glDrawElements(GL11.GL_TRIANGLES, entity.getModel().getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
// Unbind texture and shader
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
shader.unbind();
}
}An animation system allows for skeletal or keyframe animations:
/**
* Keyframe animation system for entities
*/
public class AnimationSystem {
/** Map of animation names to animation data */
private final Map<String, Animation> animations;
/** Currently playing animation */
private Animation currentAnimation;
/** Playback time in current animation */
private float animationTime;
/** Target entity to animate */
private final Entity entity;
/** Whether the animation should loop */
private boolean looping;
/**
* Creates a new animation system for an entity.
*/
public AnimationSystem(Entity entity) {
this.entity = entity;
this.animations = new HashMap<>();
this.animationTime = 0;
}
/**
* Adds an animation to the system.
*/
public void addAnimation(String name, Animation animation) {
animations.put(name, animation);
}
/**
* Plays an animation.
*/
public void playAnimation(String name, boolean loop) {
Animation animation = animations.get(name);
if (animation != null) {
currentAnimation = animation;
animationTime = 0;
looping = loop;
}
}
/**
* Updates the animation system.
*/
public void update(float deltaTime) {
if (currentAnimation != null) {
// Update animation time
animationTime += deltaTime;
// Handle looping
if (animationTime > currentAnimation.getDuration()) {
if (looping) {
animationTime %= currentAnimation.getDuration();
} else {
animationTime = currentAnimation.getDuration();
}
}
// Get keyframe data for current time
Keyframe keyframe = currentAnimation.getKeyframeAtTime(animationTime);
// Apply keyframe transformation to entity
entity.setPosition(keyframe.getPosition());
entity.setRotation(keyframe.getRotation());
entity.setScale(keyframe.getScale());
}
}
}Basic networking can be implemented for multiplayer games:
/**
* Simple networking client for multiplayer games
*/
public class NetworkClient {
/** Socket connection to server */
private Socket socket;
/** Data input stream */
private DataInputStream input;
/** Data output stream */
private DataOutputStream output;
/** Client thread for receiving messages */
private Thread clientThread;
/** Running flag for the client thread */
private boolean running;
/**
* Connects to a multiplayer server.
*/
public void connect(String address, int port) throws IOException {
socket = new Socket(address, port);
input = new DataInputStream(socket.getInputStream());
output = new DataOutputStream(socket.getOutputStream());
running = true;
clientThread = new Thread(this::receiveMessages);
clientThread.start();
}
/**
* Sends a player position update to the server.
*/
public void sendPlayerPosition(Vector3f position, Vector3f rotation) throws IOException {
output.writeInt(MessageType.PLAYER_POSITION);
output.writeFloat(position.x);
output.writeFloat(position.y);
output.writeFloat(position.z);
output.writeFloat(rotation.x);
output.writeFloat(rotation.y);
output.writeFloat(rotation.z);
output.flush();
}
/**
* Receives messages from the server.
*/
private void receiveMessages() {
try {
while (running) {
int messageType = input.readInt();
switch (messageType) {
case MessageType.PLAYER_JOINED:
handlePlayerJoined();
break;
case MessageType.PLAYER_LEFT:
handlePlayerLeft();
break;
case MessageType.PLAYER_POSITION:
handlePlayerPosition();
break;
// Handle other message types
}
}
} catch (IOException e) {
disconnect();
}
}
/**
* Disconnects from the server.
*/
public void disconnect() {
running = false;
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore close exceptions
}
}
}Status: Not implemented in j3D - Example code only
Status: Not implemented - Example code only
The component system can be extended to add new capabilities:
/**
* Health component that can be added to entities
*/
public class HealthComponent {
/** Entity this component belongs to */
private final Entity entity;
/** Current health points */
private float currentHealth;
/** Maximum health points */
private final float maxHealth;
/** Whether the entity is currently invulnerable */
private boolean invulnerable;
/** Remaining invulnerability time */
private float invulnerabilityTime;
/**
* Creates a new health component.
*/
public HealthComponent(Entity entity, float maxHealth) {
this.entity = entity;
this.maxHealth = maxHealth;
this.currentHealth = maxHealth;
this.invulnerable = false;
this.invulnerabilityTime = 0;
}
/**
* Updates the health component.
*/
public void update(float deltaTime) {
if (invulnerable) {
invulnerabilityTime -= deltaTime;
if (invulnerabilityTime <= 0) {
invulnerable = false;
}
}
}
/**
* Applies damage to the entity.
*
* @return True if the entity died from this damage
*/
public boolean takeDamage(float damage) {
if (invulnerable) {
return false;
}
currentHealth -= damage;
if (currentHealth <= 0) {
// Entity died
return true;
} else {
// Make temporarily invulnerable
setInvulnerable(1.0f);
return false;
}
}
/**
* Makes the entity invulnerable for a period of time.
*/
public void setInvulnerable(float seconds) {
invulnerable = true;
invulnerabilityTime = seconds;
}
/**
* Heals the entity.
*/
public void heal(float amount) {
currentHealth = Math.min(currentHealth + amount, maxHealth);
}
}Status: Not implemented - Example code only
Custom resource loaders can be created for new asset types:
/**
* Loader for heightmap terrain data
*/
public class HeightMapLoader {
/**
* Loads height data from an image file.
*
* @param imagePath Path to the heightmap image
* @param maxHeight Maximum terrain height
* @return 2D array of height values
*/
public static float[][] loadHeightmap(String imagePath, float maxHeight) throws Exception {
BufferedImage image = ImageIO.read(HeightMapLoader.class.getResourceAsStream(imagePath));
int width = image.getWidth();
int height = image.getHeight();
float[][] heightData = new float[width][height];
for (int z = 0; z < height; z++) {
for (int x = 0; x < width; x++) {
// Get pixel RGB and convert to grayscale
int rgb = image.getRGB(x, z);
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
// Calculate grayscale value (0-255)
float grayscale = (r + g + b) / 3.0f;
// Convert to height value
heightData[x][z] = (grayscale / 255.0f) * maxHeight;
}
}
return heightData;
}
/**
* Generates a terrain mesh from height data.
*/
public static Model generateTerrainMesh(float[][] heightData, float cellSize) {
int gridWidth = heightData.length;
int gridHeight = heightData[0].length;
// Calculate vertex and index counts
int vertexCount = gridWidth * gridHeight;
int indexCount = (gridWidth - 1) * (gridHeight - 1) * 6; // 2 triangles per grid cell
// Create arrays for vertex data
float[] positions = new float[vertexCount * 3];
float[] textureCoords = new float[vertexCount * 2];
float[] normals = new float[vertexCount * 3];
int[] indices = new int[indexCount];
// Fill vertex arrays
// ... (vertex generation code)
// Create the model
return ObjectLoader.loadToVAO(positions, textureCoords, normals, indices);
}
}Status: Not implemented - Example code only
Scene serialization allows saving and loading scenes:
/**
* Serializes scene data to JSON for persistence
*/
public class SceneSerializer {
/**
* Serializes a scene to JSON format.
*/
public static String serializeScene(TestScene scene) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// Create a serializable scene data structure
SceneData sceneData = new SceneData();
// Copy basic scene properties
sceneData.setName(scene.getName());
// Convert light data
Light light = scene.getLight();
if (light != null) {
LightData lightData = new LightData();
lightData.setPosition(serializeVector3f(light.getPosition()));
lightData.setColor(serializeVector3f(light.getColor()));
lightData.setAmbientIntensity(light.getAmbientIntensity());
sceneData.setLight(lightData);
}
// Convert entity data
List<EntityData> entityDataList = new ArrayList<>();
for (Entity entity : scene.getEntities()) {
EntityData entityData = new EntityData();
entityData.setModelName(entity.getModel().getName());
entityData.setPosition(serializeVector3f(entity.getPosition()));
entityData.setRotation(serializeVector3f(entity.getRotation()));
entityData.setScale(serializeVector3f(entity.getScale()));
// Copy entity properties
entityData.setHasTransparentTexture(entity.hasTransparentTexture());
entityData.setBillboardY(entity.isBillboardY());
entityData.setBillboardFull(entity.isBillboardFull());
entityDataList.add(entityData);
}
sceneData.setEntities(entityDataList);
// Serialize to JSON
return mapper.writeValueAsString(sceneData);
}
/**
* Deserializes a scene from JSON format.
*/
public static TestScene deserializeScene(String json, ObjectLoader loader) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SceneData sceneData = mapper.readValue(json, SceneData.class);
// Create a new scene
TestScene scene = new TestScene();
scene.setName(sceneData.getName());
// Set the light
if (sceneData.getLight() != null) {
LightData lightData = sceneData.getLight();
Light light = new Light(
deserializeVector3f(lightData.getPosition()),
deserializeVector3f(lightData.getColor()),
lightData.getAmbientIntensity()
);
scene.setLight(light);
}
// Create entities
for (EntityData entityData : sceneData.getEntities()) {
// Load the model
Model model = loader.loadOBJModel("/models/" + entityData.getModelName() + ".obj");
// Create the entity
Entity entity = new Entity(
model,
deserializeVector3f(entityData.getPosition()),
deserializeVector3f(entityData.getRotation()),
deserializeVector3f(entityData.getScale())
);
// Set entity properties
entity.setHasTransparentTexture(entityData.isHasTransparentTexture());
entity.setBillboardY(entityData.isBillboardY());
entity.setBillboardFull(entityData.isBillboardFull());
// Add to scene
scene.addEntity(entity);
}
return scene;
}
}Status: Not implemented in j3D - Example code only
Status: Not implemented - Example code only
Visual culling improves performance by skipping the rendering of objects not visible to the camera:
Status: Not implemented - Example code only
/**
* Frustum culling to skip rendering off-screen entities
*/
public class FrustumCuller {
/** The view frustum planes */
private final FrustumPlanes frustum;
/**
* Updates the frustum planes from camera and projection matrices.
*/
public void updateFrustum(Matrix4f projectionMatrix, Matrix4f viewMatrix) {
Matrix4f projView = new Matrix4f(projectionMatrix).mul(viewMatrix);
frustum.update(projView);
}
/**
* Tests if an entity is visible in the frustum.
*/
public boolean isEntityVisible(Entity entity) {
// Create a bounding sphere around the entity
Vector3f center = entity.getPosition();
float radius = entity.getMaxExtent();
// Test sphere against all six frustum planes
return frustum.isSphereInside(center, radius);
}
}Status: Not implemented - Example code only
/**
* Occlusion culling to skip rendering of entities hidden behind others
*/
public class OcclusionCuller {
/** List of potential occluders (large objects) */
private final List<Entity> occluders;
/**
* Tests if an entity is occluded.
*/
public boolean isOccluded(Entity entity, Camera camera) {
// Sort occluders by distance to camera (closest first)
occluders.sort(Comparator.comparingDouble(e ->
Vector3f.distanceSquared(e.getPosition(), camera.getPosition())));
// Test against each occluder
for (Entity occluder : occluders) {
if (occluder == entity) {
continue; // Skip self
}
if (isEntityOccludedBy(entity, occluder, camera)) {
return true;
}
}
return false;
}
}Status: Not implemented - Example code only
LOD systems dynamically adjust model complexity based on distance:
/**
* Level of Detail system for optimizing rendering
*/
public class LODSystem {
/** Distance thresholds for LOD levels */
private final float[] thresholds;
/** Models for each LOD level */
private final Model[] lodModels;
/**
* Gets the appropriate LOD model for a given distance.
*/
public Model getModelForDistance(float distance) {
// Determine LOD level based on distance
int lodLevel = 0;
for (int i = 0; i < thresholds.length; i++) {
if (distance < thresholds[i]) {
break;
}
lodLevel = i + 1;
}
// Clamp to available models
lodLevel = Math.min(lodLevel, lodModels.length - 1);
return lodModels[lodLevel];
}
}Status: Not implemented - Example code only
Batch rendering reduces draw calls by combining similar objects:
/**
* Batch renderer for similar entities
*/
public class BatchRenderer {
/** Map of model IDs to entity lists */
private final Map<Integer, List<Entity>> entityBatches;
/**
* Adds an entity to the appropriate batch.
*/
public void addEntity(Entity entity) {
int modelId = entity.getModel().getId();
if (!entityBatches.containsKey(modelId)) {
entityBatches.put(modelId, new ArrayList<>());
}
entityBatches.get(modelId).add(entity);
}
/**
* Clears all batches.
*/
public void clearBatches() {
entityBatches.clear();
}
/**
* Renders all batched entities.
*/
public void renderBatches(Camera camera, Light light) {
for (Map.Entry<Integer, List<Entity>> entry : entityBatches.entrySet()) {
int modelId = entry.getKey();
List<Entity> entities = entry.getValue();
// Bind the model VAO and texture only once for this batch
Model model = entities.get(0).getModel();
GL30.glBindVertexArray(model.getId());
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getTexture().getId());
// Render each entity in the batch
for (Entity entity : entities) {
Matrix4f transformationMatrix = Transformation.createTransformationMatrix(
entity.getPosition(),
entity.getRotation(),
entity.getScale()
);
shader.setUniform("transformationMatrix", transformationMatrix);
GL11.glDrawElements(GL11.GL_TRIANGLES, model.getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
}
// Unbind the model VAO
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
}
}
}© 2025 DiscardSoft
