Adding modding folder with a hacked build.
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
var gridVertexShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec3 vertexPosition;
|
||||
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
out vec4 gridColor;
|
||||
out vec3 originalPosition;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projectionMatrix * viewMatrix * vec4(vertexPosition, 1.0);
|
||||
|
||||
gridColor = vec4(0.0, 0.1, 0.0, 0.6);
|
||||
originalPosition = vertexPosition;
|
||||
}
|
||||
`
|
||||
|
||||
var gridFragmentShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
uniform vec4 gridSize;
|
||||
|
||||
in vec4 gridColor;
|
||||
in vec3 originalPosition;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
float modulo(float x, float y) {
|
||||
return x - y * floor(x/y);
|
||||
}
|
||||
|
||||
float nearGrid(float stepSize, float value) {
|
||||
float remainder = modulo(value - (stepSize / 2.0), stepSize) * 2.0;
|
||||
|
||||
if (remainder >= stepSize) {
|
||||
remainder = (stepSize * 2.0) - remainder;
|
||||
}
|
||||
|
||||
return remainder / stepSize;
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
float alphaX = nearGrid(256.0, originalPosition.x);
|
||||
float alphaY = nearGrid(256.0, originalPosition.y);
|
||||
bool beyondX = (originalPosition.x / 256.0) >= gridSize.x || (originalPosition.x < 0.0);
|
||||
bool beyondY = (originalPosition.y / 256.0) >= gridSize.y || (originalPosition.y < 0.0);
|
||||
float alpha = 0.0;
|
||||
|
||||
if (!beyondX && !beyondY) {
|
||||
alpha = max(alphaX, alphaY);
|
||||
} else if (beyondX && !beyondY) {
|
||||
alpha = alphaX;
|
||||
} else if (beyondY && !beyondX) {
|
||||
alpha = alphaY;
|
||||
} else {
|
||||
alpha = min(alphaX, alphaY);
|
||||
}
|
||||
|
||||
alpha = pow(2.0, 10.0 * (alpha - 1.0));
|
||||
|
||||
fragColor = vec4(gridColor.rgb, gridColor.a * alpha);
|
||||
}
|
||||
`
|
||||
|
||||
// BackgroundGrid renders a grid with transparent holes.
|
||||
type BackgroundGrid struct {
|
||||
context *render.Context
|
||||
|
||||
program uint32
|
||||
vao *opengl.VertexArrayObject
|
||||
vertexPositionBuffer uint32
|
||||
vertexPositionAttrib int32
|
||||
gridSizeUniform opengl.Vector4Uniform
|
||||
viewMatrixUniform opengl.Matrix4Uniform
|
||||
projectionMatrixUniform opengl.Matrix4Uniform
|
||||
|
||||
lastReportedColumns int
|
||||
lastReportedRows int
|
||||
}
|
||||
|
||||
// NewBackgroundGrid returns a new instance of BackgroundGrid.
|
||||
func NewBackgroundGrid(context *render.Context) *BackgroundGrid {
|
||||
gl := context.OpenGL
|
||||
program, programErr := opengl.LinkNewStandardProgram(gl, gridVertexShaderSource, gridFragmentShaderSource)
|
||||
if programErr != nil {
|
||||
panic(opengl.NamedShaderError{Name: "BackgroundGridShader", Nested: programErr})
|
||||
}
|
||||
grid := &BackgroundGrid{
|
||||
context: context,
|
||||
program: program,
|
||||
vao: opengl.NewVertexArrayObject(gl, program),
|
||||
vertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
|
||||
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
|
||||
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
|
||||
gridSizeUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "gridSize")),
|
||||
}
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
// Render renders the grid.
|
||||
func (grid *BackgroundGrid) Render(columns, rows int) {
|
||||
gl := grid.context.OpenGL
|
||||
|
||||
grid.setGridSize(columns, rows)
|
||||
|
||||
grid.vao.OnShader(func() {
|
||||
grid.viewMatrixUniform.Set(gl, grid.context.ViewMatrix)
|
||||
grid.projectionMatrixUniform.Set(gl, &grid.context.ProjectionMatrix)
|
||||
grid.gridSizeUniform.Set(gl, &[4]float32{float32(columns), float32(rows), 0, 0})
|
||||
|
||||
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
|
||||
})
|
||||
}
|
||||
|
||||
func (grid *BackgroundGrid) setGridSize(columns, rows int) {
|
||||
if (grid.lastReportedColumns == columns) && (grid.lastReportedRows == rows) {
|
||||
return
|
||||
}
|
||||
grid.lastReportedColumns = columns
|
||||
grid.lastReportedRows = rows
|
||||
|
||||
gl := grid.context.OpenGL
|
||||
{
|
||||
hTiles := float32(columns)
|
||||
vTiles := float32(rows)
|
||||
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
|
||||
fineHalf := level.FineCoordinatesPerTileSide / float32(2.0)
|
||||
hLimit := level.FineCoordinatesPerTileSide*hTiles + fineHalf
|
||||
vLimit := level.FineCoordinatesPerTileSide*vTiles + fineHalf
|
||||
var vertices = []float32{
|
||||
-fineHalf, -fineHalf, 0.0,
|
||||
hLimit, -fineHalf, 0.0,
|
||||
hLimit, vLimit, 0.0,
|
||||
|
||||
hLimit, vLimit, 0.0,
|
||||
-fineHalf, vLimit, 0.0,
|
||||
-fineHalf, -fineHalf, 0.0}
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
grid.vao.WithSetter(func(gl opengl.OpenGL) {
|
||||
gl.EnableVertexAttribArray(uint32(grid.vertexPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(grid.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ColorDisplay is an enumeration to how a tile should be colored.
|
||||
type ColorDisplay int
|
||||
|
||||
// ColorDisplay constants are listed below.
|
||||
const (
|
||||
ColorDisplayNone ColorDisplay = 0
|
||||
ColorDisplayFloor ColorDisplay = 1
|
||||
ColorDisplayCeiling ColorDisplay = 2
|
||||
)
|
||||
|
||||
// String returns a textual representation.
|
||||
func (display ColorDisplay) String() string {
|
||||
switch display {
|
||||
case ColorDisplayNone:
|
||||
return "None"
|
||||
case ColorDisplayFloor:
|
||||
return "Floor"
|
||||
case ColorDisplayCeiling:
|
||||
return "Ceiling"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown%d", int(display))
|
||||
}
|
||||
}
|
||||
|
||||
// ColorDisplays returns all ColorDisplay constants.
|
||||
func ColorDisplays() []ColorDisplay {
|
||||
return []ColorDisplay{ColorDisplayNone, ColorDisplayFloor, ColorDisplayCeiling}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package levels
|
||||
|
||||
const (
|
||||
hintUnknown = "???"
|
||||
hintReadOnly = " (read-only)"
|
||||
)
|
||||
@@ -0,0 +1,409 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v3"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/graphics"
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ss1/content/text"
|
||||
"github.com/inkyblackness/hacked/ss1/edit"
|
||||
"github.com/inkyblackness/hacked/ss1/edit/undoable/cmd"
|
||||
"github.com/inkyblackness/hacked/ss1/resource"
|
||||
"github.com/inkyblackness/hacked/ss1/world"
|
||||
"github.com/inkyblackness/hacked/ss1/world/ids"
|
||||
"github.com/inkyblackness/hacked/ui/gui"
|
||||
)
|
||||
|
||||
// ControlView is the core view for level editing.
|
||||
type ControlView struct {
|
||||
levels *edit.EditableLevels
|
||||
levelSelection *edit.LevelSelectionService
|
||||
editor *edit.LevelEditorService
|
||||
|
||||
guiScale float32
|
||||
registry cmd.Registry
|
||||
|
||||
textCache *text.Cache
|
||||
textureCache *graphics.TextureCache
|
||||
|
||||
model controlViewModel
|
||||
}
|
||||
|
||||
// NewControlView returns a new instance.
|
||||
func NewControlView(levels *edit.EditableLevels, levelSelection *edit.LevelSelectionService, editor *edit.LevelEditorService,
|
||||
guiScale float32, textCache *text.Cache, textureCache *graphics.TextureCache,
|
||||
registry cmd.Registry) *ControlView {
|
||||
view := &ControlView{
|
||||
levels: levels,
|
||||
levelSelection: levelSelection,
|
||||
editor: editor,
|
||||
|
||||
guiScale: guiScale,
|
||||
registry: registry,
|
||||
textCache: textCache,
|
||||
textureCache: textureCache,
|
||||
model: freshControlViewModel(),
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
// WindowOpen returns the flag address, to be used with the main menu.
|
||||
func (view *ControlView) WindowOpen() *bool {
|
||||
return &view.model.windowOpen
|
||||
}
|
||||
|
||||
// Render renders the view.
|
||||
func (view *ControlView) Render() {
|
||||
if view.model.restoreFocus {
|
||||
imgui.SetNextWindowFocus()
|
||||
view.model.restoreFocus = false
|
||||
view.model.windowOpen = true
|
||||
}
|
||||
if view.model.windowOpen {
|
||||
imgui.SetNextWindowSizeV(imgui.Vec2{X: 400 * view.guiScale, Y: 300 * view.guiScale}, imgui.ConditionFirstUseEver)
|
||||
title := "Level Control"
|
||||
readOnly := view.editor.IsReadOnly()
|
||||
if readOnly {
|
||||
title += hintReadOnly
|
||||
}
|
||||
if imgui.BeginV(title+"###Level Control", view.WindowOpen(), imgui.WindowFlagsNoCollapse) {
|
||||
view.renderContent(view.editor.Level(), readOnly)
|
||||
}
|
||||
imgui.End()
|
||||
}
|
||||
}
|
||||
|
||||
var levelHeights = []string{
|
||||
"32 Tiles",
|
||||
"16 Tiles",
|
||||
"8 Tiles",
|
||||
"4 Tiles",
|
||||
"2 Tiles",
|
||||
"1 Tile",
|
||||
"1/2 Tile",
|
||||
"1/4 Tile",
|
||||
}
|
||||
|
||||
func (view *ControlView) renderContent(lvl *level.Level, readOnly bool) {
|
||||
imgui.PushItemWidth(-200 * view.guiScale)
|
||||
selectedLevel := view.levelSelection.CurrentLevelID()
|
||||
if gui.StepSliderInt("Active Level", &selectedLevel, 0, archive.MaxLevels-1) {
|
||||
view.levelSelection.SetCurrentLevelID(selectedLevel)
|
||||
}
|
||||
imgui.Separator()
|
||||
levelType := "Real World"
|
||||
if lvl.IsCyberspace() {
|
||||
levelType = "Cyberspace"
|
||||
}
|
||||
imgui.LabelText("Type", levelType)
|
||||
view.renderLevelHeight(lvl, readOnly)
|
||||
|
||||
if !lvl.IsCyberspace() {
|
||||
view.renderTextureAtlas(lvl, readOnly)
|
||||
view.renderSurveillanceObjects(lvl, readOnly)
|
||||
view.renderHazards(lvl, readOnly)
|
||||
view.renderTextureAnimations(lvl, readOnly)
|
||||
}
|
||||
|
||||
imgui.PopItemWidth()
|
||||
}
|
||||
|
||||
func (view *ControlView) renderLevelHeight(lvl *level.Level, readOnly bool) {
|
||||
_, _, currentShift := lvl.Size()
|
||||
if readOnly {
|
||||
imgui.LabelText("Level Height", levelHeights[currentShift])
|
||||
} else if imgui.BeginCombo("Level Height", levelHeights[currentShift]) {
|
||||
for shift, height := range levelHeights {
|
||||
if imgui.SelectableV(height, shift == int(currentShift), 0, imgui.Vec2{}) {
|
||||
view.requestSetZShift(lvl, shift)
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
}
|
||||
|
||||
func (view *ControlView) textureName(index int) string {
|
||||
key := resource.KeyOf(ids.TextureNames, resource.LangDefault, index)
|
||||
name, err := view.textCache.Text(key)
|
||||
suffix := ""
|
||||
if err == nil {
|
||||
suffix = ": " + name
|
||||
}
|
||||
return fmt.Sprintf("%3d", index) + suffix
|
||||
}
|
||||
|
||||
func (view *ControlView) renderTextureAtlas(lvl *level.Level, readOnly bool) {
|
||||
imgui.Separator()
|
||||
|
||||
atlas := lvl.TextureAtlas()
|
||||
{
|
||||
render.TextureSelector("Level Textures", -200, view.guiScale,
|
||||
len(atlas), view.model.selectedAtlasIndex,
|
||||
view.textureCache,
|
||||
func(index int) resource.Key {
|
||||
return resource.KeyOf(ids.LargeTextures.Plus(int(atlas[index])), resource.LangAny, 0)
|
||||
},
|
||||
func(index int) string {
|
||||
textureType := "(F/C/W)"
|
||||
if index >= level.FloorCeilingTextureLimit {
|
||||
textureType = "(walls only)"
|
||||
}
|
||||
return fmt.Sprintf("Atlas Index %2d ", index) + textureType + "\n" + view.textureName(int(atlas[index]))
|
||||
}, func(newIndex int) {
|
||||
view.model.selectedAtlasIndex = newIndex
|
||||
})
|
||||
imgui.SameLine()
|
||||
imgui.Text("Level Textures")
|
||||
}
|
||||
gameTextureIndex := -1
|
||||
if (view.model.selectedAtlasIndex >= 0) && (view.model.selectedAtlasIndex < len(atlas)) {
|
||||
gameTextureIndex = int(atlas[view.model.selectedAtlasIndex])
|
||||
}
|
||||
if !readOnly {
|
||||
render.TextureSelector("Game Textures", -200, view.guiScale,
|
||||
world.MaxWorldTextures, gameTextureIndex,
|
||||
view.textureCache,
|
||||
func(index int) resource.Key {
|
||||
return resource.KeyOf(ids.LargeTextures.Plus(index), resource.LangAny, 0)
|
||||
},
|
||||
view.textureName,
|
||||
func(newIndex int) {
|
||||
view.requestSetLevelTexture(lvl, view.model.selectedAtlasIndex, newIndex)
|
||||
})
|
||||
imgui.SameLine()
|
||||
imgui.Text("Game Textures")
|
||||
}
|
||||
}
|
||||
|
||||
func (view *ControlView) renderSurveillanceObjects(lvl *level.Level, readOnly bool) {
|
||||
imgui.Separator()
|
||||
|
||||
if imgui.BeginCombo("Surveillance Object", fmt.Sprintf("Object %d", view.model.selectedSurveillanceObjectIndex)) {
|
||||
for i := 0; i < level.SurveillanceObjectCount; i++ {
|
||||
if imgui.SelectableV(fmt.Sprintf("Object %d", i), i == view.model.selectedSurveillanceObjectIndex, 0, imgui.Vec2{}) {
|
||||
view.model.selectedSurveillanceObjectIndex = i
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
sources := lvl.SurveillanceSources()
|
||||
surrogates := lvl.SurveillanceSurrogates()
|
||||
capacity := lvl.ObjectCapacity()
|
||||
|
||||
view.renderSliderInt(readOnly, "Surveillance Source", int(sources[view.model.selectedSurveillanceObjectIndex]),
|
||||
func(int) string { return "%d" },
|
||||
0, capacity,
|
||||
func(newValue int) {
|
||||
view.requestSetSurveillanceSource(lvl, view.model.selectedSurveillanceObjectIndex, level.ObjectID(newValue))
|
||||
})
|
||||
view.renderSliderInt(readOnly, "Surveillance Surrogate", int(surrogates[view.model.selectedSurveillanceObjectIndex]),
|
||||
func(int) string { return "%d" },
|
||||
0, capacity,
|
||||
func(newValue int) {
|
||||
view.requestSetSurveillanceSurrogate(lvl, view.model.selectedSurveillanceObjectIndex, level.ObjectID(newValue))
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) renderHazards(lvl *level.Level, readOnly bool) {
|
||||
imgui.Separator()
|
||||
|
||||
parameters := lvl.Parameters()
|
||||
currentCeiling := currentCeilingHazard(parameters)
|
||||
if readOnly {
|
||||
imgui.LabelText("Ceiling Hazard", currentCeiling.title)
|
||||
} else if imgui.BeginCombo("Ceiling Hazard", currentCeiling.title) {
|
||||
for _, info := range ceilingHazards {
|
||||
if imgui.SelectableV(info.title, info.title == currentCeiling.title, 0, imgui.Vec2{}) {
|
||||
view.requestSetCeilingHazard(lvl, info)
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
view.renderSliderInt(readOnly, "Ceiling Hazard Level", int(parameters.CeilingHazardLevel),
|
||||
currentCeiling.formatter, 0, 255,
|
||||
func(newValue int) {
|
||||
view.requestSetCeilingHazardLevel(lvl, byte(newValue))
|
||||
})
|
||||
|
||||
currentFloor := currentFloorHazard(parameters)
|
||||
if readOnly {
|
||||
imgui.LabelText("Floor Hazard", currentFloor.title)
|
||||
} else if imgui.BeginCombo("Floor Hazard", currentFloor.title) {
|
||||
for _, info := range floorHazards {
|
||||
if imgui.SelectableV(info.title, info.title == currentFloor.title, 0, imgui.Vec2{}) {
|
||||
view.requestSetFloorHazard(lvl, info)
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
view.renderSliderInt(readOnly, "Floor Hazard Level", int(parameters.FloorHazardLevel),
|
||||
currentFloor.formatter, 0, 255,
|
||||
func(newValue int) {
|
||||
view.requestSetFloorHazardLevel(lvl, byte(newValue))
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) renderTextureAnimations(lvl *level.Level, readOnly bool) {
|
||||
imgui.Separator()
|
||||
|
||||
animations := lvl.TextureAnimations()
|
||||
selectedText := ""
|
||||
if (view.model.selectedTextureAnimationIndex >= 1) && (view.model.selectedTextureAnimationIndex < len(animations)) {
|
||||
selectedText = fmt.Sprintf("%d", view.model.selectedTextureAnimationIndex)
|
||||
}
|
||||
if imgui.BeginCombo("Texture Animation Group", selectedText) {
|
||||
for i := 1; i < len(animations); i++ {
|
||||
if imgui.SelectableV(fmt.Sprintf("%2d", i), view.model.selectedTextureAnimationIndex == i, 0, imgui.Vec2{}) {
|
||||
view.model.selectedTextureAnimationIndex = i
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
|
||||
if (view.model.selectedTextureAnimationIndex >= 1) && (view.model.selectedTextureAnimationIndex < len(animations)) {
|
||||
animation := animations[view.model.selectedTextureAnimationIndex]
|
||||
view.renderSliderInt(readOnly, "Texture Animation Time", int(animation.FrameTime),
|
||||
func(int) string { return "%d msec" },
|
||||
0, 1000,
|
||||
func(newValue int) {
|
||||
view.requestSetTextureAnimationTime(lvl, view.model.selectedTextureAnimationIndex, uint16(newValue))
|
||||
})
|
||||
view.renderSliderInt(readOnly, "Texture Animation Frame Count", int(animation.FrameCount),
|
||||
func(int) string { return "%d" },
|
||||
0, 10,
|
||||
func(newValue int) {
|
||||
view.requestSetTextureAnimationFrameCount(lvl, view.model.selectedTextureAnimationIndex, byte(newValue))
|
||||
})
|
||||
if readOnly {
|
||||
imgui.LabelText("Texture Animation Loop Type", animation.LoopType.String())
|
||||
} else if imgui.BeginCombo("Texture Animation Loop Type", animation.LoopType.String()) {
|
||||
loopTypes := level.TextureAnimationLoopTypes()
|
||||
for _, loopType := range loopTypes {
|
||||
if imgui.SelectableV(loopType.String(), loopType == animation.LoopType, 0, imgui.Vec2{}) {
|
||||
view.requestSetTextureAnimationType(lvl, view.model.selectedTextureAnimationIndex, loopType)
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (view *ControlView) renderSliderInt(readOnly bool, label string, selectedValue int,
|
||||
formatter func(int) string, min, max int, changeHandler func(int)) {
|
||||
selectedString := formatter(selectedValue)
|
||||
labelValue := fmt.Sprintf(selectedString, selectedValue)
|
||||
if readOnly {
|
||||
imgui.LabelText(label, labelValue)
|
||||
} else if gui.StepSliderIntV(label, &selectedValue, min, max, selectedString) {
|
||||
changeHandler(selectedValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetZShift(lvl *level.Level, newValue int) {
|
||||
lvl.SetHeightShift(level.HeightShift(newValue))
|
||||
view.patchLevelResources(lvl, func() {})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetLevelTexture(lvl *level.Level, atlasIndex, worldTextureIndex int) {
|
||||
lvl.SetTextureAtlasEntry(atlasIndex, level.TextureIndex(worldTextureIndex))
|
||||
view.patchLevelResources(lvl, func() {
|
||||
view.model.selectedAtlasIndex = atlasIndex
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetSurveillanceSource(lvl *level.Level, objectIndex int, objectID level.ObjectID) {
|
||||
lvl.SetSurveillanceSource(objectIndex, objectID)
|
||||
view.patchLevelResources(lvl, func() {
|
||||
view.model.selectedSurveillanceObjectIndex = objectIndex
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetSurveillanceSurrogate(lvl *level.Level, objectIndex int, objectID level.ObjectID) {
|
||||
lvl.SetSurveillanceSurrogate(objectIndex, objectID)
|
||||
view.patchLevelResources(lvl, func() {
|
||||
view.model.selectedSurveillanceObjectIndex = objectIndex
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetCeilingHazard(lvl *level.Level, info ceilingHazardInfo) {
|
||||
parameters := lvl.Parameters()
|
||||
parameters.RadiationRegister = 0
|
||||
if info.radiationRegister {
|
||||
parameters.RadiationRegister = 2
|
||||
}
|
||||
view.patchLevelResources(lvl, func() {})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetCeilingHazardLevel(lvl *level.Level, value byte) {
|
||||
lvl.Parameters().CeilingHazardLevel = value
|
||||
view.patchLevelResources(lvl, func() {})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetFloorHazard(lvl *level.Level, info floorHazardInfo) {
|
||||
parameters := lvl.Parameters()
|
||||
parameters.BiohazardRegister = 0
|
||||
parameters.FloorHazardIsGravity = 0
|
||||
if info.isGravity {
|
||||
parameters.FloorHazardIsGravity = 1
|
||||
} else if info.biohazardRegister {
|
||||
parameters.BiohazardRegister = 2
|
||||
}
|
||||
view.patchLevelResources(lvl, func() {})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetFloorHazardLevel(lvl *level.Level, value byte) {
|
||||
lvl.Parameters().FloorHazardLevel = value
|
||||
view.patchLevelResources(lvl, func() {})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetTextureAnimationTime(lvl *level.Level, index int, value uint16) {
|
||||
lvl.TextureAnimations()[index].FrameTime = value
|
||||
view.patchLevelResources(lvl, func() {
|
||||
view.model.selectedTextureAnimationIndex = index
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetTextureAnimationFrameCount(lvl *level.Level, index int, value byte) {
|
||||
lvl.TextureAnimations()[index].FrameCount = value
|
||||
view.patchLevelResources(lvl, func() {
|
||||
view.model.selectedTextureAnimationIndex = index
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) requestSetTextureAnimationType(lvl *level.Level, index int, value level.TextureAnimationLoopType) {
|
||||
lvl.TextureAnimations()[index].LoopType = value
|
||||
view.patchLevelResources(lvl, func() {
|
||||
view.model.selectedTextureAnimationIndex = index
|
||||
})
|
||||
}
|
||||
|
||||
func (view *ControlView) patchLevelResources(lvl *level.Level, extraRestoreState func()) {
|
||||
oldLevelID := view.levelSelection.CurrentLevelID()
|
||||
extraTask := func(world.Modder) error {
|
||||
extraRestoreState()
|
||||
return nil
|
||||
}
|
||||
|
||||
err := view.registry.Register(cmd.Named("PatchLevel"),
|
||||
cmd.Forward(view.restoreFocusTask()),
|
||||
cmd.Forward(view.levelSelection.SetCurrentLevelIDTask(lvl.ID())),
|
||||
cmd.Forward(extraTask),
|
||||
cmd.Nested(func() error { return view.levels.CommitLevelChanges(lvl.ID()) }),
|
||||
cmd.Reverse(extraTask),
|
||||
cmd.Reverse(view.levelSelection.SetCurrentLevelIDTask(oldLevelID)),
|
||||
cmd.Reverse(view.restoreFocusTask()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (view *ControlView) restoreFocusTask() cmd.Task {
|
||||
return func(modder world.Modder) error {
|
||||
view.model.restoreFocus = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package levels
|
||||
|
||||
type controlViewModel struct {
|
||||
selectedAtlasIndex int
|
||||
selectedSurveillanceObjectIndex int
|
||||
selectedTextureAnimationIndex int
|
||||
|
||||
restoreFocus bool
|
||||
windowOpen bool
|
||||
}
|
||||
|
||||
func freshControlViewModel() controlViewModel {
|
||||
return controlViewModel{
|
||||
selectedTextureAnimationIndex: 1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
)
|
||||
|
||||
func tileHeightFormatterFor(levelHeight level.HeightShift) func(int) string {
|
||||
return func(value int) string {
|
||||
tileHeight, err := levelHeight.ValueFromTileHeight(level.TileHeightUnit(value))
|
||||
tileHeightString := hintUnknown
|
||||
if err == nil {
|
||||
tileHeightString = fmt.Sprintf("%2.3f", tileHeight)
|
||||
}
|
||||
return tileHeightString + " tile(s) - raw: %d"
|
||||
}
|
||||
}
|
||||
|
||||
func objectHeightFormatterFor(levelHeight level.HeightShift) func(int) string {
|
||||
return func(value int) string {
|
||||
tileHeight, err := levelHeight.ValueFromObjectHeight(level.HeightUnit(value))
|
||||
tileHeightString := hintUnknown
|
||||
if err == nil {
|
||||
tileHeightString = fmt.Sprintf("%2.3f", tileHeight)
|
||||
}
|
||||
return tileHeightString + " tile(s) - raw: %d"
|
||||
}
|
||||
}
|
||||
|
||||
func moveTileHeightFormatterFor(levelHeight level.HeightShift) func(int) string {
|
||||
normalFormatter := tileHeightFormatterFor(levelHeight)
|
||||
return func(value int) string {
|
||||
if (value >= 0) && (value <= int(level.TileHeightUnitMax)) {
|
||||
return normalFormatter(value)
|
||||
}
|
||||
return "Don't change - raw: %d"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
var highlighterVertexShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec3 vertexPosition;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
|
||||
}
|
||||
`
|
||||
|
||||
var highlighterFragmentShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
uniform vec4 inColor;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main(void) {
|
||||
fragColor = inColor;
|
||||
}
|
||||
`
|
||||
|
||||
// Highlighter draws a simple highlighting of a rectangular area.
|
||||
type Highlighter struct {
|
||||
context *render.Context
|
||||
|
||||
program uint32
|
||||
vao *opengl.VertexArrayObject
|
||||
vertexPositionBuffer uint32
|
||||
vertexPositionAttrib int32
|
||||
modelMatrixUniform opengl.Matrix4Uniform
|
||||
viewMatrixUniform opengl.Matrix4Uniform
|
||||
projectionMatrixUniform opengl.Matrix4Uniform
|
||||
inColorUniform opengl.Vector4Uniform
|
||||
}
|
||||
|
||||
// NewHighlighter returns a new instance of Highlighter.
|
||||
func NewHighlighter(context *render.Context) *Highlighter {
|
||||
gl := context.OpenGL
|
||||
program, programErr := opengl.LinkNewStandardProgram(gl, highlighterVertexShaderSource, highlighterFragmentShaderSource)
|
||||
|
||||
if programErr != nil {
|
||||
panic(opengl.NamedShaderError{Name: "HighlighterShader", Nested: programErr})
|
||||
}
|
||||
highlighter := &Highlighter{
|
||||
context: context,
|
||||
program: program,
|
||||
|
||||
vao: opengl.NewVertexArrayObject(gl, program),
|
||||
vertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
|
||||
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
|
||||
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
|
||||
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
|
||||
inColorUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "inColor"))}
|
||||
|
||||
{
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, highlighter.vertexPositionBuffer)
|
||||
half := float32(0.5)
|
||||
var vertices = []float32{
|
||||
-half, -half, 0.0,
|
||||
half, -half, 0.0,
|
||||
half, half, 0.0,
|
||||
|
||||
half, half, 0.0,
|
||||
-half, half, 0.0,
|
||||
-half, -half, 0.0}
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
highlighter.vao.OnShader(func() {
|
||||
gl.EnableVertexAttribArray(uint32(highlighter.vertexPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, highlighter.vertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(highlighter.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
|
||||
return highlighter
|
||||
}
|
||||
|
||||
// Dispose releases all resources.
|
||||
func (highlighter *Highlighter) Dispose() {
|
||||
gl := highlighter.context.OpenGL
|
||||
|
||||
highlighter.vao.Dispose()
|
||||
gl.DeleteBuffers([]uint32{highlighter.vertexPositionBuffer})
|
||||
gl.DeleteShader(highlighter.program)
|
||||
}
|
||||
|
||||
// Render renders the highlights.
|
||||
func (highlighter *Highlighter) Render(positions []MapPosition, sideLength float32, color [4]float32) {
|
||||
gl := highlighter.context.OpenGL
|
||||
|
||||
highlighter.vao.OnShader(func() {
|
||||
highlighter.viewMatrixUniform.Set(gl, highlighter.context.ViewMatrix)
|
||||
highlighter.projectionMatrixUniform.Set(gl, &highlighter.context.ProjectionMatrix)
|
||||
highlighter.inColorUniform.Set(gl, &color)
|
||||
|
||||
for _, pos := range positions {
|
||||
modelMatrix := mgl.Ident4().
|
||||
Mul4(mgl.Translate3D(float32(pos.X), float32(pos.Y), 0.0)).
|
||||
Mul4(mgl.Scale3D(sideLength, sideLength, 1.0))
|
||||
|
||||
highlighter.modelMatrixUniform.Set(gl, &modelMatrix)
|
||||
|
||||
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
)
|
||||
|
||||
type hoverItem interface {
|
||||
Pos() MapPosition
|
||||
Size() float32
|
||||
IsIn(lvl *level.Level) bool
|
||||
}
|
||||
|
||||
type tileHoverItem struct {
|
||||
pos MapPosition
|
||||
}
|
||||
|
||||
func (item tileHoverItem) Pos() MapPosition {
|
||||
return item.pos
|
||||
}
|
||||
|
||||
func (item tileHoverItem) Size() float32 {
|
||||
return level.FineCoordinatesPerTileSide
|
||||
}
|
||||
|
||||
func (item tileHoverItem) IsIn(lvl *level.Level) bool {
|
||||
return lvl.Tile(item.pos.Tile()) != nil
|
||||
}
|
||||
|
||||
type objectHoverItem struct {
|
||||
id level.ObjectID
|
||||
pos MapPosition
|
||||
}
|
||||
|
||||
func (item objectHoverItem) Pos() MapPosition {
|
||||
return item.pos
|
||||
}
|
||||
|
||||
func (item objectHoverItem) Size() float32 {
|
||||
return level.FineCoordinatesPerTileSide / 4
|
||||
}
|
||||
|
||||
func (item objectHoverItem) IsIn(lvl *level.Level) bool {
|
||||
obj := lvl.Object(item.id)
|
||||
return (obj != nil) && (obj.InUse != 0) && (item.pos.X == obj.X) && (item.pos.Y == obj.Y)
|
||||
}
|
||||
|
||||
type hoverItems struct {
|
||||
available []hoverItem
|
||||
activeIndex int
|
||||
activeItem hoverItem
|
||||
}
|
||||
|
||||
func (items *hoverItems) reset() {
|
||||
items.available = nil
|
||||
items.activeIndex = 0
|
||||
items.activeItem = nil
|
||||
}
|
||||
|
||||
func (items *hoverItems) scroll(forward bool) {
|
||||
count := len(items.available)
|
||||
if count > 0 {
|
||||
diff := 1
|
||||
if !forward {
|
||||
diff = -1
|
||||
}
|
||||
items.activeIndex = (count + (items.activeIndex + diff)) % count
|
||||
items.activeItem = items.available[items.activeIndex]
|
||||
}
|
||||
}
|
||||
|
||||
func (items *hoverItems) find(lvl *level.Level, pos MapPosition) {
|
||||
refVec := mgl.Vec2{float32(pos.X), float32(pos.Y)}
|
||||
var distances []float32
|
||||
|
||||
items.available = nil
|
||||
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
|
||||
entryVec := mgl.Vec2{float32(entry.X), float32(entry.Y)}
|
||||
distance := refVec.Sub(entryVec).Len()
|
||||
if distance < level.FineCoordinatesPerTileSide/4 {
|
||||
items.available = append(items.available,
|
||||
objectHoverItem{
|
||||
id: id,
|
||||
pos: MapPosition{X: entry.X, Y: entry.Y},
|
||||
})
|
||||
distances = append(distances, distance)
|
||||
}
|
||||
})
|
||||
items.available = append(items.available,
|
||||
tileHoverItem{
|
||||
pos: MapPosition{
|
||||
X: level.CoordinateAt(pos.X.Tile(), level.FineCoordinatesPerTileSide/2),
|
||||
Y: level.CoordinateAt(pos.Y.Tile(), level.FineCoordinatesPerTileSide/2),
|
||||
},
|
||||
})
|
||||
distances = append(distances, level.FineCoordinatesPerTileSide)
|
||||
sort.Slice(items.available, func(a, b int) bool { return distances[a] < distances[b] })
|
||||
|
||||
items.activeIndex = 0
|
||||
items.activeItem = items.available[items.activeIndex]
|
||||
}
|
||||
|
||||
func (items *hoverItems) validate(lvl *level.Level) {
|
||||
allValid := true
|
||||
for _, item := range items.available {
|
||||
if !item.IsIn(lvl) {
|
||||
allValid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allValid {
|
||||
return
|
||||
}
|
||||
|
||||
newItems := make([]hoverItem, 0, len(items.available))
|
||||
for index, item := range items.available {
|
||||
isActive := index == items.activeIndex
|
||||
if item.IsIn(lvl) {
|
||||
if isActive {
|
||||
items.activeIndex = len(newItems)
|
||||
}
|
||||
newItems = append(newItems, item)
|
||||
} else if isActive {
|
||||
items.activeIndex = 0
|
||||
}
|
||||
}
|
||||
items.available = newItems
|
||||
items.activeItem = nil
|
||||
if items.activeIndex < len(items.available) {
|
||||
items.activeItem = items.available[items.activeIndex]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
)
|
||||
|
||||
// LimitedCamera is a camera implementation with ranged controls for zooming and moving.
|
||||
type LimitedCamera struct {
|
||||
viewportWidth, viewportHeight float32
|
||||
minZoom, maxZoom float32
|
||||
minPos, maxPos float32
|
||||
|
||||
requestedZoomLevel float32
|
||||
viewOffsetX, viewOffsetY float32
|
||||
|
||||
viewMatrix mgl.Mat4
|
||||
}
|
||||
|
||||
// NewLimitedCamera returns a new instance of a LimitedCamera.
|
||||
func NewLimitedCamera(minZoom, maxZoom float32, minPos, maxPos float32) *LimitedCamera {
|
||||
cam := &LimitedCamera{
|
||||
viewportWidth: 1.0,
|
||||
viewportHeight: 1.0,
|
||||
minZoom: minZoom,
|
||||
maxZoom: maxZoom,
|
||||
minPos: minPos,
|
||||
maxPos: maxPos,
|
||||
viewMatrix: mgl.Ident4()}
|
||||
|
||||
return cam
|
||||
}
|
||||
|
||||
// SetViewportSize notifies the camera how big the view is.
|
||||
func (cam *LimitedCamera) SetViewportSize(width, height float32) {
|
||||
if (cam.viewportWidth != width) || (cam.viewportHeight != height) {
|
||||
cam.viewportWidth, cam.viewportHeight = width, height
|
||||
cam.updateViewMatrix()
|
||||
}
|
||||
}
|
||||
|
||||
// ViewMatrix implements the Viewer interface.
|
||||
func (cam *LimitedCamera) ViewMatrix() *mgl.Mat4 {
|
||||
return &cam.viewMatrix
|
||||
}
|
||||
|
||||
// MoveBy adjusts the requested view offset by given delta values in world coordinates.
|
||||
func (cam *LimitedCamera) MoveBy(dx, dy float32) {
|
||||
cam.MoveTo(cam.viewOffsetX+dx, cam.viewOffsetY+dy)
|
||||
}
|
||||
|
||||
// MoveTo sets the requested view offset to the given world coordinates.
|
||||
func (cam *LimitedCamera) MoveTo(worldX, worldY float32) {
|
||||
cam.viewOffsetX = cam.limitValue(worldX, -cam.maxPos, cam.minPos)
|
||||
cam.viewOffsetY = cam.limitValue(worldY, -cam.maxPos, cam.minPos)
|
||||
cam.updateViewMatrix()
|
||||
}
|
||||
|
||||
// ZoomAt adjusts the requested zoom level by given delta, centered around given world position.
|
||||
// Positive values zoom in.
|
||||
func (cam *LimitedCamera) ZoomAt(levelDelta float32, x, y float32) {
|
||||
cam.requestedZoomLevel = cam.limitValue(cam.requestedZoomLevel+levelDelta, cam.minZoom, cam.maxZoom)
|
||||
|
||||
focusPoint := mgl.Vec4{x, y, 0.0, 1.0}
|
||||
oldPixel := cam.viewMatrix.Mul4x1(focusPoint)
|
||||
|
||||
cam.updateViewMatrix()
|
||||
|
||||
newPixel := cam.viewMatrix.Mul4x1(focusPoint)
|
||||
scaleFactor := cam.scaleFactor()
|
||||
cam.MoveBy(-(newPixel[0]-oldPixel[0])/scaleFactor, +(newPixel[1]-oldPixel[1])/scaleFactor)
|
||||
}
|
||||
|
||||
func (cam *LimitedCamera) limitValue(value float32, min, max float32) float32 {
|
||||
result := value
|
||||
|
||||
if result < min {
|
||||
result = min
|
||||
}
|
||||
if result > max {
|
||||
result = max
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (cam *LimitedCamera) scaleFactor() float32 {
|
||||
return float32(math.Pow(2.0, float64(cam.requestedZoomLevel)))
|
||||
}
|
||||
|
||||
func (cam *LimitedCamera) updateViewMatrix() {
|
||||
scaleFactor := cam.scaleFactor()
|
||||
cam.viewMatrix = mgl.Ident4().
|
||||
Mul4(mgl.Translate3D(cam.viewportWidth/2.0, cam.viewportHeight/2.0, 0)).
|
||||
Mul4(mgl.Scale3D(scaleFactor, -scaleFactor, 1.0)).
|
||||
Mul4(mgl.Translate3D(cam.viewOffsetX, cam.viewOffsetY, 0))
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
var mapColorsVertexShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec4 vertexColor;
|
||||
in vec2 vertexPosition;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition.xy, 0.0, 1.0);
|
||||
color = vertexColor;
|
||||
}
|
||||
`
|
||||
|
||||
var mapColorsFragmentShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec4 color;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main(void) {
|
||||
fragColor = color;
|
||||
}
|
||||
`
|
||||
|
||||
// ColorQuery returns the color for the specified tile.
|
||||
type ColorQuery func(level.TilePosition) [4]float32
|
||||
|
||||
// MapColors is a renderable for the tile colorings.
|
||||
type MapColors struct {
|
||||
context *render.Context
|
||||
|
||||
program uint32
|
||||
vao *opengl.VertexArrayObject
|
||||
vertexPositionBuffer uint32
|
||||
vertexPositionAttrib int32
|
||||
vertexColorBuffer uint32
|
||||
vertexColorAttrib int32
|
||||
modelMatrixUniform opengl.Matrix4Uniform
|
||||
viewMatrixUniform opengl.Matrix4Uniform
|
||||
projectionMatrixUniform opengl.Matrix4Uniform
|
||||
|
||||
lastColorGrid [][][4]float32
|
||||
lastColorGridColumns int
|
||||
lastColorGridRows int
|
||||
}
|
||||
|
||||
// NewMapColors returns a new instance of a renderable for tile colorings.
|
||||
func NewMapColors(context *render.Context) *MapColors {
|
||||
gl := context.OpenGL
|
||||
program, programErr := opengl.LinkNewStandardProgram(gl, mapColorsVertexShaderSource, mapColorsFragmentShaderSource)
|
||||
|
||||
if programErr != nil {
|
||||
panic(opengl.NamedShaderError{Name: "MapColorsShader", Nested: programErr})
|
||||
}
|
||||
renderable := &MapColors{
|
||||
context: context,
|
||||
program: program,
|
||||
vao: opengl.NewVertexArrayObject(gl, program),
|
||||
vertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
|
||||
vertexColorBuffer: gl.GenBuffers(1)[0],
|
||||
vertexColorAttrib: gl.GetAttribLocation(program, "vertexColor"),
|
||||
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
|
||||
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
|
||||
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
|
||||
}
|
||||
|
||||
{
|
||||
top := float32(level.FineCoordinatesPerTileSide)
|
||||
left := float32(0.0)
|
||||
right := float32(level.FineCoordinatesPerTileSide)
|
||||
bottom := float32(0.0)
|
||||
|
||||
vertices := []float32{
|
||||
left, bottom,
|
||||
left, top,
|
||||
right, top,
|
||||
|
||||
right, top,
|
||||
right, bottom,
|
||||
left, bottom}
|
||||
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
|
||||
renderable.vao.WithSetter(func(gl opengl.OpenGL) {
|
||||
gl.EnableVertexAttribArray(uint32(renderable.vertexPositionAttrib))
|
||||
gl.EnableVertexAttribArray(uint32(renderable.vertexColorAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(renderable.vertexPositionAttrib), 2, opengl.FLOAT, false, 0, 0)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexColorBuffer)
|
||||
gl.VertexAttribOffset(uint32(renderable.vertexColorAttrib), 4, opengl.FLOAT, false, 0, 0)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
|
||||
return renderable
|
||||
}
|
||||
|
||||
// Dispose releases any internal resources.
|
||||
func (renderable *MapColors) Dispose() {
|
||||
gl := renderable.context.OpenGL
|
||||
gl.DeleteProgram(renderable.program)
|
||||
gl.DeleteBuffers([]uint32{renderable.vertexPositionBuffer})
|
||||
renderable.vao.Dispose()
|
||||
}
|
||||
|
||||
// Render renders the renderable.
|
||||
func (renderable *MapColors) Render(columnCount, rowCount int, query ColorQuery) {
|
||||
gl := renderable.context.OpenGL
|
||||
|
||||
if (renderable.lastColorGridColumns != columnCount) || (renderable.lastColorGridRows != rowCount) {
|
||||
renderable.lastColorGrid = make([][][4]float32, rowCount+1)
|
||||
for y := 0; y < rowCount; y++ {
|
||||
renderable.lastColorGrid[y] = make([][4]float32, columnCount+1)
|
||||
}
|
||||
renderable.lastColorGrid[rowCount] = make([][4]float32, columnCount+1)
|
||||
renderable.lastColorGridColumns = columnCount
|
||||
renderable.lastColorGridRows = rowCount
|
||||
}
|
||||
for y, row := range renderable.lastColorGrid {
|
||||
for x := 0; x < columnCount; x++ {
|
||||
row[x] = query(level.TilePosition{X: byte(x), Y: byte(y)})
|
||||
}
|
||||
}
|
||||
|
||||
renderable.vao.OnShader(func() {
|
||||
var colors [24]float32
|
||||
|
||||
renderable.viewMatrixUniform.Set(gl, renderable.context.ViewMatrix)
|
||||
renderable.projectionMatrixUniform.Set(gl, &renderable.context.ProjectionMatrix)
|
||||
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexColorBuffer)
|
||||
|
||||
for y := 0; y < rowCount; y++ {
|
||||
for x := 0; x < columnCount; x++ {
|
||||
colorBottomLeft := renderable.lastColorGrid[y][x]
|
||||
colorBottomRight := renderable.lastColorGrid[y][x+1]
|
||||
colorTopLeft := renderable.lastColorGrid[y+1][x]
|
||||
colorTopRight := renderable.lastColorGrid[y+1][x+1]
|
||||
|
||||
modelMatrix := mgl.Ident4().
|
||||
Mul4(mgl.Translate3D((float32(x))*level.FineCoordinatesPerTileSide, (float32(y))*level.FineCoordinatesPerTileSide, 0.0))
|
||||
renderable.modelMatrixUniform.Set(gl, &modelMatrix)
|
||||
|
||||
copy(colors[0:4], colorBottomLeft[:])
|
||||
copy(colors[4:8], colorTopLeft[:])
|
||||
copy(colors[8:12], colorTopRight[:])
|
||||
copy(colors[12:16], colorTopRight[:])
|
||||
copy(colors[16:20], colorBottomRight[:])
|
||||
copy(colors[20:24], colorBottomLeft[:])
|
||||
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(colors)*4, colors[:], opengl.STATIC_DRAW)
|
||||
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
|
||||
}
|
||||
}
|
||||
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/inkyblackness/imgui-go/v3"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/graphics"
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ss1/content/object"
|
||||
"github.com/inkyblackness/hacked/ss1/edit"
|
||||
"github.com/inkyblackness/hacked/ss1/resource"
|
||||
"github.com/inkyblackness/hacked/ss1/world/ids"
|
||||
"github.com/inkyblackness/hacked/ui/input"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
const contextMenuName = "MapContextMenu"
|
||||
|
||||
// MapDisplay renders a level map.
|
||||
type MapDisplay struct {
|
||||
gameObjects *edit.GameObjectsService
|
||||
levelSelection *edit.LevelSelectionService
|
||||
editor *edit.LevelEditorService
|
||||
|
||||
context render.Context
|
||||
camera *LimitedCamera
|
||||
guiScale float32
|
||||
|
||||
background *BackgroundGrid
|
||||
textures *MapTextures
|
||||
colors *MapColors
|
||||
mapGrid *MapGrid
|
||||
highlighter *Highlighter
|
||||
icons *MapIcons
|
||||
|
||||
moveCapture func(pixelX, pixelY float32)
|
||||
mouseMoved bool
|
||||
|
||||
positionPopupPos imgui.Vec2
|
||||
positionValid bool
|
||||
position MapPosition
|
||||
contextMenuRequested bool
|
||||
|
||||
selectionReference *level.TilePosition
|
||||
|
||||
hoverItems hoverItems
|
||||
}
|
||||
|
||||
// NewMapDisplay returns a new instance.
|
||||
func NewMapDisplay(gameObjects *edit.GameObjectsService, levelSelection *edit.LevelSelectionService, editor *edit.LevelEditorService,
|
||||
gl opengl.OpenGL, guiScale float32,
|
||||
textureQuery TextureQuery) *MapDisplay {
|
||||
tilesPerMapSide := float32(64)
|
||||
|
||||
tileBaseLength := float32(level.FineCoordinatesPerTileSide)
|
||||
tileBaseHalf := tileBaseLength / 2.0
|
||||
camLimit := tilesPerMapSide*tileBaseLength - tileBaseHalf
|
||||
zoomShift := guiScale - 1.0
|
||||
zoomLevelMin := float32(-5) + zoomShift
|
||||
zoomLevelMax := float32(1) + zoomShift
|
||||
|
||||
display := &MapDisplay{
|
||||
gameObjects: gameObjects,
|
||||
levelSelection: levelSelection,
|
||||
editor: editor,
|
||||
context: render.Context{
|
||||
OpenGL: gl,
|
||||
ProjectionMatrix: mgl.Ident4(),
|
||||
},
|
||||
camera: NewLimitedCamera(zoomLevelMin, zoomLevelMax, -tileBaseHalf, camLimit),
|
||||
guiScale: guiScale,
|
||||
moveCapture: func(float32, float32) {},
|
||||
}
|
||||
display.context.ViewMatrix = display.camera.ViewMatrix()
|
||||
display.background = NewBackgroundGrid(&display.context)
|
||||
display.textures = NewMapTextures(&display.context, textureQuery)
|
||||
display.colors = NewMapColors(&display.context)
|
||||
display.mapGrid = NewMapGrid(&display.context)
|
||||
display.highlighter = NewHighlighter(&display.context)
|
||||
display.icons = NewMapIcons(&display.context)
|
||||
|
||||
centerX, centerY := (tilesPerMapSide*tileBaseLength)/-2.0, (tilesPerMapSide*tileBaseLength)/-2.0
|
||||
display.camera.ZoomAt(-3+zoomShift, centerX, centerY)
|
||||
display.camera.MoveTo(centerX, centerY)
|
||||
|
||||
return display
|
||||
}
|
||||
|
||||
// Render renders the whole map display.
|
||||
func (display *MapDisplay) Render(
|
||||
paletteTexture *graphics.PaletteTexture, textureRetriever func(resource.Key) (*graphics.BitmapTexture, error),
|
||||
textureDisplay TextureDisplay, colorDisplay ColorDisplay) {
|
||||
lvl := display.editor.Level()
|
||||
columns, rows, _ := lvl.Size()
|
||||
|
||||
display.hoverItems.validate(lvl)
|
||||
|
||||
display.background.Render(columns, rows)
|
||||
if lvl.IsCyberspace() {
|
||||
display.renderCyberspaceColors(lvl, columns, rows, paletteTexture, colorDisplay)
|
||||
} else {
|
||||
display.renderRealWorldTextures(lvl, columns, rows, paletteTexture, textureDisplay)
|
||||
display.renderRealWorldShadows(lvl, columns, rows, colorDisplay)
|
||||
}
|
||||
display.mapGrid.Render(columns, rows, lvl)
|
||||
display.renderTileSelection()
|
||||
display.renderObjectBackgrounds(lvl)
|
||||
display.renderObjectIcons(lvl, paletteTexture, textureRetriever)
|
||||
display.renderObjectSelection()
|
||||
display.renderActiveHoverItem()
|
||||
display.renderPositionOverlay(lvl)
|
||||
display.renderContextMenu()
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderCyberspaceColors(lvl *level.Level, columns int, rows int, paletteTexture *graphics.PaletteTexture, colorDisplay ColorDisplay) {
|
||||
if paletteTexture == nil {
|
||||
return
|
||||
}
|
||||
var colorQuery ColorQuery
|
||||
palette := paletteTexture.Palette()
|
||||
if colorDisplay == ColorDisplayFloor {
|
||||
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
|
||||
rgb := palette[tile.TextureInfo.FloorPaletteIndex()]
|
||||
return [4]float32{float32(rgb.Red) / 255, float32(rgb.Green) / 255, float32(rgb.Blue) / 255, 0.8}
|
||||
})
|
||||
} else if colorDisplay == ColorDisplayCeiling {
|
||||
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
|
||||
rgb := palette[tile.TextureInfo.CeilingPaletteIndex()]
|
||||
return [4]float32{float32(rgb.Red) / 255, float32(rgb.Green) / 255, float32(rgb.Blue) / 255, 0.8}
|
||||
})
|
||||
}
|
||||
if colorQuery != nil {
|
||||
display.colors.Render(columns, rows, colorQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderRealWorldTextures(lvl *level.Level, columns int, rows int,
|
||||
paletteTexture *graphics.PaletteTexture, textureDisplay TextureDisplay) {
|
||||
if paletteTexture == nil {
|
||||
return
|
||||
}
|
||||
display.textures.Render(columns, rows, func(pos level.TilePosition) (level.TileType, level.TextureIndex, int) {
|
||||
tile := lvl.Tile(pos)
|
||||
if tile == nil {
|
||||
return level.TileTypeSolid, 0, 0
|
||||
}
|
||||
atlasIndex, textureRotations := textureDisplay.Func()(tile)
|
||||
atlas := lvl.TextureAtlas()
|
||||
textureIndex := level.TextureIndex(-1)
|
||||
if (int(atlasIndex) >= 0) && (int(atlasIndex) < len(atlas)) {
|
||||
textureIndex = atlas[atlasIndex]
|
||||
}
|
||||
return tile.Type, textureIndex, textureRotations
|
||||
}, paletteTexture)
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderRealWorldShadows(lvl *level.Level, columns int, rows int, colorDisplay ColorDisplay) {
|
||||
var colorQuery ColorQuery
|
||||
if colorDisplay == ColorDisplayFloor {
|
||||
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
|
||||
return [4]float32{0.0, 0.0, 0.0, float32(tile.Flags.ForRealWorld().FloorShadow()) / 15.0}
|
||||
})
|
||||
} else if colorDisplay == ColorDisplayCeiling {
|
||||
colorQuery = display.colorQueryFor(lvl, func(tile *level.TileMapEntry) [4]float32 {
|
||||
return [4]float32{0.0, 0.0, 0.0, float32(tile.Flags.ForRealWorld().CeilingShadow()) / 15.0}
|
||||
})
|
||||
}
|
||||
if colorQuery != nil {
|
||||
display.colors.Render(columns, rows, colorQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderTileSelection() {
|
||||
selectedTiles := display.levelSelection.CurrentSelectedTiles()
|
||||
tileMapPositions := make([]MapPosition, 0, len(selectedTiles))
|
||||
for _, pos := range selectedTiles {
|
||||
tileMapPositions = append(tileMapPositions, MapPosition{
|
||||
X: level.CoordinateAt(pos.X, level.FineCoordinatesPerTileSide/2),
|
||||
Y: level.CoordinateAt(pos.Y, level.FineCoordinatesPerTileSide/2),
|
||||
})
|
||||
}
|
||||
display.highlighter.Render(tileMapPositions, level.FineCoordinatesPerTileSide, [4]float32{0.0, 0.8, 0.2, 0.5})
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderObjectBackgrounds(lvl *level.Level) {
|
||||
var objects []MapPosition
|
||||
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
|
||||
objects = append(objects, MapPosition{X: entry.X, Y: entry.Y})
|
||||
})
|
||||
display.highlighter.Render(objects, level.FineCoordinatesPerTileSide/4, [4]float32{1.0, 1.0, 1.0, 0.3})
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderObjectIcons(lvl *level.Level, paletteTexture *graphics.PaletteTexture,
|
||||
textureRetriever func(resource.Key) (*graphics.BitmapTexture, error)) {
|
||||
if paletteTexture == nil {
|
||||
return
|
||||
}
|
||||
tripleInfo := display.gameObjects.BitmapInfo()
|
||||
var icons []iconData
|
||||
var highlightIcon iconData
|
||||
var highlightID level.ObjectID
|
||||
|
||||
if display.hoverItems.activeItem != nil {
|
||||
objectItem, isObject := display.hoverItems.activeItem.(objectHoverItem)
|
||||
if isObject {
|
||||
highlightID = objectItem.id
|
||||
}
|
||||
}
|
||||
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
|
||||
triple := entry.Triple()
|
||||
info, cached := tripleInfo[triple]
|
||||
if !cached {
|
||||
return
|
||||
}
|
||||
key := resource.KeyOf(ids.ObjectBitmaps, resource.LangAny, info.Start+info.IconRecommendation)
|
||||
texture, err := textureRetriever(key)
|
||||
if err == nil {
|
||||
icon := iconData{pos: MapPosition{X: entry.X, Y: entry.Y}, texture: texture}
|
||||
if highlightID == id {
|
||||
highlightIcon = icon
|
||||
} else {
|
||||
icons = append(icons, icon)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (highlightID != 0) && (highlightIcon.texture != nil) {
|
||||
icons = append(icons, highlightIcon)
|
||||
}
|
||||
display.icons.Render(paletteTexture, level.FineCoordinatesPerTileSide/4, icons)
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderObjectSelection() {
|
||||
selectedObjects := display.editor.Objects()
|
||||
selectedObjectHighlights := make([]MapPosition, 0, len(selectedObjects))
|
||||
for _, obj := range selectedObjects {
|
||||
objPos := MapPosition{X: obj.X, Y: obj.Y}
|
||||
selectedObjectHighlights = append(selectedObjectHighlights, objPos)
|
||||
}
|
||||
display.highlighter.Render(selectedObjectHighlights, level.FineCoordinatesPerTileSide/4, [4]float32{0.0, 0.8, 0.2, 0.5})
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderActiveHoverItem() {
|
||||
if display.hoverItems.activeItem == nil {
|
||||
return
|
||||
}
|
||||
display.highlighter.Render(
|
||||
[]MapPosition{display.hoverItems.activeItem.Pos()},
|
||||
display.hoverItems.activeItem.Size(),
|
||||
[4]float32{0.0, 0.2, 0.8, 0.3})
|
||||
}
|
||||
|
||||
func (display *MapDisplay) colorQueryFor(lvl *level.Level, tileToColor func(*level.TileMapEntry) [4]float32) func(level.TilePosition) [4]float32 {
|
||||
return func(pos level.TilePosition) [4]float32 {
|
||||
tile := lvl.Tile(pos)
|
||||
if tile == nil {
|
||||
return [4]float32{}
|
||||
}
|
||||
return tileToColor(tile)
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderPositionOverlay(lvl *level.Level) {
|
||||
imgui.SetNextWindowPosV(display.positionPopupPos, imgui.ConditionAlways, imgui.Vec2{X: 1.0, Y: 1.0})
|
||||
imgui.SetNextWindowSize(imgui.Vec2{X: 140 * display.guiScale, Y: 0})
|
||||
imgui.SetNextWindowBgAlpha(0.3)
|
||||
if imgui.BeginV("Position", nil, imgui.WindowFlagsNoMove|imgui.WindowFlagsNoTitleBar|imgui.WindowFlagsNoResize|imgui.WindowFlagsAlwaysAutoResize|
|
||||
imgui.WindowFlagsNoSavedSettings|imgui.WindowFlagsNoFocusOnAppearing|imgui.WindowFlagsNoNav) {
|
||||
typeString := "---"
|
||||
hasPos := false
|
||||
var pos MapPosition
|
||||
hasFloor := false
|
||||
var floorRaw int
|
||||
floorString := hintUnknown
|
||||
hasCeiling := false
|
||||
var ceilingRaw int
|
||||
ceilingString := hintUnknown
|
||||
|
||||
if display.hoverItems.activeItem != nil {
|
||||
pos = display.hoverItems.activeItem.Pos()
|
||||
hasPos = true
|
||||
|
||||
if _, isTileItem := display.hoverItems.activeItem.(tileHoverItem); isTileItem {
|
||||
pos = display.position // use raw cursor position for this display
|
||||
typeString = "Tile"
|
||||
tile := lvl.Tile(pos.Tile())
|
||||
if (tile != nil) && (tile.Type != level.TileTypeSolid) {
|
||||
_, _, heightShift := lvl.Size()
|
||||
floorHeight := tile.Floor.AbsoluteHeight()
|
||||
floorHeightInTiles, err := heightShift.ValueFromTileHeight(floorHeight)
|
||||
if err == nil {
|
||||
floorString = fmt.Sprintf("%2.3f", floorHeightInTiles)
|
||||
}
|
||||
floorRaw = int(floorHeight)
|
||||
hasFloor = true
|
||||
|
||||
ceilingHeight := tile.Ceiling.AbsoluteHeight()
|
||||
ceilingHeightInTiles, err := heightShift.ValueFromTileHeight(ceilingHeight)
|
||||
if err == nil {
|
||||
ceilingString = fmt.Sprintf("%2.3f", ceilingHeightInTiles)
|
||||
}
|
||||
ceilingRaw = int(ceilingHeight)
|
||||
hasCeiling = true
|
||||
}
|
||||
} else if objectItem, isObjectItem := display.hoverItems.activeItem.(objectHoverItem); isObjectItem {
|
||||
_, _, heightShift := lvl.Size()
|
||||
obj := lvl.Object(objectItem.id)
|
||||
if obj != nil {
|
||||
typeString = fmt.Sprintf("%3d = %v", objectItem.id, obj.Triple())
|
||||
heightInTiles, err := heightShift.ValueFromObjectHeight(obj.Z)
|
||||
if err == nil {
|
||||
floorString = fmt.Sprintf("%2.3f", heightInTiles)
|
||||
}
|
||||
floorRaw = int(obj.Z)
|
||||
hasFloor = true
|
||||
}
|
||||
}
|
||||
}
|
||||
imgui.Text("T: " + typeString)
|
||||
if hasPos {
|
||||
imgui.Text(fmt.Sprintf("X: T %2d F %3d", pos.X.Tile(), pos.X.Fine()))
|
||||
imgui.Text(fmt.Sprintf("Y: T %2d F %3d", pos.Y.Tile(), pos.Y.Fine()))
|
||||
} else {
|
||||
imgui.Text("X: T -- F ---")
|
||||
imgui.Text("Y: T -- F ---")
|
||||
}
|
||||
if hasFloor {
|
||||
imgui.Text(fmt.Sprintf("F: %3d = %s", floorRaw, floorString))
|
||||
} else {
|
||||
imgui.Text("F: -- = --.---")
|
||||
}
|
||||
if hasCeiling {
|
||||
imgui.Text(fmt.Sprintf("C: %3d = %s", ceilingRaw, ceilingString))
|
||||
} else {
|
||||
imgui.Text("C: -- = --.---")
|
||||
}
|
||||
imgui.End()
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) renderContextMenu() {
|
||||
if display.contextMenuRequested {
|
||||
imgui.OpenPopup(contextMenuName)
|
||||
display.contextMenuRequested = false
|
||||
}
|
||||
if imgui.BeginPopupV(contextMenuName, imgui.PopupFlagsMouseButtonRight) {
|
||||
readOnly := display.editor.IsReadOnly()
|
||||
if imgui.BeginMenu("New...") {
|
||||
implicitTriple := display.editor.NewObjectTriple()
|
||||
canCreateImplicitClass := display.canCreateObjectOf(implicitTriple.Class)
|
||||
if imgui.MenuItemV("Object", "Ctrl+2ndClick", false, canCreateImplicitClass) {
|
||||
display.requestCreateNewObject(false, implicitTriple)
|
||||
}
|
||||
if imgui.MenuItemV("Object (at grid)", "Ctrl+Shift+2ndClick", false, canCreateImplicitClass) {
|
||||
display.requestCreateNewObject(true, implicitTriple)
|
||||
}
|
||||
imgui.EndMenu()
|
||||
}
|
||||
imgui.Separator()
|
||||
if imgui.MenuItemV("Delete Objects", "", false, !readOnly && display.editor.HasSelectedObjects()) {
|
||||
_ = display.editor.DeleteObjects()
|
||||
}
|
||||
if imgui.MenuItemV("Clear Tiles", "", false, !readOnly && display.editor.HasSelectedTiles()) {
|
||||
_ = display.editor.ClearTiles()
|
||||
}
|
||||
imgui.EndPopup()
|
||||
}
|
||||
}
|
||||
|
||||
// WindowResized must be called to notify of a change in window geometry.
|
||||
func (display *MapDisplay) WindowResized(width int, height int) {
|
||||
display.context.ProjectionMatrix = mgl.Ortho2D(0.0, float32(width), float32(height), 0.0)
|
||||
display.camera.SetViewportSize(float32(width), float32(height))
|
||||
display.positionPopupPos.X = float32(width) - 10.0
|
||||
display.positionPopupPos.Y = float32(height) - 10.0
|
||||
}
|
||||
|
||||
func (display *MapDisplay) unprojectPixel(pixelX, pixelY float32) (x, y float32) {
|
||||
pixelVec := mgl.Vec4{pixelX, pixelY, 0.0, 1.0}
|
||||
invertedView := display.camera.ViewMatrix().Inv()
|
||||
result := invertedView.Mul4x1(pixelVec)
|
||||
|
||||
return result[0], result[1]
|
||||
}
|
||||
|
||||
// MouseButtonDown must be called when a button was pressed.
|
||||
func (display *MapDisplay) MouseButtonDown(mouseX, mouseY float32, button uint32) {
|
||||
display.updateMouseWorldPosition(mouseX, mouseY)
|
||||
if button == input.MousePrimary {
|
||||
lastPixelX, lastPixelY := mouseX, mouseY
|
||||
|
||||
display.mouseMoved = false
|
||||
display.moveCapture = func(pixelX, pixelY float32) {
|
||||
lastWorldX, lastWorldY := display.unprojectPixel(lastPixelX, lastPixelY)
|
||||
worldX, worldY := display.unprojectPixel(pixelX, pixelY)
|
||||
|
||||
display.camera.MoveBy(worldX-lastWorldX, worldY-lastWorldY)
|
||||
lastPixelX, lastPixelY = pixelX, pixelY
|
||||
display.mouseMoved = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MouseButtonUp must be called when a button was released.
|
||||
func (display *MapDisplay) MouseButtonUp(mouseX, mouseY float32, button uint32, modifier input.Modifier) {
|
||||
display.updateMouseWorldPosition(mouseX, mouseY)
|
||||
if button == input.MousePrimary {
|
||||
display.moveCapture = func(float32, float32) {}
|
||||
if !display.mouseMoved && display.positionValid {
|
||||
switch {
|
||||
case modifier.Has(input.ModControl):
|
||||
display.toggleSelectionAtActiveHoverItem()
|
||||
case modifier.Has(input.ModShift) && (display.selectionReference != nil):
|
||||
firstPos := *display.selectionReference
|
||||
|
||||
fromX := int(firstPos.X)
|
||||
fromY := int(firstPos.Y)
|
||||
toX := int(display.position.X.Tile())
|
||||
toY := int(display.position.Y.Tile())
|
||||
xIncrement := 1
|
||||
yIncrement := 1
|
||||
if fromX > toX {
|
||||
xIncrement = -1
|
||||
}
|
||||
if fromY > toY {
|
||||
yIncrement = -1
|
||||
}
|
||||
toX += xIncrement
|
||||
toY += yIncrement
|
||||
var newList []level.TilePosition
|
||||
for y := fromY; y != toY; y += yIncrement {
|
||||
for x := fromX; x != toX; x += xIncrement {
|
||||
newList = append(newList, level.TilePosition{X: byte(x), Y: byte(y)})
|
||||
}
|
||||
}
|
||||
display.levelSelection.SetCurrentSelectedTiles(newList)
|
||||
display.levelSelection.SetCurrentSelectedObjects(display.objectsInTiles(newList))
|
||||
default:
|
||||
display.setSelectionByActiveHoverItem()
|
||||
}
|
||||
}
|
||||
} else if button == input.MouseSecondary {
|
||||
if modifier.IsClear() {
|
||||
display.contextMenuRequested = true
|
||||
} else if modifier.Has(input.ModControl) {
|
||||
display.requestCreateNewObject(modifier.Has(input.ModShift), display.editor.NewObjectTriple())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) canCreateObjectOf(class object.Class) bool {
|
||||
if display.editor.IsReadOnly() {
|
||||
return false
|
||||
}
|
||||
lvl := display.editor.Level()
|
||||
return lvl.HasRoomForObjectOf(class) && display.positionValid
|
||||
}
|
||||
|
||||
func (display *MapDisplay) requestCreateNewObject(snapToGrid bool, triple object.Triple) {
|
||||
if !display.canCreateObjectOf(triple.Class) {
|
||||
return
|
||||
}
|
||||
pos := display.position
|
||||
if snapToGrid {
|
||||
toGrid := func(value byte) byte {
|
||||
cornerSnapDistance := level.FineCoordinatesPerTileSide / 4
|
||||
switch {
|
||||
case value < byte(cornerSnapDistance):
|
||||
return 0x00
|
||||
case value >= byte(level.FineCoordinatesPerTileSide-cornerSnapDistance):
|
||||
return level.FineCoordinatesPerTileSide - 1
|
||||
default:
|
||||
return level.FineCoordinatesPerTileSide / 2
|
||||
}
|
||||
}
|
||||
pos.X = level.CoordinateAt(pos.X.Tile(), toGrid(pos.X.Fine()))
|
||||
pos.Y = level.CoordinateAt(pos.Y.Tile(), toGrid(pos.Y.Fine()))
|
||||
}
|
||||
err := display.editor.CreateNewObject(triple, func(obj *level.ObjectMainEntry) {
|
||||
obj.X = pos.X
|
||||
obj.Y = pos.Y
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) setSelectionByActiveHoverItem() {
|
||||
var tiles []level.TilePosition
|
||||
var objects []level.ObjectID
|
||||
if display.hoverItems.activeItem != nil {
|
||||
if tileItem, isTile := display.hoverItems.activeItem.(tileHoverItem); isTile {
|
||||
tiles = append(tiles, tileItem.pos.Tile())
|
||||
} else if objectItem, isObject := display.hoverItems.activeItem.(objectHoverItem); isObject {
|
||||
objects = append(objects, objectItem.id)
|
||||
}
|
||||
}
|
||||
display.levelSelection.SetCurrentSelectedTiles(tiles)
|
||||
if len(tiles) > 0 {
|
||||
display.selectionReference = &tiles[0]
|
||||
display.levelSelection.SetCurrentSelectedObjects(display.objectsInTiles(tiles))
|
||||
} else {
|
||||
display.levelSelection.SetCurrentSelectedObjects(objects)
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) toggleSelectionAtActiveHoverItem() {
|
||||
if display.hoverItems.activeItem != nil {
|
||||
if tileItem, isTile := display.hoverItems.activeItem.(tileHoverItem); isTile {
|
||||
wasSelected := display.levelSelection.IsTileSelected(tileItem.pos.Tile())
|
||||
tiles := []level.TilePosition{tileItem.pos.Tile()}
|
||||
if wasSelected {
|
||||
display.levelSelection.RemoveCurrentSelectedTiles(tiles)
|
||||
display.levelSelection.RemoveCurrentSelectedObjects(display.objectsInTiles(tiles))
|
||||
} else {
|
||||
display.levelSelection.AddCurrentSelectedTiles(tiles)
|
||||
display.levelSelection.AddCurrentSelectedObjects(display.objectsInTiles(tiles))
|
||||
}
|
||||
} else if objectItem, isObject := display.hoverItems.activeItem.(objectHoverItem); isObject {
|
||||
display.levelSelection.ToggleObjectSelection([]level.ObjectID{objectItem.id})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) objectsInTiles(tiles []level.TilePosition) []level.ObjectID {
|
||||
tilesContain := func(pos level.TilePosition) bool {
|
||||
for _, entry := range tiles {
|
||||
if entry == pos {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var objects []level.ObjectID
|
||||
lvl := display.editor.Level()
|
||||
lvl.ForEachObject(func(id level.ObjectID, entry level.ObjectMainEntry) {
|
||||
if tilesContain(entry.TilePosition()) {
|
||||
objects = append(objects, id)
|
||||
}
|
||||
})
|
||||
return objects
|
||||
}
|
||||
|
||||
// MouseMoved must be called for a mouse move.
|
||||
func (display *MapDisplay) MouseMoved(mouseX, mouseY float32) {
|
||||
display.updateMouseWorldPosition(mouseX, mouseY)
|
||||
if display.positionValid {
|
||||
display.hoverItems.find(display.editor.Level(), display.position)
|
||||
} else {
|
||||
display.hoverItems.reset()
|
||||
}
|
||||
display.moveCapture(mouseX, mouseY)
|
||||
}
|
||||
|
||||
// MouseScrolled must be called for a mouse scroll.
|
||||
func (display *MapDisplay) MouseScrolled(mouseX, mouseY float32, deltaX, deltaY float32, modifier input.Modifier) {
|
||||
if modifier.Has(input.ModControl) {
|
||||
display.hoverItems.scroll(deltaY > 0)
|
||||
} else {
|
||||
worldX, worldY := display.unprojectPixel(mouseX, mouseY)
|
||||
|
||||
if deltaY < 0 {
|
||||
display.camera.ZoomAt(-0.5, worldX, worldY)
|
||||
}
|
||||
if deltaY > 0 {
|
||||
display.camera.ZoomAt(0.5, worldX, worldY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (display *MapDisplay) updateMouseWorldPosition(mouseX, mouseY float32) {
|
||||
worldX, worldY := display.unprojectPixel(mouseX, mouseY)
|
||||
lvl := display.editor.Level()
|
||||
width, height, _ := lvl.Size()
|
||||
worldWidth := float32(width) * level.FineCoordinatesPerTileSide
|
||||
worldHeight := float32(height) * level.FineCoordinatesPerTileSide
|
||||
|
||||
display.positionValid = (worldX >= 0.0) && (worldX < worldWidth) && (worldY >= 0.0) && (worldY < worldHeight)
|
||||
if display.positionValid {
|
||||
display.position = MapPosition{X: level.Coordinate(worldX + 0.5), Y: level.Coordinate(worldY + 0.5)}
|
||||
} else {
|
||||
display.position = MapPosition{}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
var mapTileGridVertexShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec4 vertexPosition;
|
||||
out float colorAlpha;
|
||||
|
||||
uniform mat4 alphaMatrix;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition.xy, 0.0, 1.0);
|
||||
colorAlpha = alphaMatrix[int(vertexPosition.z)][int(vertexPosition.w)];
|
||||
}
|
||||
`
|
||||
|
||||
var mapTileGridFragmentShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
uniform vec4 color;
|
||||
in float colorAlpha;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main(void) {
|
||||
fragColor = vec4(color.rgb, color.a*colorAlpha);
|
||||
}
|
||||
`
|
||||
|
||||
// TileMapper returns basic information to draw a 2D map.
|
||||
type TileMapper interface {
|
||||
MapGridInfo(pos level.TilePosition) (level.TileType, level.TileSlopeControl, level.WallHeights)
|
||||
}
|
||||
|
||||
// MapGrid renders the grid of the map, based on calculated wall heights.
|
||||
type MapGrid struct {
|
||||
context *render.Context
|
||||
|
||||
program uint32
|
||||
vao *opengl.VertexArrayObject
|
||||
vertexPositionBuffer uint32
|
||||
tickVertexPositionBuffer uint32
|
||||
vertexPositionAttrib int32
|
||||
colorUniform opengl.Vector4Uniform
|
||||
|
||||
/*
|
||||
The alpha matrix is set up to specify the alpha value used for the wall sections.
|
||||
There are four cardinal walls with three sections each, plus two diagonal walls (single section), as well
|
||||
as the slope-ticks that re-use the shader.
|
||||
The matrix is meant to be used like this:
|
||||
N N N D1
|
||||
E E E D2
|
||||
S S S -
|
||||
W W W T
|
||||
D1 and D2 being the two diagonals, and T specifying the cell for the tick-marks.
|
||||
The vertex buffers are an array of v4 entries, with z/w specifying the row and column index to take.
|
||||
*/
|
||||
alphaMatrixUniform opengl.Matrix4Uniform
|
||||
modelMatrixUniform opengl.Matrix4Uniform
|
||||
viewMatrixUniform opengl.Matrix4Uniform
|
||||
projectionMatrixUniform opengl.Matrix4Uniform
|
||||
|
||||
slopeTicksByType [][]int
|
||||
floorTickStarts []int32
|
||||
ceilingTickStarts []int32
|
||||
}
|
||||
|
||||
// NewMapGrid returns a new instance.
|
||||
func NewMapGrid(context *render.Context) *MapGrid {
|
||||
gl := context.OpenGL
|
||||
program, programErr := opengl.LinkNewStandardProgram(gl, mapTileGridVertexShaderSource, mapTileGridFragmentShaderSource)
|
||||
if programErr != nil {
|
||||
panic(opengl.NamedShaderError{Name: "MapGrid", Nested: programErr})
|
||||
}
|
||||
grid := &MapGrid{
|
||||
context: context,
|
||||
program: program,
|
||||
vao: opengl.NewVertexArrayObject(gl, program),
|
||||
vertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
tickVertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
|
||||
colorUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "color")),
|
||||
alphaMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "alphaMatrix")),
|
||||
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
|
||||
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
|
||||
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
|
||||
}
|
||||
|
||||
{
|
||||
tickVertices := grid.calculateTickVertices()
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.tickVertexPositionBuffer)
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(tickVertices)*4, tickVertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
{
|
||||
const parts = 3
|
||||
left := float32(-0.5)
|
||||
right := float32(0.5)
|
||||
top := float32(0.5)
|
||||
bottom := float32(-0.5)
|
||||
finePerPart := float32(1.0 / parts)
|
||||
|
||||
var wallVertices []float32
|
||||
for i := 0; i < parts; i++ {
|
||||
wallVertices = append(wallVertices, left+(finePerPart*float32(i)), top, 0.0, float32(i))
|
||||
wallVertices = append(wallVertices, left+(finePerPart*float32(i+1)), top, 0.0, float32(i))
|
||||
}
|
||||
for i := 0; i < parts; i++ {
|
||||
wallVertices = append(wallVertices, right, top-(finePerPart*float32(i)), 1.0, float32(i))
|
||||
wallVertices = append(wallVertices, right, top-(finePerPart*float32(i+1)), 1.0, float32(i))
|
||||
}
|
||||
for i := 0; i < parts; i++ {
|
||||
wallVertices = append(wallVertices, right-(finePerPart*float32(i)), bottom, 2.0, float32(i))
|
||||
wallVertices = append(wallVertices, right-(finePerPart*float32(i+1)), bottom, 2.0, float32(i))
|
||||
}
|
||||
for i := 0; i < parts; i++ {
|
||||
wallVertices = append(wallVertices, left, bottom+(finePerPart*float32(i)), 3.0, float32(i))
|
||||
wallVertices = append(wallVertices, left, bottom+(finePerPart*float32(i+1)), 3.0, float32(i))
|
||||
}
|
||||
wallVertices = append(wallVertices, left, top, 0.0, 3.0)
|
||||
wallVertices = append(wallVertices, right, bottom, 0.0, 3.0)
|
||||
wallVertices = append(wallVertices, right, top, 1.0, 3.0)
|
||||
wallVertices = append(wallVertices, left, bottom, 1.0, 3.0)
|
||||
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(wallVertices)*4, wallVertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
{
|
||||
grid.slopeTicksByType = [][]int{
|
||||
nil,
|
||||
nil,
|
||||
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
|
||||
{0, 1},
|
||||
{1, 2},
|
||||
{2, 3},
|
||||
{3, 0},
|
||||
|
||||
{3, 0, 1},
|
||||
{0, 1, 2},
|
||||
{1, 2, 3},
|
||||
{2, 3, 0},
|
||||
|
||||
{2},
|
||||
{3},
|
||||
{0},
|
||||
{1},
|
||||
}
|
||||
grid.floorTickStarts = []int32{0, 6, 12, 18}
|
||||
grid.ceilingTickStarts = []int32{3, 9, 15, 21}
|
||||
}
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
// Dispose releases any internal resources.
|
||||
func (grid *MapGrid) Dispose() {
|
||||
gl := grid.context.OpenGL
|
||||
gl.DeleteProgram(grid.program)
|
||||
gl.DeleteBuffers([]uint32{grid.vertexPositionBuffer, grid.tickVertexPositionBuffer})
|
||||
grid.vao.Dispose()
|
||||
}
|
||||
|
||||
func (grid *MapGrid) calculateTickVertices() []float32 {
|
||||
dotHalf := float32(0.05)
|
||||
dotBase := float32(0.5) - (dotHalf * 2.0)
|
||||
|
||||
top := dotBase + dotHalf
|
||||
topEnd := dotBase - dotHalf
|
||||
left := -dotBase - dotHalf
|
||||
leftEnd := -dotBase + dotHalf
|
||||
|
||||
right := top
|
||||
rightEnd := topEnd
|
||||
bottom := left
|
||||
bottomEnd := leftEnd
|
||||
|
||||
return []float32{
|
||||
left, top, 3.0, 3.0, leftEnd, top, 3.0, 3.0, left, topEnd, 3.0, 3.0,
|
||||
leftEnd, top, 3.0, 3.0, leftEnd, topEnd, 3.0, 3.0, left, topEnd, 3.0, 3.0,
|
||||
|
||||
right, top, 3.0, 3.0, right, topEnd, 3.0, 3.0, rightEnd, top, 3.0, 3.0,
|
||||
rightEnd, top, 3.0, 3.0, right, topEnd, 3.0, 3.0, rightEnd, topEnd, 3.0, 3.0,
|
||||
|
||||
right, bottom, 3.0, 3.0, rightEnd, bottom, 3.0, 3.0, right, bottomEnd, 3.0, 3.0,
|
||||
right, bottomEnd, 3.0, 3.0, rightEnd, bottom, 3.0, 3.0, rightEnd, bottomEnd, 3.0, 3.0,
|
||||
|
||||
left, bottomEnd, 3.0, 3.0, leftEnd, bottom, 3.0, 3.0, left, bottom, 3.0, 3.0,
|
||||
left, bottomEnd, 3.0, 3.0, leftEnd, bottomEnd, 3.0, 3.0, leftEnd, bottom, 3.0, 3.0,
|
||||
}
|
||||
}
|
||||
|
||||
// Render renders the grid.
|
||||
func (grid *MapGrid) Render(columns, rows int, mapper TileMapper) {
|
||||
gl := grid.context.OpenGL
|
||||
|
||||
grid.vao.OnShader(func() {
|
||||
modelMatrix := mgl.Ident4()
|
||||
lineColor := [4]float32{0.0, 0.8, 0.0, 1.0}
|
||||
|
||||
grid.viewMatrixUniform.Set(gl, grid.context.ViewMatrix)
|
||||
grid.projectionMatrixUniform.Set(gl, &grid.context.ProjectionMatrix)
|
||||
|
||||
heightFactor := [32*2 + 1]float32{
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
|
||||
0.0,
|
||||
0.1, 0.2, 0.3, 0.3, 0.4, 0.4, 0.5, 0.7,
|
||||
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
|
||||
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
|
||||
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
|
||||
}
|
||||
|
||||
for y := 0; y < rows; y++ {
|
||||
for x := 0; x < columns; x++ {
|
||||
tileType, slopeControl, wallHeights := mapper.MapGridInfo(level.TilePosition{X: byte(x), Y: byte(y)})
|
||||
if tileType == level.TileTypeSolid {
|
||||
continue
|
||||
}
|
||||
|
||||
modelMatrix = mgl.Ident4().
|
||||
Mul4(mgl.Translate3D((float32(x)+0.5)*level.FineCoordinatesPerTileSide, (float32(y)+0.5)*level.FineCoordinatesPerTileSide, 0.0)).
|
||||
Mul4(mgl.Scale3D(level.FineCoordinatesPerTileSide, level.FineCoordinatesPerTileSide, 1.0))
|
||||
grid.modelMatrixUniform.Set(gl, &modelMatrix)
|
||||
var alphaMatrix mgl.Mat4
|
||||
alphaMatrix[15] = 1.0 // for tick marks
|
||||
|
||||
gl.EnableVertexAttribArray(uint32(grid.vertexPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.vertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(grid.vertexPositionAttrib), 4, opengl.FLOAT, false, 0, 0)
|
||||
|
||||
alphaMatrix[0+0] = heightFactor[int(wallHeights.North[0])+32]
|
||||
alphaMatrix[0+1] = heightFactor[int(wallHeights.North[1])+32]
|
||||
alphaMatrix[0+2] = heightFactor[int(wallHeights.North[2])+32]
|
||||
|
||||
alphaMatrix[4+0] = heightFactor[int(wallHeights.East[0])+32]
|
||||
alphaMatrix[4+1] = heightFactor[int(wallHeights.East[1])+32]
|
||||
alphaMatrix[4+2] = heightFactor[int(wallHeights.East[2])+32]
|
||||
|
||||
alphaMatrix[8+0] = heightFactor[int(wallHeights.South[0])+32]
|
||||
alphaMatrix[8+1] = heightFactor[int(wallHeights.South[1])+32]
|
||||
alphaMatrix[8+2] = heightFactor[int(wallHeights.South[2])+32]
|
||||
|
||||
alphaMatrix[12+0] = heightFactor[int(wallHeights.West[0])+32]
|
||||
alphaMatrix[12+1] = heightFactor[int(wallHeights.West[1])+32]
|
||||
alphaMatrix[12+2] = heightFactor[int(wallHeights.West[2])+32]
|
||||
if tileType == level.TileTypeDiagonalOpenNorthEast || tileType == level.TileTypeDiagonalOpenSouthWest {
|
||||
alphaMatrix[0+3] = 1.0
|
||||
}
|
||||
if tileType == level.TileTypeDiagonalOpenNorthWest || tileType == level.TileTypeDiagonalOpenSouthEast {
|
||||
alphaMatrix[4+3] = 1.0
|
||||
}
|
||||
|
||||
grid.colorUniform.Set(gl, &lineColor)
|
||||
grid.alphaMatrixUniform.Set(gl, &alphaMatrix)
|
||||
gl.DrawArrays(opengl.LINES, 0, 28)
|
||||
|
||||
grid.renderSlopeTicks(gl, tileType, slopeControl)
|
||||
}
|
||||
}
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (grid *MapGrid) renderSlopeTicks(gl opengl.OpenGL, tileType level.TileType, slopeControl level.TileSlopeControl) {
|
||||
var floorTicks []int
|
||||
var ceilingTicks []int
|
||||
|
||||
if slopeControl != level.TileSlopeControlFloorFlat {
|
||||
floorTicks = grid.slopeTicksByType[tileType]
|
||||
}
|
||||
if slopeControl == level.TileSlopeControlCeilingMirrored {
|
||||
ceilingTicks = grid.slopeTicksByType[tileType]
|
||||
} else if slopeControl != level.TileSlopeControlCeilingFlat {
|
||||
ceilingTicks = grid.slopeTicksByType[tileType.Info().SlopeInvertedType]
|
||||
}
|
||||
|
||||
gl.EnableVertexAttribArray(uint32(grid.vertexPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, grid.tickVertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(grid.vertexPositionAttrib), 4, opengl.FLOAT, false, 0, 0)
|
||||
|
||||
floorTickColor := [4]float32{0.0, 0.8, 0.0, 0.9}
|
||||
grid.colorUniform.Set(gl, &floorTickColor)
|
||||
for _, index := range floorTicks {
|
||||
gl.DrawArrays(opengl.TRIANGLES, grid.floorTickStarts[index], 3)
|
||||
}
|
||||
ceilingTickColor := [4]float32{0.8, 0.0, 0.0, 0.9}
|
||||
grid.colorUniform.Set(gl, &ceilingTickColor)
|
||||
for _, index := range ceilingTicks {
|
||||
gl.DrawArrays(opengl.TRIANGLES, grid.ceilingTickStarts[index], 3)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/graphics"
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
var mapIconsVertexShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec3 vertexPosition;
|
||||
in vec3 uvPosition;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform vec4 uvLimit;
|
||||
|
||||
out vec2 uv;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
|
||||
|
||||
uv = vec2(uvPosition.x * uvLimit.x, uvPosition.y * uvLimit.y);
|
||||
}
|
||||
`
|
||||
|
||||
var mapIconsFragmentShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D palette;
|
||||
uniform sampler2D bitmap;
|
||||
|
||||
in vec2 uv;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main(void) {
|
||||
vec4 pixel = texture(bitmap, uv);
|
||||
|
||||
if (pixel.r > 0.0) {
|
||||
fragColor = texture(palette, vec2(pixel.r, 0.5));
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// MapIcons is a renderable for simple bitmaps.
|
||||
type MapIcons struct {
|
||||
context *render.Context
|
||||
|
||||
program uint32
|
||||
vao *opengl.VertexArrayObject
|
||||
vertexPositionBuffer uint32
|
||||
vertexPositionAttrib int32
|
||||
uvPositionBuffer uint32
|
||||
uvPositionAttrib int32
|
||||
modelMatrixUniform opengl.Matrix4Uniform
|
||||
viewMatrixUniform opengl.Matrix4Uniform
|
||||
projectionMatrixUniform opengl.Matrix4Uniform
|
||||
uvLimitUniform opengl.Vector4Uniform
|
||||
|
||||
paletteUniform int32
|
||||
bitmapUniform int32
|
||||
}
|
||||
|
||||
type iconData struct {
|
||||
pos MapPosition
|
||||
texture *graphics.BitmapTexture
|
||||
}
|
||||
|
||||
// NewMapIcons returns a new instance.
|
||||
func NewMapIcons(context *render.Context) *MapIcons {
|
||||
gl := context.OpenGL
|
||||
program, programErr := opengl.LinkNewStandardProgram(gl, mapIconsVertexShaderSource, mapIconsFragmentShaderSource)
|
||||
|
||||
if programErr != nil {
|
||||
panic(opengl.NamedShaderError{Name: "MapIconsShader", Nested: programErr})
|
||||
}
|
||||
renderable := &MapIcons{
|
||||
context: context,
|
||||
program: program,
|
||||
|
||||
vao: opengl.NewVertexArrayObject(gl, program),
|
||||
vertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
|
||||
uvPositionBuffer: gl.GenBuffers(1)[0],
|
||||
uvPositionAttrib: gl.GetAttribLocation(program, "uvPosition"),
|
||||
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
|
||||
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
|
||||
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
|
||||
uvLimitUniform: opengl.Vector4Uniform(gl.GetUniformLocation(program, "uvLimit")),
|
||||
|
||||
paletteUniform: gl.GetUniformLocation(program, "palette"),
|
||||
bitmapUniform: gl.GetUniformLocation(program, "bitmap")}
|
||||
|
||||
{
|
||||
half := float32(0.5)
|
||||
var vertices = []float32{
|
||||
-half, half, 0.0,
|
||||
half, half, 0.0,
|
||||
half, -half, 0.0,
|
||||
|
||||
half, -half, 0.0,
|
||||
-half, -half, 0.0,
|
||||
-half, half, 0.0,
|
||||
}
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(vertices)*4, vertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
{
|
||||
var uv = []float32{
|
||||
0.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
1.0, 1.0, 0.0,
|
||||
|
||||
1.0, 1.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
}
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.uvPositionBuffer)
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(uv)*4, uv, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
renderable.vao.WithSetter(func(gl opengl.OpenGL) {
|
||||
gl.EnableVertexAttribArray(uint32(renderable.vertexPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(renderable.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
|
||||
gl.EnableVertexAttribArray(uint32(renderable.uvPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.uvPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(renderable.uvPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
|
||||
return renderable
|
||||
}
|
||||
|
||||
// Render renders the icons with their center at given position.
|
||||
func (renderable *MapIcons) Render(paletteTexture *graphics.PaletteTexture, iconSize float32, icons []iconData) {
|
||||
gl := renderable.context.OpenGL
|
||||
|
||||
renderable.vao.OnShader(func() {
|
||||
renderable.viewMatrixUniform.Set(gl, renderable.context.ViewMatrix)
|
||||
renderable.projectionMatrixUniform.Set(gl, &renderable.context.ProjectionMatrix)
|
||||
|
||||
textureUnit := int32(0)
|
||||
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
|
||||
gl.BindTexture(opengl.TEXTURE_2D, paletteTexture.Handle())
|
||||
gl.Uniform1i(renderable.paletteUniform, textureUnit)
|
||||
|
||||
textureUnit = 1
|
||||
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
|
||||
gl.Uniform1i(renderable.bitmapUniform, textureUnit)
|
||||
for _, icon := range icons {
|
||||
x, y := float32(icon.pos.X), float32(icon.pos.Y)
|
||||
u, v := icon.texture.UV()
|
||||
width, height := renderable.limitedSize(iconSize, icon.texture)
|
||||
modelMatrix := mgl.Ident4().
|
||||
Mul4(mgl.Translate3D(x, y, 0.0)).
|
||||
Mul4(mgl.Scale3D(width, height, 1.0))
|
||||
|
||||
renderable.modelMatrixUniform.Set(gl, &modelMatrix)
|
||||
|
||||
uvLimit := [4]float32{u, v, 0, 0}
|
||||
renderable.uvLimitUniform.Set(gl, &uvLimit)
|
||||
|
||||
gl.BindTexture(opengl.TEXTURE_2D, icon.texture.Handle())
|
||||
gl.DrawArrays(opengl.TRIANGLES, 0, 6)
|
||||
}
|
||||
gl.BindTexture(opengl.TEXTURE_2D, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (renderable *MapIcons) limitedSize(iconSize float32, texture *graphics.BitmapTexture) (width, height float32) {
|
||||
width, height = texture.Size()
|
||||
referenceSize := float32(16.0)
|
||||
larger := width
|
||||
|
||||
if larger < height {
|
||||
larger = height
|
||||
}
|
||||
if larger > referenceSize {
|
||||
ratio := referenceSize / larger
|
||||
width *= ratio
|
||||
height *= ratio
|
||||
}
|
||||
factor := iconSize / referenceSize
|
||||
width *= factor
|
||||
height *= factor
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package levels
|
||||
|
||||
import "github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
|
||||
// MapPosition describes a specific two-dimensional point on the map.
|
||||
type MapPosition struct {
|
||||
X level.Coordinate
|
||||
Y level.Coordinate
|
||||
}
|
||||
|
||||
// Tile returns the tile position of the exact position.
|
||||
func (pos MapPosition) Tile() level.TilePosition {
|
||||
return level.TilePosition{X: pos.X.Tile(), Y: pos.Y.Tile()}
|
||||
}
|
||||
|
||||
// Fine returns the fine position of the exact position.
|
||||
func (pos MapPosition) Fine() level.FinePosition {
|
||||
return level.FinePosition{X: pos.X.Fine(), Y: pos.Y.Fine()}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
mgl "github.com/go-gl/mathgl/mgl32"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/graphics"
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ui/opengl"
|
||||
)
|
||||
|
||||
var mapTexturesVertexShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec3 vertexPosition;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
uniform mat4 uvMatrix;
|
||||
|
||||
out vec2 uv;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(vertexPosition, 1.0);
|
||||
|
||||
uv = (uvMatrix * vec4(vertexPosition, 1.0)).xy;
|
||||
}
|
||||
`
|
||||
|
||||
var mapTexturesFragmentShaderSource = `
|
||||
#version 150
|
||||
precision mediump float;
|
||||
|
||||
in vec2 uv;
|
||||
|
||||
uniform sampler2D palette;
|
||||
uniform sampler2D bitmap;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main(void) {
|
||||
vec4 pixel = texture(bitmap, uv);
|
||||
|
||||
fragColor = texture(palette, vec2(pixel.r, 0.5));
|
||||
}
|
||||
`
|
||||
|
||||
// TextureQuery is a getter function to retrieve the texture for the given
|
||||
// level texture index.
|
||||
type TextureQuery func(index level.TextureIndex) (*graphics.BitmapTexture, error)
|
||||
|
||||
// TileTextureQuery is a getter function to retrieve properties for rendering a texture of a tile.
|
||||
type TileTextureQuery func(pos level.TilePosition) (tileType level.TileType, textureIndex level.TextureIndex, textureRotations int)
|
||||
|
||||
// MapTextures is a renderable for textures.
|
||||
type MapTextures struct {
|
||||
context *render.Context
|
||||
|
||||
program uint32
|
||||
vao *opengl.VertexArrayObject
|
||||
vertexPositionBuffer uint32
|
||||
vertexPositionAttrib int32
|
||||
modelMatrixUniform opengl.Matrix4Uniform
|
||||
viewMatrixUniform opengl.Matrix4Uniform
|
||||
projectionMatrixUniform opengl.Matrix4Uniform
|
||||
uvMatrixUniform opengl.Matrix4Uniform
|
||||
|
||||
paletteUniform int32
|
||||
bitmapUniform int32
|
||||
|
||||
textureQuery TextureQuery
|
||||
|
||||
lastTileType level.TileType
|
||||
}
|
||||
|
||||
type tileTypeVertices struct {
|
||||
vertices []float32
|
||||
equivalentType level.TileType
|
||||
}
|
||||
|
||||
var tileTypeVerticesLookup []tileTypeVertices
|
||||
var uvRotations [4]*mgl.Mat4
|
||||
|
||||
func init() {
|
||||
for i := 0; i < 4; i++ {
|
||||
matrix := mgl.Translate3D(0.5, 0.5, 0.0).
|
||||
Mul4(mgl.HomogRotate3DZ(math.Pi * float32(i) / -2.0)).
|
||||
Mul4(mgl.Translate3D(-0.5, -0.5, 0.0)).
|
||||
Mul4(mgl.Scale3D(1.0, -1.0, 1.0))
|
||||
uvRotations[i] = &matrix
|
||||
}
|
||||
|
||||
for _, tileType := range level.TileTypes() {
|
||||
for int(tileType) >= len(tileTypeVerticesLookup) {
|
||||
tileTypeVerticesLookup = append(tileTypeVerticesLookup, tileTypeVertices{equivalentType: level.TileTypeSolid})
|
||||
}
|
||||
switch tileType {
|
||||
case level.TileTypeSolid:
|
||||
case level.TileTypeDiagonalOpenNorthEast:
|
||||
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
|
||||
vertices: []float32{
|
||||
0.0, 1.0, 0.0,
|
||||
1.0, 1.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
},
|
||||
equivalentType: level.TileTypeDiagonalOpenNorthEast,
|
||||
}
|
||||
case level.TileTypeDiagonalOpenNorthWest:
|
||||
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
|
||||
vertices: []float32{
|
||||
0.0, 1.0, 0.0,
|
||||
1.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
},
|
||||
equivalentType: level.TileTypeDiagonalOpenNorthWest,
|
||||
}
|
||||
case level.TileTypeDiagonalOpenSouthEast:
|
||||
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
|
||||
vertices: []float32{
|
||||
1.0, 1.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
},
|
||||
equivalentType: level.TileTypeDiagonalOpenSouthEast,
|
||||
}
|
||||
case level.TileTypeDiagonalOpenSouthWest:
|
||||
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
|
||||
vertices: []float32{
|
||||
0.0, 1.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
},
|
||||
equivalentType: level.TileTypeDiagonalOpenSouthWest,
|
||||
}
|
||||
default:
|
||||
tileTypeVerticesLookup[int(tileType)] = tileTypeVertices{
|
||||
vertices: []float32{
|
||||
0.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
1.0, 1.0, 0.0,
|
||||
|
||||
1.0, 1.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
},
|
||||
equivalentType: level.TileTypeOpen,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapTextures returns a new instance of a renderable for tile map textures.
|
||||
func NewMapTextures(context *render.Context, textureQuery TextureQuery) *MapTextures {
|
||||
gl := context.OpenGL
|
||||
program, programErr := opengl.LinkNewStandardProgram(gl, mapTexturesVertexShaderSource, mapTexturesFragmentShaderSource)
|
||||
if programErr != nil {
|
||||
panic(opengl.NamedShaderError{Name: "MapTexturesShader", Nested: programErr})
|
||||
}
|
||||
renderable := &MapTextures{
|
||||
context: context,
|
||||
program: program,
|
||||
|
||||
vao: opengl.NewVertexArrayObject(gl, program),
|
||||
vertexPositionBuffer: gl.GenBuffers(1)[0],
|
||||
vertexPositionAttrib: gl.GetAttribLocation(program, "vertexPosition"),
|
||||
modelMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "modelMatrix")),
|
||||
viewMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "viewMatrix")),
|
||||
projectionMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "projectionMatrix")),
|
||||
uvMatrixUniform: opengl.Matrix4Uniform(gl.GetUniformLocation(program, "uvMatrix")),
|
||||
paletteUniform: gl.GetUniformLocation(program, "palette"),
|
||||
bitmapUniform: gl.GetUniformLocation(program, "bitmap"),
|
||||
textureQuery: textureQuery,
|
||||
lastTileType: level.TileTypeSolid,
|
||||
}
|
||||
|
||||
renderable.vao.WithSetter(func(gl opengl.OpenGL) {
|
||||
gl.EnableVertexAttribArray(uint32(renderable.vertexPositionAttrib))
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
|
||||
gl.VertexAttribOffset(uint32(renderable.vertexPositionAttrib), 3, opengl.FLOAT, false, 0, 0)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
})
|
||||
|
||||
return renderable
|
||||
}
|
||||
|
||||
// Dispose releases any internal resources.
|
||||
func (renderable *MapTextures) Dispose() {
|
||||
gl := renderable.context.OpenGL
|
||||
|
||||
renderable.vao.Dispose()
|
||||
gl.DeleteProgram(renderable.program)
|
||||
gl.DeleteBuffers([]uint32{renderable.vertexPositionBuffer})
|
||||
}
|
||||
|
||||
// Render renders the textures.
|
||||
func (renderable *MapTextures) Render(columns, rows int, tileTextureQuery TileTextureQuery, paletteTexture *graphics.PaletteTexture) {
|
||||
gl := renderable.context.OpenGL
|
||||
|
||||
renderable.vao.OnShader(func() {
|
||||
renderable.viewMatrixUniform.Set(gl, renderable.context.ViewMatrix)
|
||||
renderable.projectionMatrixUniform.Set(gl, &renderable.context.ProjectionMatrix)
|
||||
|
||||
textureUnit := int32(0)
|
||||
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
|
||||
gl.BindTexture(opengl.TEXTURE_2D, paletteTexture.Handle())
|
||||
gl.Uniform1i(renderable.paletteUniform, textureUnit)
|
||||
|
||||
textureUnit = 1
|
||||
gl.ActiveTexture(opengl.TEXTURE0 + uint32(textureUnit))
|
||||
gl.Uniform1i(renderable.bitmapUniform, textureUnit)
|
||||
|
||||
scaling := mgl.Scale3D(level.FineCoordinatesPerTileSide, level.FineCoordinatesPerTileSide, 1.0)
|
||||
for y := 0; y < rows; y++ {
|
||||
for x := 0; x < columns; x++ {
|
||||
tileType, textureIndex, textureRotations := tileTextureQuery(level.TilePosition{X: byte(x), Y: byte(y)})
|
||||
if tileType == level.TileTypeSolid {
|
||||
continue
|
||||
}
|
||||
texture, _ := renderable.textureQuery(textureIndex)
|
||||
if texture == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
modelMatrix := mgl.Translate3D(float32(x)*level.FineCoordinatesPerTileSide, float32(y)*level.FineCoordinatesPerTileSide, 0.0).
|
||||
Mul4(scaling)
|
||||
|
||||
uvMatrix := uvRotations[textureRotations]
|
||||
renderable.uvMatrixUniform.Set(gl, uvMatrix)
|
||||
renderable.modelMatrixUniform.Set(gl, &modelMatrix)
|
||||
gl.BindTexture(opengl.TEXTURE_2D, texture.Handle())
|
||||
renderable.renderTileType(tileType)
|
||||
}
|
||||
}
|
||||
|
||||
gl.BindTexture(opengl.TEXTURE_2D, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func (renderable *MapTextures) renderTileType(tileType level.TileType) {
|
||||
info := tileTypeVerticesLookup[int(tileType)]
|
||||
vertexCount := len(info.vertices) / 3
|
||||
gl := renderable.context.OpenGL
|
||||
if info.equivalentType != renderable.lastTileType {
|
||||
renderable.lastTileType = info.equivalentType
|
||||
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, renderable.vertexPositionBuffer)
|
||||
gl.BufferData(opengl.ARRAY_BUFFER, len(info.vertices)*4, info.vertices, opengl.STATIC_DRAW)
|
||||
gl.BindBuffer(opengl.ARRAY_BUFFER, 0)
|
||||
}
|
||||
gl.DrawArrays(opengl.TRIANGLES, 0, int32(vertexCount))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
package levels
|
||||
|
||||
type objectsViewModel struct {
|
||||
restoreFocus bool
|
||||
windowOpen bool
|
||||
}
|
||||
|
||||
func freshObjectsViewModel() objectsViewModel {
|
||||
return objectsViewModel{}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
)
|
||||
|
||||
// TextureDisplay is an enumeration which texture to display in a 2D map view.
|
||||
type TextureDisplay int
|
||||
|
||||
// TextureDisplay constants are listed below.
|
||||
const (
|
||||
TextureDisplayFloor TextureDisplay = 0
|
||||
TextureDisplayWall TextureDisplay = 1
|
||||
TextureDisplayCeiling TextureDisplay = 2
|
||||
)
|
||||
|
||||
// String returns a textual representation.
|
||||
func (display TextureDisplay) String() string {
|
||||
switch display {
|
||||
case TextureDisplayFloor:
|
||||
return "Floor"
|
||||
case TextureDisplayWall:
|
||||
return "Wall"
|
||||
case TextureDisplayCeiling:
|
||||
return "Ceiling"
|
||||
default:
|
||||
return fmt.Sprintf("Unknown%d", int(display))
|
||||
}
|
||||
}
|
||||
|
||||
// TextureDisplays returns all TextureDisplay constants.
|
||||
func TextureDisplays() []TextureDisplay {
|
||||
return []TextureDisplay{TextureDisplayFloor, TextureDisplayWall, TextureDisplayCeiling}
|
||||
}
|
||||
|
||||
// TextureDisplayFunc is one that resolves texture properties.
|
||||
type TextureDisplayFunc func(*level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int)
|
||||
|
||||
// FloorTexture returns the properties for the floor.
|
||||
func FloorTexture(tile *level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int) {
|
||||
return tile.TextureInfo.FloorTextureIndex(), tile.Floor.TextureRotations()
|
||||
}
|
||||
|
||||
// CeilingTexture returns the properties for the ceiling.
|
||||
func CeilingTexture(tile *level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int) {
|
||||
return tile.TextureInfo.CeilingTextureIndex(), tile.Ceiling.TextureRotations()
|
||||
}
|
||||
|
||||
// WallTexture returns the properties for the floor.
|
||||
func WallTexture(tile *level.TileMapEntry) (atlasIndex level.AtlasIndex, textureRotations int) {
|
||||
return tile.TextureInfo.WallTextureIndex(), 0
|
||||
}
|
||||
|
||||
// Func returns the display func for the current display setting.
|
||||
func (display TextureDisplay) Func() TextureDisplayFunc {
|
||||
if display == TextureDisplayFloor {
|
||||
return FloorTexture
|
||||
} else if display == TextureDisplayCeiling {
|
||||
return CeilingTexture
|
||||
}
|
||||
return WallTexture
|
||||
}
|
||||
@@ -0,0 +1,551 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/inkyblackness/imgui-go/v3"
|
||||
|
||||
"github.com/inkyblackness/hacked/editor/graphics"
|
||||
"github.com/inkyblackness/hacked/editor/render"
|
||||
"github.com/inkyblackness/hacked/editor/values"
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
"github.com/inkyblackness/hacked/ss1/content/bitmap"
|
||||
"github.com/inkyblackness/hacked/ss1/content/text"
|
||||
"github.com/inkyblackness/hacked/ss1/edit"
|
||||
"github.com/inkyblackness/hacked/ss1/edit/undoable/cmd"
|
||||
"github.com/inkyblackness/hacked/ss1/resource"
|
||||
"github.com/inkyblackness/hacked/ss1/world"
|
||||
"github.com/inkyblackness/hacked/ss1/world/ids"
|
||||
)
|
||||
|
||||
// TilesView is for tile properties.
|
||||
type TilesView struct {
|
||||
editor *edit.LevelEditorService
|
||||
textCache *text.Cache
|
||||
textureCache *graphics.TextureCache
|
||||
|
||||
guiScale float32
|
||||
registry cmd.Registry
|
||||
model tilesViewModel
|
||||
}
|
||||
|
||||
// NewTilesView returns a new instance.
|
||||
func NewTilesView(editor *edit.LevelEditorService,
|
||||
guiScale float32, textCache *text.Cache, textureCache *graphics.TextureCache, registry cmd.Registry) *TilesView {
|
||||
view := &TilesView{
|
||||
editor: editor,
|
||||
textCache: textCache,
|
||||
textureCache: textureCache,
|
||||
|
||||
guiScale: guiScale,
|
||||
model: freshTilesViewModel(),
|
||||
registry: registry,
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
// WindowOpen returns the flag address, to be used with the main menu.
|
||||
func (view *TilesView) WindowOpen() *bool {
|
||||
return &view.model.windowOpen
|
||||
}
|
||||
|
||||
// TextureDisplay returns the current setting which textures should be displayed.
|
||||
func (view TilesView) TextureDisplay() TextureDisplay {
|
||||
return view.model.textureDisplay
|
||||
}
|
||||
|
||||
// ColorDisplay returns the current setting which colors should be displayed.
|
||||
func (view TilesView) ColorDisplay() ColorDisplay {
|
||||
lvl := view.editor.Level()
|
||||
if lvl.IsCyberspace() {
|
||||
return view.model.cyberColorDisplay
|
||||
}
|
||||
return view.model.shadowDisplay
|
||||
}
|
||||
|
||||
// Render renders the view.
|
||||
func (view *TilesView) Render() {
|
||||
if view.model.restoreFocus {
|
||||
imgui.SetNextWindowFocus()
|
||||
view.model.restoreFocus = false
|
||||
view.model.windowOpen = true
|
||||
}
|
||||
if view.model.windowOpen {
|
||||
lvl := view.editor.Level()
|
||||
tiles := view.editor.Tiles()
|
||||
readOnly := view.editor.IsReadOnly()
|
||||
title := fmt.Sprintf("Level Tiles, %d selected", len(tiles))
|
||||
if readOnly {
|
||||
title += hintReadOnly
|
||||
}
|
||||
imgui.SetNextWindowSizeV(imgui.Vec2{X: 400 * view.guiScale, Y: 500 * view.guiScale}, imgui.ConditionFirstUseEver)
|
||||
if imgui.BeginV(title+"###Level Tiles", view.WindowOpen(), 0) {
|
||||
view.renderContent(lvl, tiles, readOnly)
|
||||
}
|
||||
imgui.End()
|
||||
}
|
||||
}
|
||||
|
||||
func (view *TilesView) renderContent(lvl *level.Level, tiles []*level.TileMapEntry, readOnly bool) {
|
||||
isCyberspace := lvl.IsCyberspace()
|
||||
tileTypeUnifier := values.NewUnifier()
|
||||
floorHeightUnifier := values.NewUnifier()
|
||||
ceilingHeightUnifier := values.NewUnifier()
|
||||
slopeHeightUnifier := values.NewUnifier()
|
||||
slopeControlUnifier := values.NewUnifier()
|
||||
musicZoneUnifier := values.NewUnifier()
|
||||
|
||||
floorPaletteIndexUnifier := values.NewUnifier()
|
||||
ceilingPaletteIndexUnifier := values.NewUnifier()
|
||||
flightPullTypeUnifier := values.NewUnifier()
|
||||
gameOfLightStateUnifier := values.NewUnifier()
|
||||
|
||||
floorTextureIndexUnifier := values.NewUnifier()
|
||||
floorTextureRotationsUnifier := values.NewUnifier()
|
||||
ceilingTextureIndexUnifier := values.NewUnifier()
|
||||
ceilingTextureRotationsUnifier := values.NewUnifier()
|
||||
wallTextureIndexUnifier := values.NewUnifier()
|
||||
wallTextureOffsetUnifier := values.NewUnifier()
|
||||
useAdjacentWallTextureUnifier := values.NewUnifier()
|
||||
wallTexturePatternUnifier := values.NewUnifier()
|
||||
floorLightUnifier := values.NewUnifier()
|
||||
floorLightDeltaUnifier := values.NewUnifier()
|
||||
ceilingLightUnifier := values.NewUnifier()
|
||||
ceilingLightDeltaUnifier := values.NewUnifier()
|
||||
deconstructedUnifier := values.NewUnifier()
|
||||
perildUnifier := values.NewUnifier()
|
||||
floorHazardUnifier := values.NewUnifier()
|
||||
ceilingHazardUnifier := values.NewUnifier()
|
||||
|
||||
for _, tile := range tiles {
|
||||
tileTypeUnifier.Add(tile.Type)
|
||||
floorHeightUnifier.Add(tile.Floor.AbsoluteHeight())
|
||||
ceilingHeightUnifier.Add(tile.Ceiling.AbsoluteHeight())
|
||||
slopeHeightUnifier.Add(tile.SlopeHeight)
|
||||
slopeControlUnifier.Add(tile.Flags.SlopeControl())
|
||||
musicZoneUnifier.Add(tile.Flags.MusicZone())
|
||||
perildUnifier.Add(tile.Flags.Peril())
|
||||
if isCyberspace {
|
||||
floorPaletteIndexUnifier.Add(tile.TextureInfo.FloorPaletteIndex())
|
||||
ceilingPaletteIndexUnifier.Add(tile.TextureInfo.CeilingPaletteIndex())
|
||||
flightPullTypeUnifier.Add(tile.Flags.ForCyberspace().FlightPull())
|
||||
gameOfLightStateUnifier.Add(tile.Flags.ForCyberspace().GameOfLifeState())
|
||||
} else {
|
||||
flags := tile.Flags.ForRealWorld()
|
||||
floorTextureIndexUnifier.Add(int(tile.TextureInfo.FloorTextureIndex()))
|
||||
floorTextureRotationsUnifier.Add(tile.Floor.TextureRotations())
|
||||
ceilingTextureIndexUnifier.Add(int(tile.TextureInfo.CeilingTextureIndex()))
|
||||
ceilingTextureRotationsUnifier.Add(tile.Ceiling.TextureRotations())
|
||||
wallTextureIndexUnifier.Add(int(tile.TextureInfo.WallTextureIndex()))
|
||||
wallTextureOffsetUnifier.Add(flags.WallTextureOffset())
|
||||
useAdjacentWallTextureUnifier.Add(flags.UseAdjacentWallTexture())
|
||||
wallTexturePatternUnifier.Add(flags.WallTexturePattern())
|
||||
floorLightUnifier.Add(level.GradesOfShadow - 1 - flags.FloorShadow())
|
||||
floorLightDeltaUnifier.Add(tile.LightDelta.OfFloor())
|
||||
ceilingLightUnifier.Add(level.GradesOfShadow - 1 - flags.CeilingShadow())
|
||||
ceilingLightDeltaUnifier.Add(tile.LightDelta.OfCeiling())
|
||||
deconstructedUnifier.Add(flags.Deconstructed())
|
||||
floorHazardUnifier.Add(tile.Floor.HasHazard())
|
||||
ceilingHazardUnifier.Add(tile.Ceiling.HasHazard())
|
||||
}
|
||||
}
|
||||
|
||||
imgui.PushItemWidth(-250 * view.guiScale)
|
||||
|
||||
_, _, levelHeight := lvl.Size()
|
||||
tileHeightFormatter := tileHeightFormatterFor(levelHeight)
|
||||
|
||||
tileTypes := level.TileTypes()
|
||||
values.RenderUnifiedCombo(readOnly, "Tile Type", tileTypeUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileType)) },
|
||||
func(value int) string { return tileTypes[value].String() },
|
||||
len(tileTypes),
|
||||
func(newValue int) { view.changeTiles(setTileTypeTo(level.TileType(newValue))) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Floor Height", floorHeightUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
|
||||
tileHeightFormatter,
|
||||
0, int(level.TileHeightUnitMax)-1,
|
||||
func(newValue int) { view.changeTiles(setFloorHeightTo(level.TileHeightUnit(newValue))) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Ceiling Height (abs)", ceilingHeightUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
|
||||
tileHeightFormatter,
|
||||
1, int(level.TileHeightUnitMax),
|
||||
func(newValue int) { view.changeTiles(setCeilingHeightTo(level.TileHeightUnit(newValue))) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Slope Height", slopeHeightUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
|
||||
tileHeightFormatter,
|
||||
0, int(level.TileHeightUnitMax)-1,
|
||||
func(newValue int) { view.changeTiles(setSlopeHeightTo(level.TileHeightUnit(newValue))) })
|
||||
slopeControls := level.TileSlopeControls()
|
||||
values.RenderUnifiedCombo(readOnly, "Slope Control", slopeControlUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileSlopeControl)) },
|
||||
func(value int) string { return slopeControls[value].String() },
|
||||
len(slopeControls),
|
||||
func(newValue int) { view.changeTiles(setSlopeControlTo(slopeControls[newValue])) })
|
||||
musicZones := level.TileMusicZones()
|
||||
values.RenderUnifiedCombo(readOnly, "Music Zone", musicZoneUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileMusicZone)) },
|
||||
func(value int) string { return musicZones[value].String() },
|
||||
len(musicZones),
|
||||
func(newValue int) { view.changeTiles(setMusicZoneTo(musicZones[newValue])) })
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Music Peril", perildUnifier,
|
||||
func(newValue bool) { view.changeTiles(setPerilTo(newValue)) })
|
||||
|
||||
imgui.Separator()
|
||||
|
||||
if isCyberspace {
|
||||
if imgui.BeginCombo("Color View", view.model.cyberColorDisplay.String()) {
|
||||
displays := ColorDisplays()
|
||||
for _, display := range displays {
|
||||
displayString := display.String()
|
||||
|
||||
if imgui.SelectableV(displayString, display == view.model.cyberColorDisplay, 0, imgui.Vec2{}) {
|
||||
view.model.cyberColorDisplay = display
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
|
||||
values.RenderUnifiedSliderInt(readOnly, "Floor Color", floorPaletteIndexUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(byte)) },
|
||||
func(value int) string { return "%d" },
|
||||
0, bitmap.PaletteSize-1,
|
||||
func(newValue int) { view.changeTiles(setFloorPaletteIndexTo(newValue)) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Ceiling Color", ceilingPaletteIndexUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(byte)) },
|
||||
func(value int) string { return "%d" },
|
||||
0, bitmap.PaletteSize-1,
|
||||
func(newValue int) { view.changeTiles(setCeilingPaletteIndexTo(newValue)) })
|
||||
|
||||
imgui.Separator()
|
||||
|
||||
flightPulls := level.CyberspaceFlightPulls()
|
||||
values.RenderUnifiedCombo(readOnly, "Flight Pull Type", flightPullTypeUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.CyberspaceFlightPull)) },
|
||||
func(value int) string { return flightPulls[value].String() },
|
||||
len(flightPulls),
|
||||
func(newValue int) { view.changeTiles(setFlightPullTypeTo(flightPulls[newValue])) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Game Of Life State", gameOfLightStateUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, 3,
|
||||
func(newValue int) { view.changeTiles(setGameOfLightStateTo(newValue)) })
|
||||
} else {
|
||||
atlas := lvl.TextureAtlas()
|
||||
|
||||
if imgui.BeginCombo("Texture View", view.model.textureDisplay.String()) {
|
||||
displays := TextureDisplays()
|
||||
for _, display := range displays {
|
||||
displayString := display.String()
|
||||
|
||||
if imgui.SelectableV(displayString, display == view.model.textureDisplay, 0, imgui.Vec2{}) {
|
||||
view.model.textureDisplay = display
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
|
||||
values.RenderUnifiedSliderInt(readOnly, "Floor Texture (atlas index)", floorTextureIndexUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, level.FloorCeilingTextureLimit-1,
|
||||
func(newValue int) { view.changeTiles(setFloorTextureIndexTo(level.AtlasIndex(newValue))) })
|
||||
view.renderTextureSelector(readOnly, "Floor Texture", floorTextureIndexUnifier, atlas, 0, level.FloorCeilingTextureLimit-1,
|
||||
func(newValue int) { view.changeTiles(setFloorTextureIndexTo(level.AtlasIndex(newValue))) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Floor Texture Rotations", floorTextureRotationsUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, 3,
|
||||
func(newValue int) { view.changeTiles(setFloorTextureRotationsTo(newValue)) })
|
||||
|
||||
values.RenderUnifiedSliderInt(readOnly, "Ceiling Texture (atlas index)", ceilingTextureIndexUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, level.FloorCeilingTextureLimit-1,
|
||||
func(newValue int) { view.changeTiles(setCeilingTextureIndexTo(level.AtlasIndex(newValue))) })
|
||||
view.renderTextureSelector(readOnly, "Ceiling Texture", ceilingTextureIndexUnifier, atlas, 0, level.FloorCeilingTextureLimit-1,
|
||||
func(newValue int) { view.changeTiles(setCeilingTextureIndexTo(level.AtlasIndex(newValue))) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Ceiling Texture Rotations", ceilingTextureRotationsUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, 3,
|
||||
func(newValue int) { view.changeTiles(setCeilingTextureRotationsTo(newValue)) })
|
||||
|
||||
values.RenderUnifiedSliderInt(readOnly, "Wall Texture (atlas index)", wallTextureIndexUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, len(atlas)-1,
|
||||
func(newValue int) { view.changeTiles(setWallTextureIndexTo(level.AtlasIndex(newValue))) })
|
||||
view.renderTextureSelector(readOnly, "Wall Texture", wallTextureIndexUnifier, atlas, 0, len(atlas)-1,
|
||||
func(newValue int) { view.changeTiles(setWallTextureIndexTo(level.AtlasIndex(newValue))) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Wall Texture Offset", wallTextureOffsetUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.TileHeightUnit)) },
|
||||
tileHeightFormatter,
|
||||
0, int(level.TileHeightUnitMax)-1,
|
||||
func(newValue int) { view.changeTiles(setWallTextureOffsetTo(level.TileHeightUnit(newValue))) })
|
||||
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Use Adjacent Wall Texture", useAdjacentWallTextureUnifier,
|
||||
func(newValue bool) { view.changeTiles(setUseAdjacentWallTextureTo(newValue)) })
|
||||
wallTexturePatterns := level.WallTexturePatterns()
|
||||
values.RenderUnifiedCombo(readOnly, "Wall Texture Pattern", wallTexturePatternUnifier,
|
||||
func(u values.Unifier) int { return int(u.Unified().(level.WallTexturePattern)) },
|
||||
func(value int) string { return wallTexturePatterns[value].String() },
|
||||
len(wallTexturePatterns),
|
||||
func(newValue int) { view.changeTiles(setWallTexturePatternTo(wallTexturePatterns[newValue])) })
|
||||
|
||||
imgui.Separator()
|
||||
|
||||
if imgui.BeginCombo("Shadow View", view.model.shadowDisplay.String()) {
|
||||
displays := ColorDisplays()
|
||||
for _, display := range displays {
|
||||
displayString := display.String()
|
||||
|
||||
if imgui.SelectableV(displayString, display == view.model.shadowDisplay, 0, imgui.Vec2{}) {
|
||||
view.model.shadowDisplay = display
|
||||
}
|
||||
}
|
||||
imgui.EndCombo()
|
||||
}
|
||||
|
||||
values.RenderUnifiedSliderInt(readOnly, "Floor Light", floorLightUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, level.GradesOfShadow-1,
|
||||
func(newValue int) { view.changeTiles(setFloorLightTo(newValue)) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Floor Light Delta", floorLightDeltaUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, level.GradesOfShadow-1,
|
||||
func(newValue int) { view.changeTiles(setFloorLightDeltaTo(newValue)) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Ceiling Light", ceilingLightUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, level.GradesOfShadow-1,
|
||||
func(newValue int) { view.changeTiles(setCeilingLightTo(newValue)) })
|
||||
values.RenderUnifiedSliderInt(readOnly, "Ceiling Light Delta", ceilingLightDeltaUnifier,
|
||||
func(u values.Unifier) int { return u.Unified().(int) },
|
||||
func(value int) string { return "%d" },
|
||||
0, level.GradesOfShadow-1,
|
||||
func(newValue int) { view.changeTiles(setCeilingLightDeltaTo(newValue)) })
|
||||
|
||||
imgui.Separator()
|
||||
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Deconstructed", deconstructedUnifier,
|
||||
func(newValue bool) { view.changeTiles(setDeconstructedTo(newValue)) })
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Floor Hazard", floorHazardUnifier,
|
||||
func(newValue bool) { view.changeTiles(setFloorHazardTo(newValue)) })
|
||||
values.RenderUnifiedCheckboxCombo(readOnly, "Ceiling Hazard", ceilingHazardUnifier,
|
||||
func(newValue bool) { view.changeTiles(setCeilingHazardTo(newValue)) })
|
||||
}
|
||||
|
||||
imgui.PopItemWidth()
|
||||
}
|
||||
|
||||
func (view *TilesView) renderTextureSelector(readOnly bool, label string, unifier values.Unifier,
|
||||
atlas level.TextureAtlas, minIndex, maxIndex int, changeHandler func(int)) {
|
||||
selectedIndex := -1
|
||||
if unifier.IsUnique() {
|
||||
selectedIndex = unifier.Unified().(int)
|
||||
}
|
||||
|
||||
count := maxIndex - minIndex + 1
|
||||
if count > len(atlas) {
|
||||
count = len(atlas)
|
||||
}
|
||||
render.TextureSelector(label, -1, view.guiScale, count, selectedIndex-minIndex,
|
||||
view.textureCache,
|
||||
func(index int) resource.Key {
|
||||
return resource.KeyOf(ids.LargeTextures.Plus(int(atlas[minIndex+index])), resource.LangAny, 0)
|
||||
},
|
||||
func(index int) string { return view.textureName(int(atlas[minIndex+index])) },
|
||||
func(index int) {
|
||||
if !readOnly {
|
||||
changeHandler(index)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (view *TilesView) textureName(index int) string {
|
||||
key := resource.KeyOf(ids.TextureNames, resource.LangDefault, index)
|
||||
name, err := view.textCache.Text(key)
|
||||
suffix := ""
|
||||
if err == nil {
|
||||
suffix = ": " + name
|
||||
}
|
||||
return fmt.Sprintf("%3d", index) + suffix
|
||||
}
|
||||
|
||||
func setTileTypeTo(tileType level.TileType) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Type = tileType
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorHeightTo(height level.TileHeightUnit) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Floor = tile.Floor.WithAbsoluteHeight(height)
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingHeightTo(height level.TileHeightUnit) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Ceiling = tile.Ceiling.WithAbsoluteHeight(height)
|
||||
}
|
||||
}
|
||||
|
||||
func setSlopeHeightTo(height level.TileHeightUnit) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.SlopeHeight = height
|
||||
}
|
||||
}
|
||||
|
||||
func setSlopeControlTo(value level.TileSlopeControl) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.WithSlopeControl(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setPerilTo(value bool) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.WithPeril(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setMusicZoneTo(value level.TileMusicZone) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.WithMusicZone(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorTextureIndexTo(value level.AtlasIndex) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.TextureInfo = tile.TextureInfo.WithFloorTextureIndex(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorTextureRotationsTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Floor = tile.Floor.WithTextureRotations(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingTextureIndexTo(value level.AtlasIndex) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.TextureInfo = tile.TextureInfo.WithCeilingTextureIndex(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingTextureRotationsTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Ceiling = tile.Ceiling.WithTextureRotations(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setWallTextureIndexTo(value level.AtlasIndex) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.TextureInfo = tile.TextureInfo.WithWallTextureIndex(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setWallTextureOffsetTo(value level.TileHeightUnit) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForRealWorld().WithWallTextureOffset(value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setUseAdjacentWallTextureTo(value bool) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForRealWorld().WithUseAdjacentWallTexture(value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setWallTexturePatternTo(value level.WallTexturePattern) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForRealWorld().WithWallTexturePattern(value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorLightTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForRealWorld().WithFloorShadow(level.GradesOfShadow - 1 - value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorLightDeltaTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.LightDelta = tile.LightDelta.WithFloor(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingLightTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForRealWorld().WithCeilingShadow(level.GradesOfShadow - 1 - value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingLightDeltaTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.LightDelta = tile.LightDelta.WithCeiling(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setDeconstructedTo(value bool) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForRealWorld().WithDeconstructed(value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorHazardTo(value bool) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Floor = tile.Floor.WithHazard(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingHazardTo(value bool) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Ceiling = tile.Ceiling.WithHazard(value)
|
||||
}
|
||||
}
|
||||
|
||||
func setFloorPaletteIndexTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.TextureInfo = tile.TextureInfo.WithFloorPaletteIndex(byte(value))
|
||||
}
|
||||
}
|
||||
|
||||
func setCeilingPaletteIndexTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.TextureInfo = tile.TextureInfo.WithCeilingPaletteIndex(byte(value))
|
||||
}
|
||||
}
|
||||
|
||||
func setFlightPullTypeTo(value level.CyberspaceFlightPull) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForCyberspace().WithFlightPull(value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
func setGameOfLightStateTo(value int) tileMapEntryModifier {
|
||||
return func(tile *level.TileMapEntry) {
|
||||
tile.Flags = tile.Flags.ForCyberspace().WithGameOfLifeState(value).AsTileFlag()
|
||||
}
|
||||
}
|
||||
|
||||
type tileMapEntryModifier func(*level.TileMapEntry)
|
||||
|
||||
func (view *TilesView) changeTiles(modifier tileMapEntryModifier) {
|
||||
err := view.registry.Register(cmd.Named("ChangeTiles"),
|
||||
cmd.Forward(view.restoreFocusTask()),
|
||||
cmd.Nested(func() error { return view.editor.ChangeTiles(modifier) }),
|
||||
cmd.Reverse(view.restoreFocusTask()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (view *TilesView) restoreFocusTask() cmd.Task {
|
||||
return func(modder world.Modder) error {
|
||||
view.model.restoreFocus = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package levels
|
||||
|
||||
type tilesViewModel struct {
|
||||
textureDisplay TextureDisplay
|
||||
shadowDisplay ColorDisplay
|
||||
cyberColorDisplay ColorDisplay
|
||||
|
||||
restoreFocus bool
|
||||
windowOpen bool
|
||||
}
|
||||
|
||||
func freshTilesViewModel() tilesViewModel {
|
||||
return tilesViewModel{
|
||||
textureDisplay: TextureDisplayFloor,
|
||||
shadowDisplay: ColorDisplayNone,
|
||||
cyberColorDisplay: ColorDisplayNone,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package levels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/inkyblackness/hacked/ss1/content/archive/level"
|
||||
)
|
||||
|
||||
type hazardFormatter func(int) string
|
||||
|
||||
func rawHazardValue(int) string {
|
||||
return "raw: %d"
|
||||
}
|
||||
|
||||
func lbpHazardValue(value int) string {
|
||||
return fmt.Sprintf("%3.1f LBP - raw: %%d", float32(value)/2)
|
||||
}
|
||||
|
||||
func percentHazardValue(value int) string {
|
||||
return fmt.Sprintf("%v%%%% - raw: %%d", value*25)
|
||||
}
|
||||
|
||||
type ceilingHazardInfo struct {
|
||||
title string
|
||||
radiationRegister bool
|
||||
formatter hazardFormatter
|
||||
}
|
||||
|
||||
var ceilingHazards = []ceilingHazardInfo{
|
||||
{title: "Off", radiationRegister: false, formatter: rawHazardValue},
|
||||
{title: "Radiation", radiationRegister: true, formatter: lbpHazardValue},
|
||||
}
|
||||
|
||||
func currentCeilingHazard(param *level.Parameters) ceilingHazardInfo {
|
||||
radiationRegister := param.RadiationRegister > 1
|
||||
if radiationRegister {
|
||||
return ceilingHazards[1]
|
||||
}
|
||||
return ceilingHazards[0]
|
||||
}
|
||||
|
||||
type floorHazardInfo struct {
|
||||
title string
|
||||
biohazardRegister bool
|
||||
isGravity bool
|
||||
formatter hazardFormatter
|
||||
}
|
||||
|
||||
var floorHazards = []floorHazardInfo{
|
||||
{title: "Off", biohazardRegister: false, isGravity: false, formatter: rawHazardValue},
|
||||
{title: "Gravity", biohazardRegister: false, isGravity: true, formatter: percentHazardValue},
|
||||
{title: "Biohazard", biohazardRegister: true, isGravity: false, formatter: lbpHazardValue},
|
||||
}
|
||||
|
||||
func currentFloorHazard(param *level.Parameters) floorHazardInfo {
|
||||
biohazardRegister := param.BiohazardRegister > 1
|
||||
isGravity := param.FloorHazardIsGravity != 0
|
||||
if isGravity {
|
||||
return floorHazards[1]
|
||||
} else if biohazardRegister {
|
||||
return floorHazards[2]
|
||||
}
|
||||
return floorHazards[0]
|
||||
}
|
||||
Reference in New Issue
Block a user