physics go brrrrrr

This commit is contained in:
Orion Reed 2024-03-25 00:22:13 -07:00
parent d2f3171069
commit 366cba1ec1
15 changed files with 216 additions and 225 deletions

View File

@ -15,8 +15,7 @@
"@dimforge/rapier2d": "latest",
"@tldraw/tldraw": "2.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-helmet-async": "^2.0.4"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@biomejs/biome": "1.4.1",
@ -25,7 +24,6 @@
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"concurrently": "^8.2.0",
"gh-pages": "^6.1.1",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-top-level-await": "^1.3.1",

View File

@ -1,35 +1,16 @@
import { createShapeId, Tldraw, TLGeoShape, TLShape, TLUiComponents } from "@tldraw/tldraw";
import { createShapeId, TLUiComponents } from "@tldraw/tldraw";
import "@tldraw/tldraw/tldraw.css";
import "./css/style.css"
import { SimControls } from "./physics/ui/PhysicsControls";
import { uiOverrides } from "./physics/ui/overrides";
import { Helmet, HelmetProvider } from "react-helmet-async";
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom/client";
import { HTMLShapeUtil, HTMLShape } from "./HTMLShapeUtil";
import { HTMLShape } from "./ts/shapes/HTMLShapeUtil";
import { Default } from "./ts/components/Default";
import { Canvas } from "./ts/components/Canvas";
import { Toggle } from "./ts/components/Toggle";
import { gatherElementsInfo } from "./utils";
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
const components: TLUiComponents = {
HelpMenu: null,
StylePanel: null,
PageMenu: null,
NavigationPanel: null,
DebugMenu: null,
ContextMenu: null,
ActionsMenu: null,
QuickActions: null,
MainMenu: null,
MenuPanel: null,
// ZoomMenu: null,
// Minimap: null,
// Toolbar: null,
// KeyboardShortcutsDialog: null,
// HelperButtons: null,
// SharePanel: null,
// TopPanel: null,
}
function App() {
const [isPhysicsEnabled, setIsPhysicsEnabled] = useState(false);
const [elementsInfo, setElementsInfo] = useState<any[]>([]);
@ -57,31 +38,7 @@ function App() {
};
}, [isPhysicsEnabled]);
// Function to gather elements info asynchronously
async function gatherElementsInfo() {
const rootElement = document.getElementsByTagName('main')[0];
const info: any[] = [];
if (rootElement) {
for (const child of rootElement.children) {
if (['BUTTON'].includes(child.tagName)) continue
const rect = child.getBoundingClientRect();
let w = rect.width
// if (!['P', 'UL'].includes(child.tagName)) {
// w = measureElementTextWidth(child);
// }
// console.log(w)
info.push({
tagName: child.tagName,
x: rect.left,
y: rect.top,
w: w,
h: rect.height,
html: child.outerHTML
});
};
}
return info;
}
const shapes: HTMLShape[] = elementsInfo.map((element) => ({
id: createShapeId(),
@ -97,157 +54,29 @@ function App() {
shapes.push({
id: createShapeId(),
type: 'html',
type: 'geo',
x: 0,
y: window.innerHeight,
props: {
w: window.innerWidth,
h: 20,
html: "FOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
h: 50,
color: 'grey',
fill: 'solid'
},
meta: {
fixed: true
}
})
return (
<React.StrictMode>
<HelmetProvider>
<Toggle />
<div style={{ zIndex: 999999 }} className={`default-component ${fadeClass}`}>
{<Default />}
</div>
{isPhysicsEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}
</HelmetProvider>
<Toggle />
<div style={{ zIndex: 999999 }} className={`default-component ${fadeClass}`}>
{<Default />}
</div>
{isPhysicsEnabled && elementsInfo.length > 0 ? <Canvas shapes={shapes} /> : null}
</React.StrictMode>
);
};
function Default() {
return (
<main>
{/* <Helmet>
<link rel="stylesheet" href="src/css/default.css" />
</Helmet> */}
<header>
Orion Reed
</header>
<h1>Hello! 👋</h1>
<p>
My research investigates the intersection of computing, human-system
interfaces, and emancipatory politics. I am interested in the
potential of computing as a medium for thought, as a tool for
collective action, and as a means of emancipation.
</p>
<p>
My current focus is basic research into the nature of digital
organisation, developing theoretical toolkits to improve shared
infrastructure, and applying this research to the design of new
systems and protocols which support the self-organisation of knowledge
and computational artifacts.
</p>
<h1>My work</h1>
<p>
Alongside my independent work I am a researcher at <a href="https://block.science/">Block Science</a> building
<i>knowledge organisation infrastructure</i> and at <a href="https://economicspace.agency/">ECSA</a> working on
<i>computational media</i>. I am also part of the nascent <a href="https://libcomp.org/">Liberatory Computing</a>
collective and a co-organiser of the <a href="https://canvasprotocol.org/">OCWG</a>.
</p>
<h1>Get in touch</h1>
<p>
I am on Twitter as <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a> and on
Mastodon as <a href="https://hci.social/@orion">@orion@hci.social</a>. The best way to reach me is
through Twitter or my email, <a href="mailto:me@orionreed.com">me@orionreed.com</a>
</p>
<span className="dinkus">***</span>
<h1>Talks</h1>
<ul>
<li><a
href="objects/causal-islands-integration-domain.pdf">Spatial
Canvases: Towards an Integration Domain for HCI @ Causal Islands LA</a></li>
<li><a
href="https://www.youtube.com/watch?v=-q-kk-NMFbA">Knowledge Organisation Infrastructure Demo @ NPC
Denver</a></li>
</ul>
<h1>Writing</h1>
<ul>
<li><a
href="https://blog.block.science/objects-as-reference-toward-robust-first-principles-of-digital-organization/">Objects
as Reference: Toward Robust First Principles of Digital Organization</a></li>
</ul>
</main>
);
}
function Canvas({ shapes }: { shapes: TLShape[] }) {
return (
<div className="tldraw__editor">
{/* <Helmet>
<link rel="stylesheet" href="src/css/tldraw.css" />
</Helmet> */}
<Tldraw
overrides={uiOverrides}
components={components}
shapeUtils={[HTMLShapeUtil]}
>
<SimControls shapes={shapes} />
</Tldraw>
</div>
);
}
function Toggle() {
return (
<>
{/* <Helmet>
<link rel="stylesheet" href="src/css/toggle.css" />
</Helmet> */}
<button id="toggle-physics" onClick={() => window.dispatchEvent(new CustomEvent('togglePhysicsEvent'))}>
<img src="src/assets/gravity.svg" alt="Toggle Physics" />
</button>
</>
);
}
function Contact() {
return (
<div>
<h1>Contact</h1>
<p>Twitter: <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a></p>
<p>Mastodon: <a href="https://hci.social/@orion">orion@hci.social</a></p>
<p>Email: <a href="mailto:me@orionreed.com">me@orionreed.com</a></p>
<p>GitHub: <a href="https://github.com/orionreed">OrionReed</a></p>
</div>
)
}
function measureElementTextWidth(element: Element) {
// Create a temporary span element
const tempElement = document.createElement('span');
// Get the text content from the passed element
tempElement.textContent = element.textContent || element.innerText;
// Get the computed style of the passed element
const computedStyle = window.getComputedStyle(element);
// Apply relevant styles to the temporary element
tempElement.style.font = computedStyle.font;
tempElement.style.fontWeight = computedStyle.fontWeight;
tempElement.style.fontSize = computedStyle.fontSize;
tempElement.style.fontFamily = computedStyle.fontFamily;
tempElement.style.letterSpacing = computedStyle.letterSpacing;
// Ensure the temporary element is not visible in the viewport
tempElement.style.position = 'absolute';
tempElement.style.visibility = 'hidden';
tempElement.style.whiteSpace = 'nowrap'; // Prevent text from wrapping
// Append to the body to make measurements possible
document.body.appendChild(tempElement);
// Measure the width
const width = tempElement.getBoundingClientRect().width;
// Remove the temporary element from the document
document.body.removeChild(tempElement);
// Return the measured width
return width === 0 ? 10 : width;
}

View File

@ -134,7 +134,7 @@ ul {
.fade-out {
opacity: 0 !important;
transition: opacity 1s ease-in-out;
transition: opacity 0.2s ease-in-out;
/* visibility: hidden; */
/* display: none; */
}

View File

@ -1,22 +0,0 @@
import {
TLUiEventSource,
TLUiOverrides,
TLUiTranslationKey,
} from "@tldraw/tldraw";
// In order to see select our custom shape tool, we need to add it to the ui.
export const uiOverrides: TLUiOverrides = {
actions(_editor, actions) {
actions['toggle-physics'] = {
id: 'toggle-physics',
label: 'Toggle Physics' as TLUiTranslationKey,
readonlyOk: true,
kbd: 'p',
onSelect(_source: TLUiEventSource) {
const event = new CustomEvent('togglePhysicsEvent');
window.dispatchEvent(event);
},
}
return actions
},
}

View File

@ -0,0 +1,37 @@
import { Tldraw, TLShape, TLUiComponents } from "@tldraw/tldraw";
import { SimController } from "../physics/PhysicsControls";
import { HTMLShapeUtil } from "../shapes/HTMLShapeUtil";
const components: TLUiComponents = {
HelpMenu: null,
StylePanel: null,
PageMenu: null,
NavigationPanel: null,
DebugMenu: null,
ContextMenu: null,
ActionsMenu: null,
QuickActions: null,
MainMenu: null,
MenuPanel: null,
// ZoomMenu: null,
// Minimap: null,
// Toolbar: null,
// KeyboardShortcutsDialog: null,
// HelperButtons: null,
// SharePanel: null,
// TopPanel: null,
}
export function Canvas({ shapes }: { shapes: TLShape[]; }) {
return (
<div className="tldraw__editor">
<Tldraw
components={components}
shapeUtils={[HTMLShapeUtil]}
>
<SimController shapes={shapes} />
</Tldraw>
</div>
);
}

View File

@ -0,0 +1,14 @@
import React from "react";
function Contact() {
return (
<div>
<h1>Contact</h1>
<p>Twitter: <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a></p>
<p>Mastodon: <a href="https://hci.social/@orion">orion@hci.social</a></p>
<p>Email: <a href="mailto:me@orionreed.com">me@orionreed.com</a></p>
<p>GitHub: <a href="https://github.com/orionreed">OrionReed</a></p>
</div>
);
}

View File

@ -0,0 +1,59 @@
import React from "react";
export function Default() {
return (
<main>
<header>
Orion Reed
</header>
<h1>Hello! 👋</h1>
<p>
My research investigates the intersection of computing, human-system
interfaces, and emancipatory politics. I am interested in the
potential of computing as a medium for thought, as a tool for
collective action, and as a means of emancipation.
</p>
<p>
My current focus is basic research into the nature of digital
organisation, developing theoretical toolkits to improve shared
infrastructure, and applying this research to the design of new
systems and protocols which support the self-organisation of knowledge
and computational artifacts.
</p>
<h1>My work</h1>
<p>
Alongside my independent work I am a researcher at <a href="https://block.science/">Block Science</a> building
<i>knowledge organisation infrastructure</i> and at <a href="https://economicspace.agency/">ECSA</a> working on
<i>computational media</i>. I am also part of the nascent <a href="https://libcomp.org/">Liberatory Computing</a>
collective and a co-organiser of the <a href="https://canvasprotocol.org/">OCWG</a>.
</p>
<h1>Get in touch</h1>
<p>
I am on Twitter as <a href="https://twitter.com/OrionReedOne">@OrionReedOne</a> and on
Mastodon as <a href="https://hci.social/@orion">@orion@hci.social</a>. The best way to reach me is
through Twitter or my email, <a href="mailto:me@orionreed.com">me@orionreed.com</a>
</p>
<span className="dinkus">***</span>
<h1>Talks</h1>
<ul>
<li><a
href="objects/causal-islands-integration-domain.pdf">Spatial
Canvases: Towards an Integration Domain for HCI @ Causal Islands LA</a></li>
<li><a
href="https://www.youtube.com/watch?v=-q-kk-NMFbA">Knowledge Organisation Infrastructure Demo @ NPC
Denver</a></li>
</ul>
<h1>Writing</h1>
<ul>
<li><a
href="https://blog.block.science/objects-as-reference-toward-robust-first-principles-of-digital-organization/">Objects
as Reference: Toward Robust First Principles of Digital Organization</a></li>
</ul>
</main>
);
}

View File

@ -0,0 +1,11 @@
import React from "react";
export function Toggle() {
return (
<>
<button id="toggle-physics" onClick={() => window.dispatchEvent(new CustomEvent('togglePhysicsEvent'))}>
<img src="src/assets/gravity.svg" alt="Toggle Physics" />
</button>
</>
);
}

View File

@ -1,8 +1,8 @@
import { TLUnknownShape, useEditor } from "@tldraw/tldraw";
import { useEffect } from "react";
import { usePhysicsSimulation } from "../simulation";
import { usePhysicsSimulation } from "./simulation";
export const SimControls = ({ shapes }: { shapes: TLUnknownShape[] }) => {
export const SimController = ({ shapes }: { shapes: TLUnknownShape[] }) => {
const editor = useEditor();
useEffect(() => {

View File

@ -9,6 +9,8 @@ type BodyWithShapeData = RAPIER.RigidBody & {
};
type RigidbodyLookup = { [key: TLShapeId]: RAPIER.RigidBody };
const START_DELAY = 1500;
export class PhysicsWorld {
private editor: Editor;
private world: RAPIER.World;
@ -28,6 +30,7 @@ export class PhysicsWorld {
public start() {
this.world = new RAPIER.World(GRAVITY);
// setTimeout(() => {
this.addShapes(this.editor.getCurrentPageShapes());
const simLoop = () => {
@ -38,6 +41,7 @@ export class PhysicsWorld {
};
simLoop();
return () => cancelAnimationFrame(this.animFrame);
// }, START_DELAY);
};
public stop() {
@ -55,6 +59,7 @@ export class PhysicsWorld {
}
switch (shape.type) {
case "html":
case "geo":
case "image":
case "video":
@ -72,12 +77,12 @@ export class PhysicsWorld {
}
createShape(shape: TLGeoShape | TLDrawShape) {
if (shape.props.dash === "dashed") return; // Skip dashed shapes
if (isRigidbody(shape.props.color)) {
const gravity = getGravityFromColor(shape.props.color)
const rb = this.createRigidbody(shape, gravity);
console.log('creating shape');
if (!shape.meta.fixed) {
const rb = this.createRigidbody(shape, 1);
this.createCollider(shape, rb);
} else {
}
else {
this.createCollider(shape);
}
}
@ -379,7 +384,6 @@ export function usePhysicsSimulation(editor: Editor, enabled: boolean) {
useEffect(() => {
if (enabled) {
sim.current.start();
editor.selectNone();
return () => sim.current.stop();
}
}, [enabled, sim]);

View File

@ -23,7 +23,7 @@ export class HTMLShapeUtil extends ShapeUtil<HTMLShape> {
}
component(shape: HTMLShape) {
return <div dangerouslySetInnerHTML={{ __html: shape.props.html }} style={{ margin: 0 }} ></div>
return <div dangerouslySetInnerHTML={{ __html: shape.props.html }}></div>
}
indicator(shape: HTMLShape) {

61
src/utils.tsx Normal file
View File

@ -0,0 +1,61 @@
export function measureElementTextWidth(element: HTMLElement) {
// Create a temporary span element
const tempElement = document.createElement('span');
// Get the text content from the passed element
tempElement.textContent = element.textContent || element.innerText;
// Get the computed style of the passed element
const computedStyle = window.getComputedStyle(element);
// Apply relevant styles to the temporary element
tempElement.style.font = computedStyle.font;
tempElement.style.fontWeight = computedStyle.fontWeight;
tempElement.style.fontSize = computedStyle.fontSize;
tempElement.style.fontFamily = computedStyle.fontFamily;
tempElement.style.letterSpacing = computedStyle.letterSpacing;
// Ensure the temporary element is not visible in the viewport
tempElement.style.position = 'absolute';
tempElement.style.visibility = 'hidden';
tempElement.style.whiteSpace = 'nowrap'; // Prevent text from wrapping
// Append to the body to make measurements possible
document.body.appendChild(tempElement);
// Measure the width
const width = tempElement.getBoundingClientRect().width;
// Remove the temporary element from the document
document.body.removeChild(tempElement);
// Return the measured width
return width === 0 ? 10 : width;
}
// Function to gather elements info asynchronously
export async function gatherElementsInfo() {
const rootElement = document.getElementsByTagName('main')[0];
const info: any[] = [];
if (rootElement) {
for (const child of rootElement.children) {
if (['BUTTON'].includes(child.tagName)) continue;
const rect = child.getBoundingClientRect();
let w = rect.width;
if (!['P', 'UL'].includes(child.tagName)) {
w = measureElementTextWidth(child);
}
// Check if the element is centered
const computedStyle = window.getComputedStyle(child);
let x = rect.left; // Default x position
if (computedStyle.display === 'block' && computedStyle.textAlign === 'center') {
// Adjust x position for centered elements
const parentWidth = child.parentElement ? child.parentElement.getBoundingClientRect().width : 0;
x = (parentWidth - w) / 2 + window.scrollX + (child.parentElement ? child.parentElement.getBoundingClientRect().left : 0);
}
info.push({
tagName: child.tagName,
x: x,
y: rect.top,
w: w,
h: rect.height,
html: child.outerHTML
});
};
}
return info;
}