currently objects are defined
object.set(a, b, c)
but, for some reason, the lerp function goes ... 🤷♂️
object.lerp(new ObjectConstructor(a, b, c), alpha)
all that i have seen, vectors, colors, etc. the constructor is unnecessary, creates memory overhead (in a loop, class construction every frame), makes it harder to operate (dummy objects to save memory), and conflicts with the set method.
if this api would be consistent it would open crazy possibilities in the react world. it could behave like apples swiftUI basically, which would be a game changer:
function AnimatedPingPong() {
// define state
const [x, set] = useState(true)
// flip state every second
useEffect(() => void setInterval(() => set(x => !x), 1000), [])
// write out state as x swinging between -1 and 1
return (
<mesh position={[x ? -1 : 1, 0, 0]} position-lerp={0.1}>
<sphereBufferGeometry attach="geometry" />
</mesh>
}
automated animation would not be possible today, because due to the api the reconciler must know how the constructor is shaped, which it can't. it doesn't what what a color is or that the argument needs to be shaped { x: 1, y: 2, z: 3 }.
just allow both:
vector.set(1, 2, 3)
vector.lerp(1, 2, 3, 0.1)
vector.lerp(new Vector(1, 2, 3), 0.1)
color.set("red")
vector.lerp("red", 0.1)
vector.lerp(new Color("red"), 0.1)
or:
vector.set(1, 2, 3)
vector.lerp(1, 2, 3, 0.1)
vector.lerpVector(new Vector(1, 2, 3), 0.1)
color.set("red")
vector.lerp("red", 0.1)
vector.lerpColor(new Color("red"), 0.1)
could at least be made backward compatible for a couple of months by testing against props.
Our usage pattern is fairly consistent: set(...)
re-initializes an object from its primary member properties, copy(...)
re-initializes an object from another object of the same type. lerp(...)
is consistent with the latter, as are most modifier methods, so I don't quite follow the consistency argument on this.
On memory usage, we universally discourage creating objects in the render loop — but we accomplish that with an inversion of the order you're suggesting. The caller is responsible for reusing a parameter across calls:
var _v = new THREE.Vector3(...);
function hot ( x, y, z ) {
_v.set( x, y, z );
mesh.position.lerp( _v, 0.5 );
}
So this proposal doesn't inherently save memory, it shifts the responsibility for saving memory. Not saying that as an argument against your suggestion, but to clarify the effects here. I couldn't tell you whether that pattern is compatible with a reconciler, and maybe that's the core question.
Is this request specific to the lerp
method? Or would the same argument apply to Vector3's addScaledVector
, applyAxisAngle
, applyEuler
, applyMatrix3
, applyMatrix4
, clamp
, cross
, distanceTo
, divide
, dot
, max
, min
,muliply
, ... ? The application to lerp()
, specifically, doesn't seem unique. Supporting primitive values interchangeably with Math classes would be a pretty major change, library-wide. 🤔
hmmm, raw values would be a welcome addition for sure, haven't thought about the others, just the two were sticking out to me. but yes, now that you mention it, i've wished countless of times i could skip object creation when dealing with state or raw data values.
as for mem usage, this is what we're doing as well, but this is what's causing lots of boilerplate for dummy variables. the lerp function then just goes on to fish out x/y/z anyway. if it could handle raw data like set it's a one liner:
const hot = (x, y, z) => mesh.position.lerp(x, y, z, 0.5 )
cleaner, leaner, less indirection, no scope, no temporary variables. would be awesome if this would go for all mutator functions.
as for copy and set, i never understood why we need two:
v.set(x, y, z)
v.set(new Vector3(x, y, z))
v.set(otherVector)
isn't this easier to grasp?
isn't this easier to grasp?
It's ergonomic, I agree. Other possible effects though:
Some methods won't directly operate on primitive arguments, and will have to check the type and populate a temporary object to complete their work. In some cases, like parsing CSS colors, that would have performance cost.
Performance-critical functions should ideally be monomorphic. Using inconsistent types (polymorphism) can prevent V8 from optimizing that function, or cause it to throw out optimized versions of the function. See the section on The Optimizing Compiler from Performance Tips for JavaScript in V8:
Monomorphic functions and constructors can be inlined entirely (that's another reason why monomorphism is a good idea in V8).
See also, Why do monomorphic and polymorphic matter in JavaScript? and What's up with monomorphism. I'm under the impression that the advice still holds for modern browsers, but I haven't done profiling on this myself.
I see. Yes, it looked too good and easy but I didn't consider the consequences.
Could you automatically create cached versions of these objects for the sake of lerping here? If I'm understanding what you want this might help -- you wouldn't need to know the object type ahead of time if you used the types constructor:
const cache = new Map();
const hot = ( x, y, z ) => {
const cons = mesh.position.constructor;
let tmp= cache.get( cons );
if ( ! tmp ) {
tmp = new cons();
cache.set( cons, tmp );
}
cache.set( x, y, z );
mesh.position.lerp( tmp, 0.5 );
}
you're right, a constructor map with temp objects could indeed work, nice idea!
Most helpful comment
Our usage pattern is fairly consistent:
set(...)
re-initializes an object from its primary member properties,copy(...)
re-initializes an object from another object of the same type.lerp(...)
is consistent with the latter, as are most modifier methods, so I don't quite follow the consistency argument on this.On memory usage, we universally discourage creating objects in the render loop — but we accomplish that with an inversion of the order you're suggesting. The caller is responsible for reusing a parameter across calls:
So this proposal doesn't inherently save memory, it shifts the responsibility for saving memory. Not saying that as an argument against your suggestion, but to clarify the effects here. I couldn't tell you whether that pattern is compatible with a reconciler, and maybe that's the core question.
Is this request specific to the
lerp
method? Or would the same argument apply to Vector3'saddScaledVector
,applyAxisAngle
,applyEuler
,applyMatrix3
,applyMatrix4
,clamp
,cross
,distanceTo
,divide
,dot
,max
,min
,muliply
, ... ? The application tolerp()
, specifically, doesn't seem unique. Supporting primitive values interchangeably with Math classes would be a pretty major change, library-wide. 🤔