CustomTransformProvider API
CustomTransformProvider API
The CustomTransformProvider API allows you to create celestial bodies with custom position and rotation logic that goes beyond standard orbital mechanics.
Overview
By default, celestial bodies in Genesis follow orbital mechanics based on their orbitDistance, orbitTime, and other orbital parameters. The CustomTransformProvider allows you to override this behavior with custom logic for both position and rotation.
Both getCurrentPos() and getRotation() receive the current game time, allowing you to create dynamic, time-dependent transformations.
Use Cases
- Fixed position/rotation: Planets that don’t orbit or rotate
- Non-standard orbits: Figure-8 orbits, precessing orbits, etc.
- Custom rotation: Tidally locked bodies, tumbling asteroids, precessing axes
- Dynamic behavior: Bodies that respond to game events or time
- Testing: Controlled positions and rotations for unit tests
How to Use
Step 1: Implement CustomTransformProvider
Create a class that implements CustomTransformProvider:
package com.yourmod.space;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.resources.ResourceLocation;
import org.joml.Quaterniond;
import org.joml.Quaterniondc;
import org.joml.Vector3d;
import shipwrights.genesis.space.CustomTransformProvider;
public class MyCustomProvider implements CustomTransformProvider {
public static final ResourceLocation TYPE = ResourceLocation.parse("yourmod:my_provider");
private final double x;
private final double y;
private final double z;
public MyCustomProvider(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public ResourceLocation getType() {
return TYPE;
}
@Override
public Quaterniondc getRotation(long ticks, float subticks, Orbitable parent) {
return new Quaterniond(); // No rotation
}
@Override
public Vector3d getCurrentPos(long ticks, float subticks, Orbitable parent) {
// Return your custom position
return new Vector3d(x, y, z);
}
// Define a codec for serialization
public static final Codec<MyCustomProvider> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.DOUBLE.fieldOf("x").forGetter(p -> p.x),
Codec.DOUBLE.fieldOf("y").forGetter(p -> p.y),
Codec.DOUBLE.fieldOf("z").forGetter(p -> p.z)
).apply(instance, MyCustomProvider::new)
);
}
Step 2: Register Your Provider
Register your provider during mod initialization:
import shipwrights.genesis.space.CustomTransformProvider;
public class YourMod {
public YourMod() {
// Register your custom transform provider
CustomTransformProvider.register(
MyCustomProvider.TYPE,
MyCustomProvider.CODEC
);
}
}
Step 3: Use in JSON Configuration
Now you can use your provider in celestial body JSON configurations:
{
"ID": "yourmod:special_planet",
"parentID": "genesis:sun",
"size": 1.0,
"orbitDistance": 100,
"orbitTime": 1000,
"gravity": 1.0,
"r": 0.5,
"g": 0.7,
"b": 0.9,
"customTransform": {
"type": "yourmod:my_provider",
"x": 500.0,
"y": 100.0,
"z": 200.0
}
}
Built-in Example
Genesis includes an example implementation at:
- Class:
shipwrights.genesis.space.ExampleCustomTransformProvider - Type ID:
genesis:example_fixed_position - See: custom_transform_example.json
Advanced Examples
Time-Dependent Position
@Override
public Vector3d getCurrentPos(long ticks, float subticks, Orbitable parent) {
// Circular orbit with custom radius, offset by parent position
double angle = (ticks + subticks) * Math.PI * 2.0 / 20000.0;
double radius = 500.0;
// Get parent's position
Vector3d parentPos = parent.getCurrentPos(ticks, subticks);
// Calculate orbit position relative to parent
return new Vector3d(
parentPos.x + Math.cos(angle) * radius,
parentPos.y,
parentPos.z + Math.sin(angle) * radius
);
}
Figure-8 Orbit
@Override
public Vector3d getCurrentPos(long ticks, float subticks, Orbitable parent) {
double t = (ticks + subticks) * Math.PI * 2.0 / 20000.0;
// Get parent's position
Vector3d parentPos = parent.getCurrentPos(ticks, subticks);
// Calculate figure-8 pattern relative to parent
return new Vector3d(
parentPos.x + 400 * Math.sin(t),
parentPos.y + 200 * Math.sin(2 * t),
parentPos.z
);
}
Time-Dependent Rotation
@Override
public Quaterniondc getRotation(long ticks, float subticks, Orbitable parent) {
// Rotate based on time - completes one rotation every 20000 ticks (1000 seconds)
double angle = (ticks + subticks) * Math.PI * 2.0 / 20000.0;
return new Quaterniond().rotateY(angle);
}
Tidally Locked Rotation (always facing parent)
@Override
public Quaterniondc getRotation(long ticks, float subticks, Orbitable parent) {
// Calculate rotation to always face the parent body
Vector3d myPos = getCurrentPos(ticks, subticks, parent);
Vector3d parentPos = parent.getCurrentPos(ticks, subticks);
// Calculate direction from this body to parent
Vector3d toParent = new Vector3d(parentPos).sub(myPos).normalize();
// Create rotation that points toward parent
Quaterniond rotation = new Quaterniond();
rotation.lookAlong(toParent, new Vector3d(0, 1, 0));
return rotation;
}
Tumbling Rotation (multiple axes)
@Override
public Quaterniondc getRotation(long ticks, float subticks, Orbitable parent) {
// Tumble on multiple axes like an asteroid
double t = (ticks + subticks) * Math.PI * 2.0 / 20000.0;
return new Quaterniond()
.rotateX(t * 1.3) // Rotate on X axis
.rotateY(t * 0.7) // Rotate on Y axis at different speed
.rotateZ(t * 0.5); // Rotate on Z axis
}
Important Notes
-
Stateless & Deterministic: Both
getCurrentPos()andgetRotation()should be pure functions that only depend on theticks,subticks, andparentparameters. Given the same inputs, they should always return the same result. Do not mutate internal state - create and return new objects each time. -
Thread Safety: Your provider may be called from multiple threads. Because methods should be stateless (see above), thread safety is automatic if you follow that guideline.
-
Performance: The methods are called frequently (every frame for rendering). Keep them efficient - avoid expensive calculations when possible.
-
Serialization: All fields must be serializable via the Codec. Don’t store references to game objects like
Level,Entity, etc. Store only primitive data and configuration values. -
Backward Compatibility: The
customTransformfield is optional. Bodies without it will use standard orbital mechanics. -
Client/Server Sync: Your custom provider will be automatically synchronized to clients via the space registry sync packet.
-
Return New Objects: Always return new
Vector3dandQuaterniondinstances. Never return a mutable field that could be modified by the caller.
Testing
You can use CustomTransformProvider in unit tests to create predictable test scenarios:
@Test
public void testRaycast() {
OrbitingBody body = new OrbitingBody(
"test:body",
"test:parent",
0.5, 50, 1000, 1.0, 1f, 1f, 1f,
new MyCustomProvider(100, 0, 0) // Fixed position for testing
);
// Now you can test with a known position
// ...
}
API Reference
CustomTransformProvider Interface
public interface CustomTransformProvider {
/**
* Returns the type identifier for this provider.
* This is used during serialization to determine which codec to use.
*
* @return A unique ResourceLocation identifying this provider type
*/
ResourceLocation getType();
/**
* Returns the rotation quaternion for the celestial body at the given time.
* Should be a pure function - same inputs always produce same output.
*
* @param ticks The current game time in ticks
* @param subticks Partial tick for smooth interpolation (0.0 to 1.0)
* @param parent The parent celestial body that this body orbits
* @return A quaternion representing the celestial body's rotation
*/
Quaterniondc getRotation(long ticks, float subticks, Orbitable parent);
/**
* Returns the current position of the celestial body at the given time.
* Should be a pure function - same inputs always produce same output.
*
* @param ticks The current game time in ticks
* @param subticks Partial tick for smooth interpolation (0.0 to 1.0)
* @param parent The parent celestial body that this body orbits
* @return A Vector3d representing the celestial body's position in space
*/
Vector3d getCurrentPos(long ticks, float subticks, Orbitable parent);
/**
* Registers a custom transform provider type.
* Call this during mod initialization before any celestial bodies are loaded.
*
* @param type The unique identifier for this provider type
* @param codec The codec used to serialize/deserialize this provider
*/
static void register(ResourceLocation type, Codec<? extends CustomTransformProvider> codec);
}
Key Points
ticks: The game time in ticks (20 ticks = 1 second)subticks: A value between 0.0 and 1.0 representing partial progress through the current tick, used for smooth interpolation between framesparent: The parent celestial body that this body orbits. Can be used to calculate positions relative to the parent or implement behaviors like tidal locking- Return values: Always create and return new objects. Don’t return mutable fields that could be modified by callers.
Questions?
For more information or questions about the CustomTransformProvider API, please refer to the Genesis mod documentation or source code.