Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various Fixes + Updates #11

Merged
merged 13 commits into from
Dec 4, 2024
44 changes: 43 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
*.pem

# Editor directories and files
.vscode/*
Expand All @@ -28,5 +29,46 @@ dist-ssr

*storybook.log

# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/**/.next/
/**/out/

# production
/**/build

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

.olddocs
.examples

.next/
_pagefind/
_pagefind/

5 changes: 4 additions & 1 deletion packages/lib/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@playcanvas/react",
"description": "A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.",
"version": "0.1.20",
"version": "0.1.22",
"type": "module",
"sideEffects": false,
"main": "dist/index.js",
Expand Down Expand Up @@ -85,5 +85,8 @@
},
"devDependencies": {
"typescript": "^5.6.3"
},
"dependencies": {
"sync-ammo": "^0.1.2"
}
}
14 changes: 13 additions & 1 deletion packages/lib/src/Application.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { FC, PropsWithChildren, useLayoutEffect, useRef, useState } from 'react';
import * as Ammo from 'sync-ammo';
import {
FILLMODE_NONE,
FILLMODE_FILL_WINDOW,
Expand Down Expand Up @@ -49,6 +50,8 @@ interface ApplicationProps extends PropsWithChildren<unknown> {
maxDeltaTime?: number
/** Scales the global time delta. */
timeScale?: number,
/** Whether to use the PlayCanvas Physics system. */
usePhysics?: boolean,
/** Graphics Settings */
graphicsDeviceOptions?: GraphicsOptions
}
Expand Down Expand Up @@ -95,6 +98,7 @@ export const ApplicationWithoutCanvas: FC<ApplicationWithoutCanvasProps> = ({
resolutionMode = RESOLUTION_AUTO,
maxDeltaTime = 0.1,
timeScale = 1,
usePhysics = false,
...otherProps
}) => {

Expand All @@ -115,12 +119,15 @@ export const ApplicationWithoutCanvas: FC<ApplicationWithoutCanvasProps> = ({
const [app, setApp] = useState<PlayCanvasApplication | null>(null);
const appRef = useRef<PlayCanvasApplication | null>(null);


usePicker(appRef.current, canvasRef.current);

useLayoutEffect(() => {
const canvas = canvasRef.current;
if (canvas && !appRef.current) {

// @ts-expect-error The PC Physics system expects a global Ammo instance
if (usePhysics) globalThis.Ammo = Ammo.default

const localApp = new PlayCanvasApplication(canvas, {
mouse: new Mouse(canvas),
touch: new TouchDevice(canvas),
Expand All @@ -137,8 +144,13 @@ export const ApplicationWithoutCanvas: FC<ApplicationWithoutCanvasProps> = ({

return () => {
if (!appRef.current) return;

appRef.current.destroy();
appRef.current = null;

// @ts-expect-error Clean up the global Ammo instance
if (usePhysics && globalThis.Ammo) delete globalThis.Ammo;

setApp(null);
};
}, [canvasRef, fillMode, resolutionMode, ...Object.values(graphicsDeviceOptions)]);
Expand Down
18 changes: 14 additions & 4 deletions packages/lib/src/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ import { Entity } from ".";

interface ContainerProps {
asset: Asset;
children?: React.ReactNode;
[key: string]: unknown;
}

export const Container: FC<ContainerProps> = ({ asset, ...props }) => {
/**
* Renders a PlayCanvas asset as a React component.
* @param {Asset} asset - The PlayCanvas asset to render.
* @param {React.ReactNode} children - The children to render inside the container.
* @param {Object} [props] - Additional properties to pass to the container.
* @returns {React.ReactNode} - The rendered container.
*/
export const Container: FC<ContainerProps> = ({ asset, children, ...props }) => {

const entityRef = useRef<PcEntity | null>(null);
const assetEntityRef = useRef<PcEntity | null>(null);
Expand All @@ -32,8 +40,10 @@ export const Container: FC<ContainerProps> = ({ asset, ...props }) => {
assetEntityRef.current = null;

};
}, [app, parent, asset, asset.resource]);
}, [app, parent, asset, asset?.resource]);


return <Entity ref={entityRef} {...props}/>;
};
return <Entity ref={entityRef} {...props}>
{ children }
</Entity>;
};
14 changes: 8 additions & 6 deletions packages/lib/src/Entity.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { Entity as PcEntity } from 'playcanvas';
import { ReactNode, forwardRef, useImperativeHandle, useLayoutEffect, useMemo } from 'react';
import { useParent, ParentContext, useApp } from './hooks';
Expand All @@ -8,9 +10,9 @@ type MouseEventCallback = (event: SyntheticMouseEvent) => void;

interface EntityProps {
name?: string;
position?: [number, number, number];
scale?: [number, number, number];
rotation?: [number, number, number];
position?: number[];
scale?: number[];
rotation?: number[];
onPointerUp?: PointerEventCallback;
onPointerDown?: PointerEventCallback;
onPointerOver?: PointerEventCallback;
Expand Down Expand Up @@ -75,9 +77,9 @@ export const Entity = forwardRef<PcEntity, EntityProps> (function Entity(

useLayoutEffect(() => {
entity.name = name;
entity.setLocalPosition(...position);
entity.setLocalScale(...scale);
entity.setLocalEulerAngles(...rotation);
entity.setLocalPosition(...position as [number, number, number]);
entity.setLocalScale(...scale as [number, number, number]);
entity.setLocalEulerAngles(...rotation as [number, number, number]);
}, [entity, name, position, scale, rotation]);

return (<>
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/src/components/Align.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { BoundingBox, Entity as PcEntity, RenderComponent, Vec3, Application, Mat4 } from "playcanvas";
import { Children, useLayoutEffect, useRef, useState } from "react";
import { Entity } from "../Entity";
Expand Down
45 changes: 45 additions & 0 deletions packages/lib/src/components/Anim.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client"

import { FC, useLayoutEffect } from "react";
import { useComponent, useParent } from "../hooks";
import { AnimComponent, Asset, Entity } from "playcanvas";

interface AnimProps {
[key: string]: unknown;
asset : Asset
}

/**
* The Camera component is used to define the position and properties of a camera entity.
*/
export const Anim: FC<AnimProps> = ({ asset, ...props }) => {

// Create the anim component
useComponent("anim", props);

// Get the associated Entity
const entity : Entity = useParent();

// Assign Asset animations to Anim component
useLayoutEffect(() => {

const anim : AnimComponent | undefined = entity.anim

if( asset?.resource ) console.log('anim', asset.resource);

// Early out if component instantiation fails, or the asset contains no animations
if(!anim || !asset?.resource?.animations || asset.resource.animations.length === 0) return;

console.log('animations', asset.resource.animations);

// Filter out non animations, and assign animations to component
asset.resource.animations
.filter((animation: Asset) => animation.type ==='animation')
.forEach((animation: Asset) => {
anim.assignAnimation('animation', animation.resource)
});

}, [asset?.id, entity.getGuid()])

return null;
}
4 changes: 3 additions & 1 deletion packages/lib/src/components/Camera.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"use client"

import { FC } from "react";
import { useComponent } from "../hooks";
import { Color } from "playcanvas";
import { useColors } from "../utils/color";

interface CameraProps {
[key: string]: unknown;
clearColor: Color | string
clearColor?: Color | string
}

/**
Expand Down
15 changes: 15 additions & 0 deletions packages/lib/src/components/Collision.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"use client"

import { FC } from "react";
import { useComponent } from "../hooks";

type CollisionProps = {
[key: string]: unknown;
}

export const Collision: FC<CollisionProps> = (props) => {

useComponent("collision", props);
return null

}
2 changes: 2 additions & 0 deletions packages/lib/src/components/EnvAtlas.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { FC, useLayoutEffect } from "react";
import { useApp } from "../hooks";
import { Application, Asset } from "playcanvas";
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/src/components/GSplat.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { FC, useLayoutEffect, useRef } from "react";
import { useParent } from "../hooks";
import { Asset, Entity } from "playcanvas";
Expand Down
7 changes: 5 additions & 2 deletions packages/lib/src/components/Light.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { FC } from "react";
import { useComponent } from "../hooks";
import { useColors } from "../utils/color";

type LightProps = {
type: string;
type: "directional" | "omni" | "spot";
}

export const Light: FC<LightProps> = (props) => {

useComponent("light", props);
const colorProps = useColors(props, ['color'])

useComponent("light", { ...props, ...colorProps });
return null

}
26 changes: 24 additions & 2 deletions packages/lib/src/components/Render.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,36 @@
"use client"

import { FC } from "react";
import { useComponent } from "../hooks";
import { Container } from "../Container";
import { Asset } from "playcanvas";

interface RenderProps {
type: string;
asset?: Asset;
children?: React.ReactNode;
[key: string]: unknown;
}

export const Render: FC<RenderProps> = (props) => {

const RenderComponent: FC<RenderProps> = (props) => {
useComponent("render", props);
return null;
}

/**
* Create a render component on an entity. If the asset is a container,
* it will be rendered as a container. Otherwise, it will be rendered as a
* render component.
*/
export const Render: FC<RenderProps> = (props) => {

// Render a container if the asset is a container
if (props.asset?.type === 'container') {
return <Container asset={props.asset as Asset} >
{ props.children }
</Container>
}

// Otherwise, render the component
return <RenderComponent {...props} />;
}
23 changes: 23 additions & 0 deletions packages/lib/src/components/RigidBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { FC } from "react";
import { useComponent, useParent } from "../hooks";

type RigidBodyProps = {
[key: string]: unknown;
}

export const RigidBody: FC<RigidBodyProps> = (props) => {

const entity = useParent();

// @ts-ignore Ammo is defined in the global scope in the browser
if(!globalThis.Ammo && process.env.NODE_ENV !== 'production' ) {
throw new Error('The `<RigidBody>` component requires `usePhysics` to be set on the Application. `<Application usePhysics/>` ')
}

// If no type is defined, infer if possible from a render component
const type = entity.render && props.type === undefined ? entity.render.type : props.type;

useComponent("rigidbody", { ...props, type } );
return null

}
8 changes: 6 additions & 2 deletions packages/lib/src/components/Script.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Script as PcScript } from "playcanvas";
// import { Script as PcScript } from "playcanvas";
import { useScript } from "../hooks"
import { FC, memo, useMemo } from "react";
import { shallowEquals } from "../utils/shallow-equals";

export interface ScriptConstructor {
__name: string;
}

interface ScriptProps {
script: typeof PcScript;
script: ScriptConstructor
[key: string]: unknown;
}

Expand Down
Loading