Merge pull request #1087 from mozilla/feature/billboard-component

Add Billboard Support for Images, Videos, and Models
This commit is contained in:
Robert Long 2021-02-10 15:26:38 -08:00 коммит произвёл GitHub
Родитель 36987e8edf ab1b2e8502
Коммит a8f40606ef
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 275 добавлений и 6 удалений

Просмотреть файл

@ -18,6 +18,10 @@ export default class ImageNode extends EditorNodeMixin(Image) {
const { src, projection, controls, alphaMode, alphaCutoff } = json.components.find(c => c.name === "image").props;
if (json.components.find(c => c.name === "billboard")) {
node.billboard = true;
}
loadAsync(
(async () => {
await node.load(src, onError);
@ -36,6 +40,7 @@ export default class ImageNode extends EditorNodeMixin(Image) {
this._canonicalUrl = "";
this.controls = true;
this.billboard = false;
}
get src() {
@ -110,6 +115,7 @@ export default class ImageNode extends EditorNodeMixin(Image) {
super.copy(source, recursive);
this.controls = source.controls;
this.billboard = source.billboard;
this.alphaMode = source.alphaMode;
this.alphaCutoff = source.alphaCutoff;
this._canonicalUrl = source._canonicalUrl;
@ -118,7 +124,7 @@ export default class ImageNode extends EditorNodeMixin(Image) {
}
serialize() {
return super.serialize({
const components = {
image: {
src: this._canonicalUrl,
controls: this.controls,
@ -126,7 +132,13 @@ export default class ImageNode extends EditorNodeMixin(Image) {
alphaCutoff: this.alphaCutoff,
projection: this.projection
}
});
};
if (this.billboard) {
components.billboard = {};
}
return super.serialize(components);
}
prepareForExport() {
@ -138,14 +150,21 @@ export default class ImageNode extends EditorNodeMixin(Image) {
alphaMode: this.alphaMode,
projection: this.projection
};
if (this.alphaMode === ImageAlphaMode.Mask) {
imageData.alphaCutoff = this.alphaCutoff;
}
this.addGLTFComponent("image", imageData);
this.addGLTFComponent("networked", {
id: this.uuid
});
if (this.billboard && this.projection === "flat") {
this.addGLTFComponent("billboard", {});
}
this.replaceObject();
}

Просмотреть файл

@ -75,6 +75,10 @@ export default class ModelNode extends EditorNodeMixin(Model) {
node.castShadow = shadowComponent.props.cast;
node.receiveShadow = shadowComponent.props.receive;
}
if (json.components.find(c => c.name === "billboard")) {
node.billboard = true;
}
})()
);
@ -93,6 +97,7 @@ export default class ModelNode extends EditorNodeMixin(Model) {
this.boundingSphere = new Sphere();
this.stats = defaultStats;
this.gltfJson = null;
this._billboard = false;
}
// Overrides Model's src property and stores the original (non-resolved) url.
@ -105,6 +110,15 @@ export default class ModelNode extends EditorNodeMixin(Model) {
this.load(value).catch(console.error);
}
get billboard() {
return this._billboard;
}
set billboard(value) {
this._billboard = value;
this.updateStaticModes();
}
// Overrides Model's loadGLTF method and uses the Editor's gltf cache.
async loadGLTF(src) {
const loader = this.editor.gltfCache.getLoader(src);
@ -320,6 +334,10 @@ export default class ModelNode extends EditorNodeMixin(Model) {
}
}
}
if (this.billboard) {
setStaticMode(this.model, StaticModes.Dynamic);
}
}
serialize() {
@ -352,6 +370,10 @@ export default class ModelNode extends EditorNodeMixin(Model) {
components.combine = {};
}
if (this.billboard) {
components.billboard = {};
}
return super.serialize(components);
}
@ -372,6 +394,8 @@ export default class ModelNode extends EditorNodeMixin(Model) {
this.collidable = source.collidable;
this.walkable = source.walkable;
this.combine = source.combine;
this.billboard = source.billboard;
return this;
}
@ -399,5 +423,9 @@ export default class ModelNode extends EditorNodeMixin(Model) {
activeClipIndices: clipIndices
});
}
if (this.billboard) {
this.addGLTFComponent("billboard", {});
}
}
}

Просмотреть файл

@ -35,6 +35,10 @@ export default class VideoNode extends EditorNodeMixin(Video) {
projection
} = json.components.find(c => c.name === "video").props;
if (json.components.find(c => c.name === "billboard")) {
node.billboard = true;
}
loadAsync(
(async () => {
await node.load(src, onError);
@ -64,6 +68,7 @@ export default class VideoNode extends EditorNodeMixin(Video) {
this._autoPlay = true;
this.volume = 0.5;
this.controls = true;
this.billboard = false;
}
get src() {
@ -175,13 +180,14 @@ export default class VideoNode extends EditorNodeMixin(Video) {
super.copy(source, recursive);
this.controls = source.controls;
this.billboard = source.billboard;
this._canonicalUrl = source._canonicalUrl;
return this;
}
serialize() {
return super.serialize({
const components = {
video: {
src: this._canonicalUrl,
controls: this.controls,
@ -198,11 +204,18 @@ export default class VideoNode extends EditorNodeMixin(Video) {
coneOuterGain: this.coneOuterGain,
projection: this.projection
}
});
};
if (this.billboard) {
components.billboard = {};
}
return super.serialize(components);
}
prepareForExport() {
super.prepareForExport();
this.addGLTFComponent("video", {
src: this._canonicalUrl,
controls: this.controls,
@ -219,9 +232,15 @@ export default class VideoNode extends EditorNodeMixin(Video) {
coneOuterGain: this.coneOuterGain,
projection: this.projection
});
this.addGLTFComponent("networked", {
id: this.uuid
});
if (this.billboard && this.projection === "flat") {
this.addGLTFComponent("billboard", {});
}
this.replaceObject();
}

Просмотреть файл

@ -19,6 +19,7 @@ export default function ImageNodeEditor(props) {
const { editor, node } = props;
const onChangeSrc = useSetPropertySelected(editor, "src");
const onChangeControls = useSetPropertySelected(editor, "controls");
const onChangeBillboard = useSetPropertySelected(editor, "billboard");
const onChangeProjection = useSetPropertySelected(editor, "projection");
const onChangeTransparencyMode = useSetPropertySelected(editor, "alphaMode");
const onChangeAlphaCutoff = useSetPropertySelected(editor, "alphaCutoff");
@ -28,9 +29,15 @@ export default function ImageNodeEditor(props) {
<InputGroup name="Image Url">
<ImageInput value={node.src} onChange={onChangeSrc} />
</InputGroup>
<InputGroup name="Controls" info="Toggle the visibility of the media controls in Hubs.">
<InputGroup
name="Controls"
info="Toggle the visibility of the media controls in Hubs. Does not billboard in Spoke."
>
<BooleanInput value={node.controls} onChange={onChangeControls} />
</InputGroup>
<InputGroup name="Billboard" info="Image always faces user in Hubs.">
<BooleanInput value={node.billboard} onChange={onChangeBillboard} />
</InputGroup>
<InputGroup
name="Transparency Mode"
info={`How to apply transparency:

Просмотреть файл

@ -48,6 +48,10 @@ export default class ModelNodeEditor extends Component {
this.props.editor.setPropertySelected("combine", combine);
};
onChangeBillboard = billboard => {
this.props.editor.setPropertySelected("billboard", billboard);
};
isAnimationPropertyDisabled() {
const { multiEdit, editor, node } = this.props;
@ -92,6 +96,9 @@ export default class ModelNodeEditor extends Component {
<InputGroup name="Combine">
<BooleanInput value={node.combine} onChange={this.onChangeCombine} />
</InputGroup>
<InputGroup name="Billboard" info="Model always faces user in Hubs. Does not billboard in Spoke.">
<BooleanInput value={node.billboard} onChange={this.onChangeBillboard} />
</InputGroup>
{node.model && <GLTFInfo node={node} />}
<AttributionNodeEditor name="Attribution" {...this.props} />
</NodeEditor>

Просмотреть файл

@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import NodeEditor from "./NodeEditor";
import InputGroup from "../inputs/InputGroup";
import SelectInput from "../inputs/SelectInput";
import BooleanInput from "../inputs/BooleanInput";
import { VideoProjection } from "../../editor/objects/Video";
import VideoInput from "../inputs/VideoInput";
import { Video } from "styled-icons/fa-solid/Video";
@ -16,12 +17,16 @@ export default function VideoNodeEditor(props) {
const { editor, node } = props;
const onChangeSrc = useSetPropertySelected(editor, "src");
const onChangeProjection = useSetPropertySelected(editor, "projection");
const onChangeBillboard = useSetPropertySelected(editor, "billboard");
return (
<NodeEditor description={VideoNodeEditor.description} {...props}>
<InputGroup name="Video">
<VideoInput value={node.src} onChange={onChangeSrc} />
</InputGroup>
<InputGroup name="Billboard" info="Video always faces user in Hubs. Does not billboard in Spoke.">
<BooleanInput value={node.billboard} onChange={onChangeBillboard} />
</InputGroup>
<InputGroup name="Projection">
<SelectInput options={videoProjectionOptions} value={node.projection} onChange={onChangeProjection} />
</InputGroup>

2
test/fixtures/V5TestScene.spoke поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -3188,6 +3188,59 @@ Generated by [AVA](https://avajs.dev).
name: 'Terrain_Crater1.glb',
parent: '2266BED7-6CC4-48A6-95DD-9BCD3CF9EAFC',
},
'8A3FEDAD-3C34-42E0-ADC7-242AE66AED68': {
components: [
{
name: 'transform',
props: {
position: {
x: -3.5,
y: 0.5,
z: 7,
},
rotation: {
x: 0,
y: 0,
z: 0,
},
scale: {
x: 1,
y: 1,
z: 1,
},
},
},
{
name: 'visible',
props: {
visible: true,
},
},
{
name: 'editor-settings',
props: {
enabled: true,
},
},
{
name: 'image',
props: {
alphaCutoff: 0.5,
alphaMode: 'opaque',
controls: true,
projection: 'flat',
src: 'https://hubs.local:9090/test-assets/spoke-logo.png',
},
},
{
name: 'billboard',
props: {},
},
],
index: 23,
name: 'Image',
parent: '2266BED7-6CC4-48A6-95DD-9BCD3CF9EAFC',
},
'8A79191D-49EF-45F8-A695-130AD04FAD3A': {
components: [
{
@ -3558,6 +3611,75 @@ Generated by [AVA](https://avajs.dev).
name: 'Audio',
parent: '2266BED7-6CC4-48A6-95DD-9BCD3CF9EAFC',
},
'D77AB1F6-B5F6-42C9-9FCA-4B2F3640AEE9': {
components: [
{
name: 'transform',
props: {
position: {
x: 4,
y: 0,
z: 6.5,
},
rotation: {
x: 0,
y: 0,
z: 0,
},
scale: {
x: 0.01,
y: 0.01,
z: 0.01,
},
},
},
{
name: 'visible',
props: {
visible: true,
},
},
{
name: 'editor-settings',
props: {
enabled: true,
},
},
{
name: 'gltf-model',
props: {
attribution: null,
src: 'https://hubs.local:9090/test-assets/camera.glb',
},
},
{
name: 'shadow',
props: {
cast: false,
receive: false,
},
},
{
name: 'collidable',
props: {},
},
{
name: 'walkable',
props: {},
},
{
name: 'combine',
props: {},
},
{
name: 'billboard',
props: {},
},
],
index: 22,
name: 'Model',
parent: '2266BED7-6CC4-48A6-95DD-9BCD3CF9EAFC',
},
'D8061EA5-F0C4-4452-ADFE-B179E2F20CB1': {
components: [
{
@ -3666,6 +3788,68 @@ Generated by [AVA](https://avajs.dev).
name: 'Ground Plane',
parent: '2266BED7-6CC4-48A6-95DD-9BCD3CF9EAFC',
},
'E5CB2E94-52BB-477B-B9FE-C7D5CD9F8C95': {
components: [
{
name: 'transform',
props: {
position: {
x: -5.5,
y: 1,
z: 4,
},
rotation: {
x: 0,
y: 0,
z: 0,
},
scale: {
x: 1,
y: 1,
z: 1,
},
},
},
{
name: 'visible',
props: {
visible: true,
},
},
{
name: 'editor-settings',
props: {
enabled: true,
},
},
{
name: 'video',
props: {
audioType: 'pannernode',
autoPlay: true,
coneInnerAngle: 360,
coneOuterAngle: 360,
coneOuterGain: 0,
controls: true,
distanceModel: 'inverse',
loop: true,
maxDistance: 10000,
projection: 'flat',
refDistance: 1,
rolloffFactor: 1,
src: 'https://hubs.local:9090/test-assets/landing-video.webm',
volume: 0.5,
},
},
{
name: 'billboard',
props: {},
},
],
index: 24,
name: 'Video 1',
parent: '2266BED7-6CC4-48A6-95DD-9BCD3CF9EAFC',
},
'E8D3129B-68BF-473F-92B9-6DAEC83514BB': {
components: [
{

Двоичные данные
test/integration/snapshots/Editor.test.js.snap

Двоичный файл не отображается.