Skip to content

Commit

Permalink
First attempt at container support (x positioning is still wrong)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremypenner committed Jan 21, 2024
1 parent 78299b8 commit 43920bf
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 49 deletions.
26 changes: 22 additions & 4 deletions inspector/codec.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ const encodeWalkto = ({ fromSide, offset }) => {
return encodeSide(fromSide) | (offset & 0xfc)
}

const decodeAnimations = (data, startEndTableOff, firstCelOff, stateCount) => {
const decodeAnimations = (data, startEndTableOff, nextBlockOff, stateCount) => {
const animations = []
// The prop structure also does not encode a count for how many frames there are, so we simply
// stop parsing once we find one that doesn't make sense.
Expand All @@ -373,7 +373,7 @@ const decodeAnimations = (data, startEndTableOff, firstCelOff, stateCount) => {
// heuristic rather than failing to parse any animation data.
// It's possible for there to be no frames, which is represented by an offset of 0 (no_animation)
if (startEndTableOff != 0) {
for (let frameOff = startEndTableOff; (startEndTableOff > firstCelOff) || (frameOff < firstCelOff); frameOff += 2) {
for (let frameOff = startEndTableOff; (startEndTableOff > nextBlockOff) || (frameOff < nextBlockOff); frameOff += 2) {
// each animation is two bytes: the starting state, and the ending state
// the first byte can have its high bit set to indicate that the animation should cycle
const cycle = (data.getUint8(frameOff) & 0x80) != 0
Expand All @@ -388,12 +388,28 @@ const decodeAnimations = (data, startEndTableOff, firstCelOff, stateCount) => {
return animations
}

const decodeContentsXY = (data, off, nextBlockOff) => {
const offsets = []
// The prop structure doesn't encode the capacity of an open container - the only way to know
// how big this block is without digging into other files is to use the offset of the first cel as
// a boundary, as, in practice, this should be true of all existing props. (An object's capacity is
// defined in beta.mud, but that's per-object, not per-image. Building that association would be more
// complex than is needed here.)
// Non-containers and closed containers will have 0 here (no_cont).
if (off != 0) {
for (let xyOff = off; xyOff < nextBlockOff; xyOff += 2) {
offsets.push({ x: data.getInt8(xyOff), y: data.getInt8(xyOff + 1) })
}
}
return offsets
}

export const decodeProp = (data) => {
const prop = {
data: data,
howHeld: decodeHowHeld(data.getUint8(0)),
colorBitmask: data.getUint8(1),
containerXYOff: data.getUint8(3), // TODO: parse this when nonzero
contentsInFront: (data.getUint8(3) & 0x80) == 0,
walkto: { left: decodeWalkto(data.getUint8(4)), right: decodeWalkto(data.getUint8(5)), yoff: data.getInt8(6) },
celmasks: [],
cels: []
Expand Down Expand Up @@ -426,7 +442,9 @@ export const decodeProp = (data) => {
prop.cels.push(decodeCel(new DataView(data.buffer, celOff), (prop.colorBitmask & celbit) != 0))
allCelsMask = (allCelsMask << 1) & 0xff
}
prop.animations = decodeAnimations(data, graphicStateOff, firstCelOff, stateCount)
const contentsXYOff = data.getUint8(3) & 0x7f
prop.animations = decodeAnimations(data, graphicStateOff, contentsXYOff == 0 ? firstCelOff : contentsXYOff, stateCount)
prop.contentsXY = decodeContentsXY(data, contentsXYOff, firstCelOff)
return prop
}

Expand Down
11 changes: 9 additions & 2 deletions inspector/navigate.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import { html } from "./view.js"
import { h, render } from "preact"
import { useState, useId, useCallback, useMemo } from "preact/hooks"
import { regionView, regionSearch, itemView } from "./region.js"
import { regionView, regionSearch, itemView, standaloneItemView } from "./region.js"
import { contextMap, useJson, useHabitatJson } from "./data.js"
import { direction } from "./view.js"
import { Scale } from "./render.js"
Expand All @@ -40,7 +40,14 @@
if (filename) {
const obj = useHabitatJson(filename).find(o => o.ref == dir)
if (obj) {
return html`<span>through <div style="display: inline-block"><${Scale.provider} value="1"><${itemView} object=${obj} standalone="true"/><//></div></span>`
return html`
<span>through
<div style="display: inline-block">
<${Scale.provider} value="1">
<${itemView} object=${obj} viewer=${standaloneItemView}/>
<//>
</div>
</span>`
}
}
}
Expand Down
135 changes: 93 additions & 42 deletions inspector/region.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,12 @@ export const propFromMod = (mod, ref) => {
}
return fnAugment ? useTrap(ref, image.filename, fnAugment) : useBinary(image.filename, decodeProp, null)
}
const propLocationFromMod = (prop, mod) => {
const x = (mod.x > 208 ? signedByte(mod.x) : mod.x) / 4
const y = mod.y % 128
const zIndex = mod.y > 127 ? (128 + (256 - mod.y)) : mod.y
return [prop.isTrap ? 0 : x, y, zIndex]

const propLocationFromObjectXY = (prop, modX, modY) => {
const x = (modX > 208 ? signedByte(modX) : modX) / 4
const y = modY % 128
const zIndex = (modY > 127 ? (128 + (256 - modY)) : modY) * 2
return [prop.isTrap ? 0 : x, y, prop.contentsInFront ? zIndex : zIndex + 1]
}

const colorsFromMod = (mod) => {
Expand All @@ -158,60 +159,109 @@ const colorsFromMod = (mod) => {
return colors
}

const unwrappedItemView = ({ object, standalone }) => {
const scale = useContext(Scale)
const mod = object.mods[0]
const prop = propFromMod(mod, object.ref)
if (!prop) {
return null
}
const flipHorizontal = ((mod.orientation ?? 0) & 0x01) != 0
const grState = mod.gr_state ?? 0
const regionSpace = { minX: 0, minY: 0, maxX: 160 / 4, maxY: 127 }
const propFramesFromMod = (prop, mod) => {
const colors = colorsFromMod(mod)
const frames = useMemo(() => {
const flipHorizontal = ((mod.orientation ?? 0) & 0x01) != 0
const grState = mod.gr_state ?? 0
if (prop.animations.length > 0) {
return framesFromPropAnimation(prop.animations[grState], prop, { colors, flipHorizontal })
} else {
return [frameFromCels(celsFromMask(prop, prop.celmasks[grState]), { colors, flipHorizontal })]
}
}, [prop, mod, colors.charset])

const [propX, propY, propZ] = propLocationFromMod(prop, mod)
const objectSpace = translateSpace(compositeSpaces(frames), propX, propY)
const [x, y] = topLeftCanvasOffset(regionSpace, objectSpace)
const style = !standalone ? `position: absolute; left: ${x * scale}px; top: ${y * scale}px; z-index: ${propZ}` : ""
const image = html`<${animatedDiv} frames=${frames}/>`
const connection = mod.connection && contextMap()[mod.connection]
return html`
<div id=${object.ref} style=${style}>
${connection ? html`<a href="region.html?f=${connection.filename}">${image}</a>` : image}
</div>`
return frames
}

export const itemView = (props) => {
return html`
<${catcher} filename=${props.object.ref}>
<${unwrappedItemView} ...${props}/>
<${props.viewer} ...${props}/>
<//>`
}

export const standaloneItemView = ({ object }) => {
const mod = object.mods[0]
const prop = propFromMod(mod, object.ref)
if (!prop) {
return null
}
return html`<${animatedDiv} frames=${propFramesFromMod(prop, mod)}/>`
}

export const positionedInRegion = ({ space, z, children }) => {
const scale = useContext(Scale)
const regionSpace = { minX: 0, minY: 0, maxX: 160 / 4, maxY: 127 }
const [x, y] = topLeftCanvasOffset(regionSpace, space)
const style =`position: absolute; left: ${x * scale}px; top: ${y * scale}px; z-index: ${z}`
return html`<div style=${style}>${children}</div>`
}

export const containedItemView = ({ object, containerProp, containerMod, containerSpace }) => {
const mod = object.mods[0]
const prop = propFromMod(mod, object.ref)
if (!prop || containerProp.contentsXY.length < mod.y) {
return null
}
const [containerX, containerY, containerZ] = propLocationFromObjectXY(containerProp, containerMod.x, containerMod.y)
const { x: offsetX, y: offsetY } = containerProp.contentsXY[mod.y]
const flipHorizontal = ((mod.orientation ?? 0) & 0x01) != 0
// offsets are relative to `cel_x_origin` / `cel_y_origin`, which is in "habitat space" but with
// the y axis inverted (see render.m:115-121)
const frames = propFramesFromMod(prop, mod)
const frameSpace = compositeSpaces(frames)
// if the contents are drawn in front, the container has its origin offset by the offset of its first cel.
const originX = containerProp.contentsInFront ? containerSpace.xOrigin + 2 : 0
const originY = containerProp.contentsInFront ? containerSpace.yOrigin : 0
const x = containerX + (flipHorizontal ? -originX + offsetX : originX - offsetX)
const y = containerY - (offsetY + originY)
const z = containerProp.contentsInFront ? containerZ + 1 : containerZ - 1
const objectSpace = translateSpace(frameSpace, x, y)
return html`
<${positionedInRegion} space=${objectSpace} z=${z}>
<${animatedDiv} frames=${frames}/>
<//>`
}

export const itemInteraction = ({ mod, children }) => {
const connection = mod.connection && contextMap()[mod.connection]
if (connection) {
return html`<a href="region.html?f=${connection.filename}">${children}</a>`
}
return children
}

export const regionItemView = ({ object, contents = [] }) => {
const mod = object.mods[0]
const prop = propFromMod(mod, object.ref)
if (!prop) {
return null
}
const [propX, propY, propZ] = propLocationFromObjectXY(prop, mod.x, mod.y)
const frames = propFramesFromMod(prop, mod)
const objectSpace = translateSpace(compositeSpaces(frames), propX, propY)
const result = [html`
<${positionedInRegion} key=${object.ref} space=${objectSpace} z=${propZ}>
<${itemInteraction} mod=${mod}>
<${animatedDiv} frames=${frames}/>
<//>
</div>`]
if (prop.contentsXY.length > 0) {
for (const item of contents) {
result.push(html`<${containedItemView} key=${item.ref} object=${item} containerProp=${prop} containerMod=${mod} containerSpace=${frames[0]}/>`)
}
}
return result
}

export const regionView = ({ filename }) => {
const scale = useContext(Scale)
const objects = useHabitatJson(filename, [])
let regionRef = null
const items = objects.flatMap(obj => {
if (obj.type == "context") {
regionRef = obj.ref
} else if (obj.type != "item") {
logError(`Unknown object type ${obj.type}`, obj.ref)
} else if (regionRef && obj.in != regionRef) {
logError(`Object is inside container ${obj.in}; not yet supported`, obj.ref)
} else {
return [html`<${itemView} object=${obj}/>`]
}
return []
})
const regionRef = objects.find(o => o.type === "context")?.ref
const items = objects
.filter(obj => obj.type === "item" && obj.in === regionRef)
.map(obj => html`<${itemView} viewer=${regionItemView} object=${obj} contents=${objects.filter(o => o.in === obj.ref)} key=${obj.ref}/>`)

return html`
<div style="position: relative; line-height: 0px; width: ${320 * scale}px; height: ${128 * scale}px; overflow: hidden">
${items}
Expand Down Expand Up @@ -279,8 +329,9 @@ export const objectDetails = ({ filename }) => {
}
}
details = html`
<a href="detail.html?f=${image.filename}">${image.filename}</a>
<${itemView} object=${obj} standalone="true"/>`
<a href="detail.html?f=${image.filename}">
${image.filename}<br/><${itemView} object=${obj} viewer=${standaloneItemView}/>
</a>`
}
}
return html`
Expand Down
6 changes: 5 additions & 1 deletion inspector/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ export const frameFromText = (x, y, bytes, charset, pattern, fineXOffset, colors
return compositeLayers(layers)
}

// Habitat's coordinate space consistently has y=0 for the bottom, and increasing y means going up
// We try to consistently model Habitat's coordinate space in our rendering code as y=0 for the bottom, with increasing y meaning going up.
// However, the graphics code converts this internally to a coordinate space where increasing y means going down, and many internal
// coordinates (cel offsets, etc.) assume this.
export const frameFromCels = (cels, { colors: celColors, paintOrder, firstCelOrigin = true, flipHorizontal }) => {
if (cels.length == 0) {
return null
Expand Down Expand Up @@ -319,6 +321,8 @@ export const frameFromCels = (cels, { colors: celColors, paintOrder, firstCelOri
frame.minX = -maxX + 1
frame.maxX = -minX + 1
}
frame.xOrigin = xCorrect
frame.yOrigin = yCorrect
return translateSpace(frame, -xCorrect, -yCorrect)
}

Expand Down

0 comments on commit 43920bf

Please sign in to comment.