Skip to content

Commit

Permalink
Merge branch 'main' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
davidgtl committed Jul 12, 2023
2 parents fcbb45d + e126533 commit 655f8e6
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 16 deletions.
9 changes: 9 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ const App = observer(({ state }) => {
<button onClick={() => state.render.invalidate()}>
render count is {state.count}
</button>
<button onClick={() => { state.render.updateCamAngle(2, 0.5) }}>
cam change
</button>
<button onClick={() => { state.render.prevCamAngle() }}>
cam prev
</button>
<button onClick={() => { state.render.nextCamAngle() }}>
cam next
</button>
<p>
Edit <code>src/App.jsx</code> and save to test HMR
</p>
Expand Down
14 changes: 14 additions & 0 deletions src/fn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

/**
* return first case[1] where case[0] is true
*/
function condShort(...cases) {
for (const c in cases) {
if (cases[c][0] === true) {
return cases[c][1]
}
}
throw new Error("No default was provided")
}

export default { condShort }
50 changes: 37 additions & 13 deletions src/state/RenderState.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { makeObservable } from "mobx"
import * as tjs from 'three'
import { clampCircular, TAU, TAU4 } from "@/mathUtils"
import { ACTION, hsTrack } from "./history"

class RenderState {

Expand Down Expand Up @@ -32,19 +33,22 @@ class RenderState {
const offT = e.target.getBoundingClientRect()
const offX = e.clientX - offT.left
const offY = e.clientY - offT.top
}
this.isOrbiting = true
this.orbitStartMousePos.set(e.screenX, e.screenY)
this.orbitStartCamAngle.copy(this.cameraAngle)
this.isOrbiting = true
this.orbitStartMousePos.set(e.screenX, e.screenY)
this.orbitStartCamAngle.copy(this.cameraAngle)

// insert sentinel action to be updated by mousemove
this.updateCamAngle.trackWith({ isCall: false })(this.cameraAngle.x, this.cameraAngle.y)
}
})

window.addEventListener('mouseup', (e) => {
this.isOrbiting = false
if (this.isOrbiting) {
this.isOrbiting = false
}
})

window.addEventListener('mousemove', (e) => {

if (this.isOrbiting) {

// cameraAngle = clamp(cameraAngleStart + mouseDelta * speed)
Expand All @@ -54,13 +58,11 @@ class RenderState {
this.cameraAngle.copy(this.orbitStartCamAngle).add(mouseDelta)

// on a sphere: TAU x = full ecuator, -TAU4 y = south pole, TAU4 y = north pole
this.cameraAngle.x = clampCircular(this.cameraAngle.x, 0, TAU)
this.cameraAngle.y = tjs.MathUtils.clamp(this.cameraAngle.y, -TAU4, TAU4)

this.updateCamera()
this.invalidate()
this.updateCamAngle.trackWith({ isOverwrite: true })(
clampCircular(this.cameraAngle.x, 0, TAU),
tjs.MathUtils.clamp(this.cameraAngle.y, -TAU4, TAU4)
)
}

})

// TODO: events: resize, wheel, touch...
Expand All @@ -84,9 +86,17 @@ class RenderState {
this.scene.add(light1)

makeObservable(this, {
render: true
render: true,
updateCamAngle: true
})

this.history = hsTrack(this, {
updateCamAngle: ACTION
})

// initialize history with current value
this.updateCamAngle.trackWith({ isCall: false })(this.cameraAngle.x, this.cameraAngle.y)

this._queueRender()
}

Expand All @@ -100,6 +110,20 @@ class RenderState {
this.camera.lookAt(this.focusPoint)
}

prevCamAngle() {
this.history.updateCamAngle.tryPrev().call()
}

nextCamAngle() {
this.history.updateCamAngle.tryNext().call()
}

updateCamAngle(x, y) {
this.cameraAngle.set(x, y)
this.updateCamera()
this.invalidate()
}

get domElement() {
return this.renderer.domElement
}
Expand Down
3 changes: 0 additions & 3 deletions src/state/RootState.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { makeObservable } from "mobx"
import RenderState from "./RenderState"
import UIState from "./UIState"

class RootState {
constructor() {

this.render = new RenderState(this)
this.ui = new UIState(this)

this.count = 0

makeObservable(this, {
Expand Down
146 changes: 146 additions & 0 deletions src/state/history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@

import fn from "@/fn.js";


const ACTION = { tag: "action" }
const VALUE = { tag: "value" }

class ActionHistory {
/*
Capture calls of an action, link them in a tree
For call-level options granularity:
action.trackWith({...options})(...args)
*/
constructor(target, func) {
this.root = {
prevs: [],
nexts: [],
}
this.func = func
this.target = target
this.pointer = this.root
}

record(args) {
var node = {
nexts: [],
prevs: [this.pointer],
args: args
}
this.pointer.nexts.push(node)
this.pointer = node
}

recordInPlace(args) {
this.pointer.args = args
}

peekNext() {
/*
return the next node
picks the latest branch of nexts
*/
return this.pointer.nexts.at(-1)
}

peekPrev() {
/*
return the next node
picks the latest branch of nexts
*/
const res = this.pointer.prevs.at(-1)
if (res === this.root) return undefined
return res
}

tryNext() {
/*
moves pointer one step forward, if possible
*/
const potentialNext = this.peekNext()
this.pointer = potentialNext ?? this.pointer
return this
}

tryPrev() {
/*
moves pointer one step back, if possible
*/
const potentialPrev = this.peekPrev()
this.pointer = potentialPrev ?? this.pointer
return this
}

call() {
if (this.pointer.args === undefined) {
throw new Error('calling ActionHistory.call() on root')
}

this.func.call(this.target, ...this.pointer.args)
}

}

function hsTrack(target, props) {
/*
props.keys() -> { propKey: History(target, target[propKey]), ... }
+ auto record on call
*/
var history = {}

for (const key in props) {
if (props[key] === ACTION) {
const func = target[key]

history[key] = new ActionHistory(target, func)

function wrapper(...args) {
history[key].record(args)
return func.call(target, ...args)
}

/*
(options, default):
isTrack: true \ -- record the arguments
isOverwrite: false / -- replace arguments at pointer
isCall: true -- call the action
isArgsFromPrev: false -- use previous arguments instead, if possible
*/
wrapper.trackWith = (options) => {

const tracking = fn.condShort(
[options.isTrack === false, (args) => { }],
[options.isOverwrite, (args) => history[key].recordInPlace(args)],
[true, (args) => history[key].record(args)]
)

const calling = fn.condShort(
[options.isCall === false, (args) => { }],
[true, (args) => func.apply(target, args)]
)

const argumenting = fn.condShort(
[options.isArgsFromPrev, (args) => history[key].peekPrev()?.args ?? args],
[true, (args) => args]
)

return (...args) => {
const currentArgs = argumenting(args)
tracking(currentArgs)
return calling(currentArgs)
}
}

target[key] = wrapper

} else {
throw new Error('Not Implemented')
}
}

return history
}

export { ACTION, VALUE, hsTrack }

0 comments on commit 655f8e6

Please sign in to comment.