Skip to content

Commit

Permalink
πŸ”Žβš—οΈ ↝ Modifying a 3js package for generating worlds on the client
Browse files Browse the repository at this point in the history
We've got some 3d planets being rendered in javascript, these are based on the initial C# algorithms in Signal-K/starsailors#4, Signal-K/starsailors-boilerplate#1

We've got some plans to generate nfts from this: Signal-K/sytizen#18. The models here are based on the C# model, we'll have an API call setup later so that they're both equivalent to each other directly.

Next step is setting up some image exports and saving user data, providing a way for users to export the 3JS graphic to their Supabase storage row. That will be commenced later on tonight.

All the fields will be set to be callable via an API & we will compare the output of different Deepnote graphs generated from `lightkurve` to provide further adjustments to either end.
One issue is that on the frontend for the generator, some of the components aren't selectable. This is because the original example components were built for a much older version of React>Next. Since these will be callable via a custom API, and the editor will be changed to be a form, that won't be an issue.

Docs available at the central issues on signal-k org

Signal-K/marketplace#19
Signal-K/marketplace#3
Signal-K/marketplace#7
Signal-K/Silfur#26 -> These will be added to posts on the image/comment sharing part of the site (and later on Lens -> team members can refer to the roadmap) so that will be routed internally.
  • Loading branch information
Gizmotronn committed Jan 24, 2023
1 parent 36527c8 commit a7e0a3b
Show file tree
Hide file tree
Showing 35 changed files with 2,727 additions and 129 deletions.
Binary file modified .DS_Store
Binary file not shown.
3 changes: 3 additions & 0 deletions components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export default function Header () {
<Link href={'https://github.com/signal-k'}>
Github
</Link>
<Link href={'/generator'}>
Explore worlds {/* Make submenu items */}
</Link>
<Link href={'/publications/create'}>
Create
</Link>
Expand Down
48 changes: 48 additions & 0 deletions generator/components/Controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState, useEffect } from 'react';

import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Tabs from 'react-bootstrap/Tabs';
import Tab from 'react-bootstrap/Tabs';

import LayerPanel from './panels/LayerPanel';
import InfoPanel from './panels/InfoPanel';

import { useStatePersisted } from '../hooks/use-state-persisted';
import { PlanetEditorState } from '../hooks/use-planet-editor-state';
import GraphicsPanel from './panels/GraphicsPanel';

const tabClasses = 'border-left border-right border-bottom';
const tabStyles = {
paddingTop: '10px',
paddingLeft: '6px',
paddingRight: '6px'
};

export default function Controls ({ planetState }: { planetState: PlanetEditorState }) {
const [tab, setTab] = useStatePersisted('world-gen:active-tab', 'planet-info-tab');
console.log(tab);

return (
<>
<Row>
<Col>
<Form autoComplete='off' data-lpignore="true">
<Tabs id='control-tabs' activeKey={tab} onSelect={setTab} className='nav-fill' transition={false}>
<Tab id='planet-info-tab' eventKey='planet-info-tab' title='Info' className={tabClasses} style={tabStyles} tabIndex="-1" >
<InfoPanel {...{ planetState }} />
</Tab>
<Tab id='layers-tab' eventKey='layers-tab' title='Layers' className={tabClasses} style={{ ...tabStyles, paddingTop: 0, paddingLeft: 0, paddingRight: 0 }} tabIndex="-1">
<LayerPanel {...{ planetState }} />
</Tab>
<Tab id='graphics-tab' eventKey='graphics-tab' title='Graphics' className={tabClasses} style={tabStyles} tabIndex="-1">
<GraphicsPanel {...{ planetState }} />
</Tab>
</Tabs>
</Form>
</Col>
</Row>
</>
);
}
175 changes: 175 additions & 0 deletions generator/components/FieldEditors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import Octicon, { Sync } from '@primer/octicons-react';
import InputGroup from 'react-bootstrap/InputGroup';
import Tooltip from 'rc-tooltip';
import Slider from 'rc-slider';
import { Vector2, Vector3 } from 'three';


import { randomSeed } from '../services/helpers';
import { SliderPicker as ColorSlider } from 'react-color';

const sliderStyle = {
height: '24px'
};

export function TextBox(props: { label: string, value: string, onChange: (value: string) => void }) {
return (
<Form.Group as={Col}>
<Form.Label><strong>{props.label}:</strong> {props.value + ''}</Form.Label>
<Form.Control type="text" value={props.value + ''} onChange={handleChange} />
</Form.Group>
);

function handleChange(e: any) {
props.onChange && props.onChange(e.target.value);
}
}

export function SeedInput(props: { label?: string, value: string, onChange: (value: string) => void }) {
return (
<Form.Group as={Col}>
<Form.Label><strong>{props.label || 'Seed'}:</strong></Form.Label>
<InputGroup>
<Form.Control type="input" value={props.value + ''} onChange={handleChange} />
<InputGroup.Append>
<Button variant="outline-secondary" title='Randomize' onClick={handleRandomization}>
<Octicon icon={Sync} />
</Button>
</InputGroup.Append>
</InputGroup>
</Form.Group>
);

function handleRandomization() {
props.onChange && props.onChange(randomSeed());
}

function handleChange(e: any) {
props.onChange && props.onChange(e.target.value);
}
}

export function ColorPicker(props: { label: string, value: string, onChange: (value: string) => void }) {

return (
<Form.Group as={Col}>
<Form.Label>{props.label}: {props.value}</Form.Label>
<ColorSlider color={props.value} onChangeComplete={handleChange} />
</Form.Group>
);

function handleChange(e: any) {
props.onChange && props.onChange(e.hex.toUpperCase());
}
}

export function NumberSlider(props: { label: string, min: number, max: number, step: number, value: number, onChange: (value: number) => void }) {
return (
<Form.Group as={Col}>
<Form.Label><strong>{props.label}:</strong> {props.value}</Form.Label>
<Slider min={props.min} max={props.max} defaultValue={props.value} step={props.step} onChange={props.onChange} />
</Form.Group>
);
}

const VECTOR_LABEL_WIDTH = 3;
export function Vector2Slider({ label, min, max, step, value, onChange }: { label: string, min: Vector2 | number, max: Vector2 | number, step?: Vector2 | number, value: Vector2, onChange: (value: Vector2) => void }) {
step = typeof step === 'undefined' ? 1 : step;

let vectorMin = typeof min === 'number' ? new Vector2(min, min) : min;
let vectorMax = typeof max === 'number' ? new Vector2(max, max) : max;
let vectorStep = typeof step === 'number' ? new Vector2(step, step) : step;

return (<Form.Group as={Col}>
<Form.Label className='font-weight-bold mb-0'>{label}:</Form.Label>
<Row>
<Col xs={VECTOR_LABEL_WIDTH}><strong>X:</strong> {value.x}</Col>
<Col className='pl-0'>
<Slider min={vectorMin.x} max={vectorMax.x} defaultValue={value.x} step={vectorStep.x} onChange={handleChange('x')} />
</Col>
</Row>
<Row>
<Col xs={VECTOR_LABEL_WIDTH}><strong>Y:</strong> {value.y}</Col>
<Col className='pl-0'>
<Slider min={vectorMin.y} max={vectorMax.y} defaultValue={value.y} step={vectorStep.y} onChange={handleChange('y')} />
</Col>
</Row>
</Form.Group>);

function handleChange(part: 'x' | 'y') {
return (newValue: number) => {
if (onChange) {
if (part === 'x') {
onChange(new Vector2(newValue, value.y));
} else {
onChange(new Vector2(value.x, newValue));
}
}
}
}
}

export function Vector3Slider({ label, min, max, step, value, onChange }: { label: string, min: Vector3 | number, max: Vector3 | number, step?: Vector3 | number, value: Vector3, onChange: (value: Vector3) => void }) {
step = typeof step === 'undefined' ? 1 : step;

let vectorMin = typeof min === 'number' ? new Vector3(min, min, min) : min;
let vectorMax = typeof max === 'number' ? new Vector3(max, max, max) : max;
let vectorStep = typeof step === 'number' ? new Vector3(step, step, step) : step;

return (<Form.Group as={Col}>
<Form.Label className='font-weight-bold mb-0'>{label}:</Form.Label>
<Row>
<Col xs={VECTOR_LABEL_WIDTH}><strong>X:</strong> {value.x}</Col>
<Col className='pl-0'>
<Slider min={vectorMin.x} max={vectorMax.x} defaultValue={value.x} step={vectorStep.x} onChange={handleChange('x')} />
</Col>
</Row>
<Row>
<Col xs={VECTOR_LABEL_WIDTH}><strong>Y:</strong> {value.y}</Col>
<Col className='pl-0'>
<Slider min={vectorMin.y} max={vectorMax.y} defaultValue={value.y} step={vectorStep.y} onChange={handleChange('y')} />
</Col>
</Row>
<Row>
<Col xs={VECTOR_LABEL_WIDTH}><strong>Z:</strong> {value.z}</Col>
<Col className='pl-0'>
<Slider min={vectorMin.z} max={vectorMax.z} defaultValue={value.z} step={vectorStep.z} onChange={handleChange('z')} />
</Col>
</Row>
</Form.Group>);

function handleChange(part: 'x' | 'y' | 'z') {
return (newValue: number) => {
if (onChange) {
switch (part) {
case 'x':
onChange(new Vector3(newValue, value.y, value.z));
break;
case 'y':
onChange(new Vector3(value.x, newValue, value.z));
break;
case 'z':
onChange(new Vector3(value.x, value.y, newValue));
break;
}
}
}
}
}

export function CheckboxInput(props: { label: string, value: boolean, onChange: (value: boolean) => void }) {

return (
<Form.Group as={Col}>
<Form.Check type='checkbox' label={props.label} checked={props.value} onChange={handleChange} />
</Form.Group>
);

function handleChange(e: any) {
props.onChange && props.onChange(e.target.checked);
}
}
52 changes: 52 additions & 0 deletions generator/components/GithubCorner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

export default function GitHubCorner() {
return (
<>
<a href="https://github.com/signal-k" target="_blank" className="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250"
style={{position: 'absolute',top: 0, border: 0, right: 0, fill: '#151513', color: '#fff', zIndex: 999}} aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style={{transformOrigin: '130px 106px'}} className="octo-arm"></path>
<path
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" className="octo-body"></path>
</svg>
</a>
<style jsx>{`
.github-corner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0)
}
20%,
60% {
transform: rotate(-25deg)
}
40%,
80% {
transform: rotate(10deg)
}
}
@media (max-width:500px) {
.github-corner:hover .octo-arm {
animation: none
}
.github-corner .octo-arm {
animation: octocat-wave 560ms ease-in-out
}
}
`}</style>
</>
)
}
13 changes: 13 additions & 0 deletions generator/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Head from "next/head";
import Container from 'react-bootstrap/Container';
import GithubCorner from './GithubCorner';

export default function Layout(props: {children: any[]}) {
return <>
<Head><title>World Generation</title></Head>
<GithubCorner />
<Container fluid>
{props.children}
</Container>
</>;
}
22 changes: 22 additions & 0 deletions generator/components/SceneDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

import { useLayoutEffect, useRef } from 'react';
import SceneManager from '../services/base-scene-manager';

export default function SceneDisplay ({sceneManager}: {sceneManager: SceneManager}) {
const canvasRef = useRef<HTMLCanvasElement>(null);

if (process.browser) {
useLayoutEffect(() => {
console.log('Starting scene...');
sceneManager.init(canvasRef.current);
sceneManager.start();

return () => {
console.log('Stopping scene...');
sceneManager.stop();
};
}, []);
}

return <canvas ref={canvasRef} style={{ width: '100%' }} />
}
15 changes: 15 additions & 0 deletions generator/components/SubPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Link from "next/link";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Layout from "./Layout";

export default function SubPage ({ header, children }: {header: string, children: any }) {
return (
<Layout>
<Row>
<Col><h1 className="display-4">Hello {header}</h1></Col>
</Row>
{children}
</Layout>
)
}
12 changes: 12 additions & 0 deletions generator/components/panels/GraphicsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CheckboxInput, NumberSlider } from "../FieldEditors";
import { PlanetEditorState } from "../../hooks/use-planet-editor-state";

export default function GraphicsPanel({ planetState }: { planetState: PlanetEditorState }) {
return (
<>
<NumberSlider label='Resolution' min={2} max={128} step={1} value={planetState.resolution.current} onChange={planetState.resolution.set} />
<NumberSlider label='Rotate' min={0} max={2} step={0.01} value={planetState.rotate.current} onChange={planetState.rotate.set} />
<CheckboxInput label='Wireframes' value={planetState.wireframes.current} onChange={planetState.wireframes.set} />
</>
);
}
33 changes: 33 additions & 0 deletions generator/components/panels/InfoPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { PlanetEditorState } from "../../hooks/use-planet-editor-state";
import { NumberSlider, TextBox, SeedInput, ColorPicker } from "../FieldEditors";
import Form from "react-bootstrap/Form";
import Col from 'react-bootstrap/Col';

export default function InfoPanel({ planetState }: { planetState: PlanetEditorState }) {
return (
<>
<Form.Row>
<Form.Group as={Col}>
<TextBox label='Name' value={planetState.name.current} onChange={planetState.name.set} />
</Form.Group>
<Form.Group as={Col}>
<SeedInput label='Seed' value={planetState.seed.current} onChange={planetState.seed.set} />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col}>
<NumberSlider label='Radius' min={0.25} max={16} step={0.05} value={planetState.radius.current} onChange={planetState.radius.set} />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col}>
<NumberSlider label='Sea Level' min={0} max={2} step={0.05} value={planetState.seaLevel.current} onChange={planetState.seaLevel.set} />
</Form.Group>
</Form.Row>



{/* <ColorPicker label='Color' value={planetState.colors.current} onChange={planetState.colors.set} /> */}
</>
);
}
Loading

0 comments on commit a7e0a3b

Please sign in to comment.