Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions odradek-core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
requires java.net.http;
requires org.slf4j;

exports sh.adelessfox.odradek.animation;
exports sh.adelessfox.odradek.audio.container.at9;
exports sh.adelessfox.odradek.audio.container.riff;
exports sh.adelessfox.odradek.audio.container.wave;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package sh.adelessfox.odradek.animation;

import sh.adelessfox.odradek.scene.Skeleton;

import java.util.List;

public record Animation(
Skeleton skeleton,
float frameRate,
List<Track<?>> tracks
) {
public Animation {
tracks = List.copyOf(tracks);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package sh.adelessfox.odradek.animation;

public record KeyFrame<T>(int frame, T value) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package sh.adelessfox.odradek.animation;

import wtf.reversed.toolbox.math.Quaternion;
import wtf.reversed.toolbox.math.Vector3;

import java.util.List;

public sealed interface Track<T> {
int boneId();

List<KeyFrame<T>> keyFrames();

record Translate(int boneId, List<KeyFrame<Vector3>> keyFrames) implements Track<Vector3> {
public Translate {
keyFrames = List.copyOf(keyFrames);
}
}

record Rotate(int boneId, List<KeyFrame<Quaternion>> keyFrames) implements Track<Quaternion> {
public Rotate {
keyFrames = List.copyOf(keyFrames);
}
}

record Scale(int boneId, List<KeyFrame<Vector3>> keyFrames) implements Track<Vector3> {
public Scale {
keyFrames = List.copyOf(keyFrames);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ public record Mesh(
List<Bytes> colors,
Optional<Weights> weights
) {
public record Weights(Floats values, Ints joints, int maxInfluence) {
public record Weights(Floats values, Ints bones, int maxInfluence) {
public Weights {
if (values.length() != joints.length()) {
throw new IllegalArgumentException("weights values and joints must have the same length");
if (values.length() != bones.length()) {
throw new IllegalArgumentException("weights values and bones must have the same length");
}
if (maxInfluence <= 0) {
throw new IllegalArgumentException("maximum influence must be greater than 0");
Expand All @@ -49,7 +49,7 @@ public record Weights(Floats values, Ints joints, int maxInfluence) {
colors.forEach(bytes -> check(bytes.length(), vertices, 4));
weights.ifPresent(w -> {
check(w.values.length(), vertices, w.maxInfluence);
check(w.joints.length(), vertices, w.maxInfluence);
check(w.bones.length(), vertices, w.maxInfluence);
});

texCoords = List.copyOf(texCoords);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public final class MeshReader {
private Accessor normals;
private Accessor tangents;
private Accessor weights;
private Accessor joints;
private Accessor bones;
private final List<Accessor> texCoords = new ArrayList<>();
private final List<Accessor> colors = new ArrayList<>();

Expand Down Expand Up @@ -56,11 +56,11 @@ public MeshReader setWeights(Accessor weights) {
return this;
}

public MeshReader setJoints(Accessor joints) {
if (this.joints != null) {
throw new IllegalStateException("joints already set");
public MeshReader setBones(Accessor bones) {
if (this.bones != null) {
throw new IllegalStateException("bones already set");
}
this.joints = joints;
this.bones = bones;
return this;
}

Expand All @@ -81,8 +81,8 @@ public Mesh read() {
if (positions == null) {
throw new IllegalStateException("positions not set");
}
if (weights != null && joints == null) {
throw new IllegalStateException("weights set but joints not set");
if (weights != null && bones == null) {
throw new IllegalStateException("weights set but bones not set");
}
return new Mesh(
indices.toInts(),
Expand All @@ -95,34 +95,34 @@ public Mesh read() {
}

private Optional<Mesh.Weights> readWeights() {
if (weights == null && joints == null) {
if (weights == null && bones == null) {
return Optional.empty();
}

Floats values;
if (weights != null) {
if (joints == null) {
throw new IllegalStateException("weights set but joints not set");
if (bones == null) {
throw new IllegalStateException("weights set but bones not set");
}
if (weights.componentCount() != joints.componentCount()) {
throw new IllegalStateException("weights and joints must have the same number of components");
if (weights.componentCount() != bones.componentCount()) {
throw new IllegalStateException("weights and bones must have the same number of components");
}
if (weights.count() != joints.count()) {
throw new IllegalStateException("weights and joints must have the same count");
if (weights.count() != bones.count()) {
throw new IllegalStateException("weights and bones must have the same count");
}
values = weights.toFloatsNormalized();
} else {
// Weights MAY be absent in case there's only one bone per vertex.
if (joints.componentCount() != 1) {
throw new IllegalStateException("JOINTS accessor has " + joints.componentCount()
+ " components, but WEIGHTS accessor is missing");
if (bones.componentCount() != 1) {
throw new IllegalStateException("bones accessor has " + bones.componentCount()
+ " components, but weights accessor is missing");
}
// Build a dummy WEIGHTS accessor with all weights set to 1.0f
values = Floats.Mutable.allocate(joints.count()).fill(1.0f);
values = Floats.Mutable.allocate(bones.count()).fill(1.0f);
}

var indices = joints.toInts();
var maximumInfluence = joints.componentCount();
var indices = bones.toInts();
var maximumInfluence = bones.componentCount();

return Optional.of(new Mesh.Weights(values, indices, maximumInfluence));
}
Expand Down
15 changes: 15 additions & 0 deletions odradek-core/src/main/java/sh/adelessfox/odradek/scene/Bone.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package sh.adelessfox.odradek.scene;

import wtf.reversed.toolbox.math.Matrix4;

import java.util.OptionalInt;

/**
* Represents a bone in a skeleton.
*
* @param parent The index of the parent bone, or empty if this is the root bone.
* @param name The name of the bone.
* @param matrix The transform of the bone relative to its parent.
*/
public record Bone(OptionalInt parent, String name, Matrix4 matrix) {
}
15 changes: 0 additions & 15 deletions odradek-core/src/main/java/sh/adelessfox/odradek/scene/Joint.java

This file was deleted.

16 changes: 8 additions & 8 deletions odradek-core/src/main/java/sh/adelessfox/odradek/scene/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public record Node(
Optional<String> name,
Optional<Model> model,
Optional<Skin> skin,
Optional<Skeleton> skeleton,
List<Node> children,
Matrix4 matrix
) {
Expand Down Expand Up @@ -41,11 +41,11 @@ public static Node of(Model model) {
public Node add(Node child) {
var children = new ArrayList<>(this.children);
children.add(child);
return new Node(name, model, skin, children, matrix);
return new Node(name, model, skeleton, children, matrix);
}

public Node transform(Matrix4 transform) {
return new Node(name, model, skin, children, matrix.multiply(transform));
return new Node(name, model, skeleton, children, matrix.multiply(transform));
}

public void accept(NodeVisitor visitor) {
Expand Down Expand Up @@ -93,7 +93,7 @@ Node toNode() {
private final List<NodeOrBuilder> children = new ArrayList<>();
private String name;
private Model model;
private Skin skin;
private Skeleton skeleton;
private Matrix4 matrix = Matrix4.IDENTITY;

private Builder() {
Expand All @@ -109,8 +109,8 @@ public Builder model(Model model) {
return this;
}

public Builder skin(Skin skin) {
this.skin = skin;
public Builder skeleton(Skeleton skeleton) {
this.skeleton = skeleton;
return this;
}

Expand Down Expand Up @@ -145,7 +145,7 @@ public Node build() {
return new Node(
Optional.ofNullable(name),
Optional.ofNullable(model),
Optional.ofNullable(skin),
Optional.ofNullable(skeleton),
children.stream().map(NodeOrBuilder::toNode).toList(),
matrix);
}
Expand All @@ -156,7 +156,7 @@ public String toString() {
"children=" + children +
", name='" + name + '\'' +
", model=" + model +
", skin=" + skin +
", skeleton=" + skeleton +
", matrix=" + matrix +
'}';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package sh.adelessfox.odradek.scene;

import java.util.List;

public record Skeleton(List<Bone> bones) {
public Skeleton {
validateBones(bones);
bones = List.copyOf(bones);
}

private static void validateBones(List<Bone> bones) {
for (int i = 0; i < bones.size(); i++) {
var bone = bones.get(i);
if (bone.parent().isEmpty()) {
continue;
}
int parent = bone.parent().getAsInt();
if (parent < 0 || parent >= i) {
throw new IllegalArgumentException("Bone " + i + " has an invalid parent index " + parent);
}
}
}
}
23 changes: 0 additions & 23 deletions odradek-core/src/main/java/sh/adelessfox/odradek/scene/Skin.java

This file was deleted.

3 changes: 2 additions & 1 deletion odradek-export-cast/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
requires org.slf4j;

provides sh.adelessfox.odradek.game.Exporter with
sh.adelessfox.odradek.export.cast.CastExporter;
sh.adelessfox.odradek.export.cast.CastAnimationExporter,
sh.adelessfox.odradek.export.cast.CastSceneExporter;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package sh.adelessfox.odradek.export.cast;

import be.twofold.tinycast.*;
import sh.adelessfox.odradek.game.Exporter;
import sh.adelessfox.odradek.scene.Bone;
import sh.adelessfox.odradek.scene.Skeleton;

import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.Optional;

abstract class BaseCastExporter<T> implements Exporter.OfSingleOutput<T> {
@Override
public void export(T object, WritableByteChannel channel) throws IOException {
var cast = Cast.create();
var root = cast.createRoot();

root.createMetadata()
.setSoftware("Odradek")
.setAuthor("ShadelessFox");

export(object, root);

try {
cast.write(Channels.newOutputStream(channel));
} catch (CastException e) {
throw new IOException("Error writing cast file", e);
}
}

@Override
public String name() {
return "Cast (by DTZxPorter)";
}

@Override
public String extension() {
return "cast";
}

@Override
public Optional<String> icon() {
return Optional.of("fugue:paint-can");
}

protected abstract void export(T object, CastNodes.Root root);

protected static void mapSkeleton(CastNodes.Skeleton skeletonNode, Skeleton skeleton) {
for (Bone bone : skeleton.bones()) {
mapBone(skeletonNode.createBone(), bone);
}
}

protected static void mapBone(CastNodes.Bone boneNode, Bone bone) {
boneNode.setName(bone.name());
bone.parent().ifPresent(boneNode::setParentIndex);

var transform = bone.matrix();
var pos = transform.toTranslation();
var rot = transform.toRotation();
var scl = transform.toScale();

boneNode.setLocalPosition(new Vec3(pos.x(), pos.y(), pos.z()));
boneNode.setLocalRotation(new Vec4(rot.x(), rot.y(), rot.z(), rot.w()));
boneNode.setScale(new Vec3(scl.x(), scl.y(), scl.z()));
}
}
Loading
Loading