initial commit

This commit is contained in:
vance 2022-10-23 23:32:12 -07:00
commit ff9d8ffab6
13 changed files with 383 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
/dist
/.idea
Cargo.lock

15
Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "dreamsincode"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = { version = "0.8.1" }
web-sys = { version = "0.3.60" }
gloo-events = { version = "0.1.2" }
console_error_panic_hook = { version = "0.1.7" }
[profile.release]
opt-level = "z"

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# dreamsincode
https://dreamsinco.de

35
Trunk.release.toml Normal file
View File

@ -0,0 +1,35 @@
# An example Trunk.toml with all possible fields along with their defaults.
[build]
# The index HTML file to drive the bundling process.
target = "index.html"
# Build in release mode.
release = true
# The output dir for all final assets.
dist = "dist/release"
# The public URL from which assets are to be served.
public_url = "/"
# Whether to include hash values in the output file names.
filehash = true
[watch]
# Paths to watch. The `build.target`'s parent folder is watched by default.
watch = []
# Paths to ignore.
ignore = ["."]
[serve]
# The address to serve on.
addr = "127.0.0.1"
# The port to serve on.
port = 8080
# Open a browser tab once the initial build is complete.
open = true
# Disable auto-reload of the web app.
no_autoreload = true
[clean]
# The output dir for all final assets.
dist = "dist"
# Optionally perform a cargo clean.
cargo = true

35
Trunk.toml Normal file
View File

@ -0,0 +1,35 @@
# An example Trunk.toml with all possible fields along with their defaults.
[build]
# The index HTML file to drive the bundling process.
target = "index.html"
# Build in release mode.
release = false
# The output dir for all final assets.
dist = "dist/debug"
# The public URL from which assets are to be served.
public_url = "/"
# Whether to include hash values in the output file names.
filehash = true
[watch]
# Paths to watch. The `build.target`'s parent folder is watched by default.
watch = []
# Paths to ignore.
ignore = []
[serve]
# The address to serve on.
addr = "127.0.0.1"
# The port to serve on.
port = 8080
# Open a browser tab once the initial build is complete.
open = true
# Disable auto-reload of the web app.
no_autoreload = false
[clean]
# The output dir for all final assets.
dist = "debug"
# Optionally perform a cargo clean.
cargo = false

View File

@ -0,0 +1,11 @@
#version 450
layout(location = 0) out vec4 o_Target;
layout(set = 1, binding = 0) uniform CustomMaterial {
vec4 Color;
};
void main() {
o_Target = Color;
}

View File

@ -0,0 +1,27 @@
#version 450
layout(location = 0) in vec3 Vertex_Position;
layout(location = 1) in vec3 Vertex_Normal;
layout(location = 2) in vec2 Vertex_Uv;
layout(set = 0, binding = 0) uniform CameraViewProj {
mat4 ViewProj;
mat4 View;
mat4 InverseView;
mat4 Projection;
vec3 WorldPosition;
float near;
float far;
float width;
float height;
};
layout(set = 2, binding = 0) uniform Mesh {
mat4 Model;
mat4 InverseTransposeModel;
uint flags;
};
void main() {
gl_Position = ViewProj * Model * vec4(Vertex_Position, 1.0);
}

BIN
assets/sounds/bg.ogg Normal file

Binary file not shown.

16
index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"/>
<title>dreamsinco.de</title>
<meta name="description" content="dreamsinco.de">
<meta name="author" content="vance">
<link data-trunk rel="copy-dir" href="assets"/>
<link data-trunk rel="inline" href="web/index.css" />
<link data-trunk rel="rust" data-wasm-opt="z" />
</head>
<body>
<link data-trunk rel="inline" href="web/sound.js" />
</body>
</html>

64
src/fullviewport.rs Normal file
View File

@ -0,0 +1,64 @@
use bevy::{
prelude::{App, Plugin, Res, ResMut},
window::Windows,
};
use std::sync::{
mpsc::{Receiver, Sender},
Mutex,
};
type OnResizeSender = Sender<()>;
type OnResizeReceiver = Receiver<()>;
pub struct FullViewportPlugin;
impl Plugin for FullViewportPlugin {
fn build(&self, app: &mut App) {
let channel = std::sync::mpsc::channel();
let resize_sender: OnResizeSender = channel.0;
let resize_receiver: OnResizeReceiver = channel.1;
app.insert_resource(Mutex::new(resize_sender))
.insert_resource(Mutex::new(resize_receiver))
.add_startup_system(setup_viewport_resize_system)
.add_system(viewport_resize_system);
}
}
fn get_viewport_size() -> (f32, f32) {
let web_window = web_sys::window().expect("could not get window");
let document_element = web_window
.document()
.expect("could not get document")
.document_element()
.expect("could not get document element");
let width = document_element.client_width();
let height = document_element.client_height();
(width as f32, height as f32)
}
fn setup_viewport_resize_system(resize_sender: Res<Mutex<OnResizeSender>>) {
let web_window = web_sys::window().expect("could not get window");
let local_sender = resize_sender.lock().unwrap().clone();
local_sender.send(()).unwrap();
gloo_events::EventListener::new(&web_window, "resize", move |_event| {
local_sender.send(()).unwrap();
})
.forget();
}
fn viewport_resize_system(
mut windows: ResMut<Windows>,
resize_receiver: Res<Mutex<OnResizeReceiver>>,
) {
if resize_receiver.lock().unwrap().try_recv().is_ok() {
if let Some(window) = windows.get_primary_mut() {
let size = get_viewport_size();
window.set_resolution(size.0, size.1);
}
}
}

104
src/main.rs Normal file
View File

@ -0,0 +1,104 @@
use bevy::prelude::*;
#[cfg(target_family = "wasm")]
mod fullviewport;
#[cfg(target_family = "wasm")]
use fullviewport::FullViewportPlugin;
fn main() {
#[cfg(all(target_family = "wasm", debug_assertions))]
console_error_panic_hook::set_once();
let mut app = App::new();
#[cfg(target_family = "wasm")]
app.add_plugin(FullViewportPlugin);
app.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_startup_system(start_background_audio)
.add_system(rotate)
.run();
}
#[derive(Component)]
struct Rotatable {
speed: f32,
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.insert_resource(ClearColor(Color::BLACK));
commands.insert_resource(AmbientLight {
color: Color::VIOLET,
brightness: 0.05,
});
commands.spawn_bundle(Camera3dBundle {
transform: Transform::from_xyz(0.0, 25.0, 50.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Plane { size: 1000.0 })),
material: materials.add(StandardMaterial {
base_color: Color::BLACK,
//metallic: 1.0,
reflectance: 1.0,
//perceptual_roughness: 0.0,
..Default::default()
}),
..Default::default()
});
commands
.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 4.0 })),
material: materials.add(StandardMaterial {
base_color: Color::BLACK,
metallic: 1.0,
//reflectance: 1.0,
//perceptual_roughness: 0.0,
double_sided: true,
..Default::default()
}),
transform: Transform::from_translation(Vec3::new(0.0, 8.0, 0.0)),
..Default::default()
})
.insert(Rotatable { speed: 0.1 });
commands.spawn_bundle(DirectionalLightBundle {
directional_light: DirectionalLight {
color: Color::PINK,
illuminance: 100000.0,
shadows_enabled: true,
..Default::default()
},
transform: Transform {
translation: Vec3::new(0.0, 16.0, 0.0),
rotation: Quat::from_rotation_x(-std::f32::consts::PI),
..Default::default()
},
..Default::default()
});
}
fn start_background_audio(asset_server: Res<AssetServer>, audio: Res<Audio>) {
audio.play_with_settings(asset_server.load("sounds/bg.ogg"), PlaybackSettings::LOOP);
}
fn rotate(mut rotatables: Query<(&mut Transform, &Rotatable)>, timer: Res<Time>) {
for (mut transform, rotatable) in rotatables.iter_mut() {
let rotation = 2.0 * std::f32::consts::PI * timer.delta_seconds();
let rotation_change = Quat::from_scaled_axis(Vec3::new(
rotation * rotatable.speed,
rotation * rotatable.speed * -2.,
rotation * rotatable.speed,
));
transform.rotate(rotation_change);
}
}

8
web/index.css Normal file
View File

@ -0,0 +1,8 @@
body {
margin: 0;
display: flex;
overflow: hidden;
}
canvas {
touch-action: none;
}

62
web/sound.js Normal file
View File

@ -0,0 +1,62 @@
// Insert hack to make sound autoplay on Chrome as soon as the user interacts with the tab:
// https://developers.google.com/web/updates/2018/11/web-audio-autoplay#moving-forward
// the following function keeps track of all AudioContexts and resumes them on the first user
// interaction with the page. If the function is called and all contexts are already running,
// it will remove itself from all event listeners.
(function () {
// An array of all contexts to resume on the page
const audioContextList = [];
// An array of various user interaction events we should listen for
const userInputEventNames = [
"click",
"contextmenu",
"auxclick",
"dblclick",
"mousedown",
"mouseup",
"pointerup",
"touchend",
"keydown",
"keyup",
];
// A proxy object to intercept AudioContexts and
// add them to the array for tracking and resuming later
self.AudioContext = new Proxy(self.AudioContext, {
construct(target, args) {
const result = new target(...args);
audioContextList.push(result);
return result;
},
});
// To resume all AudioContexts being tracked
function resumeAllContexts(_event) {
let count = 0;
audioContextList.forEach((context) => {
if (context.state !== "running") {
context.resume();
} else {
count++;
}
});
// If all the AudioContexts have now resumed then we unbind all
// the event listeners from the page to prevent unnecessary resume attempts
// Checking count > 0 ensures that the user interaction happens AFTER the game started up
if (count > 0 && count === audioContextList.length) {
userInputEventNames.forEach((eventName) => {
document.removeEventListener(eventName, resumeAllContexts);
});
}
}
// We bind the resume function for each user interaction
// event on the page
userInputEventNames.forEach((eventName) => {
document.addEventListener(eventName, resumeAllContexts);
});
})();