mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Handle collision layers.
This commit is contained in:
@@ -164,6 +164,16 @@ module.exports = {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'layers') {
|
||||
behaviorContent.getChild('layers').setIntValue(parseInt(newValue, 10));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (propertyName === 'masks') {
|
||||
behaviorContent.getChild('masks').setIntValue(parseInt(newValue, 10));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
behavior.getProperties = function (behaviorContent) {
|
||||
@@ -377,6 +387,20 @@ module.exports = {
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setGroup(_('Gravity'))
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('layers')
|
||||
.setValue(behaviorContent.getChild('layers').getIntValue().toString(10))
|
||||
.setType('Number')
|
||||
.setLabel('Layers')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
behaviorProperties
|
||||
.getOrCreate('masks')
|
||||
.setValue(behaviorContent.getChild('masks').getIntValue().toString(10))
|
||||
.setType('Number')
|
||||
.setLabel('Masks')
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
@@ -398,6 +422,8 @@ module.exports = {
|
||||
behaviorContent.addChild('linearDamping').setDoubleValue(0.1);
|
||||
behaviorContent.addChild('angularDamping').setDoubleValue(0.1);
|
||||
behaviorContent.addChild('gravityScale').setDoubleValue(1);
|
||||
behaviorContent.addChild('layers').setIntValue(1 << 4 | 1 << 0);
|
||||
behaviorContent.addChild('masks').setIntValue(1 << 4 | 1 << 0);
|
||||
};
|
||||
|
||||
const sharedData = new gd.BehaviorSharedDataJsImplementation();
|
||||
@@ -436,7 +462,6 @@ module.exports = {
|
||||
sharedContent.getChild('worldScale').setDoubleValue(newValueAsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
sharedData.getProperties = function (sharedContent) {
|
||||
@@ -464,7 +489,6 @@ module.exports = {
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getNewton());
|
||||
|
||||
|
||||
sharedProperties
|
||||
.getOrCreate('worldScale')
|
||||
.setValue(
|
||||
|
@@ -46,46 +46,36 @@ namespace gdjs {
|
||||
props: Physics3DNetworkSyncDataType;
|
||||
}
|
||||
|
||||
// https://github.com/jrouwe/JoltPhysics.js/blob/main/Examples/js/example.js
|
||||
const LAYER_NON_MOVING = 0;
|
||||
const LAYER_MOVING = 1;
|
||||
const NUM_OBJECT_LAYERS = 2;
|
||||
// There are 4 bits for static layers and 4 bits for dynamic layers.
|
||||
const staticLayersMask = 0x0f;
|
||||
const dynamicLayersMask = 0xf0;
|
||||
const allLayersMask = 0xff;
|
||||
|
||||
const setupCollisionFiltering = (settings: Jolt.JoltSettings) => {
|
||||
// Layer that objects can be in, determines which other objects it can collide with
|
||||
// Typically you at least want to have 1 layer for moving bodies and 1 layer for static bodies, but you can have more
|
||||
// layers if you want. E.g. you could have a layer for high detail collision (which is not used by the physics simulation
|
||||
// but only if you do collision testing).
|
||||
let objectFilter = new Jolt.ObjectLayerPairFilterTable(NUM_OBJECT_LAYERS);
|
||||
objectFilter.EnableCollision(LAYER_NON_MOVING, LAYER_MOVING);
|
||||
objectFilter.EnableCollision(LAYER_MOVING, LAYER_MOVING);
|
||||
|
||||
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least want to have
|
||||
// a layer for non-moving and moving objects to avoid having to update a tree full of static objects every frame.
|
||||
// You can have a 1-on-1 mapping between object layers and broadphase layers (like in this case) but if you have
|
||||
// many object layers you'll be creating many broad phase trees, which is not efficient.
|
||||
const BP_LAYER_NON_MOVING = new Jolt.BroadPhaseLayer(0);
|
||||
const BP_LAYER_MOVING = new Jolt.BroadPhaseLayer(1);
|
||||
const NUM_BROAD_PHASE_LAYERS = 2;
|
||||
let bpInterface = new Jolt.BroadPhaseLayerInterfaceTable(
|
||||
NUM_OBJECT_LAYERS,
|
||||
NUM_BROAD_PHASE_LAYERS
|
||||
const objectFilter = new Jolt.ObjectLayerPairFilterMask();
|
||||
const staticBroadPhaseLayer = new Jolt.BroadPhaseLayer(0);
|
||||
const dynamicBroadPhaseLayer = new Jolt.BroadPhaseLayer(1);
|
||||
const broadPhaseLayerInterfaceMask = new Jolt.BroadPhaseLayerInterfaceMask(
|
||||
2
|
||||
);
|
||||
bpInterface.MapObjectToBroadPhaseLayer(
|
||||
LAYER_NON_MOVING,
|
||||
BP_LAYER_NON_MOVING
|
||||
broadPhaseLayerInterfaceMask.ConfigureLayer(
|
||||
staticBroadPhaseLayer,
|
||||
staticLayersMask,
|
||||
0
|
||||
);
|
||||
bpInterface.MapObjectToBroadPhaseLayer(LAYER_MOVING, BP_LAYER_MOVING);
|
||||
broadPhaseLayerInterfaceMask.ConfigureLayer(
|
||||
dynamicBroadPhaseLayer,
|
||||
dynamicLayersMask,
|
||||
0
|
||||
);
|
||||
// BroadPhaseLayer have been copied into bpInterface
|
||||
Jolt.destroy(staticBroadPhaseLayer);
|
||||
Jolt.destroy(dynamicBroadPhaseLayer);
|
||||
|
||||
settings.mObjectLayerPairFilter = objectFilter;
|
||||
settings.mBroadPhaseLayerInterface = bpInterface;
|
||||
settings.mBroadPhaseLayerInterface = broadPhaseLayerInterfaceMask;
|
||||
settings.mObjectVsBroadPhaseLayerFilter =
|
||||
new Jolt.ObjectVsBroadPhaseLayerFilterTable(
|
||||
settings.mBroadPhaseLayerInterface,
|
||||
NUM_BROAD_PHASE_LAYERS,
|
||||
settings.mObjectLayerPairFilter,
|
||||
NUM_OBJECT_LAYERS
|
||||
);
|
||||
new Jolt.ObjectVsBroadPhaseLayerFilterMask(broadPhaseLayerInterfaceMask);
|
||||
};
|
||||
|
||||
export class Physics3DSharedData {
|
||||
@@ -144,10 +134,8 @@ namespace gdjs {
|
||||
const bodyA = Jolt.wrapPointer(bodyPtrA, Jolt.Body);
|
||||
const bodyB = Jolt.wrapPointer(bodyPtrB, Jolt.Body);
|
||||
|
||||
// Get associated behaviors
|
||||
const behaviorA = bodyA.gdjsAssociatedBehavior;
|
||||
const behaviorB = bodyB.gdjsAssociatedBehavior;
|
||||
|
||||
if (!behaviorA || !behaviorB) {
|
||||
return;
|
||||
}
|
||||
@@ -184,14 +172,14 @@ namespace gdjs {
|
||||
behaviorB.onContactEnd(behaviorA);
|
||||
};
|
||||
this.contactListener.OnContactPersisted = (
|
||||
inBody1: number,
|
||||
inBody2: number,
|
||||
inManifold: number,
|
||||
ioSettings: number
|
||||
bodyPtrA: number,
|
||||
bodyPtrB: number,
|
||||
manifoldPtr: number,
|
||||
settingsPtr: number
|
||||
): void => {};
|
||||
this.contactListener.OnContactValidate = (
|
||||
inBody1: number,
|
||||
inBody2: number,
|
||||
bodyPtrA: number,
|
||||
bodyPtrB: number,
|
||||
inBaseOffset: number,
|
||||
inCollisionResult: number
|
||||
): number => {
|
||||
@@ -298,6 +286,8 @@ namespace gdjs {
|
||||
linearDamping: float;
|
||||
angularDamping: float;
|
||||
gravityScale: float;
|
||||
layers: integer;
|
||||
masks: integer;
|
||||
shapeScale: number = 1;
|
||||
|
||||
/**
|
||||
@@ -367,6 +357,8 @@ namespace gdjs {
|
||||
this.linearDamping = Math.max(0, behaviorData.linearDamping);
|
||||
this.angularDamping = Math.max(0, behaviorData.angularDamping);
|
||||
this.gravityScale = behaviorData.gravityScale;
|
||||
this.layers = behaviorData.layers;
|
||||
this.masks = behaviorData.masks;
|
||||
this.destroyedDuringFrameLogic = false;
|
||||
this._sharedData = Physics3DSharedData.getSharedData(
|
||||
instanceContainer.getScene(),
|
||||
@@ -588,7 +580,14 @@ namespace gdjs {
|
||||
: this.bodyType === 'Kinematic'
|
||||
? Jolt.EMotionType_Kinematic
|
||||
: Jolt.EMotionType_Dynamic,
|
||||
LAYER_MOVING
|
||||
Jolt.ObjectLayerPairFilterMask.prototype.sGetObjectLayer(
|
||||
// Make sure objects don't register in the wrong layer group.
|
||||
this.bodyType === 'Static'
|
||||
? this.layers & staticLayersMask
|
||||
: this.layers & dynamicLayersMask,
|
||||
// Static objects accept all collisions as it's the mask of dynamic objects that matters.
|
||||
this.bodyType === 'Static' ? allLayersMask : this.masks
|
||||
)
|
||||
);
|
||||
bodyCreationSettings.mMotionQuality = this.bullet
|
||||
? Jolt.EMotionQuality_LinearCast
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
import BehaviorPropertiesEditor from './Editors/BehaviorPropertiesEditor';
|
||||
import Physics2Editor from './Editors/Physics2Editor';
|
||||
import Physics3DEditor from './Editors/Physics3DEditor';
|
||||
|
||||
/**
|
||||
* A service returning editor components for each behavior type.
|
||||
@@ -13,8 +14,8 @@ const BehaviorsEditorService = {
|
||||
return this.components[behaviorType].component; // Custom behavior editor
|
||||
},
|
||||
components: {
|
||||
'Physics2::Physics2Behavior': {
|
||||
component: Physics2Editor,
|
||||
'Physics3D::Physics3DBehavior': {
|
||||
component: Physics3DEditor,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
411
newIDE/app/src/BehaviorsEditor/Editors/Physics3DEditor/index.js
Normal file
411
newIDE/app/src/BehaviorsEditor/Editors/Physics3DEditor/index.js
Normal file
@@ -0,0 +1,411 @@
|
||||
// @flow
|
||||
import { t } from '@lingui/macro';
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import * as React from 'react';
|
||||
import { Line, Column, Spacer } from '../../../UI/Grid';
|
||||
import Checkbox from '../../../UI/Checkbox';
|
||||
import SelectField from '../../../UI/SelectField';
|
||||
import SelectOption from '../../../UI/SelectOption';
|
||||
import SemiControlledTextField from '../../../UI/SemiControlledTextField';
|
||||
import { getMeasurementUnitShortLabel } from '../../../PropertiesEditor/PropertiesMapToSchema';
|
||||
import MeasurementUnitDocumentation from '../../../PropertiesEditor/MeasurementUnitDocumentation';
|
||||
import { type BehaviorEditorProps } from '../BehaviorEditorProps.flow';
|
||||
import Text from '../../../UI/Text';
|
||||
import DismissableAlertMessage from '../../../UI/DismissableAlertMessage';
|
||||
import { ResponsiveLineStackLayout } from '../../../UI/Layout';
|
||||
import useForceUpdate from '../../../Utils/UseForceUpdate';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import ButtonGroup from '@material-ui/core/ButtonGroup';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import Tooltip from '@material-ui/core/Tooltip';
|
||||
|
||||
type Props = BehaviorEditorProps;
|
||||
|
||||
const NumericProperty = (props: {|
|
||||
id?: string,
|
||||
properties: gdMapStringPropertyDescriptor,
|
||||
propertyName: string,
|
||||
step: number,
|
||||
onUpdate: (newValue: string) => void,
|
||||
|}) => {
|
||||
const { properties, propertyName, step, onUpdate, id } = props;
|
||||
const property = properties.get(propertyName);
|
||||
|
||||
return (
|
||||
<SemiControlledTextField
|
||||
id={id}
|
||||
fullWidth
|
||||
value={property.getValue()}
|
||||
key={propertyName}
|
||||
floatingLabelText={property.getLabel()}
|
||||
step={step}
|
||||
onChange={onUpdate}
|
||||
type="number"
|
||||
endAdornment={<UnitAdornment property={property} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const UnitAdornment = (props: {| property: gdPropertyDescriptor |}) => {
|
||||
const { property } = props;
|
||||
const measurementUnit = property.getMeasurementUnit();
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<MeasurementUnitDocumentation
|
||||
label={measurementUnit.getLabel()}
|
||||
description={measurementUnit.getDescription()}
|
||||
elementsWithWords={measurementUnit.getElementsWithWords()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<InputAdornment position="end">
|
||||
{getMeasurementUnitShortLabel(measurementUnit)}
|
||||
</InputAdornment>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const BitGroupEditor = (props: {|
|
||||
bits: Array<boolean>,
|
||||
onChange: (index: number, value: boolean) => void,
|
||||
firstIndex: number,
|
||||
disabled: boolean,
|
||||
|}) => {
|
||||
return (
|
||||
<div style={{ overflowX: 'auto', flex: 1 }}>
|
||||
<ButtonGroup disableElevation fullWidth disabled={props.disabled}>
|
||||
{props.bits.map((bit, index) => (
|
||||
<Button
|
||||
key={props.firstIndex + index}
|
||||
variant={bit ? 'contained' : 'outlined'}
|
||||
color={bit ? 'primary' : 'default'}
|
||||
onClick={() => props.onChange(props.firstIndex + index, !bit)}
|
||||
>
|
||||
{props.firstIndex + index + 1}
|
||||
</Button>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isBitEnabled = (bitsValue: number, pos: number) => {
|
||||
return !!(bitsValue & (1 << pos));
|
||||
};
|
||||
|
||||
const enableBit = (bitsValue: number, pos: number, enable: boolean) => {
|
||||
if (enable) bitsValue |= 1 << pos;
|
||||
else bitsValue &= ~(1 << pos);
|
||||
return bitsValue;
|
||||
};
|
||||
|
||||
const Physics3DEditor = (props: Props) => {
|
||||
const { behavior, onBehaviorUpdated } = props;
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const updateBehaviorProperty = React.useCallback(
|
||||
(property, value) => {
|
||||
behavior.updateProperty(property, value);
|
||||
forceUpdate();
|
||||
onBehaviorUpdated();
|
||||
},
|
||||
[behavior, forceUpdate, onBehaviorUpdated]
|
||||
);
|
||||
|
||||
const properties = behavior.getProperties();
|
||||
const staticBits = Array(4).fill(null);
|
||||
const dynamicBits = Array(4).fill(null);
|
||||
const shape = properties.get('shape').getValue();
|
||||
const layersValues = parseInt(properties.get('layers').getValue(), 10);
|
||||
const masksValues = parseInt(properties.get('masks').getValue(), 10);
|
||||
|
||||
const isStatic = properties.get('bodyType').getValue() === 'Static';
|
||||
|
||||
return (
|
||||
<Column
|
||||
expand
|
||||
// Avoid overflow on small screens
|
||||
noOverflowParent
|
||||
>
|
||||
<Line>
|
||||
<SelectField
|
||||
id="physics2-parameter-body-type"
|
||||
key={'bodyType'}
|
||||
fullWidth
|
||||
floatingLabelText={properties.get('bodyType').getLabel()}
|
||||
value={properties.get('bodyType').getValue()}
|
||||
onChange={(e, i, newValue: string) =>
|
||||
updateBehaviorProperty('bodyType', newValue)
|
||||
}
|
||||
>
|
||||
{[
|
||||
<SelectOption
|
||||
key={'dynamic'}
|
||||
value={'Dynamic'}
|
||||
label={t`Dynamic`}
|
||||
/>,
|
||||
<SelectOption key={'static'} value={'Static'} label={t`Static`} />,
|
||||
<SelectOption
|
||||
key={'kinematic'}
|
||||
value={'Kinematic'}
|
||||
label={t`Kinematic`}
|
||||
/>,
|
||||
]}
|
||||
</SelectField>
|
||||
</Line>
|
||||
<ResponsiveLineStackLayout>
|
||||
<Checkbox
|
||||
label={properties.get('bullet').getLabel()}
|
||||
checked={properties.get('bullet').getValue() === 'true'}
|
||||
onCheck={(e, checked) =>
|
||||
updateBehaviorProperty('bullet', checked ? '1' : '0')
|
||||
}
|
||||
/>
|
||||
<Checkbox
|
||||
label={properties.get('fixedRotation').getLabel()}
|
||||
checked={properties.get('fixedRotation').getValue() === 'true'}
|
||||
onCheck={(e, checked) =>
|
||||
updateBehaviorProperty('fixedRotation', checked ? '1' : '0')
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
<Line>
|
||||
<DismissableAlertMessage
|
||||
identifier="physics2-shape-collisions"
|
||||
kind="info"
|
||||
>
|
||||
<Trans>
|
||||
The shape used in the Physics behavior is independent from the
|
||||
collision mask of the object. Be sure to use the "Collision"
|
||||
condition provided by the Physics behavior in the events. The usual
|
||||
"Collision" condition won't take into account the shape that you've
|
||||
set up here.
|
||||
</Trans>
|
||||
</DismissableAlertMessage>
|
||||
</Line>
|
||||
<Line>
|
||||
<SelectField
|
||||
id="physics2-parameter-shape"
|
||||
fullWidth
|
||||
floatingLabelText={properties.get('shape').getLabel()}
|
||||
value={properties.get('shape').getValue()}
|
||||
onChange={(e, i, newValue: string) =>
|
||||
updateBehaviorProperty('shape', newValue)
|
||||
}
|
||||
>
|
||||
<SelectOption key={'sphere'} value={'Sphere'} label={t`Sphere`} />
|
||||
<SelectOption key={'box'} value={'Box'} label={t`Box`} />
|
||||
<SelectOption key={'capsule'} value={'Capsule'} label={t`Capsule`} />
|
||||
<SelectOption
|
||||
key={'cylinder'}
|
||||
value={'Cylinder'}
|
||||
label={t`Cylinder`}
|
||||
/>
|
||||
</SelectField>
|
||||
</Line>
|
||||
<ResponsiveLineStackLayout>
|
||||
<SemiControlledTextField
|
||||
fullWidth
|
||||
value={properties.get('shapeDimensionA').getValue()}
|
||||
key={'shapeDimensionA'}
|
||||
floatingLabelText={shape === 'Box' ? 'Width' : 'Radius'}
|
||||
min={0}
|
||||
onChange={newValue =>
|
||||
updateBehaviorProperty('shapeDimensionA', newValue)
|
||||
}
|
||||
type="number"
|
||||
endAdornment={
|
||||
<UnitAdornment property={properties.get('shapeDimensionA')} />
|
||||
}
|
||||
/>
|
||||
{shape !== 'Sphere' && (
|
||||
<SemiControlledTextField
|
||||
fullWidth
|
||||
value={properties.get('shapeDimensionB').getValue()}
|
||||
key={'shapeDimensionB'}
|
||||
floatingLabelText={shape === 'Box' ? 'Width' : 'Depth'}
|
||||
min={0}
|
||||
onChange={newValue =>
|
||||
updateBehaviorProperty('shapeDimensionB', newValue)
|
||||
}
|
||||
type="number"
|
||||
endAdornment={
|
||||
<UnitAdornment property={properties.get('shapeDimensionB')} />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{shape === 'Box' && (
|
||||
<SemiControlledTextField
|
||||
fullWidth
|
||||
value={properties.get('shapeDimensionC').getValue()}
|
||||
key={'shapeDimensionC'}
|
||||
floatingLabelText={'Depth'}
|
||||
min={0}
|
||||
onChange={newValue =>
|
||||
updateBehaviorProperty('shapeDimensionC', newValue)
|
||||
}
|
||||
type="number"
|
||||
endAdornment={
|
||||
<UnitAdornment property={properties.get('shapeDimensionC')} />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ResponsiveLineStackLayout>
|
||||
<ResponsiveLineStackLayout>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'shapeOffsetX'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('shapeOffsetX', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'shapeOffsetY'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('shapeOffsetY', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'shapeOffsetZ'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('shapeOffsetZ', newValue)
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
<ResponsiveLineStackLayout>
|
||||
<NumericProperty
|
||||
id="physics2-parameter-density"
|
||||
properties={properties}
|
||||
propertyName={'density'}
|
||||
step={0.1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty(
|
||||
'density',
|
||||
parseFloat(newValue) > 0 ? newValue : '0'
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'gravityScale'}
|
||||
step={0.1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('gravityScale', newValue)
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
<ResponsiveLineStackLayout>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'friction'}
|
||||
step={0.1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty(
|
||||
'friction',
|
||||
parseFloat(newValue) > 0 ? newValue : '0'
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'restitution'}
|
||||
step={0.1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty(
|
||||
'restitution',
|
||||
parseFloat(newValue) > 0 ? newValue : '0'
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
<ResponsiveLineStackLayout>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'linearDamping'}
|
||||
step={0.05}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('linearDamping', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
id="physics2-parameter-angular-damping"
|
||||
properties={properties}
|
||||
propertyName={'angularDamping'}
|
||||
step={0.05}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('angularDamping', newValue)
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
<Line>
|
||||
<Text style={{ marginRight: 10 }}>
|
||||
{properties.get('layers').getLabel()}
|
||||
</Text>
|
||||
<BitGroupEditor
|
||||
key={'static-layers'}
|
||||
firstIndex={0}
|
||||
bits={staticBits.map(
|
||||
(_, index) => isBitEnabled(layersValues, index) && isStatic
|
||||
)}
|
||||
onChange={(index, value) => {
|
||||
const newValue = enableBit(layersValues, index, value);
|
||||
updateBehaviorProperty('layers', newValue.toString(10));
|
||||
}}
|
||||
disabled={!isStatic}
|
||||
/>
|
||||
<Spacer />
|
||||
<BitGroupEditor
|
||||
key={'dynamic-layers'}
|
||||
firstIndex={4}
|
||||
bits={dynamicBits.map(
|
||||
(_, index) => isBitEnabled(layersValues, index + 4) && !isStatic
|
||||
)}
|
||||
onChange={(index, value) => {
|
||||
const newValue = enableBit(layersValues, index, value);
|
||||
updateBehaviorProperty('layers', newValue.toString(10));
|
||||
}}
|
||||
disabled={isStatic}
|
||||
/>
|
||||
</Line>
|
||||
<Line>
|
||||
<Text style={{ marginRight: 10 }}>
|
||||
{properties.get('masks').getLabel()}
|
||||
</Text>
|
||||
<BitGroupEditor
|
||||
key={'static-mask'}
|
||||
firstIndex={0}
|
||||
bits={staticBits.map(
|
||||
(_, index) => isBitEnabled(masksValues, index) || isStatic
|
||||
)}
|
||||
onChange={(index, value) => {
|
||||
const newValue = enableBit(masksValues, index, value);
|
||||
updateBehaviorProperty('masks', newValue.toString(10));
|
||||
}}
|
||||
disabled={isStatic}
|
||||
/>
|
||||
<Spacer />
|
||||
<BitGroupEditor
|
||||
key={'dynamic-mask'}
|
||||
firstIndex={4}
|
||||
bits={dynamicBits.map(
|
||||
(_, index) => isBitEnabled(masksValues, index + 4) || isStatic
|
||||
)}
|
||||
onChange={(index, value) => {
|
||||
const newValue = enableBit(masksValues, index, value);
|
||||
updateBehaviorProperty('masks', newValue.toString(10));
|
||||
}}
|
||||
disabled={isStatic}
|
||||
/>
|
||||
</Line>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default Physics3DEditor;
|
Reference in New Issue
Block a user