Skip to content

Commit

Permalink
feat: wip nasa workbench model
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaMode committed Dec 13, 2024
1 parent 7ba4c05 commit b1c6e02
Show file tree
Hide file tree
Showing 20 changed files with 1,423 additions and 217 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import com.mojang.math.Axis;
import dev.galacticraft.api.entity.rocket.render.RocketPartRenderer;
import dev.galacticraft.api.rocket.entity.Rocket;
import dev.galacticraft.mod.client.model.GCBakedModel;
import dev.galacticraft.mod.client.model.GCModelLoader;
import dev.galacticraft.mod.client.model.GCRenderTypes;
import net.fabricmc.api.EnvType;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/dev/galacticraft/mod/GalacticraftClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import dev.galacticraft.mod.client.model.GCRenderTypes;
import dev.galacticraft.mod.client.model.sprite.OxygenSealerTextureProvider;
import dev.galacticraft.mod.client.model.sprite.SolarPanelTextureProvider;
import dev.galacticraft.mod.client.model.types.ObjModel;
import dev.galacticraft.mod.client.model.types.UnbakedObjModel;
import dev.galacticraft.mod.client.network.GCClientPacketReceiver;
import dev.galacticraft.mod.client.particle.*;
import dev.galacticraft.mod.client.render.block.entity.GCBlockEntityRenderer;
Expand Down Expand Up @@ -197,7 +197,7 @@ public static void init() {
helper.registerReloadListener(GCModelLoader.INSTANCE);
helper.registerReloadListener(GCResourceReloadListener.INSTANCE);

GCModelLoader.registerModelType(ObjModel.TYPE);
GCModelLoader.registerModelType(UnbakedObjModel.TYPE);

GCDimensionEffects.register();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@
* SOFTWARE.
*/

package dev.galacticraft.mod.client.model;
package dev.galacticraft.mod.client.accessor;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import org.jetbrains.annotations.Nullable;
import de.javagl.obj.Mtl;
import de.javagl.obj.Obj;
import dev.galacticraft.mod.client.model.types.UnbakedObjModel;

public interface GCBakedModel extends AutoCloseable {
default void render(PoseStack modelStack, @Nullable GCModelState state, VertexConsumer consumer, int light, int overlay) {
this.render(modelStack, state, consumer, light, overlay, 0xFFFFFFFF);
}
import java.util.List;

void render(PoseStack modelStack, @Nullable GCModelState state, VertexConsumer consumer, int light, int overlay, int color);
public interface BlockModelAccessor {
void galacticraft$setObjData(UnbakedObjModel obj);
}
105 changes: 56 additions & 49 deletions src/main/java/dev/galacticraft/mod/client/model/BakedObjModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,70 +22,77 @@

package dev.galacticraft.mod.client.model;

import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import de.javagl.obj.*;
import dev.galacticraft.mod.client.model.types.ObjModel;
import net.fabricmc.fabric.api.renderer.v1.mesh.Mesh;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.function.Supplier;

/**
* A Model rendered via a VBO.
*/
public class BakedObjModel implements GCBakedModel {
private final Obj obj;
private final List<ObjModel.BakedMaterial> materials;
public record BakedObjModel(Mesh mesh, List<BakedQuad> legacyQuads, TextureAtlasSprite particle) implements BakedModel {
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction face, RandomSource randomSource) {
return legacyQuads();
}

@Override
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
this.mesh.outputTo(context.getEmitter());
}

@Override
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
this.mesh.outputTo(context.getEmitter());
}

@Override
public boolean isVanillaAdapter() {
return false;
}

public BakedObjModel(Obj obj, List<ObjModel.BakedMaterial> materials) {
this.obj = ObjUtils.convertToRenderable(obj);
this.materials = materials;
@Override
public boolean useAmbientOcclusion() {
return false;
}

@Override
public void render(PoseStack modelStack, @Nullable GCModelState state, VertexConsumer consumer, int light, int overlay, int color) {
ObjModel.BakedMaterial lastMaterial = null;
if (state == null) {
for (int index = 0; index < obj.getNumFaces(); index++) {
ObjFace face = obj.getFace(index);
lastMaterial = renderFace(lastMaterial, face, consumer, modelStack, light, overlay, color);
}
} else {
ObjGroup group = obj.getGroup(state.getName());
for (int index = 0; index < group.getNumFaces(); index++) {
ObjFace face = group.getFace(index);
lastMaterial = renderFace(lastMaterial, face, consumer, modelStack, light, overlay, color);
}
}
public boolean isGui3d() {
return false;
}

protected ObjModel.BakedMaterial renderFace(ObjModel.BakedMaterial lastMaterial, ObjFace face, VertexConsumer consumer, PoseStack matrices, int light, int overlay, int color) {
ObjModel.BakedMaterial material = findMaterial(this.obj.getActivatedMaterialGroupName(face), lastMaterial);
consumer = (material != null ? material.sprite() : GCModelLoader.INSTANCE.getDefaultSprite()).wrap(consumer);
PoseStack.Pose last = matrices.last();
for (int vtx = 0; vtx < face.getNumVertices(); vtx++) {
FloatTuple pos = obj.getVertex(face.getVertexIndex(vtx));
consumer.addVertex(last.pose(), pos.getX(), pos.getY(), pos.getZ());
consumer.setColor(color);
FloatTuple uv = obj.getTexCoord(face.getTexCoordIndex(vtx));
consumer.setUv(uv.getX(), 1 - uv.getY());
@Override
public boolean usesBlockLight() {
return false;
}

consumer.setOverlay(overlay);
consumer.setLight(light);
FloatTuple normals = obj.getNormal(face.getNormalIndex(vtx));
consumer.setNormal(last, normals.getX(), normals.getY(), normals.getZ());
}
@Override
public boolean isCustomRenderer() {
return false;
}

return material;
@Override
public TextureAtlasSprite getParticleIcon() {
return particle();
}

protected ObjModel.BakedMaterial findMaterial(String name, ObjModel.BakedMaterial lastMaterial) {
for (ObjModel.BakedMaterial material : materials)
if (material.material().getName().equals(name))
return material;
return lastMaterial;
@Override
public ItemTransforms getTransforms() {
return null;
}

@Override
public void close() {}
public ItemOverrides getOverrides() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import com.mojang.blaze3d.vertex.VertexConsumer;
import org.jetbrains.annotations.Nullable;

public class GCMissingModel implements GCBakedModel {
public class GCMissingModel implements GCModel {
@Override
public void render(PoseStack modelStack, @Nullable GCModelState state, VertexConsumer consumer, int light, int overlay, int color) {

Expand Down
27 changes: 8 additions & 19 deletions src/main/java/dev/galacticraft/mod/client/model/GCModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,14 @@

package dev.galacticraft.mod.client.model;

import com.mojang.serialization.MapCodec;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import org.jetbrains.annotations.Nullable;

import java.util.function.Function;

/**
* A unbaked model that's designed to not be attached to a block, useful for entity rendering.
*/
public interface GCModel {
GCModelType getType();

GCBakedModel bake(ResourceManager resourceManager, Function<Material, TextureAtlasSprite> spriteGetter);

interface GCModelType {
MapCodec<? extends GCModel> codec();

ResourceLocation getId();
public interface GCModel extends AutoCloseable {
default void render(PoseStack modelStack, @Nullable GCModelState state, VertexConsumer consumer, int light, int overlay) {
this.render(modelStack, state, consumer, light, overlay, 0xFFFFFFFF);
}

void render(PoseStack modelStack, @Nullable GCModelState state, VertexConsumer consumer, int light, int overlay, int color);
}
44 changes: 17 additions & 27 deletions src/main/java/dev/galacticraft/mod/client/model/GCModelLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,22 @@

public class GCModelLoader implements ModelLoadingPlugin, IdentifiableResourceReloadListener {
public static final GCModelLoader INSTANCE = new GCModelLoader();
public static final GCModel MISSING_MODEL = new GCMissingModel();
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
public static final FileToIdConverter MODEL_LISTER = FileToIdConverter.json("models/misc");
public static final ResourceLocation MODEL_LOADER_ID = Constant.id("model_loader");
public static final ResourceLocation WHITE_SPRITE = Constant.id("obj/white");
static final Map<ResourceLocation, GCModel.GCModelType> REGISTERED_TYPES = new ConcurrentHashMap<>();
static final Map<ResourceLocation, GCUnbakedModel.GCModelType> REGISTERED_TYPES = new ConcurrentHashMap<>();
public static final ResourceLocation TYPE_KEY = Constant.id("type");
public static final Codec<GCModel.GCModelType> MODEL_TYPE_CODEC = ResourceLocation.CODEC.flatXmap(id ->
public static final Codec<GCUnbakedModel.GCModelType> MODEL_TYPE_CODEC = ResourceLocation.CODEC.flatXmap(id ->
Optional.ofNullable(REGISTERED_TYPES.get(id))
.map(DataResult::success).orElseGet(() -> DataResult.error(() -> "(Galacticraft) No model type with id: " + id)),
type -> DataResult.success(type.getId())
);
public static final Codec<GCModel> MODEL_CODEC = MODEL_TYPE_CODEC.dispatch(TYPE_KEY.toString(), GCModel::getType, GCModel.GCModelType::codec);
public static final Codec<GCUnbakedModel> MODEL_CODEC = MODEL_TYPE_CODEC.dispatch(TYPE_KEY.toString(), GCUnbakedModel::getType, GCUnbakedModel.GCModelType::codec);
private static final ResourceLocation PARACHEST_ITEM = Constant.id("item/parachest");

private Map<ResourceLocation, GCBakedModel> models = ImmutableMap.of();
private Map<ResourceLocation, GCModel> models = ImmutableMap.of();
private AtlasSet atlases;

@Override
Expand Down Expand Up @@ -112,20 +113,9 @@ public void onInitializeModelLoader(Context pluginContext) {
}
return null;
});

pluginContext.addModels(
GalacticraftRocketPartRenderers.DEFAULT_CONE,
GalacticraftRocketPartRenderers.SLOPED_CONE,
GalacticraftRocketPartRenderers.ADVANCED_CONE,
GalacticraftRocketPartRenderers.DEFAULT_BODY,
GalacticraftRocketPartRenderers.DEFAULT_ENGINE,
GalacticraftRocketPartRenderers.DEFAULT_FIN,
GalacticraftRocketPartRenderers.BOOSTER_TIER_1,
GalacticraftRocketPartRenderers.BOOSTER_TIER_2
);
}

public static void registerModelType(GCModel.GCModelType type) {
public static void registerModelType(GCUnbakedModel.GCModelType type) {
REGISTERED_TYPES.put(type.getId(), type);
}

Expand All @@ -142,7 +132,7 @@ public CompletableFuture<Void> reload(PreparationBarrier synchronizer, ResourceM
TextureManager textureManager = Minecraft.getInstance().getTextureManager();
RocketAtlasCallback.EVENT.invoker().collectAtlases(atlasMap, textureManager);
this.atlases = new AtlasSet(atlasMap, textureManager);
CompletableFuture<Map<ResourceLocation, GCModel>> modelsFuture = loadModels(resourceManager, backgroundExecutor);
CompletableFuture<Map<ResourceLocation, GCUnbakedModel>> modelsFuture = loadModels(resourceManager, backgroundExecutor);
Map<ResourceLocation, CompletableFuture<AtlasSet.StitchResult>> stitchResult = this.atlases.scheduleLoad(resourceManager, Minecraft.getInstance().options.mipmapLevels().get(), backgroundExecutor);
preparationsProfiler.popPush("close_models");
this.models.values().forEach(gcBakedModel -> {
Expand Down Expand Up @@ -176,8 +166,8 @@ public CompletableFuture<Void> reload(PreparationBarrier synchronizer, ResourceM
}, gameExecutor);
}

private ReloadState bakeModels(ProfilerFiller profiler, Map<ResourceLocation, AtlasSet.StitchResult> preparations, Map<ResourceLocation, GCModel> models, ResourceManager resourceManager) {
Map<ResourceLocation, GCBakedModel> bakedModels = new HashMap<>();
private ReloadState bakeModels(ProfilerFiller profiler, Map<ResourceLocation, AtlasSet.StitchResult> preparations, Map<ResourceLocation, GCUnbakedModel> models, ResourceManager resourceManager) {
Map<ResourceLocation, GCModel> bakedModels = new HashMap<>();
profiler.push("baking");
Multimap<ResourceLocation, Material> missingTextures = HashMultimap.create();
models.forEach((modelId, gcModel) -> {
Expand Down Expand Up @@ -213,21 +203,21 @@ private ReloadState bakeModels(ProfilerFiller profiler, Map<ResourceLocation, At
return new ReloadState(bakedModels, preparations, readyForUpload);
}

private static CompletableFuture<Map<ResourceLocation, GCModel>> loadModels(ResourceManager resourceManager, Executor executor) {
private static CompletableFuture<Map<ResourceLocation, GCUnbakedModel>> loadModels(ResourceManager resourceManager, Executor executor) {
return CompletableFuture.supplyAsync(() -> MODEL_LISTER.listMatchingResources(resourceManager), executor)
.thenCompose(
map -> {
List<CompletableFuture<Pair<ResourceLocation, GCModel>>> models = new ArrayList<>(map.size());
List<CompletableFuture<Pair<ResourceLocation, GCUnbakedModel>>> models = new ArrayList<>(map.size());

for (Map.Entry<ResourceLocation, Resource> entry : map.entrySet()) {
models.add(CompletableFuture.supplyAsync(() -> {
ResourceLocation modelId = entry.getKey();
try {
Reader reader = entry.getValue().openAsReader();

Pair<ResourceLocation, GCModel> modelPair;
Pair<ResourceLocation, GCUnbakedModel> modelPair;
try {
DataResult<GCModel> model = MODEL_CODEC.parse(JsonOps.INSTANCE, GsonHelper.convertToJsonObject(GsonHelper.fromJson(GSON, reader, JsonElement.class), "top element"));
DataResult<GCUnbakedModel> model = MODEL_CODEC.parse(JsonOps.INSTANCE, GsonHelper.convertToJsonObject(GsonHelper.fromJson(GSON, reader, JsonElement.class), "top element"));
if (model.error().isPresent())
return null;
modelPair = Pair.of(entry.getKey(), model.getOrThrow(error -> new RuntimeException(String.format("Failed to load model: %s, %s", modelId, error))));
Expand Down Expand Up @@ -264,12 +254,12 @@ public Collection<ResourceLocation> getFabricDependencies() {
return List.of(RocketTextureManager.ID);
}

public Map<ResourceLocation, GCBakedModel> getModels() {
public Map<ResourceLocation, GCModel> getModels() {
return models;
}

public GCBakedModel getModel(ResourceLocation id) {
return this.models.getOrDefault(id, new GCMissingModel());
public GCModel getModel(ResourceLocation id) {
return this.models.getOrDefault(id, MISSING_MODEL);
}

public AtlasSet getAtlases() {
Expand All @@ -281,7 +271,7 @@ public TextureAtlasSprite getDefaultSprite() {
}

public record ReloadState(
Map<ResourceLocation, GCBakedModel> bakedModels,
Map<ResourceLocation, GCModel> bakedModels,
Map<ResourceLocation, AtlasSet.StitchResult> atlasPreparations,
CompletableFuture<Void> readyForUpload
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2019-2024 Team Galacticraft
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.galacticraft.mod.client.model;

import com.mojang.serialization.MapCodec;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.Material;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;

import java.util.function.Function;

/**
* A unbaked model that's designed to not be attached to a block, useful for entity rendering.
*/
public interface GCUnbakedModel {
GCModelType getType();

GCModel bake(ResourceManager resourceManager, Function<Material, TextureAtlasSprite> spriteGetter);

interface GCModelType {
MapCodec<? extends GCUnbakedModel> codec();

ResourceLocation getId();
}
}
Loading

0 comments on commit b1c6e02

Please sign in to comment.