diff --git a/s/dom/elements/app.ts b/s/dom/elements/app.ts index dbcd1fd..0aefd8b 100644 --- a/s/dom/elements/app.ts +++ b/s/dom/elements/app.ts @@ -1,11 +1,11 @@ import {nexus} from "../nexus.js" import styles from "./app.css.js" -import {MapEditor} from "../views/map-editor.js" +import {MapEditorView} from "../views/map-editor.js" export const TinyforgeApp = nexus.shadow_component(use => { use.styles(styles) - return MapEditor([]) + return MapEditorView([]) }) diff --git a/s/dom/views/map-editor.css.ts b/s/dom/views/map-editor.css.ts index 0ebc99f..c55042b 100644 --- a/s/dom/views/map-editor.css.ts +++ b/s/dom/views/map-editor.css.ts @@ -4,7 +4,9 @@ import {css} from "@benev/slate" export default css` .easel { - display: block; + display: flex; + justify-content: center; + align-items: center; position: relative; width: 100%; height: 100%; @@ -14,11 +16,12 @@ canvas { width: 100%; height: 100%; background: #222; + outline: none; } .sidebar { position: absolute; - width: 3em; + width: 10%; height: 100%; background: #333a; diff --git a/s/dom/views/map-editor.ts b/s/dom/views/map-editor.ts index e0d03ca..d5d51d9 100644 --- a/s/dom/views/map-editor.ts +++ b/s/dom/views/map-editor.ts @@ -3,15 +3,51 @@ import {html} from "@benev/slate" import {nexus} from "../nexus.js" import styles from "./map-editor.css.js" +import {Editor, editing} from "../../game/editor.js" +import {Bestorage, EffectsPanelData, Stage, op_effect} from "@benev/toolbox" -export const MapEditor = nexus.shadow_view(use => () => { +export const MapEditorView = nexus.shadow_view(use => () => { use.styles(styles) + use.name("map-editor") + + const goods = use.op<{ + stage: Stage, + editor: Editor, + }>() + + use.once(async() => { + await goods.load(async() => { + const stage = await Stage.create({ + background: Stage.backgrounds.black(), + allow_webgpu: false, + webgl_options: { + alpha: false, + desynchronized: true, + preserveDrawingBuffer: false, + powerPreference: "high-performance", + }, + webgpu_options: { + antialias: true, + audioEngine: true, + powerPreference: "high-performance", + }, + bestorage: new Bestorage({ + effects: {}, + resolution: 100, + }), + }) + const editor = await editing(stage) + return {stage, editor} + }) + }) return html`
- - - + ${op_effect.binary(goods.value, ({stage}) => html` + ${stage.porthole.canvas} + + + `)}
` }) diff --git a/s/game/editor.ts b/s/game/editor.ts new file mode 100644 index 0000000..1815cd0 --- /dev/null +++ b/s/game/editor.ts @@ -0,0 +1,99 @@ + +import {Level} from "./level.js" +import {Stage, Vec2, load_glb, scalar} from "@benev/toolbox" +import {ArcRotateCamera, DirectionalLight, HemisphericLight, TransformNode, Vector3} from "@babylonjs/core" + +export type Editor = Awaited> + +const tsize = { + width: 6, + height: 4, +} + +export async function editing(stage: Stage) { + const {scene} = stage + const level = new Level([12, 8]) + const camera = make_camera(stage) + + const assets = await load_glb(scene, "/assets/props.glb") + + const sun = new DirectionalLight("sun", new Vector3(.234, -1, .123), scene) + sun.intensity = 2 + + const hemi = new HemisphericLight("hemi", new Vector3(.213, -1, .345), scene) + hemi.intensity = .2 + + const assetTerrainBlock = assets.transformNodes.find(n => n.name.includes("terrain-block"))! + const originals = new TransformNode("originals", scene) + const terrainBlock = assetTerrainBlock.clone("terrain-block-ready", originals)! + + const worldspace = ([tileX, tileY]: Vec2): Vec2 => { + const [extentX, extentY] = level.extent + const midX = extentX / 2 + const midY = extentY / 2 + const {width} = tsize + const halfwidth = width / 2 + const x = ((tileX - midX) * tsize.width) + halfwidth + const y = ((tileY - midY) * tsize.width) + halfwidth + return [x, y] + } + + level.setTile([0, 0], {kind: "block", elevation: 1}) + level.setTile([1, 1], {kind: "block", elevation: 1}) + level.setTile([2, 2], {kind: "block", elevation: 1}) + + const instances = new TransformNode("instances", scene) + + for (const [tile, [x, y]] of level.loop()) { + switch (tile.kind) { + case "block": { + const [worldX, worldZ] = worldspace([x, y]) + const worldY = tile.elevation * tsize.height + const n = terrainBlock.instantiateHierarchy(instances)! + n.position = new Vector3(worldX, worldY, worldZ) + console.log([worldX, worldY, worldZ]) + break + } + case "ramp": { + break + } + case "corner": { + break + } + } + } + + for (const n of assets.transformNodes) + console.log(n.name) + + stage.gameloop.start() + + return { + level, + camera, + dispose() { + stage.gameloop.stop() + stage.engine.dispose() + stage.scene.dispose() + }, + } +} + +///////////////////////////// + +function make_camera(stage: Stage) { + const alpha = scalar.radians.from.degrees(90) + const beta = scalar.radians.from.degrees(10) + const radius = 70 + const camera = new ArcRotateCamera( + "camera", + alpha, + beta, + radius, + new Vector3(0, 0, tsize.width * 0.5), + stage.scene, + ) + stage.rendering.setCamera(camera) + return camera +} + diff --git a/s/game/level.ts b/s/game/level.ts new file mode 100644 index 0000000..dbffc07 --- /dev/null +++ b/s/game/level.ts @@ -0,0 +1,101 @@ + +import {Vec2, loop2d} from "@benev/toolbox" + +export type Elevation = ( + | 0 // water + | 1 // sand + | 2 // dirt + | 4 // grass + | 5 // rock +) + +export type Kind = "block" | "corner" | "ramp" + +export enum Cardinal { + North, + East, + South, + West, +} + +export enum Ordinal { + NorthWest, + NorthEast, + SouthEast, + SouthWest, +} + +export type Block = { + kind: "block" + elevation: Elevation +} + +export type Corner = { + kind: "corner" + elevation: Elevation + ordinal: Ordinal +} + +export type Ramp = { + kind: "ramp" + elevation: Elevation + cardinal: Cardinal +} + +export type Tile = Block | Corner | Ramp + +export function asTile(tile: T) { + return tile +} + +export class Level { + readonly extent: Vec2 + #tiles: Tile[] + + constructor(extent: Vec2) { + this.extent = extent + this.#tiles = [...loop2d(extent)].map( + () => asTile({kind: "block", elevation: 0}) + ) + } + + tileIndex([x, y]: Vec2) { + const [extentX] = this.extent + return (y * extentX) + x + } + + getTile(vec: Vec2) { + return this.#tiles[this.tileIndex(vec)] + } + + setTile(vec: Vec2, tile: Tile) { + this.#tiles[this.tileIndex(vec)] = tile + } + + overwriteTiles(tiles: Tile[]) { + this.#tiles = tiles + } + + *loop() { + for (const vec of loop2d(this.extent)) { + const tile = this.getTile(vec) + yield [tile, vec] as [Tile, Vec2] + } + } + + save() { + return JSON.stringify({ + version: 0, + extent: this.extent, + tiles: this.#tiles, + }) + } + + static load(json: string) { + const {extent, tiles} = JSON.parse(json) as any + const level = new this(extent) + level.overwriteTiles(tiles) + return new this(extent) + } +} + diff --git a/s/index.css b/s/index.css index 151f92d..85dc428 100644 --- a/s/index.css +++ b/s/index.css @@ -7,14 +7,17 @@ html, body { font-size: 16px; + height: 100%; background: #111; color: #fffc; - height: 100%; } tinyforge-app { + position: absolute; + inset: 0; margin: auto; max-width: 100%; max-height: 100%; aspect-ratio: 16 / 9; } +