puppet scale
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE scene_configuration (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL CHECK (id = 1),
|
||||||
|
puppet_scale REAL NOT NULL DEFAULT 1.0 CHECK (puppet_scale BETWEEN 0.5 AND 2.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO scene_configuration (id) VALUES (1);
|
||||||
@@ -9,6 +9,7 @@ mod network;
|
|||||||
mod profile;
|
mod profile;
|
||||||
mod puppet;
|
mod puppet;
|
||||||
mod remotes;
|
mod remotes;
|
||||||
|
mod scene_configuration;
|
||||||
mod skins;
|
mod skins;
|
||||||
mod ufa;
|
mod ufa;
|
||||||
mod ui;
|
mod ui;
|
||||||
@@ -82,6 +83,8 @@ fn specta_builder() -> tauri_specta::Builder<tauri::Wry> {
|
|||||||
images::send_image_bytes,
|
images::send_image_bytes,
|
||||||
interactions::send_interaction,
|
interactions::send_interaction,
|
||||||
puppet::list_puppet_states,
|
puppet::list_puppet_states,
|
||||||
|
scene_configuration::get_scene_configuration,
|
||||||
|
scene_configuration::update_scene_configuration,
|
||||||
ui::control_panel::open_action_window,
|
ui::control_panel::open_action_window,
|
||||||
ui::scene::update_scene_hitboxes,
|
ui::scene::update_scene_hitboxes,
|
||||||
])
|
])
|
||||||
@@ -96,6 +99,7 @@ fn specta_builder() -> tauri_specta::Builder<tauri::Wry> {
|
|||||||
ufa::FriendForegroundAppChanged,
|
ufa::FriendForegroundAppChanged,
|
||||||
interactions::FriendInteractionReceived,
|
interactions::FriendInteractionReceived,
|
||||||
puppet::PuppetStatesChanged,
|
puppet::PuppetStatesChanged,
|
||||||
|
scene_configuration::SceneConfigurationChanged,
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
use crate::db::{self, AppDatabase};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use specta::Type;
|
||||||
|
use tauri::{AppHandle, State};
|
||||||
|
use tauri_specta::Event;
|
||||||
|
|
||||||
|
const SCENE_CONFIGURATION_ID: i64 = 1;
|
||||||
|
const MIN_PUPPET_SCALE: f64 = 0.5;
|
||||||
|
const MAX_PUPPET_SCALE: f64 = 2.0;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type, sqlx::FromRow)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SceneConfiguration {
|
||||||
|
pub puppet_scale: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SceneConfigurationChanged {
|
||||||
|
pub configuration: SceneConfiguration,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(database: &AppDatabase) -> Result<SceneConfiguration, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, SceneConfiguration>(
|
||||||
|
"SELECT puppet_scale FROM scene_configuration WHERE id = ?1",
|
||||||
|
)
|
||||||
|
.bind(SCENE_CONFIGURATION_ID)
|
||||||
|
.fetch_one(database.pool())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
database: &AppDatabase,
|
||||||
|
configuration: SceneConfiguration,
|
||||||
|
) -> Result<SceneConfiguration, String> {
|
||||||
|
if !configuration.puppet_scale.is_finite()
|
||||||
|
|| !(MIN_PUPPET_SCALE..=MAX_PUPPET_SCALE).contains(&configuration.puppet_scale)
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"Puppet scale must be between {MIN_PUPPET_SCALE} and {MAX_PUPPET_SCALE}."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query("UPDATE scene_configuration SET puppet_scale = ?1 WHERE id = ?2")
|
||||||
|
.bind(configuration.puppet_scale)
|
||||||
|
.bind(SCENE_CONFIGURATION_ID)
|
||||||
|
.execute(database.pool())
|
||||||
|
.await
|
||||||
|
.map_err(db::command_error)?;
|
||||||
|
|
||||||
|
get(database).await.map_err(db::command_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_changed(handle: &AppHandle, configuration: SceneConfiguration) -> Result<(), String> {
|
||||||
|
SceneConfigurationChanged { configuration }
|
||||||
|
.emit(handle)
|
||||||
|
.map_err(db::command_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn get_scene_configuration(
|
||||||
|
handle: AppHandle,
|
||||||
|
database: State<'_, AppDatabase>,
|
||||||
|
) -> Result<SceneConfiguration, String> {
|
||||||
|
let configuration = get(&database).await.map_err(db::command_error)?;
|
||||||
|
emit_changed(&handle, configuration.clone())?;
|
||||||
|
Ok(configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
#[specta::specta]
|
||||||
|
pub async fn update_scene_configuration(
|
||||||
|
handle: AppHandle,
|
||||||
|
database: State<'_, AppDatabase>,
|
||||||
|
configuration: SceneConfiguration,
|
||||||
|
) -> Result<SceneConfiguration, String> {
|
||||||
|
let configuration = update(&database, configuration).await?;
|
||||||
|
emit_changed(&handle, configuration.clone())?;
|
||||||
|
Ok(configuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use sqlx::sqlite::SqlitePoolOptions;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
async fn database() -> AppDatabase {
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.max_connections(1)
|
||||||
|
.connect("sqlite::memory:")
|
||||||
|
.await
|
||||||
|
.expect("connect to in-memory SQLite");
|
||||||
|
sqlx::migrate!("./migrations")
|
||||||
|
.run(&pool)
|
||||||
|
.await
|
||||||
|
.expect("run database migrations");
|
||||||
|
AppDatabase::new(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn scene_configuration_defaults_to_unit_puppet_scale() {
|
||||||
|
let database = database().await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get(&database).await.unwrap(),
|
||||||
|
SceneConfiguration { puppet_scale: 1.0 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn puppet_scale_is_persisted_and_range_checked() {
|
||||||
|
let database = database().await;
|
||||||
|
|
||||||
|
let configuration = update(&database, SceneConfiguration { puppet_scale: 1.5 })
|
||||||
|
.await
|
||||||
|
.expect("update scene configuration");
|
||||||
|
assert_eq!(configuration.puppet_scale, 1.5);
|
||||||
|
assert_eq!(get(&database).await.unwrap(), configuration);
|
||||||
|
|
||||||
|
let error = update(&database, SceneConfiguration { puppet_scale: 2.1 })
|
||||||
|
.await
|
||||||
|
.expect_err("reject an out-of-range puppet scale");
|
||||||
|
assert_eq!(error, "Puppet scale must be between 0.5 and 2.");
|
||||||
|
assert_eq!(get(&database).await.unwrap().puppet_scale, 1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-2
@@ -65,6 +65,12 @@ async sendInteraction(recipientId: string, content: InteractionContent) : Promis
|
|||||||
async listPuppetStates() : Promise<PuppetState[]> {
|
async listPuppetStates() : Promise<PuppetState[]> {
|
||||||
return await TAURI_INVOKE("list_puppet_states");
|
return await TAURI_INVOKE("list_puppet_states");
|
||||||
},
|
},
|
||||||
|
async getSceneConfiguration() : Promise<SceneConfiguration> {
|
||||||
|
return await TAURI_INVOKE("get_scene_configuration");
|
||||||
|
},
|
||||||
|
async updateSceneConfiguration(configuration: SceneConfiguration) : Promise<SceneConfiguration> {
|
||||||
|
return await TAURI_INVOKE("update_scene_configuration", { configuration });
|
||||||
|
},
|
||||||
async openActionWindow(name: string, title: string, pageUrl: string) : Promise<null> {
|
async openActionWindow(name: string, title: string, pageUrl: string) : Promise<null> {
|
||||||
return await TAURI_INVOKE("open_action_window", { name, title, pageUrl });
|
return await TAURI_INVOKE("open_action_window", { name, title, pageUrl });
|
||||||
},
|
},
|
||||||
@@ -86,7 +92,8 @@ friendsChanged: FriendsChanged,
|
|||||||
networkStatusChanged: NetworkStatusChanged,
|
networkStatusChanged: NetworkStatusChanged,
|
||||||
profileChanged: ProfileChanged,
|
profileChanged: ProfileChanged,
|
||||||
puppetStatesChanged: PuppetStatesChanged,
|
puppetStatesChanged: PuppetStatesChanged,
|
||||||
remotesChanged: RemotesChanged
|
remotesChanged: RemotesChanged,
|
||||||
|
sceneConfigurationChanged: SceneConfigurationChanged
|
||||||
}>({
|
}>({
|
||||||
cursorPositionChanged: "cursor-position-changed",
|
cursorPositionChanged: "cursor-position-changed",
|
||||||
foregroundAppChanged: "foreground-app-changed",
|
foregroundAppChanged: "foreground-app-changed",
|
||||||
@@ -97,7 +104,8 @@ friendsChanged: "friends-changed",
|
|||||||
networkStatusChanged: "network-status-changed",
|
networkStatusChanged: "network-status-changed",
|
||||||
profileChanged: "profile-changed",
|
profileChanged: "profile-changed",
|
||||||
puppetStatesChanged: "puppet-states-changed",
|
puppetStatesChanged: "puppet-states-changed",
|
||||||
remotesChanged: "remotes-changed"
|
remotesChanged: "remotes-changed",
|
||||||
|
sceneConfigurationChanged: "scene-configuration-changed"
|
||||||
})
|
})
|
||||||
|
|
||||||
/** user-defined constants **/
|
/** user-defined constants **/
|
||||||
@@ -141,6 +149,8 @@ export type PuppetStatesChanged = { puppets: PuppetState[] }
|
|||||||
export type Remote = { id: string; address: string; name: string | null; port: number | null }
|
export type Remote = { id: string; address: string; name: string | null; port: number | null }
|
||||||
export type RemoteInput = { address: string; name: string | null; port: number | null }
|
export type RemoteInput = { address: string; name: string | null; port: number | null }
|
||||||
export type RemotesChanged = { remotes: Remote[] }
|
export type RemotesChanged = { remotes: Remote[] }
|
||||||
|
export type SceneConfiguration = { puppetScale: number }
|
||||||
|
export type SceneConfigurationChanged = { configuration: SceneConfiguration }
|
||||||
export type SceneHitbox = { x: number; y: number; width: number; height: number }
|
export type SceneHitbox = { x: number; y: number; width: number; height: number }
|
||||||
/**
|
/**
|
||||||
* Public user identity exchanged with peers and remote servers. `id` is the
|
* Public user identity exchanged with peers and remote servers. `id` is the
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { commands } from "$lib/bindings";
|
||||||
|
import {
|
||||||
|
sceneConfiguration,
|
||||||
|
sceneConfigurationListenerError,
|
||||||
|
} from "$lib/listeners/scene-configuration";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import PanelMessage from "./panel-message.svelte";
|
||||||
|
import type { RegisterPanel } from "./types";
|
||||||
|
|
||||||
|
let { register }: { register: RegisterPanel } = $props();
|
||||||
|
|
||||||
|
let puppetScale = $state(1);
|
||||||
|
let dirty = $state(false);
|
||||||
|
let busy = $state(false);
|
||||||
|
let error = $state("");
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!dirty) puppetScale = $sceneConfiguration.puppetScale;
|
||||||
|
register("scene", { apply, reset }, { dirty, busy });
|
||||||
|
});
|
||||||
|
|
||||||
|
onMount(() => register("scene", { apply, reset }, { dirty, busy }));
|
||||||
|
|
||||||
|
function updateDirty() {
|
||||||
|
dirty = puppetScale !== $sceneConfiguration.puppetScale;
|
||||||
|
error = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
puppetScale = $sceneConfiguration.puppetScale;
|
||||||
|
dirty = false;
|
||||||
|
error = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apply() {
|
||||||
|
if (!dirty) return true;
|
||||||
|
|
||||||
|
busy = true;
|
||||||
|
error = "";
|
||||||
|
try {
|
||||||
|
const configuration = await commands.updateSceneConfiguration({
|
||||||
|
puppetScale,
|
||||||
|
});
|
||||||
|
puppetScale = configuration.puppetScale;
|
||||||
|
dirty = false;
|
||||||
|
return true;
|
||||||
|
} catch (cause) {
|
||||||
|
error = String(cause);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
{#if error || $sceneConfigurationListenerError}
|
||||||
|
<PanelMessage kind="error">
|
||||||
|
{error || $sceneConfigurationListenerError}
|
||||||
|
</PanelMessage>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<fieldset class="fieldset border border-base-300 bg-base-100 p-3">
|
||||||
|
<legend class="fieldset-legend px-1">Puppets</legend>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-between gap-3">
|
||||||
|
<label class="fieldset-label" for="puppet-scale">Scale</label>
|
||||||
|
<output class="badge badge-outline tabular-nums" for="puppet-scale">
|
||||||
|
{puppetScale.toFixed(2)}×
|
||||||
|
</output>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="puppet-scale"
|
||||||
|
class="range range-sm"
|
||||||
|
type="range"
|
||||||
|
min="0.5"
|
||||||
|
max="2"
|
||||||
|
step="0.05"
|
||||||
|
bind:value={puppetScale}
|
||||||
|
oninput={updateDirty}
|
||||||
|
disabled={busy}
|
||||||
|
aria-describedby="puppet-scale-help"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
@@ -6,6 +6,7 @@ import { initLiveMetadataListeners } from "./live-metadata";
|
|||||||
import { initProfileListener } from "./profile";
|
import { initProfileListener } from "./profile";
|
||||||
import { initPuppetStatesListener } from "./puppets";
|
import { initPuppetStatesListener } from "./puppets";
|
||||||
import { initRemotesListener } from "./remotes";
|
import { initRemotesListener } from "./remotes";
|
||||||
|
import { initSceneConfigurationListener } from "./scene-configuration";
|
||||||
|
|
||||||
type Unlisten = () => void;
|
type Unlisten = () => void;
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ export function initAppListeners(): Unlisten {
|
|||||||
initLiveMetadataListeners(),
|
initLiveMetadataListeners(),
|
||||||
initInteractionListener(),
|
initInteractionListener(),
|
||||||
initPuppetStatesListener(),
|
initPuppetStatesListener(),
|
||||||
|
initSceneConfigurationListener(),
|
||||||
])
|
])
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
const listeners = results.flatMap((result) =>
|
const listeners = results.flatMap((result) =>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { writable } from "svelte/store";
|
||||||
|
import {
|
||||||
|
commands,
|
||||||
|
events,
|
||||||
|
type SceneConfiguration,
|
||||||
|
} from "$lib/bindings";
|
||||||
|
|
||||||
|
export const sceneConfiguration = writable<SceneConfiguration>({
|
||||||
|
puppetScale: 1,
|
||||||
|
});
|
||||||
|
export const sceneConfigurationListenerError = writable("");
|
||||||
|
|
||||||
|
export async function initSceneConfigurationListener() {
|
||||||
|
const unlisten = await events.sceneConfigurationChanged.listen((event) => {
|
||||||
|
sceneConfiguration.set(event.payload.configuration);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
sceneConfiguration.set(await commands.getSceneConfiguration());
|
||||||
|
} catch (error) {
|
||||||
|
sceneConfigurationListenerError.set(String(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlisten;
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
import FriendsPanel from "$lib/components/control-panel/friends-panel.svelte";
|
import FriendsPanel from "$lib/components/control-panel/friends-panel.svelte";
|
||||||
import GeneralPanel from "$lib/components/control-panel/general-panel.svelte";
|
import GeneralPanel from "$lib/components/control-panel/general-panel.svelte";
|
||||||
import NetworkPanel from "$lib/components/control-panel/network-panel.svelte";
|
import NetworkPanel from "$lib/components/control-panel/network-panel.svelte";
|
||||||
|
import ScenePanel from "$lib/components/control-panel/scene-panel.svelte";
|
||||||
import type {
|
import type {
|
||||||
PanelActions,
|
PanelActions,
|
||||||
PanelState,
|
PanelState,
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: "general", label: "General" },
|
{ id: "general", label: "General" },
|
||||||
|
{ id: "scene", label: "Scene" },
|
||||||
{ id: "friends", label: "Friends" },
|
{ id: "friends", label: "Friends" },
|
||||||
{ id: "network", label: "Network" },
|
{ id: "network", label: "Network" },
|
||||||
{ id: "activity", label: "Activity" },
|
{ id: "activity", label: "Activity" },
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
let actions: Partial<Record<TabId, PanelActions>> = {};
|
let actions: Partial<Record<TabId, PanelActions>> = {};
|
||||||
let panelStates = $state<Record<TabId, PanelState>>({
|
let panelStates = $state<Record<TabId, PanelState>>({
|
||||||
general: { dirty: false, busy: false },
|
general: { dirty: false, busy: false },
|
||||||
|
scene: { dirty: false, busy: false },
|
||||||
friends: { dirty: false, busy: false },
|
friends: { dirty: false, busy: false },
|
||||||
network: { dirty: false, busy: false },
|
network: { dirty: false, busy: false },
|
||||||
activity: { dirty: false, busy: false },
|
activity: { dirty: false, busy: false },
|
||||||
@@ -96,6 +99,8 @@
|
|||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
{#if tab.id === "general"}
|
{#if tab.id === "general"}
|
||||||
<GeneralPanel {register} />
|
<GeneralPanel {register} />
|
||||||
|
{:else if tab.id === "scene"}
|
||||||
|
<ScenePanel {register} />
|
||||||
{:else if tab.id === "friends"}
|
{:else if tab.id === "friends"}
|
||||||
<FriendsPanel {register} />
|
<FriendsPanel {register} />
|
||||||
{:else if tab.id === "network"}
|
{:else if tab.id === "network"}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PuppetState } from "$lib/bindings";
|
import type { PuppetState } from "$lib/bindings";
|
||||||
|
import { sceneConfiguration } from "$lib/listeners/scene-configuration";
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { PuppetManager } from "./renderer/puppet/manager";
|
import { PuppetManager } from "./renderer/puppet/manager";
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
clock.update();
|
clock.update();
|
||||||
puppetManager.update(
|
puppetManager.update(
|
||||||
puppets,
|
puppets,
|
||||||
|
$sceneConfiguration.puppetScale,
|
||||||
frozenPuppetId,
|
frozenPuppetId,
|
||||||
clock.getDelta(),
|
clock.getDelta(),
|
||||||
clock.getElapsed(),
|
clock.getElapsed(),
|
||||||
|
|||||||
@@ -32,15 +32,21 @@ export class Puppet {
|
|||||||
private readonly skin: PuppetSkin;
|
private readonly skin: PuppetSkin;
|
||||||
private readonly targetGroundPosition = new THREE.Vector3();
|
private readonly targetGroundPosition = new THREE.Vector3();
|
||||||
|
|
||||||
constructor(readonly id: string) {
|
constructor(readonly id: string, scale: number) {
|
||||||
this.rig = this.createRig();
|
this.rig = this.createRig();
|
||||||
this.root = this.rig.root;
|
this.root = this.rig.root;
|
||||||
this.animation = new PuppetAnimation(this.rig);
|
this.animation = new PuppetAnimation(this.rig);
|
||||||
const placeholderMaterial = this.rig.body.material;
|
const placeholderMaterial = this.rig.body.material;
|
||||||
this.skin = new PuppetSkin(id, this.rig);
|
this.skin = new PuppetSkin(id, this.rig);
|
||||||
|
this.setScale(scale);
|
||||||
if (!Array.isArray(placeholderMaterial)) placeholderMaterial.dispose();
|
if (!Array.isArray(placeholderMaterial)) placeholderMaterial.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setScale(scale: number) {
|
||||||
|
if (this.root.scale.x === scale) return;
|
||||||
|
this.root.scale.setScalar(scale);
|
||||||
|
}
|
||||||
|
|
||||||
update(
|
update(
|
||||||
state: PuppetState,
|
state: PuppetState,
|
||||||
world: World,
|
world: World,
|
||||||
@@ -81,50 +87,49 @@ export class Puppet {
|
|||||||
private createRig(): PuppetRig {
|
private createRig(): PuppetRig {
|
||||||
const root = new THREE.Group();
|
const root = new THREE.Group();
|
||||||
const material = new THREE.MeshStandardMaterial();
|
const material = new THREE.MeshStandardMaterial();
|
||||||
const scale = 1;
|
|
||||||
|
|
||||||
const body = new THREE.Mesh(
|
const body = new THREE.Mesh(
|
||||||
new THREE.BoxGeometry(16 * scale, 24 * scale, 8 * scale),
|
new THREE.BoxGeometry(16, 24, 8),
|
||||||
material,
|
material,
|
||||||
);
|
);
|
||||||
body.position.y = 36 * scale;
|
body.position.y = 36;
|
||||||
|
|
||||||
const head = new THREE.Mesh(
|
const head = new THREE.Mesh(
|
||||||
new THREE.BoxGeometry(16 * scale, 16 * scale, 16 * scale),
|
new THREE.BoxGeometry(16, 16, 16),
|
||||||
material,
|
material,
|
||||||
);
|
);
|
||||||
head.position.y = 56 * scale;
|
head.position.y = 56;
|
||||||
|
|
||||||
const { pivot: leftArm, mesh: leftArmMesh } = this.createLimbPivot(
|
const { pivot: leftArm, mesh: leftArmMesh } = this.createLimbPivot(
|
||||||
-12 * scale,
|
-12,
|
||||||
48 * scale,
|
48,
|
||||||
8 * scale,
|
8,
|
||||||
24 * scale,
|
24,
|
||||||
8 * scale,
|
8,
|
||||||
material,
|
material,
|
||||||
);
|
);
|
||||||
const { pivot: rightArm, mesh: rightArmMesh } = this.createLimbPivot(
|
const { pivot: rightArm, mesh: rightArmMesh } = this.createLimbPivot(
|
||||||
12 * scale,
|
12,
|
||||||
48 * scale,
|
48,
|
||||||
8 * scale,
|
8,
|
||||||
24 * scale,
|
24,
|
||||||
8 * scale,
|
8,
|
||||||
material,
|
material,
|
||||||
);
|
);
|
||||||
const { pivot: leftLeg, mesh: leftLegMesh } = this.createLimbPivot(
|
const { pivot: leftLeg, mesh: leftLegMesh } = this.createLimbPivot(
|
||||||
-4 * scale,
|
-4,
|
||||||
24 * scale,
|
24,
|
||||||
8 * scale,
|
8,
|
||||||
24 * scale,
|
24,
|
||||||
8 * scale,
|
8,
|
||||||
material,
|
material,
|
||||||
);
|
);
|
||||||
const { pivot: rightLeg, mesh: rightLegMesh } = this.createLimbPivot(
|
const { pivot: rightLeg, mesh: rightLegMesh } = this.createLimbPivot(
|
||||||
4 * scale,
|
4,
|
||||||
24 * scale,
|
24,
|
||||||
8 * scale,
|
8,
|
||||||
24 * scale,
|
24,
|
||||||
8 * scale,
|
8,
|
||||||
material,
|
material,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ export class PuppetManager {
|
|||||||
|
|
||||||
update(
|
update(
|
||||||
states: readonly PuppetState[],
|
states: readonly PuppetState[],
|
||||||
|
scale: number,
|
||||||
frozenPuppetId: string | null,
|
frozenPuppetId: string | null,
|
||||||
deltaSeconds: number,
|
deltaSeconds: number,
|
||||||
elapsedSeconds: number,
|
elapsedSeconds: number,
|
||||||
skinHashes: ReadonlyMap<string, string | null>,
|
skinHashes: ReadonlyMap<string, string | null>,
|
||||||
) {
|
) {
|
||||||
this.syncPuppets(states);
|
this.syncPuppets(states, scale);
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
this.puppets
|
this.puppets
|
||||||
@@ -46,17 +47,19 @@ export class PuppetManager {
|
|||||||
this.puppets.clear();
|
this.puppets.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private syncPuppets(states: readonly PuppetState[]) {
|
private syncPuppets(states: readonly PuppetState[], scale: number) {
|
||||||
const nextPuppetIds = new Set(states.map((state) => state.id));
|
const nextPuppetIds = new Set(states.map((state) => state.id));
|
||||||
|
|
||||||
for (const state of states) {
|
for (const state of states) {
|
||||||
if (this.puppets.has(state.id)) continue;
|
if (this.puppets.has(state.id)) continue;
|
||||||
|
|
||||||
const puppet = new Puppet(state.id);
|
const puppet = new Puppet(state.id, scale);
|
||||||
this.puppets.set(puppet.id, puppet);
|
this.puppets.set(puppet.id, puppet);
|
||||||
this.world.addObject(puppet.root);
|
this.world.addObject(puppet.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const puppet of this.puppets.values()) puppet.setScale(scale);
|
||||||
|
|
||||||
for (const [puppetId, puppet] of this.puppets) {
|
for (const [puppetId, puppet] of this.puppets) {
|
||||||
if (nextPuppetIds.has(puppetId)) continue;
|
if (nextPuppetIds.has(puppetId)) continue;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user