Skip to content

Commit

Permalink
Lights with target: Fix / Refactor target & lookAt logic incl. …
Browse files Browse the repository at this point in the history
…helper handling (#135)
  • Loading branch information
Vatroslav Vrbanic committed Dec 3, 2022
1 parent 8e62384 commit 0aaa5d0
Show file tree
Hide file tree
Showing 2 changed files with 274 additions and 12 deletions.
143 changes: 137 additions & 6 deletions src/lib/components/DirectionalLight.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
function on_instance_provided(): void {
if (store) {
if (light?.type === "DirectionalLight") {
//nothing
// light with `target`: remember built-in target.
light.target.userData.is_builtin_target
light.userData.builtin_target = light.target
light.userData.target_uuid = light.target.uuid
} else if (light) {
throw new Error(
`SVELTHREE > ${c_name} : provided 'light' instance has wrong type '${light.type}', should be '${c_name}'!`
Expand Down Expand Up @@ -266,6 +269,10 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
set_initial_userdata(light, self)
// light with `target`: remember built-in target.
light.target.userData.is_builtin_target
light.userData.builtin_target = light.target
light.userData.target_uuid = light.target.uuid
if (our_parent) our_parent.add(light)
light_uuid = light.uuid
Expand All @@ -282,6 +289,38 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
}
/** Set `target` of a `Light` with a `target` (`SpotLight` and `DirectionalLight`). Alternatively use `lookAt` which has the same funcionality. */
export let target: Vector3 | Parameters<Vector3["set"]> | Targetable | undefined | null = undefined
$: if (!matrix && !lookAt && light && target) {
set_target()
} else {
if (!light && target) {
console.error(`SVELTHREE > ${c_name} > Trying to set 'target' : Invalid 'light' instance!`, { light })
}
if (matrix && target) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'target' : You cannot use 'target' prop together with 'matrix' prop!`
)
}
if (lookAt && target) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'target' : You cannot use 'lookAt' & 'target' props together!`
)
}
}
function set_target(): void {
if (verbose && log_rs) console.debug(...c_rs(c_name, "target", target))
if (light && target) {
PropUtils.setLookAtFromValue(light, target)
} else {
console.error(`SVELTHREE > ${c_name} > set_target : invalid 'light' instance and / or 'target' value!`, {
light,
target
})
}
}
/** Override object's `.matrixAutoUpdate` set (*on initialzation*) by scene's `.matrixAutoUpdate` (*default is `true`*). Also: `mau` can be changed on-the-fly.*/
export let mau: boolean | undefined = undefined
$: if (light) light.matrixAutoUpdate = scene.matrixAutoUpdate
Expand Down Expand Up @@ -328,11 +367,25 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
}
/** With components / objects having a `target` property like `DirectionalLight`, `lookAt` is used to specify a **point (coordinates) in world space** and cannot be set to an `Object3D`!
* By setting `LookAt` to some value, you're basically moving the built-in 'blank' `Object3D` used as a point in space to look at.
* ☝️ _In order to update the `DirectionalLightHelper` correctly, the `target: boolean` attribute needs to be set / set to `true` (**default**)!_ */
export let lookAt: Vector3 | Parameters<Vector3["set"]> | Targetable | undefined = undefined
$: !matrix && light && lookAt ? set_lookat() : lookAt && light ? console.warn(w_sh.lookAt) : null
/** **shorthand** attribute for calling the `svelthree`-custom `lookAt` method with the provided value as argument. You can alternatively use the `target` **shorthand** attribute which has the same funcionality. */
export let lookAt: Vector3 | Parameters<Vector3["set"]> | Targetable | undefined | null = undefined
$: if (!matrix && !target && light && lookAt) {
set_lookat()
} else {
if (!light && lookAt) {
console.error(`SVELTHREE > ${c_name} > Trying to set 'lookAt' : Invalid 'light' instance!`, { light })
}
if (matrix && lookAt) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'lookAt' : You cannot use 'lookAt' prop together with 'matrix' prop!`
)
}
if (target && lookAt) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'lookAt' : You cannot use 'lookAt' & 'target' props together!`
)
}
}
function set_lookat() {
if (verbose && log_rs) console.debug(...c_rs(c_name, "lookAt", lookAt))
if (light && lookAt) {
Expand Down Expand Up @@ -456,6 +509,84 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
}
// update light helper on every render!
$: if (
light &&
helper &&
((!matrix && !!lookAt !== !!target) || (matrix && !lookAt && !target)) &&
light.target?.isObject3D &&
$svelthreeStores[sti]?.rendererComponent
) {
start_updating_helper()
} else {
reset_light_target()
stop_updating_helper()
update_light_helper()
}
// call this to remove the renderer component listener
let remove_helper_updater: (() => void) | undefined
function start_updating_helper() {
// IMPORTANT this does NOT cause a component update! (instead of using requestAnimationFrame)
// COOL! this way the helper is 100% synchronious (not 1 frame late)
if (!remove_helper_updater) {
if (verbose && log_rs) console.debug(...c_rs(c_name, "start updating light helper on 'before_render_int'!"))
remove_helper_updater = store?.rendererComponent?.$on("before_render_int", update_light_helper)
}
}
function update_light_helper(): void {
if (helper) {
if (light) {
if (light.userData.helper) {
light.userData.helper.update()
} else {
console.error(
`SVELTHREE > ${c_name} > update_light_target : Invalid 'light.userData.helper' value!`,
{
helper: light.userData.helper
}
)
}
} else {
console.error(`SVELTHREE > ${c_name} > update_light_target : Invalid 'light' value!`, {
light
})
}
}
}
function stop_updating_helper(): void {
if (remove_helper_updater) {
remove_helper_updater()
remove_helper_updater = undefined
}
}
// set light target to built-in target and remove it from it's parent
// we want to keep the last orientation.
function reset_light_target(): void {
if (light) {
if (light.userData.target_uuid !== light.userData.builtin_target.uuid) {
const builtin_target = light.userData.builtin_target as THREE.Object3D
light.target.getWorldPosition(builtin_target.position)
builtin_target.updateMatrix()
builtin_target.updateMatrixWorld()
light.target = light.userData.builtin_target
light.userData.target_uuid = light.userData.builtin_target.uuid
}
if (light.target.parent) {
light.target.parent.remove(light.target)
}
} else {
console.error(`SVELTHREE > ${c_name} > reset_light_target : Invalid 'light' value!`, {
light
})
}
}
/** Animation logic to be performed with the (three) object instance created by the component. */
export let animation: SvelthreeAnimationFunction | undefined = undefined
Expand Down
143 changes: 137 additions & 6 deletions src/lib/components/SpotLight.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
function on_instance_provided(): void {
if (store) {
if (light?.type === "SpotLight") {
//nothing
// light with `target`: remember built-in target.
light.target.userData.is_builtin_target
light.userData.builtin_target = light.target
light.userData.target_uuid = light.target.uuid
} else if (light) {
throw new Error(
`SVELTHREE > ${c_name} : provided 'light' instance has wrong type '${light.type}', should be '${c_name}'!`
Expand Down Expand Up @@ -262,6 +265,10 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
set_initial_userdata(light, self)
// light with `target`: remember built-in target.
light.target.userData.is_builtin_target
light.userData.builtin_target = light.target
light.userData.target_uuid = light.target.uuid
if (our_parent) our_parent.add(light)
light_uuid = light.uuid
Expand All @@ -278,6 +285,38 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
}
/** Set `target` of a `Light` with a `target` (`SpotLight` and `DirectionalLight`). Alternatively use `lookAt` which has the same funcionality. */
export let target: Vector3 | Parameters<Vector3["set"]> | Targetable | undefined | null = undefined
$: if (!matrix && !lookAt && light && target) {
set_target()
} else {
if (!light && target) {
console.error(`SVELTHREE > ${c_name} > Trying to set 'target' : Invalid 'light' instance!`, { light })
}
if (matrix && target) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'target' : You cannot use 'target' prop together with 'matrix' prop!`
)
}
if (lookAt && target) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'target' : You cannot use 'lookAt' & 'target' props together!`
)
}
}
function set_target(): void {
if (verbose && log_rs) console.debug(...c_rs(c_name, "target", target))
if (light && target) {
PropUtils.setLookAtFromValue(light, target)
} else {
console.error(`SVELTHREE > ${c_name} > set_target : invalid 'light' instance and / or 'target' value!`, {
light,
target
})
}
}
/** Override object's `.matrixAutoUpdate` set (*on initialzation*) by scene's `.matrixAutoUpdate` (*default is `true`*). Also: `mau` can be changed on-the-fly.*/
export let mau: boolean | undefined = undefined
$: if (light) light.matrixAutoUpdate = scene.matrixAutoUpdate
Expand Down Expand Up @@ -324,11 +363,25 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
}
/** With components / objects having a `target` property like `SpotLight`, `lookAt` is used to specify a **point (coordinates) in world space** and cannot be set to an `Object3D`!
* By setting `LookAt` to some value, you're basically moving the built-in 'blank' `Object3D` used as a point in space to look at.
* ☝️ _In order to update the `SpotLightHelper` correctly, the `target: boolean` attribute needs to be set / set to `true` (**default**)!_ */
export let lookAt: Vector3 | Parameters<Vector3["set"]> | Targetable | undefined = undefined
$: !matrix && light && lookAt ? set_lookat() : lookAt && light ? console.warn(w_sh.lookAt) : null
/** **shorthand** attribute for calling the `svelthree`-custom `lookAt` method with the provided value as argument. You can alternatively use the `target` **shorthand** attribute which has the same funcionality. */
export let lookAt: Vector3 | Parameters<Vector3["set"]> | Targetable | undefined | null = undefined
$: if (!matrix && !target && light && lookAt) {
set_lookat()
} else {
if (!light && lookAt) {
console.error(`SVELTHREE > ${c_name} > Trying to set 'lookAt' : Invalid 'light' instance!`, { light })
}
if (matrix && lookAt) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'lookAt' : You cannot use 'lookAt' prop together with 'matrix' prop!`
)
}
if (target && lookAt) {
console.error(
`SVELTHREE > ${c_name} > Trying to set 'lookAt' : You cannot use 'lookAt' & 'target' props together!`
)
}
}
function set_lookat() {
if (verbose && log_rs) console.debug(...c_rs(c_name, "lookAt", lookAt))
if (light && lookAt) {
Expand Down Expand Up @@ -532,6 +585,84 @@ svelthree uses svelte-accmod, where accessors are always `true`, regardless of `
}
}
// update light helper on every render!
$: if (
light &&
helper &&
((!matrix && !!lookAt !== !!target) || (matrix && !lookAt && !target)) &&
light.target?.isObject3D &&
$svelthreeStores[sti]?.rendererComponent
) {
start_updating_helper()
} else {
reset_light_target()
stop_updating_helper()
update_light_helper()
}
// call this to remove the renderer component listener
let remove_helper_updater: (() => void) | undefined
function start_updating_helper() {
// IMPORTANT this does NOT cause a component update! (instead of using requestAnimationFrame)
// COOL! this way the helper is 100% synchronious (not 1 frame late)
if (!remove_helper_updater) {
if (verbose && log_rs) console.debug(...c_rs(c_name, "start updating light helper on 'before_render_int'!"))
remove_helper_updater = store?.rendererComponent?.$on("before_render_int", update_light_helper)
}
}
function update_light_helper(): void {
if (helper) {
if (light) {
if (light.userData.helper) {
light.userData.helper.update()
} else {
console.error(
`SVELTHREE > ${c_name} > update_light_target : Invalid 'light.userData.helper' value!`,
{
helper: light.userData.helper
}
)
}
} else {
console.error(`SVELTHREE > ${c_name} > update_light_target : Invalid 'light' value!`, {
light
})
}
}
}
function stop_updating_helper(): void {
if (remove_helper_updater) {
remove_helper_updater()
remove_helper_updater = undefined
}
}
// set light target to built-in target and remove it from it's parent
// we want to keep the last orientation.
function reset_light_target(): void {
if (light) {
if (light.userData.target_uuid !== light.userData.builtin_target.uuid) {
const builtin_target = light.userData.builtin_target as THREE.Object3D
light.target.getWorldPosition(builtin_target.position)
builtin_target.updateMatrix()
builtin_target.updateMatrixWorld()
light.target = light.userData.builtin_target
light.userData.target_uuid = light.userData.builtin_target.uuid
}
if (light.target.parent) {
light.target.parent.remove(light.target)
}
} else {
console.error(`SVELTHREE > ${c_name} > reset_light_target : Invalid 'light' value!`, {
light
})
}
}
/** Animation logic to be performed with the (three) object instance created by the component. */
export let animation: SvelthreeAnimationFunction | undefined = undefined
Expand Down

0 comments on commit 0aaa5d0

Please sign in to comment.