mirror of
https://github.com/ProjectSWGCore/pswgcommon.git
synced 2026-01-17 00:04:25 -05:00
Fixed iff form writing and fixed serialization of terrain files
This commit is contained in:
@@ -251,7 +251,9 @@ public class IffChunk extends IffNode {
|
||||
while (limit < additionalLength)
|
||||
limit *= 2;
|
||||
|
||||
this.data = ByteBuffer.wrap(Arrays.copyOf(data.array(), limit));
|
||||
int newPosition = data.position();
|
||||
this.data = ByteBuffer.wrap(Arrays.copyOf(data.array(), limit)).order(ByteOrder.LITTLE_ENDIAN);
|
||||
data.position(newPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,6 +9,7 @@ import java.nio.ByteOrder;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.FileChannel.MapMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -265,6 +266,53 @@ public class IffForm extends IffNode {
|
||||
return new IffForm(tag, version, children);
|
||||
}
|
||||
|
||||
public static ByteBuffer write(IffForm form) {
|
||||
ByteBuffer data = ByteBuffer.allocate(getIffSize(form));
|
||||
data.order(ByteOrder.BIG_ENDIAN);
|
||||
write(form, data);
|
||||
assert(data.remaining() == 0);
|
||||
data.flip();
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void write(IffForm form, ByteBuffer data) {
|
||||
int expectedPosition = data.position() + getIffSize(form);
|
||||
data.putInt(0x464F524D); // FORM
|
||||
data.putInt(getIffSize(form) - 8);
|
||||
data.put(form.getTag().getBytes(StandardCharsets.US_ASCII));
|
||||
if (form.getVersion() != -1) {
|
||||
data.putInt(0x464F524D); // FORM
|
||||
data.putInt(getIffSize(form) - 20);
|
||||
data.put(String.format("%04d", form.getVersion()).getBytes(StandardCharsets.US_ASCII));
|
||||
}
|
||||
|
||||
for (IffNode node : form.getChildren()) {
|
||||
if (!node.isForm()) { // Start with chunks
|
||||
byte [] chunkData = ((IffChunk) node).getData().array();
|
||||
int chunkLength = ((IffChunk) node).getData().position();
|
||||
data.put(node.getTag().getBytes(StandardCharsets.US_ASCII));
|
||||
data.putInt(chunkLength);
|
||||
data.put(chunkData, 0, chunkLength);
|
||||
} else {
|
||||
write((IffForm) node, data);
|
||||
}
|
||||
}
|
||||
assert data.position() == expectedPosition;
|
||||
}
|
||||
|
||||
private static int getIffSize(IffForm form) {
|
||||
int size = (form.getVersion() == -1) ? 12 : 24;
|
||||
|
||||
for (IffNode node : form.getChildren()) {
|
||||
if (!node.isForm()) { // Start with chunks
|
||||
size += 8 + ((IffChunk) node).getData().position();
|
||||
} else {
|
||||
size += getIffSize((IffForm) node);
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private static boolean isValidIff(ByteBuffer buffer, int size) {
|
||||
buffer.mark();
|
||||
|
||||
|
||||
@@ -6,31 +6,32 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public interface SWGParser {
|
||||
|
||||
void read(@NotNull IffForm form);
|
||||
IffForm write();
|
||||
|
||||
static void setBasePath(String basePath) {
|
||||
SWGParserFactory.INSTANCE.setBasePath(basePath);
|
||||
}
|
||||
|
||||
static String getBasePath() {
|
||||
return SWGParserFactory.INSTANCE.getBasePath();
|
||||
}
|
||||
|
||||
static <T extends SWGParser> T parse(String file) {
|
||||
return parse(new File("clientdata/"+file));
|
||||
return SWGParserFactory.parse(file);
|
||||
}
|
||||
|
||||
static <T extends SWGParser> T parse(File file) {
|
||||
return SWGParserCache.parseIfAbsent(file);
|
||||
return SWGParserFactory.parse(file);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static <T extends SWGParser> T parse(IffForm form) {
|
||||
SWGParser parser = SWGParserFactory.createParser(form.getTag());
|
||||
if (parser == null)
|
||||
return null;
|
||||
parser.read(form);
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
T ret = (T) parser;
|
||||
return ret;
|
||||
}
|
||||
return SWGParserFactory.parse(form);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.projectswg.common.data.swgiff.parsers;
|
||||
|
||||
import com.projectswg.common.data.swgiff.IffForm;
|
||||
import com.projectswg.common.data.swgiff.parsers.appearance.*;
|
||||
import com.projectswg.common.data.swgiff.parsers.appearance.extents.*;
|
||||
import com.projectswg.common.data.swgiff.parsers.creation.CombinedProfessionTemplateParser;
|
||||
@@ -22,62 +23,95 @@ import com.projectswg.common.data.swgiff.parsers.terrain.boundaries.BoundaryRect
|
||||
import com.projectswg.common.data.swgiff.parsers.terrain.filters.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
enum SWGParserFactory {
|
||||
INSTANCE;
|
||||
|
||||
private static final Map<String, Supplier<SWGParser>> PARSERS = new HashMap<>();
|
||||
private final Map<String, Supplier<SWGParser>> parsers = new HashMap<>();
|
||||
private final AtomicReference<String> basePath = new AtomicReference<>("clientdata");
|
||||
|
||||
static {
|
||||
SWGParserFactory() {
|
||||
// Terrain
|
||||
PARSERS.put("PTAT", TerrainTemplate::new);
|
||||
PARSERS.put("LAYR", TerrainListLayer::new);
|
||||
PARSERS.put("AHCN", AffectorHeightConstant::new);
|
||||
PARSERS.put("AHFR", AffectorHeightFractal::new);
|
||||
PARSERS.put("AHTR", AffectorHeightTerrace::new);
|
||||
PARSERS.put("AROA", AffectorHeightRoad::new);
|
||||
PARSERS.put("BREC", BoundaryRectangle::new);
|
||||
PARSERS.put("BCIR", BoundaryCircle::new);
|
||||
PARSERS.put("BPLN", BoundaryPolyLine::new);
|
||||
PARSERS.put("BPOL", BoundaryPolygon::new);
|
||||
PARSERS.put("FBIT", FilterBitmap::new);
|
||||
PARSERS.put("FDIR", FilterDirection::new);
|
||||
PARSERS.put("FFRA", FilterFractal::new);
|
||||
PARSERS.put("FHGT", FilterHeight::new);
|
||||
PARSERS.put("FSLP", FilterSlope::new);
|
||||
PARSERS.put("FSHD", FilterShader::new);
|
||||
parsers.put("PTAT", TerrainTemplate::new);
|
||||
parsers.put("LAYR", TerrainListLayer::new);
|
||||
parsers.put("AHCN", AffectorHeightConstant::new);
|
||||
parsers.put("AHFR", AffectorHeightFractal::new);
|
||||
parsers.put("AHTR", AffectorHeightTerrace::new);
|
||||
parsers.put("AROA", AffectorHeightRoad::new);
|
||||
parsers.put("BREC", BoundaryRectangle::new);
|
||||
parsers.put("BCIR", BoundaryCircle::new);
|
||||
parsers.put("BPLN", BoundaryPolyLine::new);
|
||||
parsers.put("BPOL", BoundaryPolygon::new);
|
||||
parsers.put("FBIT", FilterBitmap::new);
|
||||
parsers.put("FDIR", FilterDirection::new);
|
||||
parsers.put("FFRA", FilterFractal::new);
|
||||
parsers.put("FHGT", FilterHeight::new);
|
||||
parsers.put("FSLP", FilterSlope::new);
|
||||
parsers.put("FSHD", FilterShader::new);
|
||||
|
||||
PARSERS.put("APT ", AppearanceTemplateList::new);
|
||||
PARSERS.put("APPR", AppearanceTemplate::new);
|
||||
PARSERS.put("ARGD", SlotArrangementParser::new);
|
||||
PARSERS.put("CELL", PortalLayoutCellTemplate::new);
|
||||
PARSERS.put("CMPT", ComponentExtentParser::new);
|
||||
PARSERS.put("CMSH", MeshExtentParser::new);
|
||||
PARSERS.put("CPST", CompositeExtentParser::new);
|
||||
PARSERS.put("CSTB", CrcStringDataParser::new);
|
||||
PARSERS.put("DTAL", DetailExtentParser::new);
|
||||
PARSERS.put("DTLA", DetailAppearanceTemplate::new);
|
||||
PARSERS.put("EXBX", BoxExtentParser::new);
|
||||
PARSERS.put("EXSP", SphereExtentParser::new);
|
||||
PARSERS.put("FOOT", FootprintDataParser::new);
|
||||
PARSERS.put("IDTL", IndexedTriangleListParser::new);
|
||||
PARSERS.put("MESH", MeshAppearanceTemplate::new);
|
||||
PARSERS.put("NULL", NullExtentParser::new);
|
||||
PARSERS.put("PFDT", CombinedProfessionTemplateParser::new);
|
||||
PARSERS.put("PRFI", ProfessionTemplateParser::new);
|
||||
PARSERS.put("PRTL", PortalLayoutCellPortalTemplate::new);
|
||||
PARSERS.put("PRTO", PortalLayoutTemplate::new);
|
||||
PARSERS.put("SLTD", SlotDescriptorParser::new);
|
||||
PARSERS.put("XCYL", CylinderExtentParser::new);
|
||||
PARSERS.put("XOCL", OrientedCylinderExtentParser::new);
|
||||
parsers.put("APT ", AppearanceTemplateList::new);
|
||||
parsers.put("APPR", AppearanceTemplate::new);
|
||||
parsers.put("ARGD", SlotArrangementParser::new);
|
||||
parsers.put("CELL", PortalLayoutCellTemplate::new);
|
||||
parsers.put("CMPT", ComponentExtentParser::new);
|
||||
parsers.put("CMSH", MeshExtentParser::new);
|
||||
parsers.put("CPST", CompositeExtentParser::new);
|
||||
parsers.put("CSTB", CrcStringDataParser::new);
|
||||
parsers.put("DTAL", DetailExtentParser::new);
|
||||
parsers.put("DTLA", DetailAppearanceTemplate::new);
|
||||
parsers.put("EXBX", BoxExtentParser::new);
|
||||
parsers.put("EXSP", SphereExtentParser::new);
|
||||
parsers.put("FOOT", FootprintDataParser::new);
|
||||
parsers.put("IDTL", IndexedTriangleListParser::new);
|
||||
parsers.put("MESH", MeshAppearanceTemplate::new);
|
||||
parsers.put("NULL", NullExtentParser::new);
|
||||
parsers.put("PFDT", CombinedProfessionTemplateParser::new);
|
||||
parsers.put("PRFI", ProfessionTemplateParser::new);
|
||||
parsers.put("PRTL", PortalLayoutCellPortalTemplate::new);
|
||||
parsers.put("PRTO", PortalLayoutTemplate::new);
|
||||
parsers.put("SLTD", SlotDescriptorParser::new);
|
||||
parsers.put("XCYL", CylinderExtentParser::new);
|
||||
parsers.put("XOCL", OrientedCylinderExtentParser::new);
|
||||
}
|
||||
|
||||
public void setBasePath(String basePath) {
|
||||
this.basePath.set(basePath);
|
||||
}
|
||||
|
||||
public String getBasePath() {
|
||||
return this.basePath.get();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static SWGParser createParser(String tag) {
|
||||
Supplier<SWGParser> parser = PARSERS.get(tag);
|
||||
Supplier<SWGParser> parser = INSTANCE.parsers.get(tag);
|
||||
return parser == null ? null : parser.get();
|
||||
}
|
||||
|
||||
static <T extends SWGParser> T parse(String file) {
|
||||
return parse(new File(INSTANCE.basePath.get(), file));
|
||||
}
|
||||
|
||||
static <T extends SWGParser> T parse(File file) {
|
||||
return SWGParserCache.parseIfAbsent(file);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static <T extends SWGParser> T parse(IffForm form) {
|
||||
SWGParser parser = createParser(form.getTag());
|
||||
if (parser == null)
|
||||
return null;
|
||||
parser.read(form);
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
T ret = (T) parser;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ abstract class TerrainLayer : SWGParser {
|
||||
}
|
||||
}
|
||||
|
||||
fun writeHeaderChunk(): IffChunk {
|
||||
fun writeHeaderChunk(): IffForm {
|
||||
val data = IffChunk("DATA")
|
||||
data.writeInt(if (isEnabled) 1 else 0)
|
||||
data.writeString(customName)
|
||||
|
||||
return data
|
||||
return IffForm.of("IHDR", 1, data)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.projectswg.common.data.swgiff.parsers.terrain
|
||||
|
||||
import com.projectswg.common.data.location.Rectangle2f
|
||||
import com.projectswg.common.data.swgiff.IffChunk
|
||||
import com.projectswg.common.data.swgiff.IffForm
|
||||
import com.projectswg.common.data.swgiff.IffNode
|
||||
import com.projectswg.common.data.swgiff.parsers.SWGParser
|
||||
import com.projectswg.common.data.swgiff.parsers.terrain.affectors.AffectorHeightLayer
|
||||
import com.projectswg.common.data.swgiff.parsers.terrain.boundaries.BoundaryLayer
|
||||
import com.projectswg.common.data.swgiff.parsers.terrain.filters.FilterBitmap
|
||||
import com.projectswg.common.data.swgiff.parsers.terrain.filters.FilterLayer
|
||||
|
||||
class TerrainListLayer : TerrainLayer(), SWGParser {
|
||||
@@ -66,13 +69,44 @@ class TerrainListLayer : TerrainLayer(), SWGParser {
|
||||
}
|
||||
|
||||
override fun write(): IffForm {
|
||||
TODO("Not yet implemented")
|
||||
val data = IffChunk("ADTA")
|
||||
data.writeInt(if (invertBoundaries) 1 else 0)
|
||||
data.writeInt(if (invertFilters) 1 else 0)
|
||||
data.writeInt(if (expanded) 1 else 0)
|
||||
data.writeString(notes)
|
||||
val childForms = ArrayList<IffNode>()
|
||||
childForms.add(super.writeHeaderChunk())
|
||||
childForms.add(data)
|
||||
for (child in boundaries)
|
||||
childForms.add(child.write())
|
||||
for (child in filters)
|
||||
childForms.add(child.write())
|
||||
for (child in heights)
|
||||
childForms.add(child.write())
|
||||
for (child in children)
|
||||
childForms.add(child.write())
|
||||
|
||||
return IffForm.of("LAYR", 3, childForms)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "TerrainListLayer[boundaries=${boundaries.size} filters=0 heights=${heights.size} children=${children.size}]"
|
||||
}
|
||||
|
||||
fun isBitmapReferenced(bitmapId: Int): Boolean {
|
||||
for (child in filters) {
|
||||
if (child is FilterBitmap && child.bitmapId == bitmapId)
|
||||
return true
|
||||
}
|
||||
|
||||
for (child in children) {
|
||||
if (child.isBitmapReferenced(bitmapId))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun addLayer(layer: TerrainLayer) {
|
||||
if (!layer.isEnabled)
|
||||
return
|
||||
|
||||
@@ -45,8 +45,8 @@ class TerrainTemplate : SWGParser {
|
||||
var farRadialSeed = 0
|
||||
var legacyMap = true
|
||||
|
||||
private val fractalGroup = FractalGroup()
|
||||
private val bitmapGroup = BitmapGroup()
|
||||
val fractalGroup = FractalGroup()
|
||||
val bitmapGroup = BitmapGroup()
|
||||
private val lookupInformation = TerrainInfoLookup(fractalGroup.fractals, bitmapGroup.bitmaps)
|
||||
private val topTerrainLayer = TerrainListLayer()
|
||||
|
||||
@@ -158,11 +158,24 @@ class TerrainTemplate : SWGParser {
|
||||
|
||||
val version = if (legacyMap) 14 else 15
|
||||
|
||||
val terrainGeneratorForm = IffForm.of("TGEN", 0)
|
||||
val children = ArrayList<IffForm>()
|
||||
children.add(fractalGroup.write())
|
||||
children.add(bitmapGroup.write())
|
||||
val layerChildren = ArrayList<IffForm>();
|
||||
for (child in topTerrainLayer.children) {
|
||||
layerChildren.add(child.write())
|
||||
}
|
||||
children.add(IffForm.of("LYRS", layerChildren));
|
||||
|
||||
val terrainGeneratorForm = IffForm.of("TGEN", 0, children)
|
||||
|
||||
return IffForm.of("PTAT", version, data, terrainGeneratorForm)
|
||||
}
|
||||
|
||||
fun isBitmapReferenced(bitmapId: Int): Boolean {
|
||||
return topTerrainLayer.isBitmapReferenced(bitmapId)
|
||||
}
|
||||
|
||||
fun getHeight(x: Float, z: Float): TerrainInformation {
|
||||
val waterHeight = getWaterHeight(x, z)
|
||||
val terrainHeight = getTerrainHeight(x, z)
|
||||
|
||||
@@ -46,7 +46,7 @@ class AffectorHeightFractal : AffectorHeightLayer(), SWGParser {
|
||||
data.writeInt(transformType)
|
||||
data.writeFloat(height)
|
||||
|
||||
return IffForm.of("AHFR", 3, writeHeaderChunk(), IffForm.of("DATA", -1, data))
|
||||
return IffForm.of("AHFR", 3, writeHeaderChunk(), IffForm.of("DATA", data))
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
@@ -138,7 +138,19 @@ class AffectorHeightRoad : AffectorHeightLayer(), SWGParser {
|
||||
data.writeFloat(0f)
|
||||
data.writeInt(if (hasFixedHeights) 1 else 0)
|
||||
|
||||
return IffForm.of("AROA", 6, writeHeaderChunk(), data)
|
||||
val roadSegments = ArrayList<IffChunk>()
|
||||
for (segment in segments) {
|
||||
val segmentChunk = IffChunk("SGMT")
|
||||
for (point in segment) {
|
||||
segmentChunk.writeFloat(point.x.toFloat())
|
||||
segmentChunk.writeFloat(point.y.toFloat())
|
||||
segmentChunk.writeFloat(point.z.toFloat())
|
||||
}
|
||||
roadSegments.add(segmentChunk)
|
||||
}
|
||||
val roadSegmentsForm = IffForm.of("ROAD", 1, roadSegments)
|
||||
|
||||
return IffForm.of("AROA", 6, writeHeaderChunk(), IffForm.of("DATA", roadSegmentsForm, data))
|
||||
}
|
||||
|
||||
private fun readRoadSegments(form: IffForm) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.projectswg.common.data.swgiff.parsers.terrain.bitmap
|
||||
|
||||
import com.projectswg.common.data.swgiff.parsers.SWGParser
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
@@ -24,7 +25,7 @@ class TargaBitmap {
|
||||
|
||||
fun readFile(filePath: String) {
|
||||
fileName = filePath
|
||||
FileInputStream("$BASE_PATH/$filePath").use { inputStream ->
|
||||
FileInputStream("${SWGParser.getBasePath()}/$filePath").use { inputStream ->
|
||||
idLength = inputStream.readByte()
|
||||
colorMapType = inputStream.readByte()
|
||||
dataTypeCode = inputStream.readByte()
|
||||
@@ -84,8 +85,6 @@ class TargaBitmap {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val BASE_PATH = "clientdata"
|
||||
|
||||
private fun read4ByteColorPixel(src: InputStream, dst: ByteArray, indexIn: Int) {
|
||||
var index: Int = indexIn
|
||||
dst[index++] = src.read().toByte()
|
||||
|
||||
@@ -100,7 +100,7 @@ class BoundaryRectangle : BoundaryLayer() {
|
||||
data.writeString(shaderName)
|
||||
data.writeInt(waterType)
|
||||
|
||||
return IffForm.of("BREC", 0, writeHeaderChunk(), data)
|
||||
return IffForm.of("BREC", 4, writeHeaderChunk(), data)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.projectswg.common.data.swgiff.parsers.terrain.bitmap.TargaBitmap
|
||||
|
||||
class FilterBitmap : FilterLayer() {
|
||||
|
||||
private var bitmapId = 0
|
||||
var bitmapId = 0
|
||||
private var min = 0f
|
||||
private var max = 0f
|
||||
private var gain = 0f
|
||||
|
||||
@@ -114,6 +114,7 @@ class FractalFamily : SWGParser {
|
||||
|
||||
override fun write(): IffForm {
|
||||
val data = IffChunk("DATA")
|
||||
data.writeInt(seed)
|
||||
data.writeInt(if (useBias) 1 else 0)
|
||||
data.writeFloat(bias)
|
||||
data.writeInt(if (useGain) 1 else 0)
|
||||
|
||||
@@ -40,7 +40,7 @@ class FractalGroup : SWGParser {
|
||||
familyData.writeInt(family.fractalId)
|
||||
familyData.writeString(family.fractalLabel)
|
||||
|
||||
families.add(IffForm.of("MFAM", family.write()))
|
||||
families.add(IffForm.of("MFAM", familyData, family.write()))
|
||||
}
|
||||
|
||||
return IffForm.of("MGRP", 0, families)
|
||||
|
||||
@@ -15,7 +15,7 @@ class TestTerrainEngine {
|
||||
|
||||
companion object {
|
||||
private fun createEngine(fileName: String): TerrainTemplate? {
|
||||
return SWGParser.parse(File("clientdata/terrain/$fileName"))
|
||||
return SWGParser.parse("terrain/$fileName")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user