Fixed iff form writing and fixed serialization of terrain files

This commit is contained in:
Obique PSWG
2022-07-25 11:33:32 -05:00
parent 2757628832
commit 8cc675f8c2
15 changed files with 213 additions and 69 deletions

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")
}
}