3D Character rolls with sphere - react-three-fiber

I am trying to make a spider have a certain velocity when control keys are pressed using react three cannon. The 3D model rolls along with the sphere.
The code is as follows:
// import { useFBX } from "#react-three/drei"
import { useSphere } from "#react-three/cannon"
import { useAnimations } from "#react-three/drei"
import { useLoader } from "#react-three/fiber"
import { useEffect, useState } from "react"
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
const Spider = (props) => {
const [position, setPosition] = useState(props.position)
const model = useLoader(FBXLoader, '../assets/models3D/spider/Spider.fbx')
console.log(model)
const animations = useAnimations(model.animations, model)
const actions = {
jump: {
anim: animations.actions["Spider_Armature|Jump"],
keys: [' '],
active: false
},
normal: {
anim: animations.actions["Spider_Armature|normal"],
keys: [],
active: false
},
wait: {
anim: animations.actions["Spider_Armature|warte_pose"],
keys: [],
active: true
},
walkForward: {
anim: animations.actions["Spider_Armature|walk_ani_vor"],
keys: ['ArrowUp', 'w'],
active: false
},
walkBackward: {
anim: animations.actions["Spider_Armature|walk_ani_back"],
keys: ['ArrowDown', 's'],
active: false
},
walkLeft: {
anim: animations.actions["Spider_Armature|walk_left"],
keys: ['ArrowLeft', 'a'],
active: false
},
walkRight: {
anim: animations.actions["Spider_Armature|walk_right"],
keys: ['ArrowRight', 'd'],
active: false
},
}
const [ref, api] = useSphere(() => ({ mass: 1, position: position, allowSleep: false, type: "Dynamic" }))
useEffect(() => {
actions.wait.anim.play()
window.addEventListener("keydown", ({ key }) => {
Object.keys(actions).forEach(i => {
let action = actions[i]
if (action.keys.indexOf(key) > -1) {
action.active = true
action.anim.play()
api.velocity.set(0, 0, -1);
}
})
})
window.addEventListener("keyup", ({ key }) => {
Object.keys(actions).forEach(i => {
let action = actions[i]
if (action.keys.indexOf(key) > -1) {
action.active = false
action.anim.stop()
api.velocity.set(0, 0, 0);
}
})
})
}, [])
return (<>
<primitive object={model} scale={0.05} position={position} ref={ref} />
</>)
}
export default Spider
How can the spider move along with the sphere without rolling? What could be a solution if useBox was used instead? Is reducing friction a good idea? If so how can it be done.
Please pardon my newness and thanks in advance!!

Related

How to use React useContext with leaflet routing machine and react leaflet?

I'm trying to use a useContext hook inside a react-leaflet controlComponent but I have an error when my context fires the update function.
I use a react-leaflet controlComponent because of leaflet routing machine. I think the code + the error are better than word:
MainBoard.tsx
export const CartographyContext: React.Context<CartographyContextType> = React.createContext<CartographyContextType>({ positions: [] });
...
const routeSummaryValueContext = React.useMemo(
() => ({ routeSummary, setRouteSummary }),
[routeSummary]
);
const elevationProfileValueContext = React.useMemo(
() => ({ elevationProfile, setElevationProfile }),
[elevationProfile]
);
........
<CartographyContext.Provider value={{ positions, elevationProfileValueContext, routeSummaryValueContext, positionsValueContext, addPosition, changePosition }}>
.........
<RoutingMachine
orsOptions={{
....
}} />
..........
</CartographyContext.Provider>
RoutingMachine.tsx:
const CreateRoutineMachineLayer = (props: any) => {
const geoService = new GeoLocalisationService();
const cartographyContext: CartographyContextType = React.useContext<CartographyContextType>(CartographyContext);
const [routes, setRoutes] = React.useState<any[]>();
React.useEffect(() => {
if (routes) {
//The line which cause the error
cartographyContext.elevationProfileValueContext.setElevationProfile(geoService.getElevationProfile(decodePolyline(routes[0].geometry, true)));
const summary: RouteSummary = {
ascent: routes[0].routeSummary.ascent,
descent: routes[0].routeSummary.descent,
distance: routes[0].routeSummary.distance,
estimatedDuration: routes[0].routeSummary.duration
}
cartographyContext.routeSummaryValueContext.setRouteSummary(summary);
}
}, [routes]);
const { orsOptions } = props;
const instance = L.Routing.control({
router: new OpenRouteRouter(orsOptions),
lineOptions: {
styles: [{ color: "#3933ff", weight: 4 }],
extendToWaypoints: true,
missingRouteTolerance: 0
},
routeWhileDragging: true,
autoRoute: true,
geocoder: new geocoder.Geocoder(),
}).on('routesfound', (e) => {
setRoutes(e.routes);
});
useMapEvents({
click: (e: L.LeafletMouseEvent) => {
if (instance.getWaypoints().length === 2 && instance.getWaypoints()[0].latLng == null) {
instance.spliceWaypoints(0, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else if (instance.getWaypoints().length === 2 && instance.getWaypoints()[1].latLng == null) {
instance.spliceWaypoints(1, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else {
instance.spliceWaypoints(instance.getWaypoints().length, 0, new L.Routing.Waypoint(e.latlng, null, {}));
}
}
});
return instance;
};
const RoutingMachine = createControlComponent(CreateRoutineMachineLayer);
error :
g: React has detected a change in the order of Hooks called by ForwardRef(LeafComponent). This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
Previous render Next render
------------------------------------------------------
1. useContext useContext
2. useRef useRef
3. useContext useRef
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
..............
Uncaught Error: Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
I clearly doing something wrong here but I haven't found yet.
Thank you
Kind regards
Ok I found the good implementation :
const RoutingMachine: React.FC<RoutingMachineProps> = (props) => {
//const RoutineMachine = (props: any) => {
const geoService = new GeoLocalisationService();
const cartographyContext: CartographyContextType = React.useContext<CartographyContextType>(CartographyContext);
const [instance, setInstance] = React.useState<any>();
const [alreadyDisplayed, setAlreadyDisplayed] = React.useState(false);
const { orsOptions } = props;
const map = useMap();
//const instance = L.Routing.control({
React.useEffect(() => {
const instance = L.Routing.control({
router: new OpenRouteRouter(orsOptions),
lineOptions: {
styles: [{ color: "#3933ff", weight: 4 }],
extendToWaypoints: true,
missingRouteTolerance: 0
},
routeWhileDragging: true,
autoRoute: true,
geocoder: (L.Control as any).Geocoder.google({
apiKey: GOOGLE.googleMapApiKey,
}),
}).on('routesfound', (e) => {
const routes = e.routes;
cartographyContext.setElevationProfile(geoService.getElevationProfile(decodePolyline(routes[0].geometry, true)));
const summary: RouteSummary = {
ascent: routes[0].routeSummary.ascent,
descent: routes[0].routeSummary.descent,
distance: routes[0].routeSummary.distance,
estimatedDuration: routes[0].routeSummary.duration
}
cartographyContext.setRouteSummary(summary);
})
setInstance(instance);
instance.addTo(map);
}, []);
useMapEvents({
click: (e: L.LeafletMouseEvent) => {
if (instance) {
if (instance.getWaypoints().length === 2 && instance.getWaypoints()[0].latLng == null) {
instance.spliceWaypoints(0, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else if (instance.getWaypoints().length === 2 && instance.getWaypoints()[1].latLng == null) {
instance.spliceWaypoints(1, 1, new L.Routing.Waypoint(e.latlng, null, {}));
} else {
instance.spliceWaypoints(instance.getWaypoints().length, 0, new L.Routing.Waypoint(e.latlng, null, {}));
}
}
}
});
return null;
};
export default RoutingMachine;

Pagination Apollo Client 3 - Cache merges but does not render new results when paginating

I'd love to implement nested pagination within my application. I have been reading the docs and looking at several other examples but I just can't get this to work - any help is appreciated! Thanks!
React component:
I am clicking the button to run the fetchMore function provided by the useQuery hook (apollo). The network request is going through and the new products are merged into the cache... but no new products render on the page.
export const FilterableKit = () => {
const selectedKitId = useReactiveVar(selectedKitIdVar);
const [
getKitProducts,
{ data: getKitProductsData, loading: getKitProductsLoading, fetchMore },
] = useGetKitProductsLazyQuery();
useEffect(() => {
if (selectedKitId) {
getKitProducts({
variables: {
getKitsInput: {
_id: {
string: selectedKitId,
filterBy: "OBJECTID" as StringFilterByEnum,
},
},
getProductsInput: {
config: {
pagination: {
reverse: true,
limit: 3,
},
},
},
},
});
}
}, [getKitProducts, selectedKitId]);
const kitProducts = getKitProductsData?.getKits.data?.find(
(kit) => kit?._id === selectedKitId
)?.products.data;
const handleLoadMore = () => {
if (kitProducts && kitProducts?.length > 0) {
const remaining =
getKitProductsData?.getKits.data[0]?.products.stats?.remaining;
if (remaining && remaining > 0) {
const cursor =
kitProducts[kitProducts.length - 1] &&
kitProducts[kitProducts.length - 1]?.createdAt;
fetchMore({
variables: {
getProductsInput: {
config: {
pagination: {
reverse: true,
createdAt: cursor,
},
},
},
},
});
}
}
};
return (
<CContainer>
<KitItemCards products={kitProducts} loading={getKitProductsLoading} />
<CContainer className="d-flex justify-content-center my-3">
<CButton color="primary" className="w-100" onClick={handleLoadMore}>
Load More
</CButton>
</CContainer>
</CContainer>
);
};
Type Policies: I define the "Kit" typePolicy to merge products into the correct field.
export const cache: InMemoryCache = new InMemoryCache({
typePolicies: {
Kit: {
fields: {
products: {
keyArgs: false,
merge(existing = [] as Product[], incoming: GetProductsResponse) {
if (!incoming) return existing;
if (!existing) return incoming;
const { data: products, ...rest } = incoming;
let result: any = rest;
result = [...existing, ...(products ?? [])];
return result;
},
},
},
},
});
Thanks for any pointers in the right direction! Let me know if there is something else you'd like to see.

RxJs with Jest case fails but the `tap` output shows correctly

Following test case, will return the data correctly but JEST show as failed. The test is written using TestScheduler
Jest Result
expect(received).toEqual(expected) // deep equality
- Expected
+ Received
- Array [
- Object {
- "frame": 3,
- "notification": Notification {
- "error": undefined,
- "hasValue": true,
- "kind": "N",
- "value": Object {
- "type": "INITIALIZED",
- },
- },
- },
- ]
+ Array []
Code
import { ofType } from 'redux-observable';
import { mergeMap, map, tap } from 'rxjs/operators';
import { of, from } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('routechange epic', () => {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
it('check apollo', () => {
const dependencies = {
apolloClient: {
mutate: ({ mutation, variables }: { mutation: any, variables: any }) =>
Promise.resolve({
data: { param: 'testA' }
})
},
};
const initializeOrg = (action$, state$, { apolloClient }) =>
action$
.pipe(
ofType('START'),
tap(act => console.log('---AAA', act)),
mergeMap(action =>
from(
apolloClient.mutate({
mutation: `something`,
variables: {
orgId: (action as any).params || ''
}
})
)
.pipe(
tap(x => console.log('----x', x)),
map(response => ({
type: 'INITIALIZED',
response,
}))
)
)
);
testScheduler.run(({ hot, cold, expectObservable }) => {
const action$ = hot('-a', {
a: { type: 'START', params: 'SomethingA' }
});
const state$ = null;
const output$ = initializeOrg(action$, state$, dependencies);
expectObservable(output$).toBe('---a', {
a: {
type: 'INITIALIZED'
}
})
});
});
});
We cannot use Promise.resolve as someone commented it.
It worked now with cold observable like below.
import { ofType } from 'redux-observable';
import { mergeMap, map, tap, toArray, take } from 'rxjs/operators';
import { of, from } from 'rxjs';
import { TestScheduler } from 'rxjs/testing';
describe('routechange epic', () => {
it('check apollo', async () => {
const testScheduler = new TestScheduler((actual, expected) => {
expect(actual).toEqual(expected);
});
const initializeOrg = (action$, state$, { apolloClient }) =>
action$
.pipe(
ofType('START'),
tap(act => console.log('---AAA', act)),
mergeMap(action =>
from(
apolloClient.mutate({
mutation: `something`,
variables: {
orgId: (action as any).params || ''
}
})
)
.pipe(
tap(x => console.log('----x', x)),
map(response => ({
type: 'INITIALIZED',
response,
}))
)
)
);
testScheduler.run(({ hot, cold, expectObservable }) => {
const action$ = hot('-a', {
a: { type: 'START', params: 'SomethingA' }
});
const state$ = null;
const dependencies = {
apolloClient: {
mutate: ({ mutation, variables }: { mutation: any, variables: any }) =>
cold('--a|', {
a: { data: { param: 'testA' } }
})
},
};
const output$ = initializeOrg(action$, state$, dependencies);
expectObservable(output$).toBe('---a', {
a: {
type: 'INITIALIZED',
response: {
data: { param: 'testA' }
}
}
})
});
});
});

Vue3 composition API refactor computed favoritesRecipes

I am new to composition API with vue3. I have created that computed property and I would like to have that computed variable in a different file, I'm not sure if I should create a new component or I could achieve it from a js file.
Here is the component working (I did it with setup()):
export default {
name: "Recipes",
setup() {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoritesRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
...
});
...
}
return {
...toRefs(state),
favoriteRecipes
}
// end of setup
}
You can split it into two files
state.js
export const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
export const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
})
and recipes.vue
import { state, favoriteRecipes } from "state.js";
export default {
name: "Recipes",
setup() {
return {
...toRefs(state),
favoriteRecipes,
};
},
};
But this will make the state persistent, so if you have multiple components, they will all have the same favoriteRecipes and state values.
If you want them to be unique for each component...
state.js
export const withState = () => {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter((item) => {
return item.favorite;
});
}
return tempFavs;
});
return { state, favoriteRecipes };
};
and recipes.vue
import { withState } from "state.js";
export default {
name: "Recipes",
setup() {
const {state, favoriteRecipes} = withState()
return {
...toRefs(state),
favoriteRecipes,
};
},
};

infinite scrolling using AgGridReact

I'm trying to achieve infinite scrolling using ag grid react component, but it doesn't seems to be working.
here is my implementation :
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid/dist/styles/ag-grid.css';
import 'ag-grid/dist/styles/ag-theme-balham.css';
class TasksGridContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
gridOptions: {
//virtual row model
rowModelType: 'infinite',
paginationPageSize: 100,
cacheOverflowSize: 2,
maxConcurrentDatasourceRequests: 2,
infiniteInitialRowCount: 1,
maxBlocksInCache: 2,
components: {
loadingRenderer: function(params) {
console.log('loadingCellRenderer', params);
if (params.value !== undefined) {
return params.value;
} else {
return '<img src="https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/images/loading.gif">';
}
}
},
defaultColDef: {
editable: false,
enableRowGroup: true,
enablePivot: true,
enableValue: true
}
}
};
}
componentDidMount() {
this.props.actions.getAssignedTasks();
this.props.actions.getTeamTasks();
}
componentWillReceiveProps(newProps) {
if (this.props.taskView.taskGrid.listOfTasks.length > 0) {
this.setState({
loading: false ,
gridOptions: {
datasource: this.props.taskView.taskGrid.listOfTasks
}
});
}
}
render() {
return (
<div id="tasks-grid-container">
<div style={Style.agGrid} id="myGrid" className="ag-theme-balham">
<AgGridReact
columnDefs={this.props.taskView.taskGrid.myTaskColumns}
rowData={this.props.taskView.taskGrid.listOfTasks}
gridOptions={this.state.gridOptions}>
</AgGridReact>
</div>
</div>
);
}
}
TasksGridContainer.propTypes = {
listOfTasks: PropTypes.array,
actions: PropTypes.object
};
const mapStateToProps = ({ taskView }) => {
return {
taskView: {
taskGrid: {
listOfTasks: taskView.taskGrid.listOfTasks,
myTaskColumns: taskView.taskGrid.myTaskColumns,
teamTaskColumns: taskView.taskGrid.teamTaskColumns
}
}
}
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(taskGridActions, dispatch)
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(TasksGridContainer);
columnDefs are being set once props.taskView.taskGrid.myTaskColumns is available.
a sample columndef:
[
{
cellRenderer: "loadingRenderer", checkboxSelection: true, field: "action", headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, headerName: "Action"
},
{
"activity"headerName: "Activity Name"
}
]
Although grid is loading fine, but when i scroll it should call "loadingRenderer" component. But,I'm not able to see any loading gif when i scroll.
Am i doing something wrong in implementation?
Actual issue was not calling the the props properly and was not having onGridReady function to use gridAPi.
I modified the code and it starts working:
<AgGridReact
components={this.state.components}
enableColResize={true}
rowBuffer={this.state.rowBuffer}
debug={true}
rowSelection={this.state.rowSelection}
rowDeselection={true}
rowModelType={this.state.rowModelType}
paginationPageSize={this.state.paginationPageSize}
cacheOverflowSize={this.state.cacheOverflowSize}
maxConcurrentDatasourceRequests={this.state.maxConcurrentDatasourceRequests}
infiniteInitialRowCount={this.state.infiniteInitialRowCount}
maxBlocksInCache={this.state.maxBlocksInCache}
columnDefs={this.props.columns}
rowData={this.props.rowData}
onGridReady={this.onGridReady}
>
</AgGridReact>
state :
this.state = {
components: {
loadingRenderer: function(params) {
if (params.value !== undefined) {
return params.data.action;
} else {
return '<img src="https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/images/loading.gif">';
}
}
},
rowBuffer: 0,
rowSelection: "multiple",
rowModelType: "infinite",
paginationPageSize: 100,
cacheOverflowSize: 2,
maxConcurrentDatasourceRequests: 2,
infiniteInitialRowCount: 1,
maxBlocksInCache: 2
};
onGridReady function :
onGridReady = (params, data = []) => {
this.gridApi = params;
this.gridColumnApi = params.columnApi;
this.updateData(params,data);
}

Resources