classpublicPriority 3
ObjImportPage
com.hypixel.hytale.builtin.buildertools.objimport.ObjImportPage
extends InteractiveCustomUIPage
1
Methods
1
Public Methods
17
Fields
1
Constructors
Constants
String[]AUTO_DETECT_EXTENSIONS= <complex>
String[]AUTO_DETECT_SUFFIXES= <complex>
BuilderCodec<ObjImportPage.PageData>CODEC= BuilderCodec.builder(ObjImportPage.PageData.class, ObjImportPage.PageData::new)
.addFiel...
StringDEFAULT_BLOCK= "Rock_Stone"
intDEFAULT_HEIGHT= 20
floatDEFAULT_SCALE= 1.0F
PathIMPORTS_DIR= Paths.get("imports", "models")
StringKEY_AUTO_DETECT_TEXTURES= "@AutoDetectTextures"
StringKEY_BLOCK_PATTERN= "@BlockPattern"
StringKEY_BROWSE= "Browse"
StringKEY_BROWSER_CANCEL= "BrowserCancel"
StringKEY_BROWSER_SELECT= "BrowserSelect"
StringKEY_FILL_SOLID= "@FillSolid"
StringKEY_HEIGHT= "@Height"
StringKEY_IMPORT= "Import"
StringKEY_OBJ_PATH= "@ObjPath"
StringKEY_ORIGIN= "@Origin"
StringKEY_ROTATION= "@Rotation"
StringKEY_SCALE= "@Scale"
StringKEY_SIZE_MODE= "SizeMode"
StringKEY_USE_MATERIALS= "@UseMaterials"
intMAX_HEIGHT= 320
floatMAX_SCALE= 100.0F
intMIN_HEIGHT= 1
floatMIN_SCALE= 0.01F
StringPASTE_TOOL_ID= "EditorTool_Paste"
Constructors
public
ObjImportPage(PlayerRef playerRef)Methods
Public Methods (1)
public
void handleDataEvent(Ref<EntityStore> ref, Store<EntityStore> store, ObjImportPage.PageData data)Fields
Private/Package Fields (17)
private
boolean autoDetectTexturesprivate
String blockPatternprivate
ServerFileBrowser browserprivate
boolean fillSolidprivate
boolean isErrorprivate
boolean isProcessingprivate
String objPathprivate
ObjImportPage.Origin originprivate
String originStrprivate
ObjImportPage.MeshRotation rotationprivate
String rotationStrprivate
float scaleprivate
boolean showBrowserprivate
String statusMessageprivate
int targetHeightprivate
boolean useMaterialsprivate
boolean useScaleModeInheritance
Parent
Current
Interface
Child
Use mouse wheel to zoom, drag to pan. Click nodes to navigate.
Related Classes
Used By
BlockColorIndexBuilderToolsPluginCodecKeyedCodecBuilderCodecStringUtilRefStoreHytaleLoggerVector3iCustomPageLifetimeCustomUIEventBindingTypePageSetActiveSlotMessageBlockTypePlayerInteractiveCustomUIPageInventoryItemStackItemContainerSingleplayerModuleBlockSelectionDropdownEntryInfoLocalizableStringFileBrowserConfigServerFileBrowserEventDataUICommandBuilderUIEventBuilderPlayerRefEntityStore
Source Code
package com.hypixel.hytale.builtin.buildertools.objimport;
import com.hypixel.hytale.builtin.buildertools.BlockColorIndex;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.common.util.StringUtil;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.protocol.packets.interface_.CustomPageLifetime;
import com.hypixel.hytale.protocol.packets.interface_.CustomUIEventBindingType;
import com.hypixel.hytale.protocol.packets.interface_.Page;
import com.hypixel.hytale.protocol.packets.inventory.SetActiveSlot;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.core.entity.entities.player.pages.InteractiveCustomUIPage;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import com.hypixel.hytale.server.core.ui.DropdownEntryInfo;
import com.hypixel.hytale.server.core.ui.LocalizableString;
import com.hypixel.hytale.server.core.ui.browser.FileBrowserConfig;
import com.hypixel.hytale.server.core.ui.browser.ServerFileBrowser;
import com.hypixel.hytale.server.core.ui.builder.EventData;
import com.hypixel.hytale.server.core.ui.builder.UICommandBuilder;
import com.hypixel.hytale.server.core.ui.builder.UIEventBuilder;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Map.Entry;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class ObjImportPage extends InteractiveCustomUIPage<ObjImportPage.PageData> {
private static final String DEFAULT_BLOCK = "Rock_Stone";
private static final int DEFAULT_HEIGHT = 20;
private static final int MIN_HEIGHT = 1;
private static final int MAX_HEIGHT = 320;
private static final float DEFAULT_SCALE = 1.0F;
private static final float MIN_SCALE = 0.01F;
private static final float MAX_SCALE = 100.0F;
private static final String PASTE_TOOL_ID = "EditorTool_Paste";
private static final Path IMPORTS_DIR = Paths.get("imports", "models");
@Nonnull
private String objPath = "";
private int targetHeight = 20;
private boolean useScaleMode = false;
private float scale = 1.0F;
@Nonnull
private String blockPattern = "Rock_Stone";
private boolean fillSolid = true;
private boolean useMaterials = true;
private boolean autoDetectTextures = false;
@Nonnull
private String originStr = "bottom_center";
@Nonnull
private ObjImportPage.Origin origin = ObjImportPage.Origin.BOTTOM_CENTER;
@Nonnull
private String rotationStr = "y_up";
@Nonnull
private ObjImportPage.MeshRotation rotation = ObjImportPage.MeshRotation.NONE;
@Nullable
private String statusMessage = null;
private boolean isError = false;
private boolean isProcessing = false;
private boolean showBrowser = false;
@Nonnull
private final ServerFileBrowser browser;
private static final String[] AUTO_DETECT_SUFFIXES = new String[]{"", "_dif", "_diffuse"};
private static final String[] AUTO_DETECT_EXTENSIONS = new String[]{".png", ".jpg", ".jpeg"};
public ObjImportPage(@Nonnull PlayerRef playerRef) {
super(playerRef, CustomPageLifetime.CanDismiss, ObjImportPage.PageData.CODEC);
FileBrowserConfig config = FileBrowserConfig.builder()
.listElementId("#BrowserPage #FileList")
.searchInputId("#BrowserPage #SearchInput")
.currentPathId("#BrowserPage #CurrentPath")
.roots(List.of(new FileBrowserConfig.RootEntry("Imports", IMPORTS_DIR)))
.allowedExtensions(".obj")
.enableRootSelector(false)
.enableSearch(true)
.enableDirectoryNav(true)
.maxResults(50)
.build();
try {
Files.createDirectories(IMPORTS_DIR);
} catch (IOException var4) {
}
this.browser = new ServerFileBrowser(config);
}
@Override
public void build(
@Nonnull Ref<EntityStore> ref, @Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder, @Nonnull Store<EntityStore> store
) {
commandBuilder.append("Pages/ObjImportPage.ui");
commandBuilder.set("#ObjPath #Input.Value", this.objPath);
commandBuilder.set("#HeightInput #Input.Value", this.targetHeight);
commandBuilder.set("#ScaleInput #Input.Value", this.scale);
commandBuilder.set("#BlockPattern #Input.Value", this.blockPattern);
commandBuilder.set("#FillModeCheckbox #CheckBox.Value", this.fillSolid);
commandBuilder.set("#UseMaterialsCheckbox #CheckBox.Value", this.useMaterials);
commandBuilder.set("#AutoDetectTexturesCheckbox #CheckBox.Value", this.autoDetectTextures);
commandBuilder.set("#HeightInput.Visible", !this.useScaleMode);
commandBuilder.set("#ScaleInput.Visible", this.useScaleMode);
List<DropdownEntryInfo> sizeModeEntries = new ArrayList<>();
sizeModeEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.sizeMode.height"), "height"));
sizeModeEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.sizeMode.scale"), "scale"));
commandBuilder.set("#SizeModeInput #Input.Entries", sizeModeEntries);
commandBuilder.set("#SizeModeInput #Input.Value", this.useScaleMode ? "scale" : "height");
List<DropdownEntryInfo> originEntries = new ArrayList<>();
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_front_left"), "bottom_front_left"));
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.bottom_center"), "bottom_center"));
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.center"), "center"));
originEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.origin.top_center"), "top_center"));
commandBuilder.set("#OriginInput #Input.Entries", originEntries);
commandBuilder.set("#OriginInput #Input.Value", this.originStr);
List<DropdownEntryInfo> axisEntries = new ArrayList<>();
axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.yUp"), "y_up"));
axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.zUp"), "z_up"));
axisEntries.add(new DropdownEntryInfo(LocalizableString.fromMessageId("server.customUI.objImport.axis.xUp"), "x_up"));
commandBuilder.set("#RotationInput #Input.Entries", axisEntries);
commandBuilder.set("#RotationInput #Input.Value", this.rotationStr);
this.updateStatus(commandBuilder);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ObjPath #Input", EventData.of("@ObjPath", "#ObjPath #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#HeightInput #Input", EventData.of("@Height", "#HeightInput #Input.Value"), false);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#ScaleInput #Input", EventData.of("@Scale", "#ScaleInput #Input.Value"), false);
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged, "#SizeModeInput #Input", EventData.of("SizeMode", "#SizeModeInput #Input.Value"), false
);
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged, "#BlockPattern #Input", EventData.of("@BlockPattern", "#BlockPattern #Input.Value"), false
);
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged, "#FillModeCheckbox #CheckBox", EventData.of("@FillSolid", "#FillModeCheckbox #CheckBox.Value"), false
);
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged,
"#UseMaterialsCheckbox #CheckBox",
EventData.of("@UseMaterials", "#UseMaterialsCheckbox #CheckBox.Value"),
false
);
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged,
"#AutoDetectTexturesCheckbox #CheckBox",
EventData.of("@AutoDetectTextures", "#AutoDetectTexturesCheckbox #CheckBox.Value"),
false
);
eventBuilder.addEventBinding(CustomUIEventBindingType.ValueChanged, "#OriginInput #Input", EventData.of("@Origin", "#OriginInput #Input.Value"), false);
eventBuilder.addEventBinding(
CustomUIEventBindingType.ValueChanged, "#RotationInput #Input", EventData.of("@Rotation", "#RotationInput #Input.Value"), false
);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ImportButton", EventData.of("Import", "true"));
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#ObjPath #BrowseButton", EventData.of("Browse", "true"));
commandBuilder.set("#FormContainer.Visible", !this.showBrowser);
commandBuilder.set("#BrowserPage.Visible", this.showBrowser);
if (this.showBrowser) {
this.buildBrowserPage(commandBuilder, eventBuilder);
}
}
private void buildBrowserPage(@Nonnull UICommandBuilder commandBuilder, @Nonnull UIEventBuilder eventBuilder) {
this.browser.buildSearchInput(commandBuilder, eventBuilder);
this.browser.buildCurrentPath(commandBuilder);
this.browser.buildFileList(commandBuilder, eventBuilder);
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #SelectButton", EventData.of("BrowserSelect", "true"));
eventBuilder.addEventBinding(CustomUIEventBindingType.Activating, "#BrowserPage #CancelButton", EventData.of("BrowserCancel", "true"));
}
private void updateStatus(@Nonnull UICommandBuilder commandBuilder) {
if (this.statusMessage != null) {
commandBuilder.set("#StatusText.Text", this.statusMessage);
commandBuilder.set("#StatusText.Visible", true);
commandBuilder.set("#StatusText.Style.TextColor", this.isError ? "#e74c3c" : "#cfd8e3");
} else {
commandBuilder.set("#StatusText.Visible", false);
}
}
private void setError(@Nonnull String message) {
this.statusMessage = message;
this.isError = true;
this.isProcessing = false;
this.rebuild();
}
private void setStatus(@Nonnull String message) {
this.statusMessage = message;
this.isError = false;
this.rebuild();
}
public void handleDataEvent(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store, @Nonnull ObjImportPage.PageData data) {
boolean needsUpdate = false;
if (data.browse != null && data.browse) {
this.showBrowser = true;
this.rebuild();
} else if (data.browserCancel != null && data.browserCancel) {
this.showBrowser = false;
this.rebuild();
} else if (data.browserSelect != null && data.browserSelect) {
if (!this.browser.getSelectedItems().isEmpty()) {
String selectedPath = this.browser.getSelectedItems().iterator().next();
this.objPath = this.browser.getRoot().resolve(selectedPath).toString();
}
this.showBrowser = false;
this.rebuild();
} else {
if (this.showBrowser && (data.file != null || data.searchQuery != null || data.searchResult != null)) {
boolean handled = false;
if (data.searchQuery != null) {
this.browser.setSearchQuery(data.searchQuery.trim().toLowerCase());
handled = true;
}
if (data.file != null) {
String fileName = data.file;
if ("..".equals(fileName)) {
this.browser.navigateUp();
handled = true;
} else {
Path targetPath = this.browser.resolveFromCurrent(fileName);
if (targetPath != null && Files.isDirectory(targetPath)) {
this.browser.navigateTo(Paths.get(fileName));
handled = true;
} else if (targetPath != null && Files.isRegularFile(targetPath)) {
this.objPath = targetPath.toString();
this.showBrowser = false;
this.rebuild();
return;
}
}
}
if (data.searchResult != null) {
Path resolvedPath = this.browser.resolveSecure(data.searchResult);
if (resolvedPath != null && Files.isRegularFile(resolvedPath)) {
this.objPath = resolvedPath.toString();
this.showBrowser = false;
this.rebuild();
return;
}
}
if (handled) {
UICommandBuilder commandBuilder = new UICommandBuilder();
UIEventBuilder eventBuilder = new UIEventBuilder();
this.browser.buildFileList(commandBuilder, eventBuilder);
this.browser.buildCurrentPath(commandBuilder);
this.sendUpdate(commandBuilder, eventBuilder, false);
return;
}
}
if (data.objPath != null) {
this.objPath = StringUtil.stripQuotes(data.objPath.trim());
this.statusMessage = null;
needsUpdate = true;
}
if (data.height != null) {
this.targetHeight = Math.max(1, Math.min(320, data.height));
needsUpdate = true;
}
if (data.scale != null) {
this.scale = Math.max(0.01F, Math.min(100.0F, data.scale));
needsUpdate = true;
}
if (data.sizeMode != null) {
this.useScaleMode = "scale".equalsIgnoreCase(data.sizeMode);
this.rebuild();
} else {
if (data.blockPattern != null) {
this.blockPattern = data.blockPattern.trim();
needsUpdate = true;
}
if (data.fillSolid != null) {
this.fillSolid = data.fillSolid;
needsUpdate = true;
}
if (data.useMaterials != null) {
this.useMaterials = data.useMaterials;
needsUpdate = true;
}
if (data.autoDetectTextures != null) {
this.autoDetectTextures = data.autoDetectTextures;
needsUpdate = true;
}
if (data.origin != null) {
this.originStr = data.origin.trim().toLowerCase();
String var8 = this.originStr;
this.origin = switch (var8) {
case "bottom_front_left" -> ObjImportPage.Origin.BOTTOM_FRONT_LEFT;
case "center" -> ObjImportPage.Origin.CENTER;
case "top_center" -> ObjImportPage.Origin.TOP_CENTER;
default -> ObjImportPage.Origin.BOTTOM_CENTER;
};
needsUpdate = true;
}
if (data.rotation != null) {
this.rotationStr = data.rotation.trim().toLowerCase();
String var9 = this.rotationStr;
this.rotation = switch (var9) {
case "z_up" -> ObjImportPage.MeshRotation.Z_UP_TO_Y_UP;
case "x_up" -> ObjImportPage.MeshRotation.X_UP_TO_Y_UP;
default -> ObjImportPage.MeshRotation.NONE;
};
needsUpdate = true;
}
if (data.doImport != null && data.doImport && !this.isProcessing) {
this.performImport(ref, store);
} else {
if (needsUpdate) {
this.sendUpdate();
}
}
}
}
}
@Nullable
private List<ObjImportPage.WeightedBlock> parseBlockPattern(@Nonnull String pattern) {
List<ObjImportPage.WeightedBlock> result = new ArrayList<>();
String[] parts = pattern.split(",");
for (String part : parts) {
part = part.trim();
if (!part.isEmpty()) {
int weight = 100;
String blockName = part;
int pctIdx = part.indexOf(37);
if (pctIdx > 0) {
try {
weight = Integer.parseInt(part.substring(0, pctIdx).trim());
blockName = part.substring(pctIdx + 1).trim();
} catch (NumberFormatException var12) {
return null;
}
}
int blockId = BlockType.getAssetMap().getIndex(blockName);
if (blockId == -2147483648) {
return null;
}
result.add(new ObjImportPage.WeightedBlock(blockId, weight));
}
}
return result.isEmpty() ? null : result;
}
private int selectRandomBlock(@Nonnull List<ObjImportPage.WeightedBlock> blocks, @Nonnull Random random) {
if (blocks.isEmpty()) {
throw new IllegalStateException("Cannot select from empty blocks list");
} else if (blocks.size() == 1) {
return blocks.get(0).blockId;
} else {
int totalWeight = 0;
for (ObjImportPage.WeightedBlock wb : blocks) {
totalWeight += wb.weight;
}
if (totalWeight <= 0) {
return blocks.get(0).blockId;
} else {
int roll = random.nextInt(totalWeight);
int cumulative = 0;
for (ObjImportPage.WeightedBlock wb : blocks) {
cumulative += wb.weight;
if (roll < cumulative) {
return wb.blockId;
}
}
return blocks.get(0).blockId;
}
}
}
private void performImport(@Nonnull Ref<EntityStore> ref, @Nonnull Store<EntityStore> store) {
if (this.objPath.isEmpty()) {
this.setError("Please enter a path to an OBJ file");
} else {
Path path = Paths.get(this.objPath);
if (!SingleplayerModule.isOwner(this.playerRef)) {
Path normalizedPath = path.toAbsolutePath().normalize();
Path normalizedImports = IMPORTS_DIR.toAbsolutePath().normalize();
if (!normalizedPath.startsWith(normalizedImports)) {
this.setError("Files must be in the server's imports/models directory");
return;
}
}
if (!Files.exists(path)) {
this.setError("File not found: " + this.objPath);
} else if (!this.objPath.toLowerCase().endsWith(".obj")) {
this.setError("File must be a .obj file");
} else {
List<ObjImportPage.WeightedBlock> blocks = this.parseBlockPattern(this.blockPattern);
if (blocks == null) {
this.setError("Invalid block pattern: " + this.blockPattern);
} else {
this.isProcessing = true;
this.setStatus("Processing...");
Player playerComponent = store.getComponent(ref, Player.getComponentType());
PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType());
if (playerComponent != null && playerRefComponent != null) {
int finalHeight = this.targetHeight;
boolean finalUseScaleMode = this.useScaleMode;
float finalScale = this.scale;
String finalPath = this.objPath;
boolean finalFillSolid = this.fillSolid;
boolean finalUseMaterials = this.useMaterials;
boolean finalAutoDetectTextures = this.autoDetectTextures;
ObjImportPage.Origin finalOrigin = this.origin;
ObjImportPage.MeshRotation finalRotation = this.rotation;
BuilderToolsPlugin.addToQueue(
playerComponent,
playerRefComponent,
(r, builderState, componentAccessor) -> {
try {
Path objFilePath = Paths.get(finalPath);
ObjParser.ObjMesh mesh = ObjParser.parse(objFilePath);
switch (finalRotation) {
case Z_UP_TO_Y_UP:
mesh.transformZUpToYUp();
break;
case X_UP_TO_Y_UP:
mesh.transformXUpToYUp();
}
int computedHeight;
if (finalUseScaleMode) {
float[] bounds = mesh.getBounds();
float meshHeight = bounds[4] - bounds[1];
computedHeight = Math.max(1, (int)Math.ceil((double)(meshHeight * finalScale)));
} else {
computedHeight = finalHeight;
}
if (blocks.isEmpty()) {
this.setError("No blocks available for import");
return;
}
BlockColorIndex colorIndex = BuilderToolsPlugin.get().getBlockColorIndex();
Map<String, BufferedImage> materialTextures = new HashMap<>();
Map<String, Integer> materialToBlockId = new HashMap<>();
int defaultBlockId = blocks.get(0).blockId;
if (finalUseMaterials && mesh.mtlLib() != null) {
this.loadMaterialData(objFilePath, mesh, colorIndex, materialTextures, materialToBlockId, finalAutoDetectTextures);
if (!materialToBlockId.isEmpty()) {
defaultBlockId = materialToBlockId.values().iterator().next();
}
}
boolean hasUvTextures = mesh.hasUvCoordinates() && !materialTextures.isEmpty();
boolean preserveOrigin = finalOrigin == ObjImportPage.Origin.BOTTOM_FRONT_LEFT;
MeshVoxelizer.VoxelResult result;
if (hasUvTextures) {
result = MeshVoxelizer.voxelize(
mesh, computedHeight, finalFillSolid, materialTextures, materialToBlockId, colorIndex, defaultBlockId, preserveOrigin
);
} else {
result = MeshVoxelizer.voxelize(
mesh, computedHeight, finalFillSolid, null, materialToBlockId, colorIndex, defaultBlockId, preserveOrigin
);
}
TextureSampler.clearCache();
int offsetX = 0;
int offsetY = 0;
int offsetZ = 0;
switch (finalOrigin) {
case BOTTOM_FRONT_LEFT:
default:
break;
case BOTTOM_CENTER:
offsetX = -result.sizeX() / 2;
offsetZ = -result.sizeZ() / 2;
break;
case CENTER:
offsetX = -result.sizeX() / 2;
offsetY = -result.sizeY() / 2;
offsetZ = -result.sizeZ() / 2;
break;
case TOP_CENTER:
offsetX = -result.sizeX() / 2;
offsetY = -result.sizeY();
offsetZ = -result.sizeZ() / 2;
}
BlockSelection selection = new BlockSelection(result.countSolid(), 0);
selection.setPosition(0, 0, 0);
Random random = new Random();
boolean hasMaterialBlockIds = result.blockIds() != null;
for (int x = 0; x < result.sizeX(); x++) {
for (int y = 0; y < result.sizeY(); y++) {
for (int z = 0; z < result.sizeZ(); z++) {
if (result.voxels()[x][y][z]) {
int blockId;
if (hasMaterialBlockIds) {
blockId = result.getBlockId(x, y, z);
if (blockId == 0) {
blockId = this.selectRandomBlock(blocks, random);
}
} else {
blockId = this.selectRandomBlock(blocks, random);
}
selection.addBlockAtLocalPos(x + offsetX, y + offsetY, z + offsetZ, blockId, 0, 0, 0);
}
}
}
}
selection.setSelectionArea(
new Vector3i(offsetX, offsetY, offsetZ),
new Vector3i(result.sizeX() - 1 + offsetX, result.sizeY() - 1 + offsetY, result.sizeZ() - 1 + offsetZ)
);
builderState.setSelection(selection);
builderState.sendSelectionToClient();
int blockCount = result.countSolid();
String textureInfo = hasUvTextures ? " (UV textured)" : "";
this.statusMessage = String.format(
"Success! %d blocks copied to clipboard (%dx%dx%d)%s", blockCount, result.sizeX(), result.sizeY(), result.sizeZ(), textureInfo
);
this.isProcessing = false;
playerRefComponent.sendMessage(
Message.translation("server.builderTools.objImport.success")
.param("count", blockCount)
.param("width", result.sizeX())
.param("height", result.sizeY())
.param("depth", result.sizeZ())
);
playerComponent.getPageManager().setPage(r, store, Page.None);
this.switchToPasteTool(playerComponent, playerRefComponent);
} catch (ObjParser.ObjParseException var37) {
BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("OBJ parse error: %s", var37.getMessage());
this.setError("Parse error: " + var37.getMessage());
} catch (IOException var38) {
((HytaleLogger.Api)BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var38)).log("OBJ import IO error");
this.setError("IO error: " + var38.getMessage());
} catch (Exception var39) {
((HytaleLogger.Api)BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(var39)).log("OBJ import error");
this.setError("Error: " + var39.getMessage());
}
}
);
} else {
this.setError("Player not found");
}
}
}
}
}
private void switchToPasteTool(@Nonnull Player playerComponent, @Nonnull PlayerRef playerRef) {
Inventory inventory = playerComponent.getInventory();
ItemContainer hotbar = inventory.getHotbar();
ItemContainer storage = inventory.getStorage();
ItemContainer tools = inventory.getTools();
int hotbarSize = hotbar.getCapacity();
for (short slot = 0; slot < hotbarSize; slot++) {
ItemStack itemStack = hotbar.getItemStack(slot);
if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
inventory.setActiveHotbarSlot((byte)slot);
playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)slot));
return;
}
}
short emptySlot = -1;
for (short slotx = 0; slotx < hotbarSize; slotx++) {
ItemStack itemStack = hotbar.getItemStack(slotx);
if (itemStack == null || itemStack.isEmpty()) {
emptySlot = slotx;
break;
}
}
if (emptySlot != -1) {
for (short slotxx = 0; slotxx < storage.getCapacity(); slotxx++) {
ItemStack itemStack = storage.getItemStack(slotxx);
if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
storage.moveItemStackFromSlotToSlot(slotxx, 1, hotbar, emptySlot);
inventory.setActiveHotbarSlot((byte)emptySlot);
playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot));
return;
}
}
ItemStack pasteToolStack = null;
for (short slotxxx = 0; slotxxx < tools.getCapacity(); slotxxx++) {
ItemStack itemStack = tools.getItemStack(slotxxx);
if (itemStack != null && !itemStack.isEmpty() && "EditorTool_Paste".equals(itemStack.getItemId())) {
pasteToolStack = itemStack;
break;
}
}
if (pasteToolStack != null) {
hotbar.setItemStackForSlot(emptySlot, new ItemStack(pasteToolStack.getItemId()));
inventory.setActiveHotbarSlot((byte)emptySlot);
playerRef.getPacketHandler().writeNoCache(new SetActiveSlot(-1, (byte)emptySlot));
}
}
}
private void loadMaterialData(
@Nonnull Path objPath,
@Nonnull ObjParser.ObjMesh mesh,
@Nonnull BlockColorIndex colorIndex,
@Nonnull Map<String, BufferedImage> materialTextures,
@Nonnull Map<String, Integer> materialToBlockId,
boolean autoDetectTextures
) throws IOException {
if (mesh.mtlLib() != null) {
Path mtlPath = objPath.getParent().resolve(mesh.mtlLib());
if (Files.exists(mtlPath)) {
Map<String, MtlParser.MtlMaterial> materials = MtlParser.parse(mtlPath);
Path textureDir = mtlPath.getParent();
for (Entry<String, MtlParser.MtlMaterial> entry : materials.entrySet()) {
String materialName = entry.getKey();
MtlParser.MtlMaterial material = entry.getValue();
String texturePath = material.diffuseTexturePath();
if (texturePath == null && autoDetectTextures) {
texturePath = findMatchingTexture(textureDir, materialName);
}
if (texturePath != null) {
Path resolvedPath = textureDir.resolve(texturePath);
BufferedImage texture = TextureSampler.loadTexture(resolvedPath);
if (texture != null) {
materialTextures.put(materialName, texture);
int[] avgColor = TextureSampler.getAverageColor(resolvedPath);
if (avgColor != null) {
int blockId = colorIndex.findClosestBlock(avgColor[0], avgColor[1], avgColor[2]);
if (blockId > 0) {
materialToBlockId.put(materialName, blockId);
}
}
continue;
}
}
int[] rgb = material.getDiffuseColorRGB();
if (rgb != null) {
int blockId = colorIndex.findClosestBlock(rgb[0], rgb[1], rgb[2]);
if (blockId > 0) {
materialToBlockId.put(materialName, blockId);
}
}
}
}
}
}
@Nullable
private static String findMatchingTexture(@Nonnull Path directory, @Nonnull String materialName) {
for (String suffix : AUTO_DETECT_SUFFIXES) {
for (String ext : AUTO_DETECT_EXTENSIONS) {
String filename = materialName + suffix + ext;
if (Files.exists(directory.resolve(filename))) {
return filename;
}
}
}
return null;
}
public static enum MeshRotation {
NONE,
Z_UP_TO_Y_UP,
X_UP_TO_Y_UP;
private MeshRotation() {
}
}
public static enum Origin {
BOTTOM_FRONT_LEFT,
BOTTOM_CENTER,
CENTER,
TOP_CENTER;
private Origin() {
}
}
public static class PageData {
static final String KEY_OBJ_PATH = "@ObjPath";
static final String KEY_HEIGHT = "@Height";
static final String KEY_SCALE = "@Scale";
static final String KEY_SIZE_MODE = "SizeMode";
static final String KEY_BLOCK_PATTERN = "@BlockPattern";
static final String KEY_FILL_SOLID = "@FillSolid";
static final String KEY_USE_MATERIALS = "@UseMaterials";
static final String KEY_AUTO_DETECT_TEXTURES = "@AutoDetectTextures";
static final String KEY_ORIGIN = "@Origin";
static final String KEY_ROTATION = "@Rotation";
static final String KEY_IMPORT = "Import";
static final String KEY_BROWSE = "Browse";
static final String KEY_BROWSER_SELECT = "BrowserSelect";
static final String KEY_BROWSER_CANCEL = "BrowserCancel";
public static final BuilderCodec<ObjImportPage.PageData> CODEC = BuilderCodec.builder(ObjImportPage.PageData.class, ObjImportPage.PageData::new)
.addField(new KeyedCodec<>("@ObjPath", Codec.STRING), (entry, s) -> entry.objPath = s, entry -> entry.objPath)
.addField(new KeyedCodec<>("@Height", Codec.INTEGER), (entry, i) -> entry.height = i, entry -> entry.height)
.addField(new KeyedCodec<>("@Scale", Codec.FLOAT), (entry, f) -> entry.scale = f, entry -> entry.scale)
.addField(new KeyedCodec<>("SizeMode", Codec.STRING), (entry, s) -> entry.sizeMode = s, entry -> entry.sizeMode)
.addField(new KeyedCodec<>("@BlockPattern", Codec.STRING), (entry, s) -> entry.blockPattern = s, entry -> entry.blockPattern)
.addField(new KeyedCodec<>("@FillSolid", Codec.BOOLEAN), (entry, b) -> entry.fillSolid = b, entry -> entry.fillSolid)
.addField(new KeyedCodec<>("@UseMaterials", Codec.BOOLEAN), (entry, b) -> entry.useMaterials = b, entry -> entry.useMaterials)
.addField(new KeyedCodec<>("@AutoDetectTextures", Codec.BOOLEAN), (entry, b) -> entry.autoDetectTextures = b, entry -> entry.autoDetectTextures)
.addField(new KeyedCodec<>("@Origin", Codec.STRING), (entry, s) -> entry.origin = s, entry -> entry.origin)
.addField(new KeyedCodec<>("@Rotation", Codec.STRING), (entry, s) -> entry.rotation = s, entry -> entry.rotation)
.addField(
new KeyedCodec<>("Import", Codec.STRING),
(entry, s) -> entry.doImport = "true".equalsIgnoreCase(s),
entry -> entry.doImport != null && entry.doImport ? "true" : null
)
.addField(
new KeyedCodec<>("Browse", Codec.STRING),
(entry, s) -> entry.browse = "true".equalsIgnoreCase(s),
entry -> entry.browse != null && entry.browse ? "true" : null
)
.addField(
new KeyedCodec<>("BrowserSelect", Codec.STRING),
(entry, s) -> entry.browserSelect = "true".equalsIgnoreCase(s),
entry -> entry.browserSelect != null && entry.browserSelect ? "true" : null
)
.addField(
new KeyedCodec<>("BrowserCancel", Codec.STRING),
(entry, s) -> entry.browserCancel = "true".equalsIgnoreCase(s),
entry -> entry.browserCancel != null && entry.browserCancel ? "true" : null
)
.addField(new KeyedCodec<>("File", Codec.STRING), (entry, s) -> entry.file = s, entry -> entry.file)
.addField(new KeyedCodec<>("@SearchQuery", Codec.STRING), (entry, s) -> entry.searchQuery = s, entry -> entry.searchQuery)
.addField(new KeyedCodec<>("SearchResult", Codec.STRING), (entry, s) -> entry.searchResult = s, entry -> entry.searchResult)
.build();
@Nullable
private String objPath;
@Nullable
private Integer height;
@Nullable
private Float scale;
@Nullable
private String sizeMode;
@Nullable
private String blockPattern;
@Nullable
private Boolean fillSolid;
@Nullable
private Boolean useMaterials;
@Nullable
private Boolean autoDetectTextures;
@Nullable
private String origin;
@Nullable
private String rotation;
@Nullable
private Boolean doImport;
@Nullable
private Boolean browse;
@Nullable
private Boolean browserSelect;
@Nullable
private Boolean browserCancel;
@Nullable
private String file;
@Nullable
private String searchQuery;
@Nullable
private String searchResult;
public PageData() {
}
}
private static record WeightedBlock(int blockId, int weight) {
}
}