Allow independent audio parameter overrides

This commit is contained in:
Manuel Martin 2021-09-27 21:19:52 +02:00
Родитель ff546df26e
Коммит 75585134ff
14 изменённых файлов: 534 добавлений и 340 удалений

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

@ -1,14 +1,14 @@
import EditorNodeMixin from "./EditorNodeMixin";
import { PlaneBufferGeometry, MeshBasicMaterial, Mesh, DoubleSide } from "three";
import audioIconUrl from "../../assets/audio-icon.png";
import AudioParamsNode from "./AudioParamsNode";
import AudioSource from "../objects/AudioSource";
import loadTexture from "../utils/loadTexture";
import { RethrownError } from "../utils/errors";
import { AudioType, DistanceModelType } from "../objects/AudioParams";
import { AudioElementType } from "../objects/AudioParams";
let audioHelperTexture = null;
export default class AudioNode extends EditorNodeMixin(AudioSource) {
export default class AudioNode extends AudioParamsNode(AudioSource) {
static componentName = "audio";
static nodeName = "Audio";
@ -22,18 +22,6 @@ export default class AudioNode extends EditorNodeMixin(AudioSource) {
const audioComp = json.components.find(c => c.name === "audio");
const { src, controls, autoPlay, loop } = audioComp.props;
const audioParamsComp = json.components.find(c => c.name === "audio-params");
const {
audioType,
gain,
distanceModel,
rolloffFactor,
refDistance,
maxDistance,
coneInnerAngle,
coneOuterAngle,
coneOuterGain
} = audioParamsComp.props;
loadAsync(
(async () => {
@ -41,15 +29,6 @@ export default class AudioNode extends EditorNodeMixin(AudioSource) {
node.controls = controls || false;
node.autoPlay = autoPlay;
node.loop = loop;
node.audioType = audioType;
node.gain = gain;
node.distanceModel = distanceModel;
node.rolloffFactor = rolloffFactor;
node.refDistance = refDistance;
node.maxDistance = maxDistance;
node.coneInnerAngle = coneInnerAngle;
node.coneOuterAngle = coneOuterAngle;
node.coneOuterGain = coneOuterGain;
})()
);
@ -57,7 +36,7 @@ export default class AudioNode extends EditorNodeMixin(AudioSource) {
}
constructor(editor) {
super(editor, editor.audioListener);
super(editor, editor.audioListener, AudioElementType.AUDIO);
this._canonicalUrl = "";
this._autoPlay = true;
@ -178,17 +157,6 @@ export default class AudioNode extends EditorNodeMixin(AudioSource) {
controls: this.controls,
autoPlay: this.autoPlay,
loop: this.loop
},
"audio-params": {
audioType: this.audioType,
gain: this.gain,
distanceModel: this.distanceModel,
rolloffFactor: this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
}
});
}
@ -202,20 +170,6 @@ export default class AudioNode extends EditorNodeMixin(AudioSource) {
autoPlay: this.autoPlay,
loop: this.loop
});
// We don't want artificial distance based attenuation to be applied to stereo audios
// so we set the distanceModel and rolloffFactor so the attenuation is always 1.
this.addGLTFComponent("audio-params", {
audioType: this.audioType,
gain: this.gain,
distanceModel: this.audioType === AudioType.Stereo ? DistanceModelType.Linear : this.distanceModel,
rolloffFactor: this.audioType === AudioType.Stereo ? 0 : this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
});
this.addGLTFComponent("networked", {
id: this.uuid
});

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

@ -0,0 +1,104 @@
import { AudioType, DistanceModelType, SourceType } from "../objects/AudioParams";
import EditorNodeMixin from "./EditorNodeMixin";
export default function AudioParamsNode(Type) {
return class extends EditorNodeMixin(Type) {
static async deserialize(editor, json) {
const node = await super.deserialize(editor, json);
const audioParamsProps = json.components.find(c => c.name === "audio-params").props;
node.overrideAudioSettings =
audioParamsProps.overrideAudioSettings === undefined ? true : audioParamsProps.overrideAudioSettings;
node.audioType = audioParamsProps.audioType;
node.gain = audioParamsProps.gain;
node.distanceModel = audioParamsProps.distanceModel;
node.rolloffFactor = audioParamsProps.rolloffFactor;
node.refDistance = audioParamsProps.refDistance;
node.maxDistance = audioParamsProps.maxDistance;
node.coneInnerAngle = audioParamsProps.coneInnerAngle;
node.coneOuterAngle = audioParamsProps.coneOuterAngle;
node.coneOuterGain = audioParamsProps.coneOuterGain;
return node;
}
constructor(editor, listener, type) {
super(editor, listener, type);
this._overrideAudioSettings = false;
}
get overrideAudioSettings() {
return this._overrideAudioSettings;
}
set overrideAudioSettings(overriden) {
this._overrideAudioSettings = overriden;
if (!overriden) {
this.enabledProperties = {};
}
}
copy(source, recursive = true) {
super.copy(source, recursive);
this._overrideAudioSettings = source._overrideAudioSettings;
return this;
}
serialize(components) {
return super.serialize({
...components,
...{
"audio-params": {
overrideAudioSettings: this.overrideAudioSettings,
audioType: this.audioType,
gain: this.gain,
distanceModel: this.distanceModel,
rolloffFactor: this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
}
}
});
}
prepareForExport() {
if (this.overrideAudioSettings) {
let distanceModel = this.optionalPropertyExportValue("distanceModel");
let rolloffFactor = this.optionalPropertyExportValue("rolloffFactor");
if (this.sourcetype === SourceType.MEDIA_VIDEO) {
// We don't want artificial distance based attenuation to be applied to media stereo audios
// so we set the distanceModel and rolloffFactor so the attenuation is always 1.
distanceModel = this.optionalPropertyExportValue["distanceModel"]
? this.audioType === AudioType.Stereo
? DistanceModelType.Linear
: this.optionalPropertyExportValue("distanceModel")
: undefined;
rolloffFactor = this.optionalPropertyExportValue["rolloffFactor"]
? this.audioType === AudioType.Stereo
? 0
: this.optionalPropertyExportValue("rolloffFactor")
: undefined;
}
this.addGLTFComponent("audio-params", {
audioType: this.optionalPropertyExportValue("audioType"),
gain: this.optionalPropertyExportValue("gain"),
distanceModel,
rolloffFactor,
refDistance: this.optionalPropertyExportValue("refDistance"),
maxDistance: this.optionalPropertyExportValue("maxDistance"),
coneInnerAngle: this.optionalPropertyExportValue("coneInnerAngle"),
coneOuterAngle: this.optionalPropertyExportValue("coneOuterAngle"),
coneOuterGain: this.optionalPropertyExportValue("coneOuterGain")
});
}
}
};
}

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

@ -1,10 +1,10 @@
import { Material, BoxBufferGeometry, Mesh, BoxHelper } from "three";
import AudioParams from "../objects/AudioParams";
import EditorNodeMixin from "./EditorNodeMixin";
import AudioParams, { AudioElementType } from "../objects/AudioParams";
import AudioParamsNode from "./AudioParamsNode";
const requiredProperties = ["enabled", "inOut", "outIn"];
export default class AudioZoneNode extends EditorNodeMixin(AudioParams) {
export default class AudioZoneNode extends AudioParamsNode(AudioParams) {
static componentName = "audio-zone";
static nodeName = "Audio Zone";
@ -13,8 +13,8 @@ export default class AudioZoneNode extends EditorNodeMixin(AudioParams) {
static _material = new Material();
static async deserialize(editor, json) {
const node = await super.deserialize(editor, json);
static async deserialize(editor, json, loadAsync, onError) {
const node = await super.deserialize(editor, json, loadAsync, onError);
const zoneProps = json.components.find(c => c.name === "audio-zone").props;
@ -22,23 +22,11 @@ export default class AudioZoneNode extends EditorNodeMixin(AudioParams) {
node.inOut = zoneProps.inOut;
node.outIn = zoneProps.outIn;
const audioParamsProps = json.components.find(c => c.name === "audio-params").props;
node.audioType = audioParamsProps.audioType;
node.gain = audioParamsProps.gain;
node.distanceModel = audioParamsProps.distanceModel;
node.rolloffFactor = audioParamsProps.rolloffFactor;
node.refDistance = audioParamsProps.refDistance;
node.maxDistance = audioParamsProps.maxDistance;
node.coneInnerAngle = audioParamsProps.coneInnerAngle;
node.coneOuterAngle = audioParamsProps.coneOuterAngle;
node.coneOuterGain = audioParamsProps.coneOuterGain;
return node;
}
constructor(editor) {
super(editor, editor.audioListener);
super(editor, AudioElementType.AUDIO_ZONE);
const boxMesh = new Mesh(AudioZoneNode._geometry, AudioZoneNode._material);
const box = new BoxHelper(boxMesh, 0x00ff00);
@ -78,17 +66,6 @@ export default class AudioZoneNode extends EditorNodeMixin(AudioParams) {
enabled: this.enabled,
inOut: this.inOut,
outIn: this.outIn
},
"audio-params": {
audioType: this.audioType,
gain: this.gain,
distanceModel: this.distanceModel,
rolloffFactor: this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
}
});
}
@ -110,16 +87,5 @@ export default class AudioZoneNode extends EditorNodeMixin(AudioParams) {
inOut: this.inOut,
outIn: this.outIn
});
this.addGLTFComponent("audio-params", {
audioType: this.audioType,
gain: this.gain,
distanceModel: this.distanceModel,
rolloffFactor: this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
});
}
}

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

@ -65,6 +65,12 @@ export default function EditorNodeMixin(Object3DClass) {
if (editorSettingsComponent) {
node.enabled = editorSettingsComponent.props.enabled;
}
const enabledProperties = json.components.find(c => c.name === "enabled-properties");
if (enabledProperties) {
node.enabledProperties = enabledProperties.props.enabled;
}
}
return node;
@ -89,6 +95,7 @@ export default function EditorNodeMixin(Object3DClass) {
this.loadingCube = null;
this.errorIcon = null;
this.issues = [];
this._enabledProperties = {};
}
clone(recursive) {
@ -120,6 +127,7 @@ export default function EditorNodeMixin(Object3DClass) {
this.issues = source.issues.slice();
this._visible = source._visible;
this.enabled = source.enabled;
this._enabledProperties = source.enabledProperties;
return this;
}
@ -189,6 +197,12 @@ export default function EditorNodeMixin(Object3DClass) {
props: {
enabled: this.enabled
}
},
{
name: "enabled-properties",
props: {
enabled: this.enabledProperties
}
}
]
};
@ -457,5 +471,17 @@ export default function EditorNodeMixin(Object3DClass) {
}
}
}
optionalPropertyExportValue(propName) {
return this.enabledProperties[propName] ? this[propName] : undefined;
}
set enabledProperties(object) {
this._enabledProperties = { ...this._enabledProperties, ...object };
}
get enabledProperties() {
return this._enabledProperties;
}
};
}

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

@ -566,6 +566,12 @@ export default class SceneNode extends EditorNodeMixin(Scene) {
mediaConeOuterAngle: this.mediaConeOuterAngle,
mediaConeOuterGain: this.mediaConeOuterGain
}
},
{
name: "enabled-properties",
props: {
enabled: this.enabledProperties
}
}
]
}
@ -637,18 +643,18 @@ export default class SceneNode extends EditorNodeMixin(Scene) {
if (this.overrideAudioSettings) {
this.addGLTFComponent("audio-settings", {
avatarDistanceModel: this.avatarDistanceModel,
avatarRolloffFactor: this.avatarRolloffFactor,
avatarRefDistance: this.avatarRefDistance,
avatarMaxDistance: this.avatarMaxDistance,
mediaVolume: this.mediaVolume,
mediaDistanceModel: this.mediaDistanceModel,
mediaRolloffFactor: this.mediaRolloffFactor,
mediaRefDistance: this.mediaRefDistance,
mediaMaxDistance: this.mediaMaxDistance,
mediaConeInnerAngle: this.mediaConeInnerAngle,
mediaConeOuterAngle: this.mediaConeOuterAngle,
mediaConeOuterGain: this.mediaConeOuterGain
avatarDistanceModel: this.optionalPropertyExportValue("avatarDistanceModel"),
avatarRolloffFactor: this.optionalPropertyExportValue("avatarRolloffFactor"),
avatarRefDistance: this.optionalPropertyExportValue("avatarRefDistance"),
avatarMaxDistance: this.optionalPropertyExportValue("avatarMaxDistance"),
mediaVolume: this.optionalPropertyExportValue("mediaVolume"),
mediaDistanceModel: this.optionalPropertyExportValue("mediaDistanceModel"),
mediaRolloffFactor: this.optionalPropertyExportValue("mediaRolloffFactor"),
mediaRefDistance: this.optionalPropertyExportValue("mediaRefDistance"),
mediaMaxDistance: this.optionalPropertyExportValue("mediaMaxDistance"),
mediaConeInnerAngle: this.optionalPropertyExportValue("mediaConeInnerAngle"),
mediaConeOuterAngle: this.optionalPropertyExportValue("mediaConeOuterAngle"),
mediaConeOuterGain: this.optionalPropertyExportValue("mediaConeOuterGain")
});
}
}

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

@ -1,13 +1,13 @@
import EditorNodeMixin from "./EditorNodeMixin";
import Video from "../objects/Video";
import AudioParamsNode from "./AudioParamsNode";
import Hls from "hls.js/dist/hls.light";
import isHLS from "../utils/isHLS";
import spokeLandingVideo from "../../assets/video/SpokePromo.mp4";
import { RethrownError } from "../utils/errors";
import { getObjectPerfIssues } from "../utils/performance";
import { AudioType, DistanceModelType } from "../objects/AudioParams";
import { AudioElementType } from "../objects/AudioParams";
export default class VideoNode extends EditorNodeMixin(Video) {
export default class VideoNode extends AudioParamsNode(Video) {
static componentName = "video";
static nodeName = "Video";
@ -21,18 +21,6 @@ export default class VideoNode extends EditorNodeMixin(Video) {
const videoComp = json.components.find(c => c.name === "video");
const { src, controls, autoPlay, loop, projection } = videoComp.props;
const audioParamsComp = json.components.find(c => c.name === "audio-params");
const {
audioType,
gain,
distanceModel,
rolloffFactor,
refDistance,
maxDistance,
coneInnerAngle,
coneOuterAngle,
coneOuterGain
} = audioParamsComp.props;
loadAsync(
(async () => {
@ -41,15 +29,6 @@ export default class VideoNode extends EditorNodeMixin(Video) {
node.autoPlay = autoPlay;
node.loop = loop;
node.projection = projection;
node.audioType = audioType;
node.gain = gain;
node.distanceModel = distanceModel;
node.rolloffFactor = rolloffFactor;
node.refDistance = refDistance;
node.maxDistance = maxDistance;
node.coneInnerAngle = coneInnerAngle;
node.coneOuterAngle = coneOuterAngle;
node.coneOuterGain = coneOuterGain;
})()
);
@ -69,15 +48,6 @@ export default class VideoNode extends EditorNodeMixin(Video) {
node.controls = controls || false;
node.autoPlay = autoPlay;
node.loop = loop;
node.audioType = audioType;
node.gain = gain;
node.distanceModel = distanceModel;
node.rolloffFactor = rolloffFactor;
node.refDistance = refDistance;
node.maxDistance = maxDistance;
node.coneInnerAngle = coneInnerAngle;
node.coneOuterAngle = coneOuterAngle;
node.coneOuterGain = coneOuterGain;
node.projection = projection;
})()
);
@ -86,7 +56,7 @@ export default class VideoNode extends EditorNodeMixin(Video) {
}
constructor(editor) {
super(editor, editor.audioListener);
super(editor, editor.audioListener, AudioElementType.VIDEO);
this._canonicalUrl = "";
this._autoPlay = true;
@ -219,17 +189,6 @@ export default class VideoNode extends EditorNodeMixin(Video) {
autoPlay: this.autoPlay,
loop: this.loop,
projection: this.projection
},
"audio-params": {
audioType: this.audioType,
gain: this.gain,
distanceModel: this.distanceModel,
rolloffFactor: this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
}
};
@ -267,20 +226,6 @@ export default class VideoNode extends EditorNodeMixin(Video) {
this.addGLTFComponent("link", { href: this.href });
}
// We don't want artificial distance based attenuation to be applied to stereo audios
// so we set the distanceModel and rolloffFactor so the attenuation is always 1.
this.addGLTFComponent("audio-params", {
audioType: this.audioType,
gain: this.gain,
distanceModel: this.audioType === AudioType.Stereo ? DistanceModelType.Linear : this.distanceModel,
rolloffFactor: this.audioType === AudioType.Stereo ? 0 : this.rolloffFactor,
refDistance: this.refDistance,
maxDistance: this.maxDistance,
coneInnerAngle: this.coneInnerAngle,
coneOuterAngle: this.coneOuterAngle,
coneOuterGain: this.coneOuterGain
});
this.replaceObject();
}

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

@ -1,74 +1,86 @@
// These enums need to be kept in sync with the ones in the Hubs client for consistency
import { Object3D } from "three";
export const SourceType = Object.freeze({
MEDIA_VIDEO: 0,
AVATAR_AUDIO_SOURCE: 1,
AVATAR_RIG: 2,
// TODO: Fill in missing value (2)
AUDIO_TARGET: 3,
AUDIO_ZONE: 4
});
export const AudioType = Object.freeze({
export const AudioType = {
Stereo: "stereo",
PannerNode: "pannernode"
});
};
export const DistanceModelType = Object.freeze({
export const DistanceModelType = {
Linear: "linear",
Inverse: "inverse",
Exponential: "exponential"
};
export const MediaAudioDefaults = Object.freeze({
audioType: AudioType.PannerNode,
distanceModel: DistanceModelType.Inverse,
rolloffFactor: 1,
refDistance: 1,
maxDistance: 10000,
coneInnerAngle: 360,
coneOuterAngle: 0,
coneOuterGain: 0,
gain: 0.5
});
export const AudioParamsDefaults = Object.freeze({
DISTANCE_MODEL: DistanceModelType.Inverse,
ROLLOFF_FACTOR: 1,
REF_DISTANCE: 1,
MAX_DISTANCE: 10000,
INNER_ANGLE: 360,
OUTER_ANGLE: 0,
OUTER_GAIN: 0,
GAIN: 0.5
export const AudioZoneDefaults = Object.freeze({
audioType: AudioType.PannerNode,
distanceModel: DistanceModelType.Inverse,
rolloffFactor: 1,
refDistance: 1,
maxDistance: 10000,
coneInnerAngle: 360,
coneOuterAngle: 0,
coneOuterGain: 0,
gain: 0.5
});
export const AvatarAudioParamsDefaults = Object.freeze({
DISTANCE_MODEL: DistanceModelType.Inverse,
ROLLOFF_FACTOR: 2,
REF_DISTANCE: 1,
MAX_DISTANCE: 10000,
INNER_ANGLE: 180,
OUTER_ANGLE: 360,
OUTER_GAIN: 0,
VOLUME: 1.0
export const Defaults = {
[SourceType.MEDIA_VIDEO]: MediaAudioDefaults,
[SourceType.AUDIO_ZONE]: AudioZoneDefaults
};
export const AudioElementType = Object.freeze({
AUDIO: "audio",
VIDEO: "video",
AUDIO_ZONE: "audio-zone"
});
export const MediaAudioParamsDefaults = Object.freeze({
DISTANCE_MODEL: DistanceModelType.Inverse,
ROLLOFF_FACTOR: 1,
REF_DISTANCE: 1,
MAX_DISTANCE: 10000,
INNER_ANGLE: 360,
OUTER_ANGLE: 0,
OUTER_GAIN: 0,
VOLUME: 0.5
});
export const sourceTypeForElementType = {
[AudioElementType.AUDIO]: SourceType.MEDIA_VIDEO,
[AudioElementType.VIDEO]: SourceType.MEDIA_VIDEO,
[AudioElementType.AUDIO_ZONE]: SourceType.AUDIO_ZONE
};
export const AudioTypeOptions = Object.values(AudioType).map(v => ({ label: v, value: v }));
export const DistanceModelOptions = Object.values(DistanceModelType).map(v => ({ label: v, value: v }));
export default class AudioParams extends Object3D {
constructor() {
super();
constructor(type, ...args) {
super(...args);
this.audioType = AudioType.PannerNode;
this.gain = AudioParamsDefaults.GAIN;
this.distanceModel = AudioParamsDefaults.DISTANCE_MODEL;
this.rolloffFactor = AudioParamsDefaults.ROLLOFF_FACTOR;
this.refDistance = AudioParamsDefaults.REF_DISTANCE;
this.maxDistance = AudioParamsDefaults.MAX_DISTANCE;
this.coneInnerAngle = AudioParamsDefaults.INNER_ANGLE;
this.coneOuterAngle = AudioParamsDefaults.OUTER_ANGLE;
this.coneOuterGain = AudioParamsDefaults.OUTER_GAIN;
this.sourceType = sourceTypeForElementType[type];
this.audioType = Defaults[this.sourceType].audioType;
this.gain = Defaults[this.sourceType].gain;
this.distanceModel = Defaults[this.sourceType].distanceModel;
this.rolloffFactor = Defaults[this.sourceType].rolloffFactor;
this.refDistance = Defaults[this.sourceType].refDistance;
this.maxDistance = Defaults[this.sourceType].maxDistance;
this.coneInnerAngle = Defaults[this.sourceType].coneInnerAngle;
this.coneOuterAngle = Defaults[this.sourceType].coneOuterAngle;
this.coneOuterGain = Defaults[this.sourceType].coneOuterGain;
}
get audioType() {

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

@ -1,12 +1,12 @@
import { Audio, PositionalAudio } from "three";
import { RethrownError } from "../utils/errors";
import { default as AudioParamsClass, AudioType } from "./AudioParams";
import AudioParams, { AudioType } from "./AudioParams";
export default class AudioSource extends AudioParamsClass {
constructor(audioListener, elTag = "audio") {
super();
export default class AudioSource extends AudioParams {
constructor(audioListener, type, ...args) {
super(type, ...args);
const el = document.createElement(elTag);
const el = document.createElement(type);
el.setAttribute("playsinline", "");
el.setAttribute("webkit-playsinline", "");
el.crossOrigin = "anonymous";

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

@ -20,8 +20,8 @@ export const VideoProjection = {
};
export default class Video extends AudioSource {
constructor(audioListener) {
super(audioListener, "video");
constructor(audioListener, sourceType) {
super(audioListener, sourceType);
this._videoTexture = new VideoTexture(this.el);
this._videoTexture.minFilter = LinearFilter;

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

@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import styled from "styled-components";
import { QuestionCircle } from "styled-icons/fa-regular/QuestionCircle";
import { InfoTooltip } from "../layout/Tooltip";
import BooleanInput from "./BooleanInput";
export const InputGroupContainer = styled.div`
display: flex;
@ -10,6 +11,7 @@ export const InputGroupContainer = styled.div`
padding: 4px 8px;
flex: 1;
min-height: 24px;
align-items: center;
${props =>
props.disabled &&
@ -20,7 +22,6 @@ export const InputGroupContainer = styled.div`
& > label {
display: block;
width: 25%;
color: ${props => props.theme.text2};
padding-bottom: 2px;
padding-top: 4px;
@ -28,10 +29,17 @@ export const InputGroupContainer = styled.div`
`;
export const InputGroupContent = styled.div`
${props =>
props.disabled &&
`
pointer-events: none;
opacity: 0.3;
`}
display: flex;
flex-direction: row;
flex: 1;
flex: 3;
padding-left: 8px;
align-items: center;
`;
export const InputGroupInfoIcon = styled(QuestionCircle)`
@ -43,6 +51,38 @@ export const InputGroupInfoIcon = styled(QuestionCircle)`
align-self: center;
`;
export const InputGroupHeader = styled.div`
display: flex;
flex-direction: row;
flex: 1;
align-items: center;
${props =>
props.disabled &&
`
pointer-events: none;
opacity: 0.3;
`}
& > :first-child {
padding-right: 8px;
}
`;
export const OptionalGroup = styled.div`
display: flex;
flex-direction: row;
flex: 1;
align-items: center;
${props =>
props.disabled &&
`
pointer-events: none;
opacity: 0.3;
`}
`;
export function InputGroupInfo({ info }) {
return (
<InfoTooltip info={info}>
@ -55,11 +95,14 @@ InputGroupInfo.propTypes = {
info: PropTypes.string
};
export default function InputGroup({ name, children, disabled, info, ...rest }) {
export default function InputGroup({ name, children, disabled, info, optional, enabled, onEnable, ...rest }) {
return (
<InputGroupContainer disabled={disabled} {...rest}>
<label>{name}:</label>
<InputGroupContent>
<InputGroupHeader>
{optional && <BooleanInput value={enabled} onChange={onEnable} />}
<OptionalGroup disabled={optional && !enabled}>{name && <label>{name}:</label>}</OptionalGroup>
</InputGroupHeader>
<InputGroupContent disabled={optional && !enabled}>
{children}
{info && <InputGroupInfo info={info} />}
</InputGroupContent>
@ -72,5 +115,8 @@ InputGroup.propTypes = {
children: PropTypes.any,
disabled: PropTypes.bool,
className: PropTypes.string,
info: PropTypes.string
info: PropTypes.string,
optional: PropTypes.bool,
enabled: PropTypes.bool,
onEnable: PropTypes.func
};

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

@ -1,15 +1,21 @@
import React from "react";
import PropTypes from "prop-types";
import { InputGroupContainer, InputGroupContent, InputGroupInfo } from "./InputGroup";
import { InputGroupHeader, InputGroupContainer, InputGroupContent, InputGroupInfo, OptionalGroup } from "./InputGroup";
import Scrubber from "./Scrubber";
import NumericInput from "./NumericInput";
import BooleanInput from "./BooleanInput";
export default function NumericInputGroup({ name, className, info, ...rest }) {
export default function NumericInputGroup({ name, className, info, optional, enabled, onEnable, ...rest }) {
const { displayPrecision, ...scrubberProps } = rest;
return (
<InputGroupContainer>
<Scrubber {...scrubberProps}>{name}:</Scrubber>
<InputGroupContent>
<InputGroupHeader>
{optional && <BooleanInput value={enabled} onChange={onEnable} />}
<OptionalGroup disabled={optional && !enabled}>
<Scrubber {...scrubberProps}>{name}:</Scrubber>
</OptionalGroup>
</InputGroupHeader>
<InputGroupContent disabled={optional && !enabled}>
<NumericInput {...rest} />
{info && <InputGroupInfo info={info} />}
</InputGroupContent>
@ -20,5 +26,8 @@ export default function NumericInputGroup({ name, className, info, ...rest }) {
NumericInputGroup.propTypes = {
name: PropTypes.string.isRequired,
className: PropTypes.string,
info: PropTypes.string
info: PropTypes.string,
optional: PropTypes.bool,
enabled: PropTypes.bool,
onEnable: PropTypes.func
};

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

@ -5,122 +5,168 @@ import SelectInput from "../inputs/SelectInput";
import NumericInputGroup from "../inputs/NumericInputGroup";
import CompoundNumericInput from "../inputs/CompoundNumericInput";
import { AudioType, AudioTypeOptions, DistanceModelOptions, DistanceModelType } from "../../editor/objects/AudioParams";
import useEnablePropertySelected from "./useEnablePropertySelected";
import useSetPropertySelected from "./useSetPropertySelected";
import BooleanInput from "../inputs/BooleanInput";
export default function AudioParamsProperties({ node, editor, multiEdit }) {
const onChangeAudioType = useSetPropertySelected(editor, "audioType");
const onChangeGain = useSetPropertySelected(editor, "gain");
const onChangeDistanceModel = useSetPropertySelected(editor, "distanceModel");
const onChangeRolloffFactor = useSetPropertySelected(editor, "rolloffFactor");
const onChangeRefDistance = useSetPropertySelected(editor, "refDistance");
const onChangeMaxDistance = useSetPropertySelected(editor, "maxDistance");
const onChangeConeInnerAngle = useSetPropertySelected(editor, "coneInnerAngle");
const onChangeConeOuterAngle = useSetPropertySelected(editor, "coneOuterAngle");
const onChangeConeOuterGain = useSetPropertySelected(editor, "coneOuterGain");
const onChangeOverrideAudioSettings = useSetPropertySelected(editor, "overrideAudioSettings");
const [onChangeAudioType, onEnableAudioType] = useEnablePropertySelected(editor, "audioType");
const [onChangeGain, onEnableGain] = useEnablePropertySelected(editor, "gain");
const [onChangeDistanceModel, onEnableDistanceModel] = useEnablePropertySelected(editor, "distanceModel");
const [onChangeRolloffFactor, onEnableRolloffFactor] = useEnablePropertySelected(editor, "rolloffFactor");
const [onChangeRefDistance, onEnableRefDistance] = useEnablePropertySelected(editor, "refDistance");
const [onChangeMaxDistance, onEnableMaxDistance] = useEnablePropertySelected(editor, "maxDistance");
const [onChangeConeInnerAngle, onEnableConeInnerAngle] = useEnablePropertySelected(editor, "coneInnerAngle");
const [onChangeConeOuterAngle, onEnableConeOuterAngle] = useEnablePropertySelected(editor, "coneOuterAngle");
const [onChangeConeOuterGain, onEnableConeOuterGain] = useEnablePropertySelected(editor, "coneOuterGain");
// TODO: Make node audio settings work with multi-edit
return (
<>
<InputGroup name="Audio Type">
<SelectInput options={AudioTypeOptions} value={node.audioType} onChange={onChangeAudioType} />
<InputGroup name="Override Audio Settings">
<BooleanInput value={node.overrideAudioSettings} onChange={onChangeOverrideAudioSettings} />
</InputGroup>
<InputGroup name="Volume">
<CompoundNumericInput value={node.gain} onChange={onChangeGain} />
</InputGroup>
{!multiEdit && node.audioType === AudioType.PannerNode && (
{node.overrideAudioSettings && (
<>
<InputGroup name="Distance Model" info="The algorithim used to calculate audio rolloff.">
<SelectInput options={DistanceModelOptions} value={node.distanceModel} onChange={onChangeDistanceModel} />
</InputGroup>
{node.distanceModel === DistanceModelType.linear ? (
<InputGroup
name="Rolloff Factor"
info="A double value describing how quickly the volume is reduced as the source moves away from the listener. 0 to 1"
>
<CompoundNumericInput
min={0}
max={1}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
value={node.rolloffFactor}
onChange={onChangeRolloffFactor}
/>
</InputGroup>
) : (
<NumericInputGroup
name="Rolloff Factor"
info="A double value describing how quickly the volume is reduced as the source moves away from the listener. 0 to Infinity"
min={0}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.rolloffFactor}
onChange={onChangeRolloffFactor}
/>
)}
<NumericInputGroup
name="Ref Distance"
info="A double value representing the reference distance for reducing volume as the audio source moves further from the listener."
min={0}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.refDistance}
onChange={onChangeRefDistance}
unit="m"
/>
<NumericInputGroup
name="Max Distance"
info="A double value representing the maximum distance between the audio source and the listener, after which the volume is not reduced any further."
min={0.00001}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.maxDistance}
onChange={onChangeMaxDistance}
unit="m"
/>
<NumericInputGroup
name="Cone Inner Angle"
info="A double value describing the angle, in degrees, of a cone inside of which there will be no volume reduction."
min={0}
max={360}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.coneInnerAngle}
onChange={onChangeConeInnerAngle}
unit="°"
disabled={multiEdit}
/>
<NumericInputGroup
name="Cone Outer Angle"
info="A double value describing the angle, in degrees, of a cone outside of which the volume will be reduced by a constant value, defined by the coneOuterGain attribute."
min={0}
max={360}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.coneOuterAngle}
onChange={onChangeConeOuterAngle}
unit="°"
disabled={multiEdit}
/>
<InputGroup
name="Cone Outer Gain"
info="A double value describing the amount of volume reduction outside the cone defined by the coneOuterAngle attribute. Its default value is 0, meaning that no sound can be heard."
name="Audio Type"
optional
enabled={node.enabledProperties["audioType"]}
onEnable={onEnableAudioType}
>
<CompoundNumericInput
min={0}
max={1}
step={0.01}
value={node.coneOuterGain}
onChange={onChangeConeOuterGain}
/>
<SelectInput options={AudioTypeOptions} value={node.audioType} onChange={onChangeAudioType} />
</InputGroup>
<InputGroup name="Volume" optional enabled={node.enabledProperties["gain"]} onEnable={onEnableGain}>
<CompoundNumericInput value={node.gain} onChange={onChangeGain} />
</InputGroup>
{!multiEdit && node.audioType === AudioType.PannerNode && (
<>
<InputGroup
name="Distance Model"
info="The algorithim used to calculate audio rolloff."
optional
enabled={node.enabledProperties["distanceModel"]}
onEnable={onEnableDistanceModel}
>
<SelectInput
options={DistanceModelOptions}
value={node.distanceModel}
onChange={onChangeDistanceModel}
/>
</InputGroup>
{node.distanceModel === DistanceModelType.linear ? (
<InputGroup
name="Rolloff Factor"
info="A double value describing how quickly the volume is reduced as the source moves away from the listener. 0 to 1"
optional
enabled={node.enabledProperties["rolloffFactor"]}
onEnable={onEnableRolloffFactor}
>
<CompoundNumericInput
min={0}
max={1}
smallStep={0.001}
mediumStep={0.01}
largeStep={0.1}
value={node.rolloffFactor}
onChange={onChangeRolloffFactor}
/>
</InputGroup>
) : (
<NumericInputGroup
name="Rolloff Factor"
info="A double value describing how quickly the volume is reduced as the source moves away from the listener. 0 to 1"
min={0}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.rolloffFactor}
onChange={onChangeRolloffFactor}
optional
enabled={node.enabledProperties["rolloffFactor"]}
onEnable={onEnableRolloffFactor}
/>
)}
<NumericInputGroup
name="Ref Distance"
info="A double value representing the reference distance for reducing volume as the audio source moves further from the listener."
min={0}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.refDistance}
onChange={onChangeRefDistance}
unit="m"
optional
enabled={node.enabledProperties["refDistance"]}
onEnable={onEnableRefDistance}
/>
<NumericInputGroup
name="Max Distance"
info="A double value representing the maximum distance between the audio source and the listener, after which the volume is not reduced any further."
min={0.00001}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.maxDistance}
onChange={onChangeMaxDistance}
unit="m"
optional
enabled={node.enabledProperties["maxDistance"]}
onEnable={onEnableMaxDistance}
/>
<NumericInputGroup
name="Cone Inner Angle"
info="A double value describing the angle, in degrees, of a cone inside of which there will be no volume reduction."
min={0}
max={360}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.coneInnerAngle}
onChange={onChangeConeInnerAngle}
unit="°"
disabled={multiEdit}
optional
enabled={node.enabledProperties["coneInnerAngle"]}
onEnable={onEnableConeInnerAngle}
/>
<NumericInputGroup
name="Cone Outer Angle"
info="A double value describing the angle, in degrees, of a cone outside of which the volume will be reduced by a constant value, defined by the coneOuterGain attribute."
min={0}
max={360}
smallStep={0.1}
mediumStep={1}
largeStep={10}
value={node.coneOuterAngle}
onChange={onChangeConeOuterAngle}
unit="°"
disabled={multiEdit}
optional
enabled={node.enabledProperties["coneOuterAngle"]}
onEnable={onEnableConeOuterAngle}
/>
<InputGroup
name="Cone Outer Gain"
info="A double value describing the amount of volume reduction outside the cone defined by the coneOuterAngle attribute. Its default value is 0, meaning that no sound can be heard."
optional
enabled={node.enabledProperties["coneOuterGain"]}
onEnable={onEnableConeOuterGain}
>
<CompoundNumericInput
min={0}
max={1}
step={0.01}
value={node.coneOuterGain}
onChange={onChangeConeOuterGain}
/>
</InputGroup>
</>
)}
</>
)}
</>

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

@ -11,6 +11,7 @@ import SelectInput from "../inputs/SelectInput";
import useSetPropertySelected from "./useSetPropertySelected";
import BooleanInput from "../inputs/BooleanInput";
import { DistanceModelOptions, DistanceModelType } from "../../editor/objects/AudioParams";
import useEnablePropertySelected from "./useEnablePropertySelected";
const FogTypeOptions = [
{
@ -38,18 +39,39 @@ export default function SceneNodeEditor(props) {
const onChangeFogDensity = useSetPropertySelected(editor, "fogDensity");
const onChangeOverrideAudioSettings = useSetPropertySelected(editor, "overrideAudioSettings");
const onChangeMediaVolume = useSetPropertySelected(editor, "mediaVolume");
const onChangeMediaDistanceModel = useSetPropertySelected(editor, "mediaDistanceModel");
const onChangeMediaRolloffFactor = useSetPropertySelected(editor, "mediaRolloffFactor");
const onChangeMediaRefDistance = useSetPropertySelected(editor, "mediaRefDistance");
const onChangeMediaMaxDistance = useSetPropertySelected(editor, "mediaMaxDistance");
const onChangeMediaConeInnerAngle = useSetPropertySelected(editor, "mediaConeInnerAngle");
const onChangeMediaConeOuterAngle = useSetPropertySelected(editor, "mediaConeOuterAngle");
const onChangeMediaConeOuterGain = useSetPropertySelected(editor, "mediaConeOuterGain");
const onChangeAvatarDistanceModel = useSetPropertySelected(editor, "avatarDistanceModel");
const onChangeAvatarRolloffFactor = useSetPropertySelected(editor, "avatarRolloffFactor");
const onChangeAvatarRefDistance = useSetPropertySelected(editor, "avatarRefDistance");
const onChangeAvatarMaxDistance = useSetPropertySelected(editor, "avatarMaxDistance");
const [onChangeMediaVolume, onEnableMediaVolume] = useEnablePropertySelected(editor, "mediaVolume");
const [onChangeMediaDistanceModel, onEnableMediaDistanceModel] = useEnablePropertySelected(
editor,
"mediaDistanceModel"
);
const [onChangeMediaRolloffFactor, onEnableMediaRolloffFactor] = useEnablePropertySelected(
editor,
"mediaRolloffFactor"
);
const [onChangeMediaRefDistance, onEnableMediaRefDistance] = useEnablePropertySelected(editor, "mediaRefDistance");
const [onChangeMediaMaxDistance, onEnableMediaMaxDistance] = useEnablePropertySelected(editor, "mediaMaxDistance");
const [onChangeMediaConeInnerAngle, onEnableMediaConeInnerAngle] = useEnablePropertySelected(
editor,
"mediaConeInnerAngle"
);
const [onChangeMediaConeOuterAngle, onEnableMediaConeOuterAngle] = useEnablePropertySelected(
editor,
"mediaConeOuterAngle"
);
const [onChangeMediaConeOuterGain, onEnableMediaConeOuterGain] = useEnablePropertySelected(
editor,
"mediaConeOuterGain"
);
const [onChangeAvatarDistanceModel, onEnableAvatarDistanceModel] = useEnablePropertySelected(
editor,
"avatarDistanceModel"
);
const [onChangeAvatarRolloffFactor, onEnableAvatarRolloffFactor] = useEnablePropertySelected(
editor,
"avatarRolloffFactor"
);
const [onChangeAvatarRefDistance, onEnableAvatarRefDistance] = useEnablePropertySelected(editor, "avatarRefDistance");
const [onChangeAvatarMaxDistance, onEnableAvatarMaxDistance] = useEnablePropertySelected(editor, "avatarMaxDistance");
return (
<NodeEditor {...props} description={SceneNodeEditor.description}>
@ -102,7 +124,13 @@ export default function SceneNodeEditor(props) {
</InputGroup>
{node.overrideAudioSettings && (
<>
<InputGroup name="Avatar Distance Model" info="The algorithim used to calculate audio rolloff.">
<InputGroup
name="Avatar Distance Model"
info="The algorithim used to calculate audio rolloff."
optional
enabled={node.enabledProperties["avatarDistanceModel"]}
onEnable={onEnableAvatarDistanceModel}
>
<SelectInput
options={DistanceModelOptions}
value={node.avatarDistanceModel}
@ -114,6 +142,9 @@ export default function SceneNodeEditor(props) {
<InputGroup
name="Avatar Rolloff Factor"
info="A double value describing how quickly the volume is reduced as the source moves away from the listener. 0 to 1"
optional
enabled={node.enabledProperties["avatarRolloffFactor"]}
onEnable={onEnableAvatarRolloffFactor}
>
<CompoundNumericInput
min={0}
@ -135,6 +166,9 @@ export default function SceneNodeEditor(props) {
largeStep={10}
value={node.avatarRolloffFactor}
onChange={onChangeAvatarRolloffFactor}
optional
enabled={node.enabledProperties["avatarRolloffFactor"]}
onEnable={onEnableAvatarRolloffFactor}
/>
)}
<NumericInputGroup
@ -147,6 +181,9 @@ export default function SceneNodeEditor(props) {
value={node.avatarRefDistance}
onChange={onChangeAvatarRefDistance}
unit="m"
optional
enabled={node.enabledProperties["avatarRefDistance"]}
onEnable={onEnableAvatarRefDistance}
/>
<NumericInputGroup
name="Avatar Max Distance"
@ -158,11 +195,25 @@ export default function SceneNodeEditor(props) {
value={node.avatarMaxDistance}
onChange={onChangeAvatarMaxDistance}
unit="m"
optional
enabled={node.enabledProperties["avatarMaxDistance"]}
onEnable={onEnableAvatarMaxDistance}
/>
<InputGroup name="Media Volume">
<InputGroup
name="Media Volume"
optional
enabled={node.enabledProperties["mediaVolume"]}
onEnable={onEnableMediaVolume}
>
<CompoundNumericInput value={node.mediaVolume} onChange={onChangeMediaVolume} />
</InputGroup>
<InputGroup name="Media Distance Model" info="The algorithim used to calculate audio rolloff.">
<InputGroup
name="Media Distance Model"
info="The algorithim used to calculate audio rolloff."
optional
enabled={node.enabledProperties["mediaDistanceModel"]}
onEnable={onEnableMediaDistanceModel}
>
<SelectInput
options={DistanceModelOptions}
value={node.mediaDistanceModel}
@ -174,6 +225,9 @@ export default function SceneNodeEditor(props) {
<InputGroup
name="Media Rolloff Factor"
info="A double value describing how quickly the volume is reduced as the source moves away from the listener. 0 to 1"
optional
enabled={node.enabledProperties["mediaRolloffFactor"]}
onEnable={onEnableMediaRolloffFactor}
>
<CompoundNumericInput
min={0}
@ -195,6 +249,9 @@ export default function SceneNodeEditor(props) {
largeStep={10}
value={node.mediaRolloffFactor}
onChange={onChangeMediaRolloffFactor}
optional
enabled={node.enabledProperties["mediaRolloffFactor"]}
onEnable={onEnableMediaRolloffFactor}
/>
)}
<NumericInputGroup
@ -207,6 +264,9 @@ export default function SceneNodeEditor(props) {
value={node.mediaRefDistance}
onChange={onChangeMediaRefDistance}
unit="m"
optional
enabled={node.enabledProperties["mediaRefDistance"]}
onEnable={onEnableMediaRefDistance}
/>
<NumericInputGroup
name="Media Max Distance"
@ -218,6 +278,9 @@ export default function SceneNodeEditor(props) {
value={node.mediaMaxDistance}
onChange={onChangeMediaMaxDistance}
unit="m"
optional
enabled={node.enabledProperties["mediaMaxDistance"]}
onEnable={onEnableMediaMaxDistance}
/>
<NumericInputGroup
name="Media Cone Inner Angle"
@ -230,6 +293,9 @@ export default function SceneNodeEditor(props) {
value={node.mediaConeInnerAngle}
onChange={onChangeMediaConeInnerAngle}
unit="°"
optional
enabled={node.enabledProperties["mediaConeInnerAngle"]}
onEnable={onEnableMediaConeInnerAngle}
/>
<NumericInputGroup
name="Media Cone Outer Angle"
@ -242,10 +308,16 @@ export default function SceneNodeEditor(props) {
value={node.mediaConeOuterAngle}
onChange={onChangeMediaConeOuterAngle}
unit="°"
optional
enabled={node.enabledProperties["mediaConeOuterAngle"]}
onEnable={onEnableMediaConeOuterAngle}
/>
<InputGroup
name="Media Cone Outer Gain"
info="A double value describing the amount of volume reduction outside the cone defined by the coneOuterAngle attribute. Its default value is 0, meaning that no sound can be heard."
optional
enabled={node.enabledProperties["mediaConeOuterGain"]}
onEnable={onEnableMediaConeOuterGain}
>
<CompoundNumericInput
min={0}

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

@ -0,0 +1,8 @@
import { useCallback } from "react";
export default function useEnablePropertySelected(editor, propName) {
return [
useCallback(value => editor.setPropertySelected(propName, value), [editor, propName]),
useCallback(value => editor.setPropertySelected("enabledProperties", { [propName]: value }), [editor, propName])
];
}