зеркало из https://github.com/mozilla/Spoke.git
Merge pull request #1087 from mozilla/feature/billboard-component
Add Billboard Support for Images, Videos, and Models
This commit is contained in:
Коммит
a8f40606ef
|
@ -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>
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -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
Двоичные данные
test/integration/snapshots/Editor.test.js.snap
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче