Skip to content

Commit

Permalink
first terraforming impl with UI
Browse files Browse the repository at this point in the history
only Raise for the moment
  • Loading branch information
Uriopass committed Dec 14, 2023
1 parent b5e01c5 commit c7b5b12
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 26 deletions.
3 changes: 3 additions & 0 deletions assets/ui/terraform.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 67 additions & 3 deletions geom/src/heightmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
pub type HeightmapChunkID = (u16, u16);

const MIN_HEIGHT: f32 = -40.0;
const MAX_HEIGHT: f32 = 2008.0;

#[derive(Clone)]
pub struct HeightmapChunk<const RESOLUTION: usize, const SIZE: u32> {
Expand Down Expand Up @@ -36,12 +37,14 @@ impl<const RESOLUTION: usize, const SIZE: u32> HeightmapChunk<RESOLUTION, SIZE>
}
}

#[inline]
pub fn rect(id: HeightmapChunkID) -> AABB {
let ll = vec2(id.0 as f32 * SIZE as f32, id.1 as f32 * SIZE as f32);
let ur = ll + vec2(SIZE as f32, SIZE as f32);
AABB::new(ll, ur)
}

#[inline]
pub fn id(v: Vec2) -> HeightmapChunkID {
let x = v.x / SIZE as f32;
let x = x.clamp(0.0, u16::MAX as f32) as u16;
Expand All @@ -50,6 +53,7 @@ impl<const RESOLUTION: usize, const SIZE: u32> HeightmapChunk<RESOLUTION, SIZE>
(x, y)
}

#[inline]
pub fn bbox(&self, origin: Vec2) -> AABB3 {
AABB3::new(
vec3(origin.x, origin.y, MIN_HEIGHT),
Expand All @@ -62,18 +66,21 @@ impl<const RESOLUTION: usize, const SIZE: u32> HeightmapChunk<RESOLUTION, SIZE>
}

/// assume p is in chunk-space and in-bounds
#[inline]
pub fn height_unchecked(&self, p: Vec2) -> f32 {
let v = p / SIZE as f32;
let v = v * RESOLUTION as f32;
self.heights[v.y as usize][v.x as usize]
}

#[inline]
pub fn height(&self, p: Vec2) -> Option<f32> {
let v = p / SIZE as f32;
let v = v * RESOLUTION as f32;
self.heights.get(v.y as usize)?.get(v.x as usize).copied()
}

#[inline]
pub fn heights(&self) -> &[[f32; RESOLUTION]; RESOLUTION] {
&self.heights
}
Expand All @@ -99,31 +106,88 @@ impl<const RESOLUTION: usize, const SIZE: u32> Heightmap<RESOLUTION, SIZE> {
}
}

#[inline]
pub fn bounds(&self) -> AABB {
AABB::new(
vec2(0.0, 0.0),
vec2(self.w as f32 * SIZE as f32, self.h as f32 * SIZE as f32),
)
}

#[inline]
fn check_valid(&self, id: HeightmapChunkID) -> bool {
id.0 < self.w && id.1 < self.h
}

#[inline]
pub fn set_chunk(&mut self, id: HeightmapChunkID, chunk: HeightmapChunk<RESOLUTION, SIZE>) {
if !self.check_valid(id) {
return;
}
self.chunks[(id.0 + id.1 * self.w) as usize] = chunk;
}

#[inline]
pub fn get_chunk(&self, id: HeightmapChunkID) -> Option<&HeightmapChunk<RESOLUTION, SIZE>> {
if !self.check_valid(id) {
return None;
}
unsafe { Some(self.chunks.get_unchecked((id.0 + id.1 * self.w) as usize)) }
}

fn get_chunk_mut(
&mut self,
id: HeightmapChunkID,
) -> Option<&mut HeightmapChunk<RESOLUTION, SIZE>> {
if !self.check_valid(id) {
return None;
}
unsafe {
Some(
self.chunks
.get_unchecked_mut((id.0 + id.1 * self.w) as usize),
)
}
}

/// Applies a function to every point in the heightmap in the given bounds
pub fn apply(&mut self, bounds: AABB, mut f: impl FnMut(Vec3) -> f32) -> Vec<HeightmapChunkID> {
let ll = bounds.ll / SIZE as f32;
let ur = bounds.ur / SIZE as f32;
let ll = vec2(ll.x.floor(), ll.y.floor());
let ur = vec2(ur.x.ceil(), ur.y.ceil());

let mut modified = Vec::with_capacity(((ur.x - ll.x) * (ur.y - ll.y)) as usize);

for x in ll.x as u16..ur.x as u16 {
for y in ll.y as u16..ur.y as u16 {
let id = (x, y);
let Some(chunk) = self.get_chunk_mut(id) else {
continue;
};
modified.push(id);
let corner = vec2(x as f32, y as f32) * SIZE as f32;
let mut max_height: f32 = 0.0;
for i in 0..RESOLUTION {
for j in 0..RESOLUTION {
let p = corner + vec2(j as f32, i as f32) * Self::CELL_SIZE;
let h = chunk.heights[i][j];
max_height = max_height.max(h);
if !bounds.contains(p) {
continue;
}
let new_h = f(p.z(h)).clamp(MIN_HEIGHT, MAX_HEIGHT);
chunk.heights[i][j] = new_h;
max_height = max_height.max(new_h);
}
}
chunk.max_height = max_height;
}
}

modified
}

pub fn chunks(
&self,
) -> impl Iterator<Item = (HeightmapChunkID, &HeightmapChunk<RESOLUTION, SIZE>)> + '_ {
Expand Down Expand Up @@ -281,7 +345,7 @@ fn binary_search(min: f32, max: f32, mut f: impl FnMut(f32) -> bool) -> f32 {

impl<const RESOLUTION: usize, const SIZE: u32> Serialize for HeightmapChunk<RESOLUTION, SIZE> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_seq(Some(RESOLUTION * RESOLUTION))?;
let mut seq = serializer.serialize_seq(Some(1 + RESOLUTION * RESOLUTION))?;
seq.serialize_element(&self.max_height)?;
for row in &self.heights {
for height in row {
Expand Down Expand Up @@ -315,8 +379,8 @@ impl<'de, const RESOLUTION: usize> serde::de::Visitor<'de> for HeightmapChunkVis
}

fn visit_seq<A: serde::de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let len = seq.size_hint().unwrap_or(RESOLUTION * RESOLUTION);
if len != RESOLUTION * RESOLUTION {
let len = seq.size_hint().unwrap_or(1 + RESOLUTION * RESOLUTION);
if len != 1 + RESOLUTION * RESOLUTION {
return Err(serde::de::Error::invalid_length(len, &""));
}
let max_height = seq
Expand Down
15 changes: 10 additions & 5 deletions native_app/src/game_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,19 @@ impl engine::framework::State for State {
if !ctx.egui.last_mouse_captured {
let sim = self.sim.read().unwrap();
let map = sim.map();
let unproj = self
let ray = self
.uiw
.read::<OrbitCamera>()
.unproject(ctx.input.mouse.screen, |p| {
map.terrain.height(p).map(|x| x + 0.01)
});
.camera
.unproj_ray(ctx.input.mouse.screen);
self.uiw.write::<InputMap>().ray = ray;

self.uiw.write::<InputMap>().unprojected = unproj;
if let Some(ray) = ray {
let cast = map.terrain.raycast(ray);

self.uiw.write::<InputMap>().unprojected = cast.map(|x| x.0);
self.uiw.write::<InputMap>().unprojected_normal = cast.map(|x| x.1);
}
}

self.uiw.write::<InputMap>().prepare_frame(
Expand Down
5 changes: 4 additions & 1 deletion native_app/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod roadbuild;
pub mod roadeditor;
pub mod selectable;
pub mod specialbuilding;
pub mod terraforming;
pub mod topgui;
pub mod windows;
pub mod zoneedit;
Expand All @@ -40,6 +41,7 @@ pub fn run_ui_systems(sim: &Simulation, uiworld: &mut UiWorld) {
specialbuilding::specialbuilding(sim, uiworld);
addtrain::addtrain(sim, uiworld);
zoneedit::zoneedit(sim, uiworld);
terraforming::terraforming(sim, uiworld);

// run last so other systems can have the chance to cancel select
selectable::selectable(sim, uiworld);
Expand Down Expand Up @@ -105,7 +107,6 @@ impl Default for InspectedEntity {

#[derive(Copy, Clone, Default, Serialize, Deserialize, Eq, PartialEq)]
pub enum Tool {
#[default]
Hand,
RoadbuildStraight,
RoadbuildCurved,
Expand All @@ -114,6 +115,8 @@ pub enum Tool {
LotBrush,
SpecialBuilding,
Train,
#[default]
Terraforming,
}

impl Tool {
Expand Down
56 changes: 56 additions & 0 deletions native_app/src/gui/terraforming.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use super::Tool;
use crate::inputmap::{InputAction, InputMap};
use crate::rendering::immediate::ImmediateDraw;
use crate::uiworld::UiWorld;
use common::timestep::UP_DT;
use egui_inspect::Inspect;
use geom::LinearColor;
use simulation::engine_interaction::WorldCommand;
use simulation::map::TerraformKind;
use simulation::Simulation;

#[derive(Inspect)]
pub struct TerraformingResource {
pub kind: TerraformKind,
pub radius: f32,
pub amount: f32,
}

/// Lot brush tool
/// Allows to build houses on lots
pub fn terraforming(sim: &Simulation, uiworld: &mut UiWorld) {
profiling::scope!("gui::terraforming");
let res = uiworld.write::<TerraformingResource>();
let tool = *uiworld.read::<Tool>();
let inp = uiworld.read::<InputMap>();
let mut draw = uiworld.write::<ImmediateDraw>();
let _map = sim.map();
let commands = &mut *uiworld.commands();

if !matches!(tool, Tool::Terraforming) {
return;
}

let mpos = unwrap_ret!(inp.unprojected);
draw.circle(mpos.up(0.8), res.radius)
.color(LinearColor::GREEN.a(0.1));

if inp.act.contains(&InputAction::Select) {
commands.push(WorldCommand::Terraform {
center: mpos.xy(),
radius: res.radius,
amount: res.amount * UP_DT.as_secs_f32(),
kind: res.kind,
})
}
}

impl Default for TerraformingResource {
fn default() -> Self {
Self {
kind: TerraformKind::Raise,
radius: 200.0,
amount: 200.0,
}
}
}
28 changes: 28 additions & 0 deletions native_app/src/gui/topgui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::gui::inspect::inspector;
use crate::gui::lotbrush::LotBrushResource;
use crate::gui::roadeditor::RoadEditorResource;
use crate::gui::specialbuilding::{SpecialBuildKind, SpecialBuildingResource};
use crate::gui::terraforming::TerraformingResource;
use crate::gui::windows::settings::Settings;
use crate::gui::windows::GUIWindows;
use crate::gui::{ErrorTooltip, PotentialCommands, RoadBuildResource, Tool, UiTextures};
Expand Down Expand Up @@ -162,6 +163,7 @@ impl Gui {
Roadbuilding,
Bulldozer,
Train,
Terraforming,
}
uiworld.check_present(|| Tab::Hand);

Expand Down Expand Up @@ -190,6 +192,7 @@ impl Gui {
("buildings", Tab::Roadbuilding, Tool::SpecialBuilding),
("bulldozer", Tab::Bulldozer, Tool::Bulldozer),
("traintool", Tab::Train, Tool::Train),
("terraform", Tab::Terraforming, Tool::Terraforming),
];

Window::new("Toolbox")
Expand Down Expand Up @@ -511,6 +514,31 @@ impl Gui {
});
}

if matches!(*uiworld.read::<Tab>(), Tab::Terraforming) {
let lbw = 120.0;
Window::new("Terraforming")
.min_width(lbw)
.auto_sized()
.fixed_pos([w - toolbox_w - lbw, h * 0.5 - 30.0])
.hscroll(false)
.title_bar(true)
.collapsible(false)
.resizable(false)
.show(ui, |ui| {
let mut state = uiworld.write::<TerraformingResource>();
<TerraformingResource as Inspect<TerraformingResource>>::render_mut(
&mut *state,
"Terraforming",
ui,
&InspectArgs {
header: Some(false),
indent_children: Some(false),
..Default::default()
},
);
});
}

let building_select_w = 200.0;
let registry = sim.read::<GoodsCompanyRegistry>();
let gbuildings = registry.descriptions.values().peekable();
Expand Down
2 changes: 2 additions & 0 deletions native_app/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::gui::lotbrush::LotBrushResource;
use crate::gui::roadbuild::RoadBuildResource;
use crate::gui::roadeditor::RoadEditorResource;
use crate::gui::specialbuilding::SpecialBuildingResource;
use crate::gui::terraforming::TerraformingResource;
use crate::gui::windows::debug::{DebugObjs, DebugState, TestFieldProperties};
use crate::gui::windows::settings::Settings;
use crate::gui::zoneedit::ZoneEditState;
Expand All @@ -31,6 +32,7 @@ pub fn init() {
register_resource::<LotBrushResource>("lot_brush");
register_resource::<Bindings>("bindings");

register_resource_noserialize::<TerraformingResource>();
register_resource_noserialize::<BulldozerState>();
register_resource_noserialize::<DebugObjs>();
register_resource_noserialize::<DebugState>();
Expand Down
11 changes: 10 additions & 1 deletion native_app/src/inputmap.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use common::{FastMap, FastSet};
use engine::ScanCode;
use engine::{InputContext, KeyCode, MouseButton};
use geom::{Vec2, Vec3};
use geom::{Ray3, Vec2, Vec3};
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, HashSet};
use std::fmt::{Debug, Display, Formatter};
Expand Down Expand Up @@ -53,10 +53,19 @@ struct InputTree {

#[derive(Default)]
pub struct InputMap {
/// Actions that were just pressed this frame
pub just_act: FastSet<InputAction>,
/// Actions that are currently pressed
pub act: FastSet<InputAction>,
/// Mouse wheel delta
pub wheel: f32,
/// Mouse position in world space on the terrain
pub unprojected: Option<Vec3>,

pub unprojected_normal: Option<Vec3>,
/// Ray from camera to mouse
pub ray: Option<Ray3>,
/// Mouse position in screen space
pub screen: Vec2,
input_tree: InputTree,
}
Expand Down
Loading

0 comments on commit c7b5b12

Please sign in to comment.