mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
Material base_color editing support
This commit is contained in:
@@ -3,9 +3,10 @@ import { LitElement, type PropertyValues, html, unsafeCSS } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
import { SCENE_DATA_PROVIDER, type SceneDataProvider } from "../scene-data-provider.element";
|
||||
import { type InspectorComponent } from "./inspector.types";
|
||||
import { isLinear, type InspectorComponent } from "./inspector.types";
|
||||
|
||||
import "../scene-data-provider.element";
|
||||
import "./material-inspector.element";
|
||||
import "./name-inspector.element";
|
||||
import "./transform-inspector.element";
|
||||
|
||||
@@ -48,13 +49,14 @@ export class EntityInspectorElement extends LitElement {
|
||||
})
|
||||
.map(({ name, value }) => {
|
||||
switch (name) {
|
||||
case "bevy_core::name::Name":
|
||||
case "bevy_core::name::Name": {
|
||||
return html`
|
||||
<sts-name-inspector
|
||||
.value=${value.name}
|
||||
></sts-name-inspector>
|
||||
`;
|
||||
case "bevy_transform::components::transform::Transform":
|
||||
}
|
||||
case "bevy_transform::components::transform::Transform": {
|
||||
return html`
|
||||
<sts-transform-inspector
|
||||
.translation=${value.translation}
|
||||
@@ -62,11 +64,25 @@ export class EntityInspectorElement extends LitElement {
|
||||
.scale=${value.scale}
|
||||
></sts-transform-inspector>
|
||||
`;
|
||||
default:
|
||||
}
|
||||
case "bevy_pbr::pbr_material::StandardMaterial": {
|
||||
const { colorSpace, baseColor } = isLinear(value.base_color)
|
||||
? { colorSpace: "linear", baseColor: value.base_color.RgbaLinear }
|
||||
: { colorSpace: "srgb", baseColor: value.base_color.Rgba };
|
||||
|
||||
return html`
|
||||
<sts-material-inspector
|
||||
.colorSpace=${colorSpace}
|
||||
.baseColor=${baseColor}
|
||||
></sts-material-inspector>
|
||||
`;
|
||||
}
|
||||
default: {
|
||||
return html`
|
||||
<h3>Unknown Component: ${name}</h3>
|
||||
<pre>${JSON.stringify({ name, value }, null, " ")}</pre>
|
||||
`;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type InspectorComponent
|
||||
= BevyReflect<"bevy_transform::components::transform::Transform", BevyTransform>
|
||||
| BevyReflect<"bevy_core::name::Name", BevyName>
|
||||
| BevyReflect<"bevy_pbr::pbr_material::StandardMaterial", StandardMaterial>
|
||||
;
|
||||
|
||||
export interface BevyReflect<K extends string, T> {
|
||||
@@ -31,3 +32,31 @@ export interface Vec3 {
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export interface StandardMaterial {
|
||||
base_color: BevyColor;
|
||||
}
|
||||
|
||||
export type BevyColor
|
||||
= Color_RgbaLinear
|
||||
| Color_Rgba
|
||||
;
|
||||
|
||||
export interface Color_RgbaLinear {
|
||||
RgbaLinear: Rgba;
|
||||
}
|
||||
|
||||
export interface Color_Rgba {
|
||||
Rgba: Rgba;
|
||||
}
|
||||
|
||||
export interface Rgba {
|
||||
red: number;
|
||||
green: number;
|
||||
blue: number;
|
||||
alpha: number;
|
||||
}
|
||||
|
||||
export function isLinear(color: BevyColor): color is Color_RgbaLinear {
|
||||
return "RgbaLinear" in color;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@use "sass:math";
|
||||
|
||||
:host {
|
||||
color-scheme: dark;
|
||||
font-family: "Fira Sans", sans-serif;
|
||||
}
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-weight: 500;
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
label {
|
||||
color: #FFF9;
|
||||
font-size: math.div(14, 16) * 1rem;
|
||||
margin-block-end: 0.333rem;
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import * as studio from "@storyteller/studio";
|
||||
import { match } from "@storyteller/utility";
|
||||
import { LitElement, html, unsafeCSS } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import { Rgba } from "./inspector.types";
|
||||
|
||||
import styles from "./material-inspector.element.scss?inline";
|
||||
|
||||
@customElement("sts-material-inspector")
|
||||
export class MaterialInspectorElement extends LitElement {
|
||||
static override styles = unsafeCSS(styles);
|
||||
|
||||
@property({ attribute: false })
|
||||
baseColor: Rgba = { red: 1, green: 1, blue: 1, alpha: 1 };
|
||||
|
||||
@property()
|
||||
inputColorSpace: "linear"|"srgb" = "linear";
|
||||
|
||||
#baseColorId = uuid();
|
||||
|
||||
onBaseColorInput(event: InputEvent): void {
|
||||
const hex = (event.target as HTMLInputElement).value;
|
||||
const value = match (this.inputColorSpace, {
|
||||
"linear": () => RgbaLinear.fromHexSrgb(hex, this.baseColor.alpha),
|
||||
"srgb": () => Srgba.fromHex(hex, this.baseColor.alpha),
|
||||
});
|
||||
|
||||
studio.updateComponentField({
|
||||
component: "bevy_pbr::pbr_material::StandardMaterial",
|
||||
reflectPath: "base_color",
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
const [rgb, _] = match (this.inputColorSpace, {
|
||||
"linear": () => RgbaLinear.toHexSrgb(this.baseColor),
|
||||
"srgb": () => Srgba.toHex(this.baseColor),
|
||||
});
|
||||
|
||||
return html`
|
||||
<h4>Material</h4>
|
||||
<label for=${this.#baseColorId}>
|
||||
Tint
|
||||
</label>
|
||||
<input
|
||||
id=${this.#baseColorId}
|
||||
type="color"
|
||||
.value=${rgb}
|
||||
@input=${this.onBaseColorInput}
|
||||
/>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
const HEX_PATTERN = /#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?/i;
|
||||
const HEX_BASE = 16;
|
||||
const U8_MAX = 255;
|
||||
|
||||
function channelToHex(gamma: number) {
|
||||
return (channel: number): string => {
|
||||
return Math
|
||||
.round(Math.pow(channel, gamma) * U8_MAX)
|
||||
.toString(HEX_BASE)
|
||||
.padStart(2, "0");
|
||||
}
|
||||
}
|
||||
|
||||
function channelFromHex(gamma: number) {
|
||||
return (channel: string): number => {
|
||||
return Math.pow(parseInt(channel, HEX_BASE) / U8_MAX, gamma);
|
||||
}
|
||||
}
|
||||
|
||||
namespace RgbaLinear {
|
||||
export function toHexSrgb({ red, green, blue, alpha }: Rgba): [string, number] {
|
||||
const hexRgb = [red, green, blue]
|
||||
.map(channelToHex(1 / 2.2))
|
||||
.join("");
|
||||
|
||||
return [`#${hexRgb}`, alpha];
|
||||
}
|
||||
|
||||
export function fromHexSrgb(hex: string, alpha = 1): Rgba {
|
||||
const [red, green, blue] = hex
|
||||
.match(HEX_PATTERN)!
|
||||
.slice(1)
|
||||
.map(channelFromHex(2.2));
|
||||
|
||||
return { red, green, blue, alpha };
|
||||
}
|
||||
}
|
||||
|
||||
namespace Srgba {
|
||||
export function toHex({ red, green, blue, alpha }: Rgba): [string, number] {
|
||||
const hexRgb = [red, green, blue]
|
||||
.map(channelToHex(2.2))
|
||||
.join("");
|
||||
|
||||
return [`#${hexRgb}`, alpha];
|
||||
}
|
||||
|
||||
export function fromHex(hex: string, alpha = 1): Rgba {
|
||||
const [red, green, blue] = hex
|
||||
.match(HEX_PATTERN)!
|
||||
.slice(1)
|
||||
.map(channelFromHex(1 / 2.2));
|
||||
|
||||
return { red, green, blue, alpha };
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export class TransformInspectorElement extends LitElement {
|
||||
protected override render = () => html`
|
||||
<h4>Transform</h4>
|
||||
<div class="fieldset" role="group">
|
||||
<legend>Translation</legend>
|
||||
<legend>Position</legend>
|
||||
<sts-text-field
|
||||
type="number"
|
||||
.value=${this.translation.x.toFixed(3)}
|
||||
|
||||
Reference in New Issue
Block a user